aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-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/components/_lists.sass5
-rw-r--r--app/assets/stylesheets/components/_main_nav.sass3
-rw-r--r--app/assets/stylesheets/typography/_sboiv.sass2
-rw-r--r--app/controllers/application_controller.rb1
-rw-r--r--app/controllers/calendars_controller.rb2
-rw-r--r--app/controllers/concerns/feature_checker.rb42
-rw-r--r--app/controllers/purchase_windows_controller.rb58
-rw-r--r--app/controllers/referential_vehicle_journeys_controller.rb17
-rw-r--r--app/controllers/stop_areas_controller.rb30
-rw-r--r--app/decorators/purchase_window_decorator.rb34
-rw-r--r--app/decorators/referential_decorator.rb25
-rw-r--r--app/decorators/stop_area_decorator.rb6
-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.rb27
-rw-r--r--app/models/chouette/stop_area.rb2
-rw-r--r--app/models/chouette/vehicle_journey_at_stop.rb11
-rw-r--r--app/models/concerns/date_support.rb80
-rw-r--r--app/models/concerns/period_support.rb80
-rw-r--r--app/models/organisation.rb5
-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.slim22
-rw-r--r--app/views/layouts/navigation/_main_nav_top.html.slim2
-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.slim29
-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.slim44
-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/referential_vehicle_journeys/_filters.html.slim11
-rw-r--r--app/views/referential_vehicle_journeys/index.html.slim58
-rw-r--r--app/views/referentials/_filters.html.slim4
-rw-r--r--app/views/stop_areas/_form.html.slim3
-rw-r--r--app/views/stop_areas/show.html.slim11
40 files changed, 723 insertions, 168 deletions
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/components/_lists.sass b/app/assets/stylesheets/components/_lists.sass
index d8f83d72b..3cce20021 100644
--- a/app/assets/stylesheets/components/_lists.sass
+++ b/app/assets/stylesheets/components/_lists.sass
@@ -54,3 +54,8 @@ $dlWidth: 40%
// Definition
.dl-def
width: 100% - $dlWidth
+
+ ul
+ list-style: none
+ padding-left: 0
+ margin-bottom: 0 \ No newline at end of file
diff --git a/app/assets/stylesheets/components/_main_nav.sass b/app/assets/stylesheets/components/_main_nav.sass
index fdbf5836a..9f7a8e244 100644
--- a/app/assets/stylesheets/components/_main_nav.sass
+++ b/app/assets/stylesheets/components/_main_nav.sass
@@ -17,6 +17,9 @@ $menuW: 300px
line-height: $menuH
padding-left: 10px
opacity: 0.6
+ > a
+ color: rgba(#fff, 0.9)
+ text-decoration: none
#menu_left
position: absolute
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/application_controller.rb b/app/controllers/application_controller.rb
index 97f5548ae..474277da1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,6 +1,7 @@
class ApplicationController < ActionController::Base
include PaperTrailSupport
include Pundit
+ include FeatureChecker
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb
index 2ed10a111..4a752f2b9 100644
--- a/app/controllers/calendars_controller.rb
+++ b/app/controllers/calendars_controller.rb
@@ -19,7 +19,7 @@ class CalendarsController < ChouetteController
private
def calendar_params
- permitted_params = [:id, :name, :short_name, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]]
+ permitted_params = [:id, :name, :short_name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]]
permitted_params << :shared if policy(Calendar).share?
params.require(:calendar).permit(*permitted_params)
end
diff --git a/app/controllers/concerns/feature_checker.rb b/app/controllers/concerns/feature_checker.rb
new file mode 100644
index 000000000..9ca5ed0a7
--- /dev/null
+++ b/app/controllers/concerns/feature_checker.rb
@@ -0,0 +1,42 @@
+# Check availability of optional features
+#
+# In your controller, use :
+#
+# requires_feature :test
+# requires_feature :test, only: [:show]
+#
+# In your view, use :
+#
+# has_feature? :test
+#
+module FeatureChecker
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def requires_feature(feature, options = {})
+ before_action options do
+ check_feature! feature
+ end
+ end
+ end
+
+ included do
+ helper_method :has_feature?
+ end
+
+ protected
+
+ def has_feature?(*features)
+ features.all? do |feature|
+ current_organisation.has_feature? feature
+ end
+ end
+
+ def check_feature!(*features)
+ unless has_feature?(*features)
+ raise NotAuthorizedError, "Feature not autorized"
+ end
+ end
+
+ class NotAuthorizedError < StandardError; end
+end
diff --git a/app/controllers/purchase_windows_controller.rb b/app/controllers/purchase_windows_controller.rb
new file mode 100644
index 000000000..a70535150
--- /dev/null
+++ b/app/controllers/purchase_windows_controller.rb
@@ -0,0 +1,58 @@
+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
+
+ def index
+ index! do
+ scope = self.ransack_period_range(scope: @purchase_windows, error_message: t('compliance_check_sets.filters.error_period_filter'), query: :overlapping)
+ @q = scope.ransack(params[:q])
+ @purchase_windows = decorate_purchase_windows(@q.result.paginate(page: params[:page], per_page: 30))
+ 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 ransack_contains_date
+ date =[]
+ if params[:q] && !params[:q]['date_ranges(1i)'].empty?
+ ['date_ranges(1i)', 'date_ranges(2i)', 'date_ranges(3i)'].each do |key|
+ date << params[:q][key].to_i
+ params[:q].delete(key)
+ end
+ params[:q]['date_ranges'] = Date.new(*date) rescue nil
+ end
+ end
+end
diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb
new file mode 100644
index 000000000..ad08699a5
--- /dev/null
+++ b/app/controllers/referential_vehicle_journeys_controller.rb
@@ -0,0 +1,17 @@
+#
+# Browse all VehicleJourneys of the Referential
+#
+class ReferentialVehicleJourneysController < ChouetteController
+ include ReferentialSupport
+ defaults :resource_class => Chouette::VehicleJourney, collection_name: :vehicle_journeys
+
+ requires_feature :referential_vehicle_journeys
+
+ private
+
+ def collection
+ @q ||= end_of_association_chain.ransack(params[:q])
+ @vehicle_journeys ||= @q.result.includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: 10
+ end
+
+end
diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb
index d4d996adb..498493f1e 100644
--- a/app/controllers/stop_areas_controller.rb
+++ b/app/controllers/stop_areas_controller.rb
@@ -171,7 +171,35 @@ class StopAreasController < ChouetteController
helper_method :current_referential
def stop_area_params
- params.require(:stop_area).permit( :routing_stop_ids, :routing_line_ids, :children_ids, :parent_id, :objectid, :object_version, :name, :comment, :area_type, :registration_number, :nearest_topic_name, :fare_code, :longitude, :latitude, :long_lat_type, :country_code, :street_name, :zip_code, :city_name, :mobility_restricted_suitability, :stairs_availability, :lift_availability, :int_user_needs, :coordinates, :url, :time_zone )
+ params.require(:stop_area).permit(
+ :area_type,
+ :children_ids,
+ :city_name,
+ :comment,
+ :coordinates,
+ :country_code,
+ :fare_code,
+ :int_user_needs,
+ :latitude,
+ :lift_availability,
+ :long_lat_type,
+ :longitude,
+ :mobility_restricted_suitability,
+ :name,
+ :nearest_topic_name,
+ :object_version,
+ :objectid,
+ :parent_id,
+ :registration_number,
+ :routing_line_ids,
+ :routing_stop_ids,
+ :stairs_availability,
+ :street_name,
+ :time_zone,
+ :url,
+ :waiting_time,
+ :zip_code,
+ )
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 4103790aa..0863b7f4b 100644
--- a/app/decorators/referential_decorator.rb
+++ b/app/decorators/referential_decorator.rb
@@ -3,12 +3,19 @@ class ReferentialDecorator < Draper::Decorator
def action_links
policy = h.policy(object)
- links = [
- Link.new(
- content: h.t('time_tables.index.title'),
- href: h.referential_time_tables_path(object)
+ links = []
+
+ if has_feature?(:referential_vehicle_journeys)
+ links << Link.new(
+ content: h.t('referential_vehicle_journeys.index.title'),
+ href: h.referential_vehicle_journeys_path(object)
)
- ]
+ end
+
+ links << Link.new(
+ content: h.t('time_tables.index.title'),
+ href: h.referential_time_tables_path(object)
+ )
if policy.clone?
links << Link.new(
@@ -63,4 +70,12 @@ class ReferentialDecorator < Draper::Decorator
links
end
+
+ private
+
+ # TODO move to a base Decorator (ApplicationDecorator)
+ def has_feature?(*features)
+ h.has_feature?(*features) rescue false
+ end
+
end
diff --git a/app/decorators/stop_area_decorator.rb b/app/decorators/stop_area_decorator.rb
index 8b2ebf490..cf3612f79 100644
--- a/app/decorators/stop_area_decorator.rb
+++ b/app/decorators/stop_area_decorator.rb
@@ -31,4 +31,10 @@ class StopAreaDecorator < Draper::Decorator
links
end
+
+ def waiting_time_text
+ return '-' if [nil, 0].include? waiting_time
+ h.t('stop_areas.waiting_time_format', value: waiting_time)
+ end
+
end
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..8786c7252
--- /dev/null
+++ b/app/models/chouette/purchase_window.rb
@@ -0,0 +1,27 @@
+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 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/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index f216ce449..3a9b44d59 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -39,6 +39,8 @@ module Chouette
validates_format_of :coordinates, :with => %r{\A *-?(0?[0-9](\.[0-9]*)?|[0-8][0-9](\.[0-9]*)?|90(\.[0]*)?) *\, *-?(0?[0-9]?[0-9](\.[0-9]*)?|1[0-7][0-9](\.[0-9]*)?|180(\.[0]*)?) *\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_numericality_of :waiting_time, greater_than_or_equal_to: 0, only_integer: true, if: :waiting_time
+
def self.nullable_attributes
[:registration_number, :street_name, :country_code, :fare_code,
:nearest_topic_name, :comment, :long_lat_type, :zip_code, :city_name, :url, :time_zone]
diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb
index 6f0119e74..6b3c1e7de 100644
--- a/app/models/chouette/vehicle_journey_at_stop.rb
+++ b/app/models/chouette/vehicle_journey_at_stop.rb
@@ -75,5 +75,14 @@ module Chouette
attrs << self.arrival_day_offset.to_s
end
end
+
+ def departure
+ departure_time.utc.strftime "%H:%M" if departure_time
+ end
+
+ def arrival
+ arrival_time.utc.strftime "%H:%M" if arrival_time
+ end
+
end
-end \ No newline at end of file
+end
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/organisation.rb b/app/models/organisation.rb
index 4343c87af..da7d1fcf3 100644
--- a/app/models/organisation.rb
+++ b/app/models/organisation.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
class Organisation < ActiveRecord::Base
include DataFormatEnumerations
@@ -75,4 +76,8 @@ class Organisation < ActiveRecord::Base
STIF::CodifligneLineId.lines_set_from_functional_scope( functional_scope )
end
+ def has_feature?(feature)
+ features && features.include?(feature.to_s)
+ end
+
end
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..b468fed27 100644
--- a/app/views/dashboards/_dashboard.html.slim
+++ b/app/views/dashboards/_dashboard.html.slim
@@ -31,13 +31,29 @@
= 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')
+
+ .panel.panel-default
+ .panel-heading
+ h3.panel-title.with_actions
+ = t('dasboard.purchase_windows.title')
+ div
+ = link_to '', purchase_windows_path, class: ' fa fa-chevron-right pull-right'
+ - if @dashboard.current_organisation.purchase_windows.present?
+ .list-group
+ - @dashboard.current_organisation.purchase_windows.order("updated_at desc").limit(5).each do |purchase_window|
+ = link_to purchase_window.name, referential_purchase_window_path(purchase_window.referential, purchase_window), class: 'list-group-item'
+ - else
+ .panel-body
+ em.small.text-muted
+ = t('dasboard.purchase_windows.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"
+ = t('dashboard.stop_area_referentials.title')
.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'
@@ -45,7 +61,7 @@
.panel.panel-default
.panel-heading
h3.panel-title
- = "Référentiels de lignes"
+ = t('dashboard.line_referentials.title')
.list-group
- @dashboard.current_organisation.line_referentials.all.each do |referential|
= link_to referential.name, line_referential_lines_path(referential), class: 'list-group-item'
diff --git a/app/views/layouts/navigation/_main_nav_top.html.slim b/app/views/layouts/navigation/_main_nav_top.html.slim
index 363a89b48..278249e09 100644
--- a/app/views/layouts/navigation/_main_nav_top.html.slim
+++ b/app/views/layouts/navigation/_main_nav_top.html.slim
@@ -1,5 +1,5 @@
.nav-menu#menu_top
- .brandname = t('brandname')
+ .brandname = link_to t('brandname'), root_path
.menu-content
.menu-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..9c83d20db
--- /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 :date_ranges, as: :date, label: false, wrapper_html: { class: 'date smart_date' }, class: 'form-control', include_blank: 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..8f3ba769d
--- /dev/null
+++ b/app/views/purchase_windows/_form.html.slim
@@ -0,0 +1,29 @@
+= 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'}
+
+ .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..38954b5dc
--- /dev/null
+++ b/app/views/purchase_windows/index.html.slim
@@ -0,0 +1,44 @@
+- 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))} \
+ ) \
+ ],
+ 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/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
new file mode 100644
index 000000000..963da8cea
--- /dev/null
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -0,0 +1,11 @@
+= search_form_for @q, url: referential_vehicle_journeys_path(@referential), html: {method: :get}, class: 'form form-filter' do |f|
+ .ffg-row
+ .input-group.search_bar
+ = f.search_field :published_journey_name_or_objectid_cont, placeholder: t('.published_journey_name_or_objectid'), class: 'form-control'
+ span.input-group-btn
+ button.btn.btn-default#search-btn type='submit'
+ span.fa.fa-search
+
+ .actions
+ = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link'
+ = f.submit 'Filtrer', class: 'btn btn-default'
diff --git a/app/views/referential_vehicle_journeys/index.html.slim b/app/views/referential_vehicle_journeys/index.html.slim
new file mode 100644
index 000000000..394f4a3f7
--- /dev/null
+++ b/app/views/referential_vehicle_journeys/index.html.slim
@@ -0,0 +1,58 @@
+- breadcrumb :referential_vehicle_journeys, @referential
+- content_for :page_header_title, t('.title')
+
+.page_content
+ .container-fluid
+ - if params[:q].present? or @vehicle_journeys.present?
+ .row
+ .col-lg-12
+ = render 'filters'
+
+ - if @vehicle_journeys.present?
+ .row
+ .col-lg-12
+ .select_table
+ = table_builder_2 @vehicle_journeys,
+ [ \
+ TableBuilderHelper::Column.new( \
+ name: t('objectid'), \
+ attribute: Proc.new { |n| n.get_objectid.short_id }, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :published_journey_name, \
+ attribute: 'published_journey_name', \
+ link_to: lambda do |vehicle_journey| \
+ referential_line_route_vehicle_journeys_path(@referential, vehicle_journey.route.line, vehicle_journey.route) \
+ end, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :line, \
+ attribute: Proc.new {|v| v.route.line.name}, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :route, \
+ attribute: Proc.new {|v| v.route.name}, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :departure_time, \
+ attribute: Proc.new {|v| v.vehicle_journey_at_stops.first&.departure }, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :arrival_time, \
+ attribute: Proc.new {|v| v.vehicle_journey_at_stops.last&.arrival }, \
+ sortable: false \
+ ), \
+ ],
+ cls: 'table has-filter has-search'
+
+ = new_pagination @vehicle_journeys, 'pull-right'
+
+ - unless @vehicle_journeys.any?
+ .row.mt-xs
+ .col-lg-12
+ = replacement_msg t('.search_no_results')
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/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index ac2cb4e87..e44680499 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -26,6 +26,9 @@
.stop_areas.stop_area.general_info
h3 = t("stop_areas.stop_area.general")
+ - if has_feature?(:stop_area_waiting_time)
+ = f.input :waiting_time
+
= f.input :registration_number, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number")}
= f.input :fare_code
= f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")}
diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim
index 1b1209a68..0c23710b6 100644
--- a/app/views/stop_areas/show.html.slim
+++ b/app/views/stop_areas/show.html.slim
@@ -15,12 +15,15 @@
.container-fluid
.row
.col-lg-6.col-md-6.col-sm-12.col-xs-12
- = definition_list t('metadatas'),
- { t('id_reflex') => @stop_area.get_objectid.short_id,
+ - attributes = { t('id_reflex') => @stop_area.get_objectid.short_id,
@stop_area.human_attribute_name(:stop_area_type) => Chouette::AreaType.find(@stop_area.area_type).try(:label),
@stop_area.human_attribute_name(:registration_number) => @stop_area.registration_number,
- 'Coordonnées' => geo_data(@stop_area, @stop_area_referential),
+ }
+ - attributes.merge!(@stop_area.human_attribute_name(:waiting_time) => @stop_area.waiting_time_text) if has_feature?(:stop_area_waiting_time)
+ - attributes.merge!({ "Coordonnées" => geo_data(@stop_area, @stop_area_referential),
@stop_area.human_attribute_name(:zip_code) => @stop_area.zip_code,
@stop_area.human_attribute_name(:city_name) => @stop_area.city_name,
'Etat' => (@stop_area.deleted_at ? 'Supprimé' : 'Actif'),
- @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment) }
+ @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment),
+ })
+ = definition_list t('metadatas'), attributes