diff options
202 files changed, 1857 insertions, 465 deletions
@@ -134,7 +134,7 @@ gem 'acts_as_tree', '~> 2.1.0', require: 'acts_as_tree' gem 'rabl' gem 'carrierwave', '~> 1.0' -gem 'sidekiq' +gem 'sidekiq', require: ['sidekiq', 'sidekiq/web'] gem 'whenever', github: 'af83/whenever', require: false # '~> 0.9' gem 'rake' gem 'devise-async' 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 cc7570d65..adb3b4764 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -64,13 +64,13 @@ class CalendarsController < ChouetteController end def calendar_params - permitted_params = [:id, :name, :short_name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]] + permitted_params = [:id, :name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]] permitted_params << :shared if policy(Calendar).share? params.require(:calendar).permit(*permitted_params) end def sort_column - Calendar.column_names.include?(params[:sort]) ? params[:sort] : 'short_name' + Calendar.column_names.include?(params[:sort]) ? params[:sort] : 'name' end def sort_direction @@ -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/routes_controller.rb b/app/controllers/routes_controller.rb index 96a23c938..ac243c8eb 100644 --- a/app/controllers/routes_controller.rb +++ b/app/controllers/routes_controller.rb @@ -63,7 +63,8 @@ class RoutesController < ChouetteController end def duplicate - route = Chouette::Route.find(params[:id]).duplicate + source = Chouette::Route.find(params[:id]) + route = source.duplicate params[:opposite] flash[:notice] = t('routes.duplicate.success') redirect_to referential_line_path(@referential, route.line) end diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb index d0d9f652d..b2634467d 100644 --- a/app/controllers/stop_areas_controller.rb +++ b/app/controllers/stop_areas_controller.rb @@ -161,7 +161,7 @@ class StopAreasController < ChouetteController helper_method :current_referential def stop_area_params - params.require(:stop_area).permit( + fields = [ :area_type, :children_ids, :city_name, @@ -192,7 +192,8 @@ class StopAreasController < ChouetteController :kind, :status, localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS - ) + ] + permitted_custom_fields_params(Chouette::StopArea.custom_fields) # XXX filter on the workgroup + params.require(:stop_area).permit(fields) end # Fake ransack filter diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb index 41cad237d..cce14d160 100644 --- a/app/decorators/referential_decorator.rb +++ b/app/decorators/referential_decorator.rb @@ -43,7 +43,7 @@ class ReferentialDecorator < AF83::Decorator end instance_decorator.action_link policy: :edit, secondary: :show, on: :show do |l| - l.content 'Purger' + l.content t('actions.clean_up') l.href '#' l.type 'button' l.data {{ diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb index fa6367924..4a173cbb9 100644 --- a/app/decorators/route_decorator.rb +++ b/app/decorators/route_decorator.rb @@ -71,6 +71,23 @@ class RouteDecorator < AF83::Decorator end end + instance_decorator.action_link( + secondary: :show, + policy: :create_opposite, + if: ->{h.has_feature?(:create_opposite_routes) && object.opposite_route.nil?} + ) do |l| + l.content h.t('routes.create_opposite.title') + l.method :post + l.href do + h.duplicate_referential_line_route_path( + context[:referential], + context[:line], + object, + opposite: true + ) + end + end + instance_decorator.destroy_action_link do |l| l.data confirm: h.t('routes.actions.destroy_confirm') end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 356c7e69e..a0c6796ea 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -14,14 +14,17 @@ module ApplicationHelper def page_header_title(object) # Unwrap from decorator, we want to know the object model name object = object.object if object.try(:object) - + if Referential === object return object.full_name end local = "#{object.model_name.name.underscore.pluralize}.#{params[:action]}.title" + + arg = object.organisation.name if Workbench === object + if object.try(:name) - t(local, name: object.name || object.id) + t(local, name: arg || object.name || object.id) else t(local) end @@ -133,5 +136,9 @@ module ApplicationHelper url_for(:controller => "/help", :action => "show") + '/' + target end - + def permitted_custom_fields_params custom_fields + [{ + custom_field_values: custom_fields.map(&:code) + }] + end end diff --git a/app/helpers/compliance_controls_helper.rb b/app/helpers/compliance_controls_helper.rb index ba0c538c9..297ae3afa 100644 --- a/app/helpers/compliance_controls_helper.rb +++ b/app/helpers/compliance_controls_helper.rb @@ -8,4 +8,15 @@ module ComplianceControlsHelper key, pattern = key_pattern [t("compliance_controls.filters.subclasses.#{key}"), "-#{pattern}-"] end + + def compliance_control_metadatas(compliance_control) + attributes = resource.class.dynamic_attributes + attributes.push(*resource.control_attributes.keys) if resource.respond_to? :control_attributes + + {}.tap do |hash| + attributes.each do |attribute| + hash[ComplianceControl.human_attribute_name(attribute)] = resource.send(attribute) + end + end + end end diff --git a/app/helpers/multiple_selection_toolbox_helper.rb b/app/helpers/multiple_selection_toolbox_helper.rb index e0a1d2dd4..7e02c6d73 100644 --- a/app/helpers/multiple_selection_toolbox_helper.rb +++ b/app/helpers/multiple_selection_toolbox_helper.rb @@ -20,7 +20,7 @@ module MultipleSelectionToolboxHelper data: { path: delete_path, # #5206 Missing Translations - confirm: 'Etes-vous sûr(e) de vouloir effectuer cette action ?' + confirm: t('actions.are_you_sure') }, title: t("actions.#{action}") ) do @@ -34,7 +34,7 @@ module MultipleSelectionToolboxHelper label = content_tag( :span, - ("<span>0</span> élément(s) sélectionné(s)").html_safe, + ("<span>0</span> #{t('table_builders.selected_elements')}").html_safe, class: 'info-msg' ) 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/helpers/save_button.js b/app/javascript/helpers/save_button.js index 7e0bd5bbe..af5ee54a8 100644 --- a/app/javascript/helpers/save_button.js +++ b/app/javascript/helpers/save_button.js @@ -35,7 +35,7 @@ export default class SaveButton extends Component{ this.props.editMode ? this.submitForm() : this.props.onEnterEditMode() }} > - {this.props.editMode ? "Valider" : "Editer"} + {this.props.editMode ? I18n.t('actions.submit') : I18n.t('actions.edit')} </button> </div> </form> diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js index b90908264..666157ea4 100644 --- a/app/javascript/journey_patterns/actions/index.js +++ b/app/javascript/journey_patterns/actions/index.js @@ -220,7 +220,16 @@ const actions = { }) }, fetchRouteCosts: (dispatch, key, index) => { - if (actions.routeCostsCache) { + if(actions.fetchingRouteCosts){ + // A request is already sent, wait for it + if(!actions.waitingForRouteCosts){ + actions.waitingForRouteCosts = [] + } + actions.waitingForRouteCosts.push([key, index]) + return + } + + if(actions.routeCostsCache) { // Dispatch asynchronously to prevent warning when // this executes during `render()` requestAnimationFrame(() => { @@ -231,6 +240,7 @@ const actions = { )) }) } else { + actions.fetchingRouteCosts = true fetch(window.routeCostsUrl, { credentials: 'same-origin', }).then(response => { @@ -240,6 +250,12 @@ const actions = { actions.routeCostsCache = costs dispatch(actions.receiveRouteCosts(costs, key, index)) + if(actions.waitingForRouteCosts){ + actions.waitingForRouteCosts.map(function(item, i) { + dispatch(actions.receiveRouteCosts(costs, item[0], item[1])) + }) + } + actions.fetchingRouteCosts = false }) } }, 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 15d8b6db4..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> ) } @@ -80,7 +80,6 @@ export default class JourneyPattern extends Component{ let from = null this.props.value.stop_points.map((stopPoint, i) =>{ let usePoint = stopPoint.checked - console.log(stopPoint) if(onlyCommercial && (i == 0 || i == this.props.value.stop_points.length - 1) && stopPoint.kind == "non_commercial"){ usePoint = false } @@ -131,23 +130,20 @@ export default class JourneyPattern extends Component{ } } - componentWillUpdate() { - [this.totalTime, this.totalDistance] = this.totals(false) - } - render() { this.previousSpId = undefined + let [totalTime, totalDistance] = this.totals(false) let [commercialTotalTime, commercialTotalDistance] = this.totals(true) return ( <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '') + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}> <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>{this.totalDistance}</span> - <span className="col-md-6"><i className="fa fa-clock-o"></i>{this.totalTime}</span> + <span className="col-md-6"><i className="fa fa-arrows-h"></i>{totalDistance}</span> + <span className="col-md-6"><i className="fa fa-clock-o"></i>{totalTime}</span> </div> } {this.hasFeature('costs_in_journey_patterns') && @@ -171,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'}> @@ -187,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 b787bec97..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, @@ -39,8 +40,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/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..7c79dfe52 100644 --- a/app/javascript/time_tables/actions/index.js +++ b/app/javascript/time_tables/actions/index.js @@ -4,7 +4,6 @@ import reject from 'lodash/reject' import some from 'lodash/some' import every from 'lodash/every' import clone from '../../helpers/clone' -const I18n = clone(window, "I18n") const actions = { weekDays: (index) => { @@ -307,10 +306,11 @@ const actions = { }) }, errorModalKey: (periods, dayTypes) => { - const withoutPeriodsWithDaysTypes = reject(periods, 'deleted').length == 0 && some(dayTypes) && "withoutPeriodsWithDaysTypes" + // const withoutPeriodsWithDaysTypes = reject(periods, 'deleted').length == 0 && some(dayTypes) && "withoutPeriodsWithDaysTypes" const withPeriodsWithoutDayTypes = reject(periods, 'deleted').length > 0 && every(dayTypes, dt => dt == false) && "withPeriodsWithoutDayTypes" - return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes") + // return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes") + return withPeriodsWithoutDayTypes }, errorModalMessage: (errorKey) => { diff --git a/app/javascript/time_tables/components/SaveTimetable.js b/app/javascript/time_tables/components/SaveTimetable.js index 704590abd..468f1773b 100644 --- a/app/javascript/time_tables/components/SaveTimetable.js +++ b/app/javascript/time_tables/components/SaveTimetable.js @@ -26,7 +26,7 @@ export default class SaveTimetable extends Component{ } }} > - Valider + {I18n.t('actions.submit')} </button> </form> </div> 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 d3c01f154..e4e266c79 100644 --- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js @@ -96,7 +96,7 @@ export default class EditVehicleJourney extends Component { <div className='row'> <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'> <div className='form-group'> - <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'company')}</label> + <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'published_journey_identifier')}</label> <input type='text' ref='published_journey_identifier' @@ -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/calendar.rb b/app/models/calendar.rb index 84b569ab4..32eedf9ea 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -12,8 +12,7 @@ class Calendar < ActiveRecord::Base belongs_to :organisation belongs_to :workgroup - validates_presence_of :name, :short_name, :organisation, :workgroup - validates_uniqueness_of :short_name + validates_presence_of :name, :organisation, :workgroup has_many :time_tables diff --git a/app/models/calendar_observer.rb b/app/models/calendar_observer.rb index c81addff4..0414d01d2 100644 --- a/app/models/calendar_observer.rb +++ b/app/models/calendar_observer.rb @@ -3,7 +3,7 @@ class CalendarObserver < ActiveRecord::Observer def after_update calendar return unless calendar.shared - User.with_organisation.each do |user| + User.from_workgroup(calendar.workgroup_id).each do |user| MailerJob.perform_later('CalendarMailer', 'updated', [calendar.id, user.id]) end end @@ -11,7 +11,7 @@ class CalendarObserver < ActiveRecord::Observer def after_create calendar return unless calendar.shared - User.with_organisation.each do |user| + User.from_workgroup(calendar.workgroup_id).each do |user| MailerJob.perform_later('CalendarMailer', 'created', [calendar.id, user.id]) end end 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/route.rb b/app/models/chouette/route.rb index 13288bc6b..65947c392 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -68,28 +68,34 @@ module Chouette validates_presence_of :published_name validates_presence_of :line validates :wayback, inclusion: { in: self.wayback.values } - after_save :calculate_costs!, if: ->() { TomTom.enabled? } - - def duplicate + + def duplicate opposite=false overrides = { 'opposite_route_id' => nil, 'name' => I18n.t('activerecord.copy', name: self.name) } + keys_for_create = attributes.keys - %w{id objectid created_at updated_at} atts_for_create = attributes - .slice!(*%w{id objectid created_at updated_at}) + .slice(*keys_for_create) .merge(overrides) + if opposite + atts_for_create[:wayback] = self.opposite_wayback + atts_for_create[:name] = I18n.t('routes.opposite', name: self.name) + atts_for_create[:published_name] = atts_for_create[:name] + atts_for_create[:opposite_route_id] = self.id + end new_route = self.class.create!(atts_for_create) - duplicate_stop_points(for_route: new_route) + duplicate_stop_points(for_route: new_route, opposite: opposite) new_route end - def duplicate_stop_points(for_route:) - stop_points.each(&duplicate_stop_point(for_route: for_route)) + def duplicate_stop_points(for_route:, opposite: false) + stop_points.each(&duplicate_stop_point(for_route: for_route, opposite: opposite)) end - def duplicate_stop_point(for_route:) + def duplicate_stop_point(for_route:, opposite: false) -> stop_point do - stop_point.duplicate(for_route: for_route) + stop_point.duplicate(for_route: for_route, opposite: opposite) end end diff --git a/app/models/chouette/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 6b363cd93..da2da998a 100644 --- a/app/models/chouette/stop_point.rb +++ b/app/models/chouette/stop_point.rb @@ -39,11 +39,12 @@ module Chouette end end - def duplicate(for_route:) + def duplicate(for_route:, opposite: false) keys_for_create = attributes.keys - %w{id objectid created_at updated_at} atts_for_create = attributes .slice(*keys_for_create) .merge('route_id' => for_route.id) + atts_for_create["position"] = self.route.stop_points.size - atts_for_create["position"] if opposite self.class.create!(atts_for_create) end diff --git a/app/models/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..017f496a8 100644 --- a/app/models/concerns/custom_fields_support.rb +++ b/app/models/concerns/custom_fields_support.rb @@ -3,22 +3,51 @@ module CustomFieldsSupport included do validate :custom_fields_values_are_valid + after_initialize :initialize_custom_fields - def self.custom_fields - CustomField.where(resource_type: self.name.split("::").last) + def self.custom_fields workgroup=:all + fields = CustomField.where(resource_type: self.name.split("::").last) + fields = fields.where(workgroup_id: workgroup&.id) if workgroup != :all + fields end - def custom_fields - CustomField::Collection.new self + def method_missing method_name, *args + if method_name =~ /custom_field_*/ && method_name.to_sym != :custom_field_values && !@custom_fields_initialized + initialize_custom_fields + send method_name, *args + else + super method_name, *args + end + end + + def custom_fields workgroup=:all + CustomField::Collection.new self, workgroup + end + + def custom_field_values= vals + out = {} + custom_fields.each do |code, field| + out[code] = field.preprocess_value_for_assignment(vals.symbolize_keys[code.to_sym]) + end + write_attribute :custom_field_values, out + end + + def initialize_custom_fields + self.custom_field_values ||= {} + custom_fields(:all).values.each &:initialize_custom_field + custom_fields(:all).each do |k, v| + custom_field_values[k] ||= v.default_value + end + @custom_fields_initialized = true end def custom_field_value key - (custom_field_values || {})[key.to_s] + (custom_field_values&.stringify_keys || {})[key.to_s] end private def custom_fields_values_are_valid - custom_fields.values.all?{|cf| cf.valid?} + custom_fields(:all).values.all?{|cf| cf.valid?} end end end diff --git a/app/models/concerns/min_max_values_validation.rb b/app/models/concerns/min_max_values_validation.rb index eff779d81..a79f5ec85 100644 --- a/app/models/concerns/min_max_values_validation.rb +++ b/app/models/concerns/min_max_values_validation.rb @@ -3,11 +3,13 @@ module MinMaxValuesValidation included do validates_presence_of :minimum, :maximum + validates_numericality_of :minimum, :maximum, allow_nil: true, greater_than_or_equal_to: 0 + validates_format_of :minimum, :maximum, with: %r{\A\d+(\.\d+)?\Z} validate :min_max_values_validation end def min_max_values_validation - return true if (minimum && maximum) && (minimum.to_i < maximum.to_i) + return true if (minimum && maximum) && (minimum.to_f < maximum.to_f) errors.add(:minimum, I18n.t('compliance_controls.min_max_values', min: minimum, max: maximum)) end end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 4a840744e..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,7 +42,17 @@ class CustomField < ActiveRecord::Base @valid = false end - delegate :code, :name, :field_type, :options, to: :@custom_field + 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 def validate @valid = true @@ -53,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 @@ -60,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/generic_attribute_control/min_max.rb b/app/models/generic_attribute_control/min_max.rb index 18873b683..bab900f0e 100644 --- a/app/models/generic_attribute_control/min_max.rb +++ b/app/models/generic_attribute_control/min_max.rb @@ -1,9 +1,7 @@ module GenericAttributeControl class MinMax < ComplianceControl store_accessor :control_attributes, :minimum, :maximum, :target - - validates_numericality_of :minimum, allow_nil: true, greater_than_or_equal_to: 0 - validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 + validates :target, presence: true include MinMaxValuesValidation 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/models/user.rb b/app/models/user.rb index 31e634415..eca7ede0c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -30,6 +30,8 @@ class User < ActiveRecord::Base scope :with_organisation, -> { where.not(organisation_id: nil) } + scope :from_workgroup, ->(workgroup_id) { joins(:workbenches).where(workbenches: {workgroup_id: workgroup_id}) } + # Callback invoked by DeviseCasAuthenticable::Model#authernticate_with_cas_ticket def cas_extra_attributes=(extra_attributes) @@ -67,6 +69,10 @@ class User < ActiveRecord::Base permissions && permissions.include?(permission) end + def can_monitor_sidekiq? + has_permission?("sidekiq.monitor") + end + private # remove organisation and referentials if last user of it diff --git a/app/models/vehicle_journey_control/speed.rb b/app/models/vehicle_journey_control/speed.rb index e5e331b50..c9775e7a3 100644 --- a/app/models/vehicle_journey_control/speed.rb +++ b/app/models/vehicle_journey_control/speed.rb @@ -2,8 +2,6 @@ module VehicleJourneyControl class Speed < ComplianceControl store_accessor :control_attributes, :minimum, :maximum - validates_numericality_of :minimum, allow_nil: true, greater_than_or_equal_to: 0 - validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 include MinMaxValuesValidation def self.default_code; "3-VehicleJourney-2" end diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb index 0337a5300..4fcb6be11 100644 --- a/app/policies/route_policy.rb +++ b/app/policies/route_policy.rb @@ -20,4 +20,8 @@ class RoutePolicy < ApplicationPolicy def duplicate? create? end + + def create_opposite? + create? + end end diff --git a/app/services/route_way_cost_calculator.rb b/app/services/route_way_cost_calculator.rb index 2e30c94fc..d41a2e59a 100644 --- a/app/services/route_way_cost_calculator.rb +++ b/app/services/route_way_cost_calculator.rb @@ -5,7 +5,7 @@ class RouteWayCostCalculator def calculate! way_costs = StopAreasToWayCostsConverter.new(@route.stop_areas).convert - way_costs = TomTom.batch(way_costs) + way_costs = TomTom.matrix(way_costs) way_costs = WayCostCollectionJSONSerializer.dump(way_costs) @route.update(costs: way_costs) end diff --git a/app/uploaders/custom_field_attachment_uploader.rb b/app/uploaders/custom_field_attachment_uploader.rb new file mode 100644 index 000000000..411b65bc3 --- /dev/null +++ b/app/uploaders/custom_field_attachment_uploader.rb @@ -0,0 +1,12 @@ +class CustomFieldAttachmentUploader < CarrierWave::Uploader::Base + + storage :file + + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + + def extension_whitelist + model.send "#{mounted_as}_extension_whitelist" + end +end diff --git a/app/views/autocomplete_calendars/autocomplete.rabl b/app/views/autocomplete_calendars/autocomplete.rabl index 3a7703c53..9a91e1d69 100644 --- a/app/views/autocomplete_calendars/autocomplete.rabl +++ b/app/views/autocomplete_calendars/autocomplete.rabl @@ -1,5 +1,5 @@ collection @calendars, :object_root => false -attribute :id, :name, :short_name, :shared +attribute :id, :name, :shared node :text do |cal| "<strong>" + cal.name + " - " + cal.id.to_s + "</strong>" 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/_filters.html.slim b/app/views/calendars/_filters.html.slim index 8bfe1974e..d7e2a927e 100644 --- a/app/views/calendars/_filters.html.slim +++ b/app/views/calendars/_filters.html.slim @@ -1,7 +1,7 @@ = search_form_for @q, url: workgroup_calendars_path(@workgroup), builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f| .ffg-row .input-group.search_bar class=filter_item_class(params[:q], :name_or_short_name_cont) - = f.search_field :name_or_short_name_cont, class: 'form-control', placeholder: 'Indiquez un nom/nom court de calendrier...' + = f.search_field :name_cont, class: 'form-control', placeholder: I18n.t('calendars.filters.name_cont') span.input-group-btn button.btn.btn-default#search_btn type='submit' span.fa.fa-search @@ -10,13 +10,13 @@ .form-group.togglable class=filter_item_class(params[:q], :shared_true) = f.label Calendar.human_attribute_name(:shared), required: false, class: 'control-label' .form-group.checkbox_list - = f.input :shared_true, as: :boolean, label: ("<span>Oui</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' } - = f.input :shared_false, as: :boolean, label: ("<span>Non</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' } + = f.input :shared_true, as: :boolean, label: ("<span>#{I18n.t('yes')}</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' } + = f.input :shared_false, as: :boolean, label: ("<span>#{I18n.t('no')}</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' } .form-group class=filter_item_class(params[:q], :contains_date) = f.label Calendar.human_attribute_name(:date), class: 'control-label' = f.input :contains_date, as: :date, label: false, wrapper_html: { class: 'date smart_date' }, class: 'form-control', include_blank: true .actions - = link_to 'Effacer', workgroup_calendars_path(@workgroup), class: 'btn btn-link' - = f.submit 'Filtrer', id: 'calendar_filter_btn', class: 'btn btn-default' + = link_to I18n.t('actions.erase'), workgroup_calendars_path(@workgroup), class: 'btn btn-link' + = f.submit I18n.t('actions.filter'), id: 'calendar_filter_btn', class: 'btn btn-default' diff --git a/app/views/calendars/_form_simple.html.slim b/app/views/calendars/_form_simple.html.slim index ba18c765b..a87a3dab5 100644 --- a/app/views/calendars/_form_simple.html.slim +++ b/app/views/calendars/_form_simple.html.slim @@ -4,8 +4,7 @@ .row .col-lg-12 = f.input :name - = f.input :short_name - + - if policy(@calendar).share? .form-group.has_switch = f.label :shared, class: 'col-sm-4 col-xs-5 control-label' @@ -33,24 +32,24 @@ .separator - .row - .col-lg-12 - .subform - .nested-head - .wrapper - div - .form-group - label.control-label - = t('simple_form.labels.calendar.ranges.begin') - div - .form-group - label.control-label - = t('simple_form.labels.calendar.ranges.end') - div - - = f.simple_fields_for :periods do |period| - = render 'period_fields', f: period - .links.nested-linker - = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary' + .row + .col-lg-12 + .subform + .nested-head + .wrapper + div + .form-group + label.control-label + = t('simple_form.labels.calendar.ranges.begin') + div + .form-group + label.control-label + = t('simple_form.labels.calendar.ranges.end') + div + + = f.simple_fields_for :periods do |period| + = render 'period_fields', f: period + .links.nested-linker + = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary' = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'calendar_form' diff --git a/app/views/calendars/index.html.slim b/app/views/calendars/index.html.slim index 0b58c0c72..2c87a6f7a 100644 --- a/app/views/calendars/index.html.slim +++ b/app/views/calendars/index.html.slim @@ -20,10 +20,6 @@ end \ ), \ TableBuilderHelper::Column.new( \ - key: :short_name, \ - attribute: 'short_name' \ - ), \ - TableBuilderHelper::Column.new( \ key: :organisation, \ attribute: Proc.new { |c| c.organisation.name } \ ), \ @@ -39,6 +35,6 @@ - unless @calendars.any? .row.mt-xs .col-lg-12 - = replacement_msg t('calendars.search_no_results') + = replacement_msg t('.search_no_results') = javascript_pack_tag 'date_filters' diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim index cec4f66a5..880db99f6 100644 --- a/app/views/calendars/show.html.slim +++ b/app/views/calendars/show.html.slim @@ -6,11 +6,10 @@ .row .col-lg-6.col-md-6.col-sm-12.col-xs-12 = definition_list t('metadatas'), - { 'Nom court' => resource.try(:short_name), - Calendar.human_attribute_name(:shared) => t("#{resource.shared}"), - 'Organisation' => resource.organisation.name, - Calendar.human_attribute_name(:dates) => resource.dates.collect{|d| l(d, format: :short)}.join(', ').html_safe, - Calendar.human_attribute_name(: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 } + { 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 } - if has_feature?('application_days_on_calendars') .row @@ -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/new.html.slim b/app/views/companies/new.html.slim index cc085ffe2..1747b8e10 100644 --- a/app/views/companies/new.html.slim +++ b/app/views/companies/new.html.slim @@ -1,4 +1,4 @@ -- breadcrumb :lines, @line_referential +- breadcrumb :companies, @line_referential .page_content .container-fluid .row 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_check_sets/show.html.slim b/app/views/compliance_check_sets/show.html.slim index b54bf6c5c..4e1a8e2f9 100644 --- a/app/views/compliance_check_sets/show.html.slim +++ b/app/views/compliance_check_sets/show.html.slim @@ -39,7 +39,7 @@ attribute: Proc.new { |n| I18n.t('compliance_check_sets.show.metrics', n.metrics.deep_symbolize_keys) } \ ), \ TableBuilderHelper::Column.new( \ - name: 'Téléchargement' , \ + key: :download , \ attribute: Proc.new { |n| '<i class="fa fa-download" aria-hidden="true"></i>'.html_safe }, \ sortable: false, \ link_to: lambda do |compliance_check_resource| \ diff --git a/app/views/compliance_checks/show.html.slim b/app/views/compliance_checks/show.html.slim index 3b3861e0c..535fce67d 100644 --- a/app/views/compliance_checks/show.html.slim +++ b/app/views/compliance_checks/show.html.slim @@ -9,5 +9,5 @@ = render partial: "shared/controls/metadatas" - if resource.compliance_check_block = definition_list t('compliance_controls.show.metadatas.compliance_check_block'), - I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{resource.compliance_check_block.transport_mode}"), - I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => resource.compliance_check_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") + ComplianceCheckBlock.tmf('transport_mode') => I18n.t("enumerize.transport_mode.#{resource.compliance_check_block.transport_mode}"), + ComplianceCheckBlock.tmf('transport_submode') => resource.compliance_check_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") 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/compliance_control_sets/_filters.html.slim b/app/views/compliance_control_sets/_filters.html.slim index 5cf282559..5f6d9e27b 100644 --- a/app/views/compliance_control_sets/_filters.html.slim +++ b/app/views/compliance_control_sets/_filters.html.slim @@ -8,7 +8,7 @@ .ffg-row .form-group.togglable class=filter_item_class(params[:q], :organisation_name_eq_any) = f.label t('activerecord.models.organisation.one'), required: false, class: 'control-label' - = f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'} + = f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, value_method: :name, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'} .form-group.togglable class=filter_item_class(params[:q], :updated_at) = f.label Import::Base.human_attribute_name(:updated_at), required: false, class: 'control-label' diff --git a/app/views/compliance_control_sets/show.html.slim b/app/views/compliance_control_sets/show.html.slim index 59100681d..729c1ce43 100644 --- a/app/views/compliance_control_sets/show.html.slim +++ b/app/views/compliance_control_sets/show.html.slim @@ -6,8 +6,8 @@ .row .col-lg-6.col-md-6.col-sm-12.col-xs-12 = definition_list t('metadatas'), - ComplianceControlSet.human_attribute_name(:name) => @compliance_control_set.name, - I18n.t('activerecord.attributes.compliance_control_set.owner_jdc') => @compliance_control_set.organisation.name + ComplianceControlSet.tmf('name') => @compliance_control_set.name, + ComplianceControlSet.tmf('owner_jdc') => @compliance_control_set.organisation.name - if params[:q].present? || @blocks_to_compliance_controls_map.any? || @direct_compliance_controls .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/imports/show.html.slim b/app/views/imports/show.html.slim index 48a4f334c..9d0a6423d 100644 --- a/app/views/imports/show.html.slim +++ b/app/views/imports/show.html.slim @@ -6,7 +6,7 @@ .container-fluid .row .col-lg-6.col-md-6.col-sm-12.col-xs-12 - = definition_list t('metadatas'), { 'Récupération des données' => '-', "Nom de l'archive" => @import.try(:file_identifier)} + = definition_list t('metadatas'), { t('.data_recovery') => '-', t('.filename') => @import.try(:file_identifier)} .row .col-lg-12 @@ -19,7 +19,7 @@ = table_builder_2 @import.children, [ \ TableBuilderHelper::Column.new( \ - name: 'Nom du jeu de données', \ + name: t('.referential_name'), \ attribute: 'name', \ sortable: false, \ link_to: lambda do |import| \ @@ -35,12 +35,12 @@ end \ ), \ TableBuilderHelper::Column.new( \ - name: 'Contrôle STIF', \ + name: t('.stif_control'), \ attribute: '', \ sortable: false, \ ), \ TableBuilderHelper::Column.new( \ - name: 'Contrôle organisation', \ + name: t('.organisation_control'), \ attribute: '', \ sortable: false, \ ) \ @@ -49,11 +49,11 @@ overhead: [ \ {}, \ { \ - title: "#{@import.children_succeedeed} jeu de données validé sur #{@import.children.count} présent(s) dans l'archive", \ + title: I18n.t('imports.show.results', count: @import.children_succeedeed, total: @import.children.count), \ width: 1, \ cls: "#{@import.import_status_css_class} full-border" \ }, { \ - title: 'Bilan des jeux de contrôles d\'import <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>', \ + title: I18n.t('imports.show.summary').html_safe, \ width: 2, \ cls: 'overheaded-default colspan="2"' \ } \ 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/referential_lines/show.html.slim b/app/views/referential_lines/show.html.slim index ef32ef6b0..91868a002 100644 --- a/app/views/referential_lines/show.html.slim +++ b/app/views/referential_lines/show.html.slim @@ -7,16 +7,16 @@ .col-lg-6.col-md-6.col-sm-12.col-xs-12 = definition_list t('metadatas'), { t('id_codif') => @line.get_objectid.short_id, - 'Activé' => (@line.deactivated? ? t('false') : t('true')), - @line.human_attribute_name(:network) => (@line.network.nil? ? t('lines.index.unset') : link_to(@line.network.name, [@referential, @line.network]) ), - @line.human_attribute_name(:company) => (@line.company.nil? ? t('lines.index.unset') : link_to(@line.company.name, [@referential, @line.company]) ), - 'Transporteur(s) secondaire(s)' => (@line.secondary_companies.nil? ? t('lines.index.unset') : @line.secondary_companies.collect(&:name).join(', ')), - 'Nom court' => @line.number, - 'Code public' => (@line.registration_number ? @line.registration_number : '-'), - @line.human_attribute_name(:transport_mode) => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), - @line.human_attribute_name(:transport_submode) => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'), - @line.human_attribute_name(:url) => (@line.url ? @line.url : '-'), - @line.human_attribute_name(:seasonal) => (@line.seasonal? ? t('true') : t('false')),} + Chouette::Line.tmf('activated') => (@line.deactivated? ? t('false') : t('true')), + Chouette::Line.tmf('network_id') => (@line.network.nil? ? t('lines.index.unset') : link_to(@line.network.name, [@referential, @line.network]) ), + Chouette::Line.tmf('company') => (@line.company.nil? ? t('lines.index.unset') : link_to(@line.company.name, [@referential, @line.company]) ), + Chouette::Line.tmf('secondary_company') => (@line.secondary_companies.nil? ? t('lines.index.unset') : @line.secondary_companies.collect(&:name).join(', ')), + Chouette::Line.tmf('registration_number') => @line.number, + Chouette::Line.tmf('published_name') => (@line.registration_number ? @line.registration_number : '-'), + Chouette::Line.tmf('transport_mode') => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), + Chouette::Line.tmf('transport_submode') => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'), + Chouette::Line.tmf('url') => (@line.url ? @line.url : '-'), + Chouette::Line.tmf('seasonal') => (@line.seasonal? ? t('true') : t('false')),} .col-lg-6.col-md-6.col-sm-12.col-xs-12 #routes_map.map.mb-lg .row @@ -53,12 +53,12 @@ attribute: 'wayback_text' \ ), \ TableBuilderHelper::Column.new( \ - name: 'Arrêt de départ', \ + name: Chouette::Route.tmf('stop_area_departure'), \ attribute: Proc.new { |r| r.try(:stop_points).first.try(:stop_area).try(:name) }, \ sortable: false \ ), \ TableBuilderHelper::Column.new( \ - name: "Arrêt d'arrivée", \ + name: Chouette::Route.tmf('stop_area_arrival'), \ attribute: Proc.new{ |r| r.try(:stop_points).last.try(:stop_area).try(:name) }, \ sortable: false \ ), \ @@ -79,7 +79,7 @@ - unless @routes.any? .row.mt-xs .col-lg-12 - = replacement_msg t('routes.search_no_results') + = replacement_msg t('routes.filters.no_results') = javascript_tag do | window.routes = "#{URI.escape(@routes.select{|r| r.wayback == :outbound}.map{|r| {name: r.name, id: r.id, stops: route_json_for_edit(r, serialize: false)}}.to_json)}" 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/_overview.html.slim b/app/views/referentials/_overview.html.slim index 6bed5f282..b3258ffd1 100644 --- a/app/views/referentials/_overview.html.slim +++ b/app/views/referentials/_overview.html.slim @@ -16,8 +16,8 @@ = f.input :transport_mode_eq_any, collection: overview.referential_lines.map(&:transport_mode).compact.uniq.sort, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} .actions - = link_to 'Effacer', url_for() + "##{overview.pagination_param_name}", class: 'btn btn-link' - = f.submit 'Filtrer', class: 'btn btn-default' + = link_to t('actions.erase'), url_for() + "##{overview.pagination_param_name}", class: 'btn btn-link' + = f.submit t('actions.filter'), class: 'btn btn-default' .time-travel .btn-group 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/routes/show.html.slim b/app/views/routes/show.html.slim index 375d7c57b..d2e750fb0 100644 --- a/app/views/routes/show.html.slim +++ b/app/views/routes/show.html.slim @@ -59,7 +59,7 @@ action: :index - else - = replacement_msg t('stop_areas.search_no_results') + = replacement_msg t('stop_areas.filters.search_no_results') = javascript_tag do | window.route = "#{URI.escape(route_json_for_edit(@route))}" diff --git a/app/views/routing_constraint_zones/_filters.html.slim b/app/views/routing_constraint_zones/_filters.html.slim index 74e299a8b..561359943 100644 --- a/app/views/routing_constraint_zones/_filters.html.slim +++ b/app/views/routing_constraint_zones/_filters.html.slim @@ -1,16 +1,16 @@ = search_form_for @q, url: referential_line_routing_constraint_zones_path(@referential, @line), class: 'form form-filter' do |f| .ffg-row .input-group.search_bar class=filter_item_class(params[:q], :name_or_objectid_cont) - = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: "Indiquez un nom d'ITL ou un ID..." + = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: t('.name_or_objectid_cont') span.input-group-btn button.btn.btn-default#search-btn type='submit' span.fa.fa-search .ffg-row .form-group class=filter_item_class(params[:q], :route_id_eq) - = f.label 'Itinéraire associé', required: false, class: 'control-label' - = f.input :route_id_eq, as: :select, collection: @line.routing_constraint_zones.pluck(:route_id).uniq, label: false, label_method: lambda { |r| @line.routing_constraint_zones.find_by(route_id: r).route_name }, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': 'Indiquez un itinéraire...' }, wrapper_html: { class: 'select2ed'} + = f.label t('.associated_route.title'), required: false, class: 'control-label' + = f.input :route_id_eq, as: :select, collection: @line.routing_constraint_zones.pluck(:route_id).uniq, label: false, label_method: lambda { |r| @line.routing_constraint_zones.find_by(route_id: r).route_name }, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('.associated_route.placeholder') }, wrapper_html: { class: 'select2ed'} .actions - = link_to 'Effacer', referential_line_routing_constraint_zones_path(@referential, @line), class: 'btn btn-link' - = f.submit 'Filtrer', class: 'btn btn-default' + = link_to t('actions.erase'), referential_line_routing_constraint_zones_path(@referential, @line), class: 'btn btn-link' + = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/routing_constraint_zones/index.html.slim b/app/views/routing_constraint_zones/index.html.slim index 2f67b467e..7e9fb12a3 100644 --- a/app/views/routing_constraint_zones/index.html.slim +++ b/app/views/routing_constraint_zones/index.html.slim @@ -13,7 +13,7 @@ = table_builder_2 @routing_constraint_zones, [ \ TableBuilderHelper::Column.new( \ - name: 'ID', \ + name: t('objectid'), \ attribute: Proc.new { |n| n.get_objectid.local_id }, \ sortable: false \ ), \ @@ -43,4 +43,4 @@ - unless @routing_constraint_zones.any? .row.mt-xs .col-lg-12 - = replacement_msg t('routing_constraint_zones.search_no_results') + = replacement_msg t('.search_no_results') diff --git a/app/views/shared/controls/_metadatas.html.slim b/app/views/shared/controls/_metadatas.html.slim index 49df06166..80f3936e6 100644 --- a/app/views/shared/controls/_metadatas.html.slim +++ b/app/views/shared/controls/_metadatas.html.slim @@ -7,9 +7,5 @@ I18n.t('activerecord.attributes.compliance_control.predicate') => resource.predicate, I18n.t('activerecord.attributes.compliance_control.prerequisite') => resource.prerequisite, }.merge( \ - {}.tap do |hash| \ - resource.class.dynamic_attributes.each do |attribute| \ - hash[ComplianceControl.human_attribute_name(attribute)] = resource.send(attribute) \ - end \ - end \ - ) + compliance_control_metadatas(resource) \ + )
\ No newline at end of file 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 d23c61394..c92fb7bae 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -1,10 +1,13 @@ - breadcrumb :vehicle_journeys, @referential, @route - content_for :page_header_title, t('vehicle_journeys.index.title', route: @route.name) -- if @route.opposite_route.present? - - content_for :page_header_content do - .row.mb-sm - .col-lg-12.text-right - = link_to(t('routes.actions.opposite_route_timetable'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary sticky-action') +- content_for :page_header_content do + .row.mb-sm + .col-lg-12.text-right + = link_to I18n.t("time_tables.index.title"), [@referential, :time_tables], class: 'btn btn-primary sticky-action', target: :blank + - if has_feature? :purchase_windows + = link_to I18n.t("purchase_windows.index.title"), [@referential, :purchase_windows], class: 'btn btn-primary sticky-action', target: :blank + - if @route.opposite_route.present? + = link_to(t('routes.actions.opposite_route_timetable'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary sticky-action') .page_content @@ -30,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/actions.en.yml b/config/locales/actions.en.yml index 04f4aca6b..c349b709f 100644 --- a/config/locales/actions.en.yml +++ b/config/locales/actions.en.yml @@ -16,7 +16,7 @@ en: unarchive: "Unarchive" clone: 'Clone' duplicate: 'Clone' - clean_up: 'Clean up' + clean_up: 'Purge' sync: 'Synchronize' combine: 'Combine' actualize: 'Actualize' diff --git a/config/locales/calendars.en.yml b/config/locales/calendars.en.yml index c3df413af..3d16e7c05 100644 --- a/config/locales/calendars.en.yml +++ b/config/locales/calendars.en.yml @@ -1,6 +1,8 @@ en: calendars: - search_no_results: 'No calendar matching your query' + filters: + name_cont: Search by name + search_no_results: 'No calendar template matching your query' days: monday: M tuesday: Tu @@ -37,7 +39,7 @@ en: all: All shared: Shared not_shared: Not shared - search_no_results: No calendar matching your query + search_no_results: No calendar templates matching your query date: Date new: title: Add a new calendar @@ -59,12 +61,11 @@ en: activerecord: models: calendar: - one: calendar - other: calendars + one: calendar template + other: calendar templates attributes: calendar: name: Name - short_name: Short name date_ranges: Date ranges dates: Dates shared: Shared diff --git a/config/locales/calendars.fr.yml b/config/locales/calendars.fr.yml index 6fd265925..8c933f168 100644 --- a/config/locales/calendars.fr.yml +++ b/config/locales/calendars.fr.yml @@ -1,6 +1,8 @@ fr: calendars: - search_no_results: 'Aucun calendrier ne correspond à votre recherche' + filters: + name_cont: 'Indiquez un nom de calendrier...' + no_results: 'Aucun calendrier ne correspond à votre recherche' days: monday: L tuesday: Ma @@ -64,7 +66,6 @@ fr: attributes: calendar: name: Nom - short_name: Nom court date_ranges: Intervalles de dates dates: Dates shared: Partagé diff --git a/config/locales/clean_ups.en.yml b/config/locales/clean_ups.en.yml index 876694592..6cbb2c453 100644 --- a/config/locales/clean_ups.en.yml +++ b/config/locales/clean_ups.en.yml @@ -5,7 +5,7 @@ en: success_jp: "%{count} journey patterns deleted" failure: "Fail when clean_up : %{error_message}" actions: - clean_up: "clean up" + clean_up: "Clean up" confirm: "Clean up will destroy time tables which ended on requested date\nand next recursively all object without any time table\nPlease confirm this action" activemodel: attributes: 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_check_resource.en.yml b/config/locales/compliance_check_resource.en.yml new file mode 100644 index 000000000..f8a31b81a --- /dev/null +++ b/config/locales/compliance_check_resource.en.yml @@ -0,0 +1,8 @@ +en: + activerecord: + attributes: + compliance_check_resource: + name: Name + status: Status + metrics: Metrics + download: Download
\ No newline at end of file diff --git a/config/locales/compliance_check_resources.fr.yml b/config/locales/compliance_check_resources.fr.yml new file mode 100644 index 000000000..0fe4b83ed --- /dev/null +++ b/config/locales/compliance_check_resources.fr.yml @@ -0,0 +1,8 @@ +fr: + activerecord: + attributes: + compliance_check_resource: + name: Nom + status: Statut + metrics: Métriques + download: Téléchargement
\ No newline at end of file diff --git a/config/locales/compliance_check_sets.en.yml b/config/locales/compliance_check_sets.en.yml index 5e8c3b24f..63708328b 100644 --- a/config/locales/compliance_check_sets.en.yml +++ b/config/locales/compliance_check_sets.en.yml @@ -20,7 +20,7 @@ en: title: Executed control report %{name} show: title: Compliance check set report - table_state: "%{lines_status} lines imported on %{lines_in_compliance_check_set} in the archive" + table_state: "%{lines_status} lines imported out of %{lines_in_compliance_check_set} in the archive" table_explanation: "These controls apply to all imported data and condition the construction of your organization's offer." table_title: Analysed lines state metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" diff --git a/config/locales/compliance_control_blocks.en.yml b/config/locales/compliance_control_blocks.en.yml index b9c01278c..0ec979549 100644 --- a/config/locales/compliance_control_blocks.en.yml +++ b/config/locales/compliance_control_blocks.en.yml @@ -1,4 +1,4 @@ -fr: +en: activerecord: models: compliance_control_block: @@ -9,6 +9,12 @@ fr: 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 @@ fr: 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_control_sets.en.yml b/config/locales/compliance_control_sets.en.yml index 10c4f5e9a..c69689390 100644 --- a/config/locales/compliance_control_sets.en.yml +++ b/config/locales/compliance_control_sets.en.yml @@ -10,6 +10,12 @@ en: new_control: Creating a Control select_types: Control Type Selection edit: Edit compliance control set + new: + title: New compliance control set %{name} + show: + title: Consult compliance control set %{name} + edit: + title: Edit compliance control set %{name} actions: new: Add edit: Edit @@ -18,6 +24,7 @@ en: add_compliance_control: Compliance Control add_compliance_control_block: Compliance Control Block destroy_confirm: Are you sure ? + loaded: Load the control filters: name: 'Enter name ...' search_no_results: 'No compliance control set found' diff --git a/config/locales/compliance_controls.en.yml b/config/locales/compliance_controls.en.yml index f7d461fdb..18069f2f7 100644 --- a/config/locales/compliance_controls.en.yml +++ b/config/locales/compliance_controls.en.yml @@ -29,6 +29,8 @@ en: title: "Add a new compliance control" edit: title: "Update compliance control" + select_type: + title: "Select a control type" actions: new: Add edit: Edit @@ -140,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}" @@ -207,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/imports.en.yml b/config/locales/imports.en.yml index d0db87fb1..c8683a2a7 100644 --- a/config/locales/imports.en.yml +++ b/config/locales/imports.en.yml @@ -26,6 +26,13 @@ en: compliance_check: "Validation report" compliance_check_of: "Validation of import: " import_of_validation: "Import of the validation" + data_recovery: Data recovery + filename: Filename + referential_name: Referential name + stif_control: STIF Control + organisation_control: Organization control + results: "%{count} validated referential(s) out of %{total} in the file" + summary: Summay of import compliance check sets <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span> compliance_check_task: "Validate Report" severities: info: "Information" diff --git a/config/locales/imports.fr.yml b/config/locales/imports.fr.yml index 40272889a..733254fa4 100644 --- a/config/locales/imports.fr.yml +++ b/config/locales/imports.fr.yml @@ -26,6 +26,13 @@ fr: compliance_check: "Test de conformité" compliance_check_of: "Validation de l'import : " import_of_validation: "L'import de la validation" + data_recorvery: Récupération des données + filename: Nom de l'archive + referential_name: Nom du référentiel + stif_control: Contrôle STIF + organisation_control: Contrôle organisation + results: "%{count} jeu(x) de données validé(s) sur %{total}" + summary: Bilan des jeux de contrôles d'import <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span> compliance_check_task: "Validation" severities: info: "Information" 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/purchase_windows.en.yml b/config/locales/purchase_windows.en.yml index 9ce05a1b9..9ff1576cd 100644 --- a/config/locales/purchase_windows.en.yml +++ b/config/locales/purchase_windows.en.yml @@ -63,7 +63,7 @@ en: one: purchase window other: purchase windows attributes: - purchase_windows: + purchase_window: name: Name date_ranges: Date ranges referential: Referential @@ -71,7 +71,7 @@ en: bounding_dates: Bounding Dates errors: models: - purchase_windows: + purchase_window: attributes: dates: date_in_date_ranges: A date can not be in Dates and in Date ranges. diff --git a/config/locales/referentials.en.yml b/config/locales/referentials.en.yml index b7483c0aa..1381d5ddd 100644 --- a/config/locales/referentials.en.yml +++ b/config/locales/referentials.en.yml @@ -3,7 +3,7 @@ en: filters: name_or_number_or_objectid: 'Search by name, number or objectid' name: 'Search by name' - line: 'Seach by associated lines' + line: 'Search by associated lines' search_no_results: 'No data space matching your query' error_period_filter: "The period filter must have valid bounding dates" index: @@ -51,7 +51,7 @@ en: overview: head: dates: Dates - lines: Lignes + lines: Lines today: Today prev_page: Prev. page next_page: Next page @@ -64,7 +64,7 @@ en: other: "data spaces" attributes: referential: - name: "Data space name" + name: "Name" status: "Status" slug: "Code" prefix: "Neptune Object Id prefix" @@ -105,7 +105,7 @@ en: created_from: 'Created from' updated_at: "Updated" created_at: "Created" - organisation: 'Organisation' + organisation: 'Organization' number_of_lines: 'No. of lines' formtastic: titles: diff --git a/config/locales/referentials.fr.yml b/config/locales/referentials.fr.yml index 53183a4c2..ec6c7c643 100644 --- a/config/locales/referentials.fr.yml +++ b/config/locales/referentials.fr.yml @@ -32,7 +32,7 @@ fr: title: 'Dupliquer un jeu de données' submit: "Valider" select_compliance_control_set: - title: "Sélection du jeu de contôle" + title: "Sélection du jeu de contôles" actions: new: "Créer un jeu de données" destroy_confirm: "Etes vous sûr de vouloir supprimer ce jeu de données ?" diff --git a/config/locales/routes.en.yml b/config/locales/routes.en.yml index bd8358bdd..58869b895 100644 --- a/config/locales/routes.en.yml +++ b/config/locales/routes.en.yml @@ -1,6 +1,8 @@ en: routes: - search_no_results: "No route matching your query" + filters: + placeholder: Search by name or ID + no_results: "No route matching your query" actions: new: "Add a new route" edit: "Edit this route" @@ -55,7 +57,7 @@ en: for_boarding: "Boarding" for_alighting: "Alighting" duplicate: - title: "Duplicate route" + title: "Clone route" success: "Route cloned with success" route: no_journey_pattern: "No Journey pattern" @@ -82,7 +84,7 @@ en: number: "Number" direction: "Direction" wayback: "Direction" - stop_points: "Nb Stop points" + stop_points: "Nb Stop areas" opposite_route: "Reversed route" opposite_route_id: "Reversed route" objectid: "Neptune identifier" @@ -91,6 +93,8 @@ en: updated_at: Updated at creator_id: "Created by" no_journey_pattern: "No journey pattern" + stop_area_departure: Stop area departure + stop_area_arrival: Stop area arrival formtastic: titles: route: diff --git a/config/locales/routes.fr.yml b/config/locales/routes.fr.yml index c08e64cfd..9539a2ee3 100644 --- a/config/locales/routes.fr.yml +++ b/config/locales/routes.fr.yml @@ -1,6 +1,8 @@ fr: routes: - search_no_results: "Aucun itinéraire ne correspond à votre recherche" + filters: + placeholder: Indiquez un nom d'itinéraire ou un ID... + no_results: "Aucun itinéraire ne correspond à votre recherche" actions: new: "Ajouter un itinéraire" edit: "Editer cet itinéraire" @@ -14,6 +16,7 @@ fr: add_stop_point: "Ajouter un arrêt" new_stop_point: "Créer un arrêt pour l'ajouter" opposite_route_timetable: "Horaires retour" + opposite: "%{name} (retour)" new: title: "Ajouter un itinéraire" edit: @@ -54,6 +57,9 @@ fr: stop_area_name: "Nom de l'arrêt" for_boarding: "Montée" for_alighting: "Descente" + create_opposite: + title: "Créer retour" + success: "itinéraire créé avec succès" duplicate: title: "Dupliquer l'itinéraire" success: "itinéraire dupliqué avec succès" @@ -95,6 +101,8 @@ fr: updated_at: "Edité le" creator_id: "Créé par" no_journey_pattern: "Pas de mission" + stop_area_departure: Arrêt de départ + stop_area_arrival: Arrêt d'arrivée formtastic: titles: route: diff --git a/config/locales/routing_constraint_zones.en.yml b/config/locales/routing_constraint_zones.en.yml index 5675fd5db..2143ce1e1 100644 --- a/config/locales/routing_constraint_zones.en.yml +++ b/config/locales/routing_constraint_zones.en.yml @@ -26,7 +26,11 @@ en: stop_points_not_from_route: 'Stop point does not belong to the Route of this Routing constraint zone.' all_stop_points_selected: 'All stop points from route cannot be selected.' routing_constraint_zones: - search_no_results: "No ITL matches your query" + filters: + associated_route: + title: Associated route + placeholder: Put the name of a route... + name_or_objectid_cont: Search by name or ID... actions: destroy_confirm: Are you sure you want to delete this routing constraint zone? new: @@ -36,4 +40,5 @@ en: show: title: "Routing constraint zone %{name}" index: - title: "Interdictions of local trafficous" + title: "Routing constraint zones" + search_no_results: "No ITL matches your query" diff --git a/config/locales/routing_constraint_zones.fr.yml b/config/locales/routing_constraint_zones.fr.yml index d4b97fff2..b5e0fa7fb 100644 --- a/config/locales/routing_constraint_zones.fr.yml +++ b/config/locales/routing_constraint_zones.fr.yml @@ -26,7 +26,11 @@ fr: stop_points_not_from_route: "Arrêt sur séquence d'arrêts n'appartient pas à la Route de cette Zone de contrainte." all_stop_points_selected: "Une ITL ne peut pas couvrir tous les arrêts d'un itinéraire." routing_constraint_zones: - search_no_results: "Aucune ITL ne correspond à votre recherche" + filters: + associated_route: + title: Itinéraire associé + placeholder: Indiquez un itinéraire... + name_or_objectid_cont: Indiquez un nom d'ITL ou un ID... actions: destroy_confirm: Etes vous sûr de supprimer cette ITL ? new: @@ -37,3 +41,4 @@ fr: title: "Zone de contrainte %{name}" index: title: "Interdictions de trafic local" + search_no_results: "Aucune ITL ne correspond à votre recherche" diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index 9e70993aa..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,12 +18,13 @@ en: general: "General" localisation: "Localisation" accessibility: "Accessibility" + custom_fields: "Custom fields" actions: - new: "Add a new stop" - create: "Add a new stop" - edit: "Edit this stop" - update: "Edit this stop" - destroy: "Remove" + new: "Add a new stop area" + create: "Add a new stop area" + edit: "Edit stop area" + update: "Edit stop area" + destroy: "Delete stop area" activate: "Activate this stop" deactivate: "Deactivate this stop" activate_confirm: "Are you sure you want to activate this stop ?" @@ -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/table_builders.en.yml b/config/locales/table_builders.en.yml new file mode 100644 index 000000000..9ee59a1e1 --- /dev/null +++ b/config/locales/table_builders.en.yml @@ -0,0 +1,3 @@ +en: + table_builders: + selected_elements: "selected element(s)"
\ No newline at end of file diff --git a/config/locales/table_builders.fr.yml b/config/locales/table_builders.fr.yml new file mode 100644 index 000000000..3c92640fc --- /dev/null +++ b/config/locales/table_builders.fr.yml @@ -0,0 +1,3 @@ +fr: + table_builders: + selected_elements: "élement(s) sélectionné(s)"
\ No newline at end of file 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/config/locales/workbenches.en.yml b/config/locales/workbenches.en.yml index 2d9b27045..876f18766 100644 --- a/config/locales/workbenches.en.yml +++ b/config/locales/workbenches.en.yml @@ -1,7 +1,7 @@ en: workbenches: show: - title: "%{name}" + title: "Transport offer %{name}" edit: title: "Configure the workbench" update: @@ -21,7 +21,7 @@ en: see: "See the list" no_content: "No content yet." actions: - show_output: "Merge offer" + show_output: "Merge Transport Offer" affect_ccset: "Configure" activerecord: models: @@ -29,3 +29,8 @@ en: zero: "workbench" one: "workbench" other: "workbenches" + attributes: + workbench: + import_compliance_control_set_id: Space data before import + merge_compliance_control_set_id: Space data before merge + diff --git a/config/locales/workbenches.fr.yml b/config/locales/workbenches.fr.yml index 8eee1a516..f857bfcd1 100644 --- a/config/locales/workbenches.fr.yml +++ b/config/locales/workbenches.fr.yml @@ -1,7 +1,7 @@ fr: workbenches: show: - title: "%{name}" + title: "Offre de transport %{name}" edit: title: "Configurer l'espace de travail" update: @@ -29,3 +29,7 @@ fr: zero: "espace de travail" one: "espace de travail" other: "espaces de travail" + attributes: + workbench: + import_compliance_control_set_id: Jeu de données après import + merge_compliance_control_set_id: Jeu de Données avant intégration diff --git a/config/routes.rb b/config/routes.rb index 25105241a..41b345aa5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,3 @@ -require 'sidekiq/web' - ChouetteIhm::Application.routes.draw do resource :dashboard @@ -179,7 +177,9 @@ ChouetteIhm::Application.routes.draw do end end - mount Sidekiq::Web => '/sidekiq' + authenticate :user, lambda { |u| u.can_monitor_sidekiq? } do + mount Sidekiq::Web => '/sidekiq' + end namespace :api do namespace :v1 do 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/20180315152714_remove_short_name_from_calendars.rb b/db/migrate/20180315152714_remove_short_name_from_calendars.rb new file mode 100644 index 000000000..3b6759f7b --- /dev/null +++ b/db/migrate/20180315152714_remove_short_name_from_calendars.rb @@ -0,0 +1,5 @@ +class RemoveShortNameFromCalendars < ActiveRecord::Migration + def change + remove_column :calendars, :short_name, :string + 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 58c8b0779..0f6f21b83 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -15,9 +15,10 @@ ActiveRecord::Schema.define(version: 20180319043333) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - enable_extension "postgis" enable_extension "hstore" + enable_extension "postgis" enable_extension "unaccent" + enable_extension "objectid" create_table "access_links", id: :bigserial, force: :cascade do |t| t.integer "access_point_id", limit: 8 @@ -83,20 +84,18 @@ ActiveRecord::Schema.define(version: 20180319043333) do create_table "calendars", id: :bigserial, force: :cascade do |t| t.string "name" - t.string "short_name" t.daterange "date_ranges", array: true t.date "dates", array: true t.boolean "shared", default: false t.integer "organisation_id", limit: 8 t.datetime "created_at" t.datetime "updated_at" - t.integer "workgroup_id", limit: 8 t.integer "int_day_types" t.date "excluded_dates", array: true + t.integer "workgroup_id", limit: 8 end add_index "calendars", ["organisation_id"], name: "index_calendars_on_organisation_id", using: :btree - add_index "calendars", ["short_name"], name: "index_calendars_on_short_name", unique: true, using: :btree add_index "calendars", ["workgroup_id"], name: "index_calendars_on_workgroup_id", using: :btree create_table "clean_up_results", id: :bigserial, force: :cascade do |t| @@ -119,6 +118,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do t.datetime "updated_at" t.date "end_date" t.string "date_type" + t.string "mode" end add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree @@ -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 @@ -299,18 +300,58 @@ ActiveRecord::Schema.define(version: 20180319043333) do add_index "custom_fields", ["resource_type"], name: "index_custom_fields_on_resource_type", using: :btree + create_table "export_messages", id: :bigserial, force: :cascade do |t| + t.string "criticity" + t.string "message_key" + t.hstore "message_attributes" + t.integer "export_id", limit: 8 + t.integer "resource_id", limit: 8 + t.datetime "created_at" + t.datetime "updated_at" + t.hstore "resource_attributes" + end + + add_index "export_messages", ["export_id"], name: "index_export_messages_on_export_id", using: :btree + add_index "export_messages", ["resource_id"], name: "index_export_messages_on_resource_id", using: :btree + + create_table "export_resources", id: :bigserial, force: :cascade do |t| + t.integer "export_id", limit: 8 + t.string "status" + t.datetime "created_at" + t.datetime "updated_at" + t.string "resource_type" + t.string "reference" + t.string "name" + t.hstore "metrics" + end + + add_index "export_resources", ["export_id"], name: "index_export_resources_on_export_id", using: :btree + create_table "exports", id: :bigserial, force: :cascade do |t| - t.integer "referential_id", limit: 8 t.string "status" - t.string "type" - t.string "options" + t.string "current_step_id" + t.float "current_step_progress" + t.integer "workbench_id", limit: 8 + t.integer "referential_id", limit: 8 + t.string "name" t.datetime "created_at" t.datetime "updated_at" - t.string "references_type" - t.string "reference_ids" + t.string "file" + t.datetime "started_at" + t.datetime "ended_at" + t.string "token_upload" + t.string "type" + t.integer "parent_id", limit: 8 + t.string "parent_type" + t.datetime "notified_parent_at" + t.integer "current_step", default: 0 + t.integer "total_steps", default: 0 + t.string "creator" + t.hstore "options" end add_index "exports", ["referential_id"], name: "index_exports_on_referential_id", using: :btree + add_index "exports", ["workbench_id"], name: "index_exports_on_workbench_id", using: :btree create_table "facilities", id: :bigserial, force: :cascade do |t| t.integer "stop_area_id", limit: 8 @@ -766,6 +807,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do t.datetime "created_at" t.datetime "updated_at" t.string "objectid_format" + t.string "registration_number_format" end create_table "stop_areas", id: :bigserial, force: :cascade do |t| @@ -802,6 +844,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do t.string "kind" t.jsonb "localized_names" t.datetime "confirmed_at" + 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 3a67497ec..e2b89fc26 100644 --- a/spec/controllers/exports_controller_spec.rb +++ b/spec/controllers/exports_controller_spec.rb @@ -2,7 +2,7 @@ RSpec.describe ExportsController, :type => :controller do login_user let(:workbench) { create :workbench } - let(:export) { create(:netex_export, workbench: workbench) } + let(:export) { create(:netex_export, workbench: workbench, referential: first_referential) } describe 'GET #new' do it 'should be successful if authorized' do @@ -21,7 +21,7 @@ RSpec.describe ExportsController, :type => :controller do let(:params){ {name: "foo"} } let(:request){ post :create, workbench_id: workbench.id, export: params } it 'should create no objects' do - expect{request}.to_not change{Export::Base.count} + expect{request}.to_not change{Export::Netex.count} end context "with full params" do @@ -29,11 +29,12 @@ RSpec.describe ExportsController, :type => :controller do name: "foo", type: "Export::Netex", duration: 12, - export_type: :line + export_type: :line, + referential_id: first_referential.id }} it 'should be successful' do - expect{request}.to change{Export::Base.count}.by(1) + expect{request}.to change{Export::Netex.count}.by(1) end it "displays a flash message" do @@ -51,7 +52,7 @@ RSpec.describe ExportsController, :type => :controller do }} it 'should be unsuccessful' do - expect{request}.to change{Export::Base.count}.by(0) + expect{request}.to change{Export::Netex.count}.by(0) end end @@ -59,11 +60,12 @@ RSpec.describe ExportsController, :type => :controller do let(:params){{ name: "foo", type: "Export::Workgroup", - duration: 90 + duration: 90, + referential_id: first_referential.id }} it 'should be successful' do - expect{request}.to change{Export::Base.count}.by(1) + expect{request}.to change{Export::Workgroup.count}.by(1) end end @@ -83,7 +85,7 @@ RSpec.describe ExportsController, :type => :controller do context "with the token" do it 'should be successful' do post :upload, workbench_id: workbench.id, id: export.id, token: export.token_upload - expect(response).to be_redirect + expect(response).to be_success end end diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb index e4dc6bc23..a001a942d 100644 --- a/spec/controllers/routes_controller_spec.rb +++ b/spec/controllers/routes_controller_spec.rb @@ -83,6 +83,42 @@ RSpec.describe RoutesController, type: :controller do expect(Chouette::Route.last.name).to eq(I18n.t('activerecord.copy', name: route.name)) expect(Chouette::Route.last.published_name).to eq(route.published_name) + expect(Chouette::Route.last.stop_area_ids).to eq route.stop_area_ids + end + + context "when opposite = true" do + it "creates a new route on the opposite way " do + expect do + post :duplicate, + referential_id: route.line.line_referential_id, + line_id: route.line_id, + id: route.id, + opposite: TRUE + end.to change { Chouette::Route.count }.by(1) + + expect(Chouette::Route.last.name).to eq(I18n.t('routes.opposite', name: route.name)) + expect(Chouette::Route.last.published_name).to eq(Chouette::Route.last.name) + expect(Chouette::Route.last.opposite_route).to eq(route) + expect(Chouette::Route.last.stop_area_ids).to eq route.stop_area_ids.reverse + end + end + + context "on a duplicated route" do + let!(:duplicated){ route.duplicate } + it "creates a new route on the opposite way " do + expect do + post :duplicate, + referential_id: duplicated.line.line_referential_id, + line_id: duplicated.line_id, + id: duplicated.id, + opposite: TRUE + end.to change { Chouette::Route.count }.by(1) + + expect(Chouette::Route.last.name).to eq(I18n.t('routes.opposite', name: duplicated.name)) + expect(Chouette::Route.last.published_name).to eq(Chouette::Route.last.name) + expect(Chouette::Route.last.opposite_route).to eq(duplicated) + expect(Chouette::Route.last.stop_area_ids).to eq duplicated.stop_area_ids.reverse + end end end end diff --git a/spec/factories/calendars.rb b/spec/factories/calendars.rb index d9fd242d1..d78f230c6 100644 --- a/spec/factories/calendars.rb +++ b/spec/factories/calendars.rb @@ -1,7 +1,6 @@ FactoryGirl.define do factory :calendar do sequence(:name) { |n| "Calendar #{n}" } - sequence(:short_name) { |n| "Cal #{n}" } date_ranges { [generate(:date_range)] } sequence(:dates) { |n| [ Date.yesterday - n, Date.yesterday - 2*n ] } shared false @@ -14,4 +13,3 @@ FactoryGirl.define do date..(date+1) 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/calendar_observer_spec.rb b/spec/models/calendar_observer_spec.rb index 4fba02bef..dd7034af4 100644 --- a/spec/models/calendar_observer_spec.rb +++ b/spec/models/calendar_observer_spec.rb @@ -1,8 +1,13 @@ require 'rails_helper' RSpec.describe CalendarObserver, type: :observer do - let(:calendar) { create(:calendar, shared: true) } - let(:user) { create(:user, organisation: create(:organisation)) } + let(:workgroup_1) { create :workgroup } + let(:workgroup_2) { create :workgroup } + + let(:calendar) { create(:calendar, shared: true, workgroup_id: workgroup_1.id) } + + let(:user_1) { create(:user, organisation: create(:organisation, workbenches: [create(:workbench, workgroup_id: workgroup_1.id)] )) } + let(:user_2) { create(:user, organisation: create(:organisation, workbenches: [create(:workbench, workgroup_id: workgroup_2.id)] )) } context 'after_update' do it 'should observe calendar updates' do @@ -12,14 +17,21 @@ RSpec.describe CalendarObserver, type: :observer do it 'should schedule mailer on calendar update' do calendar.name = 'edited_name' - expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user.id] + expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user_1.id] calendar.save end it 'should not schedule mailer for none shared calendar on update' do calendar = create(:calendar, shared: false) calendar.name = 'edited_name' - expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user.id] + expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user_1.id] + calendar.save + end + + it "should only send mail to user from the same workgroup" do + calendar.name = 'edited_name' + expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user_1.id] + expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'updated', [calendar.id, user_2.id] calendar.save end end @@ -31,13 +43,13 @@ RSpec.describe CalendarObserver, type: :observer do end it 'should schedule mailer on calendar create' do - expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'created', [anything, user.id] - build(:calendar, shared: true).save + expect(MailerJob).to receive(:perform_later).with 'CalendarMailer', 'created', [anything, user_1.id] + build(:calendar, shared: true, workgroup_id: workgroup_1.id).save end it 'should not schedule mailer for none shared calendar on create' do - expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'created', [anything, user.id] - build(:calendar, shared: false).save + expect(MailerJob).to_not receive(:perform_later).with 'CalendarMailer', 'created', [anything, user_1.id] + build(:calendar, shared: false, workgroup_id: workgroup_1.id).save end end end diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb index a5c0a7471..09ac0e416 100644 --- a/spec/models/calendar_spec.rb +++ b/spec/models/calendar_spec.rb @@ -4,8 +4,6 @@ RSpec.describe Calendar, :type => :model do it { is_expected.to validate_presence_of(:organisation) } it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_presence_of(:short_name) } - it { is_expected.to validate_uniqueness_of(:short_name) } it { is_expected.to be_versioned } describe '#to_time_table' do diff --git a/spec/models/chouette/route/route_duplication_spec.rb b/spec/models/chouette/route/route_duplication_spec.rb index 8b3a948a2..47233b04e 100644 --- a/spec/models/chouette/route/route_duplication_spec.rb +++ b/spec/models/chouette/route/route_duplication_spec.rb @@ -8,9 +8,6 @@ RSpec.describe Chouette::Route do route.duplicate expect( values_for_create(Chouette::Route.last, except: %w{objectid name checksum checksum_source}) ).to eq( values_for_create( route, except: %w{objectid name checksum checksum_source} ) ) end - it 'and others cannot' do - expect{ route.duplicate name: 'YAN', line_id: 42 }.to raise_error(ArgumentError) - end it 'same associated stop_areeas' do expect( route.duplicate.stop_areas.pluck(:id) ).to eq(route.stop_areas.pluck(:id)) end diff --git a/spec/models/chouette/route/route_stop_points_spec.rb b/spec/models/chouette/route/route_stop_points_spec.rb index 03c53b4cf..af26f017a 100644 --- a/spec/models/chouette/route/route_stop_points_spec.rb +++ b/spec/models/chouette/route/route_stop_points_spec.rb @@ -78,15 +78,16 @@ RSpec.describe Chouette::Route, :type => :model do end describe "#stop_points" do + let(:first_stop_point) { subject.stop_points.first} context "#find_by_stop_area" do context "when arg is first quay id" do - let(:first_stop_point) { subject.stop_points.first} it "should return first quay" do expect(subject.stop_points.find_by_stop_area( first_stop_point.stop_area_id)).to eq( first_stop_point) end end end end + describe "#stop_areas" do let(:line){ create(:line)} let(:route_1){ create(:route, :line => line)} diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index ff457c57a..41ac5d7d0 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/support/shared_examples/compliance_control_validation.rb b/spec/support/shared_examples/compliance_control_validation.rb index b23c2984f..3a8232193 100644 --- a/spec/support/shared_examples/compliance_control_validation.rb +++ b/spec/support/shared_examples/compliance_control_validation.rb @@ -12,7 +12,25 @@ RSpec.shared_examples_for 'has min_max_values' do end end + context "is valid" do + it 'if minimum is well formatted' do + subject.minimum = "12" + expect_it.to be_valid + subject.minimum = "12.0" + expect_it.to be_valid + subject.minimum = "12.01" + expect_it.to be_valid + end + end + context "is invalid" do + it 'if minimum is not well formatted' do + subject.minimum = "AA" + expect_it.not_to be_valid + subject.minimum = "12." + expect_it.not_to be_valid + end + it 'if no value is provided' do subject.minimum = nil subject.maximum = nil 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 |
