aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock5
-rw-r--r--app/assets/javascripts/main_menu.coffee1
-rw-r--r--app/assets/stylesheets/components/_color_selector.sass21
-rw-r--r--app/assets/stylesheets/typography/_sboiv.sass2
-rw-r--r--app/controllers/purchase_windows_controller.rb75
-rw-r--r--app/decorators/purchase_window_decorator.rb34
-rw-r--r--app/decorators/referential_decorator.rb7
-rw-r--r--app/helpers/table_builder_helper/url.rb2
-rw-r--r--app/models/calendar.rb151
-rw-r--r--app/models/chouette/purchase_window.rb31
-rw-r--r--app/models/concerns/date_support.rb80
-rw-r--r--app/models/concerns/period_support.rb80
-rw-r--r--app/models/referential.rb4
-rw-r--r--app/policies/purchase_window_policy.rb20
-rw-r--r--app/views/compliance_control_sets/index.html.slim2
-rw-r--r--app/views/dashboards/_dashboard.html.slim29
-rw-r--r--app/views/purchase_windows/_date_value_fields.html.slim13
-rw-r--r--app/views/purchase_windows/_filters.html.slim15
-rw-r--r--app/views/purchase_windows/_form.html.slim37
-rw-r--r--app/views/purchase_windows/_period_fields.html.slim15
-rw-r--r--app/views/purchase_windows/edit.html.slim7
-rw-r--r--app/views/purchase_windows/index.html.slim45
-rw-r--r--app/views/purchase_windows/new.html.slim6
-rw-r--r--app/views/purchase_windows/show.html.slim20
-rw-r--r--app/views/referentials/_filters.html.slim4
-rw-r--r--app/views/vehicle_journeys/index.html.slim6
-rw-r--r--config/breadcrumbs.rb10
-rw-r--r--config/initializers/apartment_null_db.rb25
-rw-r--r--config/locales/calendars.en.yml2
-rw-r--r--config/locales/calendars.fr.yml4
-rw-r--r--config/locales/compliance_controls.en.yml2
-rw-r--r--config/locales/compliance_controls.fr.yml2
-rw-r--r--config/locales/dashboard.en.yml16
-rw-r--r--config/locales/dashboard.fr.yml12
-rw-r--r--config/locales/purchase_windows.en.yml78
-rw-r--r--config/locales/purchase_windows.fr.yml79
-rw-r--r--config/locales/routes.en.yml1
-rw-r--r--config/locales/routes.fr.yml1
-rw-r--r--config/routes.rb2
-rw-r--r--db/migrate/20171214131755_create_business_calendars.rb14
-rw-r--r--db/migrate/20171215144543_rename_business_calendars_to_purchase_windows.rb5
-rw-r--r--db/migrate/20171215145023_update_purchase_windows_attributes.rb13
-rw-r--r--db/schema.rb72
-rw-r--r--spec/factories/chouette_purchase_windows.rb12
-rw-r--r--spec/factories/chouette_routes.rb7
-rw-r--r--spec/features/purchase_windows_permission_spec.rb59
-rw-r--r--spec/models/chouette/purchase_window_spec.rb27
-rw-r--r--spec/policies/purchase_window_policy_spec.rb15
-rw-r--r--spec/support/integration_spec_helper.rb6
-rw-r--r--spec/views/vehicle_journeys/index.html.slim_spec.rb30
51 files changed, 990 insertions, 217 deletions
diff --git a/Gemfile b/Gemfile
index 21f534c8b..898293ac5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -65,6 +65,7 @@ end
gem 'activerecord-postgis-adapter', "~> 3.0.0"
gem 'polylines'
+gem 'activerecord-nulldb-adapter', require: false
# Codifligne API
gem 'codifligne', af83: 'stif-codifline-api'
diff --git a/Gemfile.lock b/Gemfile.lock
index f9682dff1..27d3c18eb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -75,6 +75,8 @@ GEM
activemodel (= 4.2.8)
activesupport (= 4.2.8)
arel (~> 6.0)
+ activerecord-nulldb-adapter (0.3.7)
+ activerecord (>= 2.0.0)
activerecord-postgis-adapter (3.0.0)
activerecord (~> 4.2)
rgeo-activerecord (~> 4.0)
@@ -566,6 +568,7 @@ DEPENDENCIES
SyslogLogger
aasm
active_attr
+ activerecord-nulldb-adapter
activerecord-postgis-adapter (~> 3.0.0)
acts-as-taggable-on (~> 4.0.0)
acts_as_list (~> 0.6.0)
@@ -683,4 +686,4 @@ DEPENDENCIES
will_paginate-bootstrap
BUNDLED WITH
- 1.15.4
+ 1.16.0
diff --git a/app/assets/javascripts/main_menu.coffee b/app/assets/javascripts/main_menu.coffee
index a12c47576..1c39be736 100644
--- a/app/assets/javascripts/main_menu.coffee
+++ b/app/assets/javascripts/main_menu.coffee
@@ -24,6 +24,7 @@ $ ->
limit = 51
if $(window).scrollTop() >= limit
+ data = ""
if ($('.page-action .small').length > 0)
data = $('.page-action .small')[0].innerHTML
diff --git a/app/assets/stylesheets/components/_color_selector.sass b/app/assets/stylesheets/components/_color_selector.sass
new file mode 100644
index 000000000..07bfa0c80
--- /dev/null
+++ b/app/assets/stylesheets/components/_color_selector.sass
@@ -0,0 +1,21 @@
+select.color_selector
+ option[value='#9B9B9B']
+ background-color: #9B9B9B
+ option[value='#FFA070']
+ background-color: #FFA070
+ option[value='#C67300']
+ background-color: #C67300
+ option[value='#7F551B']
+ background-color: #7F551B
+ option[value='#41CCE3']
+ background-color: #41CCE3
+ option[value='#09B09C']
+ background-color: #09B09C
+ option[value='#3655D7']
+ background-color: #3655D7
+ option[value='#6321A0']
+ background-color: #6321A0
+ option[value='#E796C6']
+ background-color: #E796C6
+ option[value='#DD2DAA']
+ background-color: #DD2DAA \ No newline at end of file
diff --git a/app/assets/stylesheets/typography/_sboiv.sass b/app/assets/stylesheets/typography/_sboiv.sass
index f694306c4..0ed2890fa 100644
--- a/app/assets/stylesheets/typography/_sboiv.sass
+++ b/app/assets/stylesheets/typography/_sboiv.sass
@@ -89,7 +89,7 @@
.sb-OAS:before
content: '\e90f'
-.sb-calendar:before
+.sb-calendar:before, .sb-purchase_window:before
content: '\e910'
.sb-journey_pattern:before
diff --git a/app/controllers/purchase_windows_controller.rb b/app/controllers/purchase_windows_controller.rb
new file mode 100644
index 000000000..04b5736bb
--- /dev/null
+++ b/app/controllers/purchase_windows_controller.rb
@@ -0,0 +1,75 @@
+class PurchaseWindowsController < ChouetteController
+ include ReferentialSupport
+ include RansackDateFilter
+ include PolicyChecker
+ before_action :ransack_contains_date, only: [:index]
+ defaults :resource_class => Chouette::PurchaseWindow, collection_name: 'purchase_windows', instance_name: 'purchase_window'
+ belongs_to :referential
+
+ requires_feature :purchase_windows
+
+ def index
+ index! do
+ @purchase_windows = decorate_purchase_windows(@purchase_windows)
+ end
+ end
+
+ def show
+ show! do
+ @purchase_window = @purchase_window.decorate(context: {
+ referential: @referential
+ })
+ end
+ end
+
+ protected
+
+ def create_resource(purchase_window)
+ purchase_window.referential = @referential
+ super
+ end
+
+ private
+
+ def purchase_window_params
+ params.require(:purchase_window).permit(:id, :name, :color, :referential_id, periods_attributes: [:id, :begin, :end, :_destroy])
+ end
+
+ def decorate_purchase_windows(purchase_windows)
+ ModelDecorator.decorate(
+ purchase_windows,
+ with: PurchaseWindowDecorator,
+ context: {
+ referential: @referential
+ }
+ )
+ end
+
+ def sort_column
+ Chouette::PurchaseWindow.column_names.include?(params[:sort]) ? params[:sort] : 'name'
+ end
+
+ def sort_direction
+ %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
+ end
+
+ def collection
+ return @purchase_windows if @purchase_windows
+ @q = Chouette::PurchaseWindow.ransack(params[:q])
+
+ purchase_windows = @q.result
+ purchase_windows = purchase_windows.order(sort_column + ' ' + sort_direction) if sort_column && sort_direction
+ @purchase_windows = purchase_windows.paginate(page: params[:page])
+ end
+
+ def ransack_contains_date
+ date =[]
+ if 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)
+ end
+ params[:q]['contains_date'] = @date = Date.new(*date) rescue nil
+ end
+ end
+end
diff --git a/app/decorators/purchase_window_decorator.rb b/app/decorators/purchase_window_decorator.rb
new file mode 100644
index 000000000..13bc4d666
--- /dev/null
+++ b/app/decorators/purchase_window_decorator.rb
@@ -0,0 +1,34 @@
+class PurchaseWindowDecorator < Draper::Decorator
+ decorates Chouette::PurchaseWindow
+ delegate_all
+
+ def action_links
+ policy = h.policy(object)
+ links = []
+
+ if policy.update?
+ links << Link.new(
+ content: I18n.t('actions.edit'),
+ href: h.edit_referential_purchase_window_path(context[:referential].id, object)
+ )
+ end
+
+ if policy.destroy?
+ links << Link.new(
+ content: I18n.t('actions.destroy'),
+ href: h.referential_purchase_window_path(context[:referential].id, object),
+ method: :delete,
+ data: { confirm: h.t('purchase_window.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+
+ def bounding_dates
+ unless object.date_ranges.empty?
+ object.date_ranges.map(&:min).min..object.date_ranges.map(&:max).max
+ end
+ end
+
+end
diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb
index 0863b7f4b..d75ad1050 100644
--- a/app/decorators/referential_decorator.rb
+++ b/app/decorators/referential_decorator.rb
@@ -12,6 +12,13 @@ class ReferentialDecorator < Draper::Decorator
)
end
+ if has_feature?(:purchase_windows)
+ links << Link.new(
+ content: h.t('purchase_windows.index.title'),
+ href: h.referential_purchase_windows_path(object)
+ )
+ end
+
links << Link.new(
content: h.t('time_tables.index.title'),
href: h.referential_time_tables_path(object)
diff --git a/app/helpers/table_builder_helper/url.rb b/app/helpers/table_builder_helper/url.rb
index a53ac5620..28f1ade76 100644
--- a/app/helpers/table_builder_helper/url.rb
+++ b/app/helpers/table_builder_helper/url.rb
@@ -10,7 +10,7 @@ module TableBuilderHelper
polymorph_url << item.route.line if item.is_a?(Chouette::RoutingConstraintZone)
polymorph_url << item if item.respond_to? :line_referential
polymorph_url << item.stop_area if item.respond_to? :stop_area
- polymorph_url << item if item.respond_to?(:stop_points) || item.is_a?(Chouette::TimeTable)
+ polymorph_url << item if item.respond_to?(:stop_points) || item.is_a?(Chouette::TimeTable) || item.is_a?(Chouette::PurchaseWindow)
elsif item.respond_to? :referential
if item.respond_to? :workbench
polymorph_url << item.workbench
diff --git a/app/models/calendar.rb b/app/models/calendar.rb
index b2e73929f..34ed51374 100644
--- a/app/models/calendar.rb
+++ b/app/models/calendar.rb
@@ -3,22 +3,19 @@ require_relative 'calendar/date_value'
require_relative 'calendar/period'
class Calendar < ActiveRecord::Base
+ include DateSupport
+ include PeriodSupport
+
has_paper_trail
belongs_to :organisation
- has_many :time_tables
validates_presence_of :name, :short_name, :organisation
validates_uniqueness_of :short_name
- after_initialize :init_dates_and_date_ranges
+ has_many :time_tables
scope :contains_date, ->(date) { where('date ? = any (dates) OR date ? <@ any (date_ranges)', date, date) }
- def init_dates_and_date_ranges
- self.dates ||= []
- self.date_ranges ||= []
- end
-
def self.ransackable_scopes(auth_object = nil)
[:contains_date]
end
@@ -35,144 +32,4 @@ class Calendar < ActiveRecord::Base
end
end
-
- ### Calendar::Period
- # Required by coocon
- def build_period
- Calendar::Period.new
- end
-
- def periods
- @periods ||= init_periods
- end
-
- def init_periods
- (date_ranges || [])
- .each_with_index
- .map( &Calendar::Period.method(:from_range) )
- end
- private :init_periods
-
- validate :validate_periods
-
- def validate_periods
- periods_are_valid = periods.all?(&:valid?)
-
- periods.each do |period|
- if period.intersect?(periods)
- period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods'))
- periods_are_valid = false
- end
- end
-
- unless periods_are_valid
- errors.add(:periods, :invalid)
- end
- end
-
- def flatten_date_array attributes, key
- date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i }
- Date.new(*date_int)
- end
-
- def periods_attributes=(attributes = {})
- @periods = []
- attributes.each do |index, period_attribute|
- # Convert date_select to date
- ['begin', 'end'].map do |attr|
- period_attribute[attr] = flatten_date_array(period_attribute, attr)
- end
- period = Calendar::Period.new(period_attribute.merge(id: index))
- @periods << period unless period.marked_for_destruction?
- end
-
- date_ranges_will_change!
- end
-
- before_validation :fill_date_ranges
-
- def fill_date_ranges
- if @periods
- self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin)
- end
- end
-
- after_save :clear_periods
-
- def clear_periods
- @periods = nil
- end
-
- private :clear_periods
-
- ### Calendar::DateValue
-
- # Required by coocon
- def build_date_value
- Calendar::DateValue.new
- end
-
- def date_values
- @date_values ||= init_date_values
- end
-
- def init_date_values
- if dates
- dates.each_with_index.map { |d, index| Calendar::DateValue.from_date(index, d) }
- else
- []
- end
- end
- private :init_date_values
-
- validate :validate_date_values
-
- def validate_date_values
- date_values_are_valid = date_values.all?(&:valid?)
-
- date_values.each do |date_value|
- if date_values.count { |d| d.value == date_value.value } > 1
- date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates'))
- date_values_are_valid = false
- end
- date_ranges.each do |date_range|
- if date_range.cover? date_value.value
- date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges'))
- date_values_are_valid = false
- end
- end
- end
-
- unless date_values_are_valid
- errors.add(:date_values, :invalid)
- end
- end
-
- def date_values_attributes=(attributes = {})
- @date_values = []
- attributes.each do |index, date_value_attribute|
- date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value')
- date_value = Calendar::DateValue.new(date_value_attribute.merge(id: index))
- @date_values << date_value unless date_value.marked_for_destruction?
- end
-
- dates_will_change!
- end
-
- before_validation :fill_dates
-
- def fill_dates
- if @date_values
- self.dates = @date_values.map(&:value).compact.sort
- end
- end
-
- after_save :clear_date_values
-
- def clear_date_values
- @date_values = nil
- end
-
- private :clear_date_values
-
end
diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb
new file mode 100644
index 000000000..5368d790a
--- /dev/null
+++ b/app/models/chouette/purchase_window.rb
@@ -0,0 +1,31 @@
+require 'range_ext'
+require_relative '../calendar/period'
+
+module Chouette
+ class PurchaseWindow < Chouette::TridentActiveRecord
+ # include ChecksumSupport
+ include ObjectidSupport
+ include PeriodSupport
+ extend Enumerize
+ enumerize :color, in: %w(#9B9B9B #FFA070 #C67300 #7F551B #41CCE3 #09B09C #3655D7 #6321A0 #E796C6 #DD2DAA)
+
+ has_paper_trail
+ belongs_to :referential
+
+ validates_presence_of :name, :referential
+
+ scope :contains_date, ->(date) { where('date ? <@ any (date_ranges)', date) }
+
+ def self.ransackable_scopes(auth_object = nil)
+ [:contains_date]
+ end
+
+ def local_id
+ "IBOO-#{self.referential.id}-#{self.id}"
+ end
+
+ # def checksum_attributes
+ # end
+
+ end
+end \ No newline at end of file
diff --git a/app/models/concerns/date_support.rb b/app/models/concerns/date_support.rb
new file mode 100644
index 000000000..fbfe19af1
--- /dev/null
+++ b/app/models/concerns/date_support.rb
@@ -0,0 +1,80 @@
+module DateSupport
+ extend ActiveSupport::Concern
+
+ included do
+ after_initialize :init_dates
+
+ def init_dates
+ self.dates ||= []
+ end
+
+ ### Calendar::DateValue
+ # Required by coocon
+ def build_date_value
+ Calendar::DateValue.new
+ end
+
+ def date_values
+ @date_values ||= init_date_values
+ end
+
+ def init_date_values
+ if dates
+ dates.each_with_index.map { |d, index| Calendar::DateValue.from_date(index, d) }
+ else
+ []
+ end
+ end
+ private :init_date_values
+
+ validate :validate_date_values
+
+ def validate_date_values
+ date_values_are_valid = date_values.all?(&:valid?)
+
+ date_values.each do |date_value|
+ if date_values.count { |d| d.value == date_value.value } > 1
+ date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates'))
+ date_values_are_valid = false
+ end
+ date_ranges.each do |date_range|
+ if date_range.cover? date_value.value
+ date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges'))
+ date_values_are_valid = false
+ end
+ end
+ end
+
+ unless date_values_are_valid
+ errors.add(:date_values, :invalid)
+ end
+ end
+
+ def date_values_attributes=(attributes = {})
+ @date_values = []
+ attributes.each do |index, date_value_attribute|
+ date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value')
+ date_value = Calendar::DateValue.new(date_value_attribute.merge(id: index))
+ @date_values << date_value unless date_value.marked_for_destruction?
+ end
+
+ dates_will_change!
+ end
+
+ before_validation :fill_dates
+
+ def fill_dates
+ if @date_values
+ self.dates = @date_values.map(&:value).compact.sort
+ end
+ end
+
+ after_save :clear_date_values
+
+ def clear_date_values
+ @date_values = nil
+ end
+
+ private :clear_date_values
+ end
+end \ No newline at end of file
diff --git a/app/models/concerns/period_support.rb b/app/models/concerns/period_support.rb
new file mode 100644
index 000000000..f512c4e89
--- /dev/null
+++ b/app/models/concerns/period_support.rb
@@ -0,0 +1,80 @@
+module PeriodSupport
+ extend ActiveSupport::Concern
+
+ included do
+ after_initialize :init_date_ranges
+
+ def init_date_ranges
+ self.date_ranges ||= []
+ end
+
+ ### Calendar::Period
+ # Required by coocon
+ def build_period
+ Calendar::Period.new
+ end
+
+ def periods
+ @periods ||= init_periods
+ end
+
+ def init_periods
+ (date_ranges || [])
+ .each_with_index
+ .map( &Calendar::Period.method(:from_range) )
+ end
+ private :init_periods
+
+ validate :validate_periods
+
+ def validate_periods
+ periods_are_valid = periods.all?(&:valid?)
+
+ periods.each do |period|
+ if period.intersect?(periods)
+ period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods'))
+ periods_are_valid = false
+ end
+ end
+
+ unless periods_are_valid
+ errors.add(:periods, :invalid)
+ end
+ end
+
+ def flatten_date_array attributes, key
+ date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i }
+ Date.new(*date_int)
+ end
+
+ def periods_attributes=(attributes = {})
+ @periods = []
+ attributes.each do |index, period_attribute|
+ # Convert date_select to date
+ ['begin', 'end'].map do |attr|
+ period_attribute[attr] = flatten_date_array(period_attribute, attr)
+ end
+ period = Calendar::Period.new(period_attribute.merge(id: index))
+ @periods << period unless period.marked_for_destruction?
+ end
+
+ date_ranges_will_change!
+ end
+
+ before_validation :fill_date_ranges
+
+ def fill_date_ranges
+ if @periods
+ self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin)
+ end
+ end
+
+ after_save :clear_periods
+
+ def clear_periods
+ @periods = nil
+ end
+
+ private :clear_periods
+ end
+end \ No newline at end of file
diff --git a/app/models/referential.rb b/app/models/referential.rb
index 851a33653..122af65a1 100644
--- a/app/models/referential.rb
+++ b/app/models/referential.rb
@@ -128,6 +128,10 @@ class Referential < ActiveRecord::Base
Chouette::RoutingConstraintZone.all
end
+ def purchase_windows
+ Chouette::PurchaseWindow.all
+ end
+
before_validation :define_default_attributes
def define_default_attributes
diff --git a/app/policies/purchase_window_policy.rb b/app/policies/purchase_window_policy.rb
new file mode 100644
index 000000000..75143a8bd
--- /dev/null
+++ b/app/policies/purchase_window_policy.rb
@@ -0,0 +1,20 @@
+class PurchaseWindowPolicy < ApplicationPolicy
+ class Scope < Scope
+ def resolve
+ scope
+ end
+ end
+
+ def create?
+ !archived? && organisation_match? && user.has_permission?('purchase_windows.create')
+ end
+
+ def update?
+ !archived? && organisation_match? && user.has_permission?('purchase_windows.update')
+ end
+
+ def destroy?
+ !archived? && organisation_match? && user.has_permission?('purchase_windows.destroy')
+ end
+
+end \ No newline at end of file
diff --git a/app/views/compliance_control_sets/index.html.slim b/app/views/compliance_control_sets/index.html.slim
index 2a5651280..28d2254bf 100644
--- a/app/views/compliance_control_sets/index.html.slim
+++ b/app/views/compliance_control_sets/index.html.slim
@@ -30,7 +30,7 @@
), \
TableBuilderHelper::Column.new( \
key: :control_numbers, \
- attribute: 'control_numbers' \
+ attribute: Proc.new {|n| n.compliance_controls.count }\
), \
TableBuilderHelper::Column.new( \
key: :updated_at, \
diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim
index 7d547bf4c..075b94ddc 100644
--- a/app/views/dashboards/_dashboard.html.slim
+++ b/app/views/dashboards/_dashboard.html.slim
@@ -31,21 +31,24 @@
= link_to calendar.name, calendar_path(calendar), class: 'list-group-item'
- else
.panel-body
- em.small.text-muted Aucun modèle de calendrier défini
+ em.small.text-muted
+ = t('dasboard.calendars.none')
.col-lg-6.col-md-6.col-sm-6.col-xs-12
.panel.panel-default
- .panel-heading
- h3.panel-title
- = "Référentiels d'arrêts"
- .list-group
- - @dashboard.current_organisation.stop_area_referentials.each do |referential|
- = link_to referential.name, stop_area_referential_stop_areas_path(referential), class: 'list-group-item'
+ - @dashboard.current_organisation.stop_area_referentials.each do |referential|
+ .panel-heading
+ h3.panel-title
+ = referential.name
+ .list-group
+ = link_to Chouette::StopArea.model_name.human.pluralize.capitalize, stop_area_referential_stop_areas_path(referential), class: 'list-group-item'
.panel.panel-default
- .panel-heading
- h3.panel-title
- = "Référentiels de lignes"
- .list-group
- - @dashboard.current_organisation.line_referentials.all.each do |referential|
- = link_to referential.name, line_referential_lines_path(referential), class: 'list-group-item'
+ - @dashboard.current_organisation.line_referentials.all.each do |referential|
+ .panel-heading
+ h3.panel-title
+ = referential.name
+ .list-group
+ = link_to Chouette::Line.model_name.human.pluralize.capitalize, line_referential_lines_path(referential), class: 'list-group-item'
+ = link_to Chouette::Company.model_name.human.pluralize.capitalize, line_referential_companies_path(referential), class: 'list-group-item'
+ = link_to "Réseaux", line_referential_networks_path(referential), class: 'list-group-item'
diff --git a/app/views/purchase_windows/_date_value_fields.html.slim b/app/views/purchase_windows/_date_value_fields.html.slim
new file mode 100644
index 000000000..7bde06a94
--- /dev/null
+++ b/app/views/purchase_windows/_date_value_fields.html.slim
@@ -0,0 +1,13 @@
+.nested-fields
+ - if f.object.errors.has_key? :base
+ .row
+ .col-lg-12
+ .alert.alert-danger
+ - f.object.errors[:base].each do |message|
+ p.small = message
+
+ .wrapper
+ div
+ = f.input :value, as: :date, label: false, wrapper_html: { class: 'date smart_date' }
+ div
+ = link_to_remove_association '', f, class: 'fa fa-trash', data: { confirm: 'Etes-vous sûr(e) ?' }, title: t('actions.delete')
diff --git a/app/views/purchase_windows/_filters.html.slim b/app/views/purchase_windows/_filters.html.slim
new file mode 100644
index 000000000..4d7c8ce26
--- /dev/null
+++ b/app/views/purchase_windows/_filters.html.slim
@@ -0,0 +1,15 @@
+= search_form_for @q, url: referential_purchase_windows_path, builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f|
+ .ffg-row
+ .input-group.search_bar
+ = f.search_field :name_cont, class: 'form-control', placeholder: t('purchase_windows.index.filter_placeholder')
+ span.input-group-btn
+ button.btn.btn-default#search_btn type='submit'
+ span.fa.fa-search
+
+ .form-group
+ = f.label Chouette::PurchaseWindow.human_attribute_name(:date), class: 'control-label'
+ = f.input :contains_date, as: :date, label: false, wrapper_html: { class: 'date smart_date' }, class: 'form-control', default: @date, include_blank: @date ? false : true
+
+ .actions
+ = link_to t('actions.erase'), referential_purchase_windows_path, class: 'btn btn-link'
+ = f.submit t('actions.filter'), id: 'purchase_window_filter_btn', class: 'btn btn-default'
diff --git a/app/views/purchase_windows/_form.html.slim b/app/views/purchase_windows/_form.html.slim
new file mode 100644
index 000000000..8821ecc8a
--- /dev/null
+++ b/app/views/purchase_windows/_form.html.slim
@@ -0,0 +1,37 @@
+= simple_form_for [@referential, @purchase_window], html: { class: 'form-horizontal', id: 'purchase_window_form' }, wrapper: :horizontal_form do |f|
+ .row
+ .col-lg-12
+ = f.input :name
+ // = f.input :color, as: :select, boolean_style: :inline, collection: Chouette::PurchaseWindow.color.values, input_html: {class: 'color_selector '}
+ div
+ .form-group
+ label.select.optional.col-sm-4.col-xs-5.control-label
+ = @purchase_window.class.human_attribute_name :color
+ div.col-sm-8.col-xs-7
+ span.fa.fa-circle style="color:#7F551B"
+
+ = f.input :color, as: :hidden, input_html: { value: '#7F551B' }
+
+ .separator
+
+ .row
+ .col-lg-12
+ .subform
+ .nested-head
+ .wrapper
+ div
+ .form-group
+ label.control-label
+ = t('simple_form.labels.purchase_window.ranges.begin')
+ div
+ .form-group
+ label.control-label
+ = t('simple_form.labels.purchase_window.ranges.end')
+ div
+
+ = f.simple_fields_for :periods do |period|
+ = render 'period_fields', f: period
+ .links.nested-linker
+ = link_to_add_association t('simple_form.labels.purchase_window.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary'
+
+ = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'purchase_window_form'
diff --git a/app/views/purchase_windows/_period_fields.html.slim b/app/views/purchase_windows/_period_fields.html.slim
new file mode 100644
index 000000000..95e204554
--- /dev/null
+++ b/app/views/purchase_windows/_period_fields.html.slim
@@ -0,0 +1,15 @@
+.nested-fields
+ - if f.object.errors.has_key? :base
+ .row
+ .col-lg-12
+ .alert.alert-danger
+ - f.object.errors[:base].each do |message|
+ p.small = message
+
+ .wrapper
+ div
+ = f.input :begin, as: :date, label: false, wrapper_html: { class: 'date smart_date' }
+ div
+ = f.input :end, as: :date, label: false, wrapper_html: { class: 'date smart_date' }
+ div
+ = link_to_remove_association '', f, class: 'fa fa-trash', data: { confirm: 'Etes-vous sûr(e) ?' }, title: t('actions.delete')
diff --git a/app/views/purchase_windows/edit.html.slim b/app/views/purchase_windows/edit.html.slim
new file mode 100644
index 000000000..6354db853
--- /dev/null
+++ b/app/views/purchase_windows/edit.html.slim
@@ -0,0 +1,7 @@
+- breadcrumb :purchase_window, @referential, @purchase_window
+- page_header_content_for @purchase_window
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = render 'form'
diff --git a/app/views/purchase_windows/index.html.slim b/app/views/purchase_windows/index.html.slim
new file mode 100644
index 000000000..04f9fb0a8
--- /dev/null
+++ b/app/views/purchase_windows/index.html.slim
@@ -0,0 +1,45 @@
+- breadcrumb :purchase_windows, @referential
+- content_for :page_header_actions do
+ - if policy(Chouette::PurchaseWindow).create?
+ = link_to(t('actions.add'), new_referential_purchase_window_path(@referential), class: 'btn btn-default')
+
+.page_content
+ .container-fluid
+ - if params[:q].present? or @purchase_windows.any?
+ .row
+ .col-lg-12
+ = render 'filters'
+
+ - if @purchase_windows.any?
+ .row
+ .col-lg-12
+ = table_builder_2 @purchase_windows,
+ [ \
+ TableBuilderHelper::Column.new( \
+ key: :name, \
+ attribute: 'name', \
+ link_to: lambda do |purchase_window| \
+ referential_purchase_window_path(purchase_window.referential, purchase_window) \
+ end \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :color, \
+ attribute: Proc.new { |tt| tt.color ? content_tag(:span, '', class: 'fa fa-circle', style: "color:#{tt.color}") : '-' }\
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :bounding_dates, \
+ attribute: Proc.new {|w| w.bounding_dates.nil? ? '-' : t('validity_range', debut: l(w.bounding_dates.begin, format: :short), end: l(w.bounding_dates.end, format: :short))}, \
+ sortable: false \
+ ) \
+ ],
+ links: [:show],
+ cls: 'table has-filter'
+
+ = new_pagination @purchase_windows, 'pull-right'
+
+ - unless @purchase_windows.any?
+ .row.mt-xs
+ .col-lg-12
+ = replacement_msg t('purchase_windows.search_no_results')
+
+= javascript_pack_tag 'date_filters'
diff --git a/app/views/purchase_windows/new.html.slim b/app/views/purchase_windows/new.html.slim
new file mode 100644
index 000000000..402084167
--- /dev/null
+++ b/app/views/purchase_windows/new.html.slim
@@ -0,0 +1,6 @@
+- breadcrumb :purchase_windows, @referential
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = render 'form'
diff --git a/app/views/purchase_windows/show.html.slim b/app/views/purchase_windows/show.html.slim
new file mode 100644
index 000000000..9f3abb267
--- /dev/null
+++ b/app/views/purchase_windows/show.html.slim
@@ -0,0 +1,20 @@
+- breadcrumb :purchase_window, @referential, @purchase_window
+- page_header_content_for @purchase_window
+- content_for :page_header_content do
+ .row.mb-sm
+ .col-lg-12.text-right
+ - @purchase_window.action_links.each do |link|
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ = link.content
+
+.page_content
+ .container-fluid
+ .row
+ .col-lg-6.col-md-6.col-sm-12.col-xs-12
+ = definition_list t('metadatas'),
+ { Chouette::PurchaseWindow.human_attribute_name(:name) => @purchase_window.try(:name),
+ 'Organisation' => @purchase_window.referential.organisation.name,
+ Chouette::PurchaseWindow.human_attribute_name(:date_ranges) => @purchase_window.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe }
diff --git a/app/views/referentials/_filters.html.slim b/app/views/referentials/_filters.html.slim
index c5b6042f0..93fa679df 100644
--- a/app/views/referentials/_filters.html.slim
+++ b/app/views/referentials/_filters.html.slim
@@ -12,11 +12,11 @@
= f.input :transport_mode_eq_any, collection: @referential.lines.pluck(:transport_mode).uniq.compact, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
.form-group.togglable
- = f.label Chouette::Line.human_attribute_name(:network), required: false, class: 'control-label'
+ = f.label t('activerecord.attributes.referential.networks'), required: false, class: 'control-label'
= f.input :network_id_eq_any, collection: LineReferential.first.networks.order('name').pluck(:id), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{LineReferential.first.networks.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
.form-group.togglable
- = f.label Chouette::Line.human_attribute_name(:company), required: false, class: 'control-label'
+ = f.label t('activerecord.attributes.referential.companies'), required: false, class: 'control-label'
= f.input :company_id_eq_any, collection: LineReferential.first.companies.order('name').pluck(:id), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{LineReferential.first.companies.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
.actions
diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim
index 52c1a9728..4ad9d524d 100644
--- a/app/views/vehicle_journeys/index.html.slim
+++ b/app/views/vehicle_journeys/index.html.slim
@@ -1,5 +1,11 @@
- breadcrumb :vehicle_journeys, @referential, @route
- content_for :page_header_title, t('vehicle_journeys.index.title', route: @route.name)
+- if @route.opposite_route.present?
+ - content_for :page_header_content do
+ .row.mb-sm
+ .col-lg-12.text-right
+ = link_to(t('routes.actions.opposite_route_timetable'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary')
+
.page_content
.container-fluid
diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb
index 9a748cb21..2cafc4419 100644
--- a/config/breadcrumbs.rb
+++ b/config/breadcrumbs.rb
@@ -167,6 +167,16 @@ crumb :line do |line|
parent :lines, line.line_referential
end
+crumb :purchase_windows do |referential|
+ link I18n.t('purchase_windows.index.title'), referential_purchase_windows_path(referential)
+ parent :referential, referential
+end
+
+crumb :purchase_window do |referential, purchase_window|
+ link breadcrumb_name(purchase_window), referential_purchase_window_path(referential, purchase_window)
+ parent :purchase_windows, referential
+end
+
crumb :calendars do
link I18n.t('calendars.index.title'), calendars_path
end
diff --git a/config/initializers/apartment_null_db.rb b/config/initializers/apartment_null_db.rb
new file mode 100644
index 000000000..438f1e58b
--- /dev/null
+++ b/config/initializers/apartment_null_db.rb
@@ -0,0 +1,25 @@
+if ENV['RAILS_DB_ADAPTER'] == 'nulldb'
+ require 'apartment/adapters/abstract_adapter'
+
+ module Apartment
+ module Tenant
+ def adapter
+ Thread.current[:apartment_adapter] ||= nulldb_adapter(config)
+ end
+
+ def self.nulldb_adapter(config)
+ adapter = Adapters::NulldbAdapter
+ adapter.new(config)
+ end
+ end
+
+ module Adapters
+ # Default adapter when not using Postgresql Schemas
+ class NulldbAdapter < AbstractAdapter
+ def initialize config
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/config/locales/calendars.en.yml b/config/locales/calendars.en.yml
index d3cc57677..a2110d053 100644
--- a/config/locales/calendars.en.yml
+++ b/config/locales/calendars.en.yml
@@ -41,6 +41,8 @@ en:
date: Date
new:
title: Add a new calendar
+ create:
+ title: Add a new calendar
edit:
title: Update calendar %{name}
show:
diff --git a/config/locales/calendars.fr.yml b/config/locales/calendars.fr.yml
index fc895bf89..88cb275ff 100644
--- a/config/locales/calendars.fr.yml
+++ b/config/locales/calendars.fr.yml
@@ -31,7 +31,7 @@ fr:
destroy_confirm: Etes vous sûr de supprimer cet calendrier ?
errors:
overlapped_periods: Une autre période chevauche cette période
- short_period: Une période doit être d'un duréé de deux jours minimum
+ short_period: "Une période doit être d'une durée de deux jours minimum"
index:
title: Calendriers
all: Tous
@@ -41,6 +41,8 @@ fr:
date: Date
new:
title: Ajouter un calendrier
+ create:
+ title: Ajouter un calendrier
edit:
title: Editer le calendrier %{name}
show:
diff --git a/config/locales/compliance_controls.en.yml b/config/locales/compliance_controls.en.yml
index f9d7d23d2..3392afab5 100644
--- a/config/locales/compliance_controls.en.yml
+++ b/config/locales/compliance_controls.en.yml
@@ -89,7 +89,7 @@ en:
vehicle_journey_control/waiting_time:
messages:
3_vehiclejourney_1: "On the following vehicle journey %{source_objectid}, the waiting time %{error_value} a this stop point %{target_0_label} (%{target_0_objectid}) is greater than the threshold (%{reference_value})"
- description: "The waiting time at a specific stop point cannot be too big"
+ description: "The waiting time, in minutes, at a specific stop point cannot be too big"
vehicle_journey_control/speed:
messages:
3_vehiclejourney_2_1: "On the following vehicle journey %{source_objectid}, the computed speed %{error_value} between the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) is greater than the threshold (%{reference_value})"
diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml
index b77b4e6d4..cde75aaf5 100644
--- a/config/locales/compliance_controls.fr.yml
+++ b/config/locales/compliance_controls.fr.yml
@@ -88,7 +88,7 @@ fr:
vehicle_journey_control/waiting_time:
messages:
3_vehiclejourney_1: "Sur la course %{source_objectid}, le temps d'attente %{error_value} à l'arrêt %{target_0_label} (%{target_0_objectid}) est supérieur au seuil toléré (%{reference_value})"
- description: "La durée d’attente à un arrêt ne doit pas être trop grande"
+ description: "La durée d’attente, en minutes, à un arrêt ne doit pas être trop grande"
vehicle_journey_control/speed:
messages:
3_vehiclejourney_2_1: "Sur la course %{source_objectid}, la vitesse calculée %{error_value} entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) est supérieur au seuil toléré (%{reference_value})"
diff --git a/config/locales/dashboard.en.yml b/config/locales/dashboard.en.yml
new file mode 100644
index 000000000..8d46ff7aa
--- /dev/null
+++ b/config/locales/dashboard.en.yml
@@ -0,0 +1,16 @@
+en:
+ dashboards:
+ show:
+ title: "Dashboard %{organisation}"
+ calendars:
+ title: Calendars
+ none: No calendar created
+ purchase_windows:
+ title: Purchase windows
+ none: No purchase window created
+ line_referentials:
+ title: Line referential
+ none: No line referential created
+ stop_area_referentials:
+ title: Stop area referential
+ none: No stop area referential created
diff --git a/config/locales/dashboard.fr.yml b/config/locales/dashboard.fr.yml
index fffb36cd1..d0aa36d61 100644
--- a/config/locales/dashboard.fr.yml
+++ b/config/locales/dashboard.fr.yml
@@ -2,3 +2,15 @@ fr:
dashboards:
show:
title: "Tableau de bord %{organisation}"
+ calendars:
+ title: Modèles de calendrier
+ none: Aucun calendrier défini
+ purchase_windows:
+ title: Calendriers commerciaux
+ none: Aucun calendrier commercial défini
+ line_referentials:
+ title: Référentiels de lignes
+ none: Aucun référentiels de lignes défini
+ stop_area_referentials:
+ title: Référentiels d'arrêts
+ none: Aucun référentiels d'arrêts défini
diff --git a/config/locales/purchase_windows.en.yml b/config/locales/purchase_windows.en.yml
new file mode 100644
index 000000000..f8f3eb83d
--- /dev/null
+++ b/config/locales/purchase_windows.en.yml
@@ -0,0 +1,78 @@
+en:
+ purchase_windows:
+ search_no_results: 'No purchase window matching your query'
+ days:
+ monday: M
+ tuesday: Tu
+ wednesday: W
+ thursday: Th
+ friday: F
+ saturday: Sa
+ sunday: Su
+ months:
+ 1: January
+ 2: February
+ 3: March
+ 4: April
+ 5: May
+ 6: June
+ 7: July
+ 8: August
+ 9: September
+ 10: October
+ 11: November
+ 12: December
+ actions:
+ new: Add a new purchase window
+ edit: Edit this purchase window
+ destroy: Remove this purchase window
+ destroy_confirm: Are you sure you want destroy this purchase window?
+ errors:
+ overlapped_periods: Another period is overlapped with this period
+ short_period: A period needs to last at least two days
+ index:
+ title: purchase windows
+ all: All
+ shared: Shared
+ not_shared: Not shared
+ search_no_results: No purchase window matching your query
+ date: Date
+ filter_placeholder: Put the name of a purchase window...
+ create:
+ title: Add a new purchase window
+ new:
+ title: Add a new purchase window
+ edit:
+ title: Update purchase window %{name}
+ show:
+ title: purchase window %{name}
+ simple_form:
+ labels:
+ purchase_windows:
+ date_value: Date
+ add_a_date: Add a date
+ add_a_date_range: Add a date range
+ ranges:
+ begin: Beginning
+ end: End
+ activerecord:
+ models:
+ purchase_window:
+ zero: purchase window
+ one: purchase window
+ other: purchase windows
+ attributes:
+ purchase_windows:
+ name: Name
+ date_ranges: Date ranges
+ referential: Referential
+ color: Associated Color
+ bounding_dates: Bounding Dates
+ errors:
+ models:
+ purchase_windows:
+ attributes:
+ dates:
+ date_in_date_ranges: A date can not be in Dates and in Date ranges.
+ date_in_dates: A date can appear only once in the list of dates.
+ illegal_date: The date %{date} does not exist.
diff --git a/config/locales/purchase_windows.fr.yml b/config/locales/purchase_windows.fr.yml
new file mode 100644
index 000000000..589546c32
--- /dev/null
+++ b/config/locales/purchase_windows.fr.yml
@@ -0,0 +1,79 @@
+fr:
+ purchase_windows:
+ search_no_results: 'Aucun calendrier commercial ne correspond à votre recherche'
+ days:
+ monday: L
+ tuesday: Ma
+ wednesday: Me
+ thursday: J
+ friday: V
+ saturday: S
+ sunday: D
+ months:
+ 1: Janvier
+ 2: Février
+ 3: Mars
+ 4: Avril
+ 5: Mai
+ 6: Juin
+ 7: Juillet
+ 8: Août
+ 9: Septembre
+ 10: Octobre
+ 11: Novembre
+ 12: Décembre
+ actions:
+ new: Créer
+ edit: Editer
+ destroy: Supprimer
+ destroy_confirm: Etes vous sûr de supprimer cet calendrier commercial ?
+ errors:
+ overlapped_periods: Une autre période chevauche cette période
+ short_period: "Une période doit être d'une durée de deux jours minimum"
+ index:
+ title: Calendriers commerciaux
+ all: Tous
+ shared: Partagées
+ not_shared: Non partagées
+ search_no_results: Aucun calendrier commercial ne correspond à votre recherche
+ date: Date
+ filter_placeholder: Indiquez un nom de calendrier commercial...
+ new:
+ title: Ajouter un calendrier commercial
+ create:
+ title: Ajouter un calendrier commercial
+ edit:
+ title: Editer le calendrier commercial %{name}
+ show:
+ title: Calendrier commercial %{name}
+ simple_form:
+ labels:
+ purchase_window:
+ date_value: Date
+ add_a_date: Ajouter une date
+ add_a_date_range: Ajouter un intervalle de dates
+ ranges:
+ begin: Début
+ end: Fin
+ activerecord:
+ models:
+ purchase_window:
+ zero: "calendrier commercial"
+ one: "calendrier commercial"
+ other: "calendriers commerciaux"
+ attributes:
+ purchase_window:
+ name: Nom
+ short_name: Nom court
+ date_ranges: Intervalles de dates
+ referential: Jeu de données
+ color: Couleur associée
+ bounding_dates: Période englobante
+ errors:
+ models:
+ purchase_window:
+ attributes:
+ dates:
+ date_in_date_ranges: Une même date ne peut pas être incluse à la fois dans la liste et dans les intervalles de dates.
+ date_in_dates: Une même date ne peut pas être incluse plusieurs fois dans la liste.
+ illegal_date: La date %{date} n'existe pas.
diff --git a/config/locales/routes.en.yml b/config/locales/routes.en.yml
index d82ba98dd..7b82e788b 100644
--- a/config/locales/routes.en.yml
+++ b/config/locales/routes.en.yml
@@ -13,6 +13,7 @@ en:
export_hub_all: "Export HUB routes"
add_stop_point: "Add stop point"
new_stop_point: "Create new stop"
+ opposite_route_timetable: "Timetable back"
new:
title: "Add a new route"
edit:
diff --git a/config/locales/routes.fr.yml b/config/locales/routes.fr.yml
index 457345ae8..1d151e60b 100644
--- a/config/locales/routes.fr.yml
+++ b/config/locales/routes.fr.yml
@@ -13,6 +13,7 @@ fr:
export_hub_all: "Export HUB des itinéraires"
add_stop_point: "Ajouter un arrêt"
new_stop_point: "Créer un arrêt pour l'ajouter"
+ opposite_route_timetable: "Horaires retour"
new:
title: "Ajouter un itinéraire"
edit:
diff --git a/config/routes.rb b/config/routes.rb
index 6668aa402..d097d2d71 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -173,6 +173,8 @@ ChouetteIhm::Application.routes.draw do
resources :companies, controller: "referential_companies"
+ resources :purchase_windows
+
resources :time_tables do
collection do
get :tags
diff --git a/db/migrate/20171214131755_create_business_calendars.rb b/db/migrate/20171214131755_create_business_calendars.rb
new file mode 100644
index 000000000..aa7c1ab12
--- /dev/null
+++ b/db/migrate/20171214131755_create_business_calendars.rb
@@ -0,0 +1,14 @@
+class CreateBusinessCalendars < ActiveRecord::Migration
+ def change
+ create_table :business_calendars do |t|
+ t.string :name
+ t.string :short_name
+ t.string :color
+ t.daterange :date_ranges, array: true
+ t.date :dates, array: true
+ t.belongs_to :organisation, index: true
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20171215144543_rename_business_calendars_to_purchase_windows.rb b/db/migrate/20171215144543_rename_business_calendars_to_purchase_windows.rb
new file mode 100644
index 000000000..d4467d6a7
--- /dev/null
+++ b/db/migrate/20171215144543_rename_business_calendars_to_purchase_windows.rb
@@ -0,0 +1,5 @@
+class RenameBusinessCalendarsToPurchaseWindows < ActiveRecord::Migration
+ def change
+ rename_table :business_calendars, :purchase_windows
+ end
+end
diff --git a/db/migrate/20171215145023_update_purchase_windows_attributes.rb b/db/migrate/20171215145023_update_purchase_windows_attributes.rb
new file mode 100644
index 000000000..48dfb15bc
--- /dev/null
+++ b/db/migrate/20171215145023_update_purchase_windows_attributes.rb
@@ -0,0 +1,13 @@
+class UpdatePurchaseWindowsAttributes < ActiveRecord::Migration
+ def change
+ add_column :purchase_windows, :objectid, :string
+ add_column :purchase_windows, :checksum, :string
+ add_column :purchase_windows, :checksum_source, :text
+
+ remove_column :purchase_windows, :short_name, :string
+ remove_column :purchase_windows, :dates, :date
+ remove_column :purchase_windows, :organisation_id, :integer
+
+ add_reference :purchase_windows, :referential, type: :bigint, index: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f5532ec57..81966575b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -281,22 +281,6 @@ ActiveRecord::Schema.define(version: 20171220164059) do
add_index "connection_links", ["objectid"], name: "connection_links_objectid_key", unique: true, using: :btree
- create_table "delayed_jobs", id: :bigserial, force: :cascade do |t|
- t.integer "priority", default: 0
- t.integer "attempts", default: 0
- t.text "handler"
- t.text "last_error"
- t.datetime "run_at"
- t.datetime "locked_at"
- t.datetime "failed_at"
- t.string "locked_by", limit: 255
- t.string "queue", limit: 255
- t.datetime "created_at"
- t.datetime "updated_at"
- end
-
- add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
-
create_table "exports", id: :bigserial, force: :cascade do |t|
t.integer "referential_id", limit: 8
t.string "status"
@@ -416,11 +400,11 @@ ActiveRecord::Schema.define(version: 20171220164059) do
t.datetime "started_at"
t.datetime "ended_at"
t.string "token_download"
- t.string "type", limit: 255
+ t.string "type"
t.integer "parent_id", limit: 8
t.string "parent_type"
- t.integer "current_step", default: 0
- t.integer "total_steps", default: 0
+ t.integer "current_step", default: 0
+ t.integer "total_steps", default: 0
t.datetime "notified_parent_at"
t.string "creator"
end
@@ -538,19 +522,6 @@ ActiveRecord::Schema.define(version: 20171220164059) do
add_index "lines", ["registration_number"], name: "lines_registration_number_key", using: :btree
add_index "lines", ["secondary_company_ids"], name: "index_lines_on_secondary_company_ids", using: :gin
- create_table "merges", id: :bigserial, force: :cascade do |t|
- t.integer "workbench_id", limit: 8
- t.integer "referential_ids", limit: 8, array: true
- t.string "creator"
- t.string "status"
- t.datetime "started_at"
- t.datetime "ended_at"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
-
- add_index "merges", ["workbench_id"], name: "index_merges_on_workbench_id", using: :btree
-
create_table "networks", id: :bigserial, force: :cascade do |t|
t.string "objectid", null: false
t.integer "object_version", limit: 8
@@ -572,11 +543,6 @@ ActiveRecord::Schema.define(version: 20171220164059) do
add_index "networks", ["objectid"], name: "networks_objectid_key", unique: true, using: :btree
add_index "networks", ["registration_number"], name: "networks_registration_number_key", using: :btree
- create_table "object_id_factories", id: :bigserial, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
-
create_table "organisations", id: :bigserial, force: :cascade do |t|
t.string "name"
t.datetime "created_at"
@@ -606,6 +572,20 @@ ActiveRecord::Schema.define(version: 20171220164059) do
add_index "pt_links", ["objectid"], name: "pt_links_objectid_key", unique: true, using: :btree
+ create_table "purchase_windows", id: :bigserial, force: :cascade do |t|
+ t.string "name"
+ t.string "color"
+ t.daterange "date_ranges", array: true
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "objectid"
+ t.string "checksum"
+ t.text "checksum_source"
+ t.integer "referential_id", limit: 8
+ end
+
+ add_index "purchase_windows", ["referential_id"], name: "index_purchase_windows_on_referential_id", using: :btree
+
create_table "referential_clonings", id: :bigserial, force: :cascade do |t|
t.string "status"
t.datetime "started_at"
@@ -744,7 +724,7 @@ ActiveRecord::Schema.define(version: 20171220164059) do
create_table "stop_areas", id: :bigserial, force: :cascade do |t|
t.integer "parent_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", null: false
t.integer "object_version", limit: 8
t.string "name"
t.string "comment"
@@ -752,8 +732,8 @@ ActiveRecord::Schema.define(version: 20171220164059) do
t.string "registration_number"
t.string "nearest_topic_name"
t.integer "fare_code"
- t.decimal "longitude", precision: 19, scale: 16
- t.decimal "latitude", precision: 19, scale: 16
+ t.decimal "longitude", precision: 19, scale: 16
+ t.decimal "latitude", precision: 19, scale: 16
t.string "long_lat_type"
t.string "country_code"
t.string "street_name"
@@ -771,7 +751,7 @@ ActiveRecord::Schema.define(version: 20171220164059) do
t.datetime "deleted_at"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "stif_type", limit: 255
+ t.string "stif_type"
t.integer "waiting_time"
end
@@ -842,17 +822,17 @@ ActiveRecord::Schema.define(version: 20171220164059) do
add_index "time_table_periods", ["time_table_id"], name: "index_time_table_periods_on_time_table_id", using: :btree
create_table "time_tables", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
- t.integer "object_version", limit: 8, default: 1
+ t.string "objectid", null: false
+ t.integer "object_version", limit: 8, default: 1
t.string "version"
t.string "comment"
- t.integer "int_day_types", default: 0
+ t.integer "int_day_types", default: 0
t.date "start_date"
t.date "end_date"
t.integer "calendar_id", limit: 8
t.datetime "created_at"
t.datetime "updated_at"
- t.string "color", limit: 255
+ t.string "color"
t.integer "created_from_id", limit: 8
t.string "checksum"
t.text "checksum_source"
@@ -1007,9 +987,7 @@ ActiveRecord::Schema.define(version: 20171220164059) do
add_foreign_key "compliance_controls", "compliance_control_blocks"
add_foreign_key "compliance_controls", "compliance_control_sets"
add_foreign_key "group_of_lines_lines", "group_of_lines", name: "groupofline_group_fkey", on_delete: :cascade
- add_foreign_key "journey_frequencies", "timebands", name: "journey_frequencies_timeband_id_fk", on_delete: :nullify
add_foreign_key "journey_frequencies", "timebands", on_delete: :nullify
- add_foreign_key "journey_frequencies", "vehicle_journeys", name: "journey_frequencies_vehicle_journey_id_fk", on_delete: :nullify
add_foreign_key "journey_frequencies", "vehicle_journeys", on_delete: :nullify
add_foreign_key "journey_patterns", "routes", name: "jp_route_fkey", on_delete: :cascade
add_foreign_key "journey_patterns", "stop_points", column: "arrival_stop_point_id", name: "arrival_point_fkey", on_delete: :nullify
diff --git a/spec/factories/chouette_purchase_windows.rb b/spec/factories/chouette_purchase_windows.rb
new file mode 100644
index 000000000..2e2faf4d8
--- /dev/null
+++ b/spec/factories/chouette_purchase_windows.rb
@@ -0,0 +1,12 @@
+FactoryGirl.define do
+ factory :purchase_window, class: Chouette::PurchaseWindow do
+ sequence(:name) { |n| "Purchase Window #{n}" }
+ sequence(:objectid) { |n| "organisation:PurchaseWindow:#{n}:LOC" }
+ date_ranges { [generate(:periods)] }
+ end
+
+ sequence :periods do |n|
+ date = Date.today + 2*n
+ date..(date+1)
+ end
+end
diff --git a/spec/factories/chouette_routes.rb b/spec/factories/chouette_routes.rb
index 4e20059fe..7443d08bc 100644
--- a/spec/factories/chouette_routes.rb
+++ b/spec/factories/chouette_routes.rb
@@ -31,6 +31,13 @@ FactoryGirl.define do
end
end
+
+ trait :with_opposite do
+ after(:create) do |route|
+ opposite = create :route
+ route.opposite_route = opposite
+ end
+ end
end
factory :route_with_after_commit do
diff --git a/spec/features/purchase_windows_permission_spec.rb b/spec/features/purchase_windows_permission_spec.rb
new file mode 100644
index 000000000..9f155a1e8
--- /dev/null
+++ b/spec/features/purchase_windows_permission_spec.rb
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+require 'spec_helper'
+
+describe "PurchaseWindows", :type => :feature do
+ login_user
+
+ before do
+ @user.organisation.update features: %w{purchase_windows}
+ end
+
+ let(:purchase_window) { create :purchase_window, referential: first_referential}
+
+ describe 'permissions' do
+ before do
+ allow_any_instance_of(PurchaseWindowPolicy).to receive(:create?).and_return permission
+ allow_any_instance_of(PurchaseWindowPolicy).to receive(:destroy?).and_return permission
+ allow_any_instance_of(PurchaseWindowPolicy).to receive(:update?).and_return permission
+ visit path
+ end
+
+ context 'on show view' do
+ let( :path ){ referential_purchase_window_path(first_referential, purchase_window) }
+
+ context 'if present → ' do
+ let( :permission ){ true }
+ it 'view shows the corresponding buttons' do
+ expect(page).to have_content(I18n.t('purchase_windows.actions.edit'))
+ expect(page).to have_content(I18n.t('purchase_windows.actions.destroy'))
+ end
+ end
+
+ context 'if absent → ' do
+ let( :permission ){ false }
+ it 'view does not show the corresponding buttons' do
+ expect(page).not_to have_content(I18n.t('purchase_windows.actions.edit'))
+ expect(page).not_to have_content(I18n.t('purchase_windows.actions.destroy'))
+ end
+ end
+ end
+
+ context 'on index view' do
+ let( :path ){ referential_purchase_windows_path(first_referential) }
+
+ context 'if present → ' do
+ let( :permission ){ true }
+ it 'index shows an edit button' do
+ expect(page).to have_content(I18n.t('purchase_windows.actions.new'))
+ end
+ end
+
+ context 'if absent → ' do
+ let( :permission ){ false }
+ it 'index does not show any edit button' do
+ expect(page).not_to have_content(I18n.t('purchase_windows.actions.new'))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/chouette/purchase_window_spec.rb b/spec/models/chouette/purchase_window_spec.rb
new file mode 100644
index 000000000..702a44eeb
--- /dev/null
+++ b/spec/models/chouette/purchase_window_spec.rb
@@ -0,0 +1,27 @@
+RSpec.describe Chouette::PurchaseWindow, :type => :model do
+ let(:referential) {create(:referential)}
+ subject { create(:purchase_window, referential: referential) }
+
+ it { should belong_to(:referential) }
+ it { is_expected.to validate_presence_of(:name) }
+
+ describe 'validations' do
+ it 'validates and date_ranges do not overlap' do
+ expect(build(:purchase_window, referential: referential,date_ranges: [Date.today..Date.today + 10.day, Date.yesterday..Date.tomorrow])).to_not be_valid
+ # expect(build(periods: [Date.today..Date.today + 10.day, Date.yesterday..Date.tomorrow ])).to_not be_valid
+ end
+ end
+
+ describe 'before_validation' do
+ let(:purchase_window) { build(:purchase_window, referential: referential, date_ranges: []) }
+
+ it 'shoud fill date_ranges with date ranges' do
+ expected_range = Date.today..Date.tomorrow
+ purchase_window.date_ranges << expected_range
+ purchase_window.valid?
+
+ expect(purchase_window.date_ranges.map { |period| period.begin..period.end }).to eq([expected_range])
+ end
+ end
+
+end
diff --git a/spec/policies/purchase_window_policy_spec.rb b/spec/policies/purchase_window_policy_spec.rb
new file mode 100644
index 000000000..f078bf288
--- /dev/null
+++ b/spec/policies/purchase_window_policy_spec.rb
@@ -0,0 +1,15 @@
+RSpec.describe PurchaseWindowPolicy, type: :policy do
+
+ let( :record ){ build_stubbed :purchase_window }
+ before { stub_policy_scope(record) }
+
+ permissions :create? do
+ it_behaves_like 'permitted policy and same organisation', "purchase_windows.create", archived: true
+ end
+ permissions :destroy? do
+ it_behaves_like 'permitted policy and same organisation', "purchase_windows.destroy", archived: true
+ end
+ permissions :update? do
+ it_behaves_like 'permitted policy and same organisation', "purchase_windows.update", archived: true
+ end
+end
diff --git a/spec/support/integration_spec_helper.rb b/spec/support/integration_spec_helper.rb
index 78efb9027..1bf211fe1 100644
--- a/spec/support/integration_spec_helper.rb
+++ b/spec/support/integration_spec_helper.rb
@@ -1,7 +1,11 @@
module IntegrationSpecHelper
def paginate_collection klass, decorator, page=1
- ModelDecorator.decorate( klass.page(page), with: decorator )
+ coll = klass.page(page)
+ if decorator
+ coll = ModelDecorator.decorate( coll, with: decorator )
+ end
+ coll
end
def build_paginated_collection factory, decorator, opts={}
diff --git a/spec/views/vehicle_journeys/index.html.slim_spec.rb b/spec/views/vehicle_journeys/index.html.slim_spec.rb
new file mode 100644
index 000000000..7f0a9c5aa
--- /dev/null
+++ b/spec/views/vehicle_journeys/index.html.slim_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe "/vehicle_journeys/index", :type => :view do
+
+ let!(:referential) { assign :referential, create(:referential) }
+ let!(:line) { assign :line, create(:line) }
+ let!(:route) { assign :route, create(:route, line: line) }
+ let!(:vehicle_journeys) do
+ assign :vehicle_journeys, build_paginated_collection(:vehicle_journey, nil, route: route)
+ end
+
+ before :each do
+ allow(view).to receive(:link_with_search).and_return("#")
+ allow(view).to receive(:collection).and_return(vehicle_journeys)
+ allow(view).to receive(:current_referential).and_return(referential)
+ controller.request.path_parameters[:referential_id] = referential.id
+ render
+ end
+
+ context "with an opposite_route" do
+ let!(:route) { assign :route, create(:route, :with_opposite, line: line) }
+
+ it "should have an 'oppposite route timetable' button" do
+ href = view.referential_line_route_vehicle_journeys_path(referential, line, route.opposite_route)
+ oppposite_button_selector = "a[href=\"#{href}\"]"
+
+ expect(view.content_for(:page_header_content)).to have_selector oppposite_button_selector
+ end
+ end
+end