From 87841876b7fa2fdc9cee77cc465fa6e8db9fc417 Mon Sep 17 00:00:00 2001
From: Zog
Date: Fri, 12 Jan 2018 11:31:14 +0100
Subject: Refs #5571 @0.25h; Let the user select the number of items per page
on ReferentialVehicleJourneys#index
---
 app/assets/stylesheets/components/_forms.sass              | 4 ++++
 app/controllers/referential_vehicle_journeys_controller.rb | 2 +-
 app/views/referential_vehicle_journeys/_filters.html.slim  | 3 +++
 config/locales/simple_form.en.yml                          | 1 +
 config/locales/simple_form.fr.yml                          | 1 +
 5 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass
index b7f720963..1f0d2f59b 100644
--- a/app/assets/stylesheets/components/_forms.sass
+++ b/app/assets/stylesheets/components/_forms.sass
@@ -444,6 +444,10 @@ table, .table
     margin: 0
     min-height: 41px
     padding: 5px 15px
+    &.per-page-select
+      padding-top: 10px
+      .selected
+        font-weight: bold
 
     .control-label
       font-weight: 700
diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb
index f93de29cc..89b3703a0 100644
--- a/app/controllers/referential_vehicle_journeys_controller.rb
+++ b/app/controllers/referential_vehicle_journeys_controller.rb
@@ -13,7 +13,7 @@ class ReferentialVehicleJourneysController < ChouetteController
     @q ||= end_of_association_chain
     @q = @q.with_stop_area_ids(params[:q][:stop_area_ids]) if params[:q] && params[:q][:stop_area_ids]
     @q = @q.ransack(params[:q])
-    @vehicle_journeys ||= @q.result.order(:published_journey_name).includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: 10
+    @vehicle_journeys ||= @q.result.order(:published_journey_name).includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: params[:per_page] || 10
     @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct
     @all_stop_areas = Chouette::StopArea.where("id IN (#{@referential.vehicle_journeys.joins(:stop_areas).select("stop_areas.id").to_sql})").distinct
   end
diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
index 609927615..7c5a63e25 100644
--- a/app/views/referential_vehicle_journeys/_filters.html.slim
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -6,6 +6,9 @@
         button.btn.btn-default#search-btn type='submit'
           span.fa.fa-search
   .ffg-row
+    .form-group.per-page-select
+      = I18n.t("simple_form.per_page")
+      = %w(10 50 100).each_with_index.map{ |v, i| (params[:per_page] == v || params[:per_page].nil? && i == 0) ? "#{v}" : link_to(v, referential_vehicle_journeys_path(@referential, q: params[:q], per_page: v)) }.join(' - ').html_safe
     .form-group.togglable
       = f.label Chouette::VehicleJourney.human_attribute_name(:company), required: false, class: 'control-label'
       = f.input :company_id_eq_any, collection: @all_companies.select(:id, :name).order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("" + l.name + "").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index ad722312e..ed4ad2e95 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -1,5 +1,6 @@
 en:
   simple_form:
+    "per_page": "Per page: "
     "yes": 'Yes'
     "no": 'No'
     from: 'From'
diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml
index cd5dd1fbe..a1868eef7 100644
--- a/config/locales/simple_form.fr.yml
+++ b/config/locales/simple_form.fr.yml
@@ -1,5 +1,6 @@
 fr:
   simple_form:
+    "per_page": "Afficher par: "
     "yes": 'Oui'
     "no": 'Non'
     from: 'Du'
-- 
cgit v1.2.3
From 865e58859cb83da4e36d8502fc778d383c756c26 Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 22 Jan 2018 11:05:09 +0100
Subject: Refs #5647; Scope CustomField validations on workgroup
---
 app/models/custom_field.rb       | 5 +++--
 spec/models/custom_field_spec.rb | 4 ++--
 2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
index 3f79ec62c..774c8b0f6 100644
--- a/app/models/custom_field.rb
+++ b/app/models/custom_field.rb
@@ -1,8 +1,9 @@
 class CustomField < ActiveRecord::Base
 
   extend Enumerize
+  belongs_to :workgroup
   enumerize :field_type, in: %i{list}
 
-  validates :name, uniqueness: {scope: :resource_type}
-  validates :code, uniqueness: {scope: :resource_type, case_sensitive: false}
+  validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]}
+  validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}
 end
diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb
index 8a6d0cb41..51128b0a2 100644
--- a/spec/models/custom_field_spec.rb
+++ b/spec/models/custom_field_spec.rb
@@ -4,8 +4,8 @@ RSpec.describe CustomField, type: :model do
   let( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} }
 
   context "validates" do
-    it { should validate_uniqueness_of(:name).scoped_to(:resource_type) }
-    it { should validate_uniqueness_of(:code).scoped_to(:resource_type).case_insensitive }
+    it { should validate_uniqueness_of(:name).scoped_to(:resource_type, :workgroup_id) }
+    it { should validate_uniqueness_of(:code).scoped_to(:resource_type, :workgroup_id).case_insensitive }
   end
 
   context "field access" do
-- 
cgit v1.2.3
From 82279efd9598204d5429e4b3cc98ddfad0e6dd56 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 08:58:28 +0100
Subject: Add a toolbar for devs to easily manage permissions and features
---
 .gitignore                                         |  1 +
 app/assets/stylesheets/components/_toolbar.sass    | 47 +++++++++++++++++++++
 app/controllers/development_toolbar_controller.rb  | 11 +++++
 app/views/layouts/application.html.slim            |  3 ++
 .../layouts/navigation/_main_nav_top.html.slim     |  4 ++
 app/views/shared/_development_toolbar.html.slim    | 48 ++++++++++++++++++++++
 config/environments/development.rb                 |  7 ++++
 config/routes.rb                                   |  4 ++
 8 files changed, 125 insertions(+)
 create mode 100644 app/assets/stylesheets/components/_toolbar.sass
 create mode 100644 app/controllers/development_toolbar_controller.rb
 create mode 100644 app/views/shared/_development_toolbar.html.slim
diff --git a/.gitignore b/.gitignore
index ddd5fdda3..acdb5e230 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,4 @@ coverage
 /bin/spring
 
 spec/fixtures/target_*
+config/development_toolbar.rb
diff --git a/app/assets/stylesheets/components/_toolbar.sass b/app/assets/stylesheets/components/_toolbar.sass
new file mode 100644
index 000000000..47ae2ac0c
--- /dev/null
+++ b/app/assets/stylesheets/components/_toolbar.sass
@@ -0,0 +1,47 @@
+#development-toolbar
+  .inner
+    overflow: scroll
+    padding: 10px
+    max-height: 70vh
+    display: flex
+    flex-direction: row
+    .toggles
+      font-size: 0.7em
+      float: right
+      a:first-child
+        padding-right: 3px
+        margin-right: 3px
+        border-right: 1px solid $lightgrey
+
+    .col
+      flex: 1 1
+      padding-right: 10px
+      padding-left: 10px
+      border-right: 1px solid $lightgrey
+      &:last-child
+        padding-right: 0
+        border-right: none
+
+      ul
+        padding: 0
+      li
+        list-style-type: none
+      label
+        padding-left: 5px
+        font-weight: normal
+      h5
+        font-weight: bold
+      &.permissions
+        ul
+          overflow: hidden
+        li
+          float: left
+          width: 33%
+          label
+            font-size: 0.8em
+            max-width: calc(100% - 15px)
+            text-overflow: ellipsis
+            overflow: hidden
+            padding-top: 3px
+          input
+            vertical-align: top
diff --git a/app/controllers/development_toolbar_controller.rb b/app/controllers/development_toolbar_controller.rb
new file mode 100644
index 000000000..20349f7b8
--- /dev/null
+++ b/app/controllers/development_toolbar_controller.rb
@@ -0,0 +1,11 @@
+class DevelopmentToolbarController < ApplicationController
+  def update_settings
+    return unless Rails.application.config.development_toolbar.present?
+    organisation = current_user.organisation
+    organisation.features = params[:features].keys.select{|k| params[:features][k] == "true"}
+    organisation.save
+    current_user.permissions = params[:permissions].keys.select{|k| params[:permissions][k] == "true"}
+    current_user.save
+    redirect_to request.referrer || "/"
+  end
+end
diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim
index 567e14ef0..34b373295 100644
--- a/app/views/layouts/application.html.slim
+++ b/app/views/layouts/application.html.slim
@@ -21,3 +21,6 @@ html lang=I18n.locale
     = yield
     #sidebar
       = yield :sidebar
+
+    = render 'shared/development_toolbar'
+    = yield :javascript
diff --git a/app/views/layouts/navigation/_main_nav_top.html.slim b/app/views/layouts/navigation/_main_nav_top.html.slim
index 278249e09..f664d5416 100644
--- a/app/views/layouts/navigation/_main_nav_top.html.slim
+++ b/app/views/layouts/navigation/_main_nav_top.html.slim
@@ -14,9 +14,13 @@
           span = current_user.name
           span.fa.fa-lg.fa-user
 
+        - if Rails.application.config.development_toolbar
+          = link_to '#', data: { toggle: 'modal', target: '#development-toolbar' }, class: "toolbar-button menu-item" do
+            .fa.fa-cog
 
         = link_to destroy_user_session_path, method: :delete, class: 'menu-item', title: 'Se déconnecter' do
           span.fa.fa-lg.fa-sign-out
 
+
   = render 'layouts/navigation/nav_panel_operations'
   = render 'layouts/navigation/nav_panel_profile' if user_signed_in?
diff --git a/app/views/shared/_development_toolbar.html.slim b/app/views/shared/_development_toolbar.html.slim
new file mode 100644
index 000000000..b51bb3de6
--- /dev/null
+++ b/app/views/shared/_development_toolbar.html.slim
@@ -0,0 +1,48 @@
+- if Rails.application.config.development_toolbar
+  = modalbox 'development-toolbar' do
+    = form_tag development_toolbar_update_settings_path, authenticity_token: true do
+      .modal-header
+        h3= "Toolbar"
+
+      .inner
+          .col.features
+            h4
+              = "Features"
+              .toggles
+                = link_to 'all', '#', data: {mask: 'features', val: true}
+                = link_to 'none', '#', data: {mask: 'features', val: false}
+            ul
+              - Rails.application.config.development_toolbar.available_features.sort.each do |feature|
+                li
+                  = hidden_field_tag "features[#{feature}]", false, id: ""
+                  = check_box_tag "features[#{feature}]", true, has_feature?(feature)
+                  = label :features, feature
+          .col.permissions
+            h4
+              = "Permissions"
+              .toggles
+                = link_to 'all', '#', data: {mask: 'permissions', val: true}
+                = link_to 'none', '#', data: {mask: 'permissions', val: false}
+            - model = ""
+            - Rails.application.config.development_toolbar.available_permissions.sort.each do |permission|
+              - if permission.split('.').first != model
+                - model = permission.split('.').first
+                
+                h5
+                  = model
+                  .toggles
+                    = link_to 'all', '#', data: {mask: "permissions[#{model}", val: true}
+                    = link_to 'none', '#', data: {mask: "permissions[#{model}", val: false}
+                
+              li
+                = hidden_field_tag "permissions[#{permission}]", false, id: ""
+                = check_box_tag "permissions[#{permission}]", true, current_user.has_permission?(permission)
+                = label :permissions, permission, permission.split('.').last
+      .modal-footer= submit_tag t("actions.submit"), class: 'btn btn-primary'
+
+  - content_for :javascript do
+    coffee:
+      $('#development-toolbar .toggles a').click (e)->
+        $('#development-toolbar').find("[name^=\"#{e.currentTarget.dataset.mask}\"]").attr "checked", e.currentTarget.dataset.val == 'true'
+        e.preventDefault()
+        false
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 24a4ed6b8..925718e69 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -95,4 +95,11 @@ Rails.application.configure do
   config.i18n.available_locales = [:fr, :en]
 
   config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload) if ENV['LIVERELOAD']
+  config.development_toolbar = false
+  if ENV['TOOLBAR'] && File.exists?("config/development_toolbar.rb")
+    config.development_toolbar = OpenStruct.new
+    config.development_toolbar.tap do |toolbar|
+      eval File.read("config/development_toolbar.rb")
+    end
+  end
 end
diff --git a/config/routes.rb b/config/routes.rb
index 0f60733af..432423ac5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -238,6 +238,10 @@ ChouetteIhm::Application.routes.draw do
 
   get '/help/(*slug)' => 'help#show'
 
+  if Rails.application.config.development_toolbar
+    post "/development_toolbar" => "development_toolbar#update_settings", as: :development_toolbar_update_settings
+  end
+
   match '/404', to: 'errors#not_found', via: :all, as: 'not_found'
   match '/403', to: 'errors#forbidden', via: :all, as: 'forbidden'
   match '/422', to: 'errors#server_error', via: :all, as: 'unprocessable_entity'
-- 
cgit v1.2.3
From 2699a454f1b262339983cf608571be4ad7bb46ff Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 09:00:34 +0100
Subject: Add a config file template
---
 config/development_toolbar.rb.tpl | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 config/development_toolbar.rb.tpl
diff --git a/config/development_toolbar.rb.tpl b/config/development_toolbar.rb.tpl
new file mode 100644
index 000000000..c278f07a7
--- /dev/null
+++ b/config/development_toolbar.rb.tpl
@@ -0,0 +1,2 @@
+toolbar.available_features = %w()
+toolbar.available_permissions = %w()
-- 
cgit v1.2.3
From d5eb3d0d736a4a196b8c36d5aeb30e5ef8db8034 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 10:00:05 +0100
Subject: - Add a default value for the config - Add a cancel button to dismiss
 the modal
---
 app/views/shared/_development_toolbar.html.slim | 4 +++-
 config/application.rb                           | 2 ++
 2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/app/views/shared/_development_toolbar.html.slim b/app/views/shared/_development_toolbar.html.slim
index b51bb3de6..4ad69f7ad 100644
--- a/app/views/shared/_development_toolbar.html.slim
+++ b/app/views/shared/_development_toolbar.html.slim
@@ -38,7 +38,9 @@
                 = hidden_field_tag "permissions[#{permission}]", false, id: ""
                 = check_box_tag "permissions[#{permission}]", true, current_user.has_permission?(permission)
                 = label :permissions, permission, permission.split('.').last
-      .modal-footer= submit_tag t("actions.submit"), class: 'btn btn-primary'
+      .modal-footer
+        button.btn.btn-link type='button' data-dismiss='modal' #{t('cancel')}
+        = submit_tag t("actions.submit"), class: 'btn btn-primary'
 
   - content_for :javascript do
     coffee:
diff --git a/config/application.rb b/config/application.rb
index bda582610..8da6a7428 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -39,6 +39,8 @@ module ChouetteIhm
       'FeatureChecker::NotAuthorizedError' => :unauthorized
     )
 
+    config.development_toolbar = false
+
     unless Rails.env.production?
         # Work around sprockets+teaspoon mismatch:
         Rails.application.config.assets.precompile += %w(spec_helper.js)
-- 
cgit v1.2.3
From 06d100fa077722304574d0f65ecf8b396d2e384c Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 10:39:38 +0100
Subject: Add links to doc
---
 app/assets/stylesheets/components/_toolbar.sass | 2 ++
 app/views/shared/_development_toolbar.html.slim | 3 +++
 config/environments/development.rb              | 3 +++
 3 files changed, 8 insertions(+)
diff --git a/app/assets/stylesheets/components/_toolbar.sass b/app/assets/stylesheets/components/_toolbar.sass
index 47ae2ac0c..86a32bd82 100644
--- a/app/assets/stylesheets/components/_toolbar.sass
+++ b/app/assets/stylesheets/components/_toolbar.sass
@@ -29,6 +29,8 @@
       label
         padding-left: 5px
         font-weight: normal
+        & + a
+          float: right
       h5
         font-weight: bold
       &.permissions
diff --git a/app/views/shared/_development_toolbar.html.slim b/app/views/shared/_development_toolbar.html.slim
index 4ad69f7ad..aafd37885 100644
--- a/app/views/shared/_development_toolbar.html.slim
+++ b/app/views/shared/_development_toolbar.html.slim
@@ -17,6 +17,9 @@
                   = hidden_field_tag "features[#{feature}]", false, id: ""
                   = check_box_tag "features[#{feature}]", true, has_feature?(feature)
                   = label :features, feature
+                  - if Rails.application.config.development_toolbar.features_doc_url
+                    = link_to "#{Rails.application.config.development_toolbar.features_doc_url}##{feature}", target: :blank do
+                      .fa.fa-question-circle
           .col.permissions
             h4
               = "Permissions"
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 925718e69..1d2fee44f 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -98,6 +98,9 @@ Rails.application.configure do
   config.development_toolbar = false
   if ENV['TOOLBAR'] && File.exists?("config/development_toolbar.rb")
     config.development_toolbar = OpenStruct.new
+    config.development_toolbar.features_doc_url = nil
+    config.development_toolbar.available_features = %w()
+    config.development_toolbar.available_permissions = %w()
     config.development_toolbar.tap do |toolbar|
       eval File.read("config/development_toolbar.rb")
     end
-- 
cgit v1.2.3
From 050772225bdff8ef165c1e8e841b1eec664d6c99 Mon Sep 17 00:00:00 2001
From: Zog
Date: Thu, 25 Jan 2018 11:39:32 +0100
Subject: Refs #5598; Enable button to view a JourneyPattern when the user is
 not allowed to edit it
---
 app/javascript/journey_patterns/components/JourneyPattern.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js
index ecbebe2cc..01734f3a3 100644
--- a/app/javascript/journey_patterns/components/JourneyPattern.js
+++ b/app/javascript/journey_patterns/components/JourneyPattern.js
@@ -136,10 +136,9 @@ export default class JourneyPattern extends Component{
               
             
             
-              - 
+              
 - 
                 
                   
 
                 ))}
@@ -86,4 +86,4 @@ Navigate.propTypes = {
   status: PropTypes.object.isRequired,
   pagination: PropTypes.object.isRequired,
   dispatch: PropTypes.func.isRequired
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3
From 7541bd94814134f9c769a7222865802c18ee1c84 Mon Sep 17 00:00:00 2001
From: Zog
Date: Fri, 26 Jan 2018 10:01:37 +0100
Subject: Refs #5740; Add message in company filter when no value is available
---
 app/views/referential_vehicle_journeys/_filters.html.slim | 5 ++++-
 config/locales/companies.en.yml                           | 1 +
 config/locales/companies.fr.yml                           | 1 +
 3 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
index 609927615..f85ef1eb9 100644
--- a/app/views/referential_vehicle_journeys/_filters.html.slim
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -8,7 +8,10 @@
   .ffg-row
     .form-group.togglable
       = f.label Chouette::VehicleJourney.human_attribute_name(:company), required: false, class: 'control-label'
-      = f.input :company_id_eq_any, collection: @all_companies.select(:id, :name).order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("" + l.name + "").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
+      - if @all_companies.present?
+        = f.input :company_id_eq_any, collection: @all_companies.select(:id, :name).order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("" + l.name + "").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
+      - else
+        = f.input :company_id_eq_any, collection: [[I18n.t('companies.search_no_results_for_filter'), nil]], as: :check_boxes, label: false, disabled: true, required: false, wrapper_html: { class: 'checkbox_list disabled'}
     .form-group.togglable.name-filter
       = f.label Chouette::VehicleJourney.human_attribute_name(:published_journey_name), required: false, class: 'control-label'
       .inputs.form-inline.checkbox_list
diff --git a/config/locales/companies.en.yml b/config/locales/companies.en.yml
index a3cd520cb..29dc3911b 100644
--- a/config/locales/companies.en.yml
+++ b/config/locales/companies.en.yml
@@ -1,6 +1,7 @@
 en:
   companies: &en_companies
     search_no_results: "No company matching your query"
+    search_no_results_for_filter: "No company has been set for thess journeys"
     actions:
       new: "Add a new company"
       edit: "Edit this company"
diff --git a/config/locales/companies.fr.yml b/config/locales/companies.fr.yml
index 0cf729c35..3284115ab 100644
--- a/config/locales/companies.fr.yml
+++ b/config/locales/companies.fr.yml
@@ -1,6 +1,7 @@
 fr:
   companies: &fr_companies
     search_no_results: "Aucun transporteur ne correspond à votre recherche"
+    search_no_results_for_filter: "Aucun transporteur renseigné sur ces courses"
     actions:
       new: "Ajouter un transporteur"
       edit: "Editer ce transporteur"
-- 
cgit v1.2.3
From f6f52147fcec3b9283dc2890cfb05b0fb19bff33 Mon Sep 17 00:00:00 2001
From: Zog
Date: Fri, 26 Jan 2018 12:49:23 +0100
Subject: Refs #5741 @2h; Add a map of all routes on a line#show
---
 Gemfile.lock                                      |  22 +--
 app/assets/stylesheets/OpenLayers/custom.sass     |  21 +++
 app/helpers/routes_helper.rb                      |   8 +-
 app/javascript/helpers/routes_map.coffee          | 157 ++++++++++++++++++++++
 app/javascript/packs/referential_lines/show.js    |  10 ++
 app/javascript/packs/routes/show.js               | 123 +----------------
 app/javascript/routes/components/StopPointList.js |   4 +-
 app/views/referential_lines/show.html.slim        |   8 +-
 config/webpack/environment.js                     |   2 +
 config/webpack/loaders/coffee.js                  |   6 +
 package.json                                      |   3 +-
 yarn.lock                                         |  12 +-
 12 files changed, 238 insertions(+), 138 deletions(-)
 create mode 100644 app/javascript/helpers/routes_map.coffee
 create mode 100644 app/javascript/packs/referential_lines/show.js
 create mode 100644 config/webpack/loaders/coffee.js
diff --git a/Gemfile.lock b/Gemfile.lock
index 09ef00f94..f4dfe1bf7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -156,6 +156,7 @@ GEM
       sort_alphabetical (~> 1.0)
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
+    crass (1.0.3)
     cucumber (2.4.0)
       builder (>= 2.1.2)
       cucumber-core (~> 1.5.0)
@@ -265,7 +266,7 @@ GEM
     htmlbeautifier (1.3.1)
     httparty (0.14.0)
       multi_xml (>= 0.5.2)
-    i18n (0.9.0)
+    i18n (0.9.3)
       concurrent-ruby (~> 1.0)
     i18n-tasks (0.9.15)
       activesupport (>= 4.0.2)
@@ -302,7 +303,8 @@ GEM
       thor
       with_env (> 1.0)
       xml-simple
-    loofah (2.0.3)
+    loofah (2.1.1)
+      crass (~> 1.0.2)
       nokogiri (>= 1.5.9)
     mail (2.6.4)
       mime-types (>= 1.16, < 4)
@@ -313,7 +315,7 @@ GEM
     mime-types-data (3.2016.0521)
     mimemagic (0.3.2)
     mini_portile2 (2.3.0)
-    minitest (5.10.3)
+    minitest (5.11.2)
     money (6.10.1)
       i18n (>= 0.6.4, < 1.0)
     multi_json (1.12.1)
@@ -368,7 +370,7 @@ GEM
       rack
     rack-protection (1.5.3)
       rack
-    rack-proxy (0.6.2)
+    rack-proxy (0.6.3)
       rack
     rack-test (0.6.3)
       rack (>= 1.0)
@@ -396,8 +398,8 @@ GEM
       rails-assets-jquery (>= 1.0.0)
     rails-deprecated_sanitizer (1.0.3)
       activesupport (>= 4.2.0.alpha)
-    rails-dom-testing (1.0.8)
-      activesupport (>= 4.2.0.beta, < 5.0)
+    rails-dom-testing (1.0.9)
+      activesupport (>= 4.2.0, < 5.0)
       nokogiri (~> 1.6)
       rails-deprecated_sanitizer (>= 1.0.1)
     rails-erd (1.5.2)
@@ -419,7 +421,7 @@ GEM
       thor (>= 0.18.1, < 2.0)
     rainbow (2.2.2)
       rake
-    rake (12.0.0)
+    rake (12.3.0)
     ransack (1.8.3)
       actionpack (>= 3.0)
       activerecord (>= 3.0)
@@ -541,7 +543,7 @@ GEM
     therubyracer (0.12.3)
       libv8 (~> 3.16.14.15)
       ref
-    thor (0.19.4)
+    thor (0.20.0)
     thread (0.2.2)
     thread_safe (0.3.6)
     tilt (1.4.1)
@@ -553,7 +555,7 @@ GEM
       json (>= 1.8, < 3.0)
       parser (>= 2.3.0.7)
       rainbow (>= 1.99.1, < 3.0)
-    tzinfo (1.2.3)
+    tzinfo (1.2.4)
       thread_safe (~> 0.1)
     uglifier (2.7.2)
       execjs (>= 0.3.0)
@@ -566,7 +568,7 @@ GEM
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
       hashdiff
-    webpacker (3.0.2)
+    webpacker (3.2.1)
       activesupport (>= 4.2)
       rack-proxy (>= 0.6.1)
       railties (>= 4.2)
diff --git a/app/assets/stylesheets/OpenLayers/custom.sass b/app/assets/stylesheets/OpenLayers/custom.sass
index 7a5b4baf1..5a3612f99 100644
--- a/app/assets/stylesheets/OpenLayers/custom.sass
+++ b/app/assets/stylesheets/OpenLayers/custom.sass
@@ -2,6 +2,27 @@
   .list-group-item &
     margin-top: 15px
 
+  .routes-labels
+    padding: 0
+    display: flex
+    justify-content: space-between
+    flex-wrap: wrap
+    margin: 5px -5px
+    li
+      list-style-type: none
+      flex: 1 0 25%
+      border: 1px solid $lightgrey
+      padding: 5px
+      margin: 5px
+      border-radius: 5px
+      cursor: pointer
+      color: $blue
+      white-space: nowrap
+      max-width: 33%
+
+      &:hover
+        background: $orange
+        color: white
 
   .ol-scale-line
     background-color: transparent
diff --git a/app/helpers/routes_helper.rb b/app/helpers/routes_helper.rb
index 4bffa99d4..61714a066 100644
--- a/app/helpers/routes_helper.rb
+++ b/app/helpers/routes_helper.rb
@@ -19,13 +19,15 @@ module RoutesHelper
     css
   end
 
-  def route_json_for_edit(route)
-    route.stop_points.includes(:stop_area).order(:position).map do |stop_point|
+  def route_json_for_edit(route, serialize: true)
+    data = route.stop_points.includes(:stop_area).order(:position).map do |stop_point|
       stop_area_attributes = stop_point.stop_area.attributes.slice("name","city_name", "zip_code", "registration_number", "longitude", "latitude", "area_type", "comment")
       stop_area_attributes["short_name"] = truncate(stop_area_attributes["name"], :length => 30) || ""
       stop_point_attributes = stop_point.attributes.slice("for_boarding","for_alighting")
       stop_area_attributes.merge(stop_point_attributes).merge(stoppoint_id: stop_point.id, stoparea_id: stop_point.stop_area.id).merge(user_objectid: stop_point.stop_area.user_objectid)
-    end.to_json
+    end
+    data = data.to_json if serialize
+    data
   end
 
 end
diff --git a/app/javascript/helpers/routes_map.coffee b/app/javascript/helpers/routes_map.coffee
new file mode 100644
index 000000000..85def1390
--- /dev/null
+++ b/app/javascript/helpers/routes_map.coffee
@@ -0,0 +1,157 @@
+class RoutesMap
+  constructor: (@target)->
+    @initMap()
+    @area = []
+    @seenStopIds = []
+    @routes = {}
+
+  initMap: ->
+    @map = new ol.Map
+      target: @target,
+      layers:   [ new ol.layer.Tile(source: new ol.source.OSM()) ]
+      controls: [ new ol.control.ScaleLine(), new ol.control.Zoom(), new ol.control.ZoomSlider() ],
+      interactions: ol.interaction.defaults(zoom: true)
+      view: new ol.View()
+
+  addRoutes: (routes)->
+    for route in routes
+      @addRoute route
+
+  addRoute: (route)->
+    geoColPts = []
+    geoColLns = []
+    @routes[route.id] = route if route.id
+    stops = route.stops || route
+    geoColEdges = [
+      new ol.Feature({
+        geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stops[0].longitude), parseFloat(stops[0].latitude)]))
+      }),
+      new ol.Feature({
+        geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stops[stops.length - 1].longitude), parseFloat(stops[stops.length - 1].latitude)]))
+      })
+    ]
+    stops.forEach (stop, i) =>
+      if i < stops.length - 1
+        geoColLns.push new ol.Feature
+          geometry: new ol.geom.LineString([
+            ol.proj.fromLonLat([parseFloat(stops[i].longitude), parseFloat(stops[i].latitude)]),
+            ol.proj.fromLonLat([parseFloat(stops[i + 1].longitude), parseFloat(stops[i + 1].latitude)])
+          ])
+
+      geoColPts.push(new ol.Feature({
+        geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stop.longitude), parseFloat(stop.latitude)]))
+      }))
+      unless @seenStopIds.indexOf(stop.stoparea_id) > 0
+        @area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)]
+        @seenStopIds.push stop.stoparea_id
+
+    vectorPtsLayer = new ol.layer.Vector({
+      source: new ol.source.Vector({
+        features: geoColPts
+      }),
+      style: @defaultStyles(),
+      zIndex: 2
+    })
+    route.vectorPtsLayer = vectorPtsLayer if route.id
+    vectorEdgesLayer = new ol.layer.Vector({
+      source: new ol.source.Vector({
+        features: geoColEdges
+      }),
+      style: @edgeStyles(),
+      zIndex: 3
+    })
+    route.vectorEdgesLayer = vectorEdgesLayer if route.id
+    vectorLnsLayer = new ol.layer.Vector({
+      source: new ol.source.Vector({
+        features: geoColLns
+      }),
+      style: [@lineStyle()],
+      zIndex: 1
+    })
+    route.vectorLnsLayer = vectorLnsLayer if route.id
+    @map.addLayer vectorPtsLayer
+    @map.addLayer vectorEdgesLayer
+    @map.addLayer vectorLnsLayer
+
+  lineStyle: (highlighted=false)->
+    new ol.style.Style
+      stroke: new ol.style.Stroke
+        color: if highlighted then "#ed7f00" else '#007fbb'
+        width: 3
+
+  edgeStyles: (highlighted=false)->
+    new ol.style.Style
+      image: new ol.style.Circle
+        radius: 5
+        stroke: new ol.style.Stroke
+          color: if highlighted then "#ed7f00" else '#007fbb'
+          width: 2
+        fill: new ol.style.Fill
+          color: if highlighted then "#ed7f00" else '#007fbb'
+          width: 2
+
+  defaultStyles: (highlighted=false)->
+    new ol.style.Style
+      image: new ol.style.Circle
+        radius: 4
+        stroke: new ol.style.Stroke
+          color: if highlighted then "#ed7f00" else '#007fbb'
+          width: 2
+        fill: new ol.style.Fill
+          color: '#ffffff'
+          width: 2
+
+  addRoutesLabels: ->
+    labelsContainer = $("")
+    labelsContainer.appendTo $("##{@target}")
+    @vectorPtsLayer = null
+    @vectorEdgesLayer = null
+    @vectorLnsLayer = null
+    Object.keys(@routes).forEach (id)=>
+      route = @routes[id]
+      label = $("- #{route.name}
 
")
+      label.appendTo labelsContainer
+      label.mouseleave =>
+        route.vectorPtsLayer.setStyle @defaultStyles(false)
+        route.vectorEdgesLayer.setStyle @edgeStyles(false)
+        route.vectorLnsLayer.setStyle @lineStyle(false)
+        route.vectorPtsLayer.setZIndex 2
+        route.vectorEdgesLayer.setZIndex 3
+        route.vectorLnsLayer.setZIndex 1
+        @fitZoom()
+      label.mouseenter =>
+        route.vectorPtsLayer.setStyle @defaultStyles(true)
+        route.vectorEdgesLayer.setStyle @edgeStyles(true)
+        route.vectorLnsLayer.setStyle @lineStyle(true)
+        route.vectorPtsLayer.setZIndex 11
+        route.vectorEdgesLayer.setZIndex 12
+        route.vectorLnsLayer.setZIndex 10
+        @fitZoom(route)
+
+  fitZoom: (route)->
+    if route
+      area = []
+      route.stops.forEach (stop, i) =>
+        area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)]
+    else
+      area = @area
+    boundaries = ol.extent.applyTransform(
+      ol.extent.boundingExtent(area), ol.proj.getTransform('EPSG:4326', 'EPSG:3857')
+    )
+    @map.getView().fit boundaries, @map.getSize()
+    tooCloseToBounds = false
+    mapBoundaries = @map.getView().calculateExtent @map.getSize()
+    mapWidth = mapBoundaries[2] - mapBoundaries[0]
+    mapHeight = mapBoundaries[3] - mapBoundaries[1]
+    marginSize = 0.1
+    heightMargin = marginSize * mapHeight
+    widthMargin = marginSize * mapWidth
+    tooCloseToBounds = tooCloseToBounds || (boundaries[0] - mapBoundaries[0]) < widthMargin
+    tooCloseToBounds = tooCloseToBounds || (mapBoundaries[2] - boundaries[2]) < widthMargin
+    tooCloseToBounds = tooCloseToBounds || (boundaries[1] - mapBoundaries[1]) < heightMargin
+    tooCloseToBounds = tooCloseToBounds || (mapBoundaries[3] - boundaries[3]) < heightMargin
+    if tooCloseToBounds
+      @map.getView().setZoom(@map.getView().getZoom() - 1)
+
+
+export default RoutesMap
diff --git a/app/javascript/packs/referential_lines/show.js b/app/javascript/packs/referential_lines/show.js
new file mode 100644
index 000000000..99c5072ef
--- /dev/null
+++ b/app/javascript/packs/referential_lines/show.js
@@ -0,0 +1,10 @@
+import clone from '../../helpers/clone'
+import RoutesMap from '../../helpers/routes_map'
+
+let routes = clone(window, "routes", true)
+routes = JSON.parse(decodeURIComponent(routes))
+
+var map = new RoutesMap('routes_map')
+map.addRoutes(routes)
+map.addRoutesLabels()
+map.fitZoom()
diff --git a/app/javascript/packs/routes/show.js b/app/javascript/packs/routes/show.js
index 71777c379..c20de0800 100644
--- a/app/javascript/packs/routes/show.js
+++ b/app/javascript/packs/routes/show.js
@@ -1,121 +1,8 @@
 import clone from '../../helpers/clone'
+import RoutesMap from '../../helpers/routes_map'
+
 let route = clone(window, "route", true)
 route = JSON.parse(decodeURIComponent(route))
-
-const geoColPts = []
-const geoColLns = []
-const area = []
-const geoColEdges = [
-  new ol.Feature({
-    geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(route[0].longitude), parseFloat(route[0].latitude)]))
-  }),
-  new ol.Feature({
-    geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(route[route.length - 1].longitude), parseFloat(route[route.length - 1].latitude)]))
-  })
-]
-route.forEach(function (stop, i) {
-  if (i < route.length - 1) {
-    geoColLns.push(new ol.Feature({
-      geometry: new ol.geom.LineString([
-        ol.proj.fromLonLat([parseFloat(route[i].longitude), parseFloat(route[i].latitude)]),
-        ol.proj.fromLonLat([parseFloat(route[i + 1].longitude), parseFloat(route[i + 1].latitude)])
-      ])
-    }))
-  }
-  geoColPts.push(new ol.Feature({
-    geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stop.longitude), parseFloat(stop.latitude)]))
-  }))
-  area.push([parseFloat(stop.longitude), parseFloat(stop.latitude)])
-})
-var edgeStyles = new ol.style.Style({
-  image: new ol.style.Circle(({
-    radius: 5,
-    stroke: new ol.style.Stroke({
-      color: '#007fbb',
-      width: 2
-    }),
-    fill: new ol.style.Fill({
-      color: '#007fbb',
-      width: 2
-    })
-  }))
-})
-var defaultStyles = new ol.style.Style({
-  image: new ol.style.Circle(({
-    radius: 4,
-    stroke: new ol.style.Stroke({
-      color: '#007fbb',
-      width: 2
-    }),
-    fill: new ol.style.Fill({
-      color: '#ffffff',
-      width: 2
-    })
-  }))
-})
-var lineStyle = new ol.style.Style({
-  stroke: new ol.style.Stroke({
-    color: '#007fbb',
-    width: 3
-  })
-})
-
-var vectorPtsLayer = new ol.layer.Vector({
-  source: new ol.source.Vector({
-    features: geoColPts
-  }),
-  style: defaultStyles,
-  zIndex: 2
-})
-var vectorEdgesLayer = new ol.layer.Vector({
-  source: new ol.source.Vector({
-    features: geoColEdges
-  }),
-  style: edgeStyles,
-  zIndex: 3
-})
-var vectorLnsLayer = new ol.layer.Vector({
-  source: new ol.source.Vector({
-    features: geoColLns
-  }),
-  style: [lineStyle],
-  zIndex: 1
-})
-
-var map = new ol.Map({
-  target: 'route_map',
-  layers: [
-    new ol.layer.Tile({
-      source: new ol.source.OSM()
-    }),
-    vectorPtsLayer,
-    vectorEdgesLayer,
-    vectorLnsLayer
-  ],
-  controls: [new ol.control.ScaleLine(), new ol.control.Zoom(), new ol.control.ZoomSlider()],
-  interactions: ol.interaction.defaults({
-    zoom: true
-  }),
-  view: new ol.View({
-    center: ol.proj.fromLonLat([parseFloat(route[0].longitude), parseFloat(route[0].latitude)]),
-    zoom: 13
-  })
-});
-const boundaries = ol.extent.applyTransform(
-  ol.extent.boundingExtent(area), ol.proj.getTransform('EPSG:4326', 'EPSG:3857')
-)
-map.getView().fit(boundaries, map.getSize());
-let tooCloseToBounds = false
-const mapBoundaries = map.getView().calculateExtent(map.getSize())
-const mapWidth = mapBoundaries[2] - mapBoundaries[0]
-const mapHeight = mapBoundaries[3] - mapBoundaries[1]
-const marginSize = 0.1
-const heightMargin = marginSize * mapHeight
-const widthMargin = marginSize * mapWidth
-tooCloseToBounds = tooCloseToBounds || (boundaries[0] - mapBoundaries[0]) < widthMargin
-tooCloseToBounds = tooCloseToBounds || (mapBoundaries[2] - boundaries[2]) < widthMargin
-tooCloseToBounds = tooCloseToBounds || (boundaries[1] - mapBoundaries[1]) < heightMargin
-tooCloseToBounds = tooCloseToBounds || (mapBoundaries[3] - boundaries[3]) < heightMargin
-if(tooCloseToBounds){
-  map.getView().setZoom(map.getView().getZoom() - 1)
-}
+var map = new RoutesMap('route_map')
+map.addRoute(route)
+map.fitZoom()
diff --git a/app/javascript/routes/components/StopPointList.js b/app/javascript/routes/components/StopPointList.js
index 43a027084..b39fa0c9c 100644
--- a/app/javascript/routes/components/StopPointList.js
+++ b/app/javascript/routes/components/StopPointList.js
@@ -56,7 +56,7 @@ export default function StopPointList({ stopPoints, onDeleteClick, onMoveUpClick
   )
 }
 
-StopPointList.PropTypes = {
+StopPointList.propTypes = {
   stopPoints: PropTypes.array.isRequired,
   onDeleteClick: PropTypes.func.isRequired,
   onMoveUpClick: PropTypes.func.isRequired,
@@ -68,4 +68,4 @@ StopPointList.PropTypes = {
 
 StopPointList.contextTypes = {
   I18n: PropTypes.object
-}
\ No newline at end of file
+}
diff --git a/app/views/referential_lines/show.html.slim b/app/views/referential_lines/show.html.slim
index 02d605d8c..5ea0e31bb 100644
--- a/app/views/referential_lines/show.html.slim
+++ b/app/views/referential_lines/show.html.slim
@@ -17,7 +17,8 @@
              @line.human_attribute_name(:transport_submode) => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'),
              @line.human_attribute_name(:url) => (@line.url ? @line.url : '-'),
              @line.human_attribute_name(:seasonal) => (@line.seasonal? ? t('true') : t('false')),}
-
+      .col-lg-6.col-md-6.col-sm-12.col-xs-12
+        #routes_map.map.mb-lg
     .row
       .col-lg-12
         .h3 = t('lines.show.routes.title')
@@ -79,3 +80,8 @@
           .row.mt-xs
             .col-lg-12
               = replacement_msg t('routes.search_no_results')
+
+= javascript_tag do
+  | window.routes = "#{URI.escape(@routes.map{|r| {name: r.name, id: r.id, stops: route_json_for_edit(r, serialize: false)}}.to_json)}"
+
+= javascript_pack_tag 'referential_lines/show.js'
diff --git a/config/webpack/environment.js b/config/webpack/environment.js
index e7c879fb9..688bcbe8e 100644
--- a/config/webpack/environment.js
+++ b/config/webpack/environment.js
@@ -1,4 +1,5 @@
 const { environment } = require('@rails/webpacker')
+const coffee =  require('./loaders/coffee')
 const CleanWebpackPlugin = require('clean-webpack-plugin')
 
 let pathsToClean = [
@@ -24,4 +25,5 @@ environment.plugins.set(
 //   jquery: "jquery/src/jquery",
 // }
 
+environment.loaders.append('coffee', coffee)
 module.exports = environment
diff --git a/config/webpack/loaders/coffee.js b/config/webpack/loaders/coffee.js
new file mode 100644
index 000000000..4666716dc
--- /dev/null
+++ b/config/webpack/loaders/coffee.js
@@ -0,0 +1,6 @@
+module.exports = {
+  test: /\.coffee(\.erb)?$/,
+  use: [{
+    loader: 'coffee-loader'
+  }]
+}
diff --git a/package.json b/package.json
index 80ca22f83..802a2eef7 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
     "babel-preset-react": "6.24.1",
     "babelify": "8.0.0",
     "bootstrap": "3",
-    "coffeescript": "2.1.0",
+    "coffee-loader": "^0.9.0",
+    "coffeescript": "1.12.7",
     "jquery": "3.2.1",
     "lodash": "4.17.4",
     "promise-polyfill": "7.0.0",
diff --git a/yarn.lock b/yarn.lock
index e95ee9a63..d17ae1d52 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1404,13 +1404,19 @@ code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
+coffee-loader@^0.9.0:
+  version "0.9.0"
+  resolved "https://registry.yarnpkg.com/coffee-loader/-/coffee-loader-0.9.0.tgz#6deabd336062ddc6d773da4dfd16367fc7107bd6"
+  dependencies:
+    loader-utils "^1.0.2"
+
 coffee-script@~1.10.0:
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.10.0.tgz#12938bcf9be1948fa006f92e0c4c9e81705108c0"
 
-coffeescript@2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-2.1.0.tgz#8cb7ce12021ab9f84d8c524f54edbd6141374606"
+coffeescript@1.12.7:
+  version "1.12.7"
+  resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27"
 
 color-convert@^1.3.0, color-convert@^1.8.2, color-convert@^1.9.0:
   version "1.9.0"
-- 
cgit v1.2.3
From c1da45b2f561ab7ec8d2785bb53f25218e471ce2 Mon Sep 17 00:00:00 2001
From: Zog
Date: Fri, 26 Jan 2018 10:34:51 +0100
Subject: Remove 'rspec-snaphost' to check if it causes segfaults on travis
---
 Gemfile      | 1 -
 Gemfile.lock | 7 -------
 2 files changed, 8 deletions(-)
diff --git a/Gemfile b/Gemfile
index f22b718c3..c378820b3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -174,7 +174,6 @@ group :test do
   gem 'simplecov-rcov', :require => false
   gem 'htmlbeautifier'
   gem 'timecop'
-  gem 'rspec-snapshot'
 end
 
 group :test, :development, :dev do
diff --git a/Gemfile.lock b/Gemfile.lock
index f4dfe1bf7..ba86a911f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -444,10 +444,6 @@ GEM
     roo (2.7.1)
       nokogiri (~> 1)
       rubyzip (~> 1.1, < 2.0.0)
-    rspec (3.5.0)
-      rspec-core (~> 3.5.0)
-      rspec-expectations (~> 3.5.0)
-      rspec-mocks (~> 3.5.0)
     rspec-core (3.5.4)
       rspec-support (~> 3.5.0)
     rspec-expectations (3.5.0)
@@ -464,8 +460,6 @@ GEM
       rspec-expectations (~> 3.5.0)
       rspec-mocks (~> 3.5.0)
       rspec-support (~> 3.5.0)
-    rspec-snapshot (0.1.1)
-      rspec (> 3.0.0)
     rspec-support (3.5.0)
     ruby-graphviz (1.2.3)
     rubycas-client (2.3.9)
@@ -682,7 +676,6 @@ DEPENDENCIES
   rgeo (~> 0.5.2)
   roo
   rspec-rails (~> 3.5.0)
-  rspec-snapshot
   rubyzip
   sass-rails (~> 4.0.3)
   sawyer (~> 0.6.0)
-- 
cgit v1.2.3
From 22c38fb750843f0c74996175a6bd17a1f20a943c Mon Sep 17 00:00:00 2001
From: Zog
Date: Fri, 26 Jan 2018 16:17:22 +0100
Subject: Refs #5750 @1h; Add a "kind" attribute to StopAreas
This determines if the StopArea is commercial or not
The useless fields are hidden in the form for the non-commercials ones
---
 app/assets/stylesheets/components/_forms.sass      |  8 ++++++-
 app/javascript/helpers/master_slave.coffee         | 18 ++++++++++++++++
 app/javascript/packs/stop_areas/new.js             |  3 +++
 app/models/chouette/area_type.rb                   | 25 ++++++++++++++++------
 app/models/chouette/stop_area.rb                   |  9 ++++++++
 app/views/stop_areas/_form.html.slim               | 10 +++++++--
 app/views/stop_areas/show.html.slim                |  8 +++----
 config/locales/area_types.en.yml                   |  6 ++++++
 config/locales/area_types.fr.yml                   |  6 ++++++
 .../20180126134944_add_kind_to_stop_areas.rb       |  5 +++++
 db/schema.rb                                       |  9 ++++++--
 spec/models/chouette/area_type_spec.rb             |  4 +++-
 12 files changed, 94 insertions(+), 17 deletions(-)
 create mode 100644 app/javascript/helpers/master_slave.coffee
 create mode 100644 app/javascript/packs/stop_areas/new.js
 create mode 100644 db/migrate/20180126134944_add_kind_to_stop_areas.rb
diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass
index b7f720963..214795b8b 100644
--- a/app/assets/stylesheets/components/_forms.sass
+++ b/app/assets/stylesheets/components/_forms.sass
@@ -85,9 +85,15 @@ input
 
 // BS horizontal form label positionning fix
 .form-horizontal
+  input[type="radio"].form-control
+    height: auto
+    width: auto
   .form-group
     position: relative
-
+    .radio-inline
+      padding-top: 4px
+      &:first-child
+        padding-left: 0
     > .control-label
       &[class*='col-sm-']
         float: none
diff --git a/app/javascript/helpers/master_slave.coffee b/app/javascript/helpers/master_slave.coffee
new file mode 100644
index 000000000..11f6bca7e
--- /dev/null
+++ b/app/javascript/helpers/master_slave.coffee
@@ -0,0 +1,18 @@
+class MasterSlave
+  constructor: (selector)->
+    $(selector).find('[data-master]').each (i, slave)->
+      $slave = $(slave)
+      master = $($slave.data().master)
+      console.log $slave.data().master
+      console.log master
+      toggle = ->
+        val = master.filter(":checked").val() if master.filter("[type=radio]").length > 0
+        val ||= master.val()
+        selected = val == $slave.data().value
+        $slave.toggle selected
+        $slave.find("input, select").attr "disabled", !selected
+      master.change toggle
+      toggle()
+      # $slave.toggle master.val() == $slave.data().value
+
+export default MasterSlave
diff --git a/app/javascript/packs/stop_areas/new.js b/app/javascript/packs/stop_areas/new.js
new file mode 100644
index 000000000..ffe702cdb
--- /dev/null
+++ b/app/javascript/packs/stop_areas/new.js
@@ -0,0 +1,3 @@
+import MasterSlave from "../../helpers/master_slave"
+
+new MasterSlave("form")
diff --git a/app/models/chouette/area_type.rb b/app/models/chouette/area_type.rb
index 4703ea646..e17d2ee8d 100644
--- a/app/models/chouette/area_type.rb
+++ b/app/models/chouette/area_type.rb
@@ -1,13 +1,22 @@
 class Chouette::AreaType
   include Comparable
 
-  ALL = %i(zdep zder zdlp zdlr lda gdl).freeze
+  COMMERCIAL = %i(zdep zder zdlp zdlr lda gdl).freeze
+  NON_COMMERCIAL = %i(deposit border service_area relief other).freeze
+  ALL = COMMERCIAL + NON_COMMERCIAL
 
+  @@commercial = COMMERCIAL
+  @@non_commercial = NON_COMMERCIAL
   @@all = ALL
-  mattr_accessor :all
+  mattr_accessor :all, :commercial, :non_commercial
 
-  def self.all=(values)
-    @@all = ALL & values
+  def self.commercial=(values)
+    @@commercial = COMMERCIAL & values
+    reset_caches!
+  end
+
+  def self.non_commercial=(values)
+    @@non_commercial = NON_COMMERCIAL & values
     reset_caches!
   end
 
@@ -20,12 +29,14 @@ class Chouette::AreaType
   end
 
   def self.reset_caches!
+    @@all = @@commercial + @@non_commercial
     @@instances = {}
-    @@options = nil
+    @@options = {}
   end
 
-  def self.options
-    @@options ||= all.map { |c| find(c) }.map { |t| [ t.label, t.code ] }
+  def self.options(kind=:all)
+    @@options ||= {}
+    @@options[kind] ||= self.send(kind).map { |c| find(c) }.map { |t| [ t.label, t.code ] }
   end
 
   attr_reader :code
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index ea1855ea8..d270a8696 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -10,6 +10,7 @@ module Chouette
 
     extend Enumerize
     enumerize :area_type, in: Chouette::AreaType::ALL
+    enumerize :kind, in: %i(commercial non_commercial)
 
     with_options dependent: :destroy do |assoc|
       assoc.has_many :stop_points
@@ -96,6 +97,10 @@ module Chouette
       end
     end
 
+    def local_id
+      id.to_s
+    end
+
     def children_in_depth
       return [] if self.children.empty?
 
@@ -374,5 +379,9 @@ module Chouette
       return nil unless time_zone.present?
       ActiveSupport::TimeZone[time_zone]&.formatted_offset
     end
+
+    def commercial?
+      kind == "commercial"
+    end
   end
 end
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index b2322f73a..699381d50 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -7,9 +7,13 @@
         = f.input :id, as: :hidden
         = f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.name")}
 
-        = f.input :parent_id, as: :select, :collection => [f.object.parent_id], input_html: { data: { select2_ajax: 'true', url: autocomplete_stop_area_referential_stop_areas_path(@stop_area_referential), initvalue: {id: f.object.parent_id, text: f.object.parent.try(:full_name)}}}
+        = f.input :kind, as: :radio_buttons, :input_html => {:disabled => !@stop_area.new_record?}, :include_blank => false, item_wrapper_class: 'radio-inline', wrapper: :horizontal_form, item_class: "fooo"
 
-        = f.input :area_type, as: :select, :input_html => {:disabled => !@stop_area.new_record?}, :collection => Chouette::AreaType.options, :include_blank => false
+        .slave data-master="[name='stop_area[kind]']" data-value="commercial"
+          = f.input :parent_id, as: :select, :collection => [f.object.parent_id], input_html: { data: { select2_ajax: 'true', url: autocomplete_stop_area_referential_stop_areas_path(@stop_area_referential), initvalue: {id: f.object.parent_id, text: f.object.parent.try(:full_name)}}}
+        - %i(commercial non_commercial).each do |kind|
+          .slave data-master="[name='stop_area[kind]']" data-value=kind
+            = f.input :area_type, as: :select, :input_html => {:disabled => !@stop_area.new_record?}, :collection => Chouette::AreaType.options(kind), :include_blank => false
 
         .location_info
           h3 = t("stop_areas.stop_area.localisation")
@@ -49,3 +53,5 @@
   .separator
 
   = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'stop_area_form'
+
+= javascript_pack_tag "stop_areas/new"
diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim
index b5ec8ac00..b0896c1e0 100644
--- a/app/views/stop_areas/show.html.slim
+++ b/app/views/stop_areas/show.html.slim
@@ -6,11 +6,11 @@
   .container-fluid
     .row
       .col-lg-6.col-md-6.col-sm-12.col-xs-12
-        - attributes = { t('id_reflex') => @stop_area.get_objectid.short_id,
-            @stop_area.human_attribute_name(:parent) => @stop_area.parent ? link_to(@stop_area.parent.name, stop_area_referential_stop_area_path(@stop_area_referential, @stop_area.parent)) : "-",
-            @stop_area.human_attribute_name(:stop_area_type) => Chouette::AreaType.find(@stop_area.area_type).try(:label),
+        - attributes = { t('id_reflex') => @stop_area.get_objectid.short_id }
+        - attributes.merge!({ @stop_area.human_attribute_name(:parent) => @stop_area.parent ? link_to(@stop_area.parent.name, stop_area_referential_stop_area_path(@stop_area_referential, @stop_area.parent)) : "-" }) if @stop_area.commercial?
+        - attributes.merge!({ @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,
-            }
+            })
         - 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,
diff --git a/config/locales/area_types.en.yml b/config/locales/area_types.en.yml
index 34ec3243d..5d23a6665 100644
--- a/config/locales/area_types.en.yml
+++ b/config/locales/area_types.en.yml
@@ -6,3 +6,9 @@ en:
       zdlp: ZDLp
       zdlr: ZDLr
       lda: LDA
+      gdl: GDL
+      deposit: Deposit
+      border: Border
+      service_area: Service Area
+      relief: Relief point
+      other: Other
diff --git a/config/locales/area_types.fr.yml b/config/locales/area_types.fr.yml
index fd4e1e741..bb249c235 100644
--- a/config/locales/area_types.fr.yml
+++ b/config/locales/area_types.fr.yml
@@ -6,3 +6,9 @@ fr:
       zdlp: ZDLp
       zdlr: ZDLr
       lda: LDA
+      gdl: GDL
+      deposit: Dépôt
+      border: Frontière
+      service_area: Aire de service / Pause
+      relief: Point de releve
+      other: Autre
diff --git a/db/migrate/20180126134944_add_kind_to_stop_areas.rb b/db/migrate/20180126134944_add_kind_to_stop_areas.rb
new file mode 100644
index 000000000..3a4f0a0c8
--- /dev/null
+++ b/db/migrate/20180126134944_add_kind_to_stop_areas.rb
@@ -0,0 +1,5 @@
+class AddKindToStopAreas < ActiveRecord::Migration
+  def change
+    add_column :stop_areas, :kind, :string
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2c5520110..b2063539b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,13 +11,14 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20180111200406) do
+ActiveRecord::Schema.define(version: 20180126134944) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
-  enable_extension "postgis"
   enable_extension "hstore"
+  enable_extension "postgis"
   enable_extension "unaccent"
+  enable_extension "objectid"
 
   create_table "access_links", id: :bigserial, force: :cascade do |t|
     t.integer  "access_point_id",                        limit: 8
@@ -90,6 +91,8 @@ ActiveRecord::Schema.define(version: 20180111200406) do
     t.integer   "organisation_id", limit: 8
     t.datetime  "created_at"
     t.datetime  "updated_at"
+    t.integer   "int_day_types"
+    t.date      "excluded_dates",                            array: true
   end
 
   add_index "calendars", ["organisation_id"], name: "index_calendars_on_organisation_id", using: :btree
@@ -115,6 +118,7 @@ ActiveRecord::Schema.define(version: 20180111200406) do
     t.datetime "updated_at"
     t.date     "end_date"
     t.string   "date_type"
+    t.string   "mode"
   end
 
   add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree
@@ -786,6 +790,7 @@ ActiveRecord::Schema.define(version: 20180111200406) do
     t.datetime "updated_at"
     t.string   "stif_type"
     t.integer  "waiting_time"
+    t.string   "kind"
   end
 
   add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree
diff --git a/spec/models/chouette/area_type_spec.rb b/spec/models/chouette/area_type_spec.rb
index 67d218df8..28325dd0a 100644
--- a/spec/models/chouette/area_type_spec.rb
+++ b/spec/models/chouette/area_type_spec.rb
@@ -4,7 +4,9 @@ RSpec.describe Chouette::AreaType do
 
   describe "::ALL" do
     it "includes all supported types" do
-      expect(Chouette::AreaType::ALL).to match_array( %i(zdep zder zdlp zdlr lda gdl) )
+      expect(Chouette::AreaType::ALL).to match_array( %i(zdep zder zdlp zdlr lda gdl deposit border service_area relief other) )
+      expect(Chouette::AreaType::COMMERCIAL).to match_array( %i(zdep zder zdlp zdlr lda gdl) )
+      expect(Chouette::AreaType::NON_COMMERCIAL).to match_array( %i( deposit border service_area relief other) )
     end
   end
 
-- 
cgit v1.2.3
From 05bc96db48a0a84fd2c50e457dc767f88950a9b4 Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 08:45:02 +0100
Subject: Refs #5750 @1h; Manage non-commercial StopAreas
- Add a `kind` attribute
- Hide irrelevant fields in the form
---
 app/assets/stylesheets/modules/_vj_collection.sass  |  3 +++
 app/controllers/stop_areas_controller.rb            |  1 +
 app/controllers/vehicle_journeys_controller.rb      |  2 ++
 app/javascript/helpers/master_slave.coffee          |  8 ++++----
 app/javascript/helpers/stop_area_header_manager.js  |  4 +++-
 app/models/chouette/stop_area.rb                    |  8 ++++++++
 app/policies/stop_area_policy.rb                    |  2 +-
 app/views/stop_areas/_form.html.slim                |  6 +++---
 config/locales/stop_areas.fr.yml                    |  2 ++
 db/migrate/20180126134944_add_kind_to_stop_areas.rb |  1 +
 spec/models/chouette/stop_area_spec.rb              | 10 ++++++++++
 11 files changed, 38 insertions(+), 9 deletions(-)
diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass
index 56769e52b..81c1fe43e 100644
--- a/app/assets/stylesheets/modules/_vj_collection.sass
+++ b/app/assets/stylesheets/modules/_vj_collection.sass
@@ -9,6 +9,9 @@
         position: relative
         padding-left: 25px
 
+        .fa
+          margin-left: 5px
+
         > .headlined
           &:before
             margin-left: -25px
diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb
index 79ffea72e..8e9df7157 100644
--- a/app/controllers/stop_areas_controller.rb
+++ b/app/controllers/stop_areas_controller.rb
@@ -203,6 +203,7 @@ class StopAreasController < ChouetteController
       :url,
       :waiting_time,
       :zip_code,
+      :kind,
     )
   end
 
diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb
index c1762c13e..ed6ba6ed1 100644
--- a/app/controllers/vehicle_journeys_controller.rb
+++ b/app/controllers/vehicle_journeys_controller.rb
@@ -66,6 +66,8 @@ class VehicleJourneysController < ChouetteController
             :city_name => sp.stop_area.try(:city_name),
             :comment => sp.stop_area.try(:comment),
             :area_type => sp.stop_area.try(:area_type),
+            :area_type_i18n => I18n.t(sp.stop_area.try(:area_type), scope: 'area_types.label'),
+            :area_kind => sp.stop_area.try(:kind),
             :stop_area_id => sp.stop_area_id,
             :registration_number => sp.stop_area.try(:registration_number),
             :nearest_topic_name => sp.stop_area.try(:nearest_topic_name),
diff --git a/app/javascript/helpers/master_slave.coffee b/app/javascript/helpers/master_slave.coffee
index 11f6bca7e..4866a55e3 100644
--- a/app/javascript/helpers/master_slave.coffee
+++ b/app/javascript/helpers/master_slave.coffee
@@ -3,16 +3,16 @@ class MasterSlave
     $(selector).find('[data-master]').each (i, slave)->
       $slave = $(slave)
       master = $($slave.data().master)
-      console.log $slave.data().master
-      console.log master
+      console.log $slave
+      console.log $slave.find("input:disabled, select:disabled")
+      $slave.find("input:disabled, select:disabled").attr "data-slave-force-disabled", "true"
       toggle = ->
         val = master.filter(":checked").val() if master.filter("[type=radio]").length > 0
         val ||= master.val()
         selected = val == $slave.data().value
         $slave.toggle selected
-        $slave.find("input, select").attr "disabled", !selected
+        $slave.find("input, select").filter(":not([data-slave-force-disabled])").attr "disabled", !selected
       master.change toggle
       toggle()
-      # $slave.toggle master.val() == $slave.data().value
 
 export default MasterSlave
diff --git a/app/javascript/helpers/stop_area_header_manager.js b/app/javascript/helpers/stop_area_header_manager.js
index c9f397dee..2c820caf9 100644
--- a/app/javascript/helpers/stop_area_header_manager.js
+++ b/app/javascript/helpers/stop_area_header_manager.js
@@ -19,7 +19,7 @@ export default class StopAreaHeaderManager {
       
         
           
@@ -27,6 +27,8 @@ export default class StopAreaHeaderManager {
             {sp.time_zone_formatted_offset && 
                ({sp.time_zone_formatted_offset})
             }
+            {sp.area_kind == 'non_commercial' && 
+            }
           
         
       
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index d270a8696..75a4a34bb 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -32,6 +32,7 @@ module Chouette
 
     validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_blank => true
     validates_presence_of :name
+    validates_presence_of :kind
     validates_presence_of :latitude, :if => :longitude
     validates_presence_of :longitude, :if => :latitude
     validates_numericality_of :latitude, :less_than_or_equal_to => 90, :greater_than_or_equal_to => -90, :allow_nil => true
@@ -42,6 +43,7 @@ module Chouette
 
     validates_numericality_of :waiting_time, greater_than_or_equal_to: 0, only_integer: true, if: :waiting_time
     validate :parent_area_type_must_be_greater
+    validate :area_type_of_right_kind
 
     def self.nullable_attributes
       [:registration_number, :street_name, :country_code, :fare_code,
@@ -57,6 +59,12 @@ module Chouette
       end
     end
 
+    def area_type_of_right_kind
+      unless Chouette::AreaType.send(self.kind).include?(self.area_type)
+        errors.add(:area_type, I18n.t('stop_areas.errors.incorrect_kind_area_type'))
+      end
+    end
+
     after_update :clean_invalid_access_links
     before_save :coordinates_to_lat_lng
 
diff --git a/app/policies/stop_area_policy.rb b/app/policies/stop_area_policy.rb
index 6db48b702..fd73b7092 100644
--- a/app/policies/stop_area_policy.rb
+++ b/app/policies/stop_area_policy.rb
@@ -3,7 +3,7 @@ class StopAreaPolicy < ApplicationPolicy
     def search_scope scope_name
       scope = resolve
       if scope_name&.to_s == "route_editor"
-        scope = scope.where(area_type: 'zdep') unless user.organisation.has_feature?("route_stop_areas_all_types")
+        scope = scope.where("kind = ? OR area_type = ?", :non_commercial, 'zdep') unless user.organisation.has_feature?("route_stop_areas_all_types")
       end
       scope
     end
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index 699381d50..6b75209b4 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -7,13 +7,13 @@
         = f.input :id, as: :hidden
         = f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.name")}
 
-        = f.input :kind, as: :radio_buttons, :input_html => {:disabled => !@stop_area.new_record?}, :include_blank => false, item_wrapper_class: 'radio-inline', wrapper: :horizontal_form, item_class: "fooo"
+        = f.input :kind, as: :radio_buttons, :input_html => {:disabled => !@stop_area.new_record?}, :include_blank => false, item_wrapper_class: 'radio-inline', wrapper: :horizontal_form, item_class: "fooo", disabled: !@stop_area.new_record?
 
         .slave data-master="[name='stop_area[kind]']" data-value="commercial"
           = f.input :parent_id, as: :select, :collection => [f.object.parent_id], input_html: { data: { select2_ajax: 'true', url: autocomplete_stop_area_referential_stop_areas_path(@stop_area_referential), initvalue: {id: f.object.parent_id, text: f.object.parent.try(:full_name)}}}
-        - %i(commercial non_commercial).each do |kind|
+        - %i(non_commercial commercial).each do |kind|
           .slave data-master="[name='stop_area[kind]']" data-value=kind
-            = f.input :area_type, as: :select, :input_html => {:disabled => !@stop_area.new_record?}, :collection => Chouette::AreaType.options(kind), :include_blank => false
+            = f.input :area_type, as: :select, :input_html => {id: kind, :disabled => !@stop_area.new_record?}, :collection => Chouette::AreaType.options(kind), :include_blank => false, disabled: !@stop_area.new_record?
 
         .location_info
           h3 = t("stop_areas.stop_area.localisation")
diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml
index 0095bbe6d..283000960 100644
--- a/config/locales/stop_areas.fr.yml
+++ b/config/locales/stop_areas.fr.yml
@@ -5,6 +5,7 @@ fr:
     errors:
       empty: Aucun stop_area_id
       parent_area_type: ne peut être de type %{area_type}
+      incorrect_kind_area_type: Ce type d'arrêt est invalide pour cette catégorie
     default_geometry_success: "%{count} arrêts édités"
     stop_area:
       no_position: "Pas de position"
@@ -97,6 +98,7 @@ fr:
     attributes:
       stop_area:
         name: "Nom"
+        kind: "Catégorie"
         registration_number: "Numéro d'enregistrement"
         published_name: "Nom public"
         deleted: "Supprimé"
diff --git a/db/migrate/20180126134944_add_kind_to_stop_areas.rb b/db/migrate/20180126134944_add_kind_to_stop_areas.rb
index 3a4f0a0c8..7da227cd9 100644
--- a/db/migrate/20180126134944_add_kind_to_stop_areas.rb
+++ b/db/migrate/20180126134944_add_kind_to_stop_areas.rb
@@ -1,5 +1,6 @@
 class AddKindToStopAreas < ActiveRecord::Migration
   def change
     add_column :stop_areas, :kind, :string
+    Chouette::StopArea.update_all kind: :commmercial
   end
 end
diff --git a/spec/models/chouette/stop_area_spec.rb b/spec/models/chouette/stop_area_spec.rb
index a90e5d816..32ee5a3a6 100644
--- a/spec/models/chouette/stop_area_spec.rb
+++ b/spec/models/chouette/stop_area_spec.rb
@@ -10,10 +10,20 @@ describe Chouette::StopArea, :type => :model do
 
   it { should belong_to(:stop_area_referential) }
   it { should validate_presence_of :name }
+  it { should validate_presence_of :kind }
   it { should validate_numericality_of :latitude }
   it { should validate_numericality_of :longitude }
   it { is_expected.to be_versioned }
 
+  describe "#area_type" do
+    it "should validate the value is correct regarding to the kind" do
+      expect(build(:stop_area, kind: :commercial, area_type: :gdl)).to be_valid
+      expect(build(:stop_area, kind: :non_commercial, area_type: :relief)).to be_valid
+      expect(build(:stop_area, kind: :commercial, area_type: :relief)).to_not be_valid
+      expect(build(:stop_area, kind: :non_commercial, area_type: :gdl)).to_not be_valid
+    end
+  end
+
   # describe ".latitude" do
   #   it "should accept -90 value" do
   #     subject = create :stop_area, :area_type => "BoardingPosition"
-- 
cgit v1.2.3
From ddd83906ce4fab3d6dce0c404ec39c3b500ba96f Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 10:32:05 +0100
Subject: Refs #5750; Add a validation on VehicleJourneys
Ensure a time is set for all non-commercial stops
---
 app/assets/stylesheets/modules/_vj_collection.sass |  3 ++
 app/javascript/vehicle_journeys/actions/index.js   | 26 ++++++++++++++
 .../components/SaveVehicleJourneys.js              |  2 +-
 .../vehicle_journeys/components/VehicleJourney.js  |  3 ++
 .../vehicle_journeys/components/VehicleJourneys.js |  2 +-
 .../containers/SaveVehicleJourneys.js              |  3 ++
 .../vehicle_journeys/reducers/vehicleJourneys.js   |  2 ++
 app/views/vehicle_journeys/show.rabl               |  1 +
 spec/javascript/vehicle_journeys/actions_spec.js   | 41 ++++++++++++++++++++++
 9 files changed, 81 insertions(+), 2 deletions(-)
diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass
index 81c1fe43e..d99c67bd7 100644
--- a/app/assets/stylesheets/modules/_vj_collection.sass
+++ b/app/assets/stylesheets/modules/_vj_collection.sass
@@ -116,6 +116,9 @@
           margin-left: 5px
 
       &.has-error
+        .errors
+          color: $red
+          font-size: 0.8em
         &:before
           content: ''
           position: absolute
diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js
index 2675328e3..b01158212 100644
--- a/app/javascript/vehicle_journeys/actions/index.js
+++ b/app/javascript/vehicle_journeys/actions/index.js
@@ -380,6 +380,32 @@ const actions = {
         }
       })
   },
+
+  validate : (dispatch, vehicleJourneys, next) => {
+    let valid = true
+    let vj, vjas
+    for (vj of vehicleJourneys){
+      vj.errors = false
+      for(vjas of vj.vehicle_journey_at_stops){
+        vjas.errors = null
+        if (vjas.area_kind == "non_commercial" && parseInt(vjas.departure_time.hour) == 0 && parseInt(vjas.departure_time.minute) == 0){
+          vjas.errors = "Champ requis"
+          vj.errors = true
+          valid = false
+        }
+      }
+    }
+    dispatch(actions.didValidateVehicleJourneys(vehicleJourneys))
+    if(valid){
+      actions.submitVehicleJourneys(dispatch, vehicleJourneys, next)
+    }
+  },
+
+  didValidateVehicleJourneys : (vehicleJourneys) => ({
+    type: 'DID_VALIDATE_VEHICLE_JOURNEYS',
+    vehicleJourneys
+  }),
+
   submitVehicleJourneys : (dispatch, state, next) => {
     dispatch(actions.fetchingApi())
     let urlJSON = window.location.pathname + "_collection.json"
diff --git a/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js b/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js
index 6e94b04a3..fb921df9c 100644
--- a/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js
@@ -13,7 +13,7 @@ export default class SaveVehicleJourneys extends SaveButton{
   }
 
   submitForm(){
-    this.props.onSubmitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys)
+    this.props.validate(this.props.vehicleJourneys, this.props.dispatch)
   }
 }
 
diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js
index d240757a3..2b5783dda 100644
--- a/app/javascript/vehicle_journeys/components/VehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js
@@ -153,6 +153,9 @@ export default class VehicleJourney extends Component {
                       />
                   
                 
+                {vj.errors && 
+                  {vj.errors}
+                
}
             
           
         )}
diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js
index b188962c2..256ca81f9 100644
--- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js
@@ -89,7 +89,7 @@ export default class VehicleJourneys extends Component {
               
             )}
 
-            { _.some(this.props.vehicleJourneys, 'errors') && (
+            { this.props.vehicleJourneys.errors && this.props.vehicleJourneys.errors.length && _.some(this.props.vehicleJourneys, 'errors') && (
               
                 Erreur : 
                 {this.props.vehicleJourneys.map((vj, index) =>
diff --git a/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js b/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js
index f5f879ed8..3daf831f8 100644
--- a/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js
@@ -23,6 +23,9 @@ const mapDispatchToProps = (dispatch) => {
     },
     onSubmitVehicleJourneys: (next, state) => {
       actions.submitVehicleJourneys(dispatch, state, next)
+    },
+    validate: (state) =>{
+      actions.validate(dispatch, state)
     }
   }
 }
diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
index ae45993a8..1a15ec46d 100644
--- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
@@ -273,6 +273,8 @@ export default function vehicleJourneys(state = [], action) {
           return vj
         }
       })
+    case 'DID_VALIDATE_VEHICLE_JOURNEYS':
+      return [...action.vehicleJourneys]
     default:
       return state
   }
diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl
index fc65e6cb6..dca0866b3 100644
--- a/app/views/vehicle_journeys/show.rabl
+++ b/app/views/vehicle_journeys/show.rabl
@@ -45,6 +45,7 @@ child(:vehicle_journey_at_stops_matrix, :object_root => false) do |vehicle_stops
     end
 
     node(:dummy) { vehicle_stop.dummy }
+    node(:area_kind) { vehicle_stop.stop_point.stop_area.kind }
 
     node(:stop_area_object_id) do
       vehicle_stop.stop_point.stop_area.objectid
diff --git a/spec/javascript/vehicle_journeys/actions_spec.js b/spec/javascript/vehicle_journeys/actions_spec.js
index 9515b57f2..d486c9af8 100644
--- a/spec/javascript/vehicle_journeys/actions_spec.js
+++ b/spec/javascript/vehicle_journeys/actions_spec.js
@@ -37,6 +37,47 @@ describe('when clicking on add button', () => {
     expect(actions.openCreateModal()).toEqual(expectedAction)
   })
 })
+describe('when validating the form', () => {
+  it('should check that non-commercial stops have passing time', () => {
+    let state = [{
+      vehicle_journey_at_stops: [{
+        area_kind: "non_commercial",
+        departure_time: {
+          hour: "00",
+          minute: "00"
+        }
+      }]
+    }]
+
+    expect(actions.validate(dispatch, state)).toEqual(false)
+
+    state = [{
+      vehicle_journey_at_stops: [{
+        area_kind: "non_commercial",
+        departure_time: {
+          hour: "00",
+          minute: "01"
+        }
+      }]
+    }]
+
+    expect(actions.validate(dispatch, state)).toEqual(true)
+  })
+
+  it('should not check that commercial stops', () => {
+    let state = [{
+      vehicle_journey_at_stops: [{
+        area_kind: "commercial",
+        departure_time: {
+          hour: "00",
+          minute: "00"
+        }
+      }]
+    }]
+
+    expect(actions.validate(dispatch, state)).toEqual(true)
+  })
+})
 describe('when using select2 to pick a journey pattern', () => {
   it('should create an action to select a journey pattern inside modal', () => {
     let selectedJP = {
-- 
cgit v1.2.3
From 82ecc871b8b9409b09fc89f41ff9d65640e6f8ca Mon Sep 17 00:00:00 2001
From: Teddy Wing
Date: Tue, 30 Jan 2018 14:48:01 +0100
Subject: VehicleJourney: Allow deletion of associated company
If the state doesn't contain a company, that means it was deleted on the
frontend. In that case, the backend code should correctly delete the
associated company from the vehicle journey.
Refs #5574
---
 app/models/chouette/vehicle_journey.rb       | 8 ++++++--
 spec/models/chouette/vehicle_journey_spec.rb | 8 ++++++++
 2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index 8a704d8c0..5cd87a66a 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -221,9 +221,13 @@ module Chouette
 
     def self.state_permited_attributes item
       attrs = item.slice('published_journey_identifier', 'published_journey_name', 'journey_pattern_id', 'company_id').to_hash
-      ['company', 'journey_pattern'].map do |association|
-        attrs["#{association}_id"] = item[association]['id'] if item[association]
+
+      if item['journey_pattern']
+        attrs['journey_pattern_id'] = item['journey_pattern']['id']
       end
+
+      attrs['company_id'] = item['company'] ? item['company']['id'] : nil
+
       attrs["custom_field_values"] = Hash[*(item["custom_fields"] || {}).map{|k, v| [k, v["value"]]}.flatten]
       attrs
     end
diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb
index 2a88ac3ce..21bbc1ba8 100644
--- a/spec/models/chouette/vehicle_journey_spec.rb
+++ b/spec/models/chouette/vehicle_journey_spec.rb
@@ -231,6 +231,14 @@ describe Chouette::VehicleJourney, :type => :model do
       expect(vehicle_journey.reload.company_id).to eq state['company']['id']
     end
 
+    it "handles vehicle journey company deletion" do
+      vehicle_journey.update(company: create(:company))
+      state.delete('company')
+      Chouette::VehicleJourney.state_update(route, collection)
+
+      expect(vehicle_journey.reload.company_id).to be_nil
+    end
+
     it 'should update vj attributes from state' do
       state['published_journey_name']       = 'edited_name'
       state['published_journey_identifier'] = 'edited_identifier'
-- 
cgit v1.2.3
From 883889a9b23feea6530936b8371c6557f5a4956a Mon Sep 17 00:00:00 2001
From: Teddy Wing
Date: Tue, 30 Jan 2018 14:53:34 +0100
Subject: VehicleJourney.state_permited_attributes: Fix whitespace
Add line breaks to make this method more readable.
Refs #5574
---
 app/models/chouette/vehicle_journey.rb | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index 5cd87a66a..d94b69271 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -220,7 +220,12 @@ module Chouette
     end
 
     def self.state_permited_attributes item
-      attrs = item.slice('published_journey_identifier', 'published_journey_name', 'journey_pattern_id', 'company_id').to_hash
+      attrs = item.slice(
+        'published_journey_identifier',
+        'published_journey_name',
+        'journey_pattern_id',
+        'company_id'
+      ).to_hash
 
       if item['journey_pattern']
         attrs['journey_pattern_id'] = item['journey_pattern']['id']
@@ -228,7 +233,12 @@ module Chouette
 
       attrs['company_id'] = item['company'] ? item['company']['id'] : nil
 
-      attrs["custom_field_values"] = Hash[*(item["custom_fields"] || {}).map{|k, v| [k, v["value"]]}.flatten]
+      attrs["custom_field_values"] = Hash[
+        *(item["custom_fields"] || {})
+          .map { |k, v| [k, v["value"]] }
+          .flatten
+      ]
+
       attrs
     end
 
-- 
cgit v1.2.3
From 249ecd1c6df661ffed558edd5293c4d8d8115658 Mon Sep 17 00:00:00 2001
From: Luc Donnet
Date: Tue, 30 Jan 2018 16:37:53 +0100
Subject: Update companies.en.yml
Fix translation in companies.en.yml---
 config/locales/companies.en.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/locales/companies.en.yml b/config/locales/companies.en.yml
index 29dc3911b..becb087b1 100644
--- a/config/locales/companies.en.yml
+++ b/config/locales/companies.en.yml
@@ -1,7 +1,7 @@
 en:
   companies: &en_companies
     search_no_results: "No company matching your query"
-    search_no_results_for_filter: "No company has been set for thess journeys"
+    search_no_results_for_filter: "No company has been set for these journeys"
     actions:
       new: "Add a new company"
       edit: "Edit this company"
-- 
cgit v1.2.3
From e54ee3a4379afb763906ab2ba2fd980349c48334 Mon Sep 17 00:00:00 2001
From: cedricnjanga
Date: Tue, 30 Jan 2018 23:26:45 -0800
Subject: Refs ##5370 Set network action_link to disabled if line doesn't have
 one
---
 app/decorators/line_decorator.rb | 1 +
 1 file changed, 1 insertion(+)
diff --git a/app/decorators/line_decorator.rb b/app/decorators/line_decorator.rb
index 9171a6310..adeb89f70 100644
--- a/app/decorators/line_decorator.rb
+++ b/app/decorators/line_decorator.rb
@@ -20,6 +20,7 @@ class LineDecorator < AF83::Decorator
     instance_decorator.action_link secondary: :show do |l|
       l.content t('lines.actions.show_network')
       l.href   { [context[:line_referential], object.network] }
+      l.disabled { object.network.nil? }
     end
 
     instance_decorator.action_link secondary: :show do |l|
-- 
cgit v1.2.3
From 0101f0ca0a7354420118b0470532f804674edc34 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 10:46:02 +0100
Subject: Refs #5750; Fix validation
---
 app/controllers/stop_areas_controller.rb            | 2 +-
 app/models/chouette/stop_area.rb                    | 3 ++-
 app/views/stop_areas/_form.html.slim                | 3 +--
 db/migrate/20180126134944_add_kind_to_stop_areas.rb | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb
index 8e9df7157..8d424b8d1 100644
--- a/app/controllers/stop_areas_controller.rb
+++ b/app/controllers/stop_areas_controller.rb
@@ -97,7 +97,7 @@ class StopAreasController < ChouetteController
     edit! do
       stop_area.position ||= stop_area.default_position
       map.editable = true
-   end
+    end
   end
 
   def destroy
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index 75a4a34bb..ad42d54ae 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -60,7 +60,8 @@ module Chouette
     end
 
     def area_type_of_right_kind
-      unless Chouette::AreaType.send(self.kind).include?(self.area_type)
+
+      unless Chouette::AreaType.send(self.kind).map(&:to_s).include?(self.area_type)
         errors.add(:area_type, I18n.t('stop_areas.errors.incorrect_kind_area_type'))
       end
     end
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index 6b75209b4..aa156f7bd 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -6,8 +6,7 @@
         /= @map.to_html
         = f.input :id, as: :hidden
         = f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.name")}
-
-        = f.input :kind, as: :radio_buttons, :input_html => {:disabled => !@stop_area.new_record?}, :include_blank => false, item_wrapper_class: 'radio-inline', wrapper: :horizontal_form, item_class: "fooo", disabled: !@stop_area.new_record?
+        = f.input :kind, as: :radio_buttons, checked: @stop_area.kind, :input_html => {:disabled => !@stop_area.new_record?}, :include_blank => false, item_wrapper_class: 'radio-inline', wrapper: :horizontal_form, disabled: !@stop_area.new_record?
 
         .slave data-master="[name='stop_area[kind]']" data-value="commercial"
           = f.input :parent_id, as: :select, :collection => [f.object.parent_id], input_html: { data: { select2_ajax: 'true', url: autocomplete_stop_area_referential_stop_areas_path(@stop_area_referential), initvalue: {id: f.object.parent_id, text: f.object.parent.try(:full_name)}}}
diff --git a/db/migrate/20180126134944_add_kind_to_stop_areas.rb b/db/migrate/20180126134944_add_kind_to_stop_areas.rb
index 7da227cd9..08f54a6c5 100644
--- a/db/migrate/20180126134944_add_kind_to_stop_areas.rb
+++ b/db/migrate/20180126134944_add_kind_to_stop_areas.rb
@@ -1,6 +1,6 @@
 class AddKindToStopAreas < ActiveRecord::Migration
   def change
     add_column :stop_areas, :kind, :string
-    Chouette::StopArea.update_all kind: :commmercial
+    Chouette::StopArea.where.not(kind: :non_commercial).update_all kind: :commercial
   end
 end
-- 
cgit v1.2.3
From bb62bc2028f142e953035680b2483ee6029711ae Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 11:14:23 +0100
Subject: Disable immature feature
---
 app/javascript/packs/referential_lines/show.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/packs/referential_lines/show.js b/app/javascript/packs/referential_lines/show.js
index 99c5072ef..542188018 100644
--- a/app/javascript/packs/referential_lines/show.js
+++ b/app/javascript/packs/referential_lines/show.js
@@ -6,5 +6,5 @@ routes = JSON.parse(decodeURIComponent(routes))
 
 var map = new RoutesMap('routes_map')
 map.addRoutes(routes)
-map.addRoutesLabels()
+// map.addRoutesLabels()
 map.fitZoom()
-- 
cgit v1.2.3
From c463c3a950246c4c2660ce7df1c1ea8f2acbe578 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 11:17:02 +0100
Subject: Refs #5750; Remove useless validation
---
 app/javascript/vehicle_journeys/actions/index.js | 17 +----------------
 1 file changed, 1 insertion(+), 16 deletions(-)
diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js
index b01158212..4a4ec371d 100644
--- a/app/javascript/vehicle_journeys/actions/index.js
+++ b/app/javascript/vehicle_journeys/actions/index.js
@@ -382,23 +382,8 @@ const actions = {
   },
 
   validate : (dispatch, vehicleJourneys, next) => {
-    let valid = true
-    let vj, vjas
-    for (vj of vehicleJourneys){
-      vj.errors = false
-      for(vjas of vj.vehicle_journey_at_stops){
-        vjas.errors = null
-        if (vjas.area_kind == "non_commercial" && parseInt(vjas.departure_time.hour) == 0 && parseInt(vjas.departure_time.minute) == 0){
-          vjas.errors = "Champ requis"
-          vj.errors = true
-          valid = false
-        }
-      }
-    }
     dispatch(actions.didValidateVehicleJourneys(vehicleJourneys))
-    if(valid){
-      actions.submitVehicleJourneys(dispatch, vehicleJourneys, next)
-    }
+    actions.submitVehicleJourneys(dispatch, vehicleJourneys, next)
   },
 
   didValidateVehicleJourneys : (vehicleJourneys) => ({
-- 
cgit v1.2.3
From 9c322a5ea47f0badb0ba7e91de6df42d25e591c5 Mon Sep 17 00:00:00 2001
From: Teddy Wing
Date: Wed, 31 Jan 2018 13:23:24 +0100
Subject: VehicleJourneys#index: Allow company deletion in 'add' modal
While the 'x' button in the Select2 was available in the 'edit' modal
for a vehicle journey, it was not in the 'add' modal. Thus, once you
added an associated company, you couldn't remove it in the modal. You'd
either have to cancel and create your vehicle journey again, or create
it an delete the company in the 'edit' modal later.
This enables the 'x' button in the 'add' modal (it previously was only
enabled when `editMode` was active) and sets the proper action/reducer
to remove the company.
The `allowClear` attribute was changed to only work in the 'edit' modal
in 0079238842263768b88b0fa0fd977824b49eabd5. That commit appears to be
changing things so that certain functions work when not in 'edit' mode.
The case we're concerned about is that the 'edit' modal can be opened
when in 'view' mode (not 'edit' mode) in order to get more detailed
information about a vehicle journey. This means the company Select2 is
available in 'view' mode, and the 'x' button should not be visible in
that case. But, when I tested this, even with `allowClear: true`, the
'x' button wasn't visible. This, I can only assume, is because the
Select2 is in a 'disabled' state, so it's smart enough to know not to
show the 'x' button. Which works great for us, because this allows us to
not show the 'x' button here and still show it in the 'add' and 'edit'
modals.
Refs #5574
---
 app/javascript/vehicle_journeys/components/tools/CreateModal.js        | 1 +
 .../vehicle_journeys/components/tools/select2s/CompanySelect2.js       | 2 +-
 app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js  | 3 +++
 3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js
index 90328458b..8536f66e6 100644
--- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js
+++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js
@@ -66,6 +66,7 @@ export default class CreateModal extends Component {
                                this.props.onSelect2Company(e)}
+                                onUnselect2Company = {() => this.props.onUnselect2Company()}
                               />
                             
                           
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js
index 28a092945..5c7f75d99 100644
--- a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js
@@ -26,7 +26,7 @@ export default class BSelect4 extends Component {
         multiple={false}
         ref='company_id'
         options={{
-          allowClear: this.props.editMode,
+          allowClear: true,
           theme: 'bootstrap',
           width: '100%',
           placeholder: 'Filtrer par transporteur...',
diff --git a/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js
index 0db7628be..d982f5a5f 100644
--- a/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js
@@ -30,6 +30,9 @@ const mapDispatchToProps = (dispatch) => {
     },
     onSelect2Company: (e) => {
       dispatch(actions.select2Company(e.params.data))
+    },
+    onUnselect2Company: () => {
+      dispatch(actions.unselect2Company())
     }
   }
 }
-- 
cgit v1.2.3
From e12c06e4a5733c22588982db7cd857126cc6296d Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 13:37:47 +0100
Subject: Try and fix jenkins builds
---
 package.json | 2 +-
 yarn.lock    | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/package.json b/package.json
index 802a2eef7..e80f5231e 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
     "babel-preset-react": "6.24.1",
     "babelify": "8.0.0",
     "bootstrap": "3",
+    "clean-webpack-plugin": "^0.1.18",
     "coffee-loader": "^0.9.0",
     "coffeescript": "1.12.7",
     "jquery": "3.2.1",
@@ -29,7 +30,6 @@
     "node": "~6.12.0"
   },
   "devDependencies": {
-    "clean-webpack-plugin": "0.1.17",
     "es6-object-assign": "1.1.0",
     "grunt": "^1.0.1",
     "grunt-contrib-watch": "^1.0.0",
diff --git a/yarn.lock b/yarn.lock
index d17ae1d52..b32d906dd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1347,9 +1347,9 @@ clap@^1.0.9:
   dependencies:
     chalk "^1.1.3"
 
-clean-webpack-plugin@0.1.17:
-  version "0.1.17"
-  resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-0.1.17.tgz#71c57242e6d47204d46f809413176e7bed28ec49"
+clean-webpack-plugin@^0.1.18:
+  version "0.1.18"
+  resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-0.1.18.tgz#2e2173897c76646031bff047c14b9c22c80d8c4a"
   dependencies:
     rimraf "^2.6.1"
 
-- 
cgit v1.2.3
From 38762e7e0292cbb70a59bcd5b5260eeefd042c94 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 14:02:54 +0100
Subject: Fix StopArea validation breaking the specs
---
 app/models/chouette/stop_area.rb      | 2 +-
 spec/factories/chouette_stop_areas.rb | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index ad42d54ae..d7d5c2eb2 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -60,7 +60,7 @@ module Chouette
     end
 
     def area_type_of_right_kind
-
+      return unless self.kind
       unless Chouette::AreaType.send(self.kind).map(&:to_s).include?(self.area_type)
         errors.add(:area_type, I18n.t('stop_areas.errors.incorrect_kind_area_type'))
       end
diff --git a/spec/factories/chouette_stop_areas.rb b/spec/factories/chouette_stop_areas.rb
index 94517f856..9b4764781 100644
--- a/spec/factories/chouette_stop_areas.rb
+++ b/spec/factories/chouette_stop_areas.rb
@@ -3,9 +3,10 @@ FactoryGirl.define do
     sequence(:objectid) { |n| "FR:#{n}:ZDE:#{n}:STIF" }
     sequence(:name) { |n| "stop_area_#{n}" }
     sequence(:registration_number) { |n| "test-#{n}" }
-    area_type { Chouette::AreaType.all.sample }
+    area_type { Chouette::AreaType.commercial.sample }
     latitude {10.0 * rand}
     longitude {10.0 * rand}
+    kind "commercial"
 
     association :stop_area_referential
 
-- 
cgit v1.2.3
From 5e79f52c2d9db09f7c1bc94772d9b2a231cabf94 Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 11:54:47 +0100
Subject: Refs #5754; Add a filter on purchase_windows on ReferentialVJs#index
---
 app/controllers/concerns/ransack_date_filter.rb    |  5 +--
 .../referential_vehicle_journeys_controller.rb     |  5 +++
 app/models/chouette/purchase_window.rb             |  1 +
 app/models/chouette/vehicle_journey.rb             |  7 ++++
 .../_filters.html.slim                             |  8 ++++-
 config/locales/vehicle_journeys.en.yml             |  3 ++
 config/locales/vehicle_journeys.fr.yml             |  3 ++
 spec/models/chouette/vehicle_journey_spec.rb       | 41 ++++++++++++++++++++++
 8 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/app/controllers/concerns/ransack_date_filter.rb b/app/controllers/concerns/ransack_date_filter.rb
index 0fbde91d3..99889294c 100644
--- a/app/controllers/concerns/ransack_date_filter.rb
+++ b/app/controllers/concerns/ransack_date_filter.rb
@@ -29,13 +29,14 @@ module RansackDateFilter
     def ransack_period_range **options
       return options[:scope] unless !!@begin_range && !!@end_range
 
+      scope = options[:scope]
       if @begin_range > @end_range
         flash.now[:error] = options[:error_message]
       else
-        scope = options[:scope].send options[:query], @begin_range..@end_range
+        scope = scope.send options[:query], @begin_range..@end_range
       end
       scope
     end
   end
 
-end
\ No newline at end of file
+end
diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb
index 89b3703a0..e964c8108 100644
--- a/app/controllers/referential_vehicle_journeys_controller.rb
+++ b/app/controllers/referential_vehicle_journeys_controller.rb
@@ -3,6 +3,10 @@
 #
 class ReferentialVehicleJourneysController < ChouetteController
   include ReferentialSupport
+  include RansackDateFilter
+
+  before_action only: [:index] { set_date_time_params("purchase_window", Date) }
+
   defaults :resource_class => Chouette::VehicleJourney, collection_name: :vehicle_journeys
 
   requires_feature :referential_vehicle_journeys
@@ -12,6 +16,7 @@ class ReferentialVehicleJourneysController < ChouetteController
   def collection
     @q ||= end_of_association_chain
     @q = @q.with_stop_area_ids(params[:q][:stop_area_ids]) if params[:q] && params[:q][:stop_area_ids]
+    @q = ransack_period_range(scope: @q, error_message:  t('vehicle_journeys.errors.purchase_window'), query: :in_purchase_window)
     @q = @q.ransack(params[:q])
     @vehicle_journeys ||= @q.result.order(:published_journey_name).includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: params[:per_page] || 10
     @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct
diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb
index 22bcc1de1..334493015 100644
--- a/app/models/chouette/purchase_window.rb
+++ b/app/models/chouette/purchase_window.rb
@@ -18,6 +18,7 @@ module Chouette
     validates_presence_of :name, :referential
 
     scope :contains_date, ->(date) { where('date ? <@ any (date_ranges)', date) }
+    scope :overlap_dates, ->(date_range) { where('daterange(?, ?) && any (date_ranges)', date_range.first, date_range.last + 1.day) }
 
     def self.ransackable_scopes(auth_object = nil)
       [:contains_date]
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index 8a704d8c0..c2fcbfb44 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -52,8 +52,15 @@ module Chouette
       end
     }
 
+    scope :in_purchase_window, ->(range){
+      purchase_windows = Chouette::PurchaseWindow.overlap_dates(range)
+      sql = purchase_windows.joins(:vehicle_journeys).select('vehicle_journeys.id').uniq.to_sql
+      where("id IN (#{sql})")
+    }
+
     # We need this for the ransack object in the filters
     ransacker :stop_area_ids
+    ransacker :purchase_window_date_gt
 
     # TODO: Remove this validator
     # We've eliminated this validation because it prevented vehicle journeys
diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
index 1301d3dab..af8b2de3a 100644
--- a/app/views/referential_vehicle_journeys/_filters.html.slim
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -21,10 +21,16 @@
         = f.input :published_journey_name_gteq, label: false, wrapper_html: { class: 'w45'}
         .form-group.w10.to= I18n.t('vehicle_journeys.form.to')
         = f.input :published_journey_name_lteq, label: false, wrapper_html: { class: 'w45'}
-
     .form-group.togglable
       = f.label Chouette::StopArea.model_name.human.pluralize, required: false, class: 'control-label'
       = f.input :stop_area_ids, collection: @all_stop_areas.select(:id, :name).order(name: :asc), checked: params[:q] && params[:q][:stop_area_ids], as: :check_boxes, label: false, label_method: lambda{|l| ("" + l.name + "").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}, multiple: true
+    .form-group.togglable
+      = f.label Chouette::VehicleJourney.human_attribute_name(:purchase_window), class: 'control-label'
+      .filter_menu
+        = f.simple_fields_for :purchase_window do |p|
+          = p.input :start_date, as: :date, label: t('simple_form.from'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @begin_range, include_blank: @begin_range ? false : true
+          = p.input :end_date, as: :date, label: t('simple_form.to'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @end_range, include_blank: @end_range ? false : true
+
 
   .actions
     = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link'
diff --git a/config/locales/vehicle_journeys.en.yml b/config/locales/vehicle_journeys.en.yml
index 1c1f6c6bd..08a9334cc 100644
--- a/config/locales/vehicle_journeys.en.yml
+++ b/config/locales/vehicle_journeys.en.yml
@@ -67,6 +67,8 @@ en:
       time_range_filter: "Filter"
     sidebar:
       timeless: "Timeless vehicle journeys"
+    errors:
+      purchase_window: Invalid purchase window
   activerecord:
     models:
       vehicle_journey:
@@ -108,6 +110,7 @@ en:
         footnote_ids: "Footnotes"
         departure_time: "Departure"
         arrival_time: "Arrival"
+        purchase_window: "Purchase availability"
     errors:
       models:
         vehicle_journey:
diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml
index 5749f8f10..8d85d877a 100644
--- a/config/locales/vehicle_journeys.fr.yml
+++ b/config/locales/vehicle_journeys.fr.yml
@@ -67,6 +67,8 @@ fr:
       time_range_filter: "Filtrer"
     sidebar:
       timeless: "Courses sans horaire"
+    errors:
+      purchase_window: Calendrier commercial invalide
   activerecord:
     models:
       vehicle_journey:
@@ -108,6 +110,7 @@ fr:
         footnote_ids: "Notes de bas de page"
         departure_time: "Départ"
         arrival_time: "Arrivée"
+        purchase_window: "Disponibilité commerciale"
     errors:
       models:
         vehicle_journey:
diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb
index 2a88ac3ce..ea30d4e2c 100644
--- a/spec/models/chouette/vehicle_journey_spec.rb
+++ b/spec/models/chouette/vehicle_journey_spec.rb
@@ -24,6 +24,47 @@ describe Chouette::VehicleJourney, :type => :model do
     it_behaves_like 'checksum support', :vehicle_journey
   end
 
+  describe '#in_purchase_window' do
+    let(:start_date){2.month.ago.to_date}
+    let(:end_date){1.month.ago.to_date}
+
+    subject{Chouette::VehicleJourney.in_purchase_window start_date..end_date}
+
+    let!(:without_purchase_window){ create :vehicle_journey }
+    let!(:without_matching_purchase_window){
+      pw = create :purchase_window, referential: Referential.first, date_ranges: [(end_date+1.day..end_date+2.days)]
+      pw2 = create :purchase_window, referential: Referential.first, date_ranges: [(end_date+10.day..end_date+20.days)]
+      create :vehicle_journey, purchase_windows: [pw, pw2]
+    }
+    let!(:included_purchase_window){
+      pw = create :purchase_window, referential: Referential.first, date_ranges: [(start_date..end_date)]
+      pw2 = create :purchase_window, referential: Referential.first
+      create :vehicle_journey, purchase_windows: [pw, pw2]
+    }
+    let!(:overlapping_purchase_window){
+      pw = create :purchase_window, referential: Referential.first, date_ranges: [(end_date..end_date+1.day)]
+      pw2 = create :purchase_window, referential: Referential.first
+      create :vehicle_journey, purchase_windows: [pw, pw2]
+    }
+
+
+    it "should not include VJ with no purchase window" do
+      expect(subject).to_not include without_purchase_window
+    end
+
+    it "should not include VJ with no matching purchase window" do
+      expect(subject).to_not include without_matching_purchase_window
+    end
+
+    it "should include VJ with included purchase window" do
+      expect(subject).to include included_purchase_window
+    end
+
+    it "should include VJ with overlapping_purchase_window purchase window" do
+      expect(subject).to include overlapping_purchase_window
+    end
+  end
+
   describe "vjas_departure_time_must_be_before_next_stop_arrival_time",
       skip: "Validation currently commented out because it interferes with day offsets" do
 
-- 
cgit v1.2.3
From 883a51aa8fb5e644c638d6dfb87962af5111ec8c Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 13:17:14 +0100
Subject: Refs #5754; Add a filter on calendars for ReferentialVJs#index
---
 app/controllers/concerns/ransack_date_filter.rb    | 25 ++++---
 .../referential_vehicle_journeys_controller.rb     |  6 +-
 app/models/chouette/time_table.rb                  |  1 -
 app/models/chouette/vehicle_journey.rb             | 14 ++++
 .../_filters.html.slim                             | 10 ++-
 config/locales/vehicle_journeys.en.yml             |  1 +
 config/locales/vehicle_journeys.fr.yml             |  1 +
 spec/factories/chouette_time_table.rb              | 17 +++--
 spec/models/chouette/vehicle_journey_spec.rb       | 83 +++++++++++++++++++++-
 9 files changed, 137 insertions(+), 21 deletions(-)
diff --git a/app/controllers/concerns/ransack_date_filter.rb b/app/controllers/concerns/ransack_date_filter.rb
index 99889294c..055c01130 100644
--- a/app/controllers/concerns/ransack_date_filter.rb
+++ b/app/controllers/concerns/ransack_date_filter.rb
@@ -3,7 +3,15 @@ module RansackDateFilter
 
   included do
 
-    def set_date_time_params(param_name, klass)
+    def begin_range_var prefix
+      "@#{[prefix, "begin_range"].compact.join('_')}"
+    end
+
+    def end_range_var prefix
+      "@#{[prefix, "end_range"].compact.join('_')}"
+    end
+
+    def set_date_time_params(param_name, klass, prefix: nil)
       start_date = []
       end_date = []
 
@@ -16,24 +24,25 @@ module RansackDateFilter
         params[:q].delete([param_name])
 
         if klass == DateTime
-          @begin_range = klass.new(*start_date,0,0,0) rescue nil
-          @end_range = klass.new(*end_date,23,59,59) rescue nil
+          instance_variable_set begin_range_var(prefix), klass.new(*start_date,0,0,0) rescue nil
+          instance_variable_set end_range_var(prefix), klass.new(*end_date,23,59,59) rescue nil
         else
-          @begin_range = klass.new(*start_date) rescue nil
-          @end_range = klass.new(*end_date) rescue nil
+          instance_variable_set begin_range_var(prefix), klass.new(*start_date) rescue nil
+          instance_variable_set end_range_var(prefix), klass.new(*end_date) rescue nil
         end
       end
     end
 
     # Fake ransack filter
     def ransack_period_range **options
-      return options[:scope] unless !!@begin_range && !!@end_range
+      prefix = options[:prefix]
+      return options[:scope] unless !!instance_variable_get(begin_range_var(prefix)) && !!instance_variable_get(end_range_var(prefix))
 
       scope = options[:scope]
-      if @begin_range > @end_range
+      if instance_variable_get(begin_range_var(prefix)) > instance_variable_get(end_range_var(prefix))
         flash.now[:error] = options[:error_message]
       else
-        scope = scope.send options[:query], @begin_range..@end_range
+        scope = scope.send options[:query], instance_variable_get(begin_range_var(prefix))..instance_variable_get(end_range_var(prefix))
       end
       scope
     end
diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb
index e964c8108..b133602bc 100644
--- a/app/controllers/referential_vehicle_journeys_controller.rb
+++ b/app/controllers/referential_vehicle_journeys_controller.rb
@@ -5,7 +5,8 @@ class ReferentialVehicleJourneysController < ChouetteController
   include ReferentialSupport
   include RansackDateFilter
 
-  before_action only: [:index] { set_date_time_params("purchase_window", Date) }
+  before_action only: [:index] { set_date_time_params("purchase_window", Date, prefix: :purchase_window) }
+  before_action only: [:index] { set_date_time_params("time_table", Date, prefix: :time_table) }
 
   defaults :resource_class => Chouette::VehicleJourney, collection_name: :vehicle_journeys
 
@@ -16,7 +17,8 @@ class ReferentialVehicleJourneysController < ChouetteController
   def collection
     @q ||= end_of_association_chain
     @q = @q.with_stop_area_ids(params[:q][:stop_area_ids]) if params[:q] && params[:q][:stop_area_ids]
-    @q = ransack_period_range(scope: @q, error_message:  t('vehicle_journeys.errors.purchase_window'), query: :in_purchase_window)
+    @q = ransack_period_range(scope: @q, error_message:  t('vehicle_journeys.errors.purchase_window'), query: :in_purchase_window, prefix: :purchase_window)
+    @q = ransack_period_range(scope: @q, error_message:  t('vehicle_journeys.errors.time_table'), query: :with_matching_timetable, prefix: :time_table)
     @q = @q.ransack(params[:q])
     @vehicle_journeys ||= @q.result.order(:published_journey_name).includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: params[:per_page] || 10
     @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct
diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb
index db97dd2fa..07bf35444 100644
--- a/app/models/chouette/time_table.rb
+++ b/app/models/chouette/time_table.rb
@@ -311,7 +311,6 @@ module Chouette
         bounding_max = periods_max_date if periods_max_date &&
             (bounding_max.nil? || (bounding_max < periods_max_date))
       end
-
       [bounding_min, bounding_max].compact
     end
 
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index c2fcbfb44..9484226c5 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -62,6 +62,20 @@ module Chouette
     ransacker :stop_area_ids
     ransacker :purchase_window_date_gt
 
+    # returns VehicleJourneys with at least 1 day in their time_tables
+    # included in the given range
+    def self.with_matching_timetable date_range
+      out = []
+      time_tables = Chouette::TimeTable.where(id: self.joins("INNER JOIN time_tables_vehicle_journeys ON vehicle_journeys.id = time_tables_vehicle_journeys.vehicle_journey_id").pluck('time_tables_vehicle_journeys.time_table_id')).overlapping(date_range)
+      time_tables = time_tables.select do |time_table|
+        range = date_range
+        range = date_range & (time_table.start_date-1.day..time_table.end_date+1.day) || [] if time_table.start_date.present? && time_table.end_date.present?
+        range.any?{|d| time_table.include_day?(d) }
+      end
+      out += time_tables.map{|t| t.vehicle_journey_ids}.flatten
+      where(id: out)
+    end
+
     # TODO: Remove this validator
     # We've eliminated this validation because it prevented vehicle journeys
     # from being saved with at-stops having a day offset greater than 0,
diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
index af8b2de3a..bfb5b77dd 100644
--- a/app/views/referential_vehicle_journeys/_filters.html.slim
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -28,8 +28,14 @@
       = f.label Chouette::VehicleJourney.human_attribute_name(:purchase_window), class: 'control-label'
       .filter_menu
         = f.simple_fields_for :purchase_window do |p|
-          = p.input :start_date, as: :date, label: t('simple_form.from'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @begin_range, include_blank: @begin_range ? false : true
-          = p.input :end_date, as: :date, label: t('simple_form.to'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @end_range, include_blank: @end_range ? false : true
+          = p.input :start_date, as: :date, label: t('simple_form.from'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @purchase_window_begin_range, include_blank: @purchase_window_begin_range ? false : true
+          = p.input :end_date, as: :date, label: t('simple_form.to'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @purchase_window_end_range, include_blank: @purchase_window_end_range ? false : true
+    .form-group.togglable
+      = f.label Chouette::TimeTable.model_name.human, class: 'control-label'
+      .filter_menu
+        = f.simple_fields_for :time_table do |p|
+          = p.input :start_date, as: :date, label: t('simple_form.from'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @time_table_begin_range, include_blank: @time_table_begin_range ? false : true
+          = p.input :end_date, as: :date, label: t('simple_form.to'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @time_table_end_range, include_blank: @time_table_end_range ? false : true
 
 
   .actions
diff --git a/config/locales/vehicle_journeys.en.yml b/config/locales/vehicle_journeys.en.yml
index 08a9334cc..abb1da530 100644
--- a/config/locales/vehicle_journeys.en.yml
+++ b/config/locales/vehicle_journeys.en.yml
@@ -69,6 +69,7 @@ en:
       timeless: "Timeless vehicle journeys"
     errors:
       purchase_window: Invalid purchase window
+      time_table: Invalid dates
   activerecord:
     models:
       vehicle_journey:
diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml
index 8d85d877a..ca8475812 100644
--- a/config/locales/vehicle_journeys.fr.yml
+++ b/config/locales/vehicle_journeys.fr.yml
@@ -69,6 +69,7 @@ fr:
       timeless: "Courses sans horaire"
     errors:
       purchase_window: Calendrier commercial invalide
+      time_table: Dates d'application invalides
   activerecord:
     models:
       vehicle_journey:
diff --git a/spec/factories/chouette_time_table.rb b/spec/factories/chouette_time_table.rb
index 81a08ca2a..af48e1b42 100644
--- a/spec/factories/chouette_time_table.rb
+++ b/spec/factories/chouette_time_table.rb
@@ -11,18 +11,21 @@ FactoryGirl.define do
     end
 
     after(:create) do |time_table, evaluator|
-
-      0.upto(4) do |i|
-        time_table.dates  << create(:time_table_date, :time_table => time_table, :date => i.days.since.to_date, :in_out => true)
+      unless time_table.dates.any?
+        evaluator.dates_count.times do |i|
+          time_table.dates  << create(:time_table_date, :time_table => time_table, :date => i.days.since.to_date, :in_out => true)
+        end
       end
 
       start_date = Date.today
       end_date = start_date + 10
 
-      0.upto(4) do |i|
-        time_table.periods << create(:time_table_period, :time_table => time_table, :period_start => start_date, :period_end => end_date)
-        start_date = start_date + 20
-        end_date = start_date + 10
+      unless time_table.periods.any?
+        evaluator.periods_count.times do |i|
+          time_table.periods << create(:time_table_period, :time_table => time_table, :period_start => start_date, :period_end => end_date)
+          start_date = start_date + 20
+          end_date = start_date + 10
+        end
       end
       time_table.save_shortcuts
       time_table.update_checksum!
diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb
index ea30d4e2c..33c415646 100644
--- a/spec/models/chouette/vehicle_journey_spec.rb
+++ b/spec/models/chouette/vehicle_journey_spec.rb
@@ -60,11 +60,92 @@ describe Chouette::VehicleJourney, :type => :model do
       expect(subject).to include included_purchase_window
     end
 
-    it "should include VJ with overlapping_purchase_window purchase window" do
+    it "should include VJ with overlapping purchase_window purchase window" do
       expect(subject).to include overlapping_purchase_window
     end
   end
 
+  describe '#in_time_table' do
+    let(:start_date){2.month.ago.to_date}
+    let(:end_date){1.month.ago.to_date}
+
+    subject{Chouette::VehicleJourney.with_matching_timetable start_date..end_date}
+
+    context "without time table" do
+      let!(:vehicle_journey){ create :vehicle_journey }
+      it "should not include VJ " do
+        expect(subject).to_not include vehicle_journey
+      end
+    end
+
+    context "without a time table matching on a regular day" do
+      let(:timetable){
+        period = create :time_table_period, period_start: start_date-2.day, period_end: start_date
+        create :time_table, periods: [period], dates_count: 0
+      }
+      let!(:vehicle_journey){ create :vehicle_journey, time_tables: [timetable] }
+      it "should include VJ " do
+        expect(subject).to include vehicle_journey
+      end
+    end
+
+    context "without a time table matching on a regular day" do
+      let(:timetable){
+        period = create :time_table_period, period_start: end_date, period_end: end_date+1.day
+        create :time_table, periods: [period], dates_count: 0
+      }
+      let!(:vehicle_journey){ create :vehicle_journey, time_tables: [timetable] }
+      it "should include VJ " do
+        expect(subject).to include vehicle_journey
+      end
+    end
+
+    context "with a time table with a matching period but not the right day" do
+      let(:start_date){end_date - 1.day}
+      let(:end_date){Time.now.end_of_week.to_date}
+
+      let(:timetable){
+        period = create :time_table_period, period_start: start_date-1.month, period_end: end_date+1.month
+        create :time_table, periods: [period], int_day_types: 4 + 8, dates_count: 0
+      }
+      let!(:vehicle_journey){ create :vehicle_journey, time_tables: [timetable] }
+      it "should not include VJ " do
+        expect(subject).to_not include vehicle_journey
+      end
+    end
+
+    context "with a time table with a matching period but day opted-out" do
+      let(:start_date){end_date - 1.day}
+      let(:end_date){Time.now.end_of_week.to_date}
+
+      let(:timetable){
+        period = create :time_table_period, period_start: start_date-1.month, period_end: start_date-1.day
+        date = create(:time_table_date, :date => start_date, in_out: false)
+        create :time_table, periods: [period], dates: [date]
+      }
+      let!(:vehicle_journey){ create :vehicle_journey, time_tables: [timetable] }
+      it "should not include VJ " do
+        expect(subject).to_not include vehicle_journey
+      end
+    end
+
+    context "with a time table with no matching period but not the right extra day" do
+      let(:start_date){end_date - 1.day}
+      let(:end_date){Time.now.end_of_week.to_date}
+
+      let(:timetable){
+        period = create :time_table_period, period_start: start_date-1.month, period_end: start_date-1.day
+        date = create(:time_table_date, :date => start_date, in_out: true)
+        create :time_table, periods: [period], dates: [date]
+      }
+      let!(:vehicle_journey){ create :vehicle_journey, time_tables: [timetable] }
+      it "should include VJ " do
+        expect(subject).to include vehicle_journey
+      end
+    end
+
+  end
+
   describe "vjas_departure_time_must_be_before_next_stop_arrival_time",
       skip: "Validation currently commented out because it interferes with day offsets" do
 
-- 
cgit v1.2.3
From e91ce59d625734a0936a434b9e45fb2c48f0a5f8 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 14:34:48 +0100
Subject: Fix Reflex import
---
 lib/stif/reflex_synchronization.rb | 1 +
 1 file changed, 1 insertion(+)
diff --git a/lib/stif/reflex_synchronization.rb b/lib/stif/reflex_synchronization.rb
index 39a92bd1f..7570e4c49 100644
--- a/lib/stif/reflex_synchronization.rb
+++ b/lib/stif/reflex_synchronization.rb
@@ -151,6 +151,7 @@ module Stif
 
       def create_or_update_stop_area entry
         stop = Chouette::StopArea.find_or_create_by(objectid: entry['id'], stop_area_referential: self.defaut_referential )
+        stop.kind = :commercial
         stop.deleted_at            = nil
         {
           :comment        => 'Description',
-- 
cgit v1.2.3
From 90f54f0acfe65ff276a229239809ce0e9fddf0b0 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 15:10:40 +0100
Subject: Fix specs
---
 app/javascript/vehicle_journeys/actions/index.js |  1 +
 spec/javascript/vehicle_journeys/actions_spec.js |  3 ++-
 spec/models/chouette/vehicle_journey_spec.rb     | 14 ++++++++------
 3 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js
index 4a4ec371d..8970c6025 100644
--- a/app/javascript/vehicle_journeys/actions/index.js
+++ b/app/javascript/vehicle_journeys/actions/index.js
@@ -384,6 +384,7 @@ const actions = {
   validate : (dispatch, vehicleJourneys, next) => {
     dispatch(actions.didValidateVehicleJourneys(vehicleJourneys))
     actions.submitVehicleJourneys(dispatch, vehicleJourneys, next)
+    return true
   },
 
   didValidateVehicleJourneys : (vehicleJourneys) => ({
diff --git a/spec/javascript/vehicle_journeys/actions_spec.js b/spec/javascript/vehicle_journeys/actions_spec.js
index d486c9af8..9710d833c 100644
--- a/spec/javascript/vehicle_journeys/actions_spec.js
+++ b/spec/javascript/vehicle_journeys/actions_spec.js
@@ -1,6 +1,7 @@
 import actions from '../../../app/javascript/vehicle_journeys/actions/index'
 
 const dispatch = function(){}
+window.fetch = function(){return Promise.resolve()}
 const currentPage = 1
 
 describe('when cannot fetch api', () => {
@@ -49,7 +50,7 @@ describe('when validating the form', () => {
       }]
     }]
 
-    expect(actions.validate(dispatch, state)).toEqual(false)
+    expect(actions.validate(dispatch, state)).toEqual(true)
 
     state = [{
       vehicle_journey_at_stops: [{
diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb
index 70661bcc5..e9ffddd2a 100644
--- a/spec/models/chouette/vehicle_journey_spec.rb
+++ b/spec/models/chouette/vehicle_journey_spec.rb
@@ -119,9 +119,10 @@ describe Chouette::VehicleJourney, :type => :model do
       let(:end_date){Time.now.end_of_week.to_date}
 
       let(:timetable){
-        period = create :time_table_period, period_start: start_date-1.month, period_end: start_date-1.day
-        date = create(:time_table_date, :date => start_date, in_out: false)
-        create :time_table, periods: [period], dates: [date]
+        tt = create :time_table, dates_count: 0, periods_count: 0
+        create :time_table_period, period_start: start_date-1.month, period_end: start_date-1.day, time_table: tt
+        create(:time_table_date, :date => start_date, in_out: false, time_table: tt)
+        tt
       }
       let!(:vehicle_journey){ create :vehicle_journey, time_tables: [timetable] }
       it "should not include VJ " do
@@ -134,9 +135,10 @@ describe Chouette::VehicleJourney, :type => :model do
       let(:end_date){Time.now.end_of_week.to_date}
 
       let(:timetable){
-        period = create :time_table_period, period_start: start_date-1.month, period_end: start_date-1.day
-        date = create(:time_table_date, :date => start_date, in_out: true)
-        create :time_table, periods: [period], dates: [date]
+        tt = create :time_table, dates_count: 0, periods_count: 0
+        create :time_table_period, period_start: start_date-1.month, period_end: start_date-1.day, time_table: tt
+        create(:time_table_date, :date => start_date, in_out: true, time_table: tt)
+        tt
       }
       let!(:vehicle_journey){ create :vehicle_journey, time_tables: [timetable] }
       it "should include VJ " do
-- 
cgit v1.2.3
From eaf51fdc334923edd3dbd399d2217ff0bbe0699a Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 07:28:57 +0100
Subject: Refs #5682; Add application_days field to calendars
---
 app/models/calendar.rb                             |   3 +-
 app/models/chouette/time_table.rb                  |  95 +-----------------
 app/models/concerns/application_days_support.rb    | 108 +++++++++++++++++++++
 ...0180124061955_add_int_day_types_to_calendars.rb |   5 +
 db/schema.rb                                       |   2 +-
 spec/models/calendar_spec.rb                       |   3 +-
 spec/models/chouette/time_table_spec.rb            |   5 +-
 7 files changed, 121 insertions(+), 100 deletions(-)
 create mode 100644 app/models/concerns/application_days_support.rb
 create mode 100644 db/migrate/20180124061955_add_int_day_types_to_calendars.rb
diff --git a/app/models/calendar.rb b/app/models/calendar.rb
index a7fd9220c..d58e7737d 100644
--- a/app/models/calendar.rb
+++ b/app/models/calendar.rb
@@ -5,6 +5,7 @@ require_relative 'calendar/period'
 class Calendar < ActiveRecord::Base
   include DateSupport
   include PeriodSupport
+  include ApplicationDaysSupport
 
   has_paper_trail class_name: 'PublicVersion'
   belongs_to :organisation
@@ -28,7 +29,7 @@ class Calendar < ActiveRecord::Base
       self.periods.each do |p|
         tt.periods << Chouette::TimeTablePeriod.new(period_start: p.begin, period_end: p.end)
       end
-      tt.int_day_types = 508
+      tt.int_day_types = self.int_day_types
     end
   end
 
diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb
index 07bf35444..1a1972113 100644
--- a/app/models/chouette/time_table.rb
+++ b/app/models/chouette/time_table.rb
@@ -4,11 +4,12 @@ module Chouette
     include ChecksumSupport
     include TimeTableRestrictions
     include ObjectidSupport
+    include ApplicationDaysSupport
+
     # FIXME http://jira.codehaus.org/browse/JRUBY-6358
     self.primary_key = "id"
     acts_as_taggable
 
-    attr_accessor :monday,:tuesday,:wednesday,:thursday,:friday,:saturday,:sunday
     attr_accessor :tag_search
 
     def self.ransackable_attributes auth_object = nil
@@ -314,98 +315,6 @@ module Chouette
       [bounding_min, bounding_max].compact
     end
 
-    def display_day_types
-      %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')
-    end
-
-    def day_by_mask(flag)
-      int_day_types & flag == flag
-    end
-
-    def self.day_by_mask(int_day_types,flag)
-      int_day_types & flag == flag
-    end
-
-
-    def valid_days
-      # Build an array with day of calendar week (1-7, Monday is 1).
-      [].tap do |valid_days|
-        valid_days << 1  if monday
-        valid_days << 2  if tuesday
-        valid_days << 3  if wednesday
-        valid_days << 4  if thursday
-        valid_days << 5  if friday
-        valid_days << 6  if saturday
-        valid_days << 7  if sunday
-      end
-    end
-
-    def self.valid_days(int_day_types)
-      # Build an array with day of calendar week (1-7, Monday is 1).
-      [].tap do |valid_days|
-        valid_days << 1  if day_by_mask(int_day_types,4)
-        valid_days << 2  if day_by_mask(int_day_types,8)
-        valid_days << 3  if day_by_mask(int_day_types,16)
-        valid_days << 4  if day_by_mask(int_day_types,32)
-        valid_days << 5  if day_by_mask(int_day_types,64)
-        valid_days << 6  if day_by_mask(int_day_types,128)
-        valid_days << 7  if day_by_mask(int_day_types,256)
-      end
-    end
-
-    def monday
-      day_by_mask(4)
-    end
-    def tuesday
-      day_by_mask(8)
-    end
-    def wednesday
-      day_by_mask(16)
-    end
-    def thursday
-      day_by_mask(32)
-    end
-    def friday
-      day_by_mask(64)
-    end
-    def saturday
-      day_by_mask(128)
-    end
-    def sunday
-      day_by_mask(256)
-    end
-
-    def set_day(day,flag)
-      if day == '1' || day == true
-        self.int_day_types |= flag
-      else
-        self.int_day_types &= ~flag
-      end
-      shortcuts_update
-    end
-
-    def monday=(day)
-      set_day(day,4)
-    end
-    def tuesday=(day)
-      set_day(day,8)
-    end
-    def wednesday=(day)
-      set_day(day,16)
-    end
-    def thursday=(day)
-      set_day(day,32)
-    end
-    def friday=(day)
-      set_day(day,64)
-    end
-    def saturday=(day)
-      set_day(day,128)
-    end
-    def sunday=(day)
-      set_day(day,256)
-    end
-
     def effective_days_of_period(period,valid_days=self.valid_days)
       days = []
         period.period_start.upto(period.period_end) do |date|
diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb
new file mode 100644
index 000000000..927011acb
--- /dev/null
+++ b/app/models/concerns/application_days_support.rb
@@ -0,0 +1,108 @@
+module ApplicationDaysSupport
+  extend ActiveSupport::Concern
+
+  included do |into|
+    into.class_eval do
+      MONDAY    = 4
+      TUESDAY   = 8
+      WEDNESDAY = 16
+      THURSDAY  = 32
+      FRIDAY    = 64
+      SATURDAY  = 128
+      SUNDAY    = 256
+    end
+
+    # attr_accessor :monday,:tuesday,:wednesday,:thursday,:friday,:saturday,:sunday
+  end
+
+  def display_day_types
+    %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')
+  end
+
+  def day_by_mask(flag)
+    int_day_types & flag == flag
+  end
+
+  def self.day_by_mask(int_day_types,flag)
+    int_day_types & flag == flag
+  end
+
+  def valid_days
+    # Build an array with day of calendar week (1-7, Monday is 1).
+    [].tap do |valid_days|
+      valid_days << 1  if monday
+      valid_days << 2  if tuesday
+      valid_days << 3  if wednesday
+      valid_days << 4  if thursday
+      valid_days << 5  if friday
+      valid_days << 6  if saturday
+      valid_days << 7  if sunday
+    end
+  end
+
+  def self.valid_days(int_day_types)
+    # Build an array with day of calendar week (1-7, Monday is 1).
+    [].tap do |valid_days|
+      valid_days << 1  if day_by_mask(int_day_types,MONDAY)
+      valid_days << 2  if day_by_mask(int_day_types,TUESDAY)
+      valid_days << 3  if day_by_mask(int_day_types,WEDNESDAY)
+      valid_days << 4  if day_by_mask(int_day_types,THURSDAY)
+      valid_days << 5  if day_by_mask(int_day_types,FRIDAY)
+      valid_days << 6  if day_by_mask(int_day_types,SATURDAY)
+      valid_days << 7  if day_by_mask(int_day_types,SUNDAY)
+    end
+  end
+
+  def monday
+    day_by_mask(MONDAY)
+  end
+  def tuesday
+    day_by_mask(TUESDAY)
+  end
+  def wednesday
+    day_by_mask(WEDNESDAY)
+  end
+  def thursday
+    day_by_mask(THURSDAY)
+  end
+  def friday
+    day_by_mask(FRIDAY)
+  end
+  def saturday
+    day_by_mask(SATURDAY)
+  end
+  def sunday
+    day_by_mask(SUNDAY)
+  end
+
+  def set_day(day,flag)
+    if day == '1' || day == true
+      self.int_day_types |= flag
+    else
+      self.int_day_types &= ~flag
+    end
+    shortcuts_update
+  end
+
+  def monday=(day)
+    set_day(day,4)
+  end
+  def tuesday=(day)
+    set_day(day,8)
+  end
+  def wednesday=(day)
+    set_day(day,16)
+  end
+  def thursday=(day)
+    set_day(day,32)
+  end
+  def friday=(day)
+    set_day(day,64)
+  end
+  def saturday=(day)
+    set_day(day,128)
+  end
+  def sunday=(day)
+    set_day(day,256)
+  end
+end
diff --git a/db/migrate/20180124061955_add_int_day_types_to_calendars.rb b/db/migrate/20180124061955_add_int_day_types_to_calendars.rb
new file mode 100644
index 000000000..6384c7177
--- /dev/null
+++ b/db/migrate/20180124061955_add_int_day_types_to_calendars.rb
@@ -0,0 +1,5 @@
+class AddIntDayTypesToCalendars < ActiveRecord::Migration
+  def change
+    add_column :calendars, :int_day_types, :integer, default: 0
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b2063539b..34654bdda 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20180126134944) do
+ActiveRecord::Schema.define(version: 20180124061955) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb
index e71c2b081..4c65b9660 100644
--- a/spec/models/calendar_spec.rb
+++ b/spec/models/calendar_spec.rb
@@ -9,11 +9,12 @@ RSpec.describe Calendar, :type => :model do
   it { is_expected.to be_versioned }
 
   describe '#to_time_table' do
-    let(:calendar) { create(:calendar, date_ranges: [Date.today...(Date.today + 1.month)]) }
+    let(:calendar) { create(:calendar, int_day_types: Calendar::MONDAY | Calendar::SUNDAY, date_ranges: [Date.today...(Date.today + 1.month)]) }
 
     it 'should convert calendar to an instance of Chouette::TimeTable' do
       time_table = calendar.convert_to_time_table
       expect(time_table).to be_an_instance_of(Chouette::TimeTable)
+      expect(time_table.int_day_types).to eq calendar.int_day_types
       expect(time_table.periods[0].period_start).to eq(calendar.periods[0].begin)
       expect(time_table.periods[0].period_end).to eq(calendar.periods[0].end)
       expect(time_table.dates.map(&:date)).to match_array(calendar.dates)
diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb
index d4a726740..28197984e 100644
--- a/spec/models/chouette/time_table_spec.rb
+++ b/spec/models/chouette/time_table_spec.rb
@@ -926,6 +926,7 @@ end
       end
     end
   end
+  
   describe "#validity_out_between?" do
     let(:empty_tm) {build(:time_table)}
     it "should be false if empty calendar" do
@@ -1068,8 +1069,6 @@ end
       end
   end
 
-
-
   describe "#effective_days" do
       before do
         subject.periods.clear
@@ -1094,8 +1093,6 @@ end
       end
   end
 
-
-
   describe "#optimize_overlapping_periods" do
       before do
         subject.periods.clear
-- 
cgit v1.2.3
From 52e07ba395ddd01f74eb6b81593dfdc7c636d25a Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 09:56:56 +0100
Subject: Refs #5682; Fix warning
---
 app/models/concerns/application_days_support.rb | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb
index 927011acb..f83e4a5c8 100644
--- a/app/models/concerns/application_days_support.rb
+++ b/app/models/concerns/application_days_support.rb
@@ -1,20 +1,14 @@
 module ApplicationDaysSupport
   extend ActiveSupport::Concern
 
-  included do |into|
-    into.class_eval do
-      MONDAY    = 4
-      TUESDAY   = 8
-      WEDNESDAY = 16
-      THURSDAY  = 32
-      FRIDAY    = 64
-      SATURDAY  = 128
-      SUNDAY    = 256
-    end
-
-    # attr_accessor :monday,:tuesday,:wednesday,:thursday,:friday,:saturday,:sunday
-  end
-
+  MONDAY    = 4
+  TUESDAY   = 8
+  WEDNESDAY = 16
+  THURSDAY  = 32
+  FRIDAY    = 64
+  SATURDAY  = 128
+  SUNDAY    = 256
+  
   def display_day_types
     %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')
   end
-- 
cgit v1.2.3
From b83d26389e6726eaea86955c3a2d2bffa5e65b5e Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 10:15:48 +0100
Subject: Refs #5682; Set default value for application days
---
 app/models/calendar.rb                                      | 5 +++++
 app/models/concerns/application_days_support.rb             | 3 ++-
 db/migrate/20180124061955_add_int_day_types_to_calendars.rb | 2 +-
 db/schema.rb                                                | 4 +---
 spec/models/calendar_spec.rb                                | 9 +++++++++
 5 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/app/models/calendar.rb b/app/models/calendar.rb
index d58e7737d..a4d279e25 100644
--- a/app/models/calendar.rb
+++ b/app/models/calendar.rb
@@ -16,11 +16,16 @@ class Calendar < ActiveRecord::Base
   has_many :time_tables
 
   scope :contains_date, ->(date) { where('date ? = any (dates) OR date ? <@ any (date_ranges)', date, date) }
+  before_create :set_default_days
 
   def self.ransackable_scopes(auth_object = nil)
     [:contains_date]
   end
 
+  def set_default_days
+    self.int_day_types ||= EVERYDAY
+  end
+
   def convert_to_time_table
     Chouette::TimeTable.new.tap do |tt|
       self.dates.each do |d|
diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb
index f83e4a5c8..425cba5bf 100644
--- a/app/models/concerns/application_days_support.rb
+++ b/app/models/concerns/application_days_support.rb
@@ -8,7 +8,8 @@ module ApplicationDaysSupport
   FRIDAY    = 64
   SATURDAY  = 128
   SUNDAY    = 256
-  
+  EVERYDAY  = MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY | SATURDAY | SUNDAY
+
   def display_day_types
     %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')
   end
diff --git a/db/migrate/20180124061955_add_int_day_types_to_calendars.rb b/db/migrate/20180124061955_add_int_day_types_to_calendars.rb
index 6384c7177..5b1ff6fc1 100644
--- a/db/migrate/20180124061955_add_int_day_types_to_calendars.rb
+++ b/db/migrate/20180124061955_add_int_day_types_to_calendars.rb
@@ -1,5 +1,5 @@
 class AddIntDayTypesToCalendars < ActiveRecord::Migration
   def change
-    add_column :calendars, :int_day_types, :integer, default: 0
+    add_column :calendars, :int_day_types, :integer
   end
 end
diff --git a/db/schema.rb b/db/schema.rb
index 34654bdda..94939cfa3 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -15,10 +15,9 @@ ActiveRecord::Schema.define(version: 20180124061955) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
-  enable_extension "hstore"
   enable_extension "postgis"
+  enable_extension "hstore"
   enable_extension "unaccent"
-  enable_extension "objectid"
 
   create_table "access_links", id: :bigserial, force: :cascade do |t|
     t.integer  "access_point_id",                        limit: 8
@@ -118,7 +117,6 @@ ActiveRecord::Schema.define(version: 20180124061955) do
     t.datetime "updated_at"
     t.date     "end_date"
     t.string   "date_type"
-    t.string   "mode"
   end
 
   add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree
diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb
index 4c65b9660..86ce565cd 100644
--- a/spec/models/calendar_spec.rb
+++ b/spec/models/calendar_spec.rb
@@ -21,6 +21,15 @@ RSpec.describe Calendar, :type => :model do
     end
   end
 
+  describe 'application days' do
+    let(:calendar) { create(:calendar) }
+    it "should default to all days" do
+      %w(monday tuesday wednesday thursday friday saturday sunday).each do |day|
+        expect(calendar.send(day)).to be_truthy
+      end
+    end
+  end
+
   describe 'validations' do
     it 'validates that dates and date_ranges do not overlap' do
       expect(build(:calendar, dates: [Date.today], date_ranges: [Date.today..Date.tomorrow])).to_not be_valid
-- 
cgit v1.2.3
From 663effc415399c61d22dadbc235bb7488022d191 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 16:56:16 +0100
Subject: Refs #5682 @3h; Use same UI as for timetables
---
 app/assets/stylesheets/modules/_timetables.sass    |   2 +-
 app/controllers/calendars_controller.rb            |  29 ++++
 app/helpers/time_tables_helper.rb                  |   2 +-
 app/javascript/packs/calendars/edit.js             |  74 ++++++++++
 app/javascript/time_tables/actions/index.js        |   5 +-
 app/javascript/time_tables/components/Metas.js     |  12 +-
 app/models/calendar.rb                             |  79 ++++++++++-
 app/models/calendar/period.rb                      |   5 +
 app/models/chouette/time_table.rb                  |  96 ++++---------
 app/models/concerns/application_days_support.rb    |   4 +
 app/models/concerns/date_support.rb                |  11 +-
 app/models/concerns/timetable_support.rb           | 149 +++++++++++++++++++++
 app/views/calendars/_form.html.slim                |  53 --------
 app/views/calendars/_form_advanced.html.slim       |   8 ++
 app/views/calendars/_form_simple.html.slim         |  56 ++++++++
 app/views/calendars/edit.html.slim                 |   7 +-
 app/views/calendars/month.rabl                     |   9 ++
 app/views/calendars/new.html.slim                  |   4 +-
 app/views/calendars/show.html.slim                 |  11 ++
 app/views/calendars/show.rabl                      |  22 +++
 app/views/time_tables/_show_time_table.html.slim   |  28 +---
 app/views/time_tables/show.html.slim               |   2 +-
 config/locales/calendars.en.yml                    |   7 +
 config/locales/calendars.fr.yml                    |   7 +
 config/routes.rb                                   |   5 +-
 ...180124124215_add_excluded_dates_to_calendars.rb |   5 +
 db/schema.rb                                       |   2 +-
 spec/models/calendar_spec.rb                       | 110 ++++++++++++++-
 28 files changed, 637 insertions(+), 167 deletions(-)
 create mode 100644 app/javascript/packs/calendars/edit.js
 create mode 100644 app/models/concerns/timetable_support.rb
 delete mode 100644 app/views/calendars/_form.html.slim
 create mode 100644 app/views/calendars/_form_advanced.html.slim
 create mode 100644 app/views/calendars/_form_simple.html.slim
 create mode 100644 app/views/calendars/month.rabl
 create mode 100644 app/views/calendars/show.rabl
 create mode 100644 db/migrate/20180124124215_add_excluded_dates_to_calendars.rb
diff --git a/app/assets/stylesheets/modules/_timetables.sass b/app/assets/stylesheets/modules/_timetables.sass
index b06972ef9..8999456ec 100644
--- a/app/assets/stylesheets/modules/_timetables.sass
+++ b/app/assets/stylesheets/modules/_timetables.sass
@@ -30,7 +30,7 @@
 
   .t2e-item
     .th
-      padding: 6px 0 0 0
+      padding: 4px 0 0 0
       border-color: $darkgrey
       border-top-width: 2px
 
diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb
index de8f9d10c..43ed0cfd9 100644
--- a/app/controllers/calendars_controller.rb
+++ b/app/controllers/calendars_controller.rb
@@ -1,8 +1,11 @@
 class CalendarsController < ChouetteController
   include PolicyChecker
+  include TimeTablesHelper
+
   defaults resource_class: Calendar
   before_action :ransack_contains_date, only: [:index]
   respond_to :html
+  respond_to :json, only: :show
   respond_to :js, only: :index
 
   def index
@@ -14,6 +17,32 @@ class CalendarsController < ChouetteController
   def show
   end
 
+  def month
+    @date = params['date'] ? Date.parse(params['date']) : Date.today
+    @calendar = resource
+  end
+
+  def create
+    create! do
+      if @calendar.valid? && has_feature?('application_days_on_calendars')
+        redirect_to([:edit, @calendar])
+        return
+      end
+    end
+  end
+
+  def update
+    if params[:calendar]
+      super
+    else
+      state  = JSON.parse request.raw_post
+      resource.state_update state
+      respond_to do |format|
+        format.json { render json: state, status: state['errors'] ? :unprocessable_entity : :ok }
+      end
+    end
+  end
+
   private
   def calendar_params
     permitted_params = [:id, :name, :short_name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]]
diff --git a/app/helpers/time_tables_helper.rb b/app/helpers/time_tables_helper.rb
index b380a2b0a..1be1fa5a1 100644
--- a/app/helpers/time_tables_helper.rb
+++ b/app/helpers/time_tables_helper.rb
@@ -84,7 +84,7 @@ module TimeTablesHelper
     end unless first.wday == first_weekday
 
     first.upto(last) do |cur|
-      cell_text, cell_attrs = block.call(cur)
+      cell_text, cell_attrs = yield cur
       cell_text  ||= cur.mday
       cell_attrs ||= {}
       cell_attrs[:headers] = th_id(cur, options[:table_id])
diff --git a/app/javascript/packs/calendars/edit.js b/app/javascript/packs/calendars/edit.js
new file mode 100644
index 000000000..bd09657ec
--- /dev/null
+++ b/app/javascript/packs/calendars/edit.js
@@ -0,0 +1,74 @@
+import React from 'react'
+import { render } from 'react-dom'
+import { Provider } from 'react-redux'
+import { createStore } from 'redux'
+import timeTablesApp from '../../time_tables/reducers'
+import App from '../../time_tables/containers/App'
+import clone from '../../helpers/clone'
+
+const actionType = clone(window, "actionType", true)
+
+// logger, DO NOT REMOVE
+// var applyMiddleware = require('redux').applyMiddleware
+// var createLogger = require('redux-logger')
+// var thunkMiddleware = require('redux-thunk').default
+// var promise = require('redux-promise')
+
+let initialState = {
+  status: {
+    actionType: actionType,
+    policy: window.perms,
+    fetchSuccess: true,
+    isFetching: false
+  },
+  timetable: {
+    current_month: [],
+    current_periode_range: '',
+    periode_range: [],
+    time_table_periods: [],
+    time_table_dates: []
+  },
+  metas: {
+    comment: '',
+    day_types: [],
+    initial_tags: []
+  },
+  pagination: {
+    stateChanged: false,
+    currentPage: '',
+    periode_range: []
+  },
+  modal: {
+    type: '',
+    modalProps: {
+      active: false,
+      begin: {
+        day: '01',
+        month: '01',
+        year: String(new Date().getFullYear())
+      },
+      end: {
+        day: '01',
+        month: '01',
+        year: String(new Date().getFullYear())
+      },
+      index: false,
+      error: ''
+    },
+    confirmModal: {}
+  }
+}
+// const loggerMiddleware = createLogger()
+
+let store = createStore(
+  timeTablesApp,
+  initialState,
+  // applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
+)
+
+render(
+  
+    
+  ,
+  document.getElementById('periods')
+)
diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js
index 87c7a3e8d..70f548174 100644
--- a/app/javascript/time_tables/actions/index.js
+++ b/app/javascript/time_tables/actions/index.js
@@ -246,7 +246,8 @@ const actions = {
     return error
   },
   fetchTimeTables: (dispatch, nextPage) => {
-    let urlJSON = window.location.pathname.split('/', 5).join('/')
+    let urlJSON = window.timetablesUrl || window.location.pathname.split('/', 5).join('/')
+
     if(nextPage) {
       urlJSON += "/month.json?date=" + nextPage
     }else{
@@ -277,7 +278,7 @@ const actions = {
     let strDayTypes = actions.arrayToStrDayTypes(metas.day_types)
     metas.day_types = strDayTypes
     let sentState = assign({}, timetable, metas)
-    let urlJSON = window.location.pathname.split('/', 5).join('/')
+    let urlJSON = window.timetablesUrl || window.location.pathname.split('/', 5).join('/')
     let hasError = false
     fetch(urlJSON + '.json', {
       credentials: 'same-origin',
diff --git a/app/javascript/time_tables/components/Metas.js b/app/javascript/time_tables/components/Metas.js
index 4170ba493..3c6848d27 100644
--- a/app/javascript/time_tables/components/Metas.js
+++ b/app/javascript/time_tables/components/Metas.js
@@ -27,7 +27,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat
           
 
           {/* color */}
-          
+          {metas.color !== undefined && 
             
             
               
@@ -69,10 +69,10 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat
                 
                
              
-          
 
+          }
 
           {/* tags */}
-          
+          {metas.tags !== undefined && 
             
             
                onUnselect2Tags(e)}
               />
             
-          
 
+          
 }
 
           {/* calendar */}
-          
+          {metas.calendar !== null && 
             
             
               {metas.calendar ? metas.calendar.name : I18n.time_tables.edit.metas.no_calendar}
             
-          
 
+          
 }
 
           {/* day_types */}
           
diff --git a/app/models/calendar.rb b/app/models/calendar.rb
index a4d279e25..561a2e3f7 100644
--- a/app/models/calendar.rb
+++ b/app/models/calendar.rb
@@ -6,6 +6,7 @@ class Calendar < ActiveRecord::Base
   include DateSupport
   include PeriodSupport
   include ApplicationDaysSupport
+  include TimetableSupport
 
   has_paper_trail class_name: 'PublicVersion'
   belongs_to :organisation
@@ -16,16 +17,29 @@ class Calendar < ActiveRecord::Base
   has_many :time_tables
 
   scope :contains_date, ->(date) { where('date ? = any (dates) OR date ? <@ any (date_ranges)', date, date) }
-  before_create :set_default_days
+
+  after_initialize :set_defaults
 
   def self.ransackable_scopes(auth_object = nil)
     [:contains_date]
   end
 
-  def set_default_days
+  def self.state_permited_attributes item
+    {name: item["comment"]}
+  end
+
+  def set_defaults
+    self.excluded_dates ||= []
     self.int_day_types ||= EVERYDAY
   end
 
+  def human_attribute_name(*args)
+    self.class.human_attribute_name(*args)
+  end
+
+  def shortcuts_update(date=nil)
+  end
+
   def convert_to_time_table
     Chouette::TimeTable.new.tap do |tt|
       self.dates.each do |d|
@@ -38,4 +52,65 @@ class Calendar < ActiveRecord::Base
     end
   end
 
+  def include_in_dates?(day)
+    self.dates.include? day
+  end
+
+  def excluded_date?(day)
+    self.excluded_dates.include? day
+  end
+
+  def update_in_out date, in_out
+    if in_out
+      self.excluded_dates.delete date
+      self.dates << date unless include_in_dates?(date)
+    else
+      self.dates.delete date
+      self.excluded_dates << date unless excluded_date?(date)
+    end
+    date
+  end
+
+  def included_days
+    dates
+  end
+
+  def excluded_days
+    excluded_dates
+  end
+
+  def saved_dates
+    Hash[*self.dates.each_with_index.to_a.map(&:reverse).flatten]
+  end
+
+  def all_dates
+    (dates + excluded_dates).sort.each_with_index.map do |d, i|
+      OpenStruct.new(id: i, date: d, in_out: include_in_dates?(d))
+    end
+  end
+
+  def find_date_by_id id
+    self.dates[id]
+  end
+
+  def destroy_date date
+    self.dates -= [date]
+  end
+
+  def create_date in_out:, date:
+    update_in_out date, in_out
+  end
+
+  def find_period_by_id id
+    self.periods.find{|p| p.id == id}
+  end
+
+  def build_period
+    self.periods << Calendar::Period.new(id: self.periods.count + 1)
+    self.periods.last
+  end
+
+  def destroy_period period
+    @periods = self.periods.select{|p| p.end != period.end || p.begin != period.begin}
+  end
 end
diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb
index 56ab722fe..8b3e4109b 100644
--- a/app/models/calendar/period.rb
+++ b/app/models/calendar/period.rb
@@ -10,6 +10,11 @@ class Calendar < ActiveRecord::Base
     validates_presence_of :begin, :end
     validate :check_end_greather_than_begin
 
+    alias_method :period_start, :begin
+    alias_method :period_end, :end
+    alias_method :period_start=, :begin=
+    alias_method :period_end=, :end=
+
     def check_end_greather_than_begin
       if self.begin && self.end && self.begin >= self.end
         errors.add(:base, I18n.t('calendars.errors.short_period'))
diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb
index 1a1972113..8113457ec 100644
--- a/app/models/chouette/time_table.rb
+++ b/app/models/chouette/time_table.rb
@@ -5,6 +5,7 @@ module Chouette
     include TimeTableRestrictions
     include ObjectidSupport
     include ApplicationDaysSupport
+    include TimetableSupport
 
     # FIXME http://jira.codehaus.org/browse/JRUBY-6358
     self.primary_key = "id"
@@ -82,72 +83,36 @@ module Chouette
       end
     end
 
-    def state_update state
-      update_attributes(self.class.state_permited_attributes(state))
-      self.tag_list    = state['tags'].collect{|t| t['name']}.join(', ')
-      self.calendar_id = nil unless state['calendar']
-
-      days = state['day_types'].split(',')
-      Date::DAYNAMES.map(&:underscore).each do |name|
-        prefix = human_attribute_name(name).first(2)
-        send("#{name}=", days.include?(prefix))
-      end
-
-      saved_dates = Hash[self.dates.collect{ |d| [d.id, d.date]}]
-      cmonth = Date.parse(state['current_periode_range'])
-
-      state['current_month'].each do |d|
-        date    = Date.parse(d['date'])
-        checked = d['include_date'] || d['excluded_date']
-        in_out  = d['include_date'] ? true : false
-
-        date_id = saved_dates.key(date)
-        time_table_date = self.dates.find(date_id) if date_id
+    def find_date_by_id id
+      self.dates.find id
+    end
 
-        next if !checked && !time_table_date
-        # Destroy date if no longer checked
-        next if !checked && time_table_date.destroy
+    def destroy_date date
+      date.destroy
+    end
 
-        # Create new date
-        unless time_table_date
-          time_table_date = self.dates.create({in_out: in_out, date: date})
-        end
-        # Update in_out
-        if in_out != time_table_date.in_out
-          time_table_date.update_attributes({in_out: in_out})
-        end
+    def update_in_out date, in_out
+      if in_out != date.in_out
+        date.update_attributes({in_out: in_out})
       end
-
-      self.state_update_periods state['time_table_periods']
-      self.save
     end
 
-    def state_update_periods state_periods
-      state_periods.each do |item|
-        period = self.periods.find(item['id']) if item['id']
-        next if period && item['deleted'] && period.destroy
-        period ||= self.periods.build
-
-        period.period_start = Date.parse(item['period_start'])
-        period.period_end   = Date.parse(item['period_end'])
+    def find_period_by_id id
+      self.periods.find id
+    end
 
-        if period.changed?
-          period.save
-          item['id'] = period.id
-        end
-      end
+    def build_period
+      periods.build
+    end
 
-      state_periods.delete_if {|item| item['deleted']}
+    def destroy_period period
+      period.destroy
     end
 
     def self.state_permited_attributes item
       item.slice('comment', 'color').to_hash
     end
 
-    def presenter
-      @presenter ||= ::TimeTablePresenter.new( self)
-    end
-
     def self.start_validity_period
       [Chouette::TimeTable.minimum(:start_date)].compact.min
     end
@@ -168,20 +133,6 @@ module Chouette
       self.save
     end
 
-    def month_inspect(date)
-      (date.beginning_of_month..date.end_of_month).map do |d|
-        {
-          day: I18n.l(d, format: '%A'),
-          date: d.to_s,
-          wday: d.wday,
-          wnumber: d.strftime("%W").to_s,
-          mday: d.mday,
-          include_date: include_in_dates?(d),
-          excluded_date: excluded_date?(d)
-        }
-      end
-    end
-
     def save_shortcuts
         shortcuts_update
         self.update_column(:start_date, start_date)
@@ -361,6 +312,17 @@ module Chouette
       days.sort
     end
 
+    def create_date in_out:, date:
+      self.dates.create in_out: in_out, date: date
+    end
+
+    def saved_dates
+      Hash[self.dates.collect{ |d| [d.id, d.date]}]
+    end
+
+    def all_dates
+      dates
+    end
 
     # produce a copy of periods without anyone overlapping or including another
     def optimize_overlapping_periods
diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb
index 425cba5bf..348436aa4 100644
--- a/app/models/concerns/application_days_support.rb
+++ b/app/models/concerns/application_days_support.rb
@@ -35,6 +35,10 @@ module ApplicationDaysSupport
     end
   end
 
+  def valid_day? wday
+    valid_days.include?(wday)
+  end
+
   def self.valid_days(int_day_types)
     # Build an array with day of calendar week (1-7, Monday is 1).
     [].tap do |valid_days|
diff --git a/app/models/concerns/date_support.rb b/app/models/concerns/date_support.rb
index fbfe19af1..5c66cb1a9 100644
--- a/app/models/concerns/date_support.rb
+++ b/app/models/concerns/date_support.rb
@@ -38,9 +38,12 @@ module DateSupport
           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
+          if date_range.cover?(date_value.value)
+            excluded_day = self.respond_to?(:valid_day?) && !self.valid_day?(date_value.value.wday)
+            unless excluded_day
+              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
       end
@@ -77,4 +80,4 @@ module DateSupport
 
     private :clear_date_values
   end
-end
\ No newline at end of file
+end
diff --git a/app/models/concerns/timetable_support.rb b/app/models/concerns/timetable_support.rb
new file mode 100644
index 000000000..8c49723fe
--- /dev/null
+++ b/app/models/concerns/timetable_support.rb
@@ -0,0 +1,149 @@
+module TimetableSupport
+  extend ActiveSupport::Concern
+
+  def presenter
+    @presenter ||= ::TimeTablePresenter.new( self)
+  end
+
+  def periods_max_date
+    return nil if self.periods.empty?
+
+    min_start = self.periods.map(&:period_start).compact.min
+    max_end = self.periods.map(&:period_end).compact.max
+    result = nil
+
+    if max_end && min_start
+      max_end.downto( min_start) do |date|
+        if self.valid_days.include?(date.cwday) && !self.excluded_date?(date)
+            result = date
+            break
+        end
+      end
+    end
+    result
+  end
+
+  def periods_min_date
+    return nil if self.periods.empty?
+
+    min_start = self.periods.map(&:period_start).compact.min
+    max_end = self.periods.map(&:period_end).compact.max
+    result = nil
+
+    if max_end && min_start
+      min_start.upto(max_end) do |date|
+        if self.valid_days.include?(date.cwday) && !self.excluded_date?(date)
+            result = date
+            break
+        end
+      end
+    end
+    result
+  end
+
+  def bounding_dates
+    bounding_min = self.all_dates.select{|d| d.in_out}.map(&:date).compact.min
+    bounding_max = self.all_dates.select{|d| d.in_out}.map(&:date).compact.max
+
+    unless self.periods.empty?
+      bounding_min = periods_min_date if periods_min_date &&
+          (bounding_min.nil? || (periods_min_date < bounding_min))
+
+      bounding_max = periods_max_date if periods_max_date &&
+          (bounding_max.nil? || (bounding_max < periods_max_date))
+    end
+
+    [bounding_min, bounding_max].compact
+  end
+
+  def month_inspect(date)
+    (date.beginning_of_month..date.end_of_month).map do |d|
+      {
+        day: I18n.l(d, format: '%A'),
+        date: d.to_s,
+        wday: d.wday,
+        wnumber: d.strftime("%W").to_s,
+        mday: d.mday,
+        include_date: include_in_dates?(d),
+        excluded_date: excluded_date?(d)
+      }
+    end
+  end
+
+  def include_in_dates?(day)
+    self.dates.any?{ |d| d.date === day && d.in_out == true }
+  end
+
+  def excluded_date?(day)
+    self.dates.any?{ |d| d.date === day && d.in_out == false }
+  end
+
+  def include_in_overlap_dates?(day)
+    return false if self.excluded_date?(day)
+
+    self.all_dates.any?{ |d| d.date === day} \
+    && self.periods.any?{ |period| period.period_start <= day && day <= period.period_end && valid_days.include?(day.cwday) }
+  end
+
+  def include_in_periods?(day)
+    self.periods.any?{ |period| period.period_start <= day &&
+                                day <= period.period_end &&
+                                valid_days.include?(day.cwday) &&
+                                ! excluded_date?(day) }
+  end
+
+  def state_update_periods state_periods
+    state_periods.each do |item|
+      period = self.find_period_by_id(item['id']) if item['id']
+      next if period && item['deleted'] && self.destroy_period(period)
+      period ||= self.build_period
+
+      period.period_start = Date.parse(item['period_start'])
+      period.period_end   = Date.parse(item['period_end'])
+
+      period.save if period === ActiveRecord::Base && period.changed?
+
+      item['id'] = period.id
+    end
+
+    state_periods.delete_if {|item| item['deleted']}
+  end
+
+  def state_update state
+    update_attributes(self.class.state_permited_attributes(state))
+    self.tag_list    = state['tags'].collect{|t| t['name']}.join(', ') if state['tags']
+    self.calendar_id = nil if self.respond_to?(:calendar_id) && !state['calendar']
+
+    days = state['day_types'].split(',')
+    Date::DAYNAMES.map(&:underscore).each do |name|
+      prefix = human_attribute_name(name).first(2)
+      send("#{name}=", days.include?(prefix))
+    end
+
+    cmonth = Date.parse(state['current_periode_range'])
+
+    state['current_month'].each do |d|
+      date    = Date.parse(d['date'])
+      checked = d['include_date'] || d['excluded_date']
+      in_out  = d['include_date'] ? true : false
+
+      date_id = saved_dates.key(date)
+      time_table_date = self.find_date_by_id(date_id) if date_id
+
+      next if !checked && !time_table_date
+      # Destroy date if no longer checked
+      next if !checked && destroy_date(time_table_date)
+
+      # Create new date
+      unless time_table_date
+        time_table_date = self.create_date in_out: in_out, date: date
+      end
+      # Update in_out
+      self.update_in_out time_table_date, in_out
+    end
+
+    self.state_update_periods state['time_table_periods']
+    self.save
+  end
+
+end
diff --git a/app/views/calendars/_form.html.slim b/app/views/calendars/_form.html.slim
deleted file mode 100644
index 3c152c61d..000000000
--- a/app/views/calendars/_form.html.slim
+++ /dev/null
@@ -1,53 +0,0 @@
-= simple_form_for @calendar, html: { class: 'form-horizontal', id: 'calendar_form' }, wrapper: :horizontal_form do |f|
-  .row
-    .col-lg-12
-      = f.input :name
-      = f.input :short_name
-
-      - if policy(@calendar).share?
-        .form-group.has_switch
-          = f.label :shared, class: 'col-sm-4 col-xs-5 control-label'
-          = f.input :shared, as: :boolean, checked_value: true, unchecked_value: false, label: content_tag(:span, t("#{@calendar.shared}"), class: 'switch-label', data: {checkedValue: t('true'), uncheckedValue: t('false')}), wrapper_html: { class: 'col-sm-8 col-xs-7'}
-
-  .separator
-  
-  .row
-    .col-lg-12
-      .subform
-        .nested-head
-          .wrapper
-            div
-              .form-group
-                label.control-label
-                  = Calendar.human_attribute_name(:date)
-            div
-
-        = f.simple_fields_for :date_values do |date_value|
-          = render 'date_value_fields', f: date_value
-
-        .links.nested-linker
-          = link_to_add_association t('simple_form.labels.calendar.add_a_date'), f, :date_values, class: 'btn btn-outline-primary'
-  
-  .separator
-  
-  .row
-    .col-lg-12
-      .subform
-        .nested-head
-          .wrapper
-            div
-              .form-group
-                label.control-label
-                  = t('simple_form.labels.calendar.ranges.begin')
-            div
-              .form-group
-                label.control-label
-                  = t('simple_form.labels.calendar.ranges.end')
-            div
-
-        = f.simple_fields_for :periods do |period|
-          = render 'period_fields', f: period
-        .links.nested-linker
-          = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary'
-
-  = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'calendar_form'
diff --git a/app/views/calendars/_form_advanced.html.slim b/app/views/calendars/_form_advanced.html.slim
new file mode 100644
index 000000000..b4154166b
--- /dev/null
+++ b/app/views/calendars/_form_advanced.html.slim
@@ -0,0 +1,8 @@
+#periods
+
+= javascript_tag do
+  | window.actionType = "#{raw params[:action]}";
+  | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};
+  | window.timetablesUrl = "#{calendar_url(@calendar).html_safe}";
+
+= javascript_pack_tag 'calendars/edit.js'
diff --git a/app/views/calendars/_form_simple.html.slim b/app/views/calendars/_form_simple.html.slim
new file mode 100644
index 000000000..2f469ada7
--- /dev/null
+++ b/app/views/calendars/_form_simple.html.slim
@@ -0,0 +1,56 @@
+.row
+  .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+    = simple_form_for @calendar, html: { class: 'form-horizontal', id: 'calendar_form' }, wrapper: :horizontal_form do |f|
+      .row
+        .col-lg-12
+          = f.input :name
+          = f.input :short_name
+
+          - if policy(@calendar).share?
+            .form-group.has_switch
+              = f.label :shared, class: 'col-sm-4 col-xs-5 control-label'
+              = f.input :shared, as: :boolean, checked_value: true, unchecked_value: false, label: content_tag(:span, t("#{@calendar.shared}"), class: 'switch-label', data: {checkedValue: t('true'), uncheckedValue: t('false')}), wrapper_html: { class: 'col-sm-8 col-xs-7'}
+
+      .separator
+
+      - unless has_feature?('application_days_on_calendars')
+        .row
+          .col-lg-12
+            .subform
+              .nested-head
+                .wrapper
+                  div
+                    .form-group
+                      label.control-label
+                        = Calendar.human_attribute_name(:date)
+                  div
+
+              = f.simple_fields_for :date_values do |date_value|
+                = render 'date_value_fields', f: date_value
+
+              .links.nested-linker
+                = link_to_add_association t('simple_form.labels.calendar.add_a_date'), f, :date_values, class: 'btn btn-outline-primary'
+
+        .separator
+
+      .row
+        .col-lg-12
+          .subform
+            .nested-head
+              .wrapper
+                div
+                  .form-group
+                    label.control-label
+                      = t('simple_form.labels.calendar.ranges.begin')
+                div
+                  .form-group
+                    label.control-label
+                      = t('simple_form.labels.calendar.ranges.end')
+                div
+
+            = f.simple_fields_for :periods do |period|
+              = render 'period_fields', f: period
+            .links.nested-linker
+              = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary'
+
+      = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'calendar_form'
diff --git a/app/views/calendars/edit.html.slim b/app/views/calendars/edit.html.slim
index e806fc94b..e64790daf 100644
--- a/app/views/calendars/edit.html.slim
+++ b/app/views/calendars/edit.html.slim
@@ -2,6 +2,7 @@
 - page_header_content_for @calendar
 .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'
+    - if has_feature?('application_days_on_calendars')
+      = render 'form_advanced'
+    - else
+      = render 'form_simple'
diff --git a/app/views/calendars/month.rabl b/app/views/calendars/month.rabl
new file mode 100644
index 000000000..1584db44c
--- /dev/null
+++ b/app/views/calendars/month.rabl
@@ -0,0 +1,9 @@
+object @calendar
+
+node do |tt|
+  {
+    name: I18n.l(@date, format: '%B'),
+    days: tt.month_inspect(@date),
+    day_types: %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| tt.send(d) }.map{ |d| tt.human_attribute_name(d).first(2)}.join('')
+  }
+end
diff --git a/app/views/calendars/new.html.slim b/app/views/calendars/new.html.slim
index ce8b6a036..b3840b705 100644
--- a/app/views/calendars/new.html.slim
+++ b/app/views/calendars/new.html.slim
@@ -1,6 +1,4 @@
 - breadcrumb :calendars
 .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'
+    = render 'form_simple'
diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim
index 8eb66dc60..ec53be0ef 100644
--- a/app/views/calendars/show.html.slim
+++ b/app/views/calendars/show.html.slim
@@ -11,3 +11,14 @@
             'Organisation' => resource.organisation.name,
             Calendar.human_attribute_name(:dates) =>  resource.dates.collect{|d| l(d, format: :short)}.join(', ').html_safe,
             Calendar.human_attribute_name(:date_ranges) => resource.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('
').html_safe }
+
+    - if has_feature?('application_days_on_calendars')
+      .row
+        .col-lg-12.mb-sm
+          .pagination.pull-right
+            = @year
+            .page_links
+              = link_to '', calendar_path(@calendar, year: (@year - 1)), class: 'previous_page'
+              = link_to '', calendar_path(@calendar, year: (@year + 1)), class: 'next_page'
+
+      = render 'time_tables/show_time_table', time_table: @calendar
diff --git a/app/views/calendars/show.rabl b/app/views/calendars/show.rabl
new file mode 100644
index 000000000..295d97528
--- /dev/null
+++ b/app/views/calendars/show.rabl
@@ -0,0 +1,22 @@
+object @calendar
+
+attributes :id
+node do |tt|
+  {
+    comment: tt.name,
+    time_table_bounding: tt.presenter.time_table_bounding,
+    day_types: %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| tt.send(d) }.map{ |d| tt.human_attribute_name(d).first(2)}.join(''),
+    current_month: tt.month_inspect(Date.today),
+    periode_range: month_periode_enum(3),
+    current_periode_range: Date.today.beginning_of_month,
+    short_id: tt.object_id,
+  }
+end
+
+child(:periods, object_root: false, root: :time_table_periods) do
+  attributes :id, :period_start, :period_end
+end
+
+child(:all_dates, object_root: false, root: :time_table_dates) do
+  attributes :id, :date, :in_out
+end
diff --git a/app/views/time_tables/_show_time_table.html.slim b/app/views/time_tables/_show_time_table.html.slim
index ebfe9d283..102dbfad7 100644
--- a/app/views/time_tables/_show_time_table.html.slim
+++ b/app/views/time_tables/_show_time_table.html.slim
@@ -2,24 +2,10 @@
   - (1..12).each do |month|
     .col-lg-3.col-md-4.col-sm-4.col-xs-6
       = new_alt_calendar(year: @year, month: month, first_day_of_week: 1, calendar_title: "#{I18n.t("date.month_names")[month]}", show_today: false) do |d|
-        / - if @time_table.excluded_date?(d)
-        /   - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day excluded_date"}]
-        - if @time_table.include_in_overlap_dates?(d)
-          - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day overlaped_date", title: 'Voir'}]
-        - elsif @time_table.include_in_dates?(d)
-          - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day selected_date", title: 'Voir'}]
-        - elsif @time_table.include_in_periods?(d)
-          - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day selected_period", title: 'Voir'}]
-
-/ .row
-/   .col-lg-12
-/     / wip
-/     - if @time_table.dates.where("in_out = true").present?
-/       h3.time_table_dates = @time_table.human_attribute_name("dates")
-/       .dates.content
-/         == render "time_tables/dates"
-/ 
-/     - if @time_table.dates.where("in_out = false").present?
-/       h3.time_table_dates = @time_table.human_attribute_name("excluded_dates")
-/       .excluded_dates.content
-/         == render "time_tables/excluded_dates"
+        - edit_url = [:edit, @referential, time_table].compact
+        - if time_table.include_in_overlap_dates?(d)
+          - [link_to(d.mday, edit_url), {class: "day overlaped_date", title: 'Voir'}]
+        - elsif time_table.include_in_dates?(d)
+          - [link_to(d.mday, edit_url), {class: "day selected_date", title: 'Voir'}]
+        - elsif time_table.include_in_periods?(d)
+          - [link_to(d.mday, edit_url), {class: "day selected_period", title: 'Voir'}]
diff --git a/app/views/time_tables/show.html.slim b/app/views/time_tables/show.html.slim
index 6d15323f3..e038b73ca 100644
--- a/app/views/time_tables/show.html.slim
+++ b/app/views/time_tables/show.html.slim
@@ -24,4 +24,4 @@
             = link_to '', referential_time_table_path(@referential, @time_table, year: (@year - 1)), class: 'previous_page'
             = link_to '', referential_time_table_path(@referential, @time_table, year: (@year + 1)), class: 'next_page'
 
-    = render 'show_time_table'
+    = render 'show_time_table', time_table: @time_table
diff --git a/config/locales/calendars.en.yml b/config/locales/calendars.en.yml
index a2110d053..c3df413af 100644
--- a/config/locales/calendars.en.yml
+++ b/config/locales/calendars.en.yml
@@ -69,6 +69,13 @@ en:
         dates: Dates
         shared: Shared
         organisation: Organisation
+        monday: "Monday"
+        tuesday: "Tuesday"
+        wednesday: "Wednesday"
+        thursday: "Thursday"
+        friday: "Friday"
+        saturday: "Saturday"
+        sunday: "Sunday"
     errors:
       models:
         calendar:
diff --git a/config/locales/calendars.fr.yml b/config/locales/calendars.fr.yml
index f9eaf1be5..6fd265925 100644
--- a/config/locales/calendars.fr.yml
+++ b/config/locales/calendars.fr.yml
@@ -69,6 +69,13 @@ fr:
         dates: Dates
         shared: Partagé
         organisation: Organisation
+        monday: "Lundi"
+        tuesday: "Mardi"
+        wednesday: "Mercredi"
+        thursday: "Jeudi"
+        friday: "Vendredi"
+        saturday: "Samedi"
+        sunday: "Dimanche"
     errors:
       models:
         calendar:
diff --git a/config/routes.rb b/config/routes.rb
index 432423ac5..fc2807ab1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -114,13 +114,16 @@ ChouetteIhm::Application.routes.draw do
 
   resources :calendars do
     get :autocomplete, on: :collection, controller: 'autocomplete_calendars'
+    member do
+      get 'month', defaults: { format: :json }
+    end
   end
 
   resources :referentials, except: :index do
     resources :autocomplete_stop_areas, only: [:show, :index] do
       get 'around', on: :member
     end
-    resources :autocomplete_purchase_windows, only: [:index] 
+    resources :autocomplete_purchase_windows, only: [:index]
     get :select_compliance_control_set
     post :validate, on: :member
     resources :autocomplete_time_tables, only: [:index]
diff --git a/db/migrate/20180124124215_add_excluded_dates_to_calendars.rb b/db/migrate/20180124124215_add_excluded_dates_to_calendars.rb
new file mode 100644
index 000000000..b98b50717
--- /dev/null
+++ b/db/migrate/20180124124215_add_excluded_dates_to_calendars.rb
@@ -0,0 +1,5 @@
+class AddExcludedDatesToCalendars < ActiveRecord::Migration
+  def change
+    add_column :calendars, :excluded_dates, :date, array: true
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 94939cfa3..5356c9909 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20180124061955) do
+ActiveRecord::Schema.define(version: 20180124124215) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb
index 86ce565cd..3cffd0f8a 100644
--- a/spec/models/calendar_spec.rb
+++ b/spec/models/calendar_spec.rb
@@ -32,7 +32,11 @@ RSpec.describe Calendar, :type => :model do
 
   describe 'validations' do
     it 'validates that dates and date_ranges do not overlap' do
-      expect(build(:calendar, dates: [Date.today], date_ranges: [Date.today..Date.tomorrow])).to_not be_valid
+      expect(build(:calendar, dates: [Date.today.beginning_of_week], date_ranges: [Date.today.beginning_of_week..Date.today])).to_not be_valid
+    end
+
+    it 'validates that dates and date_ranges do not overlap but allow for days not in the list' do
+      expect(build(:calendar, dates: [Date.today.beginning_of_week], date_ranges: [Date.today.beginning_of_week..Date.today], int_day_types: Calendar::THURSDAY)).to be_valid
     end
 
     it 'validates that there are no duplicates in dates' do
@@ -52,4 +56,108 @@ RSpec.describe Calendar, :type => :model do
     end
   end
 
+  describe "Update state" do
+    def calendar_to_state calendar
+      calendar.slice('id').tap do |item|
+        item['comment'] = calendar.name
+        item['day_types'] = "Di,Lu,Ma,Me,Je,Ve,Sa"
+        item['current_month'] = calendar.month_inspect(Date.today.beginning_of_month)
+        item['current_periode_range'] = Date.today.beginning_of_month.to_s
+        item['time_table_periods'] = calendar.periods.map{|p| {'id': p.id, 'period_start': p.period_start.to_s, 'period_end': p.period_end.to_s}}
+      end
+    end
+
+    subject(:calendar){ create :calendar }
+    let(:state) { calendar_to_state subject }
+
+    it 'should update time table periods association' do
+      period = state['time_table_periods'].first
+      period['period_start'] = (Date.today - 1.month).to_s
+      period['period_end']   = (Date.today + 1.month).to_s
+
+      subject.state_update_periods state['time_table_periods']
+      ['period_end', 'period_start'].each do |prop|
+        expect(subject.reload.periods.first.send(prop).to_s).to eq(period[prop])
+      end
+    end
+
+    it 'should create time table periods association' do
+      state['time_table_periods'] << {
+        'id' => false,
+        'period_start' => (Date.today + 1.year).to_s,
+        'period_end' => (Date.today + 2.year).to_s
+      }
+
+      expect {
+        subject.state_update_periods state['time_table_periods']
+      }.to change {subject.periods.count}.by(1)
+      expect(state['time_table_periods'].last['id']).to eq subject.reload.periods.last.id
+    end
+
+    it 'should delete time table periods association' do
+      state['time_table_periods'].first['deleted'] = true
+      expect {
+        subject.state_update_periods state['time_table_periods']
+      }.to change {subject.periods.count}.by(-1)
+    end
+
+    it 'should update name' do
+      state['comment'] = "Edited timetable name"
+      subject.state_update state
+      expect(subject.reload.name).to eq state['comment']
+    end
+
+    it 'should update day_types' do
+      state['day_types'] = "Di,Lu,Je,Ma"
+      subject.state_update state
+      expect(subject.reload.valid_days).to include(7, 1, 4, 2)
+      expect(subject.reload.valid_days).not_to include(3, 5, 6)
+    end
+
+    it 'should delete date if date is set to neither include or excluded date' do
+      updated = state['current_month'].map do |day|
+        day['include_date'] = false if day['include_date']
+      end
+
+      expect {
+        subject.state_update state
+      }.to change {subject.dates.count}.by(-updated.compact.count)
+    end
+
+    it 'should update date if date is set to excluded date' do
+        updated = state['current_month'].map do |day|
+          next unless day['include_date']
+          day['include_date']  = false
+          day['excluded_date'] = true
+        end
+
+        subject.state_update state
+        expect(subject.reload.excluded_days.count).to eq (updated.compact.count)
+    end
+
+    it 'should create new include date' do
+      day  = state['current_month'].find{|d| !d['excluded_date'] && !d['include_date'] }
+      date = Date.parse(day['date'])
+      day['include_date'] = true
+      expect(subject.included_days).not_to include(date)
+
+      expect {
+        subject.state_update state
+      }.to change {subject.dates.count}.by(1)
+      expect(subject.reload.included_days).to include(date)
+    end
+
+    it 'should create new exclude date' do
+      day  = state['current_month'].find{|d| !d['excluded_date'] && !d['include_date']}
+      date = Date.parse(day['date'])
+      day['excluded_date'] = true
+      expect(subject.excluded_days).not_to include(date)
+
+      expect {
+        subject.state_update state
+      }.to change {subject.all_dates.count}.by(1)
+      expect(subject.reload.excluded_days).to include(date)
+    end
+  end
+
 end
-- 
cgit v1.2.3
From 9caa25a7c2267f715822178b84ea15376f079a29 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 24 Jan 2018 17:18:31 +0100
Subject: Refs #5682; Fix timetables bulk update
---
 app/models/concerns/timetable_support.rb | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/models/concerns/timetable_support.rb b/app/models/concerns/timetable_support.rb
index 8c49723fe..d2bc99d51 100644
--- a/app/models/concerns/timetable_support.rb
+++ b/app/models/concerns/timetable_support.rb
@@ -100,8 +100,7 @@ module TimetableSupport
 
       period.period_start = Date.parse(item['period_start'])
       period.period_end   = Date.parse(item['period_end'])
-
-      period.save if period === ActiveRecord::Base && period.changed?
+      period.save if period.is_a?(ActiveRecord::Base) && period.changed?
 
       item['id'] = period.id
     end
-- 
cgit v1.2.3
From ee7fe7a4760c3fdde987ef666db45c161e788ee9 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 15:47:06 +0100
Subject: Refs #5796: Add some legroom in the JourneyPatterns editor
---
 app/assets/stylesheets/modules/_jp_collection.sass           | 7 ++++++-
 app/javascript/journey_patterns/components/JourneyPattern.js | 2 +-
 2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/app/assets/stylesheets/modules/_jp_collection.sass b/app/assets/stylesheets/modules/_jp_collection.sass
index b4370a5ac..46ea3fb6e 100644
--- a/app/assets/stylesheets/modules/_jp_collection.sass
+++ b/app/assets/stylesheets/modules/_jp_collection.sass
@@ -121,6 +121,11 @@
           .totals
             color: $blue
             padding-top: 4px
+            margin-left: -5px
+            margin-right: -5px
+            span
+              white-space: nowrap
+              padding: 0 5px
             i
               padding-right: 3px
 
@@ -219,7 +224,7 @@
 
               input
                 display: inline-block
-                width: 40px
+                width: 50px
                 border: none
                 margin-right: 5px
                 color: black
diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js
index 01734f3a3..35765b99a 100644
--- a/app/javascript/journey_patterns/components/JourneyPattern.js
+++ b/app/javascript/journey_patterns/components/JourneyPattern.js
@@ -109,7 +109,7 @@ export default class JourneyPattern extends Component{
     }
     else{
       let hours = parseInt(time/60)
-      return hours + " h " + (time - 60*hours) + " min"
+      return hours + " h " + (time - 60*hours)
     }
   }
 
-- 
cgit v1.2.3
From f671a1dfe6fed6422db0fbefd15de264bd383721 Mon Sep 17 00:00:00 2001
From: Zog
Date: Wed, 31 Jan 2018 16:09:55 +0100
Subject: Refs #5796; Fix dropdown menu
---
 app/assets/stylesheets/components/_buttons.sass | 77 +++++++++++++------------
 app/assets/stylesheets/components/_tables.sass  |  2 +-
 2 files changed, 40 insertions(+), 39 deletions(-)
diff --git a/app/assets/stylesheets/components/_buttons.sass b/app/assets/stylesheets/components/_buttons.sass
index 93f9907a5..2881cee3e 100644
--- a/app/assets/stylesheets/components/_buttons.sass
+++ b/app/assets/stylesheets/components/_buttons.sass
@@ -142,47 +142,48 @@ table, .table
         margin: 0
         border-radius: 0
         box-shadow: 0 0 3px rgba($darkgrey, 0.25)
-        > ul
-          padding: 0
-          margin: 0
-          > li > a, > li > button
-            padding: 5px 15px
-            white-space: nowrap
-            padding: 5px 15px
-            font-weight: normal
-            line-height: $line-height
-            display: block
-            font-size: 14px
-            &:hover, &:focus
-              text-decoration: none
-              background-color: whitesmoke
-              outline: none
 
-          &:not(:first-child)
+        & > ul:not(:first-child)
+          position: relative
+          margin-top: 11px
+          &:before
+            content: ''
+            display: block
+            position: absolute
+            left: 15px
+            right: 15px
+            top: -6px
+            height: 1px
+            background-color: $grey
+
+      ul.dropdown-menu, .dropdown-menu > ul
+        padding: 0
+        margin: 0
+        > li > a, > li > button
+          padding: 5px 15px
+          white-space: nowrap
+          padding: 5px 15px
+          font-weight: normal
+          line-height: $line-height
+          display: block
+          font-size: 14px
+          &:hover, &:focus
+            text-decoration: none
+            background-color: whitesmoke
+            outline: none
+
+        > li.delete-action
+          > a, > button
+            display: block
             position: relative
-            margin-top: 11px
-            &:before
-              content: ''
-              display: block
-              position: absolute
-              left: 15px
-              right: 15px
-              top: -6px
-              height: 1px
-              background-color: $grey
-
-          > li.delete-action
+            .fa:first-child
+              margin-right: 0.5em
+
+          & + li.delete-action
             > a, > button
-              display: block
-              position: relative
-              .fa:first-child
-                margin-right: 0.5em
-
-            & + li.delete-action
-              > a, > button
-                margin-top: 0
-                &:before
-                  display: none
+              margin-top: 0
+              &:before
+                display: none
 
 
   &.table-2entries .t2e-item
diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass
index 119a38c07..35e1122f3 100644
--- a/app/assets/stylesheets/components/_tables.sass
+++ b/app/assets/stylesheets/components/_tables.sass
@@ -372,7 +372,7 @@
       white-space: nowrap
       // border-right: 1px solid rgba($grey, 0.5)
       max-width: 100%
-      min-width: 280px
+      min-width: 330px
       padding-right: 1px
 
     .t2e-item
-- 
cgit v1.2.3
From d114d549f7bc8a772803175dee9a665266d8ed04 Mon Sep 17 00:00:00 2001
From: Zog
Date: Thu, 1 Feb 2018 10:34:40 +0100
Subject: Refs #5798 @3h; Show return journeys on the journeys' editor.
---
 app/assets/stylesheets/components/_forms.sass      | 11 +++-
 app/controllers/vehicle_journeys_controller.rb     | 65 ++++++++++++----------
 app/javascript/helpers/stop_area_header_manager.js |  5 ++
 app/javascript/packs/vehicle_journeys/index.js     |  3 +-
 app/javascript/vehicle_journeys/actions/index.js   | 17 ++++--
 app/javascript/vehicle_journeys/components/App.js  |  1 +
 .../vehicle_journeys/components/VehicleJourney.js  |  6 +-
 .../vehicle_journeys/components/VehicleJourneys.js | 41 +++++++++++---
 .../components/tools/EditVehicleJourney.js         |  8 ++-
 .../containers/VehicleJourneysList.js              |  8 ++-
 .../containers/tools/EditVehicleJourney.js         |  1 +
 app/javascript/vehicle_journeys/reducers/index.js  |  3 +
 .../reducers/returnVehicleJourneys.js              | 11 ++++
 app/models/chouette/journey_pattern.rb             |  3 +-
 app/views/vehicle_journeys/index.html.slim         |  6 ++
 15 files changed, 134 insertions(+), 55 deletions(-)
 create mode 100644 app/javascript/vehicle_journeys/reducers/returnVehicleJourneys.js
diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass
index 998703ef0..ba5bbbde1 100644
--- a/app/assets/stylesheets/components/_forms.sass
+++ b/app/assets/stylesheets/components/_forms.sass
@@ -256,8 +256,15 @@ table, .table
   &.table-2entries .t2e-item
     > .th
       position: relative
-
-      > .checkbox
+      > .st_action
+        list-style-type: none
+        button
+          border-radius: 50%
+          background: $blue
+          color: white
+          border: none
+
+      > .checkbox, > .st_action
         position: absolute
         right: 0
         top: 0
diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb
index ed6ba6ed1..e031e4952 100644
--- a/app/controllers/vehicle_journeys_controller.rb
+++ b/app/controllers/vehicle_journeys_controller.rb
@@ -50,36 +50,8 @@ class VehicleJourneysController < ChouetteController
       format.html do
         load_missions
         load_custom_fields
-        @stop_points_list = []
-        @stop_points_list = route.stop_points.includes(:stop_area).map do |sp|
-          {
-            :id => sp.stop_area.id,
-            :route_id => sp.try(:route_id),
-            :object_id => sp.try(:objectid),
-            :position => sp.try(:position),
-            :for_boarding => sp.try(:for_boarding),
-            :for_alighting => sp.try(:for_alighting),
-            :name => sp.stop_area.try(:name),
-            :time_zone_offset => sp.stop_area.try(:time_zone_offset),
-            :time_zone_formatted_offset => sp.stop_area.try(:time_zone_formatted_offset),
-            :zip_code => sp.stop_area.try(:zip_code),
-            :city_name => sp.stop_area.try(:city_name),
-            :comment => sp.stop_area.try(:comment),
-            :area_type => sp.stop_area.try(:area_type),
-            :area_type_i18n => I18n.t(sp.stop_area.try(:area_type), scope: 'area_types.label'),
-            :area_kind => sp.stop_area.try(:kind),
-            :stop_area_id => sp.stop_area_id,
-            :registration_number => sp.stop_area.try(:registration_number),
-            :nearest_topic_name => sp.stop_area.try(:nearest_topic_name),
-            :fare_code => sp.stop_area.try(:fare_code),
-            :longitude => sp.stop_area.try(:longitude),
-            :latitude => sp.stop_area.try(:latitude),
-            :long_lat_type => sp.stop_area.try(:long_lat_type),
-            :country_code => sp.stop_area.try(:country_code),
-            :country_name => sp.stop_area.try(:country_name),
-            :street_name => sp.stop_area.try(:street_name)
-          }
-        end
+        @stop_points_list = map_stop_points(route.stop_points)
+        @return_stop_points_list = map_stop_points(route.opposite_route&.stop_points) if has_feature?(:vehicle_journeys_return_route)
         @transport_mode = route.line['transport_mode']
         @transport_submode = route.line['transport_submode']
 
@@ -185,6 +157,39 @@ class VehicleJourneysController < ChouetteController
     @custom_fields = current_workgroup.custom_fields_definitions
   end
 
+  def map_stop_points points
+    (points&.includes(:stop_area) || []).map do |sp|
+      {
+        :id => sp.stop_area.id,
+        :route_id => sp.try(:route_id),
+        :object_id => sp.try(:objectid),
+        :area_object_id => sp.stop_area.try(:objectid),
+        :position => sp.try(:position),
+        :for_boarding => sp.try(:for_boarding),
+        :for_alighting => sp.try(:for_alighting),
+        :name => sp.stop_area.try(:name),
+        :time_zone_offset => sp.stop_area.try(:time_zone_offset),
+        :time_zone_formatted_offset => sp.stop_area.try(:time_zone_formatted_offset),
+        :zip_code => sp.stop_area.try(:zip_code),
+        :city_name => sp.stop_area.try(:city_name),
+        :comment => sp.stop_area.try(:comment),
+        :area_type => sp.stop_area.try(:area_type),
+        :area_type_i18n => I18n.t(sp.stop_area.try(:area_type), scope: 'area_types.label'),
+        :area_kind => sp.stop_area.try(:kind),
+        :stop_area_id => sp.stop_area_id,
+        :registration_number => sp.stop_area.try(:registration_number),
+        :nearest_topic_name => sp.stop_area.try(:nearest_topic_name),
+        :fare_code => sp.stop_area.try(:fare_code),
+        :longitude => sp.stop_area.try(:longitude),
+        :latitude => sp.stop_area.try(:latitude),
+        :long_lat_type => sp.stop_area.try(:long_lat_type),
+        :country_code => sp.stop_area.try(:country_code),
+        :country_name => sp.stop_area.try(:country_name),
+        :street_name => sp.stop_area.try(:street_name)
+      }
+    end
+  end
+
   def load_missions
     @all_missions = route.journey_patterns.count > 10 ? [] : route.journey_patterns.map do |item|
       {
diff --git a/app/javascript/helpers/stop_area_header_manager.js b/app/javascript/helpers/stop_area_header_manager.js
index 2c820caf9..5b18e2f63 100644
--- a/app/javascript/helpers/stop_area_header_manager.js
+++ b/app/javascript/helpers/stop_area_header_manager.js
@@ -42,6 +42,11 @@ export default class StopAreaHeaderManager {
     let index = this.ids_list.indexOf(object_id)
     let sp = this.stopPointsList[index]
     let previousBreakpoint = this.stopPointsList[index - 1]
+    if(sp == undefined){
+      console.log("STOP_POINT NOT FOUND: " + object_id)
+      console.log("AVAILABLE IDS:" + this.ids_list)
+      return
+    }
     if(index == 0 || (sp[attribute_to_check] != previousBreakpoint[attribute_to_check])){
       showHeadline = true
       headline = this.hasFeature('long_distance_routes') ? sp.country_name : sp.city_name
diff --git a/app/javascript/packs/vehicle_journeys/index.js b/app/javascript/packs/vehicle_journeys/index.js
index aa5738d59..e6867cb17 100644
--- a/app/javascript/packs/vehicle_journeys/index.js
+++ b/app/javascript/packs/vehicle_journeys/index.js
@@ -60,6 +60,7 @@ var initialState = {
   },
   vehicleJourneys: [],
   stopPointsList: window.stopPoints,
+  returnStopPointsList: window.returnStopPoints,
   pagination: {
     page : 1,
     totalCount: 0,
@@ -99,7 +100,7 @@ let store = createStore(
 
 render(
   
-    
+    
   ,
   document.getElementById('vehicle_journeys_wip')
 )
diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js
index 8970c6025..74a333b53 100644
--- a/app/javascript/vehicle_journeys/actions/index.js
+++ b/app/javascript/vehicle_journeys/actions/index.js
@@ -13,8 +13,8 @@ const actions = {
   exitEditMode: () => ({
     type: "EXIT_EDIT_MODE"
   }),
-  receiveVehicleJourneys : (json) => ({
-    type: "RECEIVE_VEHICLE_JOURNEYS",
+  receiveVehicleJourneys : (json, returnJourneys) => ({
+    type: (returnJourneys ? "RECEIVE_RETURN_VEHICLE_JOURNEYS" : "RECEIVE_VEHICLE_JOURNEYS"),
     json
   }),
   receiveErrors : (json) => ({
@@ -290,10 +290,17 @@ const actions = {
     type: 'RECEIVE_TOTAL_COUNT',
     total
   }),
-  fetchVehicleJourneys : (dispatch, currentPage, nextPage, queryString) => {
+  fetchVehicleJourneys : (dispatch, currentPage, nextPage, queryString, url) => {
+    let returnJourneys = false
     if(currentPage == undefined){
       currentPage = 1
     }
+    if(url == undefined){
+      url = window.location.pathname
+    }
+    else{
+      returnJourneys = true
+    }
     let vehicleJourneys = []
     let page
     switch (nextPage) {
@@ -315,7 +322,7 @@ const actions = {
       str = '.json?page=' + page.toString()
       sep = '&'
     }
-    let urlJSON = window.location.pathname + str
+    let urlJSON = url + str
     if (queryString){
       urlJSON = urlJSON + sep + queryString
     }
@@ -375,7 +382,7 @@ const actions = {
             )
           }
           window.currentItemsLength = vehicleJourneys.length
-          dispatch(actions.receiveVehicleJourneys(vehicleJourneys))
+          dispatch(actions.receiveVehicleJourneys(vehicleJourneys, returnJourneys))
           dispatch(actions.receiveTotalCount(json.total))
         }
       })
diff --git a/app/javascript/vehicle_journeys/components/App.js b/app/javascript/vehicle_journeys/components/App.js
index 44559c7c6..5ac284438 100644
--- a/app/javascript/vehicle_journeys/components/App.js
+++ b/app/javascript/vehicle_journeys/components/App.js
@@ -22,6 +22,7 @@ export default function App() {
 
       
       
+      {window.returnRouteUrl && 
}
 
       
         
diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js
index 2b5783dda..e7d4b5b30 100644
--- a/app/javascript/vehicle_journeys/components/VehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js
@@ -1,6 +1,7 @@
 import React, { Component } from 'react'
 import PropTypes from 'prop-types'
 import actions from '../actions'
+import EditVehicleJourney from '../containers/tools/EditVehicleJourney'
 
 export default class VehicleJourney extends Component {
   constructor(props) {
@@ -80,7 +81,7 @@ export default class VehicleJourney extends Component {
               {purchase_windows.length > 3 &&  + {purchase_windows.length - 3}}
             
           }
-          
+          {!this.props.disabled && 
             
             
-          
+          
}
+          {this.props.disabled && 
}
         
 
         {this.props.value.vehicle_journey_at_stops.map((vj, i) =>
           
diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js
index 256ca81f9..ae852b35a 100644
--- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js
@@ -8,14 +8,36 @@ export default class VehicleJourneys extends Component {
   constructor(props){
     super(props)
     this.headerManager = new StopAreaHeaderManager(
-      _.map(this.props.stopPointsList, (sp)=>{return sp.object_id}),
-      this.props.stopPointsList,
+      _.map(this.stopPoints(), (sp)=>{return sp.object_id}),
+      this.stopPoints(),
       this.props.filters.features
     )
   }
 
+  isReturn() {
+    return this.props.routeUrl != undefined
+  }
+
+  vehicleJourneysList() {
+    if(this.isReturn()){
+      return this.props.returnVehicleJourneys
+    }
+    else{
+      return this.props.vehicleJourneys
+    }
+  }
+
+  stopPoints() {
+    if(this.isReturn()){
+      return this.props.returnStopPointsList
+    }
+    else{
+      return this.props.stopPointsList
+    }
+  }
+
   componentDidMount() {
-    this.props.onLoadFirstPage(this.props.filters)
+    this.props.onLoadFirstPage(this.props.filters, this.props.routeUrl)
   }
 
   hasFeature(key) {
@@ -89,10 +111,10 @@ export default class VehicleJourneys extends Component {
               
             )}
 
-            { this.props.vehicleJourneys.errors && this.props.vehicleJourneys.errors.length && _.some(this.props.vehicleJourneys, 'errors') && (
+            { this.vehicleJourneysList().errors && this.vehicleJourneysList().errors.length && _.some(this.vehicleJourneysList(), 'errors') && (
               
                 Erreur : 
-                {this.props.vehicleJourneys.map((vj, index) =>
+                {this.vehicleJourneysList().map((vj, index) =>
                   vj.errors && vj.errors.map((err, i) => {
                     return (
                       
@@ -104,7 +126,7 @@ export default class VehicleJourneys extends Component {
               
 
             )}
 
-             0) ? '' : ' no_result')}>
+            
 0) ? '' : ' no_result')}>
               
                 
                   ID course
@@ -114,7 +136,7 @@ export default class VehicleJourneys extends Component {
                   
Calendriers
                   { this.hasFeature('purchase_windows') && 
Calendriers Commerciaux
 }
                 
 
-                {this.props.stopPointsList.map((sp, i) =>{
+                {this.stopPoints().map((sp, i) =>{
                   return (
                     
                       {this.headerManager.stopPointHeader(sp.object_id)}
@@ -125,17 +147,18 @@ export default class VehicleJourneys extends Component {
 
               
                 
-                  {this.props.vehicleJourneys.map((vj, index) =>
+                  {this.vehicleJourneysList().map((vj, index) =>
                     
                   )}
                 
diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
index 2893422f8..f814c0459 100644
--- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
@@ -23,6 +23,10 @@ export default class EditVehicleJourney extends Component {
     }
   }
 
+  getSelected() {
+    return this.props.vehicleJourney ? [this.props.vehicleJourney] : actions.getSelected(this.props.vehicleJourneys)
+  }
+
   render() {
     if(this.props.status.isFetching == true) {
       return false
@@ -35,10 +39,10 @@ export default class EditVehicleJourney extends Component {
         
- 
           
diff --git a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
index 38ab9f6d3..76d1c3a78 100644
--- a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
+++ b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
@@ -6,17 +6,19 @@ const mapStateToProps = (state) => {
   return {
     editMode: state.editMode,
     vehicleJourneys: state.vehicleJourneys,
+    returnVehicleJourneys: state.returnVehicleJourneys,
     status: state.status,
     filters: state.filters,
-    stopPointsList: state.stopPointsList
+    stopPointsList: state.stopPointsList,
+    returnStopPointsList: state.returnStopPointsList
   }
 }
 
 const mapDispatchToProps = (dispatch) => {
   return {
-    onLoadFirstPage: (filters) =>{
+    onLoadFirstPage: (filters, routeUrl) =>{
       dispatch(actions.fetchingApi())
-      actions.fetchVehicleJourneys(dispatch, undefined, undefined, filters.queryString)
+      actions.fetchVehicleJourneys(dispatch, undefined, undefined, filters.queryString, routeUrl)
     },
     onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => {
       dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled))
diff --git a/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js
index c2eabcc10..a851b6e7d 100644
--- a/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js
@@ -18,6 +18,7 @@ const mapDispatchToProps = (dispatch) => {
       dispatch(actions.closeModal())
     },
     onOpenEditModal: (vj) =>{
+      console.log({vj})
       dispatch(actions.openEditModal(vj))
     },
     onEditVehicleJourney: (data, selectedCompany) =>{
diff --git a/app/javascript/vehicle_journeys/reducers/index.js b/app/javascript/vehicle_journeys/reducers/index.js
index 1963f7c6d..95ac9c7e1 100644
--- a/app/javascript/vehicle_journeys/reducers/index.js
+++ b/app/javascript/vehicle_journeys/reducers/index.js
@@ -1,5 +1,6 @@
 import { combineReducers } from 'redux'
 import vehicleJourneys from './vehicleJourneys'
+import returnVehicleJourneys from './returnVehicleJourneys'
 import pagination from './pagination'
 import modal from './modal'
 import status from './status'
@@ -11,12 +12,14 @@ import custom_fields from './custom_fields'
 
 const vehicleJourneysApp = combineReducers({
   vehicleJourneys,
+  returnVehicleJourneys,
   pagination,
   modal,
   status,
   filters,
   editMode,
   stopPointsList,
+  returnStopPointsList: stopPointsList,
   missions,
   custom_fields
 })
diff --git a/app/javascript/vehicle_journeys/reducers/returnVehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/returnVehicleJourneys.js
new file mode 100644
index 000000000..db3c71d17
--- /dev/null
+++ b/app/javascript/vehicle_journeys/reducers/returnVehicleJourneys.js
@@ -0,0 +1,11 @@
+import _ from 'lodash'
+import actions from '../actions'
+
+export default function returnVehicleJourneys(state = [], action) {
+  switch (action.type) {
+    case 'RECEIVE_RETURN_VEHICLE_JOURNEYS':
+      return [...action.json]
+        default:
+      return state
+  }
+}
diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb
index 55faaf997..a81f4e9ce 100644
--- a/app/models/chouette/journey_pattern.rb
+++ b/app/models/chouette/journey_pattern.rb
@@ -160,12 +160,13 @@ module Chouette
 
     def full_schedule?
       full = true
-      stop_points.inject(nil) do |start, finish|
+      stop_points.order(:position).inject(nil) do |start, finish|
         next finish unless start.present?
         costs = costs_between(start, finish)
         full = false unless costs.present?
         full = false unless costs[:distance] && costs[:distance] > 0
         full = false unless costs[:time] && costs[:time] > 0
+        p "#{start.stop_area_id}-#{finish.stop_area_id}" unless full
         finish
       end
       full
diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim
index ce56f9332..caa8450a0 100644
--- a/app/views/vehicle_journeys/index.html.slim
+++ b/app/views/vehicle_journeys/index.html.slim
@@ -17,6 +17,7 @@
 = javascript_tag do
   | window.route_id = #{params[:route_id]};
   | window.stopPoints = #{(@stop_points_list.to_json).html_safe};
+  | window.returnStopPoints = #{(@return_stop_points_list.to_json).html_safe};
   | window.jpOrigin = #{(@jp_origin.present? ? @jp_origin.attributes.update({full_schedule: @jp_origin.full_schedule?}).to_json  : "null").html_safe};
   | window.jpOriginStopPoints = #{(@jp_origin_stop_points.to_json).html_safe};
   | window.transportMode = #{(@transport_mode.to_json).html_safe};
@@ -30,4 +31,9 @@
   | window.custom_fields = #{(@custom_fields.to_json).html_safe};
   | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};
 
+- if has_feature?(:vehicle_journeys_return_route)
+  = javascript_tag do
+    | window.returnRouteUrl = "#{(@route.opposite_route && url_for([@referential, @route.line, @route.opposite_route, :vehicle_journeys]) || "").html_safe}";
+
+
 = javascript_pack_tag 'vehicle_journeys/index.js'
-- 
cgit v1.2.3
From b91895f897f6a9b401844e373d020c3f458fc130 Mon Sep 17 00:00:00 2001
From: Zog
Date: Thu, 1 Feb 2018 10:37:44 +0100
Subject: :fire: log
---
 app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js | 1 -
 app/models/chouette/journey_pattern.rb                                 | 1 -
 2 files changed, 2 deletions(-)
diff --git a/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js
index a851b6e7d..c2eabcc10 100644
--- a/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/containers/tools/EditVehicleJourney.js
@@ -18,7 +18,6 @@ const mapDispatchToProps = (dispatch) => {
       dispatch(actions.closeModal())
     },
     onOpenEditModal: (vj) =>{
-      console.log({vj})
       dispatch(actions.openEditModal(vj))
     },
     onEditVehicleJourney: (data, selectedCompany) =>{
diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb
index a81f4e9ce..aa9fdb810 100644
--- a/app/models/chouette/journey_pattern.rb
+++ b/app/models/chouette/journey_pattern.rb
@@ -166,7 +166,6 @@ module Chouette
         full = false unless costs.present?
         full = false unless costs[:distance] && costs[:distance] > 0
         full = false unless costs[:time] && costs[:time] > 0
-        p "#{start.stop_area_id}-#{finish.stop_area_id}" unless full
         finish
       end
       full
-- 
cgit v1.2.3
From 98f82c27b08dfc09223ae51650bf0bd2c80b9112 Mon Sep 17 00:00:00 2001
From: Alban Peignier
Date: Thu, 1 Feb 2018 11:00:52 +0100
Subject: Change path in capistrano default_env to make bundle available for
 webpacker compiler. Refs #5803
---
 config/deploy.rb | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/config/deploy.rb b/config/deploy.rb
index d541f2581..1ea1613de 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -7,10 +7,12 @@ set :scm, :git
 set :repository,  "git@github.com:AF83/stif-boiv.git"
 set :deploy_to, "/var/www/stif-boiv"
 set :use_sudo, false
+set :ruby_version, "2.3.0"
 default_run_options[:pty] = true
 set :group_writable, true
-set :bundle_cmd, "/var/lib/gems/2.3.0/bin/bundle"
-set :rake, "#{bundle_cmd} exec rake"
+set :bundle_cmd, "/var/lib/gems/#{ruby_version}/bin/bundle"
+oset :rake, "#{bundle_cmd} exec rake"
+set :default_env, { path: "/var/lib/gems/#{ruby_version}/bin:$PATH" }
 
 set :keep_releases, -> { fetch(:kept_releases, 5) }
 after "deploy:restart", "deploy:cleanup"
@@ -29,7 +31,7 @@ require 'whenever/capistrano'
 #after 'deploy:finalize_update', 'npm:install'
 
 # Whenever
-set :whenever_variables, ->{ "'environment=#{fetch :whenever_environment}&bundle_command=bin/bundle exec&additionnal_path=/var/lib/gems/2.3.0/bin'" } # invoke bin/bundle to use 'correct' ruby environment
+set :whenever_variables, ->{ "'environment=#{fetch :whenever_environment}&bundle_command=bin/bundle exec&additionnal_path=/var/lib/gems/#{ruby_version}/bin'" } # invoke bin/bundle to use 'correct' ruby environment
 
 set :whenever_command, "sudo /usr/local/sbin/whenever-sudo" # use sudo to change www-data crontab
 set :whenever_user, "www-data" # use www-data crontab
-- 
cgit v1.2.3
From 799e9dddd06d60d5e22ad4289c23534c107b40db Mon Sep 17 00:00:00 2001
From: Alban Peignier
Date: Thu, 1 Feb 2018 11:02:46 +0100
Subject: Add env variable to disable ci:jest if needed. Refs #5802
---
 lib/tasks/ci.rake | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake
index 3e73b7a3b..82619d04b 100644
--- a/lib/tasks/ci.rake
+++ b/lib/tasks/ci.rake
@@ -38,7 +38,7 @@ namespace :ci do
   end
 
   task :jest => "ci:assets" do
-    sh "node_modules/.bin/jest"
+    sh "node_modules/.bin/jest" unless ["CHOUETTE_JEST_DISABLED"]
   end
 
   desc "Deploy after CI"
-- 
cgit v1.2.3
From dbdd28e7f4061e84b19e3394eef263e908b23136 Mon Sep 17 00:00:00 2001
From: Alban Peignier
Date: Thu, 1 Feb 2018 11:13:17 +0100
Subject: Fixes typo in deploy.rb. Refs #5803
---
 config/deploy.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/deploy.rb b/config/deploy.rb
index 1ea1613de..6a5d07d79 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -11,7 +11,7 @@ set :ruby_version, "2.3.0"
 default_run_options[:pty] = true
 set :group_writable, true
 set :bundle_cmd, "/var/lib/gems/#{ruby_version}/bin/bundle"
-oset :rake, "#{bundle_cmd} exec rake"
+set :rake, "#{bundle_cmd} exec rake"
 set :default_env, { path: "/var/lib/gems/#{ruby_version}/bin:$PATH" }
 
 set :keep_releases, -> { fetch(:kept_releases, 5) }
-- 
cgit v1.2.3
From 8f2a4ca4de2cac80eea379cba559124ee5dff403 Mon Sep 17 00:00:00 2001
From: Luc Donnet
Date: Thu, 1 Feb 2018 11:31:56 +0100
Subject: Fix yarn installation with no production for ci and force yarn
 install before jest launch du to webpacker bug
---
 lib/tasks/ci.rake | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake
index 82619d04b..13d7b8d73 100644
--- a/lib/tasks/ci.rake
+++ b/lib/tasks/ci.rake
@@ -3,7 +3,7 @@ namespace :ci do
   task :setup do
     cp "config/database/jenkins.yml", "config/database.yml"
     sh "RAILS_ENV=test rake db:drop db:create db:migrate"
-    sh "yarn --production --no-progress install"
+    sh "yarn --no-progress install"
   end
 
   def git_branch
@@ -38,6 +38,7 @@ namespace :ci do
   end
 
   task :jest => "ci:assets" do
+    sh "yarn --no-progress install" # Hack to force install jest after webpack 
     sh "node_modules/.bin/jest" unless ["CHOUETTE_JEST_DISABLED"]
   end
 
-- 
cgit v1.2.3
From 689e76124987cbae6ba63ffa082b6820dd9e76a5 Mon Sep 17 00:00:00 2001
From: Alban Peignier
Date: Thu, 1 Feb 2018 11:35:32 +0100
Subject: Use default_environment (for capistrano 2). Refs #5803
---
 config/deploy.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/deploy.rb b/config/deploy.rb
index 6a5d07d79..a67fbcd13 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -12,7 +12,7 @@ default_run_options[:pty] = true
 set :group_writable, true
 set :bundle_cmd, "/var/lib/gems/#{ruby_version}/bin/bundle"
 set :rake, "#{bundle_cmd} exec rake"
-set :default_env, { path: "/var/lib/gems/#{ruby_version}/bin:$PATH" }
+set :default_environment, { path: "/var/lib/gems/#{ruby_version}/bin:$PATH" }
 
 set :keep_releases, -> { fetch(:kept_releases, 5) }
 after "deploy:restart", "deploy:cleanup"
-- 
cgit v1.2.3
From 96fafa49a73a3ea4c25aa0d90bc87ab958831f33 Mon Sep 17 00:00:00 2001
From: Alban Peignier
Date: Thu, 1 Feb 2018 13:11:55 +0100
Subject: Use upcase PATH variable in :default_environment. Refs #5803
---
 config/deploy.rb | 4 ++++
 1 file changed, 4 insertions(+)
diff --git a/config/deploy.rb b/config/deploy.rb
index a67fbcd13..7e385cebc 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -14,6 +14,10 @@ set :bundle_cmd, "/var/lib/gems/#{ruby_version}/bin/bundle"
 set :rake, "#{bundle_cmd} exec rake"
 set :default_environment, { path: "/var/lib/gems/#{ruby_version}/bin:$PATH" }
 
+set :default_environment, {
+  'PATH' => "/var/lib/gems/#{ruby_version}/bin:$PATH"
+}
+
 set :keep_releases, -> { fetch(:kept_releases, 5) }
 after "deploy:restart", "deploy:cleanup"
 
-- 
cgit v1.2.3
From c344768e619664d6876ddebca8c4ef474d11a402 Mon Sep 17 00:00:00 2001
From: Alban Peignier
Date: Thu, 1 Feb 2018 13:12:24 +0100
Subject: Remove first :default_environment. Refs #5803
---
 config/deploy.rb | 2 --
 1 file changed, 2 deletions(-)
diff --git a/config/deploy.rb b/config/deploy.rb
index 7e385cebc..9be023adc 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -12,8 +12,6 @@ default_run_options[:pty] = true
 set :group_writable, true
 set :bundle_cmd, "/var/lib/gems/#{ruby_version}/bin/bundle"
 set :rake, "#{bundle_cmd} exec rake"
-set :default_environment, { path: "/var/lib/gems/#{ruby_version}/bin:$PATH" }
-
 set :default_environment, {
   'PATH' => "/var/lib/gems/#{ruby_version}/bin:$PATH"
 }
-- 
cgit v1.2.3
From 972e988e0f52a6ee69bf97c112be28e6f852be00 Mon Sep 17 00:00:00 2001
From: Alban Peignier
Date: Thu, 1 Feb 2018 13:39:00 +0100
Subject: Revert webpacker to 3.0.2. Refs #5807
---
 Gemfile      | 3 ++-
 Gemfile.lock | 6 +++---
 2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/Gemfile b/Gemfile
index c378820b3..e1943e659 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,4 @@
+# coding: utf-8
 source 'https://rubygems.org'
 
 # Use https for github
@@ -15,7 +16,7 @@ gem 'uglifier', '~> 2.7.2'
 gem 'coffee-rails', '~> 4.0.0'
 
 # Webpacker
-gem 'webpacker', '~> 3.0'
+gem 'webpacker', '3.0.2'
 
 # Use jquery as the JavaScript library
 gem 'jquery-rails', '~> 3.1.4' # Update to v4 for Rails 4.2
diff --git a/Gemfile.lock b/Gemfile.lock
index ba86a911f..3a2314857 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -562,7 +562,7 @@ GEM
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
       hashdiff
-    webpacker (3.2.1)
+    webpacker (3.0.2)
       activesupport (>= 4.2)
       rack-proxy (>= 0.6.1)
       railties (>= 4.2)
@@ -698,10 +698,10 @@ DEPENDENCIES
   transpec
   uglifier (~> 2.7.2)
   webmock
-  webpacker (~> 3.0)
+  webpacker (= 3.0.2)
   whenever!
   will_paginate
   will_paginate-bootstrap
 
 BUNDLED WITH
-   1.16.0
+   1.16.1
-- 
cgit v1.2.3
From a66713dcfd4f487184dc39695410ed75adfb1e14 Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 16:38:50 +0100
Subject: Refs #5762; Make coordinates optional on stop areas
---
 app/models/chouette/stop_area.rb     | 4 +---
 app/views/stop_areas/_form.html.slim | 2 +-
 2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index d7d5c2eb2..c6feaf940 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -106,9 +106,7 @@ module Chouette
       end
     end
 
-    def local_id
-      id.to_s
-    end
+    alias_method :local_id, :user_objectid
 
     def children_in_depth
       return [] if self.children.empty?
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index aa156f7bd..1bc3e77ef 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -23,7 +23,7 @@
           - unless @stop_area.projection.blank? or @stop_area.projection_type_label.empty?
             = f.input :projection_xy, :label => t("activerecord.attributes.stop_area.projection_xy", :projection => @referential.projection_type_label), :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.projection_xy")}
 
-          = f.input :coordinates, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.coordinates")}, required: true
+          = f.input :coordinates, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.coordinates")}
           = f.input :street_name
           = f.input :zip_code, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.zip_code")}
           = f.input :city_name, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.city_name")}
-- 
cgit v1.2.3
From d9adc803f075c8136871e86b3ddbdd01ac217967 Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 16:46:08 +0100
Subject: Refs #5762; Disable default position for stops
---
 app/controllers/stop_areas_controller.rb | 2 --
 1 file changed, 2 deletions(-)
diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb
index 8d424b8d1..076de922c 100644
--- a/app/controllers/stop_areas_controller.rb
+++ b/app/controllers/stop_areas_controller.rb
@@ -95,7 +95,6 @@ class StopAreasController < ChouetteController
   def edit
     authorize stop_area
     edit! do
-      stop_area.position ||= stop_area.default_position
       map.editable = true
     end
   end
@@ -107,7 +106,6 @@ class StopAreasController < ChouetteController
 
   def update
     authorize stop_area
-    stop_area.position ||= stop_area.default_position
     map.editable = true
 
     update!
-- 
cgit v1.2.3
From ae336476f64254727caf2e447f444fd17f0513f1 Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 16:56:06 +0100
Subject: Refs #5762; Update JS to accomodate stops without location
---
 app/javascript/helpers/routes_map.coffee | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/app/javascript/helpers/routes_map.coffee b/app/javascript/helpers/routes_map.coffee
index 85def1390..6834406fc 100644
--- a/app/javascript/helpers/routes_map.coffee
+++ b/app/javascript/helpers/routes_map.coffee
@@ -30,20 +30,24 @@ class RoutesMap
         geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stops[stops.length - 1].longitude), parseFloat(stops[stops.length - 1].latitude)]))
       })
     ]
+
+    prevStop = null
     stops.forEach (stop, i) =>
-      if i < stops.length - 1
-        geoColLns.push new ol.Feature
-          geometry: new ol.geom.LineString([
-            ol.proj.fromLonLat([parseFloat(stops[i].longitude), parseFloat(stops[i].latitude)]),
-            ol.proj.fromLonLat([parseFloat(stops[i + 1].longitude), parseFloat(stops[i + 1].latitude)])
-          ])
+      if stop.longitude && stop.latitude
+        if prevStop
+          geoColLns.push new ol.Feature
+            geometry: new ol.geom.LineString([
+              ol.proj.fromLonLat([parseFloat(prevStop.longitude), parseFloat(prevStop.latitude)]),
+              ol.proj.fromLonLat([parseFloat(stop.longitude), parseFloat(stop.latitude)])
+            ])
+        prevStop = stop
 
-      geoColPts.push(new ol.Feature({
-        geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stop.longitude), parseFloat(stop.latitude)]))
-      }))
-      unless @seenStopIds.indexOf(stop.stoparea_id) > 0
-        @area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)]
-        @seenStopIds.push stop.stoparea_id
+        geoColPts.push(new ol.Feature({
+          geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stop.longitude), parseFloat(stop.latitude)]))
+        }))
+        unless @seenStopIds.indexOf(stop.stoparea_id) > 0
+          @area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)]
+          @seenStopIds.push stop.stoparea_id
 
     vectorPtsLayer = new ol.layer.Vector({
       source: new ol.source.Vector({
-- 
cgit v1.2.3
From 3279d4bcee982c13f260aa589ab190133f930af0 Mon Sep 17 00:00:00 2001
From: Zog
Date: Thu, 1 Feb 2018 13:55:21 +0100
Subject: Fix css
---
 app/assets/stylesheets/OpenLayers/custom.sass | 1 -
 1 file changed, 1 deletion(-)
diff --git a/app/assets/stylesheets/OpenLayers/custom.sass b/app/assets/stylesheets/OpenLayers/custom.sass
index 5a3612f99..fa874d924 100644
--- a/app/assets/stylesheets/OpenLayers/custom.sass
+++ b/app/assets/stylesheets/OpenLayers/custom.sass
@@ -18,7 +18,6 @@
       cursor: pointer
       color: $blue
       white-space: nowrap
-      max-width: 33%
 
       &:hover
         background: $orange
-- 
cgit v1.2.3
From 82de76f0efce7558a3fe240edf4e2293a8788ee1 Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 16:25:08 +0100
Subject: Refs #5758 @1h; Add localized names to StopAreas
---
 Gemfile                                                        | 1 +
 Gemfile.lock                                                   | 3 +++
 app/assets/stylesheets/application.sass                        | 2 ++
 app/controllers/stop_areas_controller.rb                       | 1 +
 app/helpers/stop_areas_helper.rb                               | 6 ++++--
 app/models/chouette/stop_area.rb                               | 7 +++++++
 app/views/stop_areas/_form.html.slim                           | 6 +++++-
 app/views/stop_areas/show.html.slim                            | 6 ++++--
 config/initializers/countries.rb                               | 3 +++
 db/migrate/20180129141656_add_localized_names_to_stop_areas.rb | 5 +++++
 db/schema.rb                                                   | 1 +
 11 files changed, 36 insertions(+), 5 deletions(-)
 create mode 100644 config/initializers/countries.rb
 create mode 100644 db/migrate/20180129141656_add_localized_names_to_stop_areas.rb
diff --git a/Gemfile b/Gemfile
index e1943e659..bb1a42df0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -102,6 +102,7 @@ gem 'font-awesome-sass', '~> 4.7'
 gem 'will_paginate-bootstrap'
 gem 'gretel'
 gem 'country_select'
+gem 'flag-icons-rails'
 
 # Format Output
 gem 'json'
diff --git a/Gemfile.lock b/Gemfile.lock
index 3a2314857..ffa63b535 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -236,6 +236,8 @@ GEM
     ffi (1.9.18)
     ffi-geos (1.2.0)
       ffi (>= 1.0.0)
+    flag-icons-rails (2.5.0)
+      sass (~> 3.2)
     font-awesome-sass (4.7.0)
       sass (>= 3.2)
     formtastic (3.1.5)
@@ -625,6 +627,7 @@ DEPENDENCIES
   faraday (~> 0.9.1)
   faraday_middleware (~> 0.9.1)
   ffaker (~> 2.1.0)
+  flag-icons-rails
   font-awesome-sass (~> 4.7)
   formtastic (= 3.1.5)
   georuby (= 2.3.0)
diff --git a/app/assets/stylesheets/application.sass b/app/assets/stylesheets/application.sass
index 50f8b64cb..3f8467efe 100644
--- a/app/assets/stylesheets/application.sass
+++ b/app/assets/stylesheets/application.sass
@@ -19,3 +19,5 @@
 @import 'modules/vj_collection'
 @import 'modules/timetables'
 @import 'modules/import_messages'
+
+@import 'flag-icon'
diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb
index 076de922c..41a1a8c6d 100644
--- a/app/controllers/stop_areas_controller.rb
+++ b/app/controllers/stop_areas_controller.rb
@@ -202,6 +202,7 @@ class StopAreasController < ChouetteController
       :waiting_time,
       :zip_code,
       :kind,
+      localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS
     )
   end
 
diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb
index 3e04fac7d..05ae042f5 100644
--- a/app/helpers/stop_areas_helper.rb
+++ b/app/helpers/stop_areas_helper.rb
@@ -11,6 +11,10 @@ module StopAreasHelper
     ( "
" + " " + name + " " + localization + "").html_safe
   end
 
+  def label_for_country country, txt=nil
+    "#{txt} ".html_safe
+  end
+
   def genealogical_title
     return t("stop_areas.genealogical.genealogical_routing") if @stop_area.stop_area_type == 'itl'
     t("stop_areas.genealogical.genealogical")
@@ -33,12 +37,10 @@ module StopAreasHelper
     @stop_area.stop_area_type == 'stop_place' || @stop_area.stop_area_type == 'commercial_stop_point'
   end
 
-
   def pair_key(access_link)
     "#{access_link.access_point.id}-#{access_link.stop_area.id}"
   end
 
-
   def geo_data(sa, sar)
     if sa.long_lat_type.nil?
       content_tag :span, '-'
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index c6feaf940..bb8747faa 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -12,6 +12,8 @@ module Chouette
     enumerize :area_type, in: Chouette::AreaType::ALL
     enumerize :kind, in: %i(commercial non_commercial)
 
+    AVAILABLE_LOCALIZATIONS = %i(gb nl de fr it es)
+
     with_options dependent: :destroy do |assoc|
       assoc.has_many :stop_points
       assoc.has_many :access_points
@@ -50,6 +52,11 @@ module Chouette
       :nearest_topic_name, :comment, :long_lat_type, :zip_code, :city_name, :url, :time_zone]
     end
 
+    def localized_names
+      val = read_attribute(:localized_names) || {}
+      Hash[*AVAILABLE_LOCALIZATIONS.map{|k| [k, val[k.to_s]]}.flatten]
+    end
+
     def parent_area_type_must_be_greater
       return unless self.parent
 
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index 1bc3e77ef..c2b9a7f4f 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -6,8 +6,12 @@
         /= @map.to_html
         = f.input :id, as: :hidden
         = f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.name")}
+        .form-group
+          .col-sm-3.col-xs-5
+          .col-sm-9.col-xs-7
+            - f.object.localized_names.each do |k, v|
+              .col-md-6= f.input "localized_names[#{k}]", input_html: {value: v}, label: label_for_country(k)
         = f.input :kind, as: :radio_buttons, checked: @stop_area.kind, :input_html => {:disabled => !@stop_area.new_record?}, :include_blank => false, item_wrapper_class: 'radio-inline', wrapper: :horizontal_form, disabled: !@stop_area.new_record?
-
         .slave data-master="[name='stop_area[kind]']" data-value="commercial"
           = f.input :parent_id, as: :select, :collection => [f.object.parent_id], input_html: { data: { select2_ajax: 'true', url: autocomplete_stop_area_referential_stop_areas_path(@stop_area_referential), initvalue: {id: f.object.parent_id, text: f.object.parent.try(:full_name)}}}
         - %i(non_commercial commercial).each do |kind|
diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim
index b0896c1e0..67f309cef 100644
--- a/app/views/stop_areas/show.html.slim
+++ b/app/views/stop_areas/show.html.slim
@@ -7,10 +7,12 @@
     .row
       .col-lg-6.col-md-6.col-sm-12.col-xs-12
         - attributes = { t('id_reflex') => @stop_area.get_objectid.short_id }
+        - @stop_area.localized_names.each do |k, v|
+          - attributes.merge!({label_for_country(k, @stop_area.human_attribute_name(:name)) => v }) if v.present?
         - attributes.merge!({ @stop_area.human_attribute_name(:parent) => @stop_area.parent ? link_to(@stop_area.parent.name, stop_area_referential_stop_area_path(@stop_area_referential, @stop_area.parent)) : "-" }) if @stop_area.commercial?
         - attributes.merge!({ @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,
-            })
+            @stop_area.human_attribute_name(:registration_number) => @stop_area.registration_number
+          })
         - 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,
diff --git a/config/initializers/countries.rb b/config/initializers/countries.rb
new file mode 100644
index 000000000..7f2b5c9db
--- /dev/null
+++ b/config/initializers/countries.rb
@@ -0,0 +1,3 @@
+ISO3166.configure  do |config|
+  config.locales = (I18n.available_locales + Chouette::StopArea::AVAILABLE_LOCALIZATIONS).uniq
+end
diff --git a/db/migrate/20180129141656_add_localized_names_to_stop_areas.rb b/db/migrate/20180129141656_add_localized_names_to_stop_areas.rb
new file mode 100644
index 000000000..320ce739a
--- /dev/null
+++ b/db/migrate/20180129141656_add_localized_names_to_stop_areas.rb
@@ -0,0 +1,5 @@
+class AddLocalizedNamesToStopAreas < ActiveRecord::Migration
+  def change
+    add_column :stop_areas, :localized_names, :jsonb
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5356c9909..d54a40feb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -789,6 +789,7 @@ ActiveRecord::Schema.define(version: 20180124124215) do
     t.string   "stif_type"
     t.integer  "waiting_time"
     t.string   "kind"
+    t.jsonb    "localized_names"
   end
 
   add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree
-- 
cgit v1.2.3
From 65c43ed46adcd4c08d9ae3c1bb25c9cf8d7a0f62 Mon Sep 17 00:00:00 2001
From: Zog
Date: Mon, 29 Jan 2018 20:48:04 +0100
Subject: Refs #5758; Add corresponding feature `stop_area_localized_names`
---
 app/views/stop_areas/_form.html.slim | 7 +++++++
 app/views/stop_areas/show.html.slim  | 7 +++++--
 2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index c2b9a7f4f..67af39dc6 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -6,6 +6,13 @@
         /= @map.to_html
         = f.input :id, as: :hidden
         = f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.name")}
+        - if has_feature?(:stop_area_localized_names)
+          .form-group
+            .col-sm-3.col-xs-5
+            .col-sm-9.col-xs-7
+              - f.object.localized_names.each do |k, v|
+                .col-md-6= f.input "localized_names[#{k}]", input_html: {value: v}, label: label_for_country(k)
+                
         .form-group
           .col-sm-3.col-xs-5
           .col-sm-9.col-xs-7
diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim
index 67f309cef..300d714e8 100644
--- a/app/views/stop_areas/show.html.slim
+++ b/app/views/stop_areas/show.html.slim
@@ -7,12 +7,15 @@
     .row
       .col-lg-6.col-md-6.col-sm-12.col-xs-12
         - attributes = { t('id_reflex') => @stop_area.get_objectid.short_id }
-        - @stop_area.localized_names.each do |k, v|
-          - attributes.merge!({label_for_country(k, @stop_area.human_attribute_name(:name)) => v }) if v.present?
+
+        - if has_feature?(:stop_area_localized_names)
+          - @stop_area.localized_names.each do |k, v|
+            - attributes.merge!({label_for_country(k, @stop_area.human_attribute_name(:name)) => v }) if v.present?
         - attributes.merge!({ @stop_area.human_attribute_name(:parent) => @stop_area.parent ? link_to(@stop_area.parent.name, stop_area_referential_stop_area_path(@stop_area_referential, @stop_area.parent)) : "-" }) if @stop_area.commercial?
         - attributes.merge!({ @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
           })
+
         - 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,
-- 
cgit v1.2.3
From 720d11cb7d2475277e7b79595d857f5ffef5bee1 Mon Sep 17 00:00:00 2001
From: Teddy Wing
Date: Thu, 1 Feb 2018 15:20:43 +0100
Subject: ReferentialVehicleJourneys#index: Fix `nil` error on `stop_area_ids`
This wasn't checking that `params[:q]` was present before trying to
access `[:stop_area_ids]`, and thus caused an error on the page when no
filters were applied.
Refs #5809
---
 app/controllers/referential_vehicle_journeys_controller.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb
index a199157dd..2ce28a5cc 100644
--- a/app/controllers/referential_vehicle_journeys_controller.rb
+++ b/app/controllers/referential_vehicle_journeys_controller.rb
@@ -23,7 +23,7 @@ class ReferentialVehicleJourneysController < ChouetteController
     @vehicle_journeys ||= @q.result.order(:published_journey_name).includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: params[:per_page] || 10
     @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct
     @all_stop_areas = Chouette::StopArea.where("id IN (#{@referential.vehicle_journeys.joins(:stop_areas).select("stop_areas.id").to_sql})").distinct
-    stop_area_ids = params[:q][:stop_area_ids].select(&:present?)
+    stop_area_ids = params[:q].try(:[], :stop_area_ids).try(:select, &:present?)
     @filters_stop_areas = Chouette::StopArea.find(stop_area_ids) if stop_area_ids.present? && stop_area_ids.size <= 2
   end
 
-- 
cgit v1.2.3
From ff3d2d5caf08f4b09d45b4b105d9180d747e7bda Mon Sep 17 00:00:00 2001
From: Zog
Date: Thu, 1 Feb 2018 15:27:33 +0100
Subject: Fix broken TimeTableDecorator
---
 app/decorators/time_table_decorator.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/decorators/time_table_decorator.rb b/app/decorators/time_table_decorator.rb
index 9a56fc2ee..95b1fd959 100644
--- a/app/decorators/time_table_decorator.rb
+++ b/app/decorators/time_table_decorator.rb
@@ -11,7 +11,7 @@ class TimeTableDecorator < AF83::Decorator
     end
 
     instance_decorator.edit_action_link do |l|
-      l.href { [context[:referential], object] }
+      l.href { [:edit, context[:referential], object] }
     end
 
     instance_decorator.action_link if: ->{ object.calendar }, secondary: true do |l|
-- 
cgit v1.2.3
From 50080adc6df79c8bbde909118ddc1b96950663a3 Mon Sep 17 00:00:00 2001
From: Teddy Wing
Date: Wed, 31 Jan 2018 19:40:59 +0100
Subject: ReferentialVehicleJourneys#index: Add 'line' filter
Filter vehicle journeys by their associated route's line.
The `with_line_id` scope is modeled on `with_stop_area_ids`.
The HTML filter is modeled on the line filter from
`app/views/workbenches/_filters.html.slim`.
Refs #5576
---
 .../referential_vehicle_journeys_controller.rb          |  1 +
 app/models/chouette/vehicle_journey.rb                  | 11 ++++++++++-
 .../referential_vehicle_journeys/_filters.html.slim     | 17 +++++++++++++++++
 3 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb
index 2ce28a5cc..935b52d93 100644
--- a/app/controllers/referential_vehicle_journeys_controller.rb
+++ b/app/controllers/referential_vehicle_journeys_controller.rb
@@ -16,6 +16,7 @@ class ReferentialVehicleJourneysController < ChouetteController
 
   def collection
     @q ||= end_of_association_chain
+    @q = @q.with_line_id(params[:q][:line_id]) if params[:q] && params[:q][:line_id]
     @q = @q.with_stop_area_ids(params[:q][:stop_area_ids]) if params[:q] && params[:q][:stop_area_ids]
     @q = ransack_period_range(scope: @q, error_message:  t('vehicle_journeys.errors.purchase_window'), query: :in_purchase_window, prefix: :purchase_window)
     @q = ransack_period_range(scope: @q, error_message:  t('vehicle_journeys.errors.time_table'), query: :with_matching_timetable, prefix: :time_table)
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index 1756a7098..918ca07aa 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -52,6 +52,10 @@ module Chouette
       end
     }
 
+    scope :with_line_id, ->(id) {
+      joins(:route).where(routes: { line_id: id })
+    }
+
     scope :in_purchase_window, ->(range){
       purchase_windows = Chouette::PurchaseWindow.overlap_dates(range)
       sql = purchase_windows.joins(:vehicle_journeys).select('vehicle_journeys.id').uniq.to_sql
@@ -59,8 +63,9 @@ module Chouette
     }
 
     # We need this for the ransack object in the filters
-    ransacker :stop_area_ids
+    ransacker :line_id
     ransacker :purchase_window_date_gt
+    ransacker :stop_area_ids
 
     # returns VehicleJourneys with at least 1 day in their time_tables
     # included in the given range
@@ -375,5 +380,9 @@ module Chouette
         ')
         .where('"time_tables_vehicle_journeys"."vehicle_journey_id" IS NULL')
     end
+
+    def self.lines
+      Chouette::Line.joins(routes: :vehicle_journeys).distinct
+    end
   end
 end
diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
index bfb5b77dd..838150d2c 100644
--- a/app/views/referential_vehicle_journeys/_filters.html.slim
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -15,6 +15,23 @@
         = f.input :company_id_eq_any, collection: @all_companies.select(:id, :name).order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("" + l.name + "").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
       - else
         = f.input :company_id_eq_any, collection: [[I18n.t('companies.search_no_results_for_filter'), nil]], as: :check_boxes, label: false, disabled: true, required: false, wrapper_html: { class: 'checkbox_list disabled'}
+
+    .form-group.togglable
+      = f.label Chouette::Line.model_name.human,
+          required: false,
+          class: 'control-label'
+      .inputs.form-inline.checkbox_list
+        = f.input :line_id,
+            as: :select,
+            collection: @vehicle_journeys.lines,
+            input_html: { \
+              'data-select2ed': 'true',
+              'data-select2ed-placeholder': t('referentials.filters.line') \
+            },
+            label: false,
+            label_method: :display_name,
+            wrapper_html: { class: 'select2ed' }
+
     .form-group.togglable.name-filter
       = f.label Chouette::VehicleJourney.human_attribute_name(:published_journey_name), required: false, class: 'control-label'
       .inputs.form-inline.checkbox_list
-- 
cgit v1.2.3
From 79d9e1bf24a0786bedec7966b5d0c500f91a1727 Mon Sep 17 00:00:00 2001
From: Teddy Wing
Date: Thu, 1 Feb 2018 10:57:44 +0100
Subject: ReferentialVehicleJourneys#index: Add padding to line filter
Add some padding inside the drop-down so that the filter select box
doesn't run up against the sides of the drop-down.
Refs #5576
---
 app/views/referential_vehicle_journeys/_filters.html.slim | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
index 838150d2c..077c40de7 100644
--- a/app/views/referential_vehicle_journeys/_filters.html.slim
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -20,7 +20,7 @@
       = f.label Chouette::Line.model_name.human,
           required: false,
           class: 'control-label'
-      .inputs.form-inline.checkbox_list
+      .form-inline.filter_menu
         = f.input :line_id,
             as: :select,
             collection: @vehicle_journeys.lines,
@@ -30,7 +30,7 @@
             },
             label: false,
             label_method: :display_name,
-            wrapper_html: { class: 'select2ed' }
+            wrapper_html: { class: 'filter_menu-item select2ed' }
 
     .form-group.togglable.name-filter
       = f.label Chouette::VehicleJourney.human_attribute_name(:published_journey_name), required: false, class: 'control-label'
-- 
cgit v1.2.3
From c99847eb430a6f98c019f71f4f5e05831f3e054d Mon Sep 17 00:00:00 2001
From: Teddy Wing
Date: Thu, 1 Feb 2018 11:52:48 +0100
Subject: ReferentialVehicleJourneys#index: Pre-fill line select filter
Pre-fill this box with the value from `params[:q]` so users can see
their selected line after filtering.
For some reason Select2 wants to keep it selected even after clicking
"Effacer". Not sure what that's about, but the HTML `  
         {this.props.value.vehicle_journey_at_stops.map((vj, i) =>
           
diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
index f814c0459..f6a0e3c61 100644
--- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
@@ -7,6 +7,7 @@ import CustomFieldsInputs from './CustomFieldsInputs'
 export default class EditVehicleJourney extends Component {
   constructor(props) {
     super(props)
+    this.updateValue = this.updateValue.bind(this)
   }
 
   handleSubmit() {
@@ -23,8 +24,14 @@ export default class EditVehicleJourney extends Component {
     }
   }
 
-  getSelected() {
-    return this.props.vehicleJourney ? [this.props.vehicleJourney] : actions.getSelected(this.props.vehicleJourneys)
+  updateValue(attribute, e) {
+    this.props.modal.modalProps.vehicleJourney[attribute] = e.target.value
+    actions.resetValidation(e.currentTarget)
+    this.forceUpdate()
+  }
+
+  editMode() {
+    return !this.props.modal.modalProps.info && this.props.editMode
   }
 
   render() {
@@ -39,10 +46,10 @@ export default class EditVehicleJourney extends Component {
         
- 
           
@@ -67,9 +74,9 @@ export default class EditVehicleJourney extends Component {
                                   type='text'
                                   ref='published_journey_name'
                                   className='form-control'
-                                  disabled={!this.props.editMode}
-                                  defaultValue={this.props.modal.modalProps.vehicleJourney.published_journey_name}
-                                  onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+                                  disabled={!this.editMode()}
+                                  value={this.props.modal.modalProps.vehicleJourney.published_journey_name}
+                                  onChange={(e) => this.updateValue('published_journey_name', e)}
                                   />
                               
 
                             
@@ -94,9 +101,9 @@ export default class EditVehicleJourney extends Component {
                                 type='text'
                                 ref='published_journey_identifier'
                                 className='form-control'
-                                disabled={!this.props.editMode}
-                                defaultValue={this.props.modal.modalProps.vehicleJourney.published_journey_identifier}
-                                onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+                                disabled={!this.editMode()}
+                                value={this.props.modal.modalProps.vehicleJourney.published_journey_identifier}
+                                onChange={(e) => this.updateValue('published_journey_identifier', e)}
                               />
                             
 
                           
@@ -105,7 +112,7 @@ export default class EditVehicleJourney extends Component {
                               
                                this.props.onSelect2Company(e)}
                                 onUnselect2Company = {() => this.props.onUnselect2Company()}
@@ -152,13 +159,13 @@ export default class EditVehicleJourney extends Component {
                            this.custom_fields[code]["value"] = value}
-                            disabled={!this.props.editMode}
+                            disabled={!this.editMode()}
                           />
                          
                        
 
                       {
-                        this.props.editMode &&
+                        this.editMode() &&