aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile7
-rw-r--r--Gemfile.lock14
-rw-r--r--app/assets/javascripts/application.js6
-rw-r--r--app/assets/stylesheets/components/_compliance_control_blocks.sass3
-rw-r--r--app/assets/stylesheets/components/_main_nav.sass5
-rw-r--r--app/assets/stylesheets/components/_pagination.sass3
-rw-r--r--app/assets/stylesheets/components/_referential_overview.sass1
-rw-r--r--app/controllers/api/v1/netex_imports_controller.rb39
-rw-r--r--app/controllers/application_controller.rb9
-rw-r--r--app/controllers/calendars_controller.rb8
-rw-r--r--app/controllers/chouette_controller.rb1
-rw-r--r--app/controllers/companies_controller.rb14
-rw-r--r--app/controllers/compliance_control_blocks_controller.rb5
-rw-r--r--app/controllers/compliance_controls_controller.rb2
-rw-r--r--app/controllers/concerns/metadata_controller_support.rb26
-rw-r--r--app/controllers/concerns/paper_trail_support.rb11
-rw-r--r--app/controllers/exports_controller.rb3
-rw-r--r--app/controllers/journey_patterns_collections_controller.rb1
-rw-r--r--app/controllers/lines_controller.rb4
-rw-r--r--app/controllers/purchase_windows_controller.rb4
-rw-r--r--app/controllers/referential_companies_controller.rb11
-rw-r--r--app/controllers/referentials_controller.rb2
-rw-r--r--app/controllers/routes_controller.rb3
-rw-r--r--app/controllers/stop_areas_controller.rb5
-rw-r--r--app/controllers/time_tables_controller.rb46
-rw-r--r--app/controllers/vehicle_journeys_collections_controller.rb4
-rw-r--r--app/controllers/vehicle_journeys_controller.rb2
-rw-r--r--app/decorators/referential_decorator.rb2
-rw-r--r--app/decorators/route_decorator.rb18
-rw-r--r--app/helpers/application_helper.rb15
-rw-r--r--app/helpers/compliance_controls_helper.rb13
-rw-r--r--app/helpers/multiple_selection_toolbox_helper.rb4
-rw-r--r--app/helpers/routes_helper.rb2
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/stop_areas_helper.rb6
-rw-r--r--app/helpers/table_builder_helper.rb7
-rw-r--r--app/javascript/helpers/polyfills.js4
-rw-r--r--app/javascript/helpers/save_button.js2
-rw-r--r--app/javascript/journey_patterns/actions/index.js38
-rw-r--r--app/javascript/journey_patterns/components/ConfirmModal.js8
-rw-r--r--app/javascript/journey_patterns/components/CreateModal.js14
-rw-r--r--app/javascript/journey_patterns/components/EditModal.js16
-rw-r--r--app/javascript/journey_patterns/components/JourneyPattern.js20
-rw-r--r--app/javascript/journey_patterns/components/JourneyPatterns.js12
-rw-r--r--app/javascript/journey_patterns/components/Navigate.js3
-rw-r--r--app/javascript/journey_patterns/reducers/journeyPatterns.js2
-rw-r--r--app/javascript/packs/calendars/edit.js2
-rw-r--r--app/javascript/packs/exports/new.js2
-rw-r--r--app/javascript/packs/journey_patterns/index.js2
-rw-r--r--app/javascript/packs/referential_lines/show.js2
-rw-r--r--app/javascript/packs/referential_overview/overview.js2
-rw-r--r--app/javascript/packs/routes/edit.js32
-rw-r--r--app/javascript/packs/routes/show.js2
-rw-r--r--app/javascript/packs/stop_areas/new.js2
-rw-r--r--app/javascript/packs/time_tables/edit.js2
-rw-r--r--app/javascript/packs/vehicle_journeys/index.js2
-rw-r--r--app/javascript/routes/components/BSelect2.js1
-rw-r--r--app/javascript/routes/components/StopPoint.js6
-rw-r--r--app/javascript/routes/index.js6
-rw-r--r--app/javascript/routes/reducers/stopPoints.js6
-rw-r--r--app/javascript/time_tables/actions/index.js17
-rw-r--r--app/javascript/time_tables/components/ConfirmModal.js8
-rw-r--r--app/javascript/time_tables/components/ErrorModal.js8
-rw-r--r--app/javascript/time_tables/components/Metas.js8
-rw-r--r--app/javascript/time_tables/components/PeriodForm.js8
-rw-r--r--app/javascript/time_tables/components/PeriodManager.js8
-rw-r--r--app/javascript/time_tables/components/SaveTimetable.js2
-rw-r--r--app/javascript/time_tables/components/TagsSelect2.js6
-rw-r--r--app/javascript/time_tables/components/Timetable.js14
-rw-r--r--app/javascript/time_tables/containers/App.js1
-rw-r--r--app/javascript/vehicle_journeys/actions/index.js32
-rw-r--r--app/javascript/vehicle_journeys/components/ConfirmModal.js4
-rw-r--r--app/javascript/vehicle_journeys/components/Filters.js4
-rw-r--r--app/javascript/vehicle_journeys/components/Navigate.js3
-rw-r--r--app/javascript/vehicle_journeys/components/VehicleJourneys.js12
-rw-r--r--app/javascript/vehicle_journeys/components/tools/CreateModal.js16
-rw-r--r--app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js13
-rw-r--r--app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js2
-rw-r--r--app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js12
-rw-r--r--app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js6
-rw-r--r--app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js14
-rw-r--r--app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js8
-rw-r--r--app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js12
-rw-r--r--app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js8
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js5
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js4
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js4
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js4
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/fr.js9
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/language.js9
-rw-r--r--app/javascript/vehicle_journeys/reducers/vehicleJourneys.js15
-rw-r--r--app/models/api/v1/api_key.rb6
-rw-r--r--app/models/application_model.rb5
-rw-r--r--app/models/calendar.rb7
-rw-r--r--app/models/calendar/date_value.rb2
-rw-r--r--app/models/calendar/period.rb2
-rw-r--r--app/models/calendar_observer.rb4
-rw-r--r--app/models/chouette/access_link.rb2
-rw-r--r--app/models/chouette/access_point.rb2
-rw-r--r--app/models/chouette/active_record.rb3
-rw-r--r--app/models/chouette/company.rb6
-rw-r--r--app/models/chouette/connection_link.rb2
-rw-r--r--app/models/chouette/for_alighting_enumerations.rb2
-rw-r--r--app/models/chouette/for_boarding_enumerations.rb2
-rw-r--r--app/models/chouette/group_of_line.rb2
-rw-r--r--app/models/chouette/journey_pattern.rb2
-rw-r--r--app/models/chouette/line.rb12
-rw-r--r--app/models/chouette/network.rb2
-rw-r--r--app/models/chouette/pt_link.rb2
-rw-r--r--app/models/chouette/purchase_window.rb2
-rw-r--r--app/models/chouette/route.rb40
-rw-r--r--app/models/chouette/routing_constraint_zone.rb2
-rw-r--r--app/models/chouette/stop_area.rb9
-rw-r--r--app/models/chouette/stop_point.rb5
-rw-r--r--app/models/chouette/time_table.rb2
-rw-r--r--app/models/chouette/timeband.rb2
-rw-r--r--app/models/chouette/vehicle_journey.rb32
-rw-r--r--app/models/clean_up.rb2
-rw-r--r--app/models/clean_up_result.rb2
-rw-r--r--app/models/compliance_check.rb2
-rw-r--r--app/models/compliance_check_block.rb2
-rw-r--r--app/models/compliance_check_message.rb2
-rw-r--r--app/models/compliance_check_resource.rb2
-rw-r--r--app/models/compliance_check_set.rb5
-rw-r--r--app/models/compliance_control.rb3
-rw-r--r--app/models/compliance_control_block.rb4
-rw-r--r--app/models/compliance_control_set.rb5
-rw-r--r--app/models/concerns/application_days_support.rb8
-rw-r--r--app/models/concerns/custom_fields_support.rb46
-rw-r--r--app/models/concerns/iev_interfaces/task.rb2
-rw-r--r--app/models/concerns/metadata_support.rb107
-rw-r--r--app/models/concerns/min_max_values_validation.rb4
-rw-r--r--app/models/custom_field.rb206
-rw-r--r--app/models/export/base.rb20
-rw-r--r--app/models/export/message.rb2
-rw-r--r--app/models/export/resource.rb2
-rw-r--r--app/models/export/simple_exporter/base.rb4
-rw-r--r--app/models/generic_attribute_control/min_max.rb4
-rw-r--r--app/models/import/base.rb4
-rw-r--r--app/models/import/gtfs.rb302
-rw-r--r--app/models/import/message.rb2
-rw-r--r--app/models/import/netex.rb38
-rw-r--r--app/models/import/resource.rb2
-rw-r--r--app/models/import/workbench.rb21
-rw-r--r--app/models/line_control/lines_scope.rb8
-rw-r--r--app/models/line_referential.rb6
-rw-r--r--app/models/line_referential_membership.rb4
-rw-r--r--app/models/line_referential_sync.rb2
-rw-r--r--app/models/line_referential_sync_message.rb2
-rw-r--r--app/models/merge.rb22
-rw-r--r--app/models/organisation.rb6
-rw-r--r--app/models/public_version.rb4
-rw-r--r--app/models/referential.rb14
-rw-r--r--app/models/referential_cloning.rb2
-rw-r--r--app/models/referential_metadata.rb6
-rw-r--r--app/models/referential_suite.rb2
-rw-r--r--app/models/simple_exporter.rb2
-rw-r--r--app/models/simple_interface.rb2
-rw-r--r--app/models/stop_area_referential.rb4
-rw-r--r--app/models/stop_area_referential_membership.rb4
-rw-r--r--app/models/stop_area_referential_sync.rb2
-rw-r--r--app/models/stop_area_referential_sync_message.rb2
-rw-r--r--app/models/user.rb10
-rw-r--r--app/models/vehicle_journey_control/speed.rb2
-rw-r--r--app/models/workbench.rb2
-rw-r--r--app/models/workgroup.rb2
-rw-r--r--app/policies/route_policy.rb4
-rw-r--r--app/services/route_way_cost_calculator.rb2
-rw-r--r--app/services/zip_service.rb45
-rw-r--r--app/uploaders/custom_field_attachment_uploader.rb12
-rw-r--r--app/views/autocomplete_calendars/autocomplete.rabl2
-rw-r--r--app/views/autocomplete_purchase_windows/index.rabl13
-rw-r--r--app/views/autocomplete_stop_areas/around.rabl1
-rw-r--r--app/views/autocomplete_stop_areas/index.rabl3
-rw-r--r--app/views/autocomplete_stop_areas/show.rabl3
-rw-r--r--app/views/calendars/_filters.html.slim10
-rw-r--r--app/views/calendars/_form_simple.html.slim41
-rw-r--r--app/views/calendars/index.html.slim6
-rw-r--r--app/views/calendars/show.html.slim13
-rw-r--r--app/views/companies/_form.html.slim4
-rw-r--r--app/views/companies/new.html.slim2
-rw-r--r--app/views/companies/show.html.slim13
-rw-r--r--app/views/compliance_check_sets/show.html.slim2
-rw-r--r--app/views/compliance_checks/show.html.slim4
-rw-r--r--app/views/compliance_control_blocks/_form.html.slim6
-rw-r--r--app/views/compliance_control_blocks/edit.html.slim1
-rw-r--r--app/views/compliance_control_blocks/new.html.slim2
-rw-r--r--app/views/compliance_control_sets/_filters.html.slim2
-rw-r--r--app/views/compliance_control_sets/show.html.slim4
-rw-r--r--app/views/dashboards/_dashboard.html.slim9
-rw-r--r--app/views/imports/show.html.slim12
-rw-r--r--app/views/layouts/navigation/_main_nav_left_content_stif.html.slim46
-rw-r--r--app/views/layouts/navigation/_nav_panel_operations.html.slim2
-rw-r--r--app/views/layouts/navigation/_nav_panel_profile.html.slim2
-rw-r--r--app/views/lines/_filters.html.slim4
-rw-r--r--app/views/lines/show.html.slim2
-rw-r--r--app/views/referential_companies/_form.html.slim37
-rw-r--r--app/views/referential_companies/edit.html.slim7
-rw-r--r--app/views/referential_companies/new.html.slim6
-rw-r--r--app/views/referential_companies/show.html.slim13
-rw-r--r--app/views/referential_lines/_filters.html.slim6
-rw-r--r--app/views/referential_lines/show.html.slim26
-rw-r--r--app/views/referential_vehicle_journeys/_filters.html.slim4
-rw-r--r--app/views/referentials/_form.html.slim2
-rw-r--r--app/views/referentials/_overview.html.slim4
-rw-r--r--app/views/referentials/select_compliance_control_set.html.slim4
-rw-r--r--app/views/routes/show.html.slim4
-rw-r--r--app/views/routing_constraint_zones/_filters.html.slim10
-rw-r--r--app/views/routing_constraint_zones/index.html.slim4
-rw-r--r--app/views/shared/controls/_metadatas.html.slim8
-rw-r--r--app/views/shared/custom_fields/_attachment.html.slim4
-rw-r--r--app/views/shared/iev_interfaces/_messages.html.slim7
-rw-r--r--app/views/stif/dashboards/_dashboard.html.slim13
-rw-r--r--app/views/stop_areas/_filters.html.slim4
-rw-r--r--app/views/stop_areas/_form.html.slim8
-rw-r--r--app/views/stop_areas/autocomplete.rabl3
-rw-r--r--app/views/stop_areas/index.html.slim2
-rw-r--r--app/views/stop_areas/show.html.slim2
-rw-r--r--app/views/time_tables/index.html.slim6
-rw-r--r--app/views/vehicle_journeys/index.html.slim14
-rw-r--r--app/views/workbench_outputs/show.html.slim3
-rw-r--r--app/views/workbenches/show.html.slim5
-rw-r--r--app/workers/gtfs_import_worker.rb7
-rw-r--r--app/workers/route_way_cost_worker.rb11
-rw-r--r--app/workers/workbench_import_worker/object_state_updater.rb27
-rw-r--r--config/database/ci.yml6
-rw-r--r--config/initializers/simple_form_bootstrap.rb4
-rw-r--r--config/locales/actions.en.yml2
-rw-r--r--config/locales/calendars.en.yml13
-rw-r--r--config/locales/calendars.fr.yml5
-rw-r--r--config/locales/clean_ups.en.yml2
-rw-r--r--config/locales/companies.en.yml2
-rw-r--r--config/locales/compliance_check_messages.en.yml3
-rw-r--r--config/locales/compliance_check_messages.fr.yml3
-rw-r--r--config/locales/compliance_check_resource.en.yml8
-rw-r--r--config/locales/compliance_check_resources.fr.yml8
-rw-r--r--config/locales/compliance_check_sets.en.yml4
-rw-r--r--config/locales/compliance_check_sets.fr.yml2
-rw-r--r--config/locales/compliance_control_blocks.en.yml12
-rw-r--r--config/locales/compliance_control_blocks.fr.yml10
-rw-r--r--config/locales/compliance_control_sets.en.yml7
-rw-r--r--config/locales/compliance_controls.en.yml9
-rw-r--r--config/locales/compliance_controls.fr.yml7
-rw-r--r--config/locales/dashboard.en.yml3
-rw-r--r--config/locales/dashboard.fr.yml3
-rw-r--r--config/locales/import_messages.en.yml5
-rw-r--r--config/locales/import_messages.fr.yml7
-rw-r--r--config/locales/import_resources.en.yml2
-rw-r--r--config/locales/import_resources.fr.yml2
-rw-r--r--config/locales/imports.en.yml7
-rw-r--r--config/locales/imports.fr.yml7
-rw-r--r--config/locales/journey_patterns.en.yml11
-rw-r--r--config/locales/journey_patterns.fr.yml10
-rw-r--r--config/locales/layouts.en.yml18
-rw-r--r--config/locales/layouts.fr.yml18
-rw-r--r--config/locales/line_referentials.en.yml2
-rw-r--r--config/locales/lines.en.yml8
-rw-r--r--config/locales/lines.fr.yml6
-rw-r--r--config/locales/merges.yml2
-rw-r--r--config/locales/networks.en.yml2
-rw-r--r--config/locales/purchase_windows.en.yml4
-rw-r--r--config/locales/referentials.en.yml8
-rw-r--r--config/locales/referentials.fr.yml2
-rw-r--r--config/locales/routes.en.yml12
-rw-r--r--config/locales/routes.fr.yml12
-rw-r--r--config/locales/routing_constraint_zones.en.yml9
-rw-r--r--config/locales/routing_constraint_zones.fr.yml7
-rw-r--r--config/locales/select2.en.yml15
-rw-r--r--config/locales/select2.fr.yml15
-rw-r--r--config/locales/stop_area_referentials.en.yml2
-rw-r--r--config/locales/stop_areas.en.yml26
-rw-r--r--config/locales/stop_areas.fr.yml6
-rw-r--r--config/locales/stop_points.en.yml4
-rw-r--r--config/locales/table_builders.en.yml3
-rw-r--r--config/locales/table_builders.fr.yml3
-rw-r--r--config/locales/vehicle_journey_exports.en.yml2
-rw-r--r--config/locales/vehicle_journeys.en.yml35
-rw-r--r--config/locales/vehicle_journeys.fr.yml27
-rw-r--r--config/locales/will_paginate.en.yml8
-rw-r--r--config/locales/workbench_outputs.en.yml6
-rw-r--r--config/locales/workbench_outputs.fr.yml4
-rw-r--r--config/locales/workbenches.en.yml9
-rw-r--r--config/locales/workbenches.fr.yml6
-rw-r--r--config/routes.rb6
-rw-r--r--config/webpacker.yml2
-rw-r--r--db/migrate/20150219175300_insert_default_organisation.rb2
-rw-r--r--db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb5
-rw-r--r--db/migrate/20180315152714_remove_short_name_from_calendars.rb5
-rw-r--r--db/migrate/20180316115003_add_custom_field_values_to_companies.rb5
-rw-r--r--db/migrate/20180330074336_add_metadata_to_routes.rb5
-rw-r--r--db/migrate/20180330124436_add_metadata_to_other_models.rb28
-rw-r--r--db/migrate/20180403065419_remove_papertrail_tables.rb5
-rw-r--r--db/migrate/20180403100007_add_registration_number_indexes.rb7
-rw-r--r--db/migrate/20180405133659_change_companies_custom_fields_values_type.rb8
-rw-r--r--db/migrate/20180413082929_clean_lines_secondary_companies.rb5
-rw-r--r--db/schema.rb128
-rw-r--r--db/seeds/dev/custom_fields.seeds.rb52
-rw-r--r--db/seeds/development/custom_fields.seeds.rb52
-rw-r--r--db/seeds/seed_helpers.rb12
-rw-r--r--db/seeds/stif.seeds.rb53
-rw-r--r--lib/af83/decorator/link.rb10
-rw-r--r--lib/gtfs/time.rb28
-rw-r--r--lib/route_way_cost_unit_converter.rb14
-rw-r--r--lib/stif/netex_file.rb28
-rw-r--r--lib/tasks/ci.rake17
-rw-r--r--lib/tom_tom.rb4
-rw-r--r--lib/tom_tom/matrix.rb114
-rw-r--r--lib/tom_tom/matrix/point.rb18
-rw-r--r--lib/tom_tom/matrix/request_json_serializer.rb25
-rw-r--r--package.json4
-rw-r--r--spec/controllers/exports_controller_spec.rb18
-rw-r--r--spec/controllers/lines_controller_spec.rb29
-rw-r--r--spec/controllers/routes_controller_spec.rb50
-rw-r--r--spec/factories/calendars.rb2
-rw-r--r--spec/factories/custom_fields.rb2
-rw-r--r--spec/features/calendars_permissions_spec.rb4
-rw-r--r--spec/features/calendars_spec.rb16
-rw-r--r--spec/features/compliance_control_sets_spec.rb2
-rw-r--r--spec/fixtures/google-sample-feed.zipbin0 -> 3217 bytes
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar.zipbin0 -> 11084 bytes
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml86
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml33
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml202
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml204
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml80
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml32
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml172
-rw-r--r--spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml172
-rw-r--r--spec/fixtures/tom_tom_matrix.json123
-rw-r--r--spec/lib/compliance_control_set_cloner_spec.rb2
-rw-r--r--spec/lib/compliance_control_set_copier_spec.rb2
-rw-r--r--spec/lib/gtfs/time_spec.rb27
-rw-r--r--spec/lib/route_way_cost_unit_converter_spec.rb28
-rw-r--r--spec/lib/tom_tom/matrix/request_json_serializer_spec.rb39
-rw-r--r--spec/lib/tom_tom/matrix_spec.rb228
-rw-r--r--spec/models/api/v1/api_key_spec.rb2
-rw-r--r--spec/models/calendar_observer_spec.rb28
-rw-r--r--spec/models/calendar_spec.rb4
-rw-r--r--spec/models/chouette/access_link_spec.rb2
-rw-r--r--spec/models/chouette/access_point_spec.rb2
-rw-r--r--spec/models/chouette/company_spec.rb2
-rw-r--r--spec/models/chouette/connection_link_spec.rb2
-rw-r--r--spec/models/chouette/group_of_line_spec.rb2
-rw-r--r--spec/models/chouette/journey_pattern_spec.rb4
-rw-r--r--spec/models/chouette/line_spec.rb2
-rw-r--r--spec/models/chouette/network_spec.rb2
-rw-r--r--spec/models/chouette/route/route_base_spec.rb11
-rw-r--r--spec/models/chouette/route/route_duplication_spec.rb3
-rw-r--r--spec/models/chouette/route/route_stop_points_spec.rb3
-rw-r--r--spec/models/chouette/routing_constraint_zone_spec.rb2
-rw-r--r--spec/models/chouette/stop_area_spec.rb2
-rw-r--r--spec/models/chouette/stop_point_spec.rb2
-rw-r--r--spec/models/chouette/time_table_spec.rb2
-rw-r--r--spec/models/chouette/timeband_spec.rb2
-rw-r--r--spec/models/chouette/vehicle_journey_spec.rb54
-rw-r--r--spec/models/compliance_check_set_spec.rb2
-rw-r--r--spec/models/compliance_control_block_spec.rb12
-rw-r--r--spec/models/compliance_control_set_spec.rb2
-rw-r--r--spec/models/custom_field_spec.rb70
-rw-r--r--spec/models/import/gtfs_spec.rb283
-rw-r--r--spec/models/referential_metadata_spec.rb19
-rw-r--r--spec/models/referential_spec.rb2
-rw-r--r--spec/models/route_spec.rb68
-rw-r--r--spec/models/simple_json_exporter_spec.rb10
-rw-r--r--spec/services/route_way_cost_calculator_spec.rb10
-rw-r--r--spec/services/zip_service_spec.rb58
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/referential.rb12
-rw-r--r--spec/support/shared_examples/compliance_control_validation.rb18
-rw-r--r--spec/views/companies/edit.html.erb_spec.rb5
-rw-r--r--spec/views/companies/new.html.erb_spec.rb5
-rw-r--r--spec/views/stop_areas/edit.html.erb_spec.rb1
-rw-r--r--spec/views/stop_areas/new.html.erb_spec.rb1
-rw-r--r--yarn.lock10
374 files changed, 4784 insertions, 945 deletions
diff --git a/Gemfile b/Gemfile
index c88041781..cca55be61 100644
--- a/Gemfile
+++ b/Gemfile
@@ -33,9 +33,6 @@ gem 'spring', group: :development
# ActiveRecord associations on top of PostgreSQL arrays
gem 'has_array_of', af83: 'has_array_of'
-# Track changes to your models' data. Good for auditing or versioning.
-gem 'paper_trail'
-
gem 'rails-observers'
# Use SeedBank for spliting seeds
@@ -134,7 +131,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'
@@ -146,6 +143,8 @@ gem 'puma', '~> 3.10.0'
gem 'newrelic_rpm'
gem 'letter_opener'
+gem 'gtfs'
+
group :development do
gem 'capistrano', '2.13.5'
gem 'capistrano-ext'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4e3c76690..4fb77eeb9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -258,6 +258,10 @@ GEM
google-analytics-rails (1.1.0)
gretel (3.0.9)
rails (>= 3.1.0)
+ gtfs (0.2.5)
+ multi_json
+ rake
+ rubyzip (~> 1.1)
has_scope (0.7.0)
actionpack (>= 4.1, < 5.1)
activesupport (>= 4.1, < 5.1)
@@ -321,7 +325,7 @@ GEM
minitest (5.11.3)
money (6.10.1)
i18n (>= 0.6.4, < 1.0)
- multi_json (1.12.1)
+ multi_json (1.13.1)
multi_test (0.1.2)
multi_xml (0.6.0)
multipart-post (2.0.0)
@@ -337,10 +341,6 @@ GEM
mini_portile2 (~> 2.3.0)
open4 (1.3.4)
orm_adapter (0.5.0)
- paper_trail (4.1.0)
- activerecord (>= 3.0, < 6.0)
- activesupport (>= 3.0, < 6.0)
- request_store (~> 1.1)
parser (2.4.0.0)
ast (~> 2.2)
pg (0.20.0)
@@ -424,7 +424,7 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
- rake (12.3.0)
+ rake (12.3.1)
ransack (1.8.3)
actionpack (>= 3.0)
activerecord (>= 3.0)
@@ -637,6 +637,7 @@ DEPENDENCIES
georuby-ext (= 0.0.5)
google-analytics-rails
gretel
+ gtfs
has_array_of!
htmlbeautifier
i18n-js
@@ -652,7 +653,6 @@ DEPENDENCIES
map_layers (= 0.0.4)
mimemagic
newrelic_rpm
- paper_trail
pg
phantomjs
poltergeist
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 6a79f7e8e..3eaade37f 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -25,3 +25,9 @@
//= require "i18n"
//= require "i18n/extended"
//= require "i18n/translations"
+
+$(document).ready(function() {
+ $('a[disabled=disabled]').click(function(event){
+ event.preventDefault(); // Prevent link from following its href
+ });
+});
diff --git a/app/assets/stylesheets/components/_compliance_control_blocks.sass b/app/assets/stylesheets/components/_compliance_control_blocks.sass
new file mode 100644
index 000000000..46880075c
--- /dev/null
+++ b/app/assets/stylesheets/components/_compliance_control_blocks.sass
@@ -0,0 +1,3 @@
+#compliance_control_block_form
+ .condition-attributes-errors
+ margin-bottom: 20px
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/assets/stylesheets/components/_referential_overview.sass b/app/assets/stylesheets/components/_referential_overview.sass
index 7a0cc98c5..cf440b22c 100644
--- a/app/assets/stylesheets/components/_referential_overview.sass
+++ b/app/assets/stylesheets/components/_referential_overview.sass
@@ -24,6 +24,7 @@
.time-travel
padding-top: 3px
padding-bottom: 4px
+ height: 43px
a.btn:first-child
margin-right: 1px
a.btn:last-child
diff --git a/app/controllers/api/v1/netex_imports_controller.rb b/app/controllers/api/v1/netex_imports_controller.rb
index 2654fa088..186ddc35c 100644
--- a/app/controllers/api/v1/netex_imports_controller.rb
+++ b/app/controllers/api/v1/netex_imports_controller.rb
@@ -25,56 +25,19 @@ module Api
def create_models
find_workbench
- create_referential
create_netex_import
end
def create_netex_import
attributes = netex_import_params.merge creator: "Webservice"
-
- attributes = attributes.merge referential_id: @new_referential.id
-
@netex_import = Import::Netex.new attributes
@netex_import.save!
-
- unless @netex_import.referential
- Rails.logger.info "Can't create referential for import #{@netex_import.id}: #{@new_referential.inspect} #{@new_referential.metadatas.inspect} #{@new_referential.errors.full_messages}"
- @netex_import.messages.create criticity: :error, message_key: "referential_creation"
- end
+ @netex_import.create_referential!
rescue ActiveRecord::RecordInvalid
render json: {errors: @netex_import.errors}, status: 406
finish_action!
end
- def create_referential
- @new_referential =
- Referential.new(
- name: netex_import_params['name'],
- organisation_id: @workbench.organisation_id,
- workbench_id: @workbench.id,
- metadatas: [metadata]
- )
- @new_referential.save
- end
-
- def metadata
- metadata = ReferentialMetadata.new
-
- if netex_import_params['file']
- netex_file = STIF::NetexFile.new(netex_import_params['file'].to_io)
- frame = netex_file.frames.first
-
- if frame
- metadata.periodes = frame.periods
-
- line_objectids = frame.line_refs.map { |ref| "STIF:CODIFLIGNE:Line:#{ref}" }
- metadata.line_ids = @workbench.lines.where(objectid: line_objectids).pluck(:id)
- end
- end
-
- metadata
- end
-
def netex_import_params
params
.require('netex_import')
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c4961123d..8b66e6097 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,5 +1,5 @@
class ApplicationController < ActionController::Base
- include PaperTrailSupport
+ include MetadataControllerSupport
include Pundit
include FeatureChecker
@@ -10,7 +10,6 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :set_locale
-
# Load helpers in rails engine
helper LanguageEngine::Engine.helpers
@@ -36,12 +35,6 @@ class ApplicationController < ActionController::Base
end
helper_method :current_organisation
- def current_functional_scope
- functional_scope = current_organisation.sso_attributes.try(:[], "functional_scope") if current_organisation
- JSON.parse(functional_scope) if functional_scope
- end
- helper_method :current_functional_scope
-
def collection_name
self.class.name.split("::").last.gsub('Controller', '').underscore
end
diff --git a/app/controllers/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/chouette_controller.rb b/app/controllers/chouette_controller.rb
index 3e4f3af27..e6e7c0a8a 100644
--- a/app/controllers/chouette_controller.rb
+++ b/app/controllers/chouette_controller.rb
@@ -1,4 +1,3 @@
class ChouetteController < InheritedResources::Base
- include PaperTrailSupport
include ApplicationHelper
end
diff --git a/app/controllers/companies_controller.rb b/app/controllers/companies_controller.rb
index 4afd12be1..a09cab783 100644
--- a/app/controllers/companies_controller.rb
+++ b/app/controllers/companies_controller.rb
@@ -38,12 +38,14 @@ class CompaniesController < ChouetteController
protected
def collection
- @q = line_referential.companies.search(params[:q])
-
+ scope = line_referential.companies
+ @q = scope.search(params[:q])
+ ids = @q.result(:distinct => true).pluck(:id)
+ scope = scope.where(id: ids)
if sort_column && sort_direction
- @companies ||= @q.result(:distinct => true).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])
+ @companies ||= scope.order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])
else
- @companies ||= @q.result(:distinct => true).order(:name).paginate(:page => params[:page])
+ @companies ||= scope.order(:name).paginate(:page => params[:page])
end
end
@@ -69,7 +71,9 @@ class CompaniesController < ChouetteController
end
def company_params
- params.require(:company).permit( :objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone )
+ fields = [:objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone]
+ fields += permitted_custom_fields_params(Chouette::Company.custom_fields(line_referential.workgroup))
+ params.require(:company).permit( fields )
end
private
diff --git a/app/controllers/compliance_control_blocks_controller.rb b/app/controllers/compliance_control_blocks_controller.rb
index 9eee8dfaf..0851e2800 100644
--- a/app/controllers/compliance_control_blocks_controller.rb
+++ b/app/controllers/compliance_control_blocks_controller.rb
@@ -10,4 +10,9 @@ class ComplianceControlBlocksController < ChouetteController
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/concerns/metadata_controller_support.rb b/app/controllers/concerns/metadata_controller_support.rb
new file mode 100644
index 000000000..db83e79ae
--- /dev/null
+++ b/app/controllers/concerns/metadata_controller_support.rb
@@ -0,0 +1,26 @@
+module MetadataControllerSupport
+ extend ActiveSupport::Concern
+
+ included do
+ after_action :set_creator_metadata, only: :create
+ after_action :set_modifier_metadata, only: :update
+ end
+
+ def user_for_metadata
+ current_user ? current_user.username : ''
+ end
+
+ def set_creator_metadata
+ if resource.valid?
+ resource.try(:set_metadata!, :creator_username, user_for_metadata)
+ resource.try(:set_metadata!, :modifier_username, user_for_metadata)
+ end
+ end
+
+ def set_modifier_metadata
+ _resource = @resources || [resource]
+ _resource.flatten.each do |r|
+ r.try :set_metadata!, :modifier_username, user_for_metadata
+ end
+ end
+end
diff --git a/app/controllers/concerns/paper_trail_support.rb b/app/controllers/concerns/paper_trail_support.rb
deleted file mode 100644
index 4b0b1a7c7..000000000
--- a/app/controllers/concerns/paper_trail_support.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module PaperTrailSupport
- extend ActiveSupport::Concern
-
- included do
- before_action :set_paper_trail_whodunnit
-
- def user_for_paper_trail
- current_user ? current_user.name : ''
- end
- end
-end
diff --git a/app/controllers/exports_controller.rb b/app/controllers/exports_controller.rb
index a5282a514..c89da5000 100644
--- a/app/controllers/exports_controller.rb
+++ b/app/controllers/exports_controller.rb
@@ -3,13 +3,14 @@ class ExportsController < ChouetteController
include RansackDateFilter
include IevInterfaces
skip_before_action :authenticate_user!, only: [:upload]
+ skip_before_action :verify_authenticity_token, only: [:upload]
defaults resource_class: Export::Base, collection_name: 'exports', instance_name: 'export'
def upload
if params[:token] == resource.token_upload
resource.file = params[:file]
resource.save!
- redirect_to [resource.workbench, resource]
+ render json: {status: :ok}
else
user_not_authorized
end
diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb
index da567779e..db92d48f3 100644
--- a/app/controllers/journey_patterns_collections_controller.rb
+++ b/app/controllers/journey_patterns_collections_controller.rb
@@ -74,6 +74,7 @@ class JourneyPatternsCollectionsController < ChouetteController
def update
state = JSON.parse request.raw_post
Chouette::JourneyPattern.state_update route, state
+ @resources = route.journey_patterns
errors = state.any? {|item| item['errors']}
respond_to do |format|
diff --git a/app/controllers/lines_controller.rb b/app/controllers/lines_controller.rb
index ae8c9ed0c..cd8091252 100644
--- a/app/controllers/lines_controller.rb
+++ b/app/controllers/lines_controller.rb
@@ -122,7 +122,7 @@ class LinesController < ChouetteController
end
def line_params
- params.require(:line).permit(
+ out = params.require(:line).permit(
:transport_mode,
:network_id,
:company_id,
@@ -148,6 +148,8 @@ class LinesController < ChouetteController
:secondary_company_ids => [],
footnotes_attributes: [:code, :label, :_destroy, :id]
)
+ out[:secondary_company_ids] = (out[:secondary_company_ids] || []).select(&:present?)
+ out
end
# Fake ransack filter
diff --git a/app/controllers/purchase_windows_controller.rb b/app/controllers/purchase_windows_controller.rb
index 293a7d8e4..cf73d0ed1 100644
--- a/app/controllers/purchase_windows_controller.rb
+++ b/app/controllers/purchase_windows_controller.rb
@@ -63,7 +63,9 @@ class PurchaseWindowsController < ChouetteController
def ransack_contains_date
date =[]
- if params[:q] && params[:q]['contains_date(1i)'].present?
+ if params[:q] && params[:q]['contains_date'].present?
+ params[:q]['contains_date'] = @date = params[:q]['contains_date'].to_date
+ elsif params[:q] && params[:q]['contains_date(1i)'].present?
['contains_date(1i)', 'contains_date(2i)', 'contains_date(3i)'].each do |key|
date << params[:q][key].to_i
params[:q].delete(key)
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/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb
index 0dcadad1e..2ac8532e0 100644
--- a/app/controllers/time_tables_controller.rb
+++ b/app/controllers/time_tables_controller.rb
@@ -128,12 +128,43 @@ class TimeTablesController < ChouetteController
scope = self.ransack_period_range(scope: scope, error_message: t('referentials.errors.validity_period'), query: :overlapping)
@q = scope.search(params[:q])
- if sort_column && sort_direction
- @time_tables ||= @q.result(:distinct => true).order("#{sort_column} #{sort_direction}")
- else
- @time_tables ||= @q.result(:distinct => true).order(:comment)
+ @time_tables ||= begin
+ time_tables = @q.result(:distinct => true)
+ if sort_column == "bounding_dates"
+ time_tables = @q.result(:distinct => false).paginate(page: params[:page], per_page: 10)
+ ids = time_tables.pluck(:id).uniq
+ query = """
+ WITH time_tables_dates AS(
+ SELECT time_tables.id, time_table_dates.date FROM time_tables
+ LEFT JOIN time_table_dates ON time_table_dates.time_table_id = time_tables.id
+ WHERE time_table_dates.in_out IS NULL OR time_table_dates.in_out = 't'
+ UNION
+ SELECT time_tables.id, time_table_periods.period_start FROM time_tables
+ LEFT JOIN time_table_periods ON time_table_periods.time_table_id = time_tables.id
+ )
+ SELECT time_tables.id, MIN(time_tables_dates.date) AS min_date FROM time_tables
+ INNER JOIN time_tables_dates ON time_tables_dates.id = time_tables.id
+ WHERE time_tables.id IN (#{ids.map(&:to_s).join(',')})
+ GROUP BY time_tables.id
+ ORDER BY min_date #{sort_direction}
+ """
+
+ ordered_ids = ActiveRecord::Base.connection.exec_query(query).map {|r| r["id"]}
+ order_by = ["CASE"]
+ ordered_ids.each_with_index do |id, index|
+ order_by << "WHEN id='#{id}' THEN #{index}"
+ end
+ order_by << "END"
+ time_tables = time_tables.order(order_by.join(" "))
+ elsif sort_column == "vehicle_journeys_count"
+ time_tables = time_tables.joins("LEFT JOIN time_tables_vehicle_journeys ON time_tables_vehicle_journeys.time_table_id = time_tables.id LEFT JOIN vehicle_journeys ON vehicle_journeys.id = time_tables_vehicle_journeys.vehicle_journey_id")\
+ .group("time_tables.id").select('time_tables.*, COUNT(vehicle_journeys.id) as vehicle_journeys_count').order("#{sort_column} #{sort_direction}")
+ else
+ time_tables = time_tables.order("#{sort_column} #{sort_direction}")
+ end
+ time_tables = time_tables.paginate(page: params[:page], per_page: 10)
+ time_tables
end
- @time_tables = @time_tables.paginate(page: params[:page], per_page: 10)
end
def select_time_tables
@@ -155,7 +186,10 @@ class TimeTablesController < ChouetteController
private
def sort_column
- referential.time_tables.column_names.include?(params[:sort]) ? params[:sort] : 'comment'
+ valid_cols = referential.time_tables.column_names
+ valid_cols << "bounding_dates"
+ valid_cols << "vehicle_journeys_count"
+ valid_cols.include?(params[:sort]) ? params[:sort] : 'comment'
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
diff --git a/app/controllers/vehicle_journeys_collections_controller.rb b/app/controllers/vehicle_journeys_collections_controller.rb
index 712bcc154..a117888ab 100644
--- a/app/controllers/vehicle_journeys_collections_controller.rb
+++ b/app/controllers/vehicle_journeys_collections_controller.rb
@@ -9,8 +9,8 @@ class VehicleJourneysCollectionsController < ChouetteController
alias_method :route, :parent
def update
- state = JSON.parse request.raw_post
- Chouette::VehicleJourney.state_update route, state
+ state = JSON.parse request.raw_post
+ @resources = Chouette::VehicleJourney.state_update route, state
errors = state.any? {|item| item['errors']}
respond_to do |format|
diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb
index 821ea83ff..220f2d29e 100644
--- a/app/controllers/vehicle_journeys_controller.rb
+++ b/app/controllers/vehicle_journeys_controller.rb
@@ -154,7 +154,7 @@ class VehicleJourneysController < ChouetteController
private
def load_custom_fields
- @custom_fields = referential.workgroup&.custom_fields_definitions || {}
+ @custom_fields = Chouette::VehicleJourney.custom_fields_definitions(referential.workgroup)
@extra_headers = Rails.application.config.vehicle_journeys_extra_headers.dup.delete_if do |header|
header[:type] == :custom_field and not @custom_fields.has_key?(header[:name].to_s)
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..646bc1620 100644
--- a/app/decorators/route_decorator.rb
+++ b/app/decorators/route_decorator.rb
@@ -71,6 +71,24 @@ class RouteDecorator < AF83::Decorator
end
end
+ instance_decorator.action_link(
+ secondary: :show,
+ policy: :create_opposite,
+ if: ->{h.has_feature?(:create_opposite_routes)}
+ ) do |l|
+ l.content h.t('routes.create_opposite.title')
+ l.method :post
+ l.disabled { object.opposite_route.present? }
+ 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..702ca0ffc 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -20,8 +20,11 @@ module ApplicationHelper
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
@@ -33,8 +36,8 @@ module ApplicationHelper
display = policy(object).synchronize? if policy(object).respond_to?(:synchronize?) rescue false
if display
info = t('last_update', time: l(object.updated_at, format: :short))
- if object.try(:versions)
- author = object.versions.try(:last).try(:whodunnit) || t('default_whodunnit')
+ if object.has_metadata?
+ author = object.metadata.modifier_username || t('default_whodunnit')
info = "#{info} <br/> #{t('whodunnit', author: author)}"
end
out += content_tag :div, info.html_safe, class: 'small last-update'
@@ -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..abf909929 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
-end
+
+ def compliance_control_metadatas(compliance_control)
+ attributes = resource.class.dynamic_attributes
+ attributes.push(*resource.control_attributes.keys) if resource&.control_attributes&.keys
+
+ {}.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/search_helper.rb b/app/helpers/search_helper.rb
index be70d974d..16081b660 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -15,7 +15,7 @@ module SearchHelper
if val.is_a?(Array)
active = val.any? &:present?
elsif val.is_a?(Hash)
- active = val.values.any? &:present?
+ active = val.values.any? {|v| v.present? && v != "false" }
else
active = true
end
diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb
index 1c9d974a1..032f68494 100644
--- a/app/helpers/stop_areas_helper.rb
+++ b/app/helpers/stop_areas_helper.rb
@@ -68,7 +68,11 @@ module StopAreasHelper
end
def stop_area_registration_number_value stop_area
- stop_area&.registration_number || stop_area&.stop_area_referential&.generate_registration_number
+ stop_area&.registration_number
+ end
+
+ def stop_area_registration_number_hint
+ t "formtastic.hints.stop_area.registration_number"
end
def stop_area_status(stop_area)
diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb
index 63125b161..e2aa2e9ea 100644
--- a/app/helpers/table_builder_helper.rb
+++ b/app/helpers/table_builder_helper.rb
@@ -402,9 +402,10 @@ module TableBuilderHelper
content_tag(
:li,
link_to(
- link.href,
- method: link.method,
- data: link.data
+ link.disabled ? '#' : link.href,
+ method: link.disabled ? nil : link.method,
+ data: link.data,
+ disabled: link.disabled
) do
link.content
end,
diff --git a/app/javascript/helpers/polyfills.js b/app/javascript/helpers/polyfills.js
new file mode 100644
index 000000000..93e3e9846
--- /dev/null
+++ b/app/javascript/helpers/polyfills.js
@@ -0,0 +1,4 @@
+import 'promise-polyfill/src/polyfill'
+import 'es6-symbol/implement'
+import 'polyfill-array-includes'
+import 'whatwg-fetch'
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..a813f4b9e 100644
--- a/app/javascript/journey_patterns/actions/index.js
+++ b/app/javascript/journey_patterns/actions/index.js
@@ -1,10 +1,3 @@
-import Promise from 'promise-polyfill'
-
-// To add to window
-if (!window.Promise) {
- window.Promise = Promise;
-}
-
const actions = {
enterEditMode: () => ({
type: "ENTER_EDIT_MODE"
@@ -43,7 +36,7 @@ const actions = {
}),
updateCheckboxValue : (e, index) => ({
type : 'UPDATE_CHECKBOX_VALUE',
- id : e.currentTarget.id,
+ position : e.currentTarget.id,
index
}),
checkConfirmModal : (event, callback, stateChanged,dispatch) => {
@@ -195,15 +188,19 @@ const actions = {
dispatch(actions.unavailableServer())
} else {
if(json.length != 0){
- let val
- for (val of json){
- for (let stop_point of val.route_short_description.stop_points){
+ let j = 0
+ while(j < json.length){
+ let val = json[j]
+ let i = 0
+ while(i < val.route_short_description.stop_points.length){
+ let stop_point = val.route_short_description.stop_points[i]
stop_point.checked = false
val.stop_area_short_descriptions.map((element) => {
if(element.stop_area_short_description.id === stop_point.id){
stop_point.checked = true
}
})
+ i ++
}
journeyPatterns.push(
_.assign({}, val, {
@@ -212,6 +209,7 @@ const actions = {
deletable: false
})
)
+ j ++
}
}
window.currentItemsLength = journeyPatterns.length
@@ -220,7 +218,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 +238,7 @@ const actions = {
))
})
} else {
+ actions.fetchingRouteCosts = true
fetch(window.routeCostsUrl, {
credentials: 'same-origin',
}).then(response => {
@@ -240,6 +248,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..946c13d9c 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')}</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>
@@ -120,4 +120,4 @@ CreateModal.propTypes = {
onOpenCreateModal: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
onAddJourneyPattern: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/journey_patterns/components/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..4eba80030 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>
)
}
@@ -45,7 +45,7 @@ export default class JourneyPattern extends Component{
<input
onChange = {(e) => this.props.onCheckboxChange(e)}
type='checkbox'
- id={sp.id}
+ id={sp.position}
checked={sp.checked}
disabled={(this.props.value.deletable || this.props.status.policy['journey_patterns.update'] == false || this.props.editMode == false) ? 'disabled' : ''}
>
@@ -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/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js
index 6c38e9288..b046f2b38 100644
--- a/app/javascript/journey_patterns/reducers/journeyPatterns.js
+++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js
@@ -22,7 +22,7 @@ const journeyPattern = (state = {}, action) =>{
}
case 'UPDATE_CHECKBOX_VALUE':
var updatedStopPoints = state.stop_points.map((s) => {
- if (String(s.id) == action.id) {
+ if (String(s.position) == action.position) {
return _.assign({}, s, {checked : !s.checked})
}else {
return s
diff --git a/app/javascript/packs/calendars/edit.js b/app/javascript/packs/calendars/edit.js
index bd09657ec..0c46313b9 100644
--- a/app/javascript/packs/calendars/edit.js
+++ b/app/javascript/packs/calendars/edit.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
diff --git a/app/javascript/packs/exports/new.js b/app/javascript/packs/exports/new.js
index ffe702cdb..b113cc709 100644
--- a/app/javascript/packs/exports/new.js
+++ b/app/javascript/packs/exports/new.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import MasterSlave from "../../helpers/master_slave"
new MasterSlave("form")
diff --git a/app/javascript/packs/journey_patterns/index.js b/app/javascript/packs/journey_patterns/index.js
index 367a8830f..075eea13a 100644
--- a/app/javascript/packs/journey_patterns/index.js
+++ b/app/javascript/packs/journey_patterns/index.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
diff --git a/app/javascript/packs/referential_lines/show.js b/app/javascript/packs/referential_lines/show.js
index 99c5072ef..523f2040f 100644
--- a/app/javascript/packs/referential_lines/show.js
+++ b/app/javascript/packs/referential_lines/show.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import clone from '../../helpers/clone'
import RoutesMap from '../../helpers/routes_map'
diff --git a/app/javascript/packs/referential_overview/overview.js b/app/javascript/packs/referential_overview/overview.js
index 59c326e9a..03da12ef3 100644
--- a/app/javascript/packs/referential_overview/overview.js
+++ b/app/javascript/packs/referential_overview/overview.js
@@ -1 +1,3 @@
+import '../../helpers/polyfills'
+
import ReferentialOverview from '../../referential_overview'
diff --git a/app/javascript/packs/routes/edit.js b/app/javascript/packs/routes/edit.js
index b787bec97..0512b7aff 100644
--- a/app/javascript/packs/routes/edit.js
+++ b/app/javascript/packs/routes/edit.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import React from 'react'
import PropTypes from 'prop-types'
@@ -29,6 +31,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("&#39;", "\'") : '',
area_type: v.area_type,
@@ -39,8 +42,8 @@ const getInitialState = () => {
name: v.name ? v.name.replace("&#39;", "\'") : '',
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("&#39;", "\'") : '',
@@ -55,12 +58,25 @@ const getInitialState = () => {
}
var initialState = { stopPoints: getInitialState() }
-const loggerMiddleware = createLogger()
-let store = createStore(
- reducers,
- initialState,
- applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
-)
+let store = null
+
+if(Object.assign){
+ const loggerMiddleware = createLogger()
+ store = createStore(
+ reducers,
+ initialState,
+ applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
+ )
+}
+else{
+ // IE
+ store = createStore(
+ reducers,
+ initialState,
+ applyMiddleware(thunkMiddleware, promise)
+ )
+}
+
render(
<Provider store={store}>
diff --git a/app/javascript/packs/routes/show.js b/app/javascript/packs/routes/show.js
index c20de0800..e8e068ddd 100644
--- a/app/javascript/packs/routes/show.js
+++ b/app/javascript/packs/routes/show.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import clone from '../../helpers/clone'
import RoutesMap from '../../helpers/routes_map'
diff --git a/app/javascript/packs/stop_areas/new.js b/app/javascript/packs/stop_areas/new.js
index ffe702cdb..b113cc709 100644
--- a/app/javascript/packs/stop_areas/new.js
+++ b/app/javascript/packs/stop_areas/new.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import MasterSlave from "../../helpers/master_slave"
new MasterSlave("form")
diff --git a/app/javascript/packs/time_tables/edit.js b/app/javascript/packs/time_tables/edit.js
index cf058d501..873bdea50 100644
--- a/app/javascript/packs/time_tables/edit.js
+++ b/app/javascript/packs/time_tables/edit.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
diff --git a/app/javascript/packs/vehicle_journeys/index.js b/app/javascript/packs/vehicle_journeys/index.js
index 9cad3870e..0b4351d26 100644
--- a/app/javascript/packs/vehicle_journeys/index.js
+++ b/app/javascript/packs/vehicle_journeys/index.js
@@ -1,3 +1,5 @@
+import '../../helpers/polyfills'
+
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
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..908e97263 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'>
@@ -40,13 +42,13 @@ export default function StopPoint(props, {I18n}) {
<div
className={'btn btn-link' + (props.first ? ' disabled' : '')}
- onClick={props.onMoveUpClick}
+ onClick={props.first ? null : props.onMoveUpClick}
>
<span className='fa fa-arrow-up'></span>
</div>
<div
className={'btn btn-link' + (props.last ? ' disabled' : '')}
- onClick={props.onMoveDownClick}
+ onClick={props.last ? null : props.onMoveDownClick}
>
<span className='fa fa-arrow-down'></span>
</div>
diff --git a/app/javascript/routes/index.js b/app/javascript/routes/index.js
index febae7d54..fc99a3086 100644
--- a/app/javascript/routes/index.js
+++ b/app/javascript/routes/index.js
@@ -22,10 +22,12 @@ const getInitialState = () => {
let fancyText = v.name.replace("&#39;", "\'")
if(v.zip_code && v.city_name)
fancyText += ", " + v.zip_code + " " + v.city_name.replace("&#39;", "\'")
+ forAlightingAndBoarding = v.stoparea_kind === 'commercial' ? 'normal' : 'forbidden'
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("&#39;", "\'") : '',
area_type: v.area_type,
@@ -36,8 +38,8 @@ const getInitialState = () => {
name: v.name ? v.name.replace("&#39;", "\'") : '',
registration_number: v.registration_number,
text: fancyText,
- for_boarding: v.for_boarding || "normal",
- for_alighting: v.for_alighting || "normal",
+ for_boarding: v.for_boarding || forAlightingAndBoarding,
+ for_alighting: v.for_alighting || forAlightingAndBoarding,
longitude: v.longitude || 0,
latitude: v.latitude || 0,
comment: v.comment ? v.comment.replace("&#39;", "\'") : '',
diff --git a/app/javascript/routes/reducers/stopPoints.js b/app/javascript/routes/reducers/stopPoints.js
index 0b42b504f..54142338d 100644
--- a/app/javascript/routes/reducers/stopPoints.js
+++ b/app/javascript/routes/reducers/stopPoints.js
@@ -61,6 +61,7 @@ const stopPoints = (state = [], action) => {
case 'UPDATE_INPUT_VALUE':
return state.map( (t, i) => {
if (i === action.index) {
+ let forAlightingAndBoarding = action.text.stoparea_kind === 'commercial' ? 'normal' : 'forbidden'
return _.assign(
{},
t,
@@ -68,6 +69,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,
@@ -76,7 +78,9 @@ const stopPoints = (state = [], action) => {
area_type: action.text.area_type,
city_name: action.text.city_name,
comment: action.text.comment,
- registration_number: action.text.registration_number
+ registration_number: action.text.registration_number,
+ for_alighting: forAlightingAndBoarding,
+ for_boarding: forAlightingAndBoarding
}
)
} else {
diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js
index 98b9eab4b..a5c454a18 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) => {
@@ -192,10 +191,13 @@ const actions = {
isInPeriod: (periods, date) => {
date = new Date(date)
- for (let period of periods) {
+ let i = 0;
+ while(i < periods.length){
+ let period = periods[i]
let begin = new Date(period.period_start)
let end = new Date(period.period_end)
if (date >= begin && date <= end) return true
+ i++
}
return false
@@ -236,12 +238,14 @@ const actions = {
let error = ''
start = new Date(start)
end = new Date(end)
-
- for (let day of in_days) {
+ let i = 0;
+ while(i < in_days){
+ let day = in_days[i]
if (start <= new Date(day.date) && end >= new Date(day.date)) {
error = I18n.t('time_tables.edit.error_submit.dates_overlaps')
break
}
+ i ++
}
return error
},
@@ -307,10 +311,11 @@ const actions = {
})
},
errorModalKey: (periods, dayTypes) => {
- const withoutPeriodsWithDaysTypes = reject(periods, 'deleted').length == 0 && some(dayTypes) && "withoutPeriodsWithDaysTypes"
+ // const withoutPeriodsWithDaysTypes = reject(periods, 'deleted').length == 0 && some(dayTypes) && "withoutPeriodsWithDaysTypes"
const withPeriodsWithoutDayTypes = reject(periods, 'deleted').length > 0 && every(dayTypes, dt => dt == false) && "withPeriodsWithoutDayTypes"
- return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes")
+ // return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes")
+ return withPeriodsWithoutDayTypes
},
errorModalMessage: (errorKey) => {
diff --git a/app/javascript/time_tables/components/ConfirmModal.js b/app/javascript/time_tables/components/ConfirmModal.js
index 4e8583bc0..e4219348d 100644
--- a/app/javascript/time_tables/components/ConfirmModal.js
+++ b/app/javascript/time_tables/components/ConfirmModal.js
@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
-export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}, {I18n}) {
+export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}) {
return (
<div className={'modal fade ' + ((modal.type == 'confirm') ? 'in' : '')} id='ConfirmModal'>
<div className='modal-container'>
@@ -45,8 +45,4 @@ ConfirmModal.propTypes = {
modal: PropTypes.object.isRequired,
onModalAccept: PropTypes.func.isRequired,
onModalCancel: PropTypes.func.isRequired
-}
-
-ConfirmModal.contextTypes = {
- I18n: PropTypes.object
-}
+} \ No newline at end of file
diff --git a/app/javascript/time_tables/components/ErrorModal.js b/app/javascript/time_tables/components/ErrorModal.js
index 8af12f1d1..a512d28fd 100644
--- a/app/javascript/time_tables/components/ErrorModal.js
+++ b/app/javascript/time_tables/components/ErrorModal.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import actions from '../actions'
-export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {
+export default function ErrorModal({dispatch, modal, onModalClose}) {
return (
<div className={'modal fade ' + ((modal.type == 'error') ? 'in' : '')} id='ErrorModal'>
<div className='modal-container'>
@@ -37,8 +37,4 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {
ErrorModal.propTypes = {
modal: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired
-}
-
-ErrorModal.contextTypes = {
- I18n: PropTypes.object
-}
+} \ No newline at end of file
diff --git a/app/javascript/time_tables/components/Metas.js b/app/javascript/time_tables/components/Metas.js
index 08a6e26fe..d9746a379 100644
--- a/app/javascript/time_tables/components/Metas.js
+++ b/app/javascript/time_tables/components/Metas.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import actions from '../actions'
import TagsSelect2 from './TagsSelect2'
-export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelect2Tags, onUnselect2Tags}, {I18n}) {
+export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdateColor, onSelect2Tags, onUnselect2Tags}) {
let colorList = ["", "#9B9B9B", "#FFA070", "#C67300", "#7F551B", "#41CCE3", "#09B09C", "#3655D7", "#6321A0", "#E796C6", "#DD2DAA"]
return (
<div className='form-horizontal'>
@@ -134,8 +134,4 @@ Metas.propTypes = {
onUpdateColor: PropTypes.func.isRequired,
onSelect2Tags: PropTypes.func.isRequired,
onUnselect2Tags: PropTypes.func.isRequired
-}
-
-Metas.contextTypes = {
- I18n: PropTypes.object
-}
+} \ No newline at end of file
diff --git a/app/javascript/time_tables/components/PeriodForm.js b/app/javascript/time_tables/components/PeriodForm.js
index d17a246f7..36ed6cfdf 100644
--- a/app/javascript/time_tables/components/PeriodForm.js
+++ b/app/javascript/time_tables/components/PeriodForm.js
@@ -33,7 +33,7 @@ const makeYearsOptions = (yearSelected) => {
return arr
}
-export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriodForm, onUpdatePeriodForm, onValidatePeriodForm}, {I18n}) {
+export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriodForm, onUpdatePeriodForm, onValidatePeriodForm}) {
return (
<div className="container-fluid">
<div className="row">
@@ -143,8 +143,4 @@ PeriodForm.propTypes = {
onUpdatePeriodForm: PropTypes.func.isRequired,
onValidatePeriodForm: PropTypes.func.isRequired,
timetable: PropTypes.object.isRequired
-}
-
-PeriodForm.contextTypes = {
- I18n: PropTypes.object
-}
+} \ No newline at end of file
diff --git a/app/javascript/time_tables/components/PeriodManager.js b/app/javascript/time_tables/components/PeriodManager.js
index 6b817fe73..6871d0b9b 100644
--- a/app/javascript/time_tables/components/PeriodManager.js
+++ b/app/javascript/time_tables/components/PeriodManager.js
@@ -55,7 +55,7 @@ export default class PeriodManager extends Component {
type='button'
onClick={() => this.props.onOpenEditPeriodForm(this.props.value, this.props.index)}
>
- Modifier
+ {I18n.t('actions.edit')}
</button>
</li>
<li className='delete-action'>
@@ -64,7 +64,7 @@ export default class PeriodManager extends Component {
onClick={() => this.props.onDeletePeriod(this.props.index, this.props.metas.day_types)}
>
<span className='fa fa-trash'></span>
- Supprimer
+ {I18n.t('actions.destroy')}
</button>
</li>
</ul>
@@ -79,8 +79,4 @@ PeriodManager.propTypes = {
currentDate: PropTypes.object.isRequired,
onDeletePeriod: PropTypes.func.isRequired,
onOpenEditPeriodForm: PropTypes.func.isRequired
-}
-
-PeriodManager.contextTypes = {
- I18n: PropTypes.object
} \ No newline at end of file
diff --git a/app/javascript/time_tables/components/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/components/TagsSelect2.js b/app/javascript/time_tables/components/TagsSelect2.js
index 43cf59fdf..dd8d6e9c0 100644
--- a/app/javascript/time_tables/components/TagsSelect2.js
+++ b/app/javascript/time_tables/components/TagsSelect2.js
@@ -40,7 +40,7 @@ export default class TagsSelect2 extends Component {
allowClear: true,
theme: 'bootstrap',
width: '100%',
- placeholder: this.context.I18n.t('time_tables.edit.select2.tag.placeholder'),
+ placeholder: I18n.t('time_tables.edit.select2.tag.placeholder'),
ajax: {
url: origin + path + '/tags.json',
dataType: 'json',
@@ -74,8 +74,4 @@ export default class TagsSelect2 extends Component {
const formatRepo = (props) => {
if(props.name) return props.name
-}
-
-TagsSelect2.contextTypes = {
- I18n: PropTypes.object
} \ No newline at end of file
diff --git a/app/javascript/time_tables/components/Timetable.js b/app/javascript/time_tables/components/Timetable.js
index 991f31435..3779fa2d0 100644
--- a/app/javascript/time_tables/components/Timetable.js
+++ b/app/javascript/time_tables/components/Timetable.js
@@ -31,11 +31,11 @@ export default class Timetable extends Component {
<div className="table table-2entries mb-sm">
<div className="t2e-head w20">
<div className="th">
- <div className="strong">{this.context.I18n.t('time_tables.edit.synthesis')}</div>
+ <div className="strong">{I18n.t('time_tables.edit.synthesis')}</div>
</div>
- <div className="td"><span>{this.context.I18n.t('time_tables.edit.day_types')}</span></div>
- <div className="td"><span>{this.context.I18n.t('time_tables.edit.periods')}</span></div>
- <div className="td"><span>{this.context.I18n.t('time_tables.edit.exceptions')}</span></div>
+ <div className="td"><span>{I18n.t('time_tables.edit.day_types')}</span></div>
+ <div className="td"><span>{I18n.t('time_tables.edit.periods')}</span></div>
+ <div className="td"><span>{I18n.t('time_tables.edit.exceptions')}</span></div>
</div>
<div className="t2e-item-list w80">
<div>
@@ -109,8 +109,4 @@ Timetable.propTypes = {
onDeletePeriod: PropTypes.func.isRequired,
onExcludeDateFromPeriod: PropTypes.func.isRequired,
onIncludeDateInPeriod: PropTypes.func.isRequired
-}
-
-Timetable.contextTypes = {
- I18n: PropTypes.object
-}
+} \ No newline at end of file
diff --git a/app/javascript/time_tables/containers/App.js b/app/javascript/time_tables/containers/App.js
index 5963f8f1d..c12e33ef5 100644
--- a/app/javascript/time_tables/containers/App.js
+++ b/app/javascript/time_tables/containers/App.js
@@ -10,7 +10,6 @@ import SaveTimetable from './SaveTimetable'
import ConfirmModal from './ConfirmModal'
import ErrorModal from './ErrorModal'
import clone from '../../helpers/clone'
-const I18n = clone(window, "I18n", true)
class App extends Component {
componentDidMount(){
diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js
index e00e9b1b0..d51012cdb 100644
--- a/app/javascript/vehicle_journeys/actions/index.js
+++ b/app/javascript/vehicle_journeys/actions/index.js
@@ -1,9 +1,3 @@
-import Promise from 'promise-polyfill'
-
-// To add to window
-if (!window.Promise) {
- window.Promise = Promise;
-}
import { batchActions } from '../batch'
const actions = {
@@ -113,14 +107,9 @@ const actions = {
type : 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL',
vehicleJourneys
}),
- selectPurchaseWindowsModal: (selectedWindow) =>({
+ selectPurchaseWindowsModal: (selectedItem) =>({
type: 'SELECT_PURCHASE_WINDOW_MODAL',
- selectedItem:{
- id: selectedWindow.id,
- name: selectedWindow.name,
- color: selectedWindow.color,
- objectid: selectedWindow.objectid
- }
+ selectedItem
}),
addSelectedPurchaseWindow: () => ({
type: 'ADD_SELECTED_PURCHASE_WINDOW'
@@ -344,16 +333,23 @@ const actions = {
if(hasError == true) {
dispatch(actions.unavailableServer())
} else {
- let val
- for (val of json.vehicle_journeys){
+ let i = 0
+ while(i < json.vehicle_journeys.length){
+ let val = json.vehicle_journeys[i]
+ i++
var timeTables = []
var purchaseWindows = []
- let tt
- for (tt of val.time_tables){
+ let k = 0
+ while(k < val.time_tables.length){
+ let tt = val.time_tables[k]
+ k++
timeTables.push(tt)
}
if(val.purchase_windows){
- for (tt of val.purchase_windows){
+ k = 0
+ while(k < val.purchase_windows.length){
+ let tt = val.purchase_windows[k]
+ k++
purchaseWindows.push(tt)
}
}
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/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js
index ae3ab3476..93fe015a8 100644
--- a/app/javascript/vehicle_journeys/components/Filters.js
+++ b/app/javascript/vehicle_journeys/components/Filters.js
@@ -145,12 +145,12 @@ export default function Filters({filters, pagination, missions, onFilter, onRese
<span
className='btn btn-link'
onClick={(e) => onResetFilters(e, pagination)}>
- Effacer
+ {I18n.t('actions.erase')}
</span>
<span
className='btn btn-default'
onClick={(e) => onFilter(e, pagination)}>
- Filtrer
+ {I18n.t('actions.filter')}
</span>
</div>
</div>
diff --git a/app/javascript/vehicle_journeys/components/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/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js
index c6f59ce9d..e4f5ad11c 100644
--- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js
@@ -87,16 +87,18 @@ export default class VehicleJourneys extends Component {
}
toggleTimetables(e) {
- $('.table-2entries .detailed-timetables').toggleClass('hidden')
- $('.table-2entries .detailed-timetables-bt').toggleClass('active')
+ let root = $(this.refs['vehicleJourneys'])
+ root.find('.table-2entries .detailed-timetables').toggleClass('hidden')
+ root.find('.table-2entries .detailed-timetables-bt').toggleClass('active')
this.componentDidUpdate()
e.preventDefault()
false
}
togglePurchaseWindows(e) {
- $('.table-2entries .detailed-purchase-windows').toggleClass('hidden')
- $('.table-2entries .detailed-purchase-windows-bt').toggleClass('active')
+ let root = $(this.refs['vehicleJourneys'])
+ root.find('.table-2entries .detailed-purchase-windows').toggleClass('hidden')
+ root.find('.table-2entries .detailed-purchase-windows-bt').toggleClass('active')
this.componentDidUpdate()
e.preventDefault()
false
@@ -186,7 +188,7 @@ export default class VehicleJourneys extends Component {
)
} else {
return (
- <div className='row'>
+ <div className='row' ref='vehicleJourneys'>
<div className='col-lg-12'>
{(this.props.status.fetchSuccess == false) && (
<div className='alert alert-danger mt-sm'>
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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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..60ad439b8 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,8 +30,8 @@ export default class BSelect4 extends Component {
allowClear: true,
theme: 'bootstrap',
width: '100%',
- placeholder: 'Filtrer par transporteur...',
- language: require('./fr'),
+ placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.affect_company'),
+ language: require('./language'),
ajax: {
url: origin + path + '/companies.json' + '?line_id=' + line,
dataType: 'json',
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js
index 72dbd0152..cec39ab4e 100644
--- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js
@@ -74,8 +74,8 @@ export default class BSelect4 extends Component {
width: '100%',
escapeMarkup: function (markup) { return markup; },
templateResult: formatRepo,
- placeholder: 'Filtrer par code, nom ou OID de mission...',
- language: require('./fr'),
+ placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.journey_pattern'),
+ language: require('./language'),
allowClear: false,
escapeMarkup: function (markup) { return markup; },
}
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js
index 0339455ca..d5aad20d0 100644
--- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js
@@ -26,8 +26,8 @@ export default class BSelect4 extends Component {
allowClear: false,
theme: 'bootstrap',
width: '100%',
- placeholder: 'Filtrer par calendrier...',
- language: require('./fr'),
+ placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.timetable'),
+ language: require('./language'),
ajax: {
url: origin + path + this.props.chunkURL,
dataType: 'json',
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js
index ccb4c9595..50a941b6d 100644
--- a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js
@@ -25,9 +25,9 @@ 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'),
+ language: require('./language'),
ajax: {
url: origin + path + '/vehicle_journeys.json',
dataType: 'json',
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/fr.js b/app/javascript/vehicle_journeys/components/tools/select2s/fr.js
deleted file mode 100644
index 20154d412..000000000
--- a/app/javascript/vehicle_journeys/components/tools/select2s/fr.js
+++ /dev/null
@@ -1,9 +0,0 @@
-module.exports = {
- errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},
- inputTooLong:function(e){var t=e.input.length-e.maximum,n="Supprimez "+t+" caractère";return t!==1&&(n+="s"),n},
- inputTooShort:function(e){var t=e.minimum-e.input.length,n="Saisissez "+t+" caractère";return t!==1&&(n+="s"),n},
- loadingMore:function(){return"Chargement de résultats supplémentaires…"},
- maximumSelected:function(e){var t="Vous pouvez seulement sélectionner "+e.maximum+" élément";return e.maximum!==1&&(t+="s"),t},
- noResults:function(){return"Aucun résultat trouvé"},
- searching:function(){return"Recherche en cours…"}
-}
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/language.js b/app/javascript/vehicle_journeys/components/tools/select2s/language.js
new file mode 100644
index 000000000..9d587f96e
--- /dev/null
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/language.js
@@ -0,0 +1,9 @@
+module.exports = {
+ errorLoading: () => I18n.t('select2.error_loading'),
+ inputTooLong: (e) => I18n.t('select2.input_too_short', { count: e.input.length - e.maximum}),
+ inputTooShort: (e) => I18n.t('select2.input_too_long', { count: e.minimum - e.input.length }),
+ loadingMore: () => I18n.t('select2.loading_more'),
+ maximumSelected: (e) => I18n.t('select2.maximum_selected', {count: e.maximum}),
+ noResults: () => I18n.t('select2.no_results'),
+ searching: () => I18n.t('select2.searching')
+}
diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
index 6524f8d6f..b02c19a69 100644
--- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
@@ -9,32 +9,35 @@ const vehicleJourney= (state = {}, action, keep) => {
return _.assign({}, state, {selected: false})
case 'ADD_VEHICLEJOURNEY':
let pristineVjasList = []
- let prevSp = action.stopPointsList[0]
+ let prevSp
let current_time = {
hour: 0,
minute: 0
}
let computeSchedule = false
- let userTZOffet = 0
+ let initTZOffet = 0
if(action.data["start_time.hour"] && action.data["start_time.hour"].value && action.data["start_time.hour"].value.length > 0 && action.data["start_time.minute"] && action.selectedJourneyPattern.full_schedule && action.selectedJourneyPattern.costs){
computeSchedule = true
- userTZOffet = action.data["tz_offset"] && parseInt(action.data["tz_offset"].value) || 0
- current_time.hour = parseInt(action.data["start_time.hour"].value) + parseInt(userTZOffet / 60)
+ initTZOffet = - action.stopPointsList[0].time_zone_offset / 60 || 0
+ current_time.hour = parseInt(action.data["start_time.hour"].value) + parseInt(initTZOffet / 60)
current_time.minute = 0
if(action.data["start_time.minute"].value){
- current_time.minute = parseInt(action.data["start_time.minute"].value) + (userTZOffet - 60 * parseInt(userTZOffet / 60))
+ current_time.minute = parseInt(action.data["start_time.minute"].value) + (initTZOffet - 60 * parseInt(initTZOffet / 60))
}
}
_.each(action.stopPointsList, (sp) =>{
let inJourney = false
let newVjas
if(computeSchedule){
- if(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){
+ if(prevSp && action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){
let delta = parseInt(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id].time)
current_time = actions.addMinutesToTime(current_time, delta)
prevSp = sp
inJourney = true
}
+ if(!prevSp){
+ prevSp = sp
+ }
let offsetHours = sp.time_zone_offset / 3600
let offsetminutes = sp.time_zone_offset/60 - 60*offsetHours
newVjas = {
diff --git a/app/models/api/v1/api_key.rb b/app/models/api/v1/api_key.rb
index 09c6f77ac..e6ceb977a 100644
--- a/app/models/api/v1/api_key.rb
+++ b/app/models/api/v1/api_key.rb
@@ -1,7 +1,8 @@
module Api
module V1
- class ApiKey < ::ActiveRecord::Base
- has_paper_trail
+ class ApiKey < ::ApplicationModel
+ has_metadata
+
before_create :generate_access_token
belongs_to :referential, :class_name => '::Referential'
belongs_to :organisation, :class_name => '::Organisation'
@@ -47,4 +48,3 @@ module Api
end
end
end
-
diff --git a/app/models/application_model.rb b/app/models/application_model.rb
new file mode 100644
index 000000000..1a2a5099d
--- /dev/null
+++ b/app/models/application_model.rb
@@ -0,0 +1,5 @@
+class ApplicationModel < ::ActiveRecord::Base
+ include MetadataSupport
+
+ self.abstract_class = true
+end
diff --git a/app/models/calendar.rb b/app/models/calendar.rb
index 84b569ab4..39e2b2cff 100644
--- a/app/models/calendar.rb
+++ b/app/models/calendar.rb
@@ -2,18 +2,17 @@ require 'range_ext'
require_relative 'calendar/date_value'
require_relative 'calendar/period'
-class Calendar < ActiveRecord::Base
+class Calendar < ApplicationModel
include DateSupport
include PeriodSupport
include ApplicationDaysSupport
include TimetableSupport
- has_paper_trail class_name: 'PublicVersion'
+ has_metadata
belongs_to :organisation
belongs_to :workgroup
- 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/date_value.rb b/app/models/calendar/date_value.rb
index a4a405d43..f50b4237c 100644
--- a/app/models/calendar/date_value.rb
+++ b/app/models/calendar/date_value.rb
@@ -1,4 +1,4 @@
-class Calendar < ActiveRecord::Base
+class Calendar < ApplicationModel
class DateValue
include ActiveAttr::Model
diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb
index 8b3e4109b..07926e818 100644
--- a/app/models/calendar/period.rb
+++ b/app/models/calendar/period.rb
@@ -1,4 +1,4 @@
-class Calendar < ActiveRecord::Base
+class Calendar < ApplicationModel
class Period
include ActiveAttr::Model
diff --git a/app/models/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/access_link.rb b/app/models/chouette/access_link.rb
index 6b08443be..7ab8ca715 100644
--- a/app/models/chouette/access_link.rb
+++ b/app/models/chouette/access_link.rb
@@ -1,6 +1,6 @@
module Chouette
class AccessLink < Chouette::TridentActiveRecord
- has_paper_trail
+ has_metadata
include ObjectidSupport
attr_accessor :access_link_type, :link_orientation_type, :link_key
diff --git a/app/models/chouette/access_point.rb b/app/models/chouette/access_point.rb
index ac6580015..884460881 100644
--- a/app/models/chouette/access_point.rb
+++ b/app/models/chouette/access_point.rb
@@ -4,7 +4,7 @@ require 'geo_ruby'
module Chouette
class AccessPoint < Chouette::ActiveRecord
- has_paper_trail
+ has_metadata
include Geokit::Mappable
include ProjectionFields
diff --git a/app/models/chouette/active_record.rb b/app/models/chouette/active_record.rb
index c2aab9d50..27f5426b3 100644
--- a/app/models/chouette/active_record.rb
+++ b/app/models/chouette/active_record.rb
@@ -1,7 +1,8 @@
#require "active_record"
require 'deep_cloneable'
module Chouette
- class ActiveRecord < ::ActiveRecord::Base
+ class ActiveRecord < ::ApplicationModel
+
self.abstract_class = true
before_save :nil_if_blank, :set_data_source_ref
diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb
index 53e412600..9d5737a6c 100644
--- a/app/models/chouette/company.rb
+++ b/app/models/chouette/company.rb
@@ -1,13 +1,15 @@
module Chouette
class Company < Chouette::ActiveRecord
+ has_metadata
+
include CompanyRestrictions
include LineReferentialSupport
include ObjectidSupport
- has_paper_trail class_name: 'PublicVersion'
+ include CustomFieldsSupport
has_many :lines
- validates_format_of :registration_number, :with => %r{\A[0-9A-Za-z_-]+\Z}, :allow_nil => true, :allow_blank => true
+ # validates_format_of :registration_number, :with => %r{\A[0-9A-Za-z_-]+\Z}, :allow_nil => true, :allow_blank => true
validates_presence_of :name
validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true
diff --git a/app/models/chouette/connection_link.rb b/app/models/chouette/connection_link.rb
index c53d6f5f1..fb93e5f90 100644
--- a/app/models/chouette/connection_link.rb
+++ b/app/models/chouette/connection_link.rb
@@ -1,6 +1,6 @@
module Chouette
class ConnectionLink < Chouette::TridentActiveRecord
- has_paper_trail
+ has_metadata
include ObjectidSupport
include ConnectionLinkRestrictions
diff --git a/app/models/chouette/for_alighting_enumerations.rb b/app/models/chouette/for_alighting_enumerations.rb
index ab07a670d..2e15fcb58 100644
--- a/app/models/chouette/for_alighting_enumerations.rb
+++ b/app/models/chouette/for_alighting_enumerations.rb
@@ -3,6 +3,6 @@ module Chouette
extend Enumerize
extend ActiveModel::Naming
- enumerize :for_alighting, in: %w[normal forbidden request_stop is_flexible]
+ enumerize :for_alighting, in: %w[normal forbidden request_stop is_flexible], default: :normal
end
end
diff --git a/app/models/chouette/for_boarding_enumerations.rb b/app/models/chouette/for_boarding_enumerations.rb
index 48f8762c2..0190bf805 100644
--- a/app/models/chouette/for_boarding_enumerations.rb
+++ b/app/models/chouette/for_boarding_enumerations.rb
@@ -3,6 +3,6 @@ module Chouette
extend Enumerize
extend ActiveModel::Naming
- enumerize :for_boarding, in: %w[normal forbidden request_stop is_flexible]
+ enumerize :for_boarding, in: %w[normal forbidden request_stop is_flexible], default: :normal
end
end
diff --git a/app/models/chouette/group_of_line.rb b/app/models/chouette/group_of_line.rb
index 3b6a7cea7..a30c34ce7 100644
--- a/app/models/chouette/group_of_line.rb
+++ b/app/models/chouette/group_of_line.rb
@@ -1,6 +1,6 @@
module Chouette
class GroupOfLine < Chouette::ActiveRecord
- has_paper_trail
+ has_metadata
include ObjectidSupport
include GroupOfLineRestrictions
include LineReferentialSupport
diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb
index 5a5132200..830a6a808 100644
--- a/app/models/chouette/journey_pattern.rb
+++ b/app/models/chouette/journey_pattern.rb
@@ -1,6 +1,6 @@
module Chouette
class JourneyPattern < Chouette::TridentActiveRecord
- has_paper_trail
+ has_metadata
include ChecksumSupport
include JourneyPatternRestrictions
include ObjectidSupport
diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb
index ae7c25377..4b5d1a68d 100644
--- a/app/models/chouette/line.rb
+++ b/app/models/chouette/line.rb
@@ -1,6 +1,6 @@
module Chouette
class Line < Chouette::ActiveRecord
- has_paper_trail class_name: 'PublicVersion'
+ has_metadata
include LineRestrictions
include LineReferentialSupport
include ObjectidSupport
@@ -29,7 +29,7 @@ module Chouette
# validates_presence_of :network
# validates_presence_of :company
- validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true
+ # validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true
validates_format_of :stable_id, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true
validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true
validates_format_of :color, :with => %r{\A[0-9a-fA-F]{6}\Z}, :allow_nil => true, :allow_blank => true
@@ -51,6 +51,14 @@ module Chouette
)
}
+ scope :for_organisation, ->(organisation){
+ if objectids = organisation&.lines_scope
+ where(objectid: objectids)
+ else
+ all
+ end
+ }
+
def self.nullable_attributes
[:published_name, :number, :comment, :url, :color, :text_color, :stable_id]
end
diff --git a/app/models/chouette/network.rb b/app/models/chouette/network.rb
index 942fc5d67..4802d7592 100644
--- a/app/models/chouette/network.rb
+++ b/app/models/chouette/network.rb
@@ -1,6 +1,6 @@
module Chouette
class Network < Chouette::ActiveRecord
- has_paper_trail class_name: 'PublicVersion'
+ has_metadata
include NetworkRestrictions
include LineReferentialSupport
include ObjectidSupport
diff --git a/app/models/chouette/pt_link.rb b/app/models/chouette/pt_link.rb
index 399539d44..680632a14 100644
--- a/app/models/chouette/pt_link.rb
+++ b/app/models/chouette/pt_link.rb
@@ -2,7 +2,7 @@ require 'geokit'
module Chouette
class PtLink < Chouette::ActiveRecord
- has_paper_trail
+ has_metadata
include Geokit::Mappable
def geometry
diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb
index 4c8014780..e10b106ec 100644
--- a/app/models/chouette/purchase_window.rb
+++ b/app/models/chouette/purchase_window.rb
@@ -11,7 +11,7 @@ module Chouette
enumerize :color, in: %w(#9B9B9B #FFA070 #C67300 #7F551B #41CCE3 #09B09C #3655D7 #6321A0 #E796C6 #DD2DAA)
- has_paper_trail
+ has_metadata
belongs_to :referential
has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney'
diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb
index 13288bc6b..c5e909d7e 100644
--- a/app/models/chouette/route.rb
+++ b/app/models/chouette/route.rb
@@ -1,15 +1,25 @@
module Chouette
class Route < Chouette::TridentActiveRecord
- has_paper_trail
+ has_metadata
+
include RouteRestrictions
include ChecksumSupport
include ObjectidSupport
extend Enumerize
+ if ENV["CHOUETTE_ROUTE_POSITION_CHECK"] == "true" || !Rails.env.production?
+ after_commit do
+ positions = stop_points.pluck(:position)
+ Rails.logger.debug "Check positions in Route #{id} : #{positions.inspect}"
+ if positions.size != positions.uniq.size
+ raise "DUPLICATED stop_points positions in Route #{id} : #{positions.inspect}"
+ end
+ end
+ end
+
enumerize :direction, in: %i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)
enumerize :wayback, in: %i(outbound inbound), default: :outbound
-
def self.nullable_attributes
[:published_name, :comment, :number, :name, :direction, :wayback]
end
@@ -68,28 +78,36 @@ module Chouette
validates_presence_of :published_name
validates_presence_of :line
validates :wayback, inclusion: { in: self.wayback.values }
+ after_commit :calculate_costs!,
+ on: [:create, :update],
+ if: ->() { TomTom.enabled? }
- after_save :calculate_costs!, if: ->() { TomTom.enabled? }
-
- def duplicate
+ def duplicate opposite=false
overrides = {
'opposite_route_id' => nil,
'name' => I18n.t('activerecord.copy', name: self.name)
}
+ keys_for_create = attributes.keys - %w{id objectid created_at updated_at}
atts_for_create = attributes
- .slice!(*%w{id objectid created_at updated_at})
+ .slice(*keys_for_create)
.merge(overrides)
+ if opposite
+ atts_for_create[:wayback] = self.opposite_wayback
+ atts_for_create[:name] = I18n.t('routes.opposite', name: self.name)
+ atts_for_create[:published_name] = atts_for_create[:name]
+ atts_for_create[:opposite_route_id] = self.id
+ end
new_route = self.class.create!(atts_for_create)
- duplicate_stop_points(for_route: new_route)
+ duplicate_stop_points(for_route: new_route, opposite: opposite)
new_route
end
- def duplicate_stop_points(for_route:)
- stop_points.each(&duplicate_stop_point(for_route: for_route))
+ def duplicate_stop_points(for_route:, opposite: false)
+ stop_points.each(&duplicate_stop_point(for_route: for_route, opposite: opposite))
end
- def duplicate_stop_point(for_route:)
+ def duplicate_stop_point(for_route:, opposite: false)
-> stop_point do
- stop_point.duplicate(for_route: for_route)
+ stop_point.duplicate(for_route: for_route, opposite: opposite)
end
end
diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb
index 58703598e..886eadc6c 100644
--- a/app/models/chouette/routing_constraint_zone.rb
+++ b/app/models/chouette/routing_constraint_zone.rb
@@ -1,6 +1,6 @@
module Chouette
class RoutingConstraintZone < Chouette::TridentActiveRecord
- has_paper_trail
+ has_metadata
include ChecksumSupport
include ObjectidSupport
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index ccdff609f..4ddc7403b 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -2,11 +2,12 @@ require 'geokit'
require 'geo_ruby'
module Chouette
class StopArea < Chouette::ActiveRecord
- has_paper_trail class_name: 'PublicVersion'
+ has_metadata
include ProjectionFields
include StopAreaRestrictions
include StopAreaReferentialSupport
include ObjectidSupport
+ include CustomFieldsSupport
extend Enumerize
enumerize :area_type, in: Chouette::AreaType::ALL
@@ -32,7 +33,7 @@ module Chouette
after_update :journey_patterns_control_route_sections,
if: Proc.new { |stop_area| ['boarding_position', 'quay'].include? stop_area.stop_area_type }
- validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_blank => true
+ # validates_format_of :registration_number, :with => %r{\A[\d\w_:\-]+\Z}, :allow_blank => true
validates_presence_of :name
validates_presence_of :kind
validates_presence_of :latitude, :if => :longitude
@@ -49,7 +50,7 @@ module Chouette
validate :registration_number_is_set
before_validation do
- self.registration_number ||= self.stop_area_referential.generate_registration_number
+ self.registration_number = self.stop_area_referential.generate_registration_number unless self.registration_number.present?
end
def self.nullable_attributes
@@ -90,7 +91,7 @@ module Chouette
end
unless self.stop_area_referential.validates_registration_number(self.registration_number)
- errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid'))
+ errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid', mask: self.stop_area_referential.registration_number_format))
end
end
diff --git a/app/models/chouette/stop_point.rb b/app/models/chouette/stop_point.rb
index 6b363cd93..1df1a664a 100644
--- a/app/models/chouette/stop_point.rb
+++ b/app/models/chouette/stop_point.rb
@@ -1,6 +1,6 @@
module Chouette
class StopPoint < Chouette::TridentActiveRecord
- has_paper_trail
+ has_metadata
def self.policy_class
RoutePolicy
end
@@ -39,11 +39,12 @@ module Chouette
end
end
- def duplicate(for_route:)
+ def duplicate(for_route:, opposite: false)
keys_for_create = attributes.keys - %w{id objectid created_at updated_at}
atts_for_create = attributes
.slice(*keys_for_create)
.merge('route_id' => for_route.id)
+ atts_for_create["position"] = self.route.stop_points.size - atts_for_create["position"] if opposite
self.class.create!(atts_for_create)
end
diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb
index 506e498b8..b59c95665 100644
--- a/app/models/chouette/time_table.rb
+++ b/app/models/chouette/time_table.rb
@@ -1,6 +1,6 @@
module Chouette
class TimeTable < Chouette::TridentActiveRecord
- has_paper_trail
+ has_metadata
include ChecksumSupport
include TimeTableRestrictions
include ObjectidSupport
diff --git a/app/models/chouette/timeband.rb b/app/models/chouette/timeband.rb
index 5a4e17b98..38260b755 100644
--- a/app/models/chouette/timeband.rb
+++ b/app/models/chouette/timeband.rb
@@ -9,7 +9,7 @@ module Chouette
class Timeband < Chouette::TridentActiveRecord
include ObjectidSupport
- has_paper_trail
+ has_metadata
validates :start_time, :end_time, presence: true
validates_with Chouette::TimebandValidator
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index 525036077..c269d478e 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -1,7 +1,7 @@
# coding: utf-8
module Chouette
class VehicleJourney < Chouette::TridentActiveRecord
- has_paper_trail
+ has_metadata
include ChecksumSupport
include CustomFieldsSupport
include VehicleJourneyRestrictions
@@ -244,11 +244,13 @@ module Chouette
end
def self.state_update route, state
+ objects = []
transaction do
state.each do |item|
item.delete('errors')
vj = find_by(objectid: item['objectid']) || state_create_instance(route, item)
next if item['deletable'] && vj.persisted? && vj.destroy
+ objects << vj
if vj.state_update_vjas?(item['vehicle_journey_at_stops'])
vj.update_vjas_from_state(item['vehicle_journey_at_stops'])
@@ -276,6 +278,7 @@ module Chouette
item['vehicle_journey_at_stops'].map {|vjas| vjas.delete('new_record') }
end
state.delete_if {|item| item['deletable']}
+ objects
end
def self.state_create_instance route, item
@@ -346,6 +349,33 @@ module Chouette
end
end
+ def fill_passing_time_at_borders
+ encountered_borders = []
+ previous_stop = nil
+ vehicle_journey_at_stops.each do |vjas|
+ sp = vjas.stop_point
+ if sp.stop_area.area_type == "border"
+ encountered_borders << vjas
+ else
+ if encountered_borders.any?
+ before_cost = journey_pattern.costs_between previous_stop.stop_point, encountered_borders.first.stop_point
+ after_cost = journey_pattern.costs_between encountered_borders.last.stop_point, sp
+ if before_cost && before_cost[:distance] && after_cost && after_cost[:distance]
+ before_distance = before_cost[:distance].to_f
+ after_distance = after_cost[:distance].to_f
+ time = previous_stop.departure_time + before_distance / (before_distance+after_distance) * (vjas.arrival_time - previous_stop.departure_time)
+ encountered_borders.each do |b|
+ b.update_attribute :arrival_time, time
+ b.update_attribute :departure_time, time
+ end
+ end
+ encountered_borders = []
+ end
+ previous_stop = vjas
+ end
+ end
+ end
+
def self.matrix(vehicle_journeys)
Hash[*VehicleJourneyAtStop.where(vehicle_journey_id: vehicle_journeys.pluck(:id)).map do |vjas|
[ "#{vjas.vehicle_journey_id}-#{vjas.stop_point_id}", vjas]
diff --git a/app/models/clean_up.rb b/app/models/clean_up.rb
index 7aab7f32e..ec47489e9 100644
--- a/app/models/clean_up.rb
+++ b/app/models/clean_up.rb
@@ -1,4 +1,4 @@
-class CleanUp < ActiveRecord::Base
+class CleanUp < ApplicationModel
extend Enumerize
include AASM
belongs_to :referential
diff --git a/app/models/clean_up_result.rb b/app/models/clean_up_result.rb
index 24d262deb..dff4f5acd 100644
--- a/app/models/clean_up_result.rb
+++ b/app/models/clean_up_result.rb
@@ -1,3 +1,3 @@
-class CleanUpResult < ActiveRecord::Base
+class CleanUpResult < ApplicationModel
belongs_to :clean_up
end
diff --git a/app/models/compliance_check.rb b/app/models/compliance_check.rb
index 9d817e146..4ef6170e9 100644
--- a/app/models/compliance_check.rb
+++ b/app/models/compliance_check.rb
@@ -1,4 +1,4 @@
-class ComplianceCheck < ActiveRecord::Base
+class ComplianceCheck < ApplicationModel
include ComplianceItemSupport
self.inheritance_column = nil
diff --git a/app/models/compliance_check_block.rb b/app/models/compliance_check_block.rb
index 059547e1b..e4f4c1c37 100644
--- a/app/models/compliance_check_block.rb
+++ b/app/models/compliance_check_block.rb
@@ -1,4 +1,4 @@
-class ComplianceCheckBlock < ActiveRecord::Base
+class ComplianceCheckBlock < ApplicationModel
include StifTransportModeEnumerations
include StifTransportSubmodeEnumerations
diff --git a/app/models/compliance_check_message.rb b/app/models/compliance_check_message.rb
index 738bd4a4b..a4b1062f6 100644
--- a/app/models/compliance_check_message.rb
+++ b/app/models/compliance_check_message.rb
@@ -1,4 +1,4 @@
-class ComplianceCheckMessage < ActiveRecord::Base
+class ComplianceCheckMessage < ApplicationModel
extend Enumerize
belongs_to :compliance_check_set
diff --git a/app/models/compliance_check_resource.rb b/app/models/compliance_check_resource.rb
index 777254aaf..d2f782e2b 100644
--- a/app/models/compliance_check_resource.rb
+++ b/app/models/compliance_check_resource.rb
@@ -1,4 +1,4 @@
-class ComplianceCheckResource < ActiveRecord::Base
+class ComplianceCheckResource < ApplicationModel
extend Enumerize
belongs_to :compliance_check_set
diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb
index 49d324c53..8b1dbdd68 100644
--- a/app/models/compliance_check_set.rb
+++ b/app/models/compliance_check_set.rb
@@ -1,6 +1,7 @@
-class ComplianceCheckSet < ActiveRecord::Base
+class ComplianceCheckSet < ApplicationModel
extend Enumerize
- has_paper_trail class_name: 'PublicVersion'
+
+ has_metadata
belongs_to :referential
belongs_to :compliance_control_set
diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb
index 537343005..672fb128c 100644
--- a/app/models/compliance_control.rb
+++ b/app/models/compliance_control.rb
@@ -1,4 +1,4 @@
-class ComplianceControl < ActiveRecord::Base
+class ComplianceControl < ApplicationModel
include ComplianceItemSupport
class << self
@@ -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..6a3c8a34e 100644
--- a/app/models/compliance_control_block.rb
+++ b/app/models/compliance_control_block.rb
@@ -1,4 +1,4 @@
-class ComplianceControlBlock < ActiveRecord::Base
+class ComplianceControlBlock < ApplicationModel
include StifTransportModeEnumerations
include StifTransportSubmodeEnumerations
@@ -12,6 +12,8 @@ class ComplianceControlBlock < ActiveRecord::Base
validates :transport_mode, presence: true
validates :compliance_control_set, presence: true
+ validates_uniqueness_of :condition_attributes, scope: :compliance_control_set_id
+
def name
ApplicationController.helpers.transport_mode_text(self)
end
diff --git a/app/models/compliance_control_set.rb b/app/models/compliance_control_set.rb
index c0ea692f2..4f0f86d08 100644
--- a/app/models/compliance_control_set.rb
+++ b/app/models/compliance_control_set.rb
@@ -1,5 +1,6 @@
-class ComplianceControlSet < ActiveRecord::Base
- has_paper_trail class_name: 'PublicVersion'
+class ComplianceControlSet < ApplicationModel
+ has_metadata
+
belongs_to :organisation
has_many :compliance_control_blocks, dependent: :destroy
has_many :compliance_controls, dependent: :destroy
diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb
index 2d00b5847..6086d9580 100644
--- a/app/models/concerns/application_days_support.rb
+++ b/app/models/concerns/application_days_support.rb
@@ -10,8 +10,10 @@ module ApplicationDaysSupport
SUNDAY = 256
EVERYDAY = MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY | SATURDAY | SUNDAY
+ ALL_DAYS = %w(monday tuesday wednesday thursday friday saturday sunday).freeze
+
def display_day_types
- %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')
+ ALL_DAYS.select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')
end
def day_by_mask(flag)
@@ -39,6 +41,10 @@ module ApplicationDaysSupport
def self.day_by_mask(int_day_types,flag)
int_day_types & flag == flag
end
+
+ def self.all_days
+ ALL_DAYS
+ end
end
def valid_days
diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb
index 6c76bd653..46fc8e73d 100644
--- a/app/models/concerns/custom_fields_support.rb
+++ b/app/models/concerns/custom_fields_support.rb
@@ -3,22 +3,56 @@ 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 self.custom_fields_definitions workgroup=:all
+ 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
+ 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
+ return unless self.attributes.has_key?("custom_field_values")
+ 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/iev_interfaces/task.rb b/app/models/concerns/iev_interfaces/task.rb
index bc78ff28c..f052b3a8f 100644
--- a/app/models/concerns/iev_interfaces/task.rb
+++ b/app/models/concerns/iev_interfaces/task.rb
@@ -25,7 +25,7 @@ module IevInterfaces::Task
scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') }
- before_create :initialize_fields
+ before_save :initialize_fields, on: :create
after_save :notify_parent
end
diff --git a/app/models/concerns/metadata_support.rb b/app/models/concerns/metadata_support.rb
new file mode 100644
index 000000000..c4bedbcda
--- /dev/null
+++ b/app/models/concerns/metadata_support.rb
@@ -0,0 +1,107 @@
+module MetadataSupport
+ extend ActiveSupport::Concern
+
+ included do
+ class << self
+ def has_metadata?
+ !!@has_metadata
+ end
+
+ def has_metadata opts={}
+ @has_metadata = true
+
+ define_method :metadata do
+ attr_name = opts[:attr_name] || :metadata
+ @wrapped_metadata ||= begin
+ wrapped = MetadataSupport::MetadataWrapper.new self.read_attribute(attr_name)
+ wrapped.attribute_name = attr_name
+ wrapped.owner = self
+ wrapped
+ end
+ end
+
+ define_method :metadata= do |val|
+ @wrapped_metadata = nil
+ super val
+ end
+
+ define_method :set_metadata! do |name, value|
+ self.metadata.send "#{name}=", value
+ self.save!
+ end
+ end
+ end
+ end
+
+ def has_metadata?
+ self.class.has_metadata?
+ end
+
+ def merge_metadata_from source
+ return unless source.has_metadata?
+ source_metadata = source.metadata
+ res = {}
+ self.metadata.each do |k, v|
+ unless self.metadata.is_timestamp_attr?(k)
+ ts = self.metadata.timestamp_attr(k)
+ if source_metadata[ts] && source_metadata[ts] > self.metadata[ts]
+ res[k] = source_metadata[k]
+ else
+ res[k] = v
+ end
+ end
+ end
+ self.metadata = res
+ self
+ end
+
+ class MetadataWrapper < OpenStruct
+ attr_accessor :attribute_name, :owner
+
+ def is_timestamp_attr? name
+ name =~ /_updated_at$/
+ end
+
+ def timestamp_attr name
+ "#{name}_updated_at".to_sym
+ end
+
+ def method_missing(mid, *args)
+ out = super(mid, *args)
+ owner.send :write_attribute, attribute_name, @table
+ out = out&.to_time if args.length == 0 && is_timestamp_attr?(mid)
+ out
+ end
+
+ def each
+ @table.each do |k,v|
+ yield k, v
+ end
+ end
+
+ def new_ostruct_member name
+ unless is_timestamp_attr?(name)
+ timestamp_attr_name = timestamp_attr(name)
+ end
+
+ name = name.to_sym
+ unless respond_to?(name)
+ if timestamp_attr_name
+ define_singleton_method(timestamp_attr_name) { @table[timestamp_attr_name]&.to_time }
+ define_singleton_method(name) { @table[name] }
+ else
+ # we are defining an accessor for a timestamp
+ define_singleton_method(name) { @table[name]&.to_time }
+ end
+
+ define_singleton_method("#{name}=") do |x|
+ modifiable[timestamp_attr_name] = Time.now if timestamp_attr_name
+ modifiable[name] = x
+ owner.send :write_attribute, attribute_name, @table
+ end
+ modifiable[timestamp_attr_name] = Time.now if timestamp_attr_name
+ end
+ name
+ end
+ end
+end
diff --git a/app/models/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..65eabb205 100644
--- a/app/models/custom_field.rb
+++ b/app/models/custom_field.rb
@@ -1,16 +1,16 @@
-class CustomField < ActiveRecord::Base
+class CustomField < ApplicationModel
extend Enumerize
belongs_to :workgroup
- enumerize :field_type, in: %i{list}
+ enumerize :field_type, in: %i{list integer string attachment}
validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]}
- validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}
+ validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}, presence: true
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 +20,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 +38,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 +63,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 +78,184 @@ class CustomField < ActiveRecord::Base
def to_hash
HashWithIndifferentAccess[*%w(code name field_type options value).map{|k| [k, send(k)]}.flatten(1)]
end
+
+ def display_value
+ value
+ end
+
+ def initialize_custom_field
+ end
+
+ def preprocess_value_for_assignment val
+ val
+ end
+
+ def render_partial
+ ActionView::Base.new(Rails.configuration.paths["app/views"].first).render(
+ :partial => "shared/custom_fields/#{field_type}",
+ :locals => { field: self}
+ )
+ end
+
+ class Input
+ def initialize instance, form_helper
+ @instance = instance
+ @form_helper = form_helper
+ end
+
+ def custom_field
+ @instance.custom_field
+ end
+
+ delegate :custom_field, :value, :options, to: :@instance
+ delegate :code, :name, :field_type, to: :custom_field
+
+ def to_s
+ out = form_input
+ out.html_safe
+ end
+
+ protected
+
+ def form_input_id
+ "custom_field_#{code}"
+ end
+
+ def form_input_name
+ "#{@form_helper.object_name}[custom_field_values][#{code}]"
+ end
+
+ def form_input_options
+ {
+ input_html: {value: value, name: form_input_name},
+ label: name
+ }
+ end
+
+ def form_input
+ @form_helper.input form_input_id, form_input_options
+ end
+ end
end
class Integer < Base
def value
- @raw_value.to_i
+ @raw_value&.to_i
end
def validate
@valid = true
- unless @raw_value =~ /\A\d*\Z/
+ return if @raw_value.is_a?(Fixnum) || @raw_value.is_a?(Float)
+ unless @raw_value.to_s =~ /\A\d*\Z/
@owner.errors.add errors_key, "'#{@raw_value}' is not a valid integer"
@valid = false
end
end
end
+ class List < Integer
+ def validate
+ super
+ return unless value.present?
+ unless value >= 0 && value < options["list_values"].size
+ @owner.errors.add errors_key, "'#{@raw_value}' is not a valid value"
+ @valid = false
+ end
+ end
+
+ def display_value
+ return unless value
+ k = options["list_values"].is_a?(Hash) ? value.to_s : value.to_i
+ options["list_values"][k]
+ end
+
+ class Input < Base::Input
+ def form_input_options
+ collection = options["list_values"]
+ collection = collection.each_with_index.to_a if collection.is_a?(Array)
+ collection = collection.map(&:reverse) if collection.is_a?(Hash)
+ super.update({
+ selected: value,
+ collection: collection
+ })
+ end
+ end
+ end
+
+ class Attachment < Base
+ def initialize_custom_field
+ custom_field_code = self.code
+ _attr_name = attr_name
+ _uploader_name = uploader_name
+ owner.send :define_singleton_method, "read_uploader" do |attr|
+ if attr.to_s == _attr_name
+ custom_field_values[custom_field_code]
+ else
+ read_attribute attr
+ end
+ end
+
+ owner.send :define_singleton_method, "write_uploader" do |attr, val|
+ if attr.to_s == _attr_name
+ custom_field_values[custom_field_code] = val
+ else
+ write_attribute attr, val
+ end
+ end
+
+ owner.send :define_singleton_method, "#{_attr_name}_will_change!" do
+ custom_field_values_will_change!
+ end
+
+ _extension_whitelist = options["extension_whitelist"]
+
+ owner.send :define_singleton_method, "#{_uploader_name}_extension_whitelist" do
+ _extension_whitelist
+ end
+
+ unless owner.class.uploaders.has_key? _uploader_name.to_sym
+ owner.class.mount_uploader _uploader_name, CustomFieldAttachmentUploader, mount_on: "custom_field_#{code}_raw_value"
+ end
+ end
+
+ def preprocess_value_for_assignment val
+ if val.present?
+ owner.send "#{uploader_name}=", val
+ else
+ @raw_value
+ end
+ end
+
+ def value
+ owner.send "custom_field_#{code}"
+ end
+
+ def raw_value
+ @raw_value
+ end
+
+ def attr_name
+ "custom_field_#{code}_raw_value"
+ end
+
+ def uploader_name
+ "custom_field_#{code}"
+ end
+
+ def display_value
+ render_partial
+ end
+
+ class Input < Base::Input
+ def form_input_options
+ super.update({
+ as: :file,
+ wrapper: :horizontal_file_input
+ })
+ end
+ end
+ end
+
class String < Base
def value
"#{@raw_value}"
diff --git a/app/models/export/base.rb b/app/models/export/base.rb
index 6cf4c6b02..c65539635 100644
--- a/app/models/export/base.rb
+++ b/app/models/export/base.rb
@@ -1,4 +1,8 @@
+require 'net/http/post/multipart'
+
class Export::Base < ActiveRecord::Base
+ include Rails.application.routes.url_helpers
+
self.table_name = "exports"
belongs_to :referential
@@ -21,6 +25,22 @@ class Export::Base < ActiveRecord::Base
%w(zip csv json)
end
+ def upload_file file
+ url = URI.parse upload_workbench_export_url(self.workbench_id, self.id, host: Rails.application.config.rails_host)
+ res = nil
+ filename = File.basename(file.path)
+ content_type = MIME::Types.type_for(filename).first&.content_type
+ File.open(file.path) do |file_content|
+ req = Net::HTTP::Post::Multipart.new url.path,
+ file: UploadIO.new(file_content, content_type, filename),
+ token: self.token_upload
+ res = Net::HTTP.start(url.host, url.port) do |http|
+ http.request(req)
+ end
+ end
+ res
+ end
+
if Rails.env.development?
def self.force_load_descendants
path = Rails.root.join 'app/models/export'
diff --git a/app/models/export/message.rb b/app/models/export/message.rb
index b64b524ac..223429900 100644
--- a/app/models/export/message.rb
+++ b/app/models/export/message.rb
@@ -1,4 +1,4 @@
-class Export::Message < ActiveRecord::Base
+class Export::Message < ApplicationModel
self.table_name = :export_messages
include IevInterfaces::Message
diff --git a/app/models/export/resource.rb b/app/models/export/resource.rb
index 98f103be4..2a63c14a8 100644
--- a/app/models/export/resource.rb
+++ b/app/models/export/resource.rb
@@ -1,4 +1,4 @@
-class Export::Resource < ActiveRecord::Base
+class Export::Resource < ApplicationModel
self.table_name = :export_resources
include IevInterfaces::Resource
diff --git a/app/models/export/simple_exporter/base.rb b/app/models/export/simple_exporter/base.rb
index 4e6e8eba4..e77e23468 100644
--- a/app/models/export/simple_exporter/base.rb
+++ b/app/models/export/simple_exporter/base.rb
@@ -48,15 +48,15 @@ class Export::SimpleExporter::Base < Export::Base
exporter.export
set_status_from_exporter
convert_exporter_journal_to_messages
- self.file = tmp
self.save!
+ upload_file tmp
end
def set_status_from_exporter
if exporter.status.to_s == "error"
self.status = :failed
elsif exporter.status.to_s == "success"
- self.status = :successful
+ self.status = :successful
else
self.status = :warning
end
diff --git a/app/models/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/import/base.rb b/app/models/import/base.rb
index 62494c92e..f98e359d4 100644
--- a/app/models/import/base.rb
+++ b/app/models/import/base.rb
@@ -1,4 +1,4 @@
-class Import::Base < ActiveRecord::Base
+class Import::Base < ApplicationModel
self.table_name = "imports"
validates :file, presence: true
@@ -41,7 +41,7 @@ class Import::Base < ActiveRecord::Base
def initialize_fields
super
- self.token_download = SecureRandom.urlsafe_base64
+ self.token_download ||= SecureRandom.urlsafe_base64
end
end
diff --git a/app/models/import/gtfs.rb b/app/models/import/gtfs.rb
index 03cf49e60..70f448132 100644
--- a/app/models/import/gtfs.rb
+++ b/app/models/import/gtfs.rb
@@ -1,36 +1,296 @@
-require 'net/http'
class Import::Gtfs < Import::Base
- before_destroy :destroy_non_ready_referential
+ after_commit :launch_worker, :on => :create
- after_commit :launch_java_import, on: :create
- before_save def abort_unless_referential
- self.status = 'aborted' unless referential
+ def launch_worker
+ GtfsImportWorker.perform_async id
end
- def launch_java_import
- return if self.class.finished_statuses.include?(status)
- threaded_call_boiv_iev
+ def import
+ update status: 'running', started_at: Time.now
+
+ import_without_status
+ update status: 'successful', ended_at: Time.now
+ rescue Exception => e
+ update status: 'failed', ended_at: Time.now
+ Rails.logger.error "Error in GTFS import: #{e} #{e.backtrace.join('\n')}"
+ ensure
+ notify_parent
+ referential&.update ready: true
+ end
+
+ def self.accept_file?(file)
+ Zip::File.open(file) do |zip_file|
+ zip_file.glob('agency.txt').size == 1
+ end
+ rescue Exception => e
+ Rails.logger.debug "Error in testing GTFS file: #{e}"
+ return false
+ end
+
+ def create_referential
+ self.referential ||= Referential.create!(
+ name: "GTFS Import",
+ organisation_id: workbench.organisation_id,
+ workbench_id: workbench.id,
+ metadatas: [referential_metadata]
+ )
+ end
+
+ def referential_metadata
+ registration_numbers = source.routes.map(&:id)
+ line_ids = line_referential.lines.where(registration_number: registration_numbers).pluck(:id)
+
+ start_dates, end_dates = source.calendars.map { |c| [c.start_date, c.end_date ] }.transpose
+ excluded_dates = source.calendar_dates.select { |d| d.exception_type == "2" }.map(&:date)
+
+ min_date = Date.parse (start_dates + [excluded_dates.min]).compact.min
+ max_date = Date.parse (end_dates + [excluded_dates.max]).compact.max
+
+ ReferentialMetadata.new line_ids: line_ids, periodes: [min_date..max_date]
+ end
+
+ attr_accessor :local_file
+ def local_file
+ @local_file ||= download_local_file
end
- private
+ attr_accessor :download_host
+ def download_host
+ @download_host ||= Rails.application.config.rails_host.gsub("http://","")
+ end
- def destroy_non_ready_referential
- if referential && !referential.ready
- referential.destroy
+ def local_temp_directory
+ Rails.application.config.try(:import_temporary_directory) ||
+ Rails.root.join('tmp', 'imports')
+ end
+
+ def local_temp_file(&block)
+ Tempfile.open("chouette-import", local_temp_directory) do |file|
+ file.binmode
+ yield file
end
end
- def threaded_call_boiv_iev
- Thread.new(&method(:call_boiv_iev))
+ def download_path
+ Rails.application.routes.url_helpers.download_workbench_import_path(workbench, id, token: token_download)
end
- def call_boiv_iev
- Rails.logger.error("Begin IEV call for import")
- Net::HTTP.get(URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}"))
- Rails.logger.error("End IEV call for import")
- rescue Exception => e
- logger.error "IEV server error : #{e.message}"
- logger.error e.backtrace.inspect
+ def download_local_file
+ local_temp_file do |file|
+ begin
+ Net::HTTP.start(download_host) do |http|
+ http.request_get(download_path) do |response|
+ response.read_body do |segment|
+ file.write segment
+ end
+ end
+ end
+ ensure
+ file.close
+ end
+
+ file.path
+ end
+ end
+
+ def source
+ @source ||= ::GTFS::Source.build local_file
+ end
+
+ delegate :line_referential, :stop_area_referential, to: :workbench
+
+ def prepare_referential
+ import_agencies
+ import_stops
+ import_routes
+
+ create_referential
+ referential.switch
+ end
+
+ def import_without_status
+ prepare_referential
+
+ import_calendars
+ import_trips
+ import_stop_times
+ end
+
+ def import_agencies
+ Chouette::Company.transaction do
+ source.agencies.each do |agency|
+ company = line_referential.companies.find_or_initialize_by(registration_number: agency.id)
+ company.attributes = { name: agency.name }
+
+ save_model company
+ end
+ end
+ end
+
+ def import_stops
+ Chouette::StopArea.transaction do
+ source.stops.each do |stop|
+ stop_area = stop_area_referential.stop_areas.find_or_initialize_by(registration_number: stop.id)
+
+ stop_area.name = stop.name
+ stop_area.area_type = stop.location_type == "1" ? "zdlp" : "zdep"
+ stop_area.parent = stop_area_referential.stop_areas.find_by!(registration_number: stop.parent_station) if stop.parent_station.present?
+ stop_area.latitude, stop_area.longitude = stop.lat, stop.lon
+ stop_area.kind = "commercial"
+
+ # TODO correct default timezone
+
+ save_model stop_area
+ end
+ end
+ end
+
+ def import_routes
+ Chouette::Line.transaction do
+ source.routes.each do |route|
+ line = line_referential.lines.find_or_initialize_by(registration_number: route.id)
+ line.name = route.long_name.presence || route.short_name
+ line.number = route.short_name
+ line.published_name = route.long_name
+
+ line.company = line_referential.companies.find_by(registration_number: route.agency_id) if route.agency_id.present?
+
+ # TODO transport mode
+
+ line.comment = route.desc
+
+ # TODO colors
+
+ line.url = route.url
+
+ save_model line
+ end
+ end
+ end
+
+ def vehicle_journey_by_trip_id
+ @vehicle_journey_by_trip_id ||= {}
+ end
+
+ def import_trips
+ source.trips.each_slice(100) do |slice|
+ slice.each do |trip|
+ Chouette::Route.transaction do
+ line = line_referential.lines.find_by registration_number: trip.route_id
+
+ route = referential.routes.build line: line
+ route.wayback = (trip.direction_id == "0" ? :outbound : :inbound)
+ # TODO better name ?
+ name = route.published_name = trip.short_name.presence || trip.headsign.presence || route.wayback.to_s.capitalize
+ route.name = name
+ save_model route
+
+ journey_pattern = route.journey_patterns.build name: name
+ save_model journey_pattern
+
+ vehicle_journey = journey_pattern.vehicle_journeys.build route: route
+ vehicle_journey.published_journey_name = trip.headsign.presence || trip.id
+ save_model vehicle_journey
+
+ time_table = referential.time_tables.find_by(id: time_tables_by_service_id[trip.service_id]) if time_tables_by_service_id[trip.service_id]
+ if time_table
+ vehicle_journey.time_tables << time_table
+ else
+ messages.create! criticity: "warning", message_key: "gtfs.trips.unkown_service_id", message_attributes: {service_id: trip.service_id}
+ end
+
+ vehicle_journey_by_trip_id[trip.id] = vehicle_journey.id
+ end
+ end
+ end
+ end
+
+ def import_stop_times
+ source.stop_times.group_by(&:trip_id).each_slice(50) do |slice|
+ slice.each do |trip_id, stop_times|
+ Chouette::VehicleJourneyAtStop.transaction do
+ vehicle_journey = referential.vehicle_journeys.find vehicle_journey_by_trip_id[trip_id]
+ journey_pattern = vehicle_journey.journey_pattern
+ route = journey_pattern.route
+
+ stop_times.sort_by! { |s| s.stop_sequence.to_i }
+
+ stop_times.each do |stop_time|
+ stop_area = stop_area_referential.stop_areas.find_by(registration_number: stop_time.stop_id)
+
+ stop_point = route.stop_points.build stop_area: stop_area
+ save_model stop_point
+
+ journey_pattern.stop_points << stop_point
+
+ # JourneyPattern#vjas_add creates automaticaly VehicleJourneyAtStop
+ vehicle_journey_at_stop = journey_pattern.vehicle_journey_at_stops.find_by(stop_point_id: stop_point.id)
+
+ departure_time = GTFS::Time.parse(stop_time.departure_time)
+ arrival_time = GTFS::Time.parse(stop_time.arrival_time)
+
+ vehicle_journey_at_stop.departure_time = departure_time.time
+ vehicle_journey_at_stop.arrival_time = arrival_time.time
+ vehicle_journey_at_stop.departure_day_offset = departure_time.day_offset
+ vehicle_journey_at_stop.arrival_day_offset = arrival_time.day_offset
+
+ # TODO offset
+
+ save_model vehicle_journey_at_stop
+ end
+ end
+ end
+ end
+ end
+
+ def time_tables_by_service_id
+ @time_tables_by_service_id ||= {}
+ end
+
+ def import_calendars
+ source.calendars.each_slice(500) do |slice|
+ Chouette::TimeTable.transaction do
+ slice.each do |calendar|
+ time_table = referential.time_tables.build comment: "Calendar #{calendar.service_id}"
+ Chouette::TimeTable.all_days.each do |day|
+ time_table.send("#{day}=", calendar.send(day))
+ end
+ time_table.periods.build period_start: calendar.start_date, period_end: calendar.end_date
+
+ save_model time_table
+
+ time_tables_by_service_id[calendar.service_id] = time_table.id
+ end
+ end
+ end
+ end
+
+ def import_calendar_dates
+ source.calendar_dates.each_slice(500) do |slice|
+ Chouette::TimeTable.transaction do
+ slice.each do |calendar_date|
+ time_table = referential.time_tables.find time_tables_by_service_id[calendar_date.service_id]
+ date = time_table.dates.build date: Date.parse(calendar_date.date), in_out: calendar_date.exception_type == "1"
+
+ save_model date
+ end
+ end
+ end
+ end
+
+ def save_model(model)
+ unless model.save
+ Rails.logger.info "Can't save #{model.class.name} : #{model.errors.inspect}"
+ raise ActiveRecord::RecordNotSaved.new("Invalid #{model.class.name} : #{model.errors.inspect}")
+ end
+ Rails.logger.debug "Created #{model.inspect}"
+ end
+
+ def notify_parent
+ return unless parent.present?
+ return if notified_parent_at
+ parent.child_change
+ update_column :notified_parent_at, Time.now
end
end
diff --git a/app/models/import/message.rb b/app/models/import/message.rb
index c1900a718..30b76ec5c 100644
--- a/app/models/import/message.rb
+++ b/app/models/import/message.rb
@@ -1,4 +1,4 @@
-class Import::Message < ActiveRecord::Base
+class Import::Message < ApplicationModel
self.table_name = :import_messages
include IevInterfaces::Message
diff --git a/app/models/import/netex.rb b/app/models/import/netex.rb
index 2b0982229..93604c5f9 100644
--- a/app/models/import/netex.rb
+++ b/app/models/import/netex.rb
@@ -10,6 +10,26 @@ class Import::Netex < Import::Base
validates_presence_of :parent
+ def create_referential!
+ self.referential =
+ Referential.new(
+ name: self.name,
+ organisation_id: workbench.organisation_id,
+ workbench_id: workbench.id,
+ metadatas: [referential_metadata]
+ )
+ self.referential.save
+ unless self.referential.valid?
+ Rails.logger.info "Can't create referential for import #{self.id}: #{referential.inspect} #{referential.metadatas.inspect} #{referential.errors.messages}"
+ if referential.metadatas.all?{|m| m.line_ids.empty?}
+ parent.messages.create criticity: :error, message_key: "referential_creation_missing_lines", message_attributes: {referential_name: referential.name}
+ else
+ parent.messages.create criticity: :error, message_key: "referential_creation", message_attributes: {referential_name: referential.name}
+ end
+ end
+ save!
+ end
+
private
def iev_callback_url
@@ -21,4 +41,22 @@ class Import::Netex < Import::Base
referential.destroy
end
end
+
+ def referential_metadata
+ metadata = ReferentialMetadata.new
+
+ if self.file
+ netex_file = STIF::NetexFile.new(self.file.path)
+ frame = netex_file.frames.first
+
+ if frame
+ metadata.periodes = frame.periods
+
+ line_objectids = frame.line_refs.map { |ref| "STIF:CODIFLIGNE:Line:#{ref}" }
+ metadata.line_ids = workbench.lines.where(objectid: line_objectids).pluck(:id)
+ end
+ end
+
+ metadata
+ end
end
diff --git a/app/models/import/resource.rb b/app/models/import/resource.rb
index 5bd011039..1951daacd 100644
--- a/app/models/import/resource.rb
+++ b/app/models/import/resource.rb
@@ -1,4 +1,4 @@
-class Import::Resource < ActiveRecord::Base
+class Import::Resource < ApplicationModel
self.table_name = :import_resources
include IevInterfaces::Resource
diff --git a/app/models/import/workbench.rb b/app/models/import/workbench.rb
index f6e15cb89..124b9b0d8 100644
--- a/app/models/import/workbench.rb
+++ b/app/models/import/workbench.rb
@@ -2,6 +2,25 @@ class Import::Workbench < Import::Base
after_commit :launch_worker, :on => :create
def launch_worker
- WorkbenchImportWorker.perform_async(id)
+ unless Import::Gtfs.accept_file?(file.path)
+ WorkbenchImportWorker.perform_async(id)
+ else
+ import_gtfs
+ end
+ end
+
+ def import_gtfs
+ update_column :status, 'running'
+ update_column :started_at, Time.now
+
+ Import::Gtfs.create! parent_id: self.id, workbench: workbench, file: File.new(file.path), name: "Import GTFS", creator: "Web service"
+
+ update_column :status, 'successful'
+ update_column :ended_at, Time.now
+ rescue Exception => e
+ Rails.logger.error "Error while processing GTFS file: #{e}"
+
+ update_column :status, 'failed'
+ update_column :ended_at, Time.now
end
end
diff --git a/app/models/line_control/lines_scope.rb b/app/models/line_control/lines_scope.rb
new file mode 100644
index 000000000..4210a10dd
--- /dev/null
+++ b/app/models/line_control/lines_scope.rb
@@ -0,0 +1,8 @@
+module LineControl
+ class LinesScope < ComplianceControl
+
+ def self.default_code; "3-Line-2" end
+
+ def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end
+ end
+end
diff --git a/app/models/line_referential.rb b/app/models/line_referential.rb
index 0d2ed39b1..08193c960 100644
--- a/app/models/line_referential.rb
+++ b/app/models/line_referential.rb
@@ -1,7 +1,7 @@
-class LineReferential < ActiveRecord::Base
+class LineReferential < ApplicationModel
include ObjectidFormatterSupport
extend StifTransportModeEnumerations
-
+
has_many :line_referential_memberships
has_many :organisations, through: :line_referential_memberships
has_many :lines, class_name: 'Chouette::Line'
@@ -14,7 +14,7 @@ class LineReferential < ActiveRecord::Base
def add_member(organisation, options = {})
attributes = options.merge organisation: organisation
- line_referential_memberships.build attributes
+ line_referential_memberships.build attributes unless organisations.include?(organisation)
end
validates :name, presence: true
diff --git a/app/models/line_referential_membership.rb b/app/models/line_referential_membership.rb
index b49d1b5b1..8371bdc32 100644
--- a/app/models/line_referential_membership.rb
+++ b/app/models/line_referential_membership.rb
@@ -1,4 +1,6 @@
-class LineReferentialMembership < ActiveRecord::Base
+class LineReferentialMembership < ApplicationModel
belongs_to :organisation
belongs_to :line_referential
+
+ validates :organisation_id, presence: true, uniqueness: { scope: :line_referential }
end
diff --git a/app/models/line_referential_sync.rb b/app/models/line_referential_sync.rb
index 75c1e48a2..39e3846f0 100644
--- a/app/models/line_referential_sync.rb
+++ b/app/models/line_referential_sync.rb
@@ -1,4 +1,4 @@
-class LineReferentialSync < ActiveRecord::Base
+class LineReferentialSync < ApplicationModel
include AASM
belongs_to :line_referential
has_many :line_referential_sync_messages, :dependent => :destroy
diff --git a/app/models/line_referential_sync_message.rb b/app/models/line_referential_sync_message.rb
index 3b6cf3367..00a2b58a3 100644
--- a/app/models/line_referential_sync_message.rb
+++ b/app/models/line_referential_sync_message.rb
@@ -1,4 +1,4 @@
-class LineReferentialSyncMessage < ActiveRecord::Base
+class LineReferentialSyncMessage < ApplicationModel
belongs_to :line_referential_sync
enum criticity: [:info, :warning, :error]
diff --git a/app/models/merge.rb b/app/models/merge.rb
index e72c794fe..8d661f209 100644
--- a/app/models/merge.rb
+++ b/app/models/merge.rb
@@ -1,4 +1,4 @@
-class Merge < ActiveRecord::Base
+class Merge < ApplicationModel
extend Enumerize
belongs_to :workbench
@@ -50,7 +50,7 @@ class Merge < ActiveRecord::Base
new =
if workbench.output.current
Rails.logger.debug "Clone current output"
- Referential.new_from(workbench.output.current, fixme_functional_scope).tap do |clone|
+ Referential.new_from(workbench.output.current, workbench.organisation).tap do |clone|
clone.inline_clone = true
end
else
@@ -138,7 +138,9 @@ class Merge < ActiveRecord::Base
new.switch do
referential_routes.each do |route|
existing_route = new.routes.find_by line_id: route.line_id, checksum: route.checksum
- unless existing_route
+ if existing_route
+ existing_route.merge_metadata_from route
+ else
objectid = Chouette::Route.where(objectid: route.objectid).exists? ? nil : route.objectid
attributes = route.attributes.merge(
id: nil,
@@ -196,7 +198,9 @@ class Merge < ActiveRecord::Base
existing_journey_pattern = new.journey_patterns.find_by route_id: existing_associated_route.id, checksum: journey_pattern.checksum
- unless existing_journey_pattern
+ if existing_journey_pattern
+ existing_journey_pattern.merge_metadata_from journey_pattern
+ else
objectid = Chouette::JourneyPattern.where(objectid: journey_pattern.objectid).exists? ? nil : journey_pattern.objectid
attributes = journey_pattern.attributes.merge(
id: nil,
@@ -241,7 +245,9 @@ class Merge < ActiveRecord::Base
existing_vehicle_journey = new.vehicle_journeys.find_by journey_pattern_id: existing_associated_journey_pattern.id, checksum: vehicle_journey.checksum
- unless existing_vehicle_journey
+ if existing_vehicle_journey
+ existing_vehicle_journey.merge_metadata_from vehicle_journey
+ else
objectid = Chouette::VehicleJourney.where(objectid: vehicle_journey.objectid).exists? ? nil : vehicle_journey.objectid
attributes = vehicle_journey.attributes.merge(
id: nil,
@@ -338,7 +344,9 @@ class Merge < ActiveRecord::Base
existing_time_table = line.time_tables.find_by checksum: candidate_time_table.checksum
- unless existing_time_table
+ if existing_time_table
+ existing_time_table.merge_metadata_from candidate_time_table
+ else
objectid = Chouette::TimeTable.where(objectid: time_table.objectid).exists? ? nil : time_table.objectid
candidate_time_table.objectid = objectid
@@ -363,7 +371,7 @@ class Merge < ActiveRecord::Base
def save_current
output.update current: new, new: nil
- output.current.update referential_suite: output
+ output.current.update referential_suite: output, ready: true
referentials.update_all merged_at: created_at, archived_at: created_at
end
diff --git a/app/models/organisation.rb b/app/models/organisation.rb
index 745bc0d22..5742c81e8 100644
--- a/app/models/organisation.rb
+++ b/app/models/organisation.rb
@@ -1,5 +1,5 @@
# coding: utf-8
-class Organisation < ActiveRecord::Base
+class Organisation < ApplicationModel
include DataFormatEnumerations
has_many :users, :dependent => :destroy
@@ -86,4 +86,8 @@ class Organisation < ActiveRecord::Base
workbenches.default
end
+ def lines_scope
+ functional_scope = sso_attributes.try(:[], "functional_scope")
+ JSON.parse(functional_scope) if functional_scope
+ end
end
diff --git a/app/models/public_version.rb b/app/models/public_version.rb
deleted file mode 100644
index 4dbf6ce27..000000000
--- a/app/models/public_version.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class PublicVersion < PaperTrail::Version
- # custom behaviour, e.g:
- self.table_name = :'public.versions'
-end
diff --git a/app/models/referential.rb b/app/models/referential.rb
index 91a88d02d..1794126a2 100644
--- a/app/models/referential.rb
+++ b/app/models/referential.rb
@@ -1,5 +1,5 @@
# coding: utf-8
-class Referential < ActiveRecord::Base
+class Referential < ApplicationModel
include DataFormatEnumerations
include ObjectidFormatterSupport
@@ -168,6 +168,10 @@ class Referential < ActiveRecord::Base
Chouette::TimeTable.all
end
+ def time_table_dates
+ Chouette::TimeTableDate.all
+ end
+
def timebands
Chouette::Timeband.all
end
@@ -184,6 +188,10 @@ class Referential < ActiveRecord::Base
Chouette::VehicleJourneyFrequency.all
end
+ def vehicle_journey_at_stops
+ Chouette::VehicleJourneyAtStop.all
+ end
+
def routing_constraint_zones
Chouette::RoutingConstraintZone.all
end
@@ -233,7 +241,7 @@ class Referential < ActiveRecord::Base
end
end
- def self.new_from(from, functional_scope)
+ def self.new_from(from, organisation)
Referential.new(
name: I18n.t("activerecord.copy", name: from.name),
slug: "#{from.slug}_clone",
@@ -244,7 +252,7 @@ class Referential < ActiveRecord::Base
stop_area_referential: from.stop_area_referential,
created_from: from,
objectid_format: from.objectid_format,
- metadatas: from.metadatas.map { |m| ReferentialMetadata.new_from(m, functional_scope) }
+ metadatas: from.metadatas.map { |m| ReferentialMetadata.new_from(m, organisation) }
)
end
diff --git a/app/models/referential_cloning.rb b/app/models/referential_cloning.rb
index d4b74bd52..f2c81009a 100644
--- a/app/models/referential_cloning.rb
+++ b/app/models/referential_cloning.rb
@@ -1,4 +1,4 @@
-class ReferentialCloning < ActiveRecord::Base
+class ReferentialCloning < ApplicationModel
include AASM
belongs_to :source_referential, class_name: 'Referential'
belongs_to :target_referential, class_name: 'Referential'
diff --git a/app/models/referential_metadata.rb b/app/models/referential_metadata.rb
index 393dc70d3..7a8a01774 100644
--- a/app/models/referential_metadata.rb
+++ b/app/models/referential_metadata.rb
@@ -1,7 +1,7 @@
require 'activeattr_ext.rb'
require 'range_ext'
-class ReferentialMetadata < ActiveRecord::Base
+class ReferentialMetadata < ApplicationModel
belongs_to :referential, touch: true
belongs_to :referential_source, class_name: 'Referential'
has_array_of :lines, class_name: 'Chouette::Line'
@@ -155,10 +155,10 @@ class ReferentialMetadata < ActiveRecord::Base
end
private :clear_periods
- def self.new_from(from, functional_scope)
+ def self.new_from(from, organisation)
from.dup.tap do |metadata|
metadata.referential_source_id = from.referential_id
- metadata.line_ids = from.referential.lines.where(id: metadata.line_ids, objectid: functional_scope).collect(&:id)
+ metadata.line_ids = from.referential.lines.where(id: metadata.line_ids).for_organisation(organisation).pluck(:id)
metadata.referential_id = nil
end
end
diff --git a/app/models/referential_suite.rb b/app/models/referential_suite.rb
index 4f825628c..f4a72f22c 100644
--- a/app/models/referential_suite.rb
+++ b/app/models/referential_suite.rb
@@ -1,4 +1,4 @@
-class ReferentialSuite < ActiveRecord::Base
+class ReferentialSuite < ApplicationModel
belongs_to :new, class_name: 'Referential'
validate def validate_consistent_new
return true if new_id.nil? || new.nil?
diff --git a/app/models/simple_exporter.rb b/app/models/simple_exporter.rb
index c267b5b8c..1fcb76a29 100644
--- a/app/models/simple_exporter.rb
+++ b/app/models/simple_exporter.rb
@@ -64,7 +64,7 @@ class SimpleExporter < SimpleInterface
def map_item_to_rows item
return [item] unless configuration.item_to_rows_mapping
- configuration.item_to_rows_mapping.call(item).map {|row| row.is_a?(ActiveRecord::Base) ? row : CustomRow.new(row) }
+ instance_exec(item, &configuration.item_to_rows_mapping).map {|row| row.is_a?(ActiveRecord::Base) ? row : CustomRow.new(row) }
end
def resolve_value item, col
diff --git a/app/models/simple_interface.rb b/app/models/simple_interface.rb
index 43c740b57..7b04a07df 100644
--- a/app/models/simple_interface.rb
+++ b/app/models/simple_interface.rb
@@ -1,4 +1,4 @@
-class SimpleInterface < ActiveRecord::Base
+class SimpleInterface < ApplicationModel
attr_accessor :configuration, :interfaces_group
class << self
diff --git a/app/models/stop_area_referential.rb b/app/models/stop_area_referential.rb
index a9d3cc9b1..6c339547c 100644
--- a/app/models/stop_area_referential.rb
+++ b/app/models/stop_area_referential.rb
@@ -1,4 +1,4 @@
-class StopAreaReferential < ActiveRecord::Base
+class StopAreaReferential < ApplicationModel
validates :registration_number_format, format: { with: /\AX*\z/ }
include ObjectidFormatterSupport
@@ -12,7 +12,7 @@ class StopAreaReferential < ActiveRecord::Base
def add_member(organisation, options = {})
attributes = options.merge organisation: organisation
- stop_area_referential_memberships.build attributes
+ stop_area_referential_memberships.build attributes unless organisations.include?(organisation)
end
def last_sync
diff --git a/app/models/stop_area_referential_membership.rb b/app/models/stop_area_referential_membership.rb
index 435970961..d507bc50e 100644
--- a/app/models/stop_area_referential_membership.rb
+++ b/app/models/stop_area_referential_membership.rb
@@ -1,4 +1,6 @@
-class StopAreaReferentialMembership < ActiveRecord::Base
+class StopAreaReferentialMembership < ApplicationModel
belongs_to :organisation
belongs_to :stop_area_referential
+
+ validates :organisation_id, presence: true, uniqueness: { scope: :stop_area_referential }
end
diff --git a/app/models/stop_area_referential_sync.rb b/app/models/stop_area_referential_sync.rb
index e6cf2ecbc..8b48d35e6 100644
--- a/app/models/stop_area_referential_sync.rb
+++ b/app/models/stop_area_referential_sync.rb
@@ -1,4 +1,4 @@
-class StopAreaReferentialSync < ActiveRecord::Base
+class StopAreaReferentialSync < ApplicationModel
include AASM
belongs_to :stop_area_referential
has_many :stop_area_referential_sync_messages, :dependent => :destroy
diff --git a/app/models/stop_area_referential_sync_message.rb b/app/models/stop_area_referential_sync_message.rb
index cd2e62405..642ccfc38 100644
--- a/app/models/stop_area_referential_sync_message.rb
+++ b/app/models/stop_area_referential_sync_message.rb
@@ -1,4 +1,4 @@
-class StopAreaReferentialSyncMessage < ActiveRecord::Base
+class StopAreaReferentialSyncMessage < ApplicationModel
belongs_to :stop_area_referential_sync
enum criticity: [:info, :warning, :error]
diff --git a/app/models/user.rb b/app/models/user.rb
index 31e634415..ba166b06f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,4 +1,4 @@
-class User < ActiveRecord::Base
+class User < ApplicationModel
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable, :database_authenticatable
@@ -9,7 +9,7 @@ class User < ActiveRecord::Base
:recoverable, :rememberable, :trackable, :async, authentication_type
# FIXME https://github.com/nbudin/devise_cas_authenticatable/issues/53
- # Work around :validatable, when database_authenticatable is diabled.
+ # Work around :validatable, when database_authenticatable is disabled.
attr_accessor :password unless authentication_type == :database_authenticatable
# Setup accessible (or protected) attributes for your model
@@ -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/models/workbench.rb b/app/models/workbench.rb
index b5f4673bb..ef0b2eaa4 100644
--- a/app/models/workbench.rb
+++ b/app/models/workbench.rb
@@ -1,4 +1,4 @@
-class Workbench < ActiveRecord::Base
+class Workbench < ApplicationModel
DEFAULT_WORKBENCH_NAME = "Gestion de l'offre"
include ObjectidFormatterSupport
diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb
index 7e3e857ec..3e8409634 100644
--- a/app/models/workgroup.rb
+++ b/app/models/workgroup.rb
@@ -1,4 +1,4 @@
-class Workgroup < ActiveRecord::Base
+class Workgroup < ApplicationModel
belongs_to :line_referential
belongs_to :stop_area_referential
diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb
index 0337a5300..4fcb6be11 100644
--- a/app/policies/route_policy.rb
+++ b/app/policies/route_policy.rb
@@ -20,4 +20,8 @@ class RoutePolicy < ApplicationPolicy
def duplicate?
create?
end
+
+ def create_opposite?
+ create?
+ end
end
diff --git a/app/services/route_way_cost_calculator.rb b/app/services/route_way_cost_calculator.rb
index 2e30c94fc..d41a2e59a 100644
--- a/app/services/route_way_cost_calculator.rb
+++ b/app/services/route_way_cost_calculator.rb
@@ -5,7 +5,7 @@ class RouteWayCostCalculator
def calculate!
way_costs = StopAreasToWayCostsConverter.new(@route.stop_areas).convert
- way_costs = TomTom.batch(way_costs)
+ way_costs = TomTom.matrix(way_costs)
way_costs = WayCostCollectionJSONSerializer.dump(way_costs)
@route.update(costs: way_costs)
end
diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb
index 7166e6448..2402721fb 100644
--- a/app/services/zip_service.rb
+++ b/app/services/zip_service.rb
@@ -1,8 +1,8 @@
class ZipService
- class Subdir < Struct.new(:name, :stream, :spurious, :foreign_lines)
+ class Subdir < Struct.new(:name, :stream, :spurious, :foreign_lines, :missing_calendar, :wrong_calendar)
def ok?
- foreign_lines.empty? && spurious.empty?
+ foreign_lines.empty? && spurious.empty? && !missing_calendar && !wrong_calendar
end
end
@@ -38,8 +38,21 @@ class ZipService
add_to_current_output entry
end
+ def validate entry
+ if is_calendar_file?(entry.name)
+ @current_calendar_is_missing = false
+ if wrong_calendar_data?(entry)
+ @current_calendar_is_wrong = true
+ return false
+ end
+ end
+ return false if is_spurious?(entry.name)
+ return false if is_foreign_line?(entry.name)
+ true
+ end
+
def add_to_current_output entry
- return if is_spurious!(entry.name) || is_foreign_line!(entry.name)
+ return unless validate(entry)
current_output.put_next_entry entry.name
write_to_current_output entry.get_input_stream
@@ -48,7 +61,7 @@ class ZipService
def write_to_current_output input_stream
# the condition below is true for directory entries
return if Zip::NullInputStream == input_stream
- current_output.write input_stream.read
+ current_output.write input_stream.read
end
def finish_current_output
@@ -58,7 +71,9 @@ class ZipService
# Second part of the solution, yield the closed stream
current_output.close_buffer,
current_spurious.to_a,
- foreign_lines)
+ foreign_lines,
+ @current_calendar_is_missing,
+ @current_calendar_is_wrong)
end
end
@@ -68,6 +83,8 @@ class ZipService
@current_output = Zip::OutputStream.new(StringIO.new(''), true, nil)
@current_spurious = Set.new
@foreign_lines = []
+ @current_calendar_is_missing = true
+ @current_calendar_is_wrong = false
end
def entry_key entry
@@ -75,7 +92,7 @@ class ZipService
entry.name.split('/').first
end
- def is_spurious! entry_name
+ def is_spurious? entry_name
segments = entry_name.split('/', 3)
return false if segments.size < 3
@@ -83,11 +100,25 @@ class ZipService
return true
end
- def is_foreign_line! entry_name
+ def is_foreign_line? entry_name
STIF::NetexFile::Frame.get_short_id(entry_name).tap do | line_object_id |
return nil unless line_object_id
return nil if line_object_id.in? allowed_lines
foreign_lines << line_object_id
end
end
+
+ def is_calendar_file? entry_name
+ entry_name =~ /calendriers.xml$/
+ end
+
+ def wrong_calendar_data? entry
+ content = entry.get_input_stream.read
+ period = STIF::NetexFile::Frame.parse_calendars content.to_s
+ return true unless period
+ return true unless period.first
+ return true unless period.end
+ return true unless period.first <= period.end
+ false
+ end
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_purchase_windows/index.rabl b/app/views/autocomplete_purchase_windows/index.rabl
index 1d0287602..bdc513c31 100644
--- a/app/views/autocomplete_purchase_windows/index.rabl
+++ b/app/views/autocomplete_purchase_windows/index.rabl
@@ -2,11 +2,12 @@ collection @purchase_windows, :object_root => false
node do |window|
{
- :id => window.id,
- :name => window.name,
- :objectid => window.objectid,
- :color => window.color,
- :short_id => window.get_objectid.short_id,
- :text => "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>"
+ id: window.id,
+ name: window.name,
+ objectid: window.objectid,
+ color: window.color,
+ short_id: window.get_objectid.short_id,
+ bounding_dates: window.bounding_dates,
+ text: "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>"
}
end
diff --git a/app/views/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/_form.html.slim b/app/views/compliance_control_blocks/_form.html.slim
index 2e87a877e..e8ae63384 100644
--- a/app/views/compliance_control_blocks/_form.html.slim
+++ b/app/views/compliance_control_blocks/_form.html.slim
@@ -1,6 +1,12 @@
= simple_form_for [@compliance_control_set, @compliance_control_block], html: { class: 'form-horizontal', id: 'compliance_control_block_form' }, wrapper: :horizontal_form do |f|
.row
.col-lg-12
+ - if @compliance_control_block.errors.has_key? :condition_attributes
+ .row.condition-attributes-errors
+ .col-lg-12
+ .alert.alert-danger
+ - @compliance_control_block.errors[:condition_attributes].each do |msg|
+ p.small = "- #{msg}"
.form-group
= f.input :transport_mode, as: :select, collection: ComplianceControlBlock.sorted_transport_modes, label: t('activerecord.attributes.compliance_control_blocks.transport_mode'), label_method: lambda {|t| ("<span>" + t("enumerize.transport_mode.#{t}") + "</span>").html_safe }
= f.input :transport_submode, as: :select, collection: ComplianceControlBlock.sorted_transport_submodes, label: t('activerecord.attributes.compliance_control_blocks.transport_submode'), label_method: lambda {|t| ("<span>" + t("enumerize.transport_submode.#{t}") + "</span>").html_safe }
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..466695b5a 100644
--- a/app/views/dashboards/_dashboard.html.slim
+++ b/app/views/dashboards/_dashboard.html.slim
@@ -5,8 +5,8 @@
.panel-heading
h3.panel-title.with_actions
div
- = link_to workbench.name, workbench_path(workbench)
- span.badge.ml-xs = workbench.referentials.count if workbench.referentials.present?
+ = link_to t('dashboards.workbench.title', organisation: workbench.organisation.name), workbench_path(workbench)
+ span.badge.ml-xs = workbench.all_referentials.uniq.count if workbench.all_referentials.present?
div
= link_to '', workbench_path(workbench), class: ' fa fa-chevron-right pull-right', title: t('workbenches.index.offers.see')
@@ -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/_main_nav_left_content_stif.html.slim b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim
index 02614dcab..3741ef19b 100644
--- a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim
+++ b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim
@@ -5,44 +5,44 @@
.panel-heading
h4.panel-title
= link_to '#miOne', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do
- |Offres courantes
+ = t('layouts.navbar.current_offer.other')
#miOne.panel-collapse.collapse
.list-group
= link_to root_path, class: "list-group-item #{(@localizationUrl == 'workbenches#index') ? 'active' : ''}" do
- span Tableau de bord
+ span = t('layouts.navbar.dashboard')
+ = link_to workbench_output_path(current_user.workbenches.first), class: 'list-group-item' do
+ span = t('layouts.navbar.workbench_outputs.organisation')
= link_to '#', class: 'list-group-item' do
- span Offre de mon organisation
- = link_to '#', class: 'list-group-item' do
- span Offre IDF
+ span = t('layouts.navbar.workbench_outputs.idf')
.menu-item.panel
.panel-heading
h4.panel-title
= link_to '#miTwo', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do
- |Espace de travail
+ - t('activerecord.models.workbench.one').capitalize
#miTwo.panel-collapse.collapse
.list-group
- current_user.workbenches.each do |current_workbench|
= link_to workbench_path(current_workbench), class: "list-group-item #{params[:controller] == 'workbenches' ? 'active' : ''}" do
- span Jeux de données
+ span = t('activerecord.models.referential.other').capitalize
= link_to workbench_imports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do
- span Import
+ span = t('activerecord.models.import.other').capitalize
= link_to workbench_exports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'exports') ? 'active' : ''}" do
- span Export
+ span = t('activerecord.models.export.other').capitalize
= link_to workgroup_calendars_path(current_workbench.workgroup), class: 'list-group-item' do
- span Modèles de calendrier
+ span = t('activerecord.models.calendar.other').capitalize
= link_to workbench_compliance_check_sets_path(current_workbench), class: 'list-group-item' do
- span Rapport de contrôle
+ span = t('activerecord.models.compliance_check_set.other').capitalize
= link_to compliance_control_sets_path, class: 'list-group-item' do
- span Jeux de contrôle
+ span = t('activerecord.models.compliance_control_set.other').capitalize
.menu-item.panel
.panel-heading
h4.panel-title
= link_to '#miThree', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do
- |Données
+ = t('layouts.navbar.referential_datas')
#miThree.panel-collapse.collapse
- if @referential.try(:id) && respond_to?(:current_referential)
@@ -57,7 +57,7 @@
span = t('companies.index.title')
= link_to '#', class: 'list-group-item disabled' do
- span Tracés
+ span = t('layouts.navbar.shapes')
= link_to referential_time_tables_path(current_referential), class: 'list-group-item' do
span = t('time_tables.index.title')
@@ -65,45 +65,45 @@
- else
.panel-body
em.text-muted
- = "Sélectionnez un jeu de données pour accéder à plus de fonctionnalités"
+ = t('layouts.navbar.select_referential_for_more_features')
.menu-item.panel
.panel-heading
h4.panel-title
= link_to '#miFour', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do
- |Synchronisation
+ = t('layouts.navbar.sync')
#miFour.panel-collapse.collapse
.list-group
= link_to line_referential_path(1), class: "list-group-item #{(@localizationUrl == 'line_referentials#show') ? 'active' : ''}" do
- span Synchronisation iLICO
+ span = t('layouts.navbar.sync_ilico')
= link_to stop_area_referential_path(1), class: "list-group-item #{(@localizationUrl == 'stop_area_referentials#show') ? 'active' : ''}" do
- span Synchronisation iCAR
+ span = t('layouts.navbar.sync_icar')
.menu-item.panel
.panel-heading
h4.panel-title
= link_to '#miFive', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do
- |Outils
+ = t('layouts.navbar.tools')
#miFive.panel-collapse.collapse
.list-group
= link_to Rails.application.config.try(:portal_url), target: '_blank', class: 'list-group-item' do
span
span.fa.fa-2x.fa-circle
- |Portail (POSTIF)
+ = t('layouts.navbar.portal')
= link_to Rails.application.config.try(:codifligne_url), target: '_blank', class: 'list-group-item' do
span
span.fa.fa-2x.fa-circle
- |iLICO
+ = t('layouts.navbar.ilico')
= link_to Rails.application.config.try(:reflex_url), target: '_blank', class: 'list-group-item' do
span
span.fa.fa-2x.fa-circle
- |iCAR
+ = t('layouts.navbar.icar')
= link_to '#', target: '_blank', class: 'list-group-item' do
span
span.fa.fa-2x.fa-circle
- |Support
+ = t('layouts.navbar.support') \ No newline at end of file
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/lines/_filters.html.slim b/app/views/lines/_filters.html.slim
index da0539bd0..f745d10a4 100644
--- a/app/views/lines/_filters.html.slim
+++ b/app/views/lines/_filters.html.slim
@@ -44,5 +44,5 @@
.actions
- = link_to 'Effacer', @workbench, class: 'btn btn-link'
- = f.submit 'Filtrer', class: 'btn btn-default'
+ = link_to t('actions.erase'), @workbench, class: 'btn btn-link'
+ = f.submit t('actions.filter'), class: 'btn btn-default'
diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim
index 96bb5bb0d..9e1ae6d6f 100644
--- a/app/views/lines/show.html.slim
+++ b/app/views/lines/show.html.slim
@@ -6,7 +6,7 @@
.row
.col-lg-6.col-md-6.col-sm-12.col-xs-12
= definition_list t('metadatas'),
- { t('id_codif') => @line.get_objectid.short_id,
+ { t('objectid') => @line.get_objectid.short_id,
@line.human_attribute_name(:deactivated) => (@line.deactivated? ? t('false') : t('true')),
@line.human_attribute_name(:network_id) => (@line.network.nil? ? t('lines.index.unset') : @line.network.name),
@line.human_attribute_name(:company_id) => (@line.company.nil? ? t('lines.index.unset') : @line.company.name),
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..0c9fb1f87 100644
--- a/app/views/referential_companies/edit.html.slim
+++ b/app/views/referential_companies/edit.html.slim
@@ -1,3 +1,8 @@
- breadcrumb :referential_company, @referential, @company
- page_header_content_for @company
-= 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/new.html.slim b/app/views/referential_companies/new.html.slim
index 5e59db139..1dfdc8eb5 100644
--- a/app/views/referential_companies/new.html.slim
+++ b/app/views/referential_companies/new.html.slim
@@ -1,2 +1,6 @@
- breadcrumb :referential_companies, @referential
-= render 'form'
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = render 'form'
diff --git a/app/views/referential_companies/show.html.slim b/app/views/referential_companies/show.html.slim
index 1599145be..8ad011edf 100644
--- a/app/views/referential_companies/show.html.slim
+++ b/app/views/referential_companies/show.html.slim
@@ -17,8 +17,11 @@
.container-fluid
.row
.col-lg-6.col-md-6.col-sm-12.col-xs-12
- = definition_list t('metadatas'),
- { t('id_codif') => @company.try(:objectid).try(:local_id),
- Chouette::Company.human_attribute_name(:phone) => @company.phone,
- Chouette::Company.human_attribute_name(:email) => @company.email,
- Chouette::Company.human_attribute_name(:url) => @company.url }
+ - attributes = { t('id_codif') => @company.try(:objectid).try(:local_id),
+ Chouette::Company.human_attribute_name(:phone) => @company.phone,
+ Chouette::Company.human_attribute_name(:email) => @company.email,
+ Chouette::Company.human_attribute_name(:url) => @company.url }
+ - @company.custom_fields(@referential.workgroup).each do |code, field|
+ - attributes.merge!(field.name => field.display_value)
+
+ = definition_list t('metadatas'), attributes
diff --git a/app/views/referential_lines/_filters.html.slim b/app/views/referential_lines/_filters.html.slim
index 501f61c16..15db0e33e 100644
--- a/app/views/referential_lines/_filters.html.slim
+++ b/app/views/referential_lines/_filters.html.slim
@@ -1,7 +1,7 @@
= search_form_for @q, url: referential_line_path(@referential, @line), class: 'form form-filter' do |f|
.ffg-row
.input-group.search_bar class=filter_item_class(params[:q], :name_or_objectid_cont)
- = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: "Indiquez un nom d'itinéraire ou un ID..."
+ = f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: t('.name_or_objectid_cont')
span.input-group-btn
button.btn.btn-default#search-btn type='submit'
span.fa.fa-search
@@ -12,5 +12,5 @@
= f.input :wayback_eq_any, class: 'form-control', collection: Chouette::Route.wayback.values, as: :check_boxes, label: false, required: false, wrapper_html: { class: 'checkbox_list'}, label_method: lambda{|l| ("<span>" + t("enumerize.route.wayback.#{l}") + "</span>").html_safe}
.actions
- = link_to 'Effacer', referential_line_path(@referential, @line), class: 'btn btn-link'
- = f.submit 'Filtrer', class: 'btn btn-default'
+ = link_to t('actions.erase'), referential_line_path(@referential, @line), class: 'btn btn-link'
+ = f.submit t('actions.filter'), class: 'btn btn-default'
diff --git a/app/views/referential_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/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
index f1fbdb5d8..f9fa4fcf7 100644
--- a/app/views/referential_vehicle_journeys/_filters.html.slim
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -68,5 +68,5 @@
.actions
- = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link'
- = f.submit 'Filtrer', class: 'btn btn-default'
+ = link_to t('actions.erase'), referential_vehicle_journeys_path(@referential), class: 'btn btn-link'
+ = f.submit t('actions.filter'), class: 'btn btn-default'
diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim
index 96d847ec1..c378f871e 100644
--- a/app/views/referentials/_form.html.slim
+++ b/app/views/referentials/_form.html.slim
@@ -49,7 +49,7 @@
.separator
.row
.col-lg-11
- = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).where(objectid: current_functional_scope), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' }
+ = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).for_organisation(current_organisation), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' }
.col-lg-1
a.clear-lines.btn.btn-default href='#'
.fa.fa-trash
diff --git a/app/views/referentials/_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..7be82364d 100644
--- a/app/views/referentials/select_compliance_control_set.html.slim
+++ b/app/views/referentials/select_compliance_control_set.html.slim
@@ -1,3 +1,5 @@
+- breadcrumb @referential
+
.page_content
.container-fluid
.row
@@ -6,7 +8,7 @@
.row
.col-lg-12
.form-group
- = label_tag 'name', nil, class: 'string required col-sm-4 col-xs-5 control-label'
+ = label_tag ComplianceControlSet.ts, nil, class: 'string required col-sm-4 col-xs-5 control-label'
.col-sm-8.col-xs-7
= select_tag :compliance_control_set, options_from_collection_for_select(@compliance_control_sets, "id", "name"), class: 'select optional form-control'
.separator
diff --git a/app/views/routes/show.html.slim b/app/views/routes/show.html.slim
index 375d7c57b..d4571c173 100644
--- a/app/views/routes/show.html.slim
+++ b/app/views/routes/show.html.slim
@@ -34,7 +34,7 @@
end \
), \
TableBuilderHelper::Column.new( \
- key: :deleted_at, \
+ name: Chouette::Line.tmf('activated'), \
attribute: Proc.new { |s| line_status(s.try(:stop_area).deleted_at) } \
), \
TableBuilderHelper::Column.new( \
@@ -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..4e2c4d849 100644
--- a/app/views/shared/iev_interfaces/_messages.html.slim
+++ b/app/views/shared/iev_interfaces/_messages.html.slim
@@ -1,14 +1,15 @@
- if messages.any?
ul.list-unstyled.import_message-list
- messages.order(:created_at).each do | message |
+ - width = message.resource_attributes.present? ? 6 : 12
li
.row class=bootstrap_class_for_message_criticity(message.criticity)
- if message.message_attributes && message.message_attributes["line"]
.col-md-1= "L. #{message.message_attributes["line"]}"
- .col-md-5= export_message_content message
+ div class="col-md-#{width-1}"= export_message_content message
- else
- .col-md-6= export_message_content message
+ div class="col-md-#{width}"= export_message_content message
.col-md-6
- - if message.criticity != "info"
+ - if message.resource_attributes.present?
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..74e607e26 100644
--- a/app/views/stif/dashboards/_dashboard.html.slim
+++ b/app/views/stif/dashboards/_dashboard.html.slim
@@ -1,16 +1,17 @@
.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
h3.panel-title
= t('.organisation')
- .panel-body
- em.small.text-muted = t('.no_content')
+ - if @dashboard.workbench.output.referentials.present?
+ - @dashboard.workbench.output.referentials.first(5).each do |referential|
+ .list-group
+ = link_to referential.name, referential_path(referential), class: 'list-group-item'
+ - else
+ .panel-body
+ em.small.text-muted = t('.no_content')
.panel.panel-default
.panel-heading
diff --git a/app/views/stop_areas/_filters.html.slim b/app/views/stop_areas/_filters.html.slim
index a32638567..c698eaaa5 100644
--- a/app/views/stop_areas/_filters.html.slim
+++ b/app/views/stop_areas/_filters.html.slim
@@ -41,5 +41,5 @@
input_html: { checked: @status.try(:[], :deactivated) }
.actions
- = link_to 'Effacer', @workbench, class: 'btn btn-link'
- = f.submit 'Filtrer', class: 'btn btn-default'
+ = link_to t('actions.erase'), @workbench, class: 'btn btn-link'
+ = f.submit t('actions.filter'), class: 'btn btn-default'
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index 1cba88f94..00f2ad8bb 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -48,7 +48,7 @@
- if has_feature?(:stop_area_waiting_time)
= f.input :waiting_time, input_html: { min: 0 }
- = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)}
+ = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)}, hint: stop_area_registration_number_hint
= f.input :fare_code
= f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")}
= f.input :comment, as: :text, :input_html => {:rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment")}
@@ -62,6 +62,12 @@
= f.input :stairs_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true
= f.input :lift_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true
+ - if resource.custom_fields(resource.stop_area_referential.workgroup).any?
+ .custom_fields
+ h3 = t("stop_areas.stop_area.custom_fields")
+ - resource.custom_fields(resource.stop_area_referential.workgroup).each do |code, field|
+ = field.input(f).to_s
+
.separator
= f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'stop_area_form'
diff --git a/app/views/stop_areas/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/index.html.slim b/app/views/stop_areas/index.html.slim
index 587efbdaa..fbdb54e02 100644
--- a/app/views/stop_areas/index.html.slim
+++ b/app/views/stop_areas/index.html.slim
@@ -32,7 +32,7 @@
attribute: 'registration_number' \
), \
TableBuilderHelper::Column.new( \
- name: t('activerecord.attributes.stop_area.state'), \
+ name: Chouette::StopArea.tmf('state'), \
attribute: Proc.new { |s| stop_area_status(s) } \
), \
TableBuilderHelper::Column.new( \
diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim
index a6147b86d..851bd9b82 100644
--- a/app/views/stop_areas/show.html.slim
+++ b/app/views/stop_areas/show.html.slim
@@ -23,4 +23,6 @@
t('activerecord.attributes.stop_area.state') => stop_area_status(@stop_area),
@stop_area.human_attribute_name(:comment) => @stop_area.try(:comment),
})
+ - @stop_area.custom_fields.each do |code, field|
+ - attributes.merge!(field.name => field.display_value)
= definition_list t('metadatas'), attributes
diff --git a/app/views/time_tables/index.html.slim b/app/views/time_tables/index.html.slim
index 6913712a0..58bf66a9d 100644
--- a/app/views/time_tables/index.html.slim
+++ b/app/views/time_tables/index.html.slim
@@ -28,14 +28,14 @@
end \
), \
TableBuilderHelper::Column.new( \
+ key: :bounding_dates, \
name: "Période englobante", \
- attribute: Proc.new { |tt| tt.bounding_dates.empty? ? '-' : t('bounding_dates', debut: l(tt.bounding_dates.min), end: l(tt.bounding_dates.max)) }, \
- sortable: false \
+ attribute: Proc.new { |tt| tt.object.bounding_dates.empty? ? '-' : t('bounding_dates', debut: l(tt.object.bounding_dates.min), end: l(tt.object.bounding_dates.max)) }, \
), \
TableBuilderHelper::Column.new( \
+ key: :vehicle_journeys_count, \
name: "Nombre de courses associées", \
attribute: Proc.new{ |tt| tt.vehicle_journeys.count }, \
- sortable: false \
), \
TableBuilderHelper::Column.new( \
name: "Journées d'application", \
diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim
index d23c61394..7fcee545f 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.reversed_vehicle_journey'), [@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/app/views/workbench_outputs/show.html.slim b/app/views/workbench_outputs/show.html.slim
index a9e106dbb..b310119e6 100644
--- a/app/views/workbench_outputs/show.html.slim
+++ b/app/views/workbench_outputs/show.html.slim
@@ -6,7 +6,8 @@
.row.mb-sm
.col-lg-12.text-right
= link_to t('.see_current_output'), referential_path(@workbench.output.current), class: 'btn btn-primary' if @workbench.output&.current
- = link_to t('merges.actions.create'), new_workbench_merge_path(@workbench), class: 'btn btn-primary'
+ - if policy(Merge).create?
+ = link_to t('merges.actions.create'), new_workbench_merge_path(@workbench), class: 'btn btn-primary'
.page_content
.container-fluid
diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim
index 8312338d0..7dd1583fa 100644
--- a/app/views/workbenches/show.html.slim
+++ b/app/views/workbenches/show.html.slim
@@ -32,7 +32,8 @@
end \
), \
TableBuilderHelper::Column.new( \
- key: :status, \
+ key: :archived_at, \
+ name: Referential.tmf('status'), \
attribute: Proc.new {|w| w.referential_read_only? ? ("<div class='td-block'><span class='fa fa-archive'></span><span>#{t('activerecord.attributes.referential.archived_at')}</span></div>").html_safe : ("<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>#{t('activerecord.attributes.referential.archived_at_null')}</span></div>").html_safe} \
), \
TableBuilderHelper::Column.new( \
@@ -45,7 +46,7 @@
), \
TableBuilderHelper::Column.new( \
key: :lines, \
- name: t('activerecord.attributes.referential.number_of_lines'), \
+ name: Referential.tmf('number_of_lines'), \
attribute: Proc.new {|w| w.lines.count} \
), \
TableBuilderHelper::Column.new( \
diff --git a/app/workers/gtfs_import_worker.rb b/app/workers/gtfs_import_worker.rb
new file mode 100644
index 000000000..02f5053b0
--- /dev/null
+++ b/app/workers/gtfs_import_worker.rb
@@ -0,0 +1,7 @@
+class GtfsImportWorker
+ include Sidekiq::Worker
+
+ def perform(import_id)
+ Import::Gtfs.find(import_id).import
+ end
+end
diff --git a/app/workers/route_way_cost_worker.rb b/app/workers/route_way_cost_worker.rb
index d6bfed592..b62416c3d 100644
--- a/app/workers/route_way_cost_worker.rb
+++ b/app/workers/route_way_cost_worker.rb
@@ -7,10 +7,11 @@ class RouteWayCostWorker
# Prevent recursive worker spawning since this call updates the
# `costs` field of the route.
- Chouette::Route.skip_callback(:save, :after, :calculate_costs!)
-
- RouteWayCostCalculator.new(route).calculate!
-
- Chouette::Route.set_callback(:save, :after, :calculate_costs!)
+ begin
+ Chouette::Route.skip_callback(:commit, :after, :calculate_costs!)
+ RouteWayCostCalculator.new(route).calculate!
+ ensure
+ Chouette::Route.set_callback(:commit, :after, :calculate_costs!)
+ end
end
end
diff --git a/app/workers/workbench_import_worker/object_state_updater.rb b/app/workers/workbench_import_worker/object_state_updater.rb
index 67bdc0654..1edc6b9a1 100644
--- a/app/workers/workbench_import_worker/object_state_updater.rb
+++ b/app/workers/workbench_import_worker/object_state_updater.rb
@@ -6,9 +6,10 @@ class WorkbenchImportWorker
workbench_import.update( total_steps: count )
update_spurious entry
update_foreign_lines entry
+ update_missing_calendar entry
+ update_wrong_calendar entry
end
-
private
def update_foreign_lines entry
@@ -19,7 +20,7 @@ class WorkbenchImportWorker
message_attributes: {
'source_filename' => workbench_import.file.file.file,
'foreign_lines' => entry.foreign_lines.join(', ')
- })
+ })
end
def update_spurious entry
@@ -30,7 +31,27 @@ class WorkbenchImportWorker
message_attributes: {
'source_filename' => workbench_import.file.file.file,
'spurious_dirs' => entry.spurious.join(', ')
- })
+ })
+ end
+
+ def update_missing_calendar entry
+ return unless entry.missing_calendar
+ workbench_import.messages.create(
+ criticity: :error,
+ message_key: 'missing_calendar_in_zip_file',
+ message_attributes: {
+ 'source_filename' => entry.name
+ })
+ end
+
+ def update_wrong_calendar entry
+ return unless entry.wrong_calendar
+ workbench_import.messages.create(
+ criticity: :error,
+ message_key: 'wrong_calendar_in_zip_file',
+ message_attributes: {
+ 'source_filename' => entry.name
+ })
end
end
end
diff --git a/config/database/ci.yml b/config/database/ci.yml
index 44103454a..5671cb6ad 100644
--- a/config/database/ci.yml
+++ b/config/database/ci.yml
@@ -1,4 +1,4 @@
-test:
+test: &default
adapter: <%= ENV.fetch 'RAILS_DB_ADAPTER', 'postgis' %>
encoding: unicode
pool: <%= ENV.fetch 'RAILS_DB_POOLSIZE', '5' %>
@@ -9,3 +9,7 @@ test:
database: <%= ENV.fetch 'RAILS_DB_NAME', 'stif_boiv_test' %>
username: <%= ENV['RAILS_DB_USER'] || ENV['POSTGRESQL_ENV_POSTGRES_USER'] || 'jenkins' %>
password: <%= ENV['RAILS_DB_PASSWORD'] || ENV['POSTGRESQL_ENV_POSTGRES_PASSWORD'] %>
+
+# Only used to build assets
+production:
+ <<: *default
diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb
index 4b9bd320d..8dbc3afee 100644
--- a/config/initializers/simple_form_bootstrap.rb
+++ b/config/initializers/simple_form_bootstrap.rb
@@ -72,9 +72,9 @@ SimpleForm.setup do |config|
b.use :placeholder
b.optional :maxlength
b.optional :readonly
- b.use :label, class: 'col-sm-3 control-label'
+ b.use :label, class: 'col-sm-4 col-xs-5 control-label'
- b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
+ b.wrapper tag: 'div', class: 'col-sm-8 col-xs-7' do |ba|
ba.use :input
ba.use :error, wrap_with: { tag: 'span', class: 'help-block small' }
ba.use :hint, wrap_with: { tag: 'p', class: 'help-block small' }
diff --git a/config/locales/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..696ae2734 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,16 +61,15 @@ 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
- organisation: Organisation
+ organisation: Organization
monday: "Monday"
tuesday: "Tuesday"
wednesday: "Wednesday"
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/companies.en.yml b/config/locales/companies.en.yml
index becb087b1..f2b19bc19 100644
--- a/config/locales/companies.en.yml
+++ b/config/locales/companies.en.yml
@@ -16,7 +16,7 @@ en:
index:
title: "Companies"
name: "Search by name..."
- name_or_objectid: "Search by name or by Codifligne ID..."
+ name_or_objectid: "Search by name or by ID..."
advanced_search: "Advanced search"
activerecord:
models:
diff --git a/config/locales/compliance_check_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..73ecf8996 100644
--- a/config/locales/compliance_check_sets.en.yml
+++ b/config/locales/compliance_check_sets.en.yml
@@ -20,10 +20,10 @@ 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"
+ metrics: "%{error_count} errors, %{warning_count} warnings"
metadatas:
referential: "Object analysed"
referential_type: "Apply to"
diff --git a/config/locales/compliance_check_sets.fr.yml b/config/locales/compliance_check_sets.fr.yml
index 20bf11d85..045fed4ce 100644
--- a/config/locales/compliance_check_sets.fr.yml
+++ b/config/locales/compliance_check_sets.fr.yml
@@ -19,7 +19,7 @@ fr:
table_state: "%{lines_status} lignes valides sur %{lines_in_compliance_check_set} présentes dans l'offre de transport"
table_explanation: Ces contrôles s’appliquent pour toutes les données importées et conditionnent la construction de l’offre de votre organisation
table_title: État des lignes analysées
- metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"
+ metrics: "%{error_count} errors, %{warning_count} warnings"
metadatas:
referential: "Objet analysé"
referential_type: "Appliqué à"
diff --git a/config/locales/compliance_control_blocks.en.yml b/config/locales/compliance_control_blocks.en.yml
index 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..472f5bc2f 100644
--- a/config/locales/dashboard.en.yml
+++ b/config/locales/dashboard.en.yml
@@ -1,7 +1,10 @@
en:
dashboards:
+ main_nav_left: Dashboard
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..65022fcb8 100644
--- a/config/locales/dashboard.fr.yml
+++ b/config/locales/dashboard.fr.yml
@@ -1,7 +1,10 @@
fr:
dashboards:
+ main_nav_left: Tableau de bord
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/import_messages.en.yml b/config/locales/import_messages.en.yml
index bc06c46f0..9f370d319 100644
--- a/config/locales/import_messages.en.yml
+++ b/config/locales/import_messages.en.yml
@@ -2,7 +2,10 @@ en:
import_message:
corrupt_zip_file: "The zip file %{source_filename} is corrupted and cannot be read"
inconsistent_zip_file: "The zip file %{source_filename} contains unexpected directories: %{spurious_dirs}, which are ignored"
- referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes"
+ missing_calendar_in_zip_file: "The folder %{source_filename} lacks a calendar file"
+ wrong_calendar_in_zip_file: "The calendar file %{source_filename} cannot be parsed, or contains inconsistant data"
+ referential_creation: "The referential %{referential_name} has not been created because another referential with the same lines and periods already exists"
+ referential_creation_missing_lines: "The referential %{referential_name} has not been created because no matching line has been found"
1_netexstif_2: "Le fichier %{source_filename} ne respecte pas la syntaxe XML ou la XSD NeTEx : erreur '%{error_value}' rencontré"
1_netexstif_5: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet %{source_label} d'identifiant %{source_objectid} a une date de mise à jour dans le futur"
2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NETEX_COMMUN"
diff --git a/config/locales/import_messages.fr.yml b/config/locales/import_messages.fr.yml
index 1e5054648..5ecef68d1 100644
--- a/config/locales/import_messages.fr.yml
+++ b/config/locales/import_messages.fr.yml
@@ -2,10 +2,13 @@ fr:
import_messages:
corrupt_zip_file: "Le fichier zip est corrompu, et ne peut être lu"
inconsistent_zip_file: "Le fichier zip contient des repertoires non prévus : %{spurious_dirs} qui seront ignorés"
- referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes"
+ missing_calendar_in_zip_file: "Le dossier %{source_filename} ne contient pas de calendrier"
+ wrong_calendar_in_zip_file: "Le calendrier contenu dans %{source_filename} contient des données incorrectes ou incohérentes"
+ referential_creation: "Le référentiel %{referential_name} n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes"
+ referential_creation_missing_lines: "Le référentiel %{referential_name} n'a pas pu être créé car aucune ligne ne correspond"
1_netexstif_2: "Le fichier %{source_filename} ne respecte pas la syntaxe XML ou la XSD NeTEx : erreur '%{error_value}' rencontré"
1_netexstif_5: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet %{source_label} d'identifiant %{source_objectid} a une date de mise à jour dans le futur"
- 2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NETEX_COMMUN"
+ 2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NTEX_COMMUN"
2_netexstif_1_2: "Le fichier commun.xml contient une frame nommée %{error_value} non acceptée"
2_netexstif_2_1: "Le fichier calendriers.xml ne contient pas de frame nommée NETEX_CALENDRIER"
2_netexstif_2_2: "Le fichier calendriers.xml contient une frame nommée %{error_value} non acceptée"
diff --git a/config/locales/import_resources.en.yml b/config/locales/import_resources.en.yml
index 386039319..4b1f9c394 100644
--- a/config/locales/import_resources.en.yml
+++ b/config/locales/import_resources.en.yml
@@ -6,7 +6,7 @@ en:
table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile"
table_title: "Satus of anlyzed files"
table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed."
- metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"
+ metrics: "%{error_count} errors, %{warning_count} warnings"
import_resources:
<<: *resources
activerecord:
diff --git a/config/locales/import_resources.fr.yml b/config/locales/import_resources.fr.yml
index 50fb7f1ca..93a576f01 100644
--- a/config/locales/import_resources.fr.yml
+++ b/config/locales/import_resources.fr.yml
@@ -6,7 +6,7 @@ fr:
table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive"
table_title: "Etat des fichiers analysés"
table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité."
- metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"
+ metrics: "%{error_count} errors, %{warning_count} warnings"
import_resources:
<<: *resources
activerecord:
diff --git a/config/locales/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..954525ac4 100644
--- a/config/locales/layouts.en.yml
+++ b/config/locales/layouts.en.yml
@@ -3,15 +3,33 @@ en:
back_to_dashboard: "Back to Dashboard"
help: "Help"
home: "Home"
+ operations: Operations
user:
profile: "My Profile"
sign_out: "Sign out"
navbar:
return_to_referentials: "Return to data spaces"
select_referential: "Select data space"
+ select_referential_for_more_features: "Select data space for more foeatures"
select_referential_datas: "Select datas"
return_to_dashboard: "Return to Dashboard"
+ dashboard: Dashboard
referential_datas: "Datas"
+ current_offer:
+ one: Current offer
+ other: Current offers
+ workbench_output:
+ organisation: Organisation offers
+ idf: IDF offers
+ tools: Tools
+ sync: Synchronization
+ sync_icar: iCAR synchronization
+ sync_ilico: iLICO synchronization
+ icar: iCAR
+ ilico: iLICO
+ portal: Portal (POSTIF)
+ support: Support
+ shapes: Shapes
history_tag:
title: "Metadatas"
created_at: "Created at"
diff --git a/config/locales/layouts.fr.yml b/config/locales/layouts.fr.yml
index 5e835bcf7..f125a002d 100644
--- a/config/locales/layouts.fr.yml
+++ b/config/locales/layouts.fr.yml
@@ -3,15 +3,33 @@ fr:
back_to_dashboard: "Retour au Tableau de Bord"
help: "Aide"
home: "Accueil"
+ operations: Opérations
user:
profile: "Mon Profil"
sign_out: "Déconnexion"
navbar:
return_to_referentials: "Retour à la liste des espaces de données"
select_referential: "Sélection de l'espace de données"
+ select_referential_for_more_features: "Sélectionnez un jeu de données pour accéder à plus de fonctionnalités"
select_referential_datas: "Sélection des données"
return_to_dashboard: "Retour au Tableau de Bord"
+ dashboard: Tableau de bord
referential_datas: "Données"
+ current_offer:
+ one: Offres courante
+ other: Offres courantes
+ workbench_outputs:
+ organisation: Offres de mon organisation
+ idf: Offres IDF
+ tools: Outils
+ sync: Synchronisation
+ sync_icar: Synchronisation iCAR
+ sync_ilico: Synchronisation iLICO
+ icar: iCAR
+ ilico: iLICO
+ portal: Portail (POSTIF)
+ support: Support
+ shapes: Tracés
history_tag:
title: "Métadonnées"
created_at: "Créé le"
diff --git a/config/locales/line_referentials.en.yml b/config/locales/line_referentials.en.yml
index 5663ed691..18ff28c24 100644
--- a/config/locales/line_referentials.en.yml
+++ b/config/locales/line_referentials.en.yml
@@ -7,7 +7,7 @@ en:
edit:
title: "Edit %{name} referential"
show:
- title: "iLICO synchronization"
+ title: Line referential
synchronized: Synchronized
status: Status
message: Message
diff --git a/config/locales/lines.en.yml b/config/locales/lines.en.yml
index e61013725..1cd5150db 100644
--- a/config/locales/lines.en.yml
+++ b/config/locales/lines.en.yml
@@ -1,6 +1,5 @@
en:
lines: &en_lines
- search_no_results: "No line matching your query"
actions:
new: "Add a new line"
edit: "Edit this line"
@@ -20,6 +19,8 @@ en:
show: 'Show'
show_network: 'Show network'
show_company: 'Show company'
+ filters:
+ name_or_objectid_cont: "Search by name or objectid"
new:
title: "Add a new line"
create:
@@ -33,11 +34,12 @@ en:
routes:
title: "Routes list"
group_of_lines: "Groups of lines"
+ search_no_results: "No line matching your query"
index:
deactivated: "Disabled line"
title: "Lines"
line: "Line %{line}"
- name_or_number_or_objectid: "Search by name, short name or ID Codif..."
+ name_or_number_or_objectid: "Search by name, short name or ID..."
no_networks: "No networks"
no_companies: "No companies"
no_group_of_lines: "No group of lines"
@@ -115,7 +117,7 @@ en:
creator_id: "Created by"
footnotes: "Footnotes"
stable_id: External permanent idenifier"
- state: State
+ state: Status
activated: Activated
deactivated: Deactivated
formtastic:
diff --git a/config/locales/lines.fr.yml b/config/locales/lines.fr.yml
index d3069f1d1..6f4a2e9bf 100644
--- a/config/locales/lines.fr.yml
+++ b/config/locales/lines.fr.yml
@@ -1,6 +1,5 @@
fr:
lines: &fr_lines
- search_no_results: "Aucune ligne ne correspond à votre recherche"
actions:
new: "Ajouter une ligne"
edit: "Editer cette ligne"
@@ -20,6 +19,8 @@ fr:
show: 'Consulter'
show_network: 'Voir le réseau'
show_company: 'Voir le transporteur principal'
+ filters:
+ name_or_objectid_cont: "Indiquez un nom d'itinéraire ou un ID..."
new:
title: "Ajouter une ligne"
create:
@@ -34,11 +35,12 @@ fr:
title: "Liste des Itinéraires"
itineraries: "Liste des séquences d'arrêts de la ligne"
group_of_lines: "Groupes de lignes"
+ search_no_results: "Aucune ligne ne correspond à votre recherche"
index:
deactivated: "Ligne désactivée"
title: "Lignes"
line: "Ligne %{line}"
- name_or_number_or_objectid: "Recherche par nom, nom court ou ID Codif..."
+ name_or_number_or_objectid: "Recherche par nom, nom court ou ID..."
no_networks: "Aucun réseaux"
no_companies: "Aucun transporteurs"
no_group_of_lines: "Aucun groupes de ligne"
diff --git a/config/locales/merges.yml b/config/locales/merges.yml
index d8511a7b4..345727b74 100644
--- a/config/locales/merges.yml
+++ b/config/locales/merges.yml
@@ -8,7 +8,7 @@ fr:
show:
title: "Finalisation de l'offre %{name}"
actions:
- create: "Finaliser des Jeux de Données"
+ create: Finaliser des Jeux de Données
activerecord:
models:
merge: "Finalisation de l'offre"
diff --git a/config/locales/networks.en.yml b/config/locales/networks.en.yml
index 94a8d9df0..2046a30ae 100644
--- a/config/locales/networks.en.yml
+++ b/config/locales/networks.en.yml
@@ -15,7 +15,7 @@ en:
index:
title: "Networks"
name: "Search by name..."
- name_or_objectid: "Search by name or by Codifligne ID..."
+ name_or_objectid: "Search by name or by ID..."
advanced_search: "Advanced search"
activerecord:
models:
diff --git a/config/locales/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..cf012ef8e 100644
--- a/config/locales/referentials.fr.yml
+++ b/config/locales/referentials.fr.yml
@@ -32,7 +32,7 @@ fr:
title: 'Dupliquer un jeu de données'
submit: "Valider"
select_compliance_control_set:
- title: "Sélection du jeu de contôle"
+ title: "Sélection du jeu de contrôles"
actions:
new: "Créer un jeu de données"
destroy_confirm: "Etes vous sûr de vouloir supprimer ce jeu de données ?"
diff --git a/config/locales/routes.en.yml b/config/locales/routes.en.yml
index bd8358bdd..66805e050 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"
@@ -13,7 +15,7 @@ en:
export_hub_all: "Export HUB routes"
add_stop_point: "Add stop point"
new_stop_point: "Create new stop"
- opposite_route_timetable: "Timetable back"
+ reversed_vehicle_journey: "Reversed vehicle journeys"
new:
title: "Add a new route"
edit:
@@ -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..f4eefa10d 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"
@@ -13,7 +15,8 @@ fr:
export_hub_all: "Export HUB des itinéraires"
add_stop_point: "Ajouter un arrêt"
new_stop_point: "Créer un arrêt pour l'ajouter"
- opposite_route_timetable: "Horaires retour"
+ reversed_vehicle_journey: "Horaires retour"
+ opposite: "%{name} (retour)"
new:
title: "Ajouter un itinéraire"
edit:
@@ -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/select2.en.yml b/config/locales/select2.en.yml
new file mode 100644
index 000000000..308540af0
--- /dev/null
+++ b/config/locales/select2.en.yml
@@ -0,0 +1,15 @@
+en:
+ select2:
+ error_loading: The results cannot be loaded.
+ input_too_long:
+ one: Remove %{count} letter
+ other: Remove %{count} letters
+ input_too_short:
+ one: Type %{count} letter
+ other: Type %{count} letters
+ loading_more: Loading more…
+ maximum_selected:
+ one: You can select %{count} element
+ other: You can select %{count} elements
+ no_results: No Results
+ searching: Searching...
diff --git a/config/locales/select2.fr.yml b/config/locales/select2.fr.yml
new file mode 100644
index 000000000..4a37a3d78
--- /dev/null
+++ b/config/locales/select2.fr.yml
@@ -0,0 +1,15 @@
+fr:
+ select2:
+ error_loading: Les résultats ne peuvent pas être chargés.
+ input_too_long:
+ one: Supprimez %{count} caractère
+ other: Supprimez %{count} caractères
+ input_too_short:
+ one: Saisissez %{count} caractère
+ other: Saisissez %{count} caractères
+ loading_more: Chargement de résultats supplémentaires…
+ maximum_selected:
+ one: Vous pouvez sélectionner %{count} élément
+ other: Vous pouvez sélectionner %{count} élément
+ no_results: Aucun résultat trouvé
+ searching: Recherche en cours... \ No newline at end of file
diff --git a/config/locales/stop_area_referentials.en.yml b/config/locales/stop_area_referentials.en.yml
index 11baf67e2..9d49d7c5d 100644
--- a/config/locales/stop_area_referentials.en.yml
+++ b/config/locales/stop_area_referentials.en.yml
@@ -4,7 +4,7 @@ en:
sync: "Launch a new reflex synchronization"
cancel_sync: "Cancel reflex synchronization"
show:
- title: 'Synchronization iCAR'
+ title: 'Stop area referential'
activerecord:
models:
stop_area_referential:
diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml
index 9e70993aa..1da4b58b4 100644
--- a/config/locales/stop_areas.en.yml
+++ b/config/locales/stop_areas.en.yml
@@ -8,7 +8,7 @@ en:
registration_number:
already_taken: Already taken
cannot_be_empty: This field is mandatory
- invalid: Incorrect value
+ invalid: "Incorrect value (expected value: \"%{mask}\")"
default_geometry_success: "%{count} modified stop areas"
stop_area:
no_position: "No Position"
@@ -18,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 ?"
@@ -47,9 +48,9 @@ en:
export_hub_physical: "Export HUB physical"
filters:
name_or_objectid: "Search by name or by objectid..."
- zip_code: Type a zip code...
- city_name: Type a city name...
- area_type: Type an area type...
+ zip_code: Enter a zip code...
+ city_name: Enter a city name...
+ area_type: Enter an area type...
new:
title: "Add a new stop"
update:
@@ -69,7 +70,7 @@ en:
stop_managment: "Parent-child relations"
access_managment: "Access Points and Links managment"
access_points: "Access Points"
- not_editable: "Le type d'arrêt est non modifiable"
+ not_editable: "The area type is not editable"
state:
active: Active
deactivated: Deactivated
@@ -147,7 +148,7 @@ en:
zip_code: "Zip code"
city_name: "City"
waiting_time: Waiting time (minutes)
- state: State
+ state: Status
formtastic:
titles:
stop_area:
@@ -205,6 +206,9 @@ en:
comment: "Maximum length = 255."
coordinates: "Coordinates are mandatory."
projection_xy: "x,y in secondary referential, dot for decimal separator"
+ hints:
+ stop_area:
+ registration_number: Leave empty for automatic value.
referential_stop_areas:
<<: *en_stop_areas
diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml
index aee112be7..6a5fbf24b 100644
--- a/config/locales/stop_areas.fr.yml
+++ b/config/locales/stop_areas.fr.yml
@@ -9,7 +9,7 @@ fr:
registration_number:
already_taken: Déjà utilisé
cannot_be_empty: Ce champ est requis
- invalid: Valeur invalide
+ invalid: "Valeur invalide (valeur attendue: \"%{mask}\")"
default_geometry_success: "%{count} arrêts édités"
stop_area:
no_position: "Pas de position"
@@ -19,6 +19,7 @@ fr:
general: "General"
localisation: "Localisation"
accessibility: "Accessibilité"
+ custom_fields: "Champs personnalisés"
actions:
new: "Ajouter un arrêt"
create: "Ajouter un arrêt"
@@ -208,6 +209,9 @@ fr:
comment: "Longueur maximale = 255."
coordinates: "Les coordonnées sont obligatoires."
projection_xy: "x,y dans le référentiel secondaire, le séparateur de décimales est 'point'"
+ hints:
+ stop_area:
+ registration_number: Laisser blanc pour assigner une valeur automatiquement.
referential_stop_areas:
<<: *fr_stop_areas
diff --git a/config/locales/stop_points.en.yml b/config/locales/stop_points.en.yml
index 72e138270..76e142ba1 100644
--- a/config/locales/stop_points.en.yml
+++ b/config/locales/stop_points.en.yml
@@ -43,8 +43,8 @@ en:
created_at: Created
updated_at: Updated
deleted_at: "Activated"
- for_boarding: "For boarding"
- for_alighting: "For alighting"
+ for_boarding: "Boarding"
+ for_alighting: "Alighting"
area_type: "Area type"
city_name: "City name"
zip_code: "Zip code"
diff --git a/config/locales/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..12d8d0da4 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...
+ journey_pattern: Filter by journey pattern...
+ timetable: Filter by timetable...
cancel_selection: "Cancel Selection"
fetching_error: "There has been a problem fetching the data. Please reload the page to try again."
line_routes: "Line's routes"
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/workbench_outputs.en.yml b/config/locales/workbench_outputs.en.yml
index 05cf52d0e..2af2f7023 100644
--- a/config/locales/workbench_outputs.en.yml
+++ b/config/locales/workbench_outputs.en.yml
@@ -1,7 +1,7 @@
en:
workbench_outputs:
show:
- title: "Finalisations de l'offre"
- see_current_output: "Voir l'Offre actuelle"
+ title: Current offers
+ see_current_output: See the current offer
table_headers:
- ended_at: "Offer created date"
+ ended_at: "Ended at"
diff --git a/config/locales/workbench_outputs.fr.yml b/config/locales/workbench_outputs.fr.yml
index 560888c54..b4d339434 100644
--- a/config/locales/workbench_outputs.fr.yml
+++ b/config/locales/workbench_outputs.fr.yml
@@ -1,7 +1,7 @@
fr:
workbench_outputs:
show:
- title: "Finalisations de l'offre"
+ title: Finaliser des jeux de données
see_current_output: "Voir l'Offre actuelle"
table_headers:
- ended_at: "Date et heure de création de l'offre"
+ ended_at: "Date et heure de création"
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/config/webpacker.yml b/config/webpacker.yml
index be1105bad..24a7e32dc 100644
--- a/config/webpacker.yml
+++ b/config/webpacker.yml
@@ -41,7 +41,7 @@ development:
public: localhost:3035
hmr: false
# Inline should be set to true if using HMR
- inline: true
+ inline: false
overlay: true
disable_host_check: true
use_local_ip: false
diff --git a/db/migrate/20150219175300_insert_default_organisation.rb b/db/migrate/20150219175300_insert_default_organisation.rb
index 2734893f5..ac8ecb9b6 100644
--- a/db/migrate/20150219175300_insert_default_organisation.rb
+++ b/db/migrate/20150219175300_insert_default_organisation.rb
@@ -1,5 +1,5 @@
class InsertDefaultOrganisation < ActiveRecord::Migration
- class Organisation < ActiveRecord::Base
+ class Organisation < ApplicationModel
attr_accessor :name
end
diff --git a/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb b/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb
new file mode 100644
index 000000000..e49be7e40
--- /dev/null
+++ b/db/migrate/20180313082623_add_custom_field_values_to_stop_areas.rb
@@ -0,0 +1,5 @@
+class AddCustomFieldValuesToStopAreas < ActiveRecord::Migration
+ def change
+ add_column :stop_areas, :custom_field_values, :jsonb
+ end
+end
diff --git a/db/migrate/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/migrate/20180330074336_add_metadata_to_routes.rb b/db/migrate/20180330074336_add_metadata_to_routes.rb
new file mode 100644
index 000000000..1a35dbb65
--- /dev/null
+++ b/db/migrate/20180330074336_add_metadata_to_routes.rb
@@ -0,0 +1,5 @@
+class AddMetadataToRoutes < ActiveRecord::Migration
+ def change
+ add_column :routes, :metadata, :jsonb
+ end
+end
diff --git a/db/migrate/20180330124436_add_metadata_to_other_models.rb b/db/migrate/20180330124436_add_metadata_to_other_models.rb
new file mode 100644
index 000000000..db01c77df
--- /dev/null
+++ b/db/migrate/20180330124436_add_metadata_to_other_models.rb
@@ -0,0 +1,28 @@
+class AddMetadataToOtherModels < ActiveRecord::Migration
+ def change
+ [
+ Api::V1::ApiKey,
+ Calendar,
+ Chouette::AccessLink,
+ Chouette::AccessPoint,
+ Chouette::Company,
+ Chouette::ConnectionLink,
+ Chouette::GroupOfLine,
+ Chouette::JourneyPattern,
+ Chouette::Line,
+ Chouette::Network,
+ Chouette::PtLink,
+ Chouette::PurchaseWindow,
+ Chouette::RoutingConstraintZone,
+ Chouette::StopArea,
+ Chouette::StopPoint,
+ Chouette::TimeTable,
+ Chouette::Timeband,
+ Chouette::VehicleJourney,
+ ComplianceCheckSet,
+ ComplianceControlSet,
+ ].each do |model|
+ add_column model.table_name.split(".").last, :metadata, :jsonb, default: {}
+ end
+ end
+end
diff --git a/db/migrate/20180403065419_remove_papertrail_tables.rb b/db/migrate/20180403065419_remove_papertrail_tables.rb
new file mode 100644
index 000000000..8494058e1
--- /dev/null
+++ b/db/migrate/20180403065419_remove_papertrail_tables.rb
@@ -0,0 +1,5 @@
+class RemovePapertrailTables < ActiveRecord::Migration
+ def change
+ drop_table :versions
+ end
+end
diff --git a/db/migrate/20180403100007_add_registration_number_indexes.rb b/db/migrate/20180403100007_add_registration_number_indexes.rb
new file mode 100644
index 000000000..bc2d48329
--- /dev/null
+++ b/db/migrate/20180403100007_add_registration_number_indexes.rb
@@ -0,0 +1,7 @@
+class AddRegistrationNumberIndexes < ActiveRecord::Migration
+ def change
+ add_index :stop_areas, [:stop_area_referential_id, :registration_number], name: 'index_stop_areas_on_referential_id_and_registration_number'
+ add_index :lines, [:line_referential_id, :registration_number], name: 'index_lines_on_referential_id_and_registration_number'
+ add_index :companies, [:line_referential_id, :registration_number], name: 'index_companies_on_referential_id_and_registration_number'
+ end
+end
diff --git a/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb b/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb
new file mode 100644
index 000000000..7248c29f6
--- /dev/null
+++ b/db/migrate/20180405133659_change_companies_custom_fields_values_type.rb
@@ -0,0 +1,8 @@
+class ChangeCompaniesCustomFieldsValuesType < ActiveRecord::Migration
+ def change
+ reversible do |dir|
+ dir.up { change_column :companies, :custom_field_values, 'jsonb USING CAST(custom_field_values AS jsonb)', :default => {} }
+ dir.down { change_column :companies, :custom_field_values, 'json USING CAST(custom_field_values AS json)', :default => {} }
+ end
+ end
+end
diff --git a/db/migrate/20180413082929_clean_lines_secondary_companies.rb b/db/migrate/20180413082929_clean_lines_secondary_companies.rb
new file mode 100644
index 000000000..4c5659e8f
--- /dev/null
+++ b/db/migrate/20180413082929_clean_lines_secondary_companies.rb
@@ -0,0 +1,5 @@
+class CleanLinesSecondaryCompanies < ActiveRecord::Migration
+ def up
+ Chouette::Line.where("secondary_company_ids = '{NULL}'").update_all secondary_company_ids: nil
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 58c8b0779..7e0e9c2b5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -22,7 +22,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
create_table "access_links", id: :bigserial, force: :cascade do |t|
t.integer "access_point_id", limit: 8
t.integer "stop_area_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
t.string "comment"
@@ -39,6 +39,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.string "link_orientation"
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "metadata", default: {}
end
add_index "access_links", ["objectid"], name: "access_links_objectid_key", unique: true, using: :btree
@@ -66,6 +67,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.text "import_xml"
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "metadata", default: {}
end
add_index "access_points", ["objectid"], name: "access_points_objectid_key", unique: true, using: :btree
@@ -77,13 +79,13 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "organisation_id", limit: 8
+ t.jsonb "metadata", default: {}
end
add_index "api_keys", ["organisation_id"], name: "index_api_keys_on_organisation_id", using: :btree
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
@@ -93,10 +95,10 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.integer "workgroup_id", limit: 8
t.integer "int_day_types"
t.date "excluded_dates", array: true
+ t.jsonb "metadata", default: {}
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|
@@ -124,7 +126,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree
create_table "companies", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
t.string "short_name"
@@ -141,6 +143,8 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.text "import_xml"
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "custom_field_values"
+ t.jsonb "metadata", default: {}
end
add_index "companies", ["line_referential_id"], name: "index_companies_on_line_referential_id", using: :btree
@@ -193,14 +197,15 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.string "status"
t.integer "parent_id", limit: 8
t.string "parent_type"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.string "current_step_id"
t.float "current_step_progress"
t.string "name"
t.datetime "started_at"
t.datetime "ended_at"
t.datetime "notified_parent_at"
+ t.jsonb "metadata", default: {}
end
add_index "compliance_check_sets", ["compliance_control_set_id"], name: "index_compliance_check_sets_on_compliance_control_set_id", using: :btree
@@ -239,8 +244,9 @@ ActiveRecord::Schema.define(version: 20180319043333) do
create_table "compliance_control_sets", id: :bigserial, force: :cascade do |t|
t.string "name"
t.integer "organisation_id", limit: 8
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.jsonb "metadata", default: {}
end
add_index "compliance_control_sets", ["organisation_id"], name: "index_compliance_control_sets_on_organisation_id", using: :btree
@@ -266,7 +272,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
create_table "connection_links", id: :bigserial, force: :cascade do |t|
t.integer "departure_id", limit: 8
t.integer "arrival_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
t.string "comment"
@@ -282,6 +288,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.integer "int_user_needs"
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "metadata", default: {}
end
add_index "connection_links", ["objectid"], name: "connection_links_objectid_key", unique: true, using: :btree
@@ -299,18 +306,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
@@ -359,7 +406,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
end
create_table "group_of_lines", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
t.string "comment"
@@ -368,6 +415,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.text "import_xml"
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "metadata", default: {}
end
add_index "group_of_lines", ["line_referential_id"], name: "index_group_of_lines_on_line_referential_id", using: :btree
@@ -421,9 +469,9 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.string "type"
t.integer "parent_id", limit: 8
t.string "parent_type"
+ t.datetime "notified_parent_at"
t.integer "current_step", default: 0
t.integer "total_steps", default: 0
- t.datetime "notified_parent_at"
t.string "creator"
end
@@ -446,7 +494,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
create_table "journey_patterns", id: :bigserial, force: :cascade do |t|
t.integer "route_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
t.string "comment"
@@ -460,6 +508,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.text "checksum_source"
t.string "data_source_ref"
t.json "costs"
+ t.jsonb "metadata", default: {}
end
add_index "journey_patterns", ["objectid"], name: "journey_patterns_objectid_key", unique: true, using: :btree
@@ -533,6 +582,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "seasonal"
+ t.jsonb "metadata", default: {}
end
add_index "lines", ["line_referential_id"], name: "index_lines_on_line_referential_id", using: :btree
@@ -554,7 +604,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
add_index "merges", ["workbench_id"], name: "index_merges_on_workbench_id", using: :btree
create_table "networks", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.date "version_date"
t.string "description"
@@ -568,6 +618,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.integer "line_referential_id", limit: 8
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "metadata", default: {}
end
add_index "networks", ["line_referential_id"], name: "index_networks_on_line_referential_id", using: :btree
@@ -592,13 +643,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.integer "start_of_link_id", limit: 8
t.integer "end_of_link_id", limit: 8
t.integer "route_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
t.string "comment"
t.decimal "link_distance", precision: 19, scale: 2
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "metadata", default: {}
end
add_index "pt_links", ["objectid"], name: "pt_links_objectid_key", unique: true, using: :btree
@@ -606,13 +658,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do
create_table "purchase_windows", id: :bigserial, force: :cascade do |t|
t.string "name"
t.string "color"
- t.daterange "date_ranges", array: true
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.daterange "date_ranges", array: true
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.string "objectid"
t.string "checksum"
t.text "checksum_source"
t.integer "referential_id", limit: 8
+ t.jsonb "metadata", default: {}
end
add_index "purchase_windows", ["referential_id"], name: "index_purchase_windows_on_referential_id", using: :btree
@@ -703,6 +756,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.text "checksum_source"
t.string "data_source_ref"
t.json "costs"
+ t.jsonb "metadata"
end
add_index "routes", ["objectid"], name: "routes_objectid_key", unique: true, using: :btree
@@ -711,13 +765,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.integer "route_id", limit: 8
- t.integer "stop_point_ids", limit: 8, array: true
+ t.integer "stop_point_ids", limit: 8, array: true
t.string "checksum"
t.text "checksum_source"
t.string "data_source_ref"
+ t.jsonb "metadata", default: {}
end
create_table "routing_constraints_lines", id: false, force: :cascade do |t|
@@ -766,11 +821,12 @@ 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|
t.integer "parent_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
t.string "comment"
@@ -802,6 +858,8 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.string "kind"
t.jsonb "localized_names"
t.datetime "confirmed_at"
+ t.jsonb "custom_field_values"
+ t.jsonb "metadata", default: {}
end
add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree
@@ -817,13 +875,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do
create_table "stop_points", id: :bigserial, force: :cascade do |t|
t.integer "route_id", limit: 8
t.integer "stop_area_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.integer "position"
t.string "for_boarding"
t.string "for_alighting"
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "metadata", default: {}
end
add_index "stop_points", ["objectid"], name: "stop_points_objectid_key", unique: true, using: :btree
@@ -871,7 +930,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
add_index "time_table_periods", ["time_table_id"], name: "index_time_table_periods_on_time_table_id", using: :btree
create_table "time_tables", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8, default: 1
t.string "version"
t.string "comment"
@@ -886,6 +945,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.string "checksum"
t.text "checksum_source"
t.string "data_source_ref"
+ t.jsonb "metadata", default: {}
end
add_index "time_tables", ["calendar_id"], name: "index_time_tables_on_calendar_id", using: :btree
@@ -901,13 +961,14 @@ ActiveRecord::Schema.define(version: 20180319043333) do
add_index "time_tables_vehicle_journeys", ["vehicle_journey_id"], name: "index_time_tables_vehicle_journeys_on_vehicle_journey_id", using: :btree
create_table "timebands", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
- t.time "start_time", null: false
- t.time "end_time", null: false
+ t.time "start_time", null: false
+ t.time "end_time", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.jsonb "metadata", default: {}
end
create_table "users", id: :bigserial, force: :cascade do |t|
@@ -990,6 +1051,7 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.text "checksum_source"
t.string "data_source_ref"
t.jsonb "custom_field_values", default: {}
+ t.jsonb "metadata", default: {}
end
add_index "vehicle_journeys", ["objectid"], name: "vehicle_journeys_objectid_key", unique: true, using: :btree
@@ -1031,8 +1093,10 @@ ActiveRecord::Schema.define(version: 20180319043333) do
t.string "name"
t.integer "line_referential_id", limit: 8
t.integer "stop_area_referential_id", limit: 8
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "import_types", default: [], array: true
+ t.string "export_types", default: [], array: true
end
add_foreign_key "access_links", "access_points", name: "aclk_acpt_fkey"
diff --git a/db/seeds/dev/custom_fields.seeds.rb b/db/seeds/dev/custom_fields.seeds.rb
new file mode 100644
index 000000000..eb3afc394
--- /dev/null
+++ b/db/seeds/dev/custom_fields.seeds.rb
@@ -0,0 +1,52 @@
+# coding: utf-8
+
+require_relative '../seed_helpers'
+
+Workgroup.find_each do |workgroup|
+ puts workgroup.inspect
+
+ workgroup.custom_fields.seed_by(code: "capacity") do |field|
+ field.resource_type = "VehicleJourney"
+ field.name = "Bus Capacity"
+ field.field_type = "list"
+ field.options = { list_values: { "0": "", "1": "48 places", "2": "54 places" }}
+ end
+
+ workgroup.custom_fields.seed_by(code: "company_commercial_name") do |field|
+ field.resource_type = "Company"
+ field.name = "Nom commercial"
+ field.field_type = "list"
+ field.options = { list_values: { "0": "", "1": "OuiBus", "2": "Alsa" }}
+ end
+
+ workgroup.custom_fields.seed_by(code: "company_contact_name") do |field|
+ field.resource_type = "Company"
+ field.name = "Nom du référent"
+ field.field_type = "string"
+ end
+
+ workgroup.custom_fields.seed_by(code: "stop_area_test_list") do |field|
+ field.resource_type = "StopArea"
+ field.name = "Test de Liste"
+ field.field_type = "list"
+ field.options = { list_values: { "0": "", "1": "Valeur 1", "2": "Valeur 2" }}
+ end
+
+ workgroup.custom_fields.seed_by(code: "stop_area_test_string") do |field|
+ field.resource_type = "StopArea"
+ field.name = "Test de Texte"
+ field.field_type = "string"
+ end
+
+ workgroup.custom_fields.seed_by(code: "stop_area_test_integer") do |field|
+ field.resource_type = "StopArea"
+ field.name = "Test de Nomber"
+ field.field_type = "integer"
+ end
+
+ workgroup.custom_fields.seed_by(code: "stop_area_test_attachment") do |field|
+ field.resource_type = "StopArea"
+ field.name = "Test de Piece Jointe"
+ field.field_type = "attachment"
+ end
+end
diff --git a/db/seeds/development/custom_fields.seeds.rb b/db/seeds/development/custom_fields.seeds.rb
new file mode 100644
index 000000000..eb3afc394
--- /dev/null
+++ b/db/seeds/development/custom_fields.seeds.rb
@@ -0,0 +1,52 @@
+# coding: utf-8
+
+require_relative '../seed_helpers'
+
+Workgroup.find_each do |workgroup|
+ puts workgroup.inspect
+
+ workgroup.custom_fields.seed_by(code: "capacity") do |field|
+ field.resource_type = "VehicleJourney"
+ field.name = "Bus Capacity"
+ field.field_type = "list"
+ field.options = { list_values: { "0": "", "1": "48 places", "2": "54 places" }}
+ end
+
+ workgroup.custom_fields.seed_by(code: "company_commercial_name") do |field|
+ field.resource_type = "Company"
+ field.name = "Nom commercial"
+ field.field_type = "list"
+ field.options = { list_values: { "0": "", "1": "OuiBus", "2": "Alsa" }}
+ end
+
+ workgroup.custom_fields.seed_by(code: "company_contact_name") do |field|
+ field.resource_type = "Company"
+ field.name = "Nom du référent"
+ field.field_type = "string"
+ end
+
+ workgroup.custom_fields.seed_by(code: "stop_area_test_list") do |field|
+ field.resource_type = "StopArea"
+ field.name = "Test de Liste"
+ field.field_type = "list"
+ field.options = { list_values: { "0": "", "1": "Valeur 1", "2": "Valeur 2" }}
+ end
+
+ workgroup.custom_fields.seed_by(code: "stop_area_test_string") do |field|
+ field.resource_type = "StopArea"
+ field.name = "Test de Texte"
+ field.field_type = "string"
+ end
+
+ workgroup.custom_fields.seed_by(code: "stop_area_test_integer") do |field|
+ field.resource_type = "StopArea"
+ field.name = "Test de Nomber"
+ field.field_type = "integer"
+ end
+
+ workgroup.custom_fields.seed_by(code: "stop_area_test_attachment") do |field|
+ field.resource_type = "StopArea"
+ field.name = "Test de Piece Jointe"
+ field.field_type = "attachment"
+ end
+end
diff --git a/db/seeds/seed_helpers.rb b/db/seeds/seed_helpers.rb
new file mode 100644
index 000000000..708362a6c
--- /dev/null
+++ b/db/seeds/seed_helpers.rb
@@ -0,0 +1,12 @@
+class ActiveRecord::Base
+ def self.seed_by(key_attribute, &block)
+ model = find_or_initialize_by key_attribute
+ print "Seed #{name} #{key_attribute.inspect} "
+ yield model
+
+ puts "[#{(model.changed? ? 'updated' : 'no change')}]"
+ model.save!
+
+ model
+ end
+end
diff --git a/db/seeds/stif.seeds.rb b/db/seeds/stif.seeds.rb
index aa87b6f6c..98192385f 100644
--- a/db/seeds/stif.seeds.rb
+++ b/db/seeds/stif.seeds.rb
@@ -1,46 +1,25 @@
# coding: utf-8
-# This file should contain all the record creation needed to seed the database with its default values.
-# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
-stop_area_referential = StopAreaReferential.find_or_create_by!(name: "Reflex", objectid_format: "stif_netex")
-line_referential = LineReferential.find_or_create_by!(name: "CodifLigne", objectid_format: "stif_netex")
+require_relative 'seed_helpers'
-workgroup = Workgroup.find_or_create_by!(name: "Gestion de l'offre théorique IDFm") do |w|
- w.line_referential = line_referential
- w.stop_area_referential = stop_area_referential
- w.export_types = ["Export::Netex"]
+stif = Organisation.seed_by(code: "STIF") do |o|
+ o.name = 'STIF'
end
-Workbench.update_all workgroup_id: workgroup
-
-# Organisations
-stif = Organisation.find_or_create_by!(code: "STIF") do |org|
- org.name = 'STIF'
+stop_area_referential = StopAreaReferential.seed_by(name: "Reflex") do |r|
+ r.objectid_format = "stif_netex"
+ r.add_member stif, owner: true
end
-# operator = Organisation.find_or_create_by!(code: 'transporteur-a') do |organisation|
-# organisation.name = "Transporteur A"
-# end
-
-# Member
-line_referential.add_member stif, owner: true
-# line_referential.add_member operator
-stop_area_referential.add_member stif, owner: true
-# stop_area_referential.add_member operator
+line_referential = LineReferential.seed_by(name: "CodifLigne") do |r|
+ r.objectid_format = "stif_codifligne"
+ r.add_member stif, owner: true
+end
-# Users
-# stif.users.find_or_create_by!(username: "admin") do |user|
-# user.email = 'stif-boiv@af83.com'
-# user.password = "secret"
-# user.name = "STIF Administrateur"
-# end
-#
-# operator.users.find_or_create_by!(username: "transporteur") do |user|
-# user.email = 'stif-boiv+transporteur@af83.com'
-# user.password = "secret"
-# user.name = "Martin Lejeune"
-# end
+workgroup = Workgroup.seed_by(name: "Gestion de l'offre théorique IDFm") do |w|
+ w.line_referential = line_referential
+ w.stop_area_referential = stop_area_referential
+ w.export_types = ["Export::Netex"]
+end
-# Include all Lines in organisation functional_scope
-stif.update sso_attributes: { functional_scope: line_referential.lines.pluck(:objectid) }
-#operator.update sso_attributes: { functional_scope: line_referential.lines.limit(3).pluck(:objectid) }
+Workbench.update_all workgroup_id: workgroup
diff --git a/lib/af83/decorator/link.rb b/lib/af83/decorator/link.rb
index ee09f80dc..9bf6c1ed9 100644
--- a/lib/af83/decorator/link.rb
+++ b/lib/af83/decorator/link.rb
@@ -85,6 +85,10 @@ class AF83::Decorator::Link
in_group_for_action? :secondary
end
+ def disabled?
+ !!disabled
+ end
+
def enabled?
enabled = false
if @options[:_if].nil?
@@ -131,9 +135,9 @@ class AF83::Decorator::Link
out[:class] = extra_class
out.delete(:link_class)
out.delete(:link_method)
- out[:class] += " disabled" if disabled
+ out[:class] += " disabled" if disabled?
out[:class].strip!
- out[:disabled] = !!disabled
+ out[:disabled] = disabled?
out
end
@@ -150,7 +154,7 @@ class AF83::Decorator::Link
html_options
).to_html
else
- context.h.link_to content, href, html_options
+ context.h.link_to content, (disabled? ? "#" : href), html_options
end
end
end
diff --git a/lib/gtfs/time.rb b/lib/gtfs/time.rb
new file mode 100644
index 000000000..49546532a
--- /dev/null
+++ b/lib/gtfs/time.rb
@@ -0,0 +1,28 @@
+module GTFS
+ class Time
+ attr_reader :hours, :minutes, :seconds
+ def initialize(hours, minutes, seconds)
+ @hours, @minutes, @seconds = hours, minutes, seconds
+ end
+
+ def real_hours
+ hours.modulo(24)
+ end
+
+ def time
+ @time ||= ::Time.new(2000, 1, 1, real_hours, minutes, seconds, "+00:00")
+ end
+
+ def day_offset
+ hours / 24
+ end
+
+ FORMAT = /(\d{1,2}):(\d{2}):(\d{2})/
+
+ def self.parse(definition)
+ if definition.to_s =~ FORMAT
+ new *[$1, $2, $3].map(&:to_i)
+ end
+ end
+ end
+end
diff --git a/lib/route_way_cost_unit_converter.rb b/lib/route_way_cost_unit_converter.rb
index 45edbf538..52515e52c 100644
--- a/lib/route_way_cost_unit_converter.rb
+++ b/lib/route_way_cost_unit_converter.rb
@@ -8,18 +8,24 @@ class RouteWayCostUnitConverter
end
end
- private
-
# Round to 2 decimal places to appease JavaScript validation
def self.meters_to_kilometers(num)
return 0 unless num
- (num / 1000.0).to_i
+ snap_to_one(num / 1000.0).to_i
end
def self.seconds_to_minutes(num)
return 0 unless num
- num / 60
+ snap_to_one(num / 60.0).to_i
+ end
+
+ private
+
+ def self.snap_to_one(decimal)
+ return 1 if decimal > 0 && decimal <= 1
+
+ decimal
end
end
diff --git a/lib/stif/netex_file.rb b/lib/stif/netex_file.rb
index db0801bbe..1e78ca04a 100644
--- a/lib/stif/netex_file.rb
+++ b/lib/stif/netex_file.rb
@@ -47,6 +47,23 @@ module STIF
base_name = File.basename(file_name)
STIF::NetexFile::LINE_FILE_FORMAT.match(base_name).try(:[], 'line_object_id')
end
+
+ def parse_calendars calendars
+ # <netex:ValidBetween>
+ # <netex:FromDate>2017-03-01</netex:FromDate>
+ # <netex:ToDate>2017-03-31</netex:ToDate>
+ # </netex:ValidBetween>
+ xml = Nokogiri::XML(calendars)
+ from_date = nil
+ to_date = nil
+ xml.xpath("//netex:ValidBetween", "netex" => NetexFile::XML_NAME_SPACE).each do |valid_between|
+ from_date = valid_between.xpath("netex:FromDate").try :text
+ to_date = valid_between.xpath("netex:ToDate").try :text
+ end
+ from_date = from_date && Date.parse(from_date)
+ to_date = to_date && Date.parse(to_date)
+ Range.new from_date, to_date
+ end
end
attr_accessor :name
@@ -56,16 +73,7 @@ module STIF
end
def parse_calendars(calendars)
- # <netex:ValidBetween>
- # <netex:FromDate>2017-03-01</netex:FromDate>
- # <netex:ToDate>2017-03-31</netex:ToDate>
- # </netex:ValidBetween>
- xml = Nokogiri::XML(calendars)
- xml.xpath("//netex:ValidBetween", "netex" => NetexFile::XML_NAME_SPACE).each do |valid_between|
- from_date = valid_between.xpath("netex:FromDate").try :text
- to_date = valid_between.xpath("netex:ToDate").try :text
- periods << Range.new(Date.parse(from_date), Date.parse(to_date))
- end
+ periods << self.class.parse_calendars(calendars)
end
def add_offer_file(line_object_id)
diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake
index 5b2c8ae3c..cb9ed77e7 100644
--- a/lib/tasks/ci.rake
+++ b/lib/tasks/ci.rake
@@ -10,6 +10,7 @@ namespace :ci do
desc "Prepare CI build"
task :setup do
+ cp "config/database.yml", "config/database.yml.orig"
cp "config/database/ci.yml", "config/database.yml"
puts "Use #{database_name} database"
sh "RAILS_ENV=test rake db:drop db:create db:migrate"
@@ -28,6 +29,7 @@ namespace :ci do
end
def deploy_env
+ return ENV["DEPLOY_ENV"] if ENV["DEPLOY_ENV"]
if git_branch == "master"
"dev"
elsif git_branch.in?(deploy_envs)
@@ -54,12 +56,12 @@ namespace :ci do
desc "Deploy after CI"
task :deploy do
- return if ENV["CHOUETTE_DEPLOY_DISABLED"]
-
- if deploy_env
- sh "cap #{deploy_env} deploy:migrations"
- else
- puts "No deploy for branch #{git_branch}"
+ unless ENV["CHOUETTE_DEPLOY_DISABLED"]
+ if deploy_env
+ sh "cap #{deploy_env} deploy:migrations deploy:seed"
+ else
+ puts "No deploy for branch #{git_branch}"
+ end
end
end
@@ -75,6 +77,9 @@ namespace :ci do
task :clean do
puts "Drop #{database_name} database"
sh "RAILS_ENV=test rake db:drop"
+
+ # Restore projet config/database.yml
+ # cp "config/database.yml.orig", "config/database.yml" if File.exists?("config/database.yml.orig")
end
end
diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb
index a1a2bda43..fcebcc7ac 100644
--- a/lib/tom_tom.rb
+++ b/lib/tom_tom.rb
@@ -19,4 +19,8 @@ module TomTom
def self.batch(way_costs)
TomTom::Batch.new(@connection).batch(way_costs)
end
+
+ def self.matrix(way_costs)
+ TomTom::Matrix.new(@connection).matrix(way_costs)
+ end
end
diff --git a/lib/tom_tom/matrix.rb b/lib/tom_tom/matrix.rb
new file mode 100644
index 000000000..b0c8cc335
--- /dev/null
+++ b/lib/tom_tom/matrix.rb
@@ -0,0 +1,114 @@
+module TomTom
+ class Matrix
+ def initialize(connection)
+ @connection = connection
+ end
+
+ def matrix(way_costs)
+ points_with_ids = points_from_way_costs(way_costs)
+ points = points_as_params(points_with_ids)
+
+ Rails.logger.info "Invoke TomTom for #{points.size} points"
+
+ response = @connection.post do |req|
+ req.url '/routing/1/matrix/json'
+ req.headers['Content-Type'] = 'application/json'
+
+ req.params[:routeType] = 'shortest'
+ req.params[:travelMode] = 'bus'
+
+ req.body = build_request_body(points)
+ end
+
+ extract_costs_to_way_costs!(
+ way_costs,
+ points_with_ids,
+ JSON.parse(response.body)
+ )
+ end
+
+ def points_from_way_costs(way_costs)
+ points = []
+
+ way_costs.each do |way_cost|
+ departure_id, arrival_id = way_cost.id.split('-')
+
+ departure = TomTom::Matrix::Point.new(
+ way_cost.departure,
+ departure_id
+ )
+ arrival = TomTom::Matrix::Point.new(
+ way_cost.arrival,
+ arrival_id
+ )
+
+ # Don't add duplicate coordinates. This assumes that
+ # `way_costs` consists of an ordered route of points where
+ # each departure coordinate is the same as the preceding
+ # arrival coordinate.
+ if points.empty? ||
+ points.last.coordinates != departure.coordinates
+ points << departure
+ end
+
+ points << arrival
+ end
+
+ points
+ end
+
+ def points_as_params(points)
+ points.map do |point|
+ {
+ point: {
+ latitude: point.coordinates.lat,
+ longitude: point.coordinates.lng
+ }
+ }
+ end
+ end
+
+ def build_request_body(points)
+ # Serialize `BigDecimal` values as floats to please the TomTom API
+ RequestJSONSerializer.dump({
+ origins: points,
+ destinations: points
+ })
+ end
+
+ def extract_costs_to_way_costs!(way_costs, points, matrix_json)
+ way_costs = []
+
+ # `row` and `column` order is the same as `points`
+ matrix_json['matrix'].each_with_index do |row, row_i|
+ row.each_with_index do |column, column_i|
+ next if column['statusCode'] != 200
+
+ distance = column['response']['routeSummary']['lengthInMeters']
+
+ # Ignore costs between a point and itself (e.g. from A to A)
+ next if distance == 0
+
+ departure = points[row_i]
+ arrival = points[column_i]
+
+ way_costs << WayCost.new(
+ departure: Geokit::LatLng.new(
+ departure.coordinates.lat,
+ departure.coordinates.lng
+ ),
+ arrival: Geokit::LatLng.new(
+ arrival.coordinates.lat,
+ arrival.coordinates.lng
+ ),
+ distance: distance,
+ time: column['response']['routeSummary']['travelTimeInSeconds'],
+ id: "#{departure.id}-#{arrival.id}"
+ )
+ end
+ end
+
+ way_costs
+ end
+ end
+end
diff --git a/lib/tom_tom/matrix/point.rb b/lib/tom_tom/matrix/point.rb
new file mode 100644
index 000000000..435b4d4b0
--- /dev/null
+++ b/lib/tom_tom/matrix/point.rb
@@ -0,0 +1,18 @@
+module TomTom
+ class Matrix
+ class Point
+ attr_reader :coordinates, :id
+
+ def initialize(coordinates, id)
+ @coordinates = coordinates
+ @id = id
+ end
+
+ def ==(other)
+ other.is_a?(self.class) &&
+ @coordinates == other.coordinates &&
+ @id == other.id
+ end
+ end
+ end
+end
diff --git a/lib/tom_tom/matrix/request_json_serializer.rb b/lib/tom_tom/matrix/request_json_serializer.rb
new file mode 100644
index 000000000..f4d12e482
--- /dev/null
+++ b/lib/tom_tom/matrix/request_json_serializer.rb
@@ -0,0 +1,25 @@
+module TomTom
+ class Matrix
+ class RequestJSONSerializer
+ def self.dump(hash)
+ hash[:origins].map! do |point|
+ point_to_f(point)
+ end
+ hash[:destinations].map! do |point|
+ point_to_f(point)
+ end
+
+ JSON.dump(hash)
+ end
+
+ private
+
+ def self.point_to_f(point)
+ point[:point][:latitude] = point[:point][:latitude].to_f
+ point[:point][:longitude] = point[:point][:longitude].to_f
+
+ point
+ end
+ end
+ end
+end
diff --git a/package.json b/package.json
index 262d80b97..ef956105c 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,10 @@
"clean-webpack-plugin": "^0.1.18",
"coffee-loader": "^0.9.0",
"coffeescript": "1.12.7",
+ "es6-symbol": "^3.1.1",
"jquery": "3.2.1",
"lodash": "4.17.4",
+ "polyfill-array-includes": "^1.0.0",
"promise-polyfill": "7.0.0",
"prop-types": "^15.6.0",
"react": "16.2.0",
@@ -24,7 +26,7 @@
"redux-promise": "0.5.3",
"redux-thunk": "2.2.0",
"uglify-js": "3.3.2",
- "whatwg-fetch": "2.0.3"
+ "whatwg-fetch": "^2.0.4"
},
"license": "MIT",
"engines": {
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/lines_controller_spec.rb b/spec/controllers/lines_controller_spec.rb
index 96f49bb36..78020484b 100644
--- a/spec/controllers/lines_controller_spec.rb
+++ b/spec/controllers/lines_controller_spec.rb
@@ -1,9 +1,36 @@
RSpec.describe LinesController, :type => :controller do
login_user
- let(:line_referential) { create :line_referential, member: @user.organisation }
+ let(:line_referential) { create :line_referential, member: @user.organisation, objectid_format: :netex }
let(:line) { create :line, line_referential: line_referential }
+ describe 'POST create' do
+ let(:line_attrs){{
+ name: "test",
+ transport_mode: "bus"
+ }}
+ let(:request){ post :create, line_referential_id: line_referential.id, line: line_attrs }
+
+ with_permission "lines.create" do
+ it "should create a new line" do
+ expect{request}.to change{ line_referential.lines.count }.by 1
+ end
+
+ context "with an empty value in secondary_company_ids" do
+ let(:line_attrs){{
+ name: "test",
+ transport_mode: "bus",
+ secondary_company_ids: [""]
+ }}
+
+ it "should cleanup secondary_company_ids" do
+ expect{request}.to change{ line_referential.lines.count }.by 1
+ expect(line_referential.lines.last.secondary_company_ids).to eq []
+ end
+ end
+ end
+ end
+
describe 'PUT deactivate' do
let(:request){ put :deactivate, id: line.id, line_referential_id: line_referential.id }
diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb
index e4dc6bc23..59020914d 100644
--- a/spec/controllers/routes_controller_spec.rb
+++ b/spec/controllers/routes_controller_spec.rb
@@ -42,11 +42,14 @@ RSpec.describe RoutesController, type: :controller do
before(:each) do
post :create, line_id: route.line_id,
referential_id: referential.id,
- route: { name: "changed"}
+ route: { name: "changed", published_name: "published_name"}
end
it_behaves_like "line and referential linked"
it_behaves_like "redirected to referential_line_path(referential,line)"
+ it "sets metadata" do
+ expect(Chouette::Route.last.metadata.creator_username).to eq @user.username
+ end
end
describe "PUT /update" do
@@ -58,6 +61,9 @@ RSpec.describe RoutesController, type: :controller do
it_behaves_like "route, line and referential linked"
it_behaves_like "redirected to referential_line_path(referential,line)"
+ it "sets metadata" do
+ expect(Chouette::Route.last.metadata.modifier_username).to eq @user.username
+ end
end
describe "GET /show" do
@@ -83,6 +89,48 @@ 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
+ before do
+ @positions = Hash[*route.stop_points.map{|sp| [sp.id, sp.position]}.flatten]
+ end
+ 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
+ route.reload.stop_points.each do |sp|
+ expect(sp.position).to eq @positions[sp.id]
+ end
+ 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/google-sample-feed.zip b/spec/fixtures/google-sample-feed.zip
new file mode 100644
index 000000000..79819e21a
--- /dev/null
+++ b/spec/fixtures/google-sample-feed.zip
Binary files differ
diff --git a/spec/fixtures/multiple_with_wrong_calendar.zip b/spec/fixtures/multiple_with_wrong_calendar.zip
new file mode 100644
index 000000000..85c0ec61d
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar.zip
Binary files differ
diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml
new file mode 100644
index 000000000..bfbd0aea1
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<netex:PublicationDelivery xmlns:netex="http://www.netex.org.uk/netex"
+ xmlns:siri="http://www.siri.org.uk/siri" xmlns:core="http://www.govtalk.gov.uk/core"
+ xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:ifopt="http://www.ifopt.org.uk/ifopt"
+ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ version="1.0">
+ <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp>
+ <netex:ParticipantRef>CITYWAY</netex:ParticipantRef>
+ <netex:dataObjects>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_CALENDRIER-1_20170214090012:LOC"
+ version="any">
+ <netex:TypeOfFrameRef ref="NETEX_CALENDRIER"/>
+ <netex:ValidBetween>
+ <netex:FromDate>2017-03-01</netex:FromDate>
+ <netex:ToDate>2017-03-31</netex:ToDate>
+ </netex:ValidBetween>
+ <netex:members>
+ <netex:dayTypes>
+ <netex:DayType id="CITYWAY:DayType:1:LOC" version="any" >
+ <netex:Name>Semaine</netex:Name>
+ <netex:properties>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Monday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Tuesday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Wednesday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Thursday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Friday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ </netex:properties>
+ </netex:DayType>
+ <netex:DayType id="CITYWAY:DayType:2:LOC" version="any" >
+ <netex:Name>Fin de semaine</netex:Name>
+ <netex:properties>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Saturday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Sunday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ </netex:properties>
+ </netex:DayType>
+ <netex:DayType id="CITYWAY:DayType:3:LOC" version="any" >
+ <netex:Name>Service spécial</netex:Name>
+ </netex:DayType>
+ <netex:DayType id="CITYWAY:DayType:4:LOC" version="any" >
+ <netex:Name>Restriction</netex:Name>
+ </netex:DayType>
+ </netex:dayTypes>
+ <netex:dayTypeAssignments>
+ <netex:DayTypeAssignment version="any" >
+ <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/>
+ <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC" version="any"/>
+ </netex:DayTypeAssignment>
+ <netex:DayTypeAssignment version="any" >
+ <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/>
+ <netex:DayTypeRef ref="CITYWAY:DayType:2:LOC" version="any"/>
+ </netex:DayTypeAssignment>
+ <netex:DayTypeAssignment version="any" >
+ <netex:Date>2017-03-15</netex:Date>
+ <netex:DayTypeRef ref="CITYWAY:DayType:3:LOC" version="any"/>
+ <netex:isAvailable>true</netex:isAvailable>
+ </netex:DayTypeAssignment>
+ <netex:DayTypeAssignment version="any" >
+ <netex:Date>2017-03-15</netex:Date>
+ <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC" version="any"/>
+ <netex:isAvailable>false</netex:isAvailable>
+ </netex:DayTypeAssignment>
+ </netex:dayTypeAssignments>
+ <netex:operatingPeriods>
+ <netex:OperatingPeriod id="CITYWAY:OperatingPeriod:1:LOC" version="any" >
+ <netex:FromDate>2017-01-01</netex:FromDate>
+ <netex:ToDate>2017-12-31</netex:ToDate>
+ </netex:OperatingPeriod>
+ </netex:operatingPeriods>
+ </netex:members>
+ </netex:GeneralFrame>
+ </netex:dataObjects>
+</netex:PublicationDelivery>
diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml
new file mode 100644
index 000000000..266c8a598
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<netex:PublicationDelivery xmlns:netex="http://www.netex.org.uk/netex"
+ xmlns:siri="http://www.siri.org.uk/siri" xmlns:core="http://www.govtalk.gov.uk/core"
+ xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:ifopt="http://www.ifopt.org.uk/ifopt"
+ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ version="1.0">
+ <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp>
+ <netex:ParticipantRef>CITYWAY</netex:ParticipantRef>
+ <netex:dataObjects>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_COMMUN-1_20170214090012:LOC" version="any">
+ <netex:TypeOfFrameRef ref="NETEX_COMMUN"/>
+ <netex:members>
+ <netex:notices>
+ <netex:Notice id="CITYWAY:Notice:1:LOC" version="any">
+ <netex:Text>Notice 1</netex:Text>
+ <netex:PublicCode>1</netex:PublicCode>
+ <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef>
+ </netex:Notice>
+ <netex:Notice id="CITYWAY:Notice:2:LOC" version="any">
+ <netex:Text>Notice 2</netex:Text>
+ <netex:PublicCode>2</netex:PublicCode>
+ <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef>
+ </netex:Notice>
+ <netex:Notice id="CITYWAY:Notice:3:LOC" version="any">
+ <netex:Text>Notice 3</netex:Text>
+ <netex:PublicCode>3</netex:PublicCode>
+ <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef>
+ </netex:Notice>
+ </netex:notices>
+ </netex:members>
+ </netex:GeneralFrame>
+ </netex:dataObjects>
+</netex:PublicationDelivery>
diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml
new file mode 100644
index 000000000..832793036
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd"
+ xmlns:netex="http://www.netex.org.uk/netex" xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2"
+ xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0">
+ <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp>
+ <netex:ParticipantRef>CITYWAY</netex:ParticipantRef>
+ <netex:dataObjects>
+ <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any">
+ <netex:Name>Ligne 1</netex:Name>
+ <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/>
+ <netex:frames>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC"
+ version="any">
+ <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/>
+ <netex:members>
+ <netex:routes>
+ <netex:Route id="CITYWAY:Route:1:LOC" version="any">
+ <netex:Name>route 1</netex:Name>
+ <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef>
+ <netex:DirectionType>outbound</netex:DirectionType>
+ <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/>
+ <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/>
+ </netex:Route>
+ <netex:Route id="CITYWAY:Route:2:LOC" version="any">
+ <netex:Name>route 2</netex:Name>
+ <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef>
+ <netex:DirectionType>inbound</netex:DirectionType>
+ <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/>
+ <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/>
+ </netex:Route>
+ </netex:routes>
+ <netex:directions>
+ <netex:Direction id="CITYWAY:Direction:1:LOC" version="any">
+ <netex:Name>Par ici</netex:Name>
+ </netex:Direction>
+ <netex:Direction id="CITYWAY:Direction:2:LOC" version="any">
+ <netex:Name>Par là</netex:Name>
+ </netex:Direction>
+ </netex:directions>
+ <netex:serviceJourneyPatterns>
+ <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC"
+ version="any">
+ <netex:Name>Par ici</netex:Name>
+ <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/>
+ <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC"
+ version="any"/>
+ <netex:pointsInSequence>
+ <netex:StopPointInJourneyPattern
+ id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1"
+ version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ <netex:StopPointInJourneyPattern
+ id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2"
+ version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ </netex:pointsInSequence>
+ <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType>
+ </netex:ServiceJourneyPattern>
+ <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC"
+ version="any">
+ <netex:Name>Par là</netex:Name>
+ <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/>
+ <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC"
+ version="any"/>
+ <netex:pointsInSequence>
+ <netex:StopPointInJourneyPattern
+ id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1"
+ version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ <netex:StopPointInJourneyPattern
+ id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2"
+ version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ </netex:pointsInSequence>
+ <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType>
+ </netex:ServiceJourneyPattern>
+ </netex:serviceJourneyPatterns>
+ <netex:destinationDisplays>
+ <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC"
+ version="any">
+ <netex:FrontText>Mission 1</netex:FrontText>
+ <netex:PublicCode>1234</netex:PublicCode>
+ </netex:DestinationDisplay>
+ <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC"
+ version="any">
+ <netex:FrontText>Mission 2</netex:FrontText>
+ <netex:PublicCode>2345</netex:PublicCode>
+ </netex:DestinationDisplay>
+ </netex:destinationDisplays>
+ <netex:scheduledStopPoints>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC"
+ version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC"
+ version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC"
+ version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC"
+ version="any"/>
+ </netex:scheduledStopPoints>
+ <netex:passengerStopAssignments>
+ <netex:PassengerStopAssignment
+ id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment
+ id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50009052:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment
+ id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50009053:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment
+ id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ </netex:passengerStopAssignments>
+ <netex:routingConstraintZones>
+ <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC"
+ version="any">
+ <netex:Name>ITL 1</netex:Name>
+ <netex:members>
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ </netex:members>
+ <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse>
+ </netex:RoutingConstraintZone>
+ </netex:routingConstraintZones>
+ </netex:members>
+ </netex:GeneralFrame>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC"
+ version="any">
+ <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/>
+ <netex:members>
+ <netex:serviceJourneys>
+ <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any">
+ <netex:Name>Course 1 par ici</netex:Name>
+ <netex:noticeAssignments>
+ <netex:NoticeAssignment>
+ <netex:NoticeRef ref="CITYWAY:Notice:1:LOC">
+ version="any"</netex:NoticeRef>
+ </netex:NoticeAssignment>
+ </netex:noticeAssignments>
+ <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC">
+ version="any"</netex:DayTypeRef>
+ <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC"
+ version="any"/>
+ <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:011">
+ version="any"</netex:OperatorRef>
+ <netex:trainNumbers>
+ <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef>
+ </netex:trainNumbers>
+ <netex:passingTimes>
+ <netex:TimetabledPassingTime version="any">
+ <netex:ArrivalTime>01:01:00.000</netex:ArrivalTime>
+ <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset>
+ <netex:DepartureTime>01:01:00.000</netex:DepartureTime>
+ <netex:DepartureDayOffset>0</netex:DepartureDayOffset>
+ </netex:TimetabledPassingTime>
+ <netex:TimetabledPassingTime version="any">
+ <netex:ArrivalTime>01:05:00.000</netex:ArrivalTime>
+ <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset>
+ <netex:DepartureTime>01:05:00.000</netex:DepartureTime>
+ <netex:DepartureDayOffset>0</netex:DepartureDayOffset>
+ </netex:TimetabledPassingTime>
+ </netex:passingTimes>
+ </netex:ServiceJourney>
+ </netex:serviceJourneys>
+ </netex:members>
+ </netex:GeneralFrame>
+ </netex:frames>
+ </netex:CompositeFrame>
+ </netex:dataObjects>
+</netex:PublicationDelivery>
diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml
new file mode 100644
index 000000000..9dff0d850
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd"
+ xmlns:netex="http://www.netex.org.uk/netex" xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2"
+ xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0">
+ <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp>
+ <netex:ParticipantRef>CITYWAY</netex:ParticipantRef>
+ <netex:dataObjects>
+ <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any">
+ <netex:Name>Ligne 1</netex:Name>
+ <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/>
+ <netex:frames>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC"
+ version="any">
+ <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/>
+ <netex:members>
+ <netex:routes>
+ <netex:Route id="CITYWAY:Route:1:LOC" version="any">
+ <netex:Name>route 1</netex:Name>
+ <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef>
+ <netex:DirectionType>outbound</netex:DirectionType>
+ <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/>
+ <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/>
+ </netex:Route>
+ <netex:Route id="CITYWAY:Route:2:LOC" version="any">
+ <netex:Name>route 2</netex:Name>
+ <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef>
+ <netex:DirectionType>inbound</netex:DirectionType>
+ <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/>
+ <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/>
+ </netex:Route>
+ </netex:routes>
+ <netex:directions>
+ <netex:Direction id="CITYWAY:Direction:1:LOC" version="any">
+ <netex:Name>Par ici aussi</netex:Name>
+ </netex:Direction>
+ <netex:Direction id="CITYWAY:Direction:2:LOC" version="any">
+ <netex:Name>Par là aussi</netex:Name>
+ </netex:Direction>
+ </netex:directions>
+ <netex:serviceJourneyPatterns>
+ <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC"
+ version="any">
+ <netex:Name>Par ici itou</netex:Name>
+ <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/>
+ <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC"
+ version="any"/>
+ <netex:pointsInSequence>
+ <netex:StopPointInJourneyPattern
+ id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1"
+ version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ <netex:StopPointInJourneyPattern
+ id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2"
+ version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ </netex:pointsInSequence>
+ <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType>
+ </netex:ServiceJourneyPattern>
+ <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC"
+ version="any">
+ <netex:Name>Par là itou</netex:Name>
+ <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/>
+ <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC"
+ version="any"/>
+ <netex:pointsInSequence>
+ <netex:StopPointInJourneyPattern
+ id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1"
+ version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ <netex:StopPointInJourneyPattern
+ id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2"
+ version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ </netex:pointsInSequence>
+ <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType>
+ </netex:ServiceJourneyPattern>
+ </netex:serviceJourneyPatterns>
+ <netex:destinationDisplays>
+ <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC"
+ version="any">
+ <netex:FrontText>Mission 1 bis</netex:FrontText>
+ <netex:PublicCode>1234</netex:PublicCode>
+ </netex:DestinationDisplay>
+ <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC"
+ version="any">
+ <netex:FrontText>Mission 2 bis</netex:FrontText>
+ <netex:PublicCode>2345</netex:PublicCode>
+ </netex:DestinationDisplay>
+ </netex:destinationDisplays>
+ <netex:scheduledStopPoints>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC"
+ version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC"
+ version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC"
+ version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC"
+ version="any"/>
+ </netex:scheduledStopPoints>
+ <netex:passengerStopAssignments>
+ <netex:PassengerStopAssignment
+ id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment
+ id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78402:ZDE:50000918:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment
+ id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78402:ZDE:50000917:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment
+ id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any">
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ </netex:passengerStopAssignments>
+ <netex:routingConstraintZones>
+ <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC"
+ version="any">
+ <netex:Name>ITL 1</netex:Name>
+ <netex:members>
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ScheduledStopPointRef
+ ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ </netex:members>
+ <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse>
+ </netex:RoutingConstraintZone>
+ </netex:routingConstraintZones>
+ </netex:members>
+ </netex:GeneralFrame>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC"
+ version="any">
+ <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/>
+ <netex:members>
+ <netex:serviceJourneys>
+ <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any">
+ <netex:Name>Course 1 par ici aussi</netex:Name>
+ <netex:noticeAssignments>
+ <netex:NoticeAssignment>
+ <netex:NoticeRef ref="CITYWAY:Notice:2:LOC">
+ version="any"</netex:NoticeRef>
+ </netex:NoticeAssignment>
+ </netex:noticeAssignments>
+ <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC">
+ version="any"</netex:DayTypeRef>
+ <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC">
+ version="any"</netex:DayTypeRef>
+ <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC"
+ version="any"/>
+ <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:212">
+ version="any"</netex:OperatorRef>
+ <netex:trainNumbers>
+ <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef>
+ </netex:trainNumbers>
+ <netex:passingTimes>
+ <netex:TimetabledPassingTime version="any">
+ <netex:ArrivalTime>23:58:00.000</netex:ArrivalTime>
+ <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset>
+ <netex:DepartureTime>23:59:00.000</netex:DepartureTime>
+ <netex:DepartureDayOffset>0</netex:DepartureDayOffset>
+ </netex:TimetabledPassingTime>
+ <netex:TimetabledPassingTime version="any">
+ <netex:ArrivalTime>00:03:00.000</netex:ArrivalTime>
+ <netex:ArrivalDayOffset>1</netex:ArrivalDayOffset>
+ <netex:DepartureTime>00:04:00.000</netex:DepartureTime>
+ <netex:DepartureDayOffset>1</netex:DepartureDayOffset>
+ </netex:TimetabledPassingTime>
+ </netex:passingTimes>
+ </netex:ServiceJourney>
+ </netex:serviceJourneys>
+ </netex:members>
+ </netex:GeneralFrame>
+ </netex:frames>
+ </netex:CompositeFrame>
+ </netex:dataObjects>
+</netex:PublicationDelivery>
diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml
new file mode 100644
index 000000000..712ea8be1
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex"
+ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt"
+ xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core"
+ xmlns:siri="http://www.siri.org.uk/siri" version="1.0">
+ <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp>
+ <netex:ParticipantRef>CITYWAY</netex:ParticipantRef>
+ <netex:dataObjects>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_CALENDRIER-1_20170214090012:LOC" version="any">
+ <netex:ValidBetween>
+ <netex:FromDate>2017-04-01T00:00:00</netex:FromDate>
+ <netex:ToDate>2016-12-31T00:00:00</netex:ToDate>
+ </netex:ValidBetween>
+ <netex:TypeOfFrameRef ref="NETEX_CALENDRIER"/>
+ <netex:members>
+ <netex:DayType id="CITYWAY:DayType:1:LOC" version="any">
+ <netex:Name>Semaine</netex:Name>
+ <netex:properties>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Monday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Tuesday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Wednesday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Thursday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Friday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ </netex:properties>
+ </netex:DayType>
+ <netex:DayType id="CITYWAY:DayType:2:LOC" version="any">
+ <netex:Name>Fin de semaine</netex:Name>
+ <netex:properties>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Saturday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ <netex:PropertyOfDay>
+ <netex:DaysOfWeek>Sunday</netex:DaysOfWeek>
+ </netex:PropertyOfDay>
+ </netex:properties>
+ </netex:DayType>
+ <netex:DayType id="CITYWAY:DayType:3:LOC" version="any">
+ <netex:Name>Service spécial</netex:Name>
+ </netex:DayType>
+ <netex:DayType id="CITYWAY:DayType:4:LOC" version="any">
+ <netex:Name>Restriction</netex:Name>
+ </netex:DayType>
+ <netex:DayTypeAssignment id="dta1" version="any" order="0">
+ <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/>
+ <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC" version="any"/>
+ </netex:DayTypeAssignment>
+ <netex:DayTypeAssignment id="dta2" version="any" order="0">
+ <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/>
+ <netex:DayTypeRef ref="CITYWAY:DayType:2:LOC" version="any"/>
+ </netex:DayTypeAssignment>
+ <netex:DayTypeAssignment id="dta3" version="any" order="0">
+ <netex:Date>2017-03-15</netex:Date>
+ <netex:DayTypeRef ref="CITYWAY:DayType:3:LOC" version="any"/>
+ <netex:isAvailable>true</netex:isAvailable>
+ </netex:DayTypeAssignment>
+ <netex:DayTypeAssignment id="dta4" version="any" order="0">
+ <netex:Date>2017-03-15</netex:Date>
+ <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC" version="any"/>
+ <netex:isAvailable>false</netex:isAvailable>
+ </netex:DayTypeAssignment>
+ <netex:OperatingPeriod id="CITYWAY:OperatingPeriod:1:LOC" version="any">
+ <netex:FromDate>2017-01-01T00:00:00</netex:FromDate>
+ <netex:ToDate>2017-12-31T00:00:00</netex:ToDate>
+ </netex:OperatingPeriod>
+
+ </netex:members>
+ </netex:GeneralFrame>
+ </netex:dataObjects>
+</netex:PublicationDelivery>
diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml
new file mode 100644
index 000000000..f59f8ac2d
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex"
+ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2"
+ xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0">
+ <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp>
+ <netex:ParticipantRef>CITYWAY</netex:ParticipantRef>
+ <netex:dataObjects>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_COMMUN-1_20170214090012:LOC" version="any">
+ <netex:TypeOfFrameRef ref="NETEX_COMMUN"/>
+ <netex:members>
+
+ <netex:Notice id="CITYWAY:Notice:1:LOC" version="any">
+ <netex:Text>Notice 1</netex:Text>
+ <netex:PublicCode>1</netex:PublicCode>
+ <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/>
+ </netex:Notice>
+ <netex:Notice id="CITYWAY:Notice:2:LOC" version="any">
+ <netex:Text>Notice 2</netex:Text>
+ <netex:PublicCode>2</netex:PublicCode>
+ <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/>
+ </netex:Notice>
+ <netex:Notice id="CITYWAY:Notice:3:LOC" version="any">
+ <netex:Text>Notice 3</netex:Text>
+ <netex:PublicCode>3</netex:PublicCode>
+ <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/>
+ </netex:Notice>
+
+ </netex:members>
+ </netex:GeneralFrame>
+ </netex:dataObjects>
+</netex:PublicationDelivery>
diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml
new file mode 100644
index 000000000..9eefeeb43
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex"
+ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt"
+ xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core"
+ xmlns:siri="http://www.siri.org.uk/siri" version="1.0">
+ <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp>
+ <netex:ParticipantRef>CITYWAY</netex:ParticipantRef>
+ <netex:dataObjects>
+ <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any">
+ <netex:Name>Ligne 1</netex:Name>
+ <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/>
+ <netex:frames>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" version="any">
+ <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/>
+ <netex:members>
+
+ <netex:Route id="CITYWAY:Route:1:LOC" version="any">
+ <netex:Name>route 1</netex:Name>
+ <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef>
+ <netex:DirectionType>outbound</netex:DirectionType>
+ <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/>
+ <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/>
+ </netex:Route>
+ <netex:Route id="CITYWAY:Route:2:LOC" version="any">
+ <netex:Name>route 2</netex:Name>
+ <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef>
+ <netex:DirectionType>inbound</netex:DirectionType>
+ <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/>
+ <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/>
+ </netex:Route>
+
+
+ <netex:Direction id="CITYWAY:Direction:1:LOC" version="any">
+ <netex:Name>Par ici</netex:Name>
+ </netex:Direction>
+ <netex:Direction id="CITYWAY:Direction:2:LOC" version="any">
+ <netex:Name>Par là</netex:Name>
+ </netex:Direction>
+
+
+ <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" version="any">
+ <netex:Name>Par ici</netex:Name>
+ <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/>
+ <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" version="any"/>
+ <netex:pointsInSequence>
+ <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1"
+ version="any">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2"
+ version="any">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ </netex:pointsInSequence>
+ <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType>
+ </netex:ServiceJourneyPattern>
+ <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" version="any">
+ <netex:Name>Par là</netex:Name>
+ <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/>
+ <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" version="any"/>
+ <netex:pointsInSequence>
+ <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1"
+ version="any">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2"
+ version="any">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ </netex:pointsInSequence>
+ <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType>
+ </netex:ServiceJourneyPattern>
+
+
+ <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" version="any">
+ <netex:FrontText>Mission 1</netex:FrontText>
+ <netex:PublicCode>1234</netex:PublicCode>
+ </netex:DestinationDisplay>
+ <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" version="any">
+ <netex:FrontText>Mission 2</netex:FrontText>
+ <netex:PublicCode>2345</netex:PublicCode>
+ </netex:DestinationDisplay>
+
+
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+
+
+ <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any" order="0">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any" order="0">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50009052:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any" order="0">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50009053:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any" order="0">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+
+
+ <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" version="any">
+ <netex:Name>ITL 1</netex:Name>
+ <netex:members>
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ </netex:members>
+ <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse>
+ </netex:RoutingConstraintZone>
+
+ </netex:members>
+ </netex:GeneralFrame>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" version="any">
+ <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/>
+ <netex:members>
+
+ <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any">
+ <netex:Name>Course 1 par ici</netex:Name>
+ <netex:noticeAssignments>
+ <netex:NoticeAssignment id="ns1" version="any" order="0">
+ <netex:NoticeRef ref="CITYWAY:Notice:1:LOC">
+ version="any"</netex:NoticeRef>
+ </netex:NoticeAssignment>
+ </netex:noticeAssignments>
+ <netex:dayTypes>
+ <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> version="any"</netex:DayTypeRef>
+ </netex:dayTypes>
+
+ <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"/>
+ <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:011">
+ version="any"</netex:OperatorRef>
+ <netex:trainNumbers>
+ <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef>
+ </netex:trainNumbers>
+ <netex:passingTimes>
+ <netex:TimetabledPassingTime version="any">
+ <netex:ArrivalTime>01:01:00.000</netex:ArrivalTime>
+ <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset>
+ <netex:DepartureTime>01:01:00.000</netex:DepartureTime>
+ <netex:DepartureDayOffset>0</netex:DepartureDayOffset>
+ </netex:TimetabledPassingTime>
+ <netex:TimetabledPassingTime version="any">
+ <netex:ArrivalTime>01:05:00.000</netex:ArrivalTime>
+ <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset>
+ <netex:DepartureTime>01:05:00.000</netex:DepartureTime>
+ <netex:DepartureDayOffset>0</netex:DepartureDayOffset>
+ </netex:TimetabledPassingTime>
+ </netex:passingTimes>
+ </netex:ServiceJourney>
+
+ </netex:members>
+ </netex:GeneralFrame>
+ </netex:frames>
+ </netex:CompositeFrame>
+ </netex:dataObjects>
+</netex:PublicationDelivery>
diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml
new file mode 100644
index 000000000..d260ef17e
--- /dev/null
+++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex"
+ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt"
+ xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core"
+ xmlns:siri="http://www.siri.org.uk/siri" version="1.0">
+ <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp>
+ <netex:ParticipantRef>CITYWAY</netex:ParticipantRef>
+ <netex:dataObjects>
+ <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any">
+ <netex:Name>Ligne 1</netex:Name>
+ <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/>
+ <netex:frames>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" version="any">
+ <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/>
+ <netex:members>
+
+ <netex:Route id="CITYWAY:Route:1:LOC" version="any">
+ <netex:Name>route 1</netex:Name>
+ <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef>
+ <netex:DirectionType>outbound</netex:DirectionType>
+ <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/>
+ <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/>
+ </netex:Route>
+ <netex:Route id="CITYWAY:Route:2:LOC" version="any">
+ <netex:Name>route 2</netex:Name>
+ <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef>
+ <netex:DirectionType>inbound</netex:DirectionType>
+ <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/>
+ <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/>
+ </netex:Route>
+
+
+ <netex:Direction id="CITYWAY:Direction:1:LOC" version="any">
+ <netex:Name>Par ici aussi</netex:Name>
+ </netex:Direction>
+ <netex:Direction id="CITYWAY:Direction:2:LOC" version="any">
+ <netex:Name>Par là aussi</netex:Name>
+ </netex:Direction>
+
+
+ <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" version="any">
+ <netex:Name>Par ici itou</netex:Name>
+ <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/>
+ <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" version="any"/>
+ <netex:pointsInSequence>
+ <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1"
+ version="any">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2"
+ version="any">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ </netex:pointsInSequence>
+ <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType>
+ </netex:ServiceJourneyPattern>
+ <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" version="any">
+ <netex:Name>Par là itou</netex:Name>
+ <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/>
+ <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" version="any"/>
+ <netex:pointsInSequence>
+ <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1"
+ version="any">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2"
+ version="any">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+ <netex:ForAlighting>true</netex:ForAlighting>
+ <netex:ForBoarding>true</netex:ForBoarding>
+ </netex:StopPointInJourneyPattern>
+ </netex:pointsInSequence>
+ <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType>
+ </netex:ServiceJourneyPattern>
+
+
+ <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" version="any">
+ <netex:FrontText>Mission 1 bis</netex:FrontText>
+ <netex:PublicCode>1234</netex:PublicCode>
+ </netex:DestinationDisplay>
+ <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" version="any">
+ <netex:FrontText>Mission 2 bis</netex:FrontText>
+ <netex:PublicCode>2345</netex:PublicCode>
+ </netex:DestinationDisplay>
+
+
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+
+
+ <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any" order="0">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any" order="0">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78402:ZDE:50000918:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any" order="0">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78402:ZDE:50000917:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+ <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any" order="0">
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/>
+ <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef>
+ </netex:PassengerStopAssignment>
+
+
+ <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" version="any">
+ <netex:Name>ITL 1</netex:Name>
+ <netex:members>
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/>
+ <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/>
+ </netex:members>
+ <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse>
+ </netex:RoutingConstraintZone>
+
+ </netex:members>
+ </netex:GeneralFrame>
+ <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" version="any">
+ <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/>
+ <netex:members>
+
+ <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any">
+ <netex:Name>Course 1 par ici aussi</netex:Name>
+ <netex:noticeAssignments>
+ <netex:NoticeAssignment id="ns1" version="any" order="0">
+ <netex:NoticeRef ref="CITYWAY:Notice:2:LOC">
+ version="any"</netex:NoticeRef>
+ </netex:NoticeAssignment>
+ </netex:noticeAssignments>
+ <netex:dayTypes>
+ <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> version="any"</netex:DayTypeRef>
+ <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC"> version="any"</netex:DayTypeRef>
+ </netex:dayTypes>
+ <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"/>
+ <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:212">
+ version="any"</netex:OperatorRef>
+ <netex:trainNumbers>
+ <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef>
+ </netex:trainNumbers>
+ <netex:passingTimes>
+ <netex:TimetabledPassingTime version="any">
+ <netex:ArrivalTime>23:58:00.000</netex:ArrivalTime>
+ <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset>
+ <netex:DepartureTime>23:59:00.000</netex:DepartureTime>
+ <netex:DepartureDayOffset>0</netex:DepartureDayOffset>
+ </netex:TimetabledPassingTime>
+ <netex:TimetabledPassingTime version="any">
+ <netex:ArrivalTime>00:03:00.000</netex:ArrivalTime>
+ <netex:ArrivalDayOffset>1</netex:ArrivalDayOffset>
+ <netex:DepartureTime>00:04:00.000</netex:DepartureTime>
+ <netex:DepartureDayOffset>1</netex:DepartureDayOffset>
+ </netex:TimetabledPassingTime>
+ </netex:passingTimes>
+ </netex:ServiceJourney>
+
+ </netex:members>
+ </netex:GeneralFrame>
+ </netex:frames>
+ </netex:CompositeFrame>
+ </netex:dataObjects>
+</netex:PublicationDelivery>
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/gtfs/time_spec.rb b/spec/lib/gtfs/time_spec.rb
new file mode 100644
index 000000000..540d7cc79
--- /dev/null
+++ b/spec/lib/gtfs/time_spec.rb
@@ -0,0 +1,27 @@
+require "rails_helper"
+
+RSpec.describe GTFS::Time do
+
+ it "returns an UTC Time with given H:M:S" do
+ expect(GTFS::Time.parse("14:29:00").time).to eq(Time.parse("2000-01-01 14:29:00 +00"))
+ end
+
+ it "support hours with a single number" do
+ expect(GTFS::Time.parse("4:29:00").time).to eq(Time.parse("2000-01-01 04:29:00 +00"))
+ end
+
+ it "return nil for invalid format" do
+ expect(GTFS::Time.parse("abc")).to be_nil
+ end
+
+ it "removes 24 hours after 23:59:59" do
+ expect(GTFS::Time.parse("25:29:00").time).to eq(Time.parse("2000-01-01 01:29:00 +00"))
+ end
+
+ it "returns a day_offset for each 24 hours turn" do
+ expect(GTFS::Time.parse("10:00:00").day_offset).to eq(0)
+ expect(GTFS::Time.parse("30:00:00").day_offset).to eq(1)
+ expect(GTFS::Time.parse("50:00:00").day_offset).to eq(2)
+ end
+
+end
diff --git a/spec/lib/route_way_cost_unit_converter_spec.rb b/spec/lib/route_way_cost_unit_converter_spec.rb
index 3c5e51710..aa25d57d2 100644
--- a/spec/lib/route_way_cost_unit_converter_spec.rb
+++ b/spec/lib/route_way_cost_unit_converter_spec.rb
@@ -35,4 +35,32 @@ RSpec.describe RouteWayCostUnitConverter do
})
end
end
+
+ describe ".meters_to_kilometers" do
+ it "converts meters to integer kilometres" do
+ expect(
+ RouteWayCostUnitConverter.meters_to_kilometers(6350)
+ ).to eq(6)
+ end
+
+ it "snaps values between 0 and 1 to 1" do
+ expect(
+ RouteWayCostUnitConverter.meters_to_kilometers(50)
+ ).to eq(1)
+ end
+ end
+
+ describe ".seconds_to_minutes" do
+ it "converts seconds to minutes" do
+ expect(
+ RouteWayCostUnitConverter.seconds_to_minutes(300)
+ ).to eq(5)
+ end
+
+ it "snaps values between 0 and 1 to 1" do
+ expect(
+ RouteWayCostUnitConverter.seconds_to_minutes(3)
+ ).to eq(1)
+ end
+ end
end
diff --git a/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb b/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb
new file mode 100644
index 000000000..1fafad302
--- /dev/null
+++ b/spec/lib/tom_tom/matrix/request_json_serializer_spec.rb
@@ -0,0 +1,39 @@
+RSpec.describe TomTom::Matrix::RequestJSONSerializer do
+ describe ".dump" do
+ it "serializes BigDecimal values to floats" do
+ points = [{
+ point: {
+ latitude: 52.50867.to_d,
+ longitude: 13.42879.to_d
+ },
+ }]
+ data = {
+ origins: points,
+ destinations: points
+ }
+
+ expect(
+ TomTom::Matrix::RequestJSONSerializer.dump(data)
+ ).to eq(<<-JSON.delete(" \n"))
+ {
+ "origins": [
+ {
+ "point": {
+ "latitude": 52.50867,
+ "longitude": 13.42879
+ }
+ }
+ ],
+ "destinations": [
+ {
+ "point": {
+ "latitude": 52.50867,
+ "longitude": 13.42879
+ }
+ }
+ ]
+ }
+ JSON
+ end
+ end
+end
diff --git a/spec/lib/tom_tom/matrix_spec.rb b/spec/lib/tom_tom/matrix_spec.rb
new file mode 100644
index 000000000..605f1d254
--- /dev/null
+++ b/spec/lib/tom_tom/matrix_spec.rb
@@ -0,0 +1,228 @@
+RSpec.describe TomTom::Matrix do
+ let(:matrix) { TomTom::Matrix.new(nil) }
+
+ describe "#points_from_way_costs" do
+ it "extracts a set of lat/lng coordinates from a list of WayCosts" do
+ way_costs = [
+ WayCost.new(
+ departure: Geokit::LatLng.new(48.85086, 2.36143),
+ arrival: Geokit::LatLng.new(47.91231, 1.87606),
+ id: '44-77'
+ ),
+ WayCost.new(
+ departure: Geokit::LatLng.new(47.91231, 1.87606),
+ arrival: Geokit::LatLng.new(52.50867, 13.42879),
+ id: '77-88'
+ )
+ ]
+
+ expect(
+ matrix.points_from_way_costs(way_costs)
+ ).to eq([
+ TomTom::Matrix::Point.new(
+ Geokit::LatLng.new(48.85086, 2.36143),
+ '44'
+ ),
+ TomTom::Matrix::Point.new(
+ Geokit::LatLng.new(47.91231, 1.87606),
+ '77'
+ ),
+ TomTom::Matrix::Point.new(
+ Geokit::LatLng.new(52.50867, 13.42879),
+ '88'
+ )
+ ])
+ end
+ end
+
+ describe "#points_as_params" do
+ it "transforms a set of LatLng points into a hash for use by TomTom Matrix" do
+ points = [
+ TomTom::Matrix::Point.new(
+ Geokit::LatLng.new(48.85086, 2.36143),
+ '44'
+ ),
+ TomTom::Matrix::Point.new(
+ Geokit::LatLng.new(47.91231, 1.87606),
+ '77'
+ ),
+ TomTom::Matrix::Point.new(
+ Geokit::LatLng.new(52.50867, 13.42879),
+ '88'
+ )
+ ]
+
+ expect(
+ matrix.points_as_params(points)
+ ).to eq([
+ {
+ point: {
+ latitude: 48.85086,
+ longitude: 2.36143
+ },
+ },
+ {
+ point: {
+ latitude: 47.91231,
+ longitude: 1.87606
+ },
+ },
+ {
+ point: {
+ latitude: 52.50867,
+ longitude: 13.42879
+ },
+ }
+ ])
+ end
+ end
+
+ describe "#build_request_body" do
+ it "serializes BigDecimal coordinates to floats" do
+ points = [
+ {
+ point: {
+ latitude: 48.85086.to_d,
+ longitude: 2.36143.to_d
+ },
+ },
+ {
+ point: {
+ latitude: 47.91231.to_d,
+ longitude: 1.87606.to_d
+ },
+ },
+ {
+ point: {
+ latitude: 52.50867.to_d,
+ longitude: 13.42879.to_d
+ },
+ }
+ ]
+
+ expect(
+ matrix.build_request_body(points)
+ ).to eq(<<-JSON.delete(" \n"))
+ {
+ "origins": [
+ {
+ "point": {
+ "latitude": 48.85086,
+ "longitude": 2.36143
+ }
+ },
+ {
+ "point": {
+ "latitude": 47.91231,
+ "longitude": 1.87606
+ }
+ },
+ {
+ "point": {
+ "latitude": 52.50867,
+ "longitude": 13.42879
+ }
+ }
+ ],
+ "destinations": [
+ {
+ "point": {
+ "latitude": 48.85086,
+ "longitude": 2.36143
+ }
+ },
+ {
+ "point": {
+ "latitude": 47.91231,
+ "longitude": 1.87606
+ }
+ },
+ {
+ "point": {
+ "latitude": 52.50867,
+ "longitude": 13.42879
+ }
+ }
+ ]
+ }
+ JSON
+ end
+ end
+
+ describe "#extract_costs_to_way_costs!" do
+ it "puts distance & time costs in way_costs" do
+ way_costs = [
+ WayCost.new(
+ departure: Geokit::LatLng.new(48.85086, 2.36143),
+ arrival: Geokit::LatLng.new(47.91231, 1.87606),
+ id: '55-99'
+ ),
+ WayCost.new(
+ departure: Geokit::LatLng.new(47.91231, 1.87606),
+ arrival: Geokit::LatLng.new(52.50867, 13.42879),
+ id: '99-22'
+ )
+ ]
+
+ expected_way_costs = [
+ WayCost.new(
+ departure: Geokit::LatLng.new(48.85086, 2.36143),
+ arrival: Geokit::LatLng.new(47.91231, 1.87606),
+ distance: 117947,
+ time: 8356,
+ id: '55-99'
+ ),
+ WayCost.new(
+ departure: Geokit::LatLng.new(48.85086, 2.36143),
+ arrival: Geokit::LatLng.new(52.50867, 13.42879),
+ distance: 999088,
+ time: 62653,
+ id: '55-22'
+ ),
+ WayCost.new(
+ departure: Geokit::LatLng.new(47.91231, 1.87606),
+ arrival: Geokit::LatLng.new(48.85086, 2.36143),
+ distance: 117231,
+ time: 9729,
+ id: '99-55'
+ ),
+ WayCost.new(
+ departure: Geokit::LatLng.new(47.91231, 1.87606),
+ arrival: Geokit::LatLng.new(52.50867, 13.42879),
+ distance: 1114635,
+ time: 72079,
+ id: '99-22'
+ ),
+ WayCost.new(
+ departure: Geokit::LatLng.new(52.50867, 13.42879),
+ arrival: Geokit::LatLng.new(48.85086, 2.36143),
+ distance: 997232,
+ time: 63245,
+ id: '22-55'
+ ),
+ WayCost.new(
+ departure: Geokit::LatLng.new(52.50867, 13.42879),
+ arrival: Geokit::LatLng.new(47.91231, 1.87606),
+ distance: 1113108,
+ time: 68485,
+ id: '22-99'
+ ),
+ WayCost.new(
+ departure: Geokit::LatLng.new(52.50867, 13.42879),
+ arrival: Geokit::LatLng.new(52.50867, 13.42879),
+ distance: 344,
+ time: 109,
+ id: '22-22'
+ )
+ ]
+
+ matrix_response = JSON.parse(read_fixture('tom_tom_matrix.json'))
+
+ points = matrix.points_from_way_costs(way_costs)
+
+ expect(
+ matrix.extract_costs_to_way_costs!(way_costs, points, matrix_response)
+ ).to match_array(expected_way_costs)
+ end
+ end
+end
diff --git a/spec/models/api/v1/api_key_spec.rb b/spec/models/api/v1/api_key_spec.rb
index cc483a118..5c5a6bde1 100644
--- a/spec/models/api/v1/api_key_spec.rb
+++ b/spec/models/api/v1/api_key_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::ApiKey, type: :model do
subject { create(:api_key) }
it { should validate_presence_of :organisation }
- it { is_expected.to be_versioned }
+
it 'should have a valid factory' do
expect(build(:api_key)).to be_valid
diff --git a/spec/models/calendar_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..e57eee3b2 100644
--- a/spec/models/calendar_spec.rb
+++ b/spec/models/calendar_spec.rb
@@ -4,9 +4,7 @@ RSpec.describe Calendar, :type => :model do
it { is_expected.to validate_presence_of(:organisation) }
it { is_expected.to validate_presence_of(:name) }
- it { is_expected.to 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
let(:calendar) { create(:calendar, int_day_types: Calendar::MONDAY | Calendar::SUNDAY, date_ranges: [Date.today...(Date.today + 1.month)]) }
diff --git a/spec/models/chouette/access_link_spec.rb b/spec/models/chouette/access_link_spec.rb
index ced99eb1d..448c22d33 100644
--- a/spec/models/chouette/access_link_spec.rb
+++ b/spec/models/chouette/access_link_spec.rb
@@ -4,7 +4,7 @@ describe Chouette::AccessLink, :type => :model do
subject { create(:access_link) }
it { is_expected.to validate_uniqueness_of :objectid }
- it { is_expected.to be_versioned }
+
describe '#get_objectid' do
subject { super().get_objectid }
diff --git a/spec/models/chouette/access_point_spec.rb b/spec/models/chouette/access_point_spec.rb
index 2184c6ec2..9c637cf41 100644
--- a/spec/models/chouette/access_point_spec.rb
+++ b/spec/models/chouette/access_point_spec.rb
@@ -12,7 +12,7 @@ describe Chouette::AccessPoint, :type => :model do
it { is_expected.to validate_presence_of :name }
it { is_expected.to validate_numericality_of :latitude }
it { is_expected.to validate_numericality_of :longitude }
- it { is_expected.to be_versioned }
+
describe ".latitude" do
it "should accept -90 value" do
diff --git a/spec/models/chouette/company_spec.rb b/spec/models/chouette/company_spec.rb
index 34b19eeda..677c60dd9 100644
--- a/spec/models/chouette/company_spec.rb
+++ b/spec/models/chouette/company_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Chouette::Company, :type => :model do
subject { create(:company) }
it { should validate_presence_of :name }
- it { is_expected.to be_versioned }
+
describe "#nullables empty" do
it "should set null empty nullable attributes" do
diff --git a/spec/models/chouette/connection_link_spec.rb b/spec/models/chouette/connection_link_spec.rb
index 4ab67d007..4486e348c 100644
--- a/spec/models/chouette/connection_link_spec.rb
+++ b/spec/models/chouette/connection_link_spec.rb
@@ -8,7 +8,7 @@ describe Chouette::ConnectionLink, :type => :model do
subject { create(:connection_link) }
it { is_expected.to validate_uniqueness_of :objectid }
- it { is_expected.to be_versioned }
+
describe '#get_objectid' do
subject { super().get_objectid }
diff --git a/spec/models/chouette/group_of_line_spec.rb b/spec/models/chouette/group_of_line_spec.rb
index d43d75374..8b2df69e5 100644
--- a/spec/models/chouette/group_of_line_spec.rb
+++ b/spec/models/chouette/group_of_line_spec.rb
@@ -4,7 +4,7 @@ describe Chouette::GroupOfLine, :type => :model do
subject { create(:group_of_line) }
it { should validate_presence_of :name }
- it { is_expected.to be_versioned }
+
describe "#stop_areas" do
let!(:line){create(:line, :group_of_lines => [subject])}
diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb
index dac45d6b5..02a54f056 100644
--- a/spec/models/chouette/journey_pattern_spec.rb
+++ b/spec/models/chouette/journey_pattern_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Chouette::JourneyPattern, :type => :model do
- it { is_expected.to be_versioned }
+
subject { create(:journey_pattern) }
describe 'checksum' do
@@ -92,7 +92,7 @@ describe Chouette::JourneyPattern, :type => :model do
let(:journey_pattern) { create :journey_pattern }
let(:distances){ [] }
it "should raise an error" do
- expect{journey_pattern.set_distances(distances)}.to raise_error
+ expect{journey_pattern.set_distances(distances)}.to raise_error(RuntimeError)
end
context "with consistent data" do
diff --git a/spec/models/chouette/line_spec.rb b/spec/models/chouette/line_spec.rb
index 056d5da9e..cd7cdcb09 100644
--- a/spec/models/chouette/line_spec.rb
+++ b/spec/models/chouette/line_spec.rb
@@ -7,7 +7,7 @@ describe Chouette::Line, :type => :model do
# it { is_expected.to validate_presence_of :network }
# it { is_expected.to validate_presence_of :company }
it { should validate_presence_of :name }
- it { is_expected.to be_versioned }
+
describe '#display_name' do
it 'should display local_id, number, name and company name' do
diff --git a/spec/models/chouette/network_spec.rb b/spec/models/chouette/network_spec.rb
index 78a4150df..11ad7cacb 100644
--- a/spec/models/chouette/network_spec.rb
+++ b/spec/models/chouette/network_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Chouette::Network, :type => :model do
subject { create(:network) }
it { should validate_presence_of :name }
- it { is_expected.to be_versioned }
+
describe "#stop_areas" do
let!(:line){create(:line, :network => subject)}
diff --git a/spec/models/chouette/route/route_base_spec.rb b/spec/models/chouette/route/route_base_spec.rb
index d24ad6348..43ff28c40 100644
--- a/spec/models/chouette/route/route_base_spec.rb
+++ b/spec/models/chouette/route/route_base_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe Chouette::Route, :type => :model do
#it { is_expected.to validate_presence_of :direction_code }
it { is_expected.to validate_inclusion_of(:direction).in_array(%i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)) }
it { is_expected.to validate_inclusion_of(:wayback).in_array(%i(outbound inbound)) }
- it { is_expected.to be_versioned }
-
+
context "reordering methods" do
let(:bad_stop_point_ids){subject.stop_points.map { |sp| sp.id + 1}}
let(:ident){subject.stop_points.map(&:id)}
@@ -63,17 +62,17 @@ RSpec.describe Chouette::Route, :type => :model do
end
context "callbacks" do
- it "calls #calculate_costs! after_save when TomTom is enabled" do
+ it "calls #calculate_costs! after_commit when TomTom is enabled", truncation: true do
allow(TomTom).to receive(:enabled?).and_return(true)
- route = create(:route)
+ route = build(:route)
expect(route).to receive(:calculate_costs!)
route.save
end
- it "doesn't call #calculate_costs! after_save if TomTom is disabled" do
+ it "doesn't call #calculate_costs! after_commit if TomTom is disabled", truncation: true do
allow(TomTom).to receive(:enabled?).and_return(false)
- route = create(:route)
+ route = build(:route)
expect(route).not_to receive(:calculate_costs!)
route.save
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/routing_constraint_zone_spec.rb b/spec/models/chouette/routing_constraint_zone_spec.rb
index bda6bb04a..321b41b7b 100644
--- a/spec/models/chouette/routing_constraint_zone_spec.rb
+++ b/spec/models/chouette/routing_constraint_zone_spec.rb
@@ -8,7 +8,7 @@ describe Chouette::RoutingConstraintZone, type: :model do
it { is_expected.to validate_presence_of :route_id }
# shoulda matcher to validate length of array ?
xit { is_expected.to validate_length_of(:stop_point_ids).is_at_least(2) }
- it { is_expected.to be_versioned }
+
describe 'checksum' do
it_behaves_like 'checksum support'
diff --git a/spec/models/chouette/stop_area_spec.rb b/spec/models/chouette/stop_area_spec.rb
index e35300caf..185820388 100644
--- a/spec/models/chouette/stop_area_spec.rb
+++ b/spec/models/chouette/stop_area_spec.rb
@@ -13,7 +13,7 @@ describe Chouette::StopArea, :type => :model do
it { should validate_presence_of :kind }
it { should validate_numericality_of :latitude }
it { should validate_numericality_of :longitude }
- it { is_expected.to be_versioned }
+
describe "#area_type" do
it "should validate the value is correct regarding to the kind" do
diff --git a/spec/models/chouette/stop_point_spec.rb b/spec/models/chouette/stop_point_spec.rb
index 6b9e7727f..ba3799619 100644
--- a/spec/models/chouette/stop_point_spec.rb
+++ b/spec/models/chouette/stop_point_spec.rb
@@ -4,7 +4,7 @@ describe Chouette::StopPoint, :type => :model do
it { is_expected.to validate_uniqueness_of :objectid }
it { is_expected.to validate_presence_of :stop_area }
- it { is_expected.to be_versioned }
+
describe '#objectid' do
subject { super().get_objectid }
diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb
index bb88877b9..a3354facb 100644
--- a/spec/models/chouette/time_table_spec.rb
+++ b/spec/models/chouette/time_table_spec.rb
@@ -6,7 +6,7 @@ describe Chouette::TimeTable, :type => :model do
it { is_expected.to validate_presence_of :comment }
it { is_expected.to validate_uniqueness_of :objectid }
- it { is_expected.to be_versioned }
+
def create_time_table_periode time_table, start_date, end_date
create(:time_table_period, time_table: time_table, :period_start => start_date, :period_end => end_date)
diff --git a/spec/models/chouette/timeband_spec.rb b/spec/models/chouette/timeband_spec.rb
index b960c203f..fa7c8f06e 100644
--- a/spec/models/chouette/timeband_spec.rb
+++ b/spec/models/chouette/timeband_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Chouette::Timeband, :type => :model do
- it { is_expected.to be_versioned }
+
describe '#create' do
context 'when valid' do
diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb
index c69655bd4..6d44eeb2f 100644
--- a/spec/models/chouette/vehicle_journey_spec.rb
+++ b/spec/models/chouette/vehicle_journey_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Chouette::VehicleJourney, :type => :model do
subject { create(:vehicle_journey) }
- it { is_expected.to be_versioned }
+
it { should have_and_belong_to_many(:purchase_windows) }
it "must be valid with an at-stop day offset of 1" do
@@ -420,6 +420,7 @@ describe Chouette::VehicleJourney, :type => :model do
state['published_journey_name'] = 'edited_name'
state['published_journey_identifier'] = 'edited_identifier'
state['custom_fields'] = {energy: {value: 99}}
+ create :custom_field, field_type: :integer, code: :energy, name: :energy
Chouette::VehicleJourney.state_update(route, collection)
expect(state['errors']).to be_nil
@@ -867,7 +868,6 @@ describe Chouette::VehicleJourney, :type => :model do
let!( :footnote_first) {create( :footnote, :code => "1", :label => "dummy 1", :line => route.line)}
let!( :footnote_second) {create( :footnote, :code => "2", :label => "dummy 2", :line => route.line)}
-
it "should update vehicle's footnotes" do
expect(Chouette::VehicleJourney.find(subject.id).footnotes).to be_empty
subject.footnote_ids = [ footnote_first.id ]
@@ -876,4 +876,54 @@ describe Chouette::VehicleJourney, :type => :model do
end
end
end
+
+ describe "#fill_passing_time_at_borders" do
+ before do
+ start = create :stop_area
+ border = create :stop_area, kind: :non_commercial, area_type: :border
+ border_2 = create :stop_area, kind: :non_commercial, area_type: :border
+ middle = create :stop_area
+ border_3 = create :stop_area, kind: :non_commercial, area_type: :border
+ border_4 = create :stop_area, kind: :non_commercial, area_type: :border
+ _end = create :stop_area
+ journey_pattern = create :journey_pattern
+ journey_pattern.stop_points.destroy_all
+ journey_pattern.stop_points << start_point = create(:stop_point, stop_area: start, position: 0)
+ journey_pattern.stop_points << border_point = create(:stop_point, stop_area: border, position: 1)
+ journey_pattern.stop_points << border_point_2 = create(:stop_point, stop_area: border_2, position: 2)
+ journey_pattern.stop_points << middle_point = create(:stop_point, stop_area: middle, position: 3)
+ journey_pattern.stop_points << border_point_3 = create(:stop_point, stop_area: border_3, position: 4)
+ journey_pattern.stop_points << border_point_4 = create(:stop_point, stop_area: border_4, position: 5)
+ journey_pattern.stop_points << end_point = create(:stop_point, stop_area: _end, position: 6)
+ journey_pattern.update_attribute :costs, {
+ "#{start_point.stop_area_id}-#{border_point.stop_area_id}" => {distance: 50},
+ "#{border_point.stop_area_id}-#{border_point_2.stop_area_id}" => {distance: 0},
+ "#{border_point_2.stop_area_id}-#{middle_point.stop_area_id}" => {distance: 100},
+ "#{middle_point.stop_area_id}-#{border_point_3.stop_area_id}" => {distance: 100},
+ "#{border_point_3.stop_area_id}-#{border_point_4.stop_area_id}" => {distance: 0},
+ "#{border_point_4.stop_area_id}-#{end_point.stop_area_id}" => {distance: 100}
+ }
+ @journey = create :vehicle_journey, journey_pattern: journey_pattern
+ @journey.vehicle_journey_at_stops.destroy_all
+ @start = create :vehicle_journey_at_stop, stop_point: start_point, vehicle_journey: @journey
+ @target = create :vehicle_journey_at_stop, stop_point: border_point, vehicle_journey: @journey, arrival_time: nil, departure_time: nil
+ @target_2 = create :vehicle_journey_at_stop, stop_point: border_point_2, vehicle_journey: @journey, arrival_time: nil, departure_time: nil
+ @middle = create :vehicle_journey_at_stop, stop_point: middle_point, vehicle_journey: @journey, arrival_time: @start.arrival_time + 4.hours, departure_time: @start.departure_time + 4.hours
+ @target_3 = create :vehicle_journey_at_stop, stop_point: border_point_3, vehicle_journey: @journey, arrival_time: nil, departure_time: nil
+ @target_4 = create :vehicle_journey_at_stop, stop_point: border_point_4, vehicle_journey: @journey, arrival_time: nil, departure_time: nil
+ @end = create :vehicle_journey_at_stop, stop_point: end_point, vehicle_journey: @journey, arrival_time: @middle.arrival_time + 4.hours, departure_time: @middle.departure_time + 4.hours
+ end
+
+ it "should compute passing time" do
+ @journey.reload.fill_passing_time_at_borders
+ expect(@target.reload.arrival_time.to_i).to eq (@start.reload.departure_time + 1.0/3 * (@middle.reload.arrival_time - @start.departure_time)).to_i
+ expect(@target_2.reload.arrival_time).to eq @target.arrival_time
+ expect(@target.departure_time).to eq @target.arrival_time
+ expect(@target_2.departure_time).to eq @target.arrival_time
+ expect(@target_3.reload.arrival_time.to_i).to eq (@middle.reload.departure_time + 0.5 * (@end.reload.arrival_time - @middle.departure_time)).to_i
+ expect(@target_4.reload.arrival_time).to eq @target_3.arrival_time
+ expect(@target_3.departure_time).to eq @target_3.arrival_time
+ expect(@target_4.departure_time).to eq @target_3.arrival_time
+ end
+ end
end
diff --git a/spec/models/compliance_check_set_spec.rb b/spec/models/compliance_check_set_spec.rb
index 61421287a..b6f854829 100644
--- a/spec/models/compliance_check_set_spec.rb
+++ b/spec/models/compliance_check_set_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe ComplianceCheckSet, type: :model do
it { should have_many :compliance_checks }
it { should have_many :compliance_check_blocks }
- it { is_expected.to be_versioned }
+
describe "#update_status" do
it "updates :status to successful when all resources are OK" do
diff --git a/spec/models/compliance_control_block_spec.rb b/spec/models/compliance_control_block_spec.rb
index 4abe0ed9c..089d78434 100644
--- a/spec/models/compliance_control_block_spec.rb
+++ b/spec/models/compliance_control_block_spec.rb
@@ -17,4 +17,16 @@ RSpec.describe ComplianceControlBlock, type: :model do
it { should_not allow_values( *%w{ demandResponseBus nightus irportLinkBus highrequencyBus expressBUs
Shuttle suburban regioalRail interregion4lRail })
.for(:transport_submode) }
+
+ context "transport mode & submode uniqueness" do
+ let(:cc_block) {create :compliance_control_block, transport_mode: 'bus', transport_submode: 'nightBus'}
+ let(:cc_set1) { cc_block.compliance_control_set }
+ let(:cc_set2) { create :compliance_control_set }
+
+ it "sould be unique in a compliance control set" do
+ expect( ComplianceControlBlock.new(transport_mode: 'bus', transport_submode: 'nightBus', compliance_control_set: cc_set1) ).not_to be_valid
+ expect( ComplianceControlBlock.new(transport_mode: 'bus', transport_submode: 'nightBus', compliance_control_set: cc_set2) ).to be_valid
+ end
+
+end
end
diff --git a/spec/models/compliance_control_set_spec.rb b/spec/models/compliance_control_set_spec.rb
index c157dcaf3..a66e7f030 100644
--- a/spec/models/compliance_control_set_spec.rb
+++ b/spec/models/compliance_control_set_spec.rb
@@ -10,5 +10,5 @@ RSpec.describe ComplianceControlSet, type: :model do
it { should have_many(:compliance_control_blocks).dependent(:destroy) }
it { should validate_presence_of :name }
- it { is_expected.to be_versioned }
+
end
diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb
index b92bcfbdb..ce6ce9fa5 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,46 @@ 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
+ expect(vj.custom_fields[:energy].value).to eq 1
+ expect(vj.custom_fields[:energy].display_value).to eq "bar"
+ end
+
+ it "should not break initailizartion if the model does not have the :custom_field_values attribute" do
+ expect{Chouette::VehicleJourney.where(id: vj.id).select(:id).last}.to_not raise_error
+ 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 +83,7 @@ RSpec.describe CustomField, type: :model do
it "should validate the value" do
{
+ 99 => true,
"99" => true,
"azerty" => false,
"91a" => false,
@@ -64,10 +101,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/import/gtfs_spec.rb b/spec/models/import/gtfs_spec.rb
new file mode 100644
index 000000000..b4b23be00
--- /dev/null
+++ b/spec/models/import/gtfs_spec.rb
@@ -0,0 +1,283 @@
+require "rails_helper"
+
+RSpec.describe Import::Gtfs do
+
+ let(:referential) do
+ create :referential do |referential|
+ referential.line_referential.objectid_format = "netex"
+ referential.stop_area_referential.objectid_format = "netex"
+ end
+ end
+
+ let(:workbench) do
+ create :workbench do |workbench|
+ workbench.line_referential.objectid_format = "netex"
+ workbench.stop_area_referential.objectid_format = "netex"
+ end
+ end
+
+ def create_import(file)
+ Import::Gtfs.new workbench: workbench, local_file: fixtures_path(file)
+ end
+
+ describe "#import_agencies" do
+ let(:import) { create_import "google-sample-feed.zip" }
+ it "should create a company for each agency" do
+ import.import_agencies
+
+ expect(workbench.line_referential.companies.pluck(:registration_number, :name)).to eq([["DTA","Demo Transit Authority"]])
+ end
+ end
+
+ describe "#import_stops" do
+ let(:import) { create_import "google-sample-feed.zip" }
+ it "should create a company for each agency" do
+ import.import_stops
+
+ defined_attributes = [
+ :registration_number, :name, :parent_id, :latitude, :longitude
+ ]
+ expected_attributes = [
+ ["AMV", "Amargosa Valley (Demo)", nil, 36.641496, -116.40094],
+ ["EMSI", "E Main St / S Irving St (Demo)", nil, 36.905697, -116.76218],
+ ["DADAN", "Doing Ave / D Ave N (Demo)", nil, 36.909489, -116.768242],
+ ["NANAA", "North Ave / N A Ave (Demo)", nil, 36.914944, -116.761472],
+ ["NADAV", "North Ave / D Ave N (Demo)", nil, 36.914893, -116.76821],
+ ["STAGECOACH", "Stagecoach Hotel & Casino (Demo)", nil, 36.915682, -116.751677],
+ ["BULLFROG", "Bullfrog (Demo)", nil, 36.88108, -116.81797],
+ ["BEATTY_AIRPORT", "Nye County Airport (Demo)", nil, 36.868446, -116.784582],
+ ["FUR_CREEK_RES", "Furnace Creek Resort (Demo)", nil, 36.425288, -117.133162]
+ ]
+
+ expect(workbench.stop_area_referential.stop_areas.pluck(*defined_attributes)).to match_array(expected_attributes)
+ end
+ end
+
+ describe "#import_routes" do
+ let(:import) { create_import "google-sample-feed.zip" }
+ it "should create a line for each route" do
+ import.import_routes
+
+ defined_attributes = [
+ :registration_number, :name, :number, :published_name,
+ "companies.registration_number",
+ :comment, :url
+ ]
+ expected_attributes = [
+ ["AAMV", "Airport - Amargosa Valley", "50", "Airport - Amargosa Valley", nil, nil, nil],
+ ["CITY", "City", "40", "City", nil, nil, nil],
+ ["STBA", "Stagecoach - Airport Shuttle", "30", "Stagecoach - Airport Shuttle", nil, nil, nil],
+ ["BFC", "Bullfrog - Furnace Creek Resort", "20", "Bullfrog - Furnace Creek Resort", nil, nil, nil],
+ ["AB", "Airport - Bullfrog", "10", "Airport - Bullfrog", nil, nil, nil]
+ ]
+
+ expect(workbench.line_referential.lines.includes(:company).pluck(*defined_attributes)).to match_array(expected_attributes)
+ end
+ end
+
+ describe "#import_trips" do
+ let(:import) { create_import "google-sample-feed.zip" }
+ before do
+ import.prepare_referential
+ import.import_calendars
+ end
+
+ it "should create a Route for each trip" do
+ import.import_trips
+
+ defined_attributes = [
+ "lines.registration_number", :wayback, :name, :published_name
+ ]
+ expected_attributes = [
+ ["AB", "outbound", "to Bullfrog", "to Bullfrog"],
+ ["AB", "inbound", "to Airport", "to Airport"],
+ ["STBA", "inbound", "Shuttle", "Shuttle"],
+ ["CITY", "outbound", "Outbound", "Outbound"],
+ ["CITY", "inbound", "Inbound", "Inbound"],
+ ["BFC", "outbound", "to Furnace Creek Resort", "to Furnace Creek Resort"],
+ ["BFC", "inbound", "to Bullfrog", "to Bullfrog"],
+ ["AAMV", "outbound", "to Amargosa Valley", "to Amargosa Valley"],
+ ["AAMV", "inbound", "to Airport", "to Airport"],
+ ["AAMV", "outbound", "to Amargosa Valley", "to Amargosa Valley"],
+ ["AAMV", "inbound", "to Airport", "to Airport"]
+ ]
+
+ expect(import.referential.routes.includes(:line).pluck(*defined_attributes)).to match_array(expected_attributes)
+ end
+
+ it "should create a JourneyPattern for each trip" do
+ import.import_trips
+
+ defined_attributes = [
+ :name
+ ]
+ expected_attributes = [
+ "to Bullfrog", "to Airport", "Shuttle", "Outbound", "Inbound", "to Furnace Creek Resort", "to Bullfrog", "to Amargosa Valley", "to Airport", "to Amargosa Valley", "to Airport"
+ ]
+
+ expect(import.referential.journey_patterns.pluck(*defined_attributes)).to match_array(expected_attributes)
+ end
+
+ it "should create a VehicleJourney for each trip" do
+ import.import_trips
+
+ defined_attributes = ->(v) {
+ [v.published_journey_name, v.time_tables.first&.comment]
+ }
+ expected_attributes = [
+ ["to Bullfrog", "Calendar FULLW"],
+ ["to Airport", "Calendar FULLW"],
+ ["Shuttle", "Calendar FULLW"],
+ ["CITY1", "Calendar FULLW"],
+ ["CITY2", "Calendar FULLW"],
+ ["to Furnace Creek Resort", "Calendar FULLW"],
+ ["to Bullfrog", "Calendar FULLW"],
+ ["to Amargosa Valley", "Calendar WE"],
+ ["to Airport", "Calendar WE"],
+ ["to Amargosa Valley", "Calendar WE"],
+ ["to Airport", "Calendar WE"]
+ ]
+
+ expect(import.referential.vehicle_journeys.map(&defined_attributes)).to match_array(expected_attributes)
+ end
+ end
+
+ describe "#import_stop_times" do
+ let(:import) { create_import "google-sample-feed.zip" }
+
+ before do
+ import.prepare_referential
+ import.import_calendars
+ import.import_trips
+ end
+
+ it "should create a VehicleJourneyAtStop for each stop_time" do
+ import.import_stop_times
+
+ def t(value)
+ Time.parse(value)
+ end
+
+ defined_attributes = [
+ "stop_areas.registration_number", :position, :departure_time, :arrival_time,
+ ]
+ expected_attributes = [
+ ["STAGECOACH", 0, t("2000-01-01 06:00:00 UTC"), t("2000-01-01 06:00:00 UTC")],
+ ["BEATTY_AIRPORT", 1, t("2000-01-01 06:20:00 UTC"), t("2000-01-01 06:20:00 UTC")],
+ ["STAGECOACH", 0, t("2000-01-01 06:00:00 UTC"), t("2000-01-01 06:00:00 UTC")],
+ ["NANAA", 1, t("2000-01-01 06:07:00 UTC"), t("2000-01-01 06:05:00 UTC")],
+ ["NADAV", 2, t("2000-01-01 06:14:00 UTC"), t("2000-01-01 06:12:00 UTC")],
+ ["DADAN", 3, t("2000-01-01 06:21:00 UTC"), t("2000-01-01 06:19:00 UTC")],
+ ["EMSI", 4, t("2000-01-01 06:28:00 UTC"), t("2000-01-01 06:26:00 UTC")],
+ ["EMSI", 0, t("2000-01-01 06:30:00 UTC"), t("2000-01-01 06:28:00 UTC")],
+ ["DADAN", 1, t("2000-01-01 06:37:00 UTC"), t("2000-01-01 06:35:00 UTC")],
+ ["NADAV", 2, t("2000-01-01 06:44:00 UTC"), t("2000-01-01 06:42:00 UTC")],
+ ["NANAA", 3, t("2000-01-01 06:51:00 UTC"), t("2000-01-01 06:49:00 UTC")],
+ ["STAGECOACH", 4, t("2000-01-01 06:58:00 UTC"), t("2000-01-01 06:56:00 UTC")],
+ ["BEATTY_AIRPORT", 0, t("2000-01-01 08:00:00 UTC"), t("2000-01-01 08:00:00 UTC")],
+ ["BULLFROG", 1, t("2000-01-01 08:15:00 UTC"), t("2000-01-01 08:10:00 UTC")],
+ ["BULLFROG", 0, t("2000-01-01 12:05:00 UTC"), t("2000-01-01 12:05:00 UTC")],
+ ["BEATTY_AIRPORT", 1, t("2000-01-01 12:15:00 UTC"), t("2000-01-01 12:15:00 UTC")],
+ ["BULLFROG", 0, t("2000-01-01 08:20:00 UTC"), t("2000-01-01 08:20:00 UTC")],
+ ["FUR_CREEK_RES", 1, t("2000-01-01 09:20:00 UTC"), t("2000-01-01 09:20:00 UTC")],
+ ["FUR_CREEK_RES", 0, t("2000-01-01 11:00:00 UTC"), t("2000-01-01 11:00:00 UTC")],
+ ["BULLFROG", 1, t("2000-01-01 12:00:00 UTC"), t("2000-01-01 12:00:00 UTC")],
+ ["BEATTY_AIRPORT", 0, t("2000-01-01 08:00:00 UTC"), t("2000-01-01 08:00:00 UTC")],
+ ["AMV", 1, t("2000-01-01 09:00:00 UTC"), t("2000-01-01 09:00:00 UTC")],
+ ["AMV", 0, t("2000-01-01 10:00:00 UTC"), t("2000-01-01 10:00:00 UTC")],
+ ["BEATTY_AIRPORT", 1, t("2000-01-01 11:00:00 UTC"), t("2000-01-01 11:00:00 UTC")],
+ ["BEATTY_AIRPORT", 0, t("2000-01-01 13:00:00 UTC"), t("2000-01-01 13:00:00 UTC")],
+ ["AMV", 1, t("2000-01-01 14:00:00 UTC"), t("2000-01-01 14:00:00 UTC")],
+ ["AMV", 0, t("2000-01-01 15:00:00 UTC"), t("2000-01-01 15:00:00 UTC")],
+ ["BEATTY_AIRPORT", 1, t("2000-01-01 16:00:00 UTC"), t("2000-01-01 16:00:00 UTC")]
+ ]
+ expect(referential.vehicle_journey_at_stops.includes(stop_point: :stop_area).pluck(*defined_attributes)).to match_array(expected_attributes)
+ end
+ end
+
+ describe "#import_calendars" do
+ let(:import) { create_import "google-sample-feed.zip" }
+
+ before do
+ import.prepare_referential
+ end
+
+ it "should create a Timetable for each calendar" do
+ import.import_calendars
+
+ def d(value)
+ Date.parse(value)
+ end
+
+ defined_attributes = ->(t) {
+ [t.comment, t.valid_days, t.periods.first.period_start, t.periods.first.period_end]
+ }
+ expected_attributes = [
+ ["Calendar FULLW", [1, 2, 3, 4, 5, 6, 7], d("Mon, 01 Jan 2007"), d("Fri, 31 Dec 2010")],
+ ["Calendar WE", [6, 7], d("Mon, 01 Jan 2007"), d("Fri, 31 Dec 2010")]
+ ]
+ expect(referential.time_tables.map(&defined_attributes)).to match_array(expected_attributes)
+ end
+ end
+
+ describe "#import_calendar_dates" do
+ let(:import) { create_import "google-sample-feed.zip" }
+
+ before do
+ import.prepare_referential
+ import.import_calendars
+ end
+
+ it "should create a Timetable::Date for each calendar date" do
+ import.import_calendar_dates
+
+ def d(value)
+ Date.parse(value)
+ end
+
+ defined_attributes = ->(d) {
+ [d.time_table.comment, d.date, d.in_out]
+ }
+ expected_attributes = [
+ ["Calendar FULLW", d("Mon, 04 Jun 2007"), false]
+ ]
+ expect(referential.time_table_dates.map(&defined_attributes)).to match_array(expected_attributes)
+ end
+ end
+
+ describe "#download_local_file" do
+ let(:file) { "google-sample-feed.zip" }
+ let(:import) do
+ Import::Gtfs.create! name: "GTFS test", creator: "Test", workbench: workbench, file: open_fixture(file), download_host: "rails_host"
+ end
+
+ let(:download_url) { "#{import.download_host}/workbenches/#{import.workbench_id}/imports/#{import.id}/download?token=#{import.token_download}" }
+
+ before do
+ stub_request(:get, download_url).to_return(status: 200, body: read_fixture(file))
+ end
+
+ it "should download local_file" do
+ expect(File.read(import.download_local_file)).to eq(read_fixture(file))
+ end
+ end
+
+ describe "#download_host" do
+ it "should return host defined by Rails.application.config.rails_host (without http:// schema)" do
+ allow(Rails.application.config).to receive(:rails_host).and_return("http://download_host")
+
+ expect(Import::Gtfs.new.download_host).to eq("download_host")
+ end
+ end
+
+ describe "#download_path" do
+ let(:file) { "google-sample-feed.zip" }
+ let(:import) do
+ Import::Gtfs.create! name: "GTFS test", creator: "Test", workbench: workbench, file: open_fixture(file), download_host: "rails_host"
+ end
+
+ it "should return the pathwith the token" do
+ expect(import.download_path).to eq("/workbenches/#{import.workbench_id}/imports/#{import.id}/download?token=#{import.token_download}")
+ end
+ end
+end
diff --git a/spec/models/referential_metadata_spec.rb b/spec/models/referential_metadata_spec.rb
index 291ed974a..88a12b2bb 100644
--- a/spec/models/referential_metadata_spec.rb
+++ b/spec/models/referential_metadata_spec.rb
@@ -12,14 +12,19 @@ RSpec.describe ReferentialMetadata, :type => :model do
describe ".new_from" do
let(:referential_metadata) { create :referential_metadata, referential_source: create(:referential) }
- let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, []) }
+ let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, nil) }
+ before do
+ referential_metadata.line_ids.each do |id|
+ Chouette::Line.find(id).update_attribute :line_referential_id, referential_metadata.referential.line_referential_id
+ end
+ end
it "should not have an associated referential" do
expect(new_referential_metadata).to be_a_new(ReferentialMetadata)
end
- xit "should have the same lines" do
- expect(new_referential_metadata.lines).to eq(referential_metadata.lines)
+ it "should have the same lines" do
+ expect(new_referential_metadata.line_ids.sort).to eq(referential_metadata.line_ids.sort)
end
it "should have the same periods" do
@@ -34,6 +39,14 @@ RSpec.describe ReferentialMetadata, :type => :model do
expect(new_referential_metadata.referential_source).to eq(referential_metadata.referential)
end
+ context "with a functional scope" do
+ let(:organisation){ create :organisation, sso_attributes: {"functional_scope" => [referential_metadata.referential.lines.first.objectid]} }
+ let(:new_referential_metadata) { ReferentialMetadata.new_from(referential_metadata, organisation) }
+
+ it "should scope the lines" do
+ expect(new_referential_metadata.line_ids).to eq [referential_metadata.referential.lines.first.id]
+ end
+ end
end
describe "Period" do
diff --git a/spec/models/referential_spec.rb b/spec/models/referential_spec.rb
index 1d9b3d78a..ca2caf57f 100644
--- a/spec/models/referential_spec.rb
+++ b/spec/models/referential_spec.rb
@@ -55,7 +55,7 @@ describe Referential, :type => :model do
context "Cloning referential" do
let(:clone) do
- Referential.new_from(ref, [])
+ Referential.new_from(ref, nil)
end
let!(:workbench){ create :workbench }
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
new file mode 100644
index 000000000..b407cd866
--- /dev/null
+++ b/spec/models/route_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+RSpec.describe Chouette::Route, :type => :model do
+ subject(:route){ create :route }
+ context "metadatas" do
+ it "should be empty at first" do
+ expect(Chouette::Route.has_metadata?).to be_truthy
+ expect(route.has_metadata?).to be_truthy
+ expect(route.metadata.creator_username).to be_nil
+ expect(route.metadata.modifier_username).to be_nil
+ end
+
+ context "once set" do
+ it "should set the correct values" do
+ Timecop.freeze(Time.now) do
+ route.metadata.creator_username = "john.doe"
+ route.save!
+ id = route.id
+ route = Chouette::Route.find id
+ expect(route.metadata.creator_username).to eq "john.doe"
+ expect(route.metadata.creator_username_updated_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N')
+ end
+ end
+ end
+
+ describe "#merge_metadata_from" do
+ let(:source){ create :route }
+ let(:metadata){ target.merge_metadata_from(source).metadata }
+ let(:target){ create :route }
+ before do
+ target.metadata.creator_username = "john"
+ target.metadata.modifier_username = "john"
+ end
+ context "when the source has no metadata" do
+ it "should do nothing" do
+ expect(metadata.creator_username).to eq "john"
+ expect(metadata.modifier_username).to eq "john"
+ end
+ end
+
+ context "when the source has older metadata" do
+ before do
+ source.metadata.creator_username = "jane"
+ source.metadata.modifier_username = "jane"
+ source.metadata.creator_username_updated_at = 1.month.ago
+ source.metadata.modifier_username_updated_at = 1.month.ago
+ end
+ it "should do nothing" do
+ expect(metadata.creator_username).to eq "john"
+ expect(metadata.modifier_username).to eq "john"
+ end
+ end
+
+ context "when the source has new metadata" do
+ before do
+ source.metadata.creator_username = "jane"
+ source.metadata.modifier_username = "jane"
+ target.metadata.creator_username_updated_at = 1.month.ago
+ target.metadata.modifier_username_updated_at = 1.month.ago
+ end
+ it "should update metadata" do
+ expect(metadata.creator_username).to eq "jane"
+ expect(metadata.modifier_username).to eq "jane"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/simple_json_exporter_spec.rb b/spec/models/simple_json_exporter_spec.rb
index 4b48dc732..afecf7e51 100644
--- a/spec/models/simple_json_exporter_spec.rb
+++ b/spec/models/simple_json_exporter_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe SimpleJsonExporter do
SimpleJsonExporter.define :foo
expect do
SimpleJsonExporter.new(configuration_name: :test).export
- end.to raise_error
+ end.to raise_error(RuntimeError)
end
end
context "with a complete configuration" do
@@ -18,9 +18,9 @@ RSpec.describe SimpleJsonExporter do
it "should define an exporter" do
expect{SimpleJsonExporter.find_configuration(:foo)}.to_not raise_error
expect{SimpleJsonExporter.new(configuration_name: :foo, filepath: "").export}.to_not raise_error
- expect{SimpleJsonExporter.find_configuration(:bar)}.to raise_error
- expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "")}.to raise_error
- expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error
+ expect{SimpleJsonExporter.find_configuration(:bar)}.to raise_error(RuntimeError)
+ expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError)
+ expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error(RuntimeError)
expect{SimpleJsonExporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleJsonExporter.count}.by 1
end
end
@@ -33,7 +33,7 @@ RSpec.describe SimpleJsonExporter do
config.add_field :name
config.add_field :name
end
- end.to raise_error
+ end.to raise_error(RuntimeError)
end
end
end
diff --git a/spec/services/route_way_cost_calculator_spec.rb b/spec/services/route_way_cost_calculator_spec.rb
index d5358fcf6..79b81e34d 100644
--- a/spec/services/route_way_cost_calculator_spec.rb
+++ b/spec/services/route_way_cost_calculator_spec.rb
@@ -7,18 +7,20 @@ RSpec.describe RouteWayCostCalculator do
# things in the request or response. This is just to fake the request so
# we don't actually call their API in tests. The test doesn't test
# anything given in the response.
- stub_request(:post, "https://api.tomtom.com/routing/1/batch/json?key")
+ stub_request(
+ :post,
+ "https://api.tomtom.com/routing/1/matrix/json?key&routeType=shortest&travelMode=bus"
+ )
.with(
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Content-Type'=>'application/json',
'User-Agent'=>'Faraday v0.9.2'
- }
- )
+ })
.to_return(
status: 200,
- body: "{\"formatVersion\":\"0.0.1\",\"batchItems\":[{\"statusCode\":200,\"response\":{\"routes\":[{\"summary\":{\"lengthInMeters\":117947,\"travelTimeInSeconds\":7969,\"trafficDelayInSeconds\":0,\"departureTime\":\"2018-03-12T12:32:26+01:00\",\"arrivalTime\":\"2018-03-12T14:45:14+01:00\"}}]}}]}",
+ body: "{\"formatVersion\":\"0.0.1\",\"matrix\":[[{\"statusCode\":200,\"response\":{\"routeSummary\":{\"lengthInMeters\":0,\"travelTimeInSeconds\":0,\"trafficDelayInSeconds\":0,\"departureTime\":\"2018-03-23T11:20:17+01:00\",\"arrivalTime\":\"2018-03-23T11:20:17+01:00\"}}}]]}",
headers: {}
)
diff --git a/spec/services/zip_service_spec.rb b/spec/services/zip_service_spec.rb
index 1eadaa3bf..540de5bfc 100644
--- a/spec/services/zip_service_spec.rb
+++ b/spec/services/zip_service_spec.rb
@@ -42,6 +42,24 @@ RSpec.describe ZipService, type: :zip do
end
end
+ context 'one referential without calendar' do
+ let( :zip_name ){ 'one_referential_no_calendar.zip' }
+ let( :zip_content ){ first_referential_no_calendar_data }
+
+ it 'returns a not ok object' do
+ expect_incorrect_subdir subject.first, expected_missing_calendar: true
+ end
+ end
+
+ context 'one referential with an unparsable calendar' do
+ let( :zip_name ){ 'one_referential_unparsable_calendar.zip' }
+ let( :zip_content ){ first_referential_unparsable_calendar_data }
+
+ it 'returns a not ok object' do
+ expect_incorrect_subdir subject.first, expected_wrong_calendar: true
+ end
+ end
+
context 'one referential with a foreign line' do
let( :zip_name ){ 'one_referential_foreign.zip' }
let( :zip_content ){ first_referential_foreign_data }
@@ -109,17 +127,29 @@ RSpec.describe ZipService, type: :zip do
end
end
- def expect_incorrect_subdir subdir, expected_spurious: [], expected_foreign_lines: []
+ def expect_incorrect_subdir subdir, expected_spurious: [], expected_foreign_lines: [], expected_missing_calendar: false, expected_wrong_calendar: false
expect( subdir ).not_to be_ok
expect( subdir.foreign_lines ).to eq(expected_foreign_lines)
expect( subdir.spurious ).to eq(expected_spurious)
+ expect( subdir.missing_calendar ).to eq(expected_missing_calendar)
+ expect( subdir.wrong_calendar ).to eq(expected_wrong_calendar)
end
# Data
# ----
+ let :valid_calendar do
+ """
+ <netex:PublicationDelivery xmlns:netex=\"http://www.netex.org.uk/netex\">
+ <netex:ValidBetween>
+ <netex:FromDate>2017-03-01</netex:FromDate>
+ <netex:ToDate>2017-03-31</netex:ToDate>
+ </netex:ValidBetween>
+ </netex:PublicationDelivery>
+ """
+ end
let :first_referential_ok_data do
{
- 'Referential1/calendriers.xml' => 'calendriers',
+ 'Referential1/calendriers.xml' => valid_calendar,
'Referential1/commun.xml' => 'common',
'Referential1/offre_C00108_9.xml' => 'line 108 ref 1',
'Referential1/offre_C00109_10.xml' => 'line 109 ref 1'
@@ -127,7 +157,7 @@ RSpec.describe ZipService, type: :zip do
end
let :first_referential_foreign_data do
{
- 'Referential2/calendriers.xml' => 'calendriers',
+ 'Referential2/calendriers.xml' => valid_calendar,
'Referential2/commun.xml' => 'common',
'Referential2/offre_C00110_11.xml' => 'foreign line ref 1',
'Referential2/offre_C00108_9.xml' => 'line 108 ref 1',
@@ -136,7 +166,7 @@ RSpec.describe ZipService, type: :zip do
end
let :first_referential_spurious_data do
{
- 'Referential3/calendriers.xml' => 'calendriers',
+ 'Referential3/calendriers.xml' => valid_calendar,
'Referential3/commun.xml' => 'common',
'Referential3/SPURIOUS/commun.xml' => 'common',
'Referential3/offre_C00108_9.xml' => 'line 108 ref 1',
@@ -145,7 +175,7 @@ RSpec.describe ZipService, type: :zip do
end
let :second_referential_ok_data do
{
- 'Referential4/calendriers.xml' => 'calendriers',
+ 'Referential4/calendriers.xml' => valid_calendar,
'Referential4/commun.xml' => 'common',
'Referential4/offre_C00108_9.xml' => 'line 108 ref 2',
'Referential4/offre_C00109_10.xml' => 'line 109 ref 2'
@@ -153,7 +183,7 @@ RSpec.describe ZipService, type: :zip do
end
let :messed_up_referential_data do
{
- 'Referential5/calendriers.xml' => 'calendriers',
+ 'Referential5/calendriers.xml' => valid_calendar,
'Referential5/commun.xml' => 'common',
'Referential5/SPURIOUS1/commun.xml' => 'common',
'Referential5/SPURIOUS2/commun.xml' => 'common',
@@ -163,5 +193,21 @@ RSpec.describe ZipService, type: :zip do
'Referential5/offre_C00109_10.xml' => 'line 109 ref 1'
}
end
+ let :first_referential_no_calendar_data do
+ {
+ 'Referential6/commun.xml' => 'common',
+ 'Referential6/offre_C00108_9.xml' => 'line 108 ref 1',
+ 'Referential6/offre_C00109_10.xml' => 'line 109 ref 1'
+ }
+ end
+ let :first_referential_unparsable_calendar_data do
+ {
+ 'Referential7/calendriers.xml' => 'calendriers',
+ 'Referential7/commun.xml' => 'common',
+ 'Referential7/offre_C00108_9.xml' => 'line 108 ref 1',
+ 'Referential7/offre_C00109_10.xml' => 'line 109 ref 1'
+ }
+ end
+
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index cde252236..947efd602 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -34,7 +34,6 @@ require 'webmock/rspec'
require 'simplecov'
require 'sidekiq/testing'
Sidekiq::Testing.fake!
-require 'paper_trail/frameworks/rspec'
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
diff --git a/spec/support/referential.rb b/spec/support/referential.rb
index 9acdce73a..b50844ae4 100644
--- a/spec/support/referential.rb
+++ b/spec/support/referential.rb
@@ -8,6 +8,10 @@ module ReferentialHelper
Organisation.find_by!(code: "first")
end
+ def first_workgroup
+ Workgroup.find_by_name('IDFM')
+ end
+
def self.included(base)
base.class_eval do
extend ClassMethods
@@ -53,10 +57,18 @@ RSpec.configure do |config|
referential.add_member organisation, owner: true
end
+ workgroup = FactoryGirl.create(
+ :workgroup,
+ name: "IDFM",
+ line_referential: line_referential,
+ stop_area_referential: stop_area_referential
+ )
+
workbench = FactoryGirl.create(
:workbench,
name: "Gestion de l'offre",
organisation: organisation,
+ workgroup: workgroup,
line_referential: line_referential,
stop_area_referential: stop_area_referential
)
diff --git a/spec/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
diff --git a/yarn.lock b/yarn.lock
index b32d906dd..d0e8ff6a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4719,6 +4719,10 @@ pn@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
+polyfill-array-includes@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/polyfill-array-includes/-/polyfill-array-includes-1.0.0.tgz#3dda070475859e99d653acf06ec3622cc76f8430"
+
portfinder@^1.0.9:
version "1.0.13"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
@@ -6777,10 +6781,14 @@ whatwg-encoding@^1.0.1:
dependencies:
iconv-lite "0.4.13"
-whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0:
+whatwg-fetch@>=0.10.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
+whatwg-fetch@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
+
whatwg-url@^6.3.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08"