aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/controllers/companies_controller.rb1
-rw-r--r--app/controllers/routing_constraint_zones_controller.rb17
-rw-r--r--app/controllers/stop_areas_controller.rb2
-rw-r--r--app/javascript/helpers/CustomFieldsInputs.js6
-rw-r--r--app/javascript/vehicle_journeys/actions/index.js20
-rw-r--r--app/javascript/vehicle_journeys/components/Tools.js3
-rw-r--r--app/javascript/vehicle_journeys/components/tools/ConstraintExclusionEditVehicleJourney.js182
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/ConstraintZoneSelect2.js37
-rw-r--r--app/javascript/vehicle_journeys/containers/tools/ConstraintExclusionEditVehicleJourney.js37
-rw-r--r--app/javascript/vehicle_journeys/reducers/modal.js47
-rw-r--r--app/javascript/vehicle_journeys/reducers/vehicleJourneys.js16
-rw-r--r--app/models/chouette/routing_constraint_zone.rb2
-rw-r--r--app/models/chouette/trident_active_record.rb6
-rw-r--r--app/models/chouette/vehicle_journey.rb3
-rw-r--r--app/models/concerns/custom_fields_support.rb42
-rw-r--r--app/models/concerns/line_referential_support.rb4
-rw-r--r--app/models/concerns/metadata_support.rb4
-rw-r--r--app/models/concerns/stop_area_referential_support.rb4
-rw-r--r--app/models/custom_field.rb10
-rw-r--r--app/models/referential_metadata.rb4
-rw-r--r--app/views/companies/_form.html.slim2
-rw-r--r--app/views/stop_areas/_form.html.slim4
-rw-r--r--app/views/vehicle_journeys/index.html.slim1
-rw-r--r--app/views/vehicle_journeys/show.rabl2
-rw-r--r--config/locales/referentials.en.yml2
-rw-r--r--config/locales/referentials.fr.yml2
-rw-r--r--config/locales/vehicle_journeys.en.yml5
-rw-r--r--config/locales/vehicle_journeys.fr.yml4
-rw-r--r--db/migrate/20180424125207_add_ignored_routing_contraint_zone_ids_to_vehicle_journeys.rb5
-rw-r--r--spec/models/custom_field_spec.rb43
-rw-r--r--spec/models/referential_metadata_spec.rb2
31 files changed, 472 insertions, 47 deletions
diff --git a/app/controllers/companies_controller.rb b/app/controllers/companies_controller.rb
index a09cab783..2c32ed3a5 100644
--- a/app/controllers/companies_controller.rb
+++ b/app/controllers/companies_controller.rb
@@ -37,6 +37,7 @@ class CompaniesController < ChouetteController
protected
+
def collection
scope = line_referential.companies
@q = scope.search(params[:q])
diff --git a/app/controllers/routing_constraint_zones_controller.rb b/app/controllers/routing_constraint_zones_controller.rb
index 47df211d0..886247e79 100644
--- a/app/controllers/routing_constraint_zones_controller.rb
+++ b/app/controllers/routing_constraint_zones_controller.rb
@@ -13,13 +13,16 @@ class RoutingConstraintZonesController < ChouetteController
def index
index! do |format|
- @routing_constraint_zones = RoutingConstraintZoneDecorator.decorate(
- @routing_constraint_zones,
- context: {
- referential: referential,
- line: parent
- }
- )
+ format.html do
+ @routing_constraint_zones = RoutingConstraintZoneDecorator.decorate(
+ @routing_constraint_zones,
+ context: {
+ referential: referential,
+ line: parent
+ }
+ )
+ end
+ format.json
end
end
diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb
index 76732afd7..734152c64 100644
--- a/app/controllers/stop_areas_controller.rb
+++ b/app/controllers/stop_areas_controller.rb
@@ -203,7 +203,7 @@ class StopAreasController < ChouetteController
:kind,
:status,
localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS
- ] + permitted_custom_fields_params(Chouette::StopArea.custom_fields) # XXX filter on the workgroup
+ ] + permitted_custom_fields_params(Chouette::StopArea.custom_fields(stop_area_referential.workgroup))
params.require(:stop_area).permit(fields)
end
diff --git a/app/javascript/helpers/CustomFieldsInputs.js b/app/javascript/helpers/CustomFieldsInputs.js
index abc8097d5..9547021eb 100644
--- a/app/javascript/helpers/CustomFieldsInputs.js
+++ b/app/javascript/helpers/CustomFieldsInputs.js
@@ -16,7 +16,7 @@ export default class CustomFieldsInputs extends Component {
})}
ref={'custom_fields.' + cf.code}
className='form-control'
- defaultValue={cf.value}
+ defaultValue={cf.value || cf.options.default}
disabled={this.props.disabled}
options={{
theme: 'bootstrap',
@@ -34,7 +34,7 @@ export default class CustomFieldsInputs extends Component {
ref={'custom_fields.' + cf.code}
className='form-control'
disabled={this.props.disabled}
- value={cf.value}
+ value={cf.value || cf.options.default}
onChange={(e) => {this.props.onUpdate(cf.code, e.target.value); this.forceUpdate()} }
/>
)
@@ -47,7 +47,7 @@ export default class CustomFieldsInputs extends Component {
ref={'custom_fields.' + cf.code}
className='form-control'
disabled={this.props.disabled}
- value={cf.value}
+ value={cf.value || cf.options.default}
onChange={(e) => {this.props.onUpdate(cf.code, e.target.value); this.forceUpdate()} }
/>
)
diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js
index 70d6e953a..c67f9f0cf 100644
--- a/app/javascript/vehicle_journeys/actions/index.js
+++ b/app/javascript/vehicle_journeys/actions/index.js
@@ -97,10 +97,30 @@ const actions = {
vehicleJourneys,
timetables
}),
+ editVehicleJourneyConstraintZones : (vehicleJourneys, zones) => ({
+ type: 'EDIT_VEHICLEJOURNEYS_CONSTRAINT_ZONES',
+ vehicleJourneys,
+ zones
+ }),
openPurchaseWindowsEditModal : (vehicleJourneys) => ({
type : 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL',
vehicleJourneys
}),
+ openConstraintExclusionEditModal : (vehicleJourneys) => ({
+ type : 'EDIT_CONSTRAINT_EXCLUSIONS_VEHICLEJOURNEY_MODAL',
+ vehicleJourneys
+ }),
+ selectConstraintZone: (selectedZone) =>({
+ type: 'SELECT_CONSTRAINT_ZONE_MODAL',
+ selectedZone: {
+ id: selectedZone.id,
+ name: selectedZone.text
+ }
+ }),
+ deleteConstraintZone : (constraintZone) => ({
+ type : 'DELETE_CONSTRAINT_ZONE_MODAL',
+ constraintZone
+ }),
selectPurchaseWindowsModal: (selectedItem) =>({
type: 'SELECT_PURCHASE_WINDOW_MODAL',
selectedItem
diff --git a/app/javascript/vehicle_journeys/components/Tools.js b/app/javascript/vehicle_journeys/components/Tools.js
index 22ea44283..efae6b2b5 100644
--- a/app/javascript/vehicle_journeys/components/Tools.js
+++ b/app/javascript/vehicle_journeys/components/Tools.js
@@ -9,6 +9,7 @@ import EditVehicleJourney from '../containers/tools/EditVehicleJourney'
import NotesEditVehicleJourney from '../containers/tools/NotesEditVehicleJourney'
import TimetablesEditVehicleJourney from '../containers/tools/TimetablesEditVehicleJourney'
import PurchaseWindowsEditVehicleJourney from '../containers/tools/PurchaseWindowsEditVehicleJourney'
+import ConstraintExclusionEditVehicleJourney from '../containers/tools/ConstraintExclusionEditVehicleJourney'
export default class Tools extends Component {
@@ -36,10 +37,12 @@ export default class Tools extends Component {
<DuplicateVehicleJourney disabled={!this.hasPolicy("create") || !this.hasPolicy("update") || !editMode}/>
<ShiftVehicleJourney disabled={!this.hasPolicy("update") || !editMode}/>
<EditVehicleJourney disabled={!this.hasPolicy("update")}/>
+
<TimetablesEditVehicleJourney disabled={!this.hasPolicy("update")}/>
{ this.hasFeature('purchase_windows') &&
<PurchaseWindowsEditVehicleJourney disabled={!this.hasPolicy("update")}/>
}
+ <ConstraintExclusionEditVehicleJourney disabled={!this.hasPolicy("update")}/>
<NotesEditVehicleJourney disabled={!this.hasPolicy("update")}/>
<DeleteVehicleJourneys disabled={!this.hasPolicy("destroy") || !editMode}/>
</ul>
diff --git a/app/javascript/vehicle_journeys/components/tools/ConstraintExclusionEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/ConstraintExclusionEditVehicleJourney.js
new file mode 100644
index 000000000..bab77926f
--- /dev/null
+++ b/app/javascript/vehicle_journeys/components/tools/ConstraintExclusionEditVehicleJourney.js
@@ -0,0 +1,182 @@
+import _ from 'lodash'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import actions from '../../actions'
+import ConstraintZoneSelect2 from './select2s/ConstraintZoneSelect2'
+
+
+export default class ConstraintExclusionEditVehicleJourney extends Component {
+ constructor(props) {
+ super(props)
+ this.handleSubmit = this.handleSubmit.bind(this)
+ this.constraintZoneUrl = this.constraintZoneUrl.bind(this)
+ this.excluded_constraint_zones = this.excluded_constraint_zones.bind(this)
+ this.constraint_zones = null
+ }
+
+ handleSubmit() {
+ this.props.onConstraintZonesEditVehicleJourney(this.props.modal.modalProps.vehicleJourneys, this.props.modal.modalProps.selectedConstraintZones)
+ this.props.onModalClose()
+ $('#ConstraintExclusionEditVehicleJourney').modal('hide')
+ }
+
+ constraintZoneUrl(contraint_zone) {
+ return window.constraint_zones_routes + "/" + contraint_zone.id
+ }
+
+ excluded_constraint_zones() {
+ let out = []
+ this.props.modal.modalProps.selectedConstraintZones.map((id, _)=>{
+ this.constraint_zones.map((zone, _)=>{
+ if(zone.id == id){
+ out.push(zone)
+ }
+ })
+ })
+ return out
+ }
+
+ fetch_constraint_zones() {
+ let url = window.constraint_zones_routes + ".json"
+ fetch(url, {
+ credentials: 'same-origin',
+ }).then(response => {
+ return response.json()
+ }).then((json) => {
+ this.constraint_zones = []
+ json.map((item, i)=>{
+ this.constraint_zones.push(
+ _.assign({}, item, {text: item.name})
+ )
+ })
+ this.forceUpdate()
+ })
+ }
+
+ render() {
+ if(this.constraint_zones === null) {
+ this.fetch_constraint_zones()
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <li className='st_action'>
+ <button
+ type='button'
+ disabled={(actions.getSelected(this.props.vehicleJourneys).length < 1 || this.props.disabled)}
+ data-toggle='modal'
+ data-target='#ConstraintExclusionEditVehicleJourney'
+ onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))}
+ title={I18n.t('activerecord.attributes.vehicle_journey.constraint_exclusions')}
+ >
+ <span className='fa fa-ban fa-strong'></span>
+ </button>
+
+ <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='ConstraintExclusionEditVehicleJourney'>
+ <div className='modal-container'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>{I18n.t('activerecord.attributes.vehicle_journey.constraint_exclusions')}</h4>
+ <span type="button" className="close modal-close" data-dismiss="modal">&times;</span>
+ </div>
+
+ {(this.props.modal.type == 'constraint_exclusions_edit') && (
+ <form>
+ <div className='modal-body'>
+ <div className='row'>
+ <div className='col-lg-12'>
+ <div className='subform'>
+ <div className='nested-head'>
+ <div className='wrapper'>
+ <div>
+ <div className='form-group'>
+ <label className='control-label'>{this.excluded_constraint_zones().length == 0 ? I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_excluded_constraint_zones') : I18n.t('vehicle_journeys.form.excluded_constraint_zones')}</label>
+ </div>
+ </div>
+ <div></div>
+ </div>
+ </div>
+ {this.excluded_constraint_zones().map((contraint_zone, i) =>
+ <div className='nested-fields' key={i}>
+ <div className='wrapper'>
+ <div> <a href={this.constraintZoneUrl(contraint_zone)} target="_blank">
+ {contraint_zone.name}
+ </a> </div>
+ {
+ this.props.editMode &&
+ <div>
+ <a
+ href='#'
+ title='Supprimer'
+ className='fa fa-trash remove_fields'
+ style={{ height: 'auto', lineHeight: 'normal' }}
+ onClick={(e) => {
+ e.preventDefault()
+ this.props.onDeleteConstraintZone(contraint_zone)
+ }}
+ ></a>
+ </div>
+ }
+ </div>
+ </div>
+ )}
+ {
+ this.props.editMode &&
+ <div className='nested-fields'>
+ <div className='wrapper'>
+ <div>
+ <ConstraintZoneSelect2
+ data={this.constraint_zones}
+ values={this.props.modal.modalProps.constraint_exclusions}
+ placeholder={I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.constraint_zone')}
+ onSelectConstraintZone={this.props.onSelectConstraintZone}
+ />
+ </div>
+ </div>
+ </div>
+ }
+ </div>
+ </div>
+ </div>
+ </div>
+ {
+ this.props.editMode &&
+ <div className='modal-footer'>
+ <button
+ className='btn btn-link'
+ data-dismiss='modal'
+ type='button'
+ onClick={this.props.onModalClose}
+ >
+ {I18n.t('cancel')}
+ </button>
+ <button
+ className='btn btn-primary'
+ type='button'
+ onClick={this.handleSubmit}
+ >
+ {I18n.t('actions.submit')}
+ </button>
+ </div>
+ }
+ </form>
+ )}
+
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+ConstraintExclusionEditVehicleJourney.propTypes = {
+ onOpenCalendarsEditModal: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ disabled: PropTypes.bool.isRequired
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/ConstraintZoneSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/ConstraintZoneSelect2.js
new file mode 100644
index 000000000..1caaf2401
--- /dev/null
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/ConstraintZoneSelect2.js
@@ -0,0 +1,37 @@
+import _ from 'lodash'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Select2 from 'react-select2-wrapper'
+import actions from '../../../actions'
+
+export default class BSelect4 extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+ render() {
+ return (
+ <Select2
+ data={this.props.data}
+ value={this.props.value}
+ onSelect={(e) => this.props.onSelectConstraintZone(e) }
+ multiple={false}
+ ref='constraint_zone_id'
+ options={{
+ allowClear: false,
+ theme: 'bootstrap',
+ width: '100%',
+ placeholder: this.props.placeholder,
+ language: require('./language'),
+ minimumInputLength: 1,
+ escapeMarkup: function (markup) { return markup; },
+ templateResult: formatRepo
+ }}
+ />
+ )
+ }
+}
+
+const formatRepo = (props) => {
+ if(props.text) return props.text
+}
diff --git a/app/javascript/vehicle_journeys/containers/tools/ConstraintExclusionEditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/ConstraintExclusionEditVehicleJourney.js
new file mode 100644
index 000000000..76d85dfe6
--- /dev/null
+++ b/app/javascript/vehicle_journeys/containers/tools/ConstraintExclusionEditVehicleJourney.js
@@ -0,0 +1,37 @@
+import actions from '../../actions'
+import { connect } from 'react-redux'
+import ConstraintExclusionEditVehicleJourneyComponent from '../../components/tools/ConstraintExclusionEditVehicleJourney'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ editMode: state.editMode,
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ disabled: ownProps.disabled
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ },
+ onOpenCalendarsEditModal: (vehicleJourneys) =>{
+ dispatch(actions.openConstraintExclusionEditModal(vehicleJourneys))
+ },
+ onDeleteConstraintZone: (constraint_zone) => {
+ dispatch(actions.deleteConstraintZone(constraint_zone))
+ },
+ onConstraintZonesEditVehicleJourney: (vehicleJourneys, constraint_zones) => {
+ dispatch(actions.editVehicleJourneyConstraintZones(vehicleJourneys, constraint_zones))
+ },
+ onSelectConstraintZone: (e) => {
+ dispatch(actions.selectConstraintZone(e.params.data))
+ },
+ }
+}
+
+const ConstraintExclusionEditVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(ConstraintExclusionEditVehicleJourneyComponent)
+
+export default ConstraintExclusionEditVehicleJourney
diff --git a/app/javascript/vehicle_journeys/reducers/modal.js b/app/javascript/vehicle_journeys/reducers/modal.js
index 84567ec0d..75ab2f4ca 100644
--- a/app/javascript/vehicle_journeys/reducers/modal.js
+++ b/app/javascript/vehicle_journeys/reducers/modal.js
@@ -82,6 +82,30 @@ export default function modal(state = {}, action) {
},
confirmModal: {}
}
+ case 'EDIT_CONSTRAINT_EXCLUSIONS_VEHICLEJOURNEY_MODAL':
+ var vehicleJourneys = JSON.parse(JSON.stringify(action.vehicleJourneys))
+ let uniqExclusions = []
+ vehicleJourneys.map((vj, i) => {
+ vj.ignored_routing_contraint_zone_ids.map((exclusion, j) =>{
+ let found = false
+ uniqExclusions.map((id, i)=>{
+ if(id == parseInt(exclusion)){
+ found = true
+ }
+ })
+ if(!found){
+ uniqExclusions.push(parseInt(exclusion))
+ }
+ })
+ })
+ return {
+ type: 'constraint_exclusions_edit',
+ modalProps: {
+ vehicleJourneys: vehicleJourneys,
+ selectedConstraintZones: uniqExclusions
+ },
+ confirmModal: {}
+ }
case 'SELECT_CP_EDIT_MODAL':
vehicleJourney = _.assign({}, state.modalProps.vehicleJourney, {company: action.selectedItem})
newModalProps = _.assign({}, state.modalProps, {vehicleJourney})
@@ -93,9 +117,28 @@ export default function modal(state = {}, action) {
case 'SELECT_TT_CALENDAR_MODAL':
newModalProps = _.assign({}, state.modalProps, {selectedTimetable : action.selectedItem})
return _.assign({}, state, {modalProps: newModalProps})
- case 'SELECT_PURCHASE_WINDOW_MODAL':
- newModalProps = _.assign({}, state.modalProps, {selectedPurchaseWindow : action.selectedItem})
+ case 'SELECT_CONSTRAINT_ZONE_MODAL':
+ let selectedConstraintZones = state.modalProps.selectedConstraintZones
+ let already_present = false
+ selectedConstraintZones.map((zone_id, i)=>{
+ if(zone_id == parseInt(action.selectedZone.id)){
+ already_present = true
+ }
+ })
+ if(already_present){ return state }
+ selectedConstraintZones.push(parseInt(action.selectedZone.id))
+ newModalProps = _.assign({}, state.modalProps, {selectedConstraintZones})
return _.assign({}, state, {modalProps: newModalProps})
+ case 'DELETE_CONSTRAINT_ZONE_MODAL':
+ newModalProps = JSON.parse(JSON.stringify(state.modalProps))
+ selectedConstraintZones = state.modalProps.selectedConstraintZones.slice(0)
+ selectedConstraintZones.map((zone_id, i) =>{
+ if(zone_id == parseInt(action.constraintZone.id)){
+ selectedConstraintZones.splice(i, 1)
+ }
+ })
+ newModalProps.selectedConstraintZones = selectedConstraintZones
+ return _.assign({}, state, {modalProps: newModalProps})
case 'ADD_SELECTED_TIMETABLE':
if(state.modalProps.selectedTimetable){
newModalProps = JSON.parse(JSON.stringify(state.modalProps))
diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
index b02c19a69..8432c5abe 100644
--- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
@@ -116,6 +116,7 @@ const vehicleJourney= (state = {}, action, keep) => {
footnotes: [],
time_tables: [],
purchase_windows: [],
+ ignored_routing_contraint_zone_ids: [],
vehicle_journey_at_stops: pristineVjasList,
selected: false,
deletable: false,
@@ -232,6 +233,21 @@ export default function vehicleJourneys(state = [], action) {
return vj
}
})
+ case 'EDIT_VEHICLEJOURNEYS_CONSTRAINT_ZONES':
+ let newExclusions = JSON.parse(JSON.stringify(action.zones))
+ return state.map((vj,i) =>{
+ if(vj.selected){
+ let updatedVJ = _.assign({}, vj)
+ action.vehicleJourneys.map((vjm, j) =>{
+ if(vj.objectid == vjm.objectid){
+ updatedVJ.ignored_routing_contraint_zone_ids = newExclusions
+ }
+ })
+ return updatedVJ
+ }else{
+ return vj
+ }
+ })
case 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS':
let newWindows = JSON.parse(JSON.stringify(action.purchase_windows))
return state.map((vj,i) =>{
diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb
index 2cfb60bdd..6b7c228b0 100644
--- a/app/models/chouette/routing_constraint_zone.rb
+++ b/app/models/chouette/routing_constraint_zone.rb
@@ -1,6 +1,6 @@
module Chouette
class RoutingConstraintZone < Chouette::TridentActiveRecord
- # has_metadata
+ has_metadata
include ChecksumSupport
include ObjectidSupport
diff --git a/app/models/chouette/trident_active_record.rb b/app/models/chouette/trident_active_record.rb
index 18b7bbf9b..475589e13 100644
--- a/app/models/chouette/trident_active_record.rb
+++ b/app/models/chouette/trident_active_record.rb
@@ -7,6 +7,10 @@ module Chouette
@referential ||= Referential.where(:slug => Apartment::Tenant.current).first!
end
+ def workgroup
+ referential&.workgroup
+ end
+
def hub_restricted?
referential.data_format == "hub"
end
@@ -16,4 +20,4 @@ module Chouette
end
end
-end \ No newline at end of file
+end
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index 814eeb388..b5476c210 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -299,7 +299,8 @@ module Chouette
'published_journey_identifier',
'published_journey_name',
'journey_pattern_id',
- 'company_id'
+ 'company_id',
+ 'ignored_routing_contraint_zone_ids'
).to_hash
if item['journey_pattern']
diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb
index c39dfd1fc..6e744d550 100644
--- a/app/models/concerns/custom_fields_support.rb
+++ b/app/models/concerns/custom_fields_support.rb
@@ -5,18 +5,19 @@ module CustomFieldsSupport
validate :custom_fields_values_are_valid
after_initialize :initialize_custom_fields
- def self.custom_fields workgroup=:all
+ def self.custom_fields workgroup
+ return [] unless workgroup
fields = CustomField.where(resource_type: self.name.split("::").last)
- fields = fields.where(workgroup_id: workgroup&.id) if workgroup != :all
+ fields = fields.where(workgroup_id: workgroup.id)
fields
end
- def self.custom_fields_definitions workgroup=:all
+ def self.custom_fields_definitions workgroup
Hash[*custom_fields(workgroup).map{|cf| [cf.code, cf]}.flatten]
end
def method_missing method_name, *args
- if method_name =~ /custom_field_*/ && method_name.to_sym != :custom_field_values && !@custom_fields_initialized
+ if !@custom_fields_initialized && method_name =~ /custom_field_*/ && method_name.to_sym != :custom_field_values
initialize_custom_fields
send method_name, *args
else
@@ -24,8 +25,9 @@ module CustomFieldsSupport
end
end
- def custom_fields workgroup=:all
- CustomField::Collection.new self, workgroup
+ def custom_fields deprecated_workgroup=nil
+ _workgroup = deprecated_workgroup || self.workgroup
+ CustomField::Collection.new self, _workgroup
end
def custom_fields_checksum
@@ -33,21 +35,37 @@ module CustomFieldsSupport
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])
+ if custom_fields_initialized?
+ out = {}
+ custom_fields.each do |code, field|
+ out[code] = field.preprocess_value_for_assignment(vals.symbolize_keys[code.to_sym])
+ end
+ @custom_fields_values_initialized = true
+ else
+ out = vals
end
write_attribute :custom_field_values, out
end
+ def custom_fields_initialized?
+ !!@custom_fields_initialized
+ end
+
+ def custom_fields_values_initialized?
+ !!@custom_fields_values_initialized
+ end
+
def initialize_custom_fields
+ return if custom_fields_initialized?
return unless self.attributes.has_key?("custom_field_values")
+ return unless self.workgroup.present?
self.custom_field_values ||= {}
- custom_fields(:all).values.each &:initialize_custom_field
- custom_fields(:all).each do |k, v|
+ custom_fields.values.each &:initialize_custom_field
+ custom_fields.each do |k, v|
custom_field_values[k] ||= v.default_value
end
@custom_fields_initialized = true
+ self.custom_field_values = self.custom_field_values unless custom_fields_values_initialized?
end
def custom_field_value key
@@ -56,7 +74,7 @@ module CustomFieldsSupport
private
def custom_fields_values_are_valid
- custom_fields(:all).values.all?{|cf| cf.valid?}
+ custom_fields.values.all?{|cf| cf.valid?}
end
end
end
diff --git a/app/models/concerns/line_referential_support.rb b/app/models/concerns/line_referential_support.rb
index 5eade3557..b77a178eb 100644
--- a/app/models/concerns/line_referential_support.rb
+++ b/app/models/concerns/line_referential_support.rb
@@ -7,6 +7,10 @@ module LineReferentialSupport
alias_method :referential, :line_referential
end
+ def workgroup
+ line_referential&.workgroup
+ end
+
def hub_restricted?
false
end
diff --git a/app/models/concerns/metadata_support.rb b/app/models/concerns/metadata_support.rb
index c4bedbcda..182ab8310 100644
--- a/app/models/concerns/metadata_support.rb
+++ b/app/models/concerns/metadata_support.rb
@@ -66,6 +66,10 @@ module MetadataSupport
"#{name}_updated_at".to_sym
end
+ def as_json
+ @table.as_json
+ end
+
def method_missing(mid, *args)
out = super(mid, *args)
owner.send :write_attribute, attribute_name, @table
diff --git a/app/models/concerns/stop_area_referential_support.rb b/app/models/concerns/stop_area_referential_support.rb
index f29397b3a..fb9edd7d5 100644
--- a/app/models/concerns/stop_area_referential_support.rb
+++ b/app/models/concerns/stop_area_referential_support.rb
@@ -7,6 +7,10 @@ module StopAreaReferentialSupport
alias_method :referential, :stop_area_referential
end
+ def workgroup
+ stop_area_referential&.workgroup
+ end
+
def hub_restricted?
false
end
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
index 88783b5b4..15aee9a41 100644
--- a/app/models/custom_field.rb
+++ b/app/models/custom_field.rb
@@ -8,7 +8,7 @@ class CustomField < ApplicationModel
validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}, presence: true
class Collection < HashWithIndifferentAccess
- def initialize object, workgroup=:all
+ def initialize object, workgroup=nil
vals = object.class.custom_fields(workgroup).map do |v|
[v.code, CustomField::Instance.new(object, v, object.custom_field_value(v.code))]
end
@@ -147,6 +147,10 @@ class CustomField < ApplicationModel
@raw_value&.to_i
end
+ def preprocess_value_for_assignment val
+ val&.to_i
+ end
+
def validate
@valid = true
return if @raw_value.is_a?(Fixnum) || @raw_value.is_a?(Float)
@@ -167,6 +171,10 @@ class CustomField < ApplicationModel
end
end
+ def preprocess_value_for_assignment val
+ val
+ end
+
def display_value
return unless value
k = options["list_values"].is_a?(Hash) ? value.to_s : value.to_i
diff --git a/app/models/referential_metadata.rb b/app/models/referential_metadata.rb
index 7a8a01774..a4e6333d7 100644
--- a/app/models/referential_metadata.rb
+++ b/app/models/referential_metadata.rb
@@ -44,8 +44,8 @@ class ReferentialMetadata < ApplicationModel
validate :check_end_greather_than_begin
def check_end_greather_than_begin
- if self.begin and self.end and self.begin >= self.end
- errors.add(:base, I18n.t('referentials.errors.short_period'))
+ if self.begin and self.end and self.begin > self.end
+ errors.add(:base, I18n.t('referentials.errors.invalid_period'))
end
end
diff --git a/app/views/companies/_form.html.slim b/app/views/companies/_form.html.slim
index e8b3fcede..ec003b836 100644
--- a/app/views/companies/_form.html.slim
+++ b/app/views/companies/_form.html.slim
@@ -12,7 +12,7 @@
= 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?
+ - if resource.custom_fields.present?
- resource.custom_fields.each do |code, field|
= field.input(f).to_s
.separator
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index 00f2ad8bb..220097a69 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -62,10 +62,10 @@
= 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?
+ - if resource.custom_fields.present?
.custom_fields
h3 = t("stop_areas.stop_area.custom_fields")
- - resource.custom_fields(resource.stop_area_referential.workgroup).each do |code, field|
+ - resource.custom_fields.each do |code, field|
= field.input(f).to_s
.separator
diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim
index 7fcee545f..2af0e5345 100644
--- a/app/views/vehicle_journeys/index.html.slim
+++ b/app/views/vehicle_journeys/index.html.slim
@@ -33,6 +33,7 @@
| 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.constraint_zones_routes = "#{url_for([@referential, @route.line, :routing_constraint_zones]).html_safe}";
- if has_feature?(:vehicle_journeys_return_route)
= javascript_tag do
diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl
index 6c588416c..3a551f237 100644
--- a/app/views/vehicle_journeys/show.rabl
+++ b/app/views/vehicle_journeys/show.rabl
@@ -1,6 +1,6 @@
object @vehicle_journey
-[:objectid, :published_journey_name, :published_journey_identifier, :company_id, :comment, :checksum, :custom_fields].each do |attr|
+[:objectid, :published_journey_name, :published_journey_identifier, :company_id, :comment, :checksum, :custom_fields, :ignored_routing_contraint_zone_ids].each do |attr|
attributes attr, :unless => lambda { |m| m.send( attr).nil?}
end
diff --git a/config/locales/referentials.en.yml b/config/locales/referentials.en.yml
index 1381d5ddd..f52eaa1cb 100644
--- a/config/locales/referentials.en.yml
+++ b/config/locales/referentials.en.yml
@@ -47,7 +47,7 @@ en:
user_excluded: "%{user} is a reserved value"
overlapped_referential: "%{referential} cover the same perimeter"
overlapped_period: "Another period is on the same period"
- short_period: Min period length is two days
+ invalid_period: The begin date must be before end date
overview:
head:
dates: Dates
diff --git a/config/locales/referentials.fr.yml b/config/locales/referentials.fr.yml
index cf012ef8e..c4633014b 100644
--- a/config/locales/referentials.fr.yml
+++ b/config/locales/referentials.fr.yml
@@ -47,7 +47,7 @@ fr:
user_excluded: "%{user} est une valeur réservée"
overlapped_referential: "%{referential} couvre le même périmètre d'offre"
overlapped_period: "Une autre période chevauche cette période"
- short_period: "La durée minimum d'une période est de deux jours"
+ invalid_period: La date de début doit être antérieure à la date de fin
overview:
head:
dates: Dates
diff --git a/config/locales/vehicle_journeys.en.yml b/config/locales/vehicle_journeys.en.yml
index c0d6f74d4..c073fd1e7 100644
--- a/config/locales/vehicle_journeys.en.yml
+++ b/config/locales/vehicle_journeys.en.yml
@@ -5,7 +5,7 @@ en:
id: Filter by ID...
journey_pattern: Filter by journey pattern...
timetable: Filter by timetable...
-
+ constraint_zone: Select a routing constraint zone
purchase_window: Filter by purchase window
cancel_selection: "Cancel Selection"
fetching_error: "There has been a problem fetching the data. Please reload the page to try again."
@@ -17,6 +17,7 @@ en:
no_associated_timetables: No associated timetables
no_associated_purchase_windows: No associated purchase windows
no_associated_footnotes: No associated footnotes
+ no_excluded_constraint_zones: No exluded constraint zone
duplicate:
one: Clone %{count} vehicle journey
other: Clone %{count} vehicle journeys
@@ -58,6 +59,7 @@ en:
start: Start
end: End
ending_stop: "Arrival"
+ excluded_constraint_zones: Excluded constraint zones
infos: Informations
set: "Set"
show_arrival_time: "Show and edit arrival times"
@@ -161,6 +163,7 @@ en:
vehicle_journey_at_stop_ids: "Time list"
vehicle_type_identifier: "Vehicle Type Identifier"
start_time: Start time
+ constraint_exclusions: Constraint Zones exclusions
errors:
models:
vehicle_journey:
diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml
index b17e102a8..e9d97c8cc 100644
--- a/config/locales/vehicle_journeys.fr.yml
+++ b/config/locales/vehicle_journeys.fr.yml
@@ -6,6 +6,7 @@ fr:
journey_pattern: 'Filtrer par code, nom ou OID de mission...'
timetable: Filtrer par calendrier...
purchase_windows: Filtrer par calendrier commercial...
+ constraint_zone: Choisir une ITL
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"
@@ -16,6 +17,7 @@ fr:
no_associated_timetables: Aucun calendrier associé
no_associated_purchase_windows: Aucun calendrier commercial associé
no_associated_footnotes: Aucune note associée
+ no_excluded_constraint_zones: Aucune ITL exclue
duplicate:
one: Dupliquer %{count} course
other: Dupliquer %{count} courses
@@ -57,6 +59,7 @@ fr:
start: Début
end: Fin
ending_stop: "Arrivée"
+ excluded_constraint_zones: Exclusions d'ITL
infos: Informations
set: "Fixer"
show_arrival_time: "Afficher et éditer les horaires d'arrivée"
@@ -161,6 +164,7 @@ fr:
vehicle_journey_at_stop_ids: "Liste des horaires"
vehicle_type_identifier: "Type d'identifiant du véhicule"
start_time: Heure de départ
+ constraint_exclusions: Exclusions d'ITL
errors:
models:
vehicle_journey:
diff --git a/db/migrate/20180424125207_add_ignored_routing_contraint_zone_ids_to_vehicle_journeys.rb b/db/migrate/20180424125207_add_ignored_routing_contraint_zone_ids_to_vehicle_journeys.rb
new file mode 100644
index 000000000..8886f6595
--- /dev/null
+++ b/db/migrate/20180424125207_add_ignored_routing_contraint_zone_ids_to_vehicle_journeys.rb
@@ -0,0 +1,5 @@
+class AddIgnoredRoutingContraintZoneIdsToVehicleJourneys < ActiveRecord::Migration
+ def change
+ add_column :vehicle_journeys, :ignored_routing_contraint_zone_ids, :integer, array: true, default: []
+ end
+end
diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb
index ce6ce9fa5..54fd4f9d8 100644
--- a/spec/models/custom_field_spec.rb
+++ b/spec/models/custom_field_spec.rb
@@ -3,6 +3,11 @@ require 'rails_helper'
RSpec.describe CustomField, type: :model do
let( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} }
+ let(:referential){ create :workbench_referential }
+ let(:workgroup){ referential.workgroup }
+ before do
+ referential.switch
+ end
context "validates" do
it { should validate_uniqueness_of(:name).scoped_to(:resource_type, :workgroup_id) }
@@ -18,14 +23,14 @@ RSpec.describe CustomField, type: :model do
end
context "custom fields for a resource" do
- let!( :fields ){ [create(:custom_field), create(:custom_field, code: :energy)] }
+ let!( :fields ){ [create(:custom_field, workgroup: workgroup), create(:custom_field, code: :energy, workgroup: workgroup)] }
let!( :instance_fields ){
{
fields[0].code => fields[0].slice(:code, :name, :field_type, :options).update(value: nil),
"energy" => fields[1].slice(:code, :name, :field_type, :options).update(value: 99)
}
}
- it { expect(Chouette::VehicleJourney.custom_fields).to eq(fields) }
+ it { expect(Chouette::VehicleJourney.custom_fields(workgroup)).to eq(fields) }
it {
instance_fields.each do |code, cf|
cf.each do |k, v|
@@ -37,14 +42,36 @@ RSpec.describe CustomField, type: :model do
context "custom field_values for a resource" do
before do
- create :custom_field, field_type: :integer, code: :energy, name: :energy
+ create :custom_field, field_type: :integer, code: :energy, name: :energy, workgroup: workgroup
end
it { expect(vj.custom_field_value("energy")).to eq(99) }
+
+ context "given different workgroups" do
+ let(:ref1){ create :workbench_referential }
+ let(:ref2){ create :workbench_referential }
+ before do
+ create :custom_field, field_type: :integer, code: :ref1_energy, name: :energy, workgroup: ref1.workgroup, options: {default: 12}
+ create :custom_field, field_type: :integer, code: :ref1_energy, name: :energy, workgroup: ref1.workgroup, options: {default: 12}, resource_type: "Company"
+ create :custom_field, field_type: :integer, code: :ref2_energy, name: :energy, workgroup: ref2.workgroup
+ end
+ it "should only initialize fields from the right workgroup" do
+ ref1.switch
+ expect(Chouette::VehicleJourney.new.custom_fields.keys).to eq ["ref1_energy"]
+ expect(Chouette::VehicleJourney.new.custom_field_values["ref1_energy"]).to eq 12
+ expect(Chouette::VehicleJourney.new(custom_field_values: {ref1_energy: 13}).custom_field_values["ref1_energy"]).to eq 13
+ line_referential = create(:line_referential, workgroup: ref1.workgroup)
+ expect(line_referential.companies.build(custom_field_values: {ref1_energy: "13"}).custom_field_values["ref1_energy"]).to eq 13
+
+ ref2.switch
+ expect(Chouette::VehicleJourney.new.custom_fields.keys).to eq ["ref2_energy"]
+ expect(Chouette::VehicleJourney.new.custom_field_values).to_not have_key "ref1_energy"
+ end
+ end
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!(:field){ [create(:custom_field, code: :energy, field_type: 'list', options: {list_values: %w(foo bar baz)}, workgroup: workgroup)] }
let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "1"} }
it "should cast the value" do
expect(vj.custom_fields[:energy].value).to eq 1
@@ -75,7 +102,7 @@ RSpec.describe CustomField, type: :model do
end
context "with an 'integer' field_type" do
- let!(:field){ [create(:custom_field, code: :energy, field_type: 'integer')] }
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'integer', workgroup: workgroup)] }
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
@@ -101,7 +128,7 @@ RSpec.describe CustomField, type: :model do
end
context "with a 'string' field_type" do
- let!(:field){ [create(:custom_field, code: :energy, field_type: 'string')] }
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'string', workgroup: workgroup)] }
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'
@@ -109,7 +136,7 @@ RSpec.describe CustomField, type: :model do
end
context "with a 'attachment' field_type" do
- let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment')] }
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment', workgroup: workgroup)] }
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
@@ -129,7 +156,7 @@ RSpec.describe CustomField, type: :model do
end
context "with a whitelist" do
- let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment', options: {extension_whitelist: %w(zip)})] }
+ let!(:field){ [create(:custom_field, code: :energy, field_type: 'attachment', options: {extension_whitelist: %w(zip)}, workgroup: workgroup)] }
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
diff --git a/spec/models/referential_metadata_spec.rb b/spec/models/referential_metadata_spec.rb
index 88a12b2bb..210f95e14 100644
--- a/spec/models/referential_metadata_spec.rb
+++ b/spec/models/referential_metadata_spec.rb
@@ -100,7 +100,7 @@ RSpec.describe ReferentialMetadata, :type => :model do
it "should validate that end is greather than or equlals to begin" do
expect(period(begin: "2016-11-21", end: "2016-11-22")).to be_valid
- expect(period(begin: "2016-11-21", end: "2016-11-21")).to_not be_valid
+ expect(period(begin: "2016-11-21", end: "2016-11-21")).to be_valid
expect(period(begin: "2016-11-22", end: "2016-11-21")).to_not be_valid
end