aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock9
-rw-r--r--app/assets/javascripts/application.js6
-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/purchase_windows_controller.rb4
-rw-r--r--app/controllers/time_tables_controller.rb46
-rw-r--r--app/controllers/vehicle_journeys_controller.rb2
-rw-r--r--app/decorators/route_decorator.rb3
-rw-r--r--app/helpers/table_builder_helper.rb7
-rw-r--r--app/javascript/routes/components/StopPoint.js6
-rw-r--r--app/javascript/vehicle_journeys/reducers/vehicleJourneys.js15
-rw-r--r--app/models/chouette/company.rb2
-rw-r--r--app/models/chouette/line.rb2
-rw-r--r--app/models/chouette/stop_area.rb2
-rw-r--r--app/models/concerns/application_days_support.rb8
-rw-r--r--app/models/concerns/custom_fields_support.rb5
-rw-r--r--app/models/concerns/iev_interfaces/task.rb2
-rw-r--r--app/models/import/base.rb2
-rw-r--r--app/models/import/gtfs.rb302
-rw-r--r--app/models/import/netex.rb38
-rw-r--r--app/models/import/workbench.rb21
-rw-r--r--app/models/referential.rb8
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/zip_service.rb45
-rw-r--r--app/views/dashboards/_dashboard.html.slim2
-rw-r--r--app/views/lines/show.html.slim2
-rw-r--r--app/views/shared/iev_interfaces/_messages.html.slim7
-rw-r--r--app/views/time_tables/index.html.slim6
-rw-r--r--app/workers/gtfs_import_worker.rb7
-rw-r--r--app/workers/workbench_import_worker/object_state_updater.rb27
-rw-r--r--config/locales/import_messages.en.yml5
-rw-r--r--config/locales/import_messages.fr.yml7
-rw-r--r--config/locales/lines.fr.yml2
-rw-r--r--db/migrate/20180403100007_add_registration_number_indexes.rb7
-rw-r--r--lib/af83/decorator/link.rb10
-rw-r--r--lib/gtfs/time.rb28
-rw-r--r--lib/stif/netex_file.rb28
-rw-r--r--lib/tasks/ci.rake2
-rw-r--r--spec/controllers/routes_controller_spec.rb6
-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/lib/gtfs/time_spec.rb27
-rw-r--r--spec/models/chouette/journey_pattern_spec.rb4
-rw-r--r--spec/models/custom_field_spec.rb5
-rw-r--r--spec/models/import/gtfs_spec.rb283
-rw-r--r--spec/models/simple_json_exporter_spec.rb10
-rw-r--r--spec/services/zip_service_spec.rb58
56 files changed, 1953 insertions, 140 deletions
diff --git a/Gemfile b/Gemfile
index 2da189f2b..cca55be61 100644
--- a/Gemfile
+++ b/Gemfile
@@ -143,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 0c1243593..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)
@@ -420,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)
@@ -633,6 +637,7 @@ DEPENDENCIES
georuby-ext (= 0.0.5)
google-analytics-rails
gretel
+ gtfs
has_array_of!
htmlbeautifier
i18n-js
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/_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/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/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_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/route_decorator.rb b/app/decorators/route_decorator.rb
index 4a173cbb9..646bc1620 100644
--- a/app/decorators/route_decorator.rb
+++ b/app/decorators/route_decorator.rb
@@ -74,10 +74,11 @@ class RouteDecorator < AF83::Decorator
instance_decorator.action_link(
secondary: :show,
policy: :create_opposite,
- if: ->{h.has_feature?(:create_opposite_routes) && object.opposite_route.nil?}
+ 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],
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/routes/components/StopPoint.js b/app/javascript/routes/components/StopPoint.js
index 368ec8261..768d069c0 100644
--- a/app/javascript/routes/components/StopPoint.js
+++ b/app/javascript/routes/components/StopPoint.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import BSelect2 from './BSelect2'
import OlMap from './OlMap'
-import { defaultAttribute } from '../actions'
+import { defaultAttribute } from '../actions'
export default function StopPoint(props, {I18n}) {
return (
@@ -42,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/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/chouette/company.rb b/app/models/chouette/company.rb
index cb2266a3d..9d5737a6c 100644
--- a/app/models/chouette/company.rb
+++ b/app/models/chouette/company.rb
@@ -9,7 +9,7 @@ module Chouette
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/line.rb b/app/models/chouette/line.rb
index 51851fc2e..4b5d1a68d 100644
--- a/app/models/chouette/line.rb
+++ b/app/models/chouette/line.rb
@@ -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
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index 9f28b7ee6..4ddc7403b 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -33,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
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 017f496a8..46fc8e73d 100644
--- a/app/models/concerns/custom_fields_support.rb
+++ b/app/models/concerns/custom_fields_support.rb
@@ -11,6 +11,10 @@ module CustomFieldsSupport
fields
end
+ 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
@@ -33,6 +37,7 @@ module CustomFieldsSupport
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|
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/import/base.rb b/app/models/import/base.rb
index 82494b1dc..f98e359d4 100644
--- a/app/models/import/base.rb
+++ b/app/models/import/base.rb
@@ -41,7 +41,7 @@ class Import::Base < ApplicationModel
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/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/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/referential.rb b/app/models/referential.rb
index 3304108d0..1794126a2 100644
--- a/app/models/referential.rb
+++ b/app/models/referential.rb
@@ -168,6 +168,10 @@ class Referential < ApplicationModel
Chouette::TimeTable.all
end
+ def time_table_dates
+ Chouette::TimeTableDate.all
+ end
+
def timebands
Chouette::Timeband.all
end
@@ -184,6 +188,10 @@ class Referential < ApplicationModel
Chouette::VehicleJourneyFrequency.all
end
+ def vehicle_journey_at_stops
+ Chouette::VehicleJourneyAtStop.all
+ end
+
def routing_constraint_zones
Chouette::RoutingConstraintZone.all
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 29148d9e9..ba166b06f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -9,7 +9,7 @@ class User < ApplicationModel
: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
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/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim
index e1be3df4a..466695b5a 100644
--- a/app/views/dashboards/_dashboard.html.slim
+++ b/app/views/dashboards/_dashboard.html.slim
@@ -6,7 +6,7 @@
h3.panel-title.with_actions
div
= link_to t('dashboards.workbench.title', organisation: workbench.organisation.name), workbench_path(workbench)
- span.badge.ml-xs = workbench.referentials.count if workbench.referentials.present?
+ 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')
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/shared/iev_interfaces/_messages.html.slim b/app/views/shared/iev_interfaces/_messages.html.slim
index 14157a88d..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.resource_attributes
+ - if message.resource_attributes.present?
pre
= JSON.pretty_generate message.resource_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/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/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/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/lines.fr.yml b/config/locales/lines.fr.yml
index 058238710..6f4a2e9bf 100644
--- a/config/locales/lines.fr.yml
+++ b/config/locales/lines.fr.yml
@@ -40,7 +40,7 @@ fr:
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/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/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/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 d025a9bf1..cb9ed77e7 100644
--- a/lib/tasks/ci.rake
+++ b/lib/tasks/ci.rake
@@ -79,7 +79,7 @@ namespace :ci do
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")
+ # cp "config/database.yml.orig", "config/database.yml" if File.exists?("config/database.yml.orig")
end
end
diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb
index b7cb66b46..59020914d 100644
--- a/spec/controllers/routes_controller_spec.rb
+++ b/spec/controllers/routes_controller_spec.rb
@@ -93,6 +93,9 @@ RSpec.describe RoutesController, type: :controller do
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,
@@ -106,6 +109,9 @@ RSpec.describe RoutesController, type: :controller do
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
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/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/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb
index 078e3c1f1..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
-
+
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/custom_field_spec.rb b/spec/models/custom_field_spec.rb
index 1dfe6d33c..ce6ce9fa5 100644
--- a/spec/models/custom_field_spec.rb
+++ b/spec/models/custom_field_spec.rb
@@ -47,11 +47,14 @@ RSpec.describe CustomField, type: :model do
let!(:field){ [create(:custom_field, code: :energy, field_type: 'list', options: {list_values: %w(foo bar baz)})] }
let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "1"} }
it "should cast the value" do
- p vj.custom_fields
expect(vj.custom_fields[:energy].value).to eq 1
expect(vj.custom_fields[:energy].display_value).to eq "bar"
end
+ it "should 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,
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/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/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