diff options
Diffstat (limited to 'app')
137 files changed, 1548 insertions, 554 deletions
diff --git a/app/assets/javascripts/smart_date.coffee b/app/assets/javascripts/smart_date.coffee index 48aa1c2f9..9c8b44207 100644 --- a/app/assets/javascripts/smart_date.coffee +++ b/app/assets/javascripts/smart_date.coffee @@ -14,11 +14,16 @@ window.isLeapYear = (year) -> window.smartCorrectDate = -> allSelectors = $(@).parent().children('select') - allVals = allSelectors.map (index, sel) -> - parseInt($(sel).val()) + + yearSelect = allSelectors.filter("[name$='(1i)]']") + monthSelect = allSelectors.filter("[name$='(2i)]']") + daySelect = allSelectors.filter("[name$='(3i)]']") + # We expect [day, month, year], so french + allVals = [daySelect, monthSelect, yearSelect].map (sel, index) -> + parseInt(sel.val()) + correctedDay = correctDay allVals - daySelector = allSelectors.first() - $(daySelector).val(correctedDay) + daySelect.val(correctedDay) $ -> $(document).on 'change', '.smart_date select', smartCorrectDate diff --git a/app/assets/stylesheets/components/_compliance_control_blocks.sass b/app/assets/stylesheets/components/_compliance_control_blocks.sass new file mode 100644 index 000000000..46880075c --- /dev/null +++ b/app/assets/stylesheets/components/_compliance_control_blocks.sass @@ -0,0 +1,3 @@ +#compliance_control_block_form + .condition-attributes-errors + margin-bottom: 20px diff --git a/app/controllers/api/v1/netex_imports_controller.rb b/app/controllers/api/v1/netex_imports_controller.rb index 186ddc35c..2c9caf8fb 100644 --- a/app/controllers/api/v1/netex_imports_controller.rb +++ b/app/controllers/api/v1/netex_imports_controller.rb @@ -31,8 +31,7 @@ module Api def create_netex_import attributes = netex_import_params.merge creator: "Webservice" @netex_import = Import::Netex.new attributes - @netex_import.save! - @netex_import.create_referential! + @netex_import.create_with_referential! rescue ActiveRecord::RecordInvalid render json: {errors: @netex_import.errors}, status: 406 finish_action! diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8b66e6097..7f071a6a4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base # Load helpers in rails engine helper LanguageEngine::Engine.helpers + layout :layout_by_resource def set_locale # I18n.locale = session[:language] || I18n.default_locale @@ -56,4 +57,15 @@ class ApplicationController < ActionController::Base def after_sign_out_path_for(resource_or_scope) new_user_session_path end + + private + + def layout_by_resource + if devise_controller? + "devise" + else + "application" + end + end + end diff --git a/app/controllers/clean_ups_controller.rb b/app/controllers/clean_ups_controller.rb index ec28aa0fc..c25df1a00 100644 --- a/app/controllers/clean_ups_controller.rb +++ b/app/controllers/clean_ups_controller.rb @@ -4,12 +4,12 @@ class CleanUpsController < ChouetteController belongs_to :referential def create - clean_up = CleanUp.new(clean_up_params) - clean_up.referential = @referential - if clean_up.valid? - clean_up.save + @clean_up = CleanUp.new(clean_up_params) + @clean_up.referential = @referential + if @clean_up.valid? + @clean_up.save else - flash[:alert] = clean_up.errors.full_messages.join("<br/>") + flash[:alert] = @clean_up.errors.full_messages.join("<br/>") end redirect_to referential_path(@referential) end diff --git a/app/controllers/companies_controller.rb b/app/controllers/companies_controller.rb index a09cab783..2c32ed3a5 100644 --- a/app/controllers/companies_controller.rb +++ b/app/controllers/companies_controller.rb @@ -37,6 +37,7 @@ class CompaniesController < ChouetteController protected + def collection scope = line_referential.companies @q = scope.search(params[:q]) diff --git a/app/controllers/compliance_control_blocks_controller.rb b/app/controllers/compliance_control_blocks_controller.rb index 1173a548a..0851e2800 100644 --- a/app/controllers/compliance_control_blocks_controller.rb +++ b/app/controllers/compliance_control_blocks_controller.rb @@ -4,14 +4,6 @@ class ComplianceControlBlocksController < ChouetteController belongs_to :compliance_control_set actions :all, :except => [:show, :index] - after_action :display_errors, only: [:create, :update] - - def display_errors - unless @compliance_control_block.errors[:condition_attributes].empty? - flash[:error] = @compliance_control_block.errors[:condition_attributes].join(', ') - end - end - private def compliance_control_block_params diff --git a/app/controllers/concerns/metadata_controller_support.rb b/app/controllers/concerns/metadata_controller_support.rb index db83e79ae..4dcbfe5d0 100644 --- a/app/controllers/concerns/metadata_controller_support.rb +++ b/app/controllers/concerns/metadata_controller_support.rb @@ -20,7 +20,7 @@ module MetadataControllerSupport def set_modifier_metadata _resource = @resources || [resource] _resource.flatten.each do |r| - r.try :set_metadata!, :modifier_username, user_for_metadata + r.try(:set_metadata!, :modifier_username, user_for_metadata) if r.valid? end end end diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb index db92d48f3..c1a307464 100644 --- a/app/controllers/journey_patterns_collections_controller.rb +++ b/app/controllers/journey_patterns_collections_controller.rb @@ -25,6 +25,8 @@ class JourneyPatternsCollectionsController < ChouetteController @q = @q.includes(:stop_points) @ppage = 10 @journey_patterns ||= @q.paginate(page: params[:page], per_page: @ppage).order(:name) + @custom_fields = Chouette::JourneyPattern.custom_fields_definitions(referential.workgroup) + respond_to do |format| format.json do @journey_patterns = @journey_patterns.includes(stop_points: {stop_area: :stop_area_referential}) diff --git a/app/controllers/line_referentials_controller.rb b/app/controllers/line_referentials_controller.rb index 03dab3f8f..e661fbb04 100644 --- a/app/controllers/line_referentials_controller.rb +++ b/app/controllers/line_referentials_controller.rb @@ -2,6 +2,12 @@ class LineReferentialsController < ChouetteController defaults :resource_class => LineReferential + def show + show! do + @line_referential = LineReferentialDecorator.decorate(@line_referential) + end + end + def sync authorize resource, :synchronize? @sync = resource.line_referential_syncs.build diff --git a/app/controllers/lines_controller.rb b/app/controllers/lines_controller.rb index ae8c9ed0c..cd8091252 100644 --- a/app/controllers/lines_controller.rb +++ b/app/controllers/lines_controller.rb @@ -122,7 +122,7 @@ class LinesController < ChouetteController end def line_params - params.require(:line).permit( + out = params.require(:line).permit( :transport_mode, :network_id, :company_id, @@ -148,6 +148,8 @@ class LinesController < ChouetteController :secondary_company_ids => [], footnotes_attributes: [:code, :label, :_destroy, :id] ) + out[:secondary_company_ids] = (out[:secondary_company_ids] || []).select(&:present?) + out end # Fake ransack filter diff --git a/app/controllers/merges_controller.rb b/app/controllers/merges_controller.rb index 1ce64ed58..663b6e750 100644 --- a/app/controllers/merges_controller.rb +++ b/app/controllers/merges_controller.rb @@ -1,5 +1,5 @@ class MergesController < ChouetteController - # include PolicyChecker + include PolicyChecker defaults resource_class: Merge belongs_to :workbench diff --git a/app/controllers/referential_companies_controller.rb b/app/controllers/referential_companies_controller.rb index 200e56a89..4f73c4268 100644 --- a/app/controllers/referential_companies_controller.rb +++ b/app/controllers/referential_companies_controller.rb @@ -18,6 +18,10 @@ class ReferentialCompaniesController < ChouetteController @companies = decorate_companies(@companies) } + format.json { + render json: companies_maps + } + format.js { @companies = decorate_companies(@companies) } @@ -85,4 +89,13 @@ class ReferentialCompaniesController < ChouetteController ) end + def companies_maps + @companies.collect do |company| + { :id => company.id.to_s, + :objectid => company.raw_objectid, + :name => company.name, + } + end + end + end diff --git a/app/controllers/routing_constraint_zones_controller.rb b/app/controllers/routing_constraint_zones_controller.rb index 47df211d0..886247e79 100644 --- a/app/controllers/routing_constraint_zones_controller.rb +++ b/app/controllers/routing_constraint_zones_controller.rb @@ -13,13 +13,16 @@ class RoutingConstraintZonesController < ChouetteController def index index! do |format| - @routing_constraint_zones = RoutingConstraintZoneDecorator.decorate( - @routing_constraint_zones, - context: { - referential: referential, - line: parent - } - ) + format.html do + @routing_constraint_zones = RoutingConstraintZoneDecorator.decorate( + @routing_constraint_zones, + context: { + referential: referential, + line: parent + } + ) + end + format.json end end diff --git a/app/controllers/stop_area_referentials_controller.rb b/app/controllers/stop_area_referentials_controller.rb index f2d375e49..0e6a54b49 100644 --- a/app/controllers/stop_area_referentials_controller.rb +++ b/app/controllers/stop_area_referentials_controller.rb @@ -1,6 +1,13 @@ class StopAreaReferentialsController < ChouetteController defaults :resource_class => StopAreaReferential + + def show + show! do + @stop_area_referential = StopAreaReferentialDecorator.decorate(@stop_area_referential) + end + end + def sync authorize resource, :synchronize? @sync = resource.stop_area_referential_syncs.build diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb index b2634467d..734152c64 100644 --- a/app/controllers/stop_areas_controller.rb +++ b/app/controllers/stop_areas_controller.rb @@ -126,7 +126,18 @@ class StopAreasController < ChouetteController if sort_column && sort_direction @stop_areas ||= begin - stop_areas = @q.result.order(sort_column + ' ' + sort_direction) + if sort_column == "area_type" + sorted_area_type_labels = Chouette::AreaType.options(:all, I18n.locale).sort.transpose.last + sorted_area_type_labels = sorted_area_type_labels.reverse if sort_direction != 'asc' + order_by = ["CASE"] + sorted_area_type_labels.each_with_index do |area_type, index| + order_by << "WHEN area_type='#{area_type}' THEN #{index}" + end + order_by << "END" + stop_areas = @q.result.order(order_by.join(" ")) + else + stop_areas = @q.result.order(sort_column + ' ' + sort_direction) + end stop_areas = stop_areas.paginate(:page => params[:page], :per_page => @per_page) if @per_page.present? stop_areas end @@ -192,7 +203,7 @@ class StopAreasController < ChouetteController :kind, :status, localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS - ] + permitted_custom_fields_params(Chouette::StopArea.custom_fields) # XXX filter on the workgroup + ] + permitted_custom_fields_params(Chouette::StopArea.custom_fields(stop_area_referential.workgroup)) params.require(:stop_area).permit(fields) end diff --git a/app/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb index 2ac8532e0..4ca2293f0 100644 --- a/app/controllers/time_tables_controller.rb +++ b/app/controllers/time_tables_controller.rb @@ -77,7 +77,7 @@ class TimeTablesController < ChouetteController end def index - request.format.kml? ? @per_page = nil : @per_page = 12 + # request.format.kml? ? @per_page = nil : @per_page = 12 index! do |format| format.html { @@ -130,6 +130,7 @@ class TimeTablesController < ChouetteController @time_tables ||= begin time_tables = @q.result(:distinct => true) + sort_column if sort_column == "bounding_dates" time_tables = @q.result(:distinct => false).paginate(page: params[:page], per_page: 10) ids = time_tables.pluck(:id).uniq @@ -186,10 +187,13 @@ class TimeTablesController < ChouetteController private def sort_column - valid_cols = referential.time_tables.column_names - valid_cols << "bounding_dates" - valid_cols << "vehicle_journeys_count" - valid_cols.include?(params[:sort]) ? params[:sort] : 'comment' + @@valid_cols ||= begin + valid_cols = %w(id color comment) + valid_cols << "bounding_dates" + valid_cols << "vehicle_journeys_count" + valid_cols + end + @@valid_cols.include?(params[:sort]) ? params[:sort] : 'comment' end def sort_direction %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc' diff --git a/app/controllers/vehicle_journeys_collections_controller.rb b/app/controllers/vehicle_journeys_collections_controller.rb index 712bcc154..a117888ab 100644 --- a/app/controllers/vehicle_journeys_collections_controller.rb +++ b/app/controllers/vehicle_journeys_collections_controller.rb @@ -9,8 +9,8 @@ class VehicleJourneysCollectionsController < ChouetteController alias_method :route, :parent def update - state = JSON.parse request.raw_post - Chouette::VehicleJourney.state_update route, state + state = JSON.parse request.raw_post + @resources = Chouette::VehicleJourney.state_update route, state errors = state.any? {|item| item['errors']} respond_to do |format| diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index 220f2d29e..95eec7517 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -208,11 +208,12 @@ class VehicleJourneysController < ChouetteController short_id: item.get_objectid.short_id, full_schedule: item.full_schedule?, costs: item.costs, - stop_area_short_descriptions: item.stop_areas.map do |stop| + journey_length: item.journey_length, + stop_area_short_descriptions: item.stop_points.map do |stop| { stop_area_short_description: { - id: stop.id, - name: stop.name, + id: stop.stop_area_id, + name: stop.stop_area.name, object_id: item.objectid } } diff --git a/app/decorators/line_decorator.rb b/app/decorators/line_decorator.rb index 0e7b6b9ae..077978c36 100644 --- a/app/decorators/line_decorator.rb +++ b/app/decorators/line_decorator.rb @@ -35,11 +35,6 @@ class LineDecorator < AF83::Decorator edit_action_link do |l| l.content {|l| l.primary? ? h.t('actions.edit') : h.t('lines.actions.edit') } end - - action_link on: :index, secondary: :index do |l| - l.content t('lines.actions.new') - l.href { h.new_line_referential_line_path(context[:line_referential]) } - end end ### the option :policy will automatically check for the corresponding method diff --git a/app/decorators/line_referential_decorator.rb b/app/decorators/line_referential_decorator.rb new file mode 100644 index 000000000..1ca0312c3 --- /dev/null +++ b/app/decorators/line_referential_decorator.rb @@ -0,0 +1,13 @@ +class LineReferentialDecorator < AF83::Decorator + decorates LineReferential + + with_instance_decorator do |instance_decorator| + + instance_decorator.action_link policy: :synchronize, primary: :show do |l| + l.content t('actions.sync') + l.href { h. sync_line_referential_path(object.id) } + l.method :post + end + + end +end diff --git a/app/decorators/stop_area_referential_decorator.rb b/app/decorators/stop_area_referential_decorator.rb new file mode 100644 index 000000000..d30501ec9 --- /dev/null +++ b/app/decorators/stop_area_referential_decorator.rb @@ -0,0 +1,13 @@ +class StopAreaReferentialDecorator < AF83::Decorator + decorates StopAreaReferential + + with_instance_decorator do |instance_decorator| + + instance_decorator.action_link policy: :synchronize, primary: :show do |l| + l.content t('actions.sync') + l.href { h. sync_stop_area_referential_path(object.id) } + l.method :post + end + + end +end diff --git a/app/helpers/line_referential_syncs_helper.rb b/app/helpers/line_referential_syncs_helper.rb new file mode 100644 index 000000000..37f08b154 --- /dev/null +++ b/app/helpers/line_referential_syncs_helper.rb @@ -0,0 +1,31 @@ +module LineReferentialSyncsHelper + + def last_line_ref_sync_message(line_ref_sync) + line_ref_sync.line_referential_sync_messages.last + end + + def line_referential_sync_created_at(line_ref_sync) + l(last_line_ref_sync_message(line_ref_sync).created_at, format: :short_with_time) + end + + def line_referential_sync_status(line_ref_sync) + status = line_ref_sync.status + + if %w[new pending].include? status + content_tag :span, '', class: "fa fa-clock-o" + else + cls ='' + cls = 'success' if status == 'successful' + cls = 'danger' if status == 'failed' + + content_tag :span, '', class: "fa fa-circle text-#{cls}" + end + end + + def line_referential_sync_message(line_ref_sync) + last_line_ref_sync_message = last_line_ref_sync_message(line_ref_sync) + data = last_line_ref_sync_message.message_attributes.symbolize_keys! + data[:processing_time] = distance_of_time_in_words(data[:processing_time].to_i) + t("line_referential_sync.message.#{last_line_ref_sync_message.message_key}", last_line_ref_sync_message.message_attributes.symbolize_keys!).html_safe + end +end diff --git a/app/helpers/multiple_selection_toolbox_helper.rb b/app/helpers/multiple_selection_toolbox_helper.rb index 7e02c6d73..012851b4a 100644 --- a/app/helpers/multiple_selection_toolbox_helper.rb +++ b/app/helpers/multiple_selection_toolbox_helper.rb @@ -4,7 +4,7 @@ module MultipleSelectionToolboxHelper # #5206 method too long def multiple_selection_toolbox(actions, collection_name:) links = content_tag :ul do - + # #5206 `if params[:controller]` mieux passer comme parametre si besoin delete_path = nil @@ -19,8 +19,7 @@ module MultipleSelectionToolboxHelper method: :delete, data: { path: delete_path, - # #5206 Missing Translations - confirm: t('actions.are_you_sure') + confirm: t('are_you_sure') }, title: t("actions.#{action}") ) do @@ -38,7 +37,7 @@ module MultipleSelectionToolboxHelper class: 'info-msg' ) - content_tag :div, '', + content_tag :div, '', class: 'select_toolbox noselect', id: "selected-#{collection_name}-action-box" do links + label diff --git a/app/helpers/referentials_helper.rb b/app/helpers/referentials_helper.rb index 9c3852322..9b5b13ace 100644 --- a/app/helpers/referentials_helper.rb +++ b/app/helpers/referentials_helper.rb @@ -2,12 +2,13 @@ module ReferentialsHelper # Outputs a green check icon and the text "Oui" or a red exclamation mark # icon and the text "Non" based on `status` def line_status(status) - if status + case status + when :deactivated content_tag(:span, nil, class: 'fa fa-exclamation-circle fa-lg text-danger') + - t('activerecord.attributes.line.deactivated') + Chouette::Line.tmf('deactivated') else content_tag(:span, nil, class: 'fa fa-check-circle fa-lg text-success') + - t('activerecord.attributes.line.activated') + Chouette::Line.tmf('activated') end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index be70d974d..16081b660 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -15,7 +15,7 @@ module SearchHelper if val.is_a?(Array) active = val.any? &:present? elsif val.is_a?(Hash) - active = val.values.any? &:present? + active = val.values.any? {|v| v.present? && v != "false" } else active = true end diff --git a/app/helpers/stop_area_referential_syncs_helper.rb b/app/helpers/stop_area_referential_syncs_helper.rb new file mode 100644 index 000000000..3e2837fda --- /dev/null +++ b/app/helpers/stop_area_referential_syncs_helper.rb @@ -0,0 +1,31 @@ +module StopAreaReferentialSyncsHelper + + def last_stop_area_ref_sync_message(stop_area_ref_sync) + stop_area_ref_sync.stop_area_referential_sync_messages.last + end + + def stop_area_referential_sync_created_at(stop_area_ref_sync) + l(last_stop_area_ref_sync_message(stop_area_ref_sync).created_at, format: :short_with_time) + end + + def stop_area_referential_sync_status(stop_area_ref_sync) + status = stop_area_ref_sync.status + + if %w[new pending].include? status + content_tag :span, '', class: "fa fa-clock-o" + else + cls ='' + cls = 'success' if status == 'successful' + cls = 'danger' if status == 'failed' + + content_tag :span, '', class: "fa fa-circle text-#{cls}" + end + end + + def stop_area_referential_sync_message(stop_area_ref_sync) + last_stop_area_ref_sync_message = last_stop_area_ref_sync_message(stop_area_ref_sync) + data = last_stop_area_ref_sync_message.message_attributes.symbolize_keys! + data[:processing_time] = distance_of_time_in_words(data[:processing_time].to_i) + t("stop_area_referential_sync.message.#{last_stop_area_ref_sync_message.message_key}", last_stop_area_ref_sync_message.message_attributes.symbolize_keys!).html_safe + end +end diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb index 032f68494..314154fb7 100644 --- a/app/helpers/stop_areas_helper.rb +++ b/app/helpers/stop_areas_helper.rb @@ -49,7 +49,7 @@ module StopAreasHelper content_tag :span, "#{sa.projection_x}, #{sa.projection_y}" elsif !sa.long_lat_type.nil? - content_tag :span, "#{sa.long_lat_type} : #{sa.longitude}, #{sa.latitude}" + content_tag :span, "#{sa.long_lat_type} : #{sa.latitude}, #{sa.longitude}" end end end diff --git a/app/inputs/full_time_zone_input.rb b/app/inputs/full_time_zone_input.rb new file mode 100644 index 000000000..f5d8a9ba2 --- /dev/null +++ b/app/inputs/full_time_zone_input.rb @@ -0,0 +1,24 @@ +class FullTimeZoneInput < SimpleForm::Inputs::CollectionSelectInput + def collection + @collection ||= begin + collection = options.delete(:collection) || ActiveSupport::TimeZone::MAPPING + collection.respond_to?(:call) ? collection.call : collection.to_a + end + end + + def detect_collection_methods + label, value = options.delete(:label_method), options.delete(:value_method) + + label ||= ->(tz) do + tz = ActiveSupport::TimeZone[tz.last] + "(#{tz.formatted_offset}) #{tz.name}" + end + value ||= :last + + [label, value] + end + + def input(wrapper_options = {}) + super wrapper_options + end +end diff --git a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js b/app/javascript/helpers/CustomFieldsInputs.js index 827c36b76..9547021eb 100644 --- a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js +++ b/app/javascript/helpers/CustomFieldsInputs.js @@ -16,7 +16,7 @@ export default class CustomFieldsInputs extends Component { })} ref={'custom_fields.' + cf.code} className='form-control' - defaultValue={cf.value} + defaultValue={cf.value || cf.options.default} disabled={this.props.disabled} options={{ theme: 'bootstrap', @@ -34,8 +34,21 @@ export default class CustomFieldsInputs extends Component { ref={'custom_fields.' + cf.code} className='form-control' disabled={this.props.disabled} - defaultValue={cf.value} - onChange={(e) => this.props.onUpdate(cf.code, e.target.value) } + value={cf.value || cf.options.default} + onChange={(e) => {this.props.onUpdate(cf.code, e.target.value); this.forceUpdate()} } + /> + ) + } + + integerInput(cf){ + return( + <input + type='number' + ref={'custom_fields.' + cf.code} + className='form-control' + disabled={this.props.disabled} + value={cf.value || cf.options.default} + onChange={(e) => {this.props.onUpdate(cf.code, e.target.value); this.forceUpdate()} } /> ) } diff --git a/app/javascript/helpers/polyfills.js b/app/javascript/helpers/polyfills.js new file mode 100644 index 000000000..93e3e9846 --- /dev/null +++ b/app/javascript/helpers/polyfills.js @@ -0,0 +1,4 @@ +import 'promise-polyfill/src/polyfill' +import 'es6-symbol/implement' +import 'polyfill-array-includes' +import 'whatwg-fetch' diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js index ea54f4e05..a813f4b9e 100644 --- a/app/javascript/journey_patterns/actions/index.js +++ b/app/javascript/journey_patterns/actions/index.js @@ -1,10 +1,3 @@ -import Promise from 'promise-polyfill' - -// To add to window -if (!window.Promise) { - window.Promise = Promise; -} - const actions = { enterEditMode: () => ({ type: "ENTER_EDIT_MODE" @@ -195,15 +188,19 @@ const actions = { dispatch(actions.unavailableServer()) } else { if(json.length != 0){ - let val - for (val of json){ - for (let stop_point of val.route_short_description.stop_points){ + let j = 0 + while(j < json.length){ + let val = json[j] + let i = 0 + while(i < val.route_short_description.stop_points.length){ + let stop_point = val.route_short_description.stop_points[i] stop_point.checked = false val.stop_area_short_descriptions.map((element) => { if(element.stop_area_short_description.id === stop_point.id){ stop_point.checked = true } }) + i ++ } journeyPatterns.push( _.assign({}, val, { @@ -212,6 +209,7 @@ const actions = { deletable: false }) ) + j ++ } } window.currentItemsLength = journeyPatterns.length diff --git a/app/javascript/journey_patterns/components/CreateModal.js b/app/javascript/journey_patterns/components/CreateModal.js index 946c13d9c..51f6f6c1b 100644 --- a/app/javascript/journey_patterns/components/CreateModal.js +++ b/app/javascript/journey_patterns/components/CreateModal.js @@ -1,15 +1,17 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import actions from '../actions' +import CustomFieldsInputs from '../../helpers/CustomFieldsInputs' export default class CreateModal extends Component { constructor(props) { super(props) + this.custom_fields = _.assign({}, this.props.custom_fields) } handleSubmit() { if(actions.validateFields(this.refs) == true) { - this.props.onAddJourneyPattern(this.refs) + this.props.onAddJourneyPattern(_.assign({}, this.refs, {custom_fields: this.custom_fields})) this.props.onModalClose() $('#NewJourneyPatternModal').modal('hide') } @@ -78,8 +80,14 @@ export default class CreateModal extends Component { /> </div> </div> + <CustomFieldsInputs + values={this.props.custom_fields} + onUpdate={(code, value) => this.custom_fields[code]["value"] = value} + disabled={false} + /> </div> </div> + <div className='modal-footer'> <button className='btn btn-link' diff --git a/app/javascript/journey_patterns/components/EditModal.js b/app/javascript/journey_patterns/components/EditModal.js index 1960849fb..a23a57f96 100644 --- a/app/javascript/journey_patterns/components/EditModal.js +++ b/app/javascript/journey_patterns/components/EditModal.js @@ -1,19 +1,27 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import actions from '../actions' +import CustomFieldsInputs from '../../helpers/CustomFieldsInputs' export default class EditModal extends Component { constructor(props) { super(props) + this.updateValue = this.updateValue.bind(this) } handleSubmit() { if(actions.validateFields(this.refs) == true) { - this.props.saveModal(this.props.modal.modalProps.index, this.refs) + this.props.saveModal(this.props.modal.modalProps.index, _.assign({}, this.refs, {custom_fields: this.custom_fields})) $('#JourneyPatternModal').modal('hide') } } + updateValue(attribute, e) { + actions.resetValidation(e.currentTarget) + this.props.modal.modalProps.journeyPattern[attribute] = e.target.value + this.forceUpdate() + } + renderModalTitle() { if (this.props.editMode) { return ( @@ -28,6 +36,9 @@ export default class EditModal extends Component { } render() { + if(this.props.modal.modalProps.journeyPattern){ + this.custom_fields = _.assign({}, this.props.modal.modalProps.journeyPattern.custom_fields) + } return ( <div className={ 'modal fade ' + ((this.props.modal.type == 'edit') ? 'in' : '') } id='JourneyPatternModal'> <div className='modal-container'> @@ -48,8 +59,8 @@ export default class EditModal extends Component { className='form-control' disabled={!this.props.editMode} id={this.props.modal.modalProps.index} - defaultValue={this.props.modal.modalProps.journeyPattern.name} - onKeyDown={(e) => actions.resetValidation(e.currentTarget)} + value={this.props.modal.modalProps.journeyPattern.name} + onChange={(e) => this.updateValue('name', e)} required /> </div> @@ -64,8 +75,8 @@ export default class EditModal extends Component { className='form-control' disabled={!this.props.editMode} id={this.props.modal.modalProps.index} - defaultValue={this.props.modal.modalProps.journeyPattern.published_name} - onKeyDown={(e) => actions.resetValidation(e.currentTarget)} + value={this.props.modal.modalProps.journeyPattern.published_name} + onChange={(e) => this.updateValue('published_name', e)} required /> </div> @@ -79,12 +90,19 @@ export default class EditModal extends Component { className='form-control' disabled={!this.props.editMode} id={this.props.modal.modalProps.index} - defaultValue={this.props.modal.modalProps.journeyPattern.registration_number} - onKeyDown={(e) => actions.resetValidation(e.currentTarget)} + value={this.props.modal.modalProps.journeyPattern.registration_number} + onChange={(e) => this.updateValue('registration_number', e)} /> </div> </div> </div> + <div className='row'> + <CustomFieldsInputs + values={this.props.modal.modalProps.journeyPattern.custom_fields} + onUpdate={(code, value) => this.custom_fields[code]["value"] = value} + disabled={!this.props.editMode} + /> + </div> <div> <label className='control-label'>{I18n.attribute_name('journey_pattern', 'checksum')}</label> <input @@ -92,7 +110,7 @@ export default class EditModal extends Component { ref='checksum' className='form-control' disabled='disabled' - defaultValue={this.props.modal.modalProps.journeyPattern.checksum} + value={this.props.modal.modalProps.journeyPattern.checksum} /> </div> </div> diff --git a/app/javascript/journey_patterns/containers/AddJourneyPattern.js b/app/javascript/journey_patterns/containers/AddJourneyPattern.js index b093fd111..9e85afd5e 100644 --- a/app/javascript/journey_patterns/containers/AddJourneyPattern.js +++ b/app/javascript/journey_patterns/containers/AddJourneyPattern.js @@ -7,7 +7,8 @@ const mapStateToProps = (state) => { modal: state.modal, journeyPatterns: state.journeyPatterns, editMode: state.editMode, - status: state.status + status: state.status, + custom_fields: state.custom_fields } } diff --git a/app/javascript/journey_patterns/containers/Modal.js b/app/javascript/journey_patterns/containers/Modal.js index 33ee8583c..fc04843e4 100644 --- a/app/javascript/journey_patterns/containers/Modal.js +++ b/app/javascript/journey_patterns/containers/Modal.js @@ -7,7 +7,8 @@ const mapStateToProps = (state) => { return { editMode: state.editMode, modal: state.modal, - journeyPattern: state.journeyPattern + journeyPattern: state.journeyPattern, + custom_fields: state.custom_fields, } } diff --git a/app/javascript/journey_patterns/reducers/index.js b/app/javascript/journey_patterns/reducers/index.js index 2ffaf86d4..d3a1d29a2 100644 --- a/app/javascript/journey_patterns/reducers/index.js +++ b/app/javascript/journey_patterns/reducers/index.js @@ -12,7 +12,8 @@ const journeyPatternsApp = combineReducers({ journeyPatterns, pagination, stopPointsList, - modal + modal, + custom_fields: (state = [], action) => state }) export default journeyPatternsApp diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js index b046f2b38..1a6a27da6 100644 --- a/app/javascript/journey_patterns/reducers/journeyPatterns.js +++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js @@ -103,7 +103,8 @@ export default function journeyPatterns (state = [], action) { return _.assign({}, j, { name: action.data.name.value, published_name: action.data.published_name.value, - registration_number: action.data.registration_number.value + registration_number: action.data.registration_number.value, + custom_fields: action.data.custom_fields, }) } else { return j diff --git a/app/javascript/packs/calendars/edit.js b/app/javascript/packs/calendars/edit.js index bd09657ec..0c46313b9 100644 --- a/app/javascript/packs/calendars/edit.js +++ b/app/javascript/packs/calendars/edit.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' diff --git a/app/javascript/packs/exports/new.js b/app/javascript/packs/exports/new.js index ffe702cdb..b113cc709 100644 --- a/app/javascript/packs/exports/new.js +++ b/app/javascript/packs/exports/new.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import MasterSlave from "../../helpers/master_slave" new MasterSlave("form") diff --git a/app/javascript/packs/journey_patterns/index.js b/app/javascript/packs/journey_patterns/index.js index 367a8830f..bd7df2634 100644 --- a/app/javascript/packs/journey_patterns/index.js +++ b/app/javascript/packs/journey_patterns/index.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' @@ -32,7 +34,8 @@ var initialState = { type: '', modalProps: {}, confirmModal: {} - } + }, + custom_fields: window.custom_fields } // const loggerMiddleware = createLogger() diff --git a/app/javascript/packs/referential_lines/show.js b/app/javascript/packs/referential_lines/show.js index 99c5072ef..523f2040f 100644 --- a/app/javascript/packs/referential_lines/show.js +++ b/app/javascript/packs/referential_lines/show.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import clone from '../../helpers/clone' import RoutesMap from '../../helpers/routes_map' diff --git a/app/javascript/packs/referential_overview/overview.js b/app/javascript/packs/referential_overview/overview.js index 59c326e9a..03da12ef3 100644 --- a/app/javascript/packs/referential_overview/overview.js +++ b/app/javascript/packs/referential_overview/overview.js @@ -1 +1,3 @@ +import '../../helpers/polyfills' + import ReferentialOverview from '../../referential_overview' diff --git a/app/javascript/packs/routes/edit.js b/app/javascript/packs/routes/edit.js index fc7aa203d..0512b7aff 100644 --- a/app/javascript/packs/routes/edit.js +++ b/app/javascript/packs/routes/edit.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import React from 'react' import PropTypes from 'prop-types' @@ -56,12 +58,25 @@ const getInitialState = () => { } var initialState = { stopPoints: getInitialState() } -const loggerMiddleware = createLogger() -let store = createStore( - reducers, - initialState, - applyMiddleware(thunkMiddleware, promise, loggerMiddleware) -) +let store = null + +if(Object.assign){ + const loggerMiddleware = createLogger() + store = createStore( + reducers, + initialState, + applyMiddleware(thunkMiddleware, promise, loggerMiddleware) + ) +} +else{ + // IE + store = createStore( + reducers, + initialState, + applyMiddleware(thunkMiddleware, promise) + ) +} + render( <Provider store={store}> diff --git a/app/javascript/packs/routes/show.js b/app/javascript/packs/routes/show.js index c20de0800..e8e068ddd 100644 --- a/app/javascript/packs/routes/show.js +++ b/app/javascript/packs/routes/show.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import clone from '../../helpers/clone' import RoutesMap from '../../helpers/routes_map' diff --git a/app/javascript/packs/stop_areas/new.js b/app/javascript/packs/stop_areas/new.js index ffe702cdb..b113cc709 100644 --- a/app/javascript/packs/stop_areas/new.js +++ b/app/javascript/packs/stop_areas/new.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import MasterSlave from "../../helpers/master_slave" new MasterSlave("form") diff --git a/app/javascript/packs/time_tables/edit.js b/app/javascript/packs/time_tables/edit.js index cf058d501..873bdea50 100644 --- a/app/javascript/packs/time_tables/edit.js +++ b/app/javascript/packs/time_tables/edit.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' diff --git a/app/javascript/packs/vehicle_journeys/index.js b/app/javascript/packs/vehicle_journeys/index.js index 9cad3870e..0b4351d26 100644 --- a/app/javascript/packs/vehicle_journeys/index.js +++ b/app/javascript/packs/vehicle_journeys/index.js @@ -1,3 +1,5 @@ +import '../../helpers/polyfills' + import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' diff --git a/app/javascript/routes/actions/index.js b/app/javascript/routes/actions/index.js index 5fbf5bce9..13b2d60b2 100644 --- a/app/javascript/routes/actions/index.js +++ b/app/javascript/routes/actions/index.js @@ -56,12 +56,7 @@ const actions = { unselectMarker: (index) => ({ type: 'UNSELECT_MARKER', index - }), - defaultAttribute: (attribute, stopAreaKind) => { - if (attribute !== '') return attribute - if (stopAreaKind === undefined) return '' - return stopAreaKind === "commercial" ? "normal" : "forbidden" - } + }) } module.exports = actions diff --git a/app/javascript/routes/components/App.js b/app/javascript/routes/components/App.js index 26e69bf53..6f8cdf749 100644 --- a/app/javascript/routes/components/App.js +++ b/app/javascript/routes/components/App.js @@ -3,14 +3,9 @@ import PropTypes from 'prop-types' import AddStopPoint from '../containers/AddStopPoint' import VisibleStopPoints from'../containers/VisibleStopPoints' import clone from '../../helpers/clone' -const I18n = clone(window , "I18n", true) export default class App extends Component { - getChildContext() { - return { I18n } - } - render() { return ( <div> @@ -20,7 +15,3 @@ export default class App extends Component { ) } } - -App.childContextTypes = { - I18n: PropTypes.object -} diff --git a/app/javascript/routes/components/BSelect2.js b/app/javascript/routes/components/BSelect2.js index 89e1b6cfa..90f288944 100644 --- a/app/javascript/routes/components/BSelect2.js +++ b/app/javascript/routes/components/BSelect2.js @@ -10,8 +10,8 @@ var path = window.location.pathname.split('/', 3).join('/') export default class BSelect3 extends Component { - constructor(props, context) { - super(props, context) + constructor(props) { + super(props) } onChange(e) { this.props.onChange(this.props.index, { @@ -86,7 +86,7 @@ class BSelect2 extends Component{ onSelect={ this.props.onSelect } ref='newSelect' options={{ - placeholder: this.context.I18n.t("routes.edit.select2.placeholder"), + placeholder: I18n.t("routes.edit.select2.placeholder"), allowClear: true, language: 'fr', /* Doesn't seem to work... :( */ theme: 'bootstrap', @@ -129,8 +129,4 @@ class BSelect2 extends Component{ /> ) } -} - -BSelect2.contextTypes = { - I18n: PropTypes.object -} +}
\ No newline at end of file diff --git a/app/javascript/routes/components/OlMap.js b/app/javascript/routes/components/OlMap.js index 4beb02872..16fec0e87 100644 --- a/app/javascript/routes/components/OlMap.js +++ b/app/javascript/routes/components/OlMap.js @@ -3,8 +3,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' export default class OlMap extends Component{ - constructor(props, context){ - super(props, context) + constructor(props){ + super(props) } fetchApiURL(id){ @@ -115,40 +115,40 @@ export default class OlMap extends Component{ <strong>{this.props.value.olMap.json.name}</strong> </p> <p> - <strong>{this.context.I18n.t('routes.edit.map.stop_point_type')} : </strong> + <strong>{I18n.t('routes.edit.map.stop_point_type')} : </strong> {this.props.value.olMap.json.area_type} </p> <p> - <strong>{this.context.I18n.t('routes.edit.map.short_name')} : </strong> + <strong>{I18n.t('routes.edit.map.short_name')} : </strong> {this.props.value.olMap.json.short_name} </p> <p> - <strong>{this.context.I18n.t('id_reflex')} : </strong> + <strong>{I18n.t('id_reflex')} : </strong> {this.props.value.olMap.json.user_objectid} </p> - <p><strong>{this.context.I18n.t('routes.edit.map.coordinates')} : </strong></p> + <p><strong>{I18n.t('routes.edit.map.coordinates')} : </strong></p> <p style={{paddingLeft: 10, marginTop: 0}}> - <em>{this.context.I18n.t('routes.edit.map.proj')}.: </em>WSG84<br/> - <em>{this.context.I18n.t('routes.edit.map.lat')}.: </em>{this.props.value.olMap.json.latitude} <br/> - <em>{this.context.I18n.t('routes.edit.map.lon')}.: </em>{this.props.value.olMap.json.longitude} + <em>{I18n.t('routes.edit.map.proj')}.: </em>WSG84<br/> + <em>{I18n.t('routes.edit.map.lat')}.: </em>{this.props.value.olMap.json.latitude} <br/> + <em>{I18n.t('routes.edit.map.lon')}.: </em>{this.props.value.olMap.json.longitude} </p> <p> - <strong>{this.context.I18n.t('routes.edit.map.postal_code')} : </strong> + <strong>{I18n.t('routes.edit.map.postal_code')} : </strong> {this.props.value.olMap.json.zip_code} </p> <p> - <strong>{this.context.I18n.t('routes.edit.map.city')} : </strong> + <strong>{I18n.t('routes.edit.map.city')} : </strong> {this.props.value.olMap.json.city_name} </p> <p> - <strong>{this.context.I18n.t('routes.edit.map.comment')} : </strong> + <strong>{I18n.t('routes.edit.map.comment')} : </strong> {this.props.value.olMap.json.comment} </p> {(this.props.value.stoparea_id != this.props.value.olMap.json.stoparea_id) &&( <div className='btn btn-outline-primary btn-sm' onClick= {() => {this.props.onUpdateViaOlMap(this.props.index, this.props.value.olMap.json)}} - >{this.context.I18n.t('actions.select')}</div> + >{I18n.t('actions.select')}</div> )} </div> <div className='map_content'> @@ -164,7 +164,3 @@ export default class OlMap extends Component{ OlMap.propTypes = { } - -OlMap.contextTypes = { - I18n: PropTypes.object -} diff --git a/app/javascript/routes/components/StopPoint.js b/app/javascript/routes/components/StopPoint.js index 768d069c0..916052b42 100644 --- a/app/javascript/routes/components/StopPoint.js +++ b/app/javascript/routes/components/StopPoint.js @@ -6,7 +6,7 @@ import OlMap from './OlMap' import { defaultAttribute } from '../actions' -export default function StopPoint(props, {I18n}) { +export default function StopPoint(props) { return ( <div className='nested-fields'> <div className='wrapper'> @@ -19,14 +19,14 @@ export default function StopPoint(props, {I18n}) { </div> <div> - <select className='form-control' value={defaultAttribute(props.value.for_boarding, props.value.stoparea_kind)} id="for_boarding" onChange={props.onSelectChange}> + <select className='form-control' value={props.value.for_boarding} id="for_boarding" onChange={props.onSelectChange}> <option value="normal">{I18n.t('routes.edit.stop_point.boarding.normal')}</option> <option value="forbidden">{I18n.t('routes.edit.stop_point.boarding.forbidden')}</option> </select> </div> <div> - <select className='form-control' value={defaultAttribute(props.value.for_alighting, props.value.stoparea_kind)} id="for_alighting" onChange={props.onSelectChange}> + <select className='form-control' value={props.value.for_alighting} id="for_alighting" onChange={props.onSelectChange}> <option value="normal">{I18n.t('routes.edit.stop_point.alighting.normal')}</option> <option value="forbidden">{I18n.t('routes.edit.stop_point.alighting.forbidden')}</option> </select> @@ -92,7 +92,3 @@ StopPoint.propTypes = { index: PropTypes.number, value: PropTypes.object } - -StopPoint.contextTypes = { - I18n: PropTypes.object -} diff --git a/app/javascript/routes/components/StopPointList.js b/app/javascript/routes/components/StopPointList.js index b227abdea..9bc5e02d1 100644 --- a/app/javascript/routes/components/StopPointList.js +++ b/app/javascript/routes/components/StopPointList.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import StopPoint from './StopPoint' -export default function StopPointList({ stopPoints, onDeleteClick, onMoveUpClick, onMoveDownClick, onChange, onSelectChange, onToggleMap, onToggleEdit, onSelectMarker, onUnselectMarker, onUpdateViaOlMap }, {I18n}) { +export default function StopPointList({ stopPoints, onDeleteClick, onMoveUpClick, onMoveDownClick, onChange, onSelectChange, onToggleMap, onToggleEdit, onSelectMarker, onUnselectMarker, onUpdateViaOlMap }) { return ( <div className='subform'> <div className='nested-head'> diff --git a/app/javascript/routes/containers/AddStopPoint.js b/app/javascript/routes/containers/AddStopPoint.js index fd9227ff3..4b169807d 100644 --- a/app/javascript/routes/containers/AddStopPoint.js +++ b/app/javascript/routes/containers/AddStopPoint.js @@ -11,7 +11,7 @@ let AddStopPoint = ({ dispatch }) => { dispatch(actions.addStop()) }}> <button type="submit" className="btn btn-outline-primary"> - Ajouter un arrêt + {I18n.t('stop_areas.actions.new')} </button> </form> </div> diff --git a/app/javascript/routes/index.js b/app/javascript/routes/index.js index 3c7322953..fc99a3086 100644 --- a/app/javascript/routes/index.js +++ b/app/javascript/routes/index.js @@ -22,6 +22,7 @@ const getInitialState = () => { let fancyText = v.name.replace("'", "\'") if(v.zip_code && v.city_name) fancyText += ", " + v.zip_code + " " + v.city_name.replace("'", "\'") + forAlightingAndBoarding = v.stoparea_kind === 'commercial' ? 'normal' : 'forbidden' state.push({ stoppoint_id: v.stoppoint_id, @@ -37,8 +38,8 @@ const getInitialState = () => { name: v.name ? v.name.replace("'", "\'") : '', registration_number: v.registration_number, text: fancyText, - for_boarding: v.for_boarding || '', - for_alighting: v.for_alighting || '', + for_boarding: v.for_boarding || forAlightingAndBoarding, + for_alighting: v.for_alighting || forAlightingAndBoarding, longitude: v.longitude || 0, latitude: v.latitude || 0, comment: v.comment ? v.comment.replace("'", "\'") : '', diff --git a/app/javascript/routes/reducers/stopPoints.js b/app/javascript/routes/reducers/stopPoints.js index ba183d002..54142338d 100644 --- a/app/javascript/routes/reducers/stopPoints.js +++ b/app/javascript/routes/reducers/stopPoints.js @@ -8,8 +8,8 @@ const stopPoint = (state = {}, action, length) => { text: '', index: length, edit: true, - for_boarding: '', - for_alighting: '', + for_boarding: 'normal', + for_alighting: 'normal', olMap: { isOpened: false, json: {} @@ -61,6 +61,7 @@ const stopPoints = (state = [], action) => { case 'UPDATE_INPUT_VALUE': return state.map( (t, i) => { if (i === action.index) { + let forAlightingAndBoarding = action.text.stoparea_kind === 'commercial' ? 'normal' : 'forbidden' return _.assign( {}, t, @@ -77,7 +78,9 @@ const stopPoints = (state = [], action) => { area_type: action.text.area_type, city_name: action.text.city_name, comment: action.text.comment, - registration_number: action.text.registration_number + registration_number: action.text.registration_number, + for_alighting: forAlightingAndBoarding, + for_boarding: forAlightingAndBoarding } ) } else { diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js index b56279889..283897b63 100644 --- a/app/javascript/time_tables/actions/index.js +++ b/app/javascript/time_tables/actions/index.js @@ -190,10 +190,13 @@ const actions = { isInPeriod: (periods, date) => { date = new Date(date) - for (let period of periods) { + let i = 0; + while(i < periods.length){ + let period = periods[i] let begin = new Date(period.period_start) let end = new Date(period.period_end) if (date >= begin && date <= end) return true + i++ } return false @@ -234,12 +237,14 @@ const actions = { let error = '' start = new Date(start) end = new Date(end) - - for (let day of in_days) { + let i = 0; + while(i < in_days){ + let day = in_days[i] if (start <= new Date(day.date) && end >= new Date(day.date)) { error = I18n.t('time_tables.edit.error_submit.dates_overlaps') break } + i ++ } return error }, diff --git a/app/javascript/time_tables/components/Metas.js b/app/javascript/time_tables/components/Metas.js index d9746a379..186af540a 100644 --- a/app/javascript/time_tables/components/Metas.js +++ b/app/javascript/time_tables/components/Metas.js @@ -76,7 +76,6 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'tag_list')}</label> <div className="col-sm-8"> <TagsSelect2 - initialTags={metas.initial_tags} tags={metas.tags} onSelect2Tags={(e) => onSelect2Tags(e)} onUnselect2Tags={(e) => onUnselect2Tags(e)} diff --git a/app/javascript/time_tables/components/TagsSelect2.js b/app/javascript/time_tables/components/TagsSelect2.js index dd8d6e9c0..fe610ed58 100644 --- a/app/javascript/time_tables/components/TagsSelect2.js +++ b/app/javascript/time_tables/components/TagsSelect2.js @@ -27,7 +27,7 @@ export default class TagsSelect2 extends Component { return ( <Select2 value={(this.props.tags.length) ? map(this.props.tags, 'id') : undefined} - data={(this.props.initialTags.length) ? this.mapKeys(this.props.initialTags) : undefined} + data={(this.props.tags.length) ? this.mapKeys(this.props.tags) : undefined} onSelect={(e) => this.props.onSelect2Tags(e)} onUnselect={(e) => setTimeout( () => this.props.onUnselect2Tags(e, 150))} multiple={true} @@ -74,4 +74,4 @@ export default class TagsSelect2 extends Component { const formatRepo = (props) => { if(props.name) return props.name -}
\ No newline at end of file +} diff --git a/app/javascript/time_tables/containers/Metas.js b/app/javascript/time_tables/containers/Metas.js index ebccf556e..7bc3ef4e1 100644 --- a/app/javascript/time_tables/containers/Metas.js +++ b/app/javascript/time_tables/containers/Metas.js @@ -24,6 +24,7 @@ const mapDispatchToProps = (dispatch) => { }, onSelect2Tags: (e) => { e.preventDefault() + $(e.target).find('[data-select2-tag]').remove() dispatch(actions.select2Tags(e.params.data)) }, onUnselect2Tags: (e) => { diff --git a/app/javascript/time_tables/reducers/metas.js b/app/javascript/time_tables/reducers/metas.js index 51e1ec149..012f29511 100644 --- a/app/javascript/time_tables/reducers/metas.js +++ b/app/javascript/time_tables/reducers/metas.js @@ -31,11 +31,13 @@ export default function metas(state = {}, action) { return assign({}, state, {color: action.color}) case 'UPDATE_SELECT_TAG': let tags = [...state.tags] - tags.push(action.selectedItem) + if(tags.length == 0 || tags[tags.length-1].name != action.selectedItem.name){ + tags.push(action.selectedItem) + } return assign({}, state, {tags: tags}) case 'UPDATE_UNSELECT_TAG': return assign({}, state, {tags: filter(state.tags, (t) => (t.id != action.selectedItem.id))}) default: return state } -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index 537dcfc06..c67f9f0cf 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -1,9 +1,3 @@ -import Promise from 'promise-polyfill' - -// To add to window -if (!window.Promise) { - window.Promise = Promise; -} import { batchActions } from '../batch' const actions = { @@ -54,16 +48,10 @@ const actions = { }), selectJPCreateModal : (selectedJP) => ({ type : 'SELECT_JP_CREATE_MODAL', - selectedItem: { - id: selectedJP.id, + selectedItem: _.assign({}, selectedJP, { objectid: selectedJP.object_id, - short_id: selectedJP.short_id, - name: selectedJP.name, - published_name: selectedJP.published_name, - stop_areas: selectedJP.stop_area_short_descriptions, - costs: selectedJP.costs, - full_schedule: selectedJP.full_schedule - } + stop_areas: selectedJP.stop_area_short_descriptions + }) }), openEditModal : (vehicleJourney) => ({ type : 'EDIT_VEHICLEJOURNEY_MODAL', @@ -109,10 +97,30 @@ const actions = { vehicleJourneys, timetables }), + editVehicleJourneyConstraintZones : (vehicleJourneys, zones) => ({ + type: 'EDIT_VEHICLEJOURNEYS_CONSTRAINT_ZONES', + vehicleJourneys, + zones + }), openPurchaseWindowsEditModal : (vehicleJourneys) => ({ type : 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL', vehicleJourneys }), + openConstraintExclusionEditModal : (vehicleJourneys) => ({ + type : 'EDIT_CONSTRAINT_EXCLUSIONS_VEHICLEJOURNEY_MODAL', + vehicleJourneys + }), + selectConstraintZone: (selectedZone) =>({ + type: 'SELECT_CONSTRAINT_ZONE_MODAL', + selectedZone: { + id: selectedZone.id, + name: selectedZone.text + } + }), + deleteConstraintZone : (constraintZone) => ({ + type : 'DELETE_CONSTRAINT_ZONE_MODAL', + constraintZone + }), selectPurchaseWindowsModal: (selectedItem) =>({ type: 'SELECT_PURCHASE_WINDOW_MODAL', selectedItem @@ -339,16 +347,23 @@ const actions = { if(hasError == true) { dispatch(actions.unavailableServer()) } else { - let val - for (val of json.vehicle_journeys){ + let i = 0 + while(i < json.vehicle_journeys.length){ + let val = json.vehicle_journeys[i] + i++ var timeTables = [] var purchaseWindows = [] - let tt - for (tt of val.time_tables){ + let k = 0 + while(k < val.time_tables.length){ + let tt = val.time_tables[k] + k++ timeTables.push(tt) } if(val.purchase_windows){ - for (tt of val.purchase_windows){ + k = 0 + while(k < val.purchase_windows.length){ + let tt = val.purchase_windows[k] + k++ purchaseWindows.push(tt) } } diff --git a/app/javascript/vehicle_journeys/components/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js index 93fe015a8..1e43a490e 100644 --- a/app/javascript/vehicle_journeys/components/Filters.js +++ b/app/javascript/vehicle_journeys/components/Filters.js @@ -33,6 +33,7 @@ export default function Filters({filters, pagination, missions, onFilter, onRese {/* Calendriers */} <div className='form-group w33'> <TimetableSelect2 + placeholder={I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.timetable')} onSelect2Timetable={onSelect2Timetable} hasRoute={true} chunkURL={("/autocomplete_time_tables.json?route_id=" + String(window.route_id))} diff --git a/app/javascript/vehicle_journeys/components/Tools.js b/app/javascript/vehicle_journeys/components/Tools.js index 22ea44283..41a521d12 100644 --- a/app/javascript/vehicle_journeys/components/Tools.js +++ b/app/javascript/vehicle_journeys/components/Tools.js @@ -9,6 +9,7 @@ import EditVehicleJourney from '../containers/tools/EditVehicleJourney' import NotesEditVehicleJourney from '../containers/tools/NotesEditVehicleJourney' import TimetablesEditVehicleJourney from '../containers/tools/TimetablesEditVehicleJourney' import PurchaseWindowsEditVehicleJourney from '../containers/tools/PurchaseWindowsEditVehicleJourney' +import ConstraintExclusionEditVehicleJourney from '../containers/tools/ConstraintExclusionEditVehicleJourney' export default class Tools extends Component { @@ -35,11 +36,13 @@ export default class Tools extends Component { <AddVehicleJourney disabled={!this.hasPolicy("create") || !editMode} /> <DuplicateVehicleJourney disabled={!this.hasPolicy("create") || !this.hasPolicy("update") || !editMode}/> <ShiftVehicleJourney disabled={!this.hasPolicy("update") || !editMode}/> - <EditVehicleJourney disabled={!this.hasPolicy("update")}/> - <TimetablesEditVehicleJourney disabled={!this.hasPolicy("update")}/> + <EditVehicleJourney disabled={false}/> + + <TimetablesEditVehicleJourney disabled={false}/> { this.hasFeature('purchase_windows') && - <PurchaseWindowsEditVehicleJourney disabled={!this.hasPolicy("update")}/> + <PurchaseWindowsEditVehicleJourney disabled={false}/> } + <ConstraintExclusionEditVehicleJourney disabled={false}/> <NotesEditVehicleJourney disabled={!this.hasPolicy("update")}/> <DeleteVehicleJourneys disabled={!this.hasPolicy("destroy") || !editMode}/> </ul> diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index 73d99d120..f7ae9341f 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -10,6 +10,10 @@ export default class VehicleJourney extends Component { this.previousCity = undefined } + journey_length() { + return this.props.value.journey_pattern.journey_length + "km" + } + cityNameChecker(sp) { return this.props.vehicleJourneys.showHeader(sp.stop_point_objectid) } @@ -107,7 +111,21 @@ export default class VehicleJourney extends Component { } > <div className='strong mb-xs'>{this.props.value.short_id || '-'}</div> - <div>{this.props.value.published_journey_name && this.props.value.published_journey_name != I18n.t('undefined') ? this.props.value.published_journey_name : '-'}</div> + <div> + <a href="#" + onClick={(e) => { + if(this.props.disabled){ return } + e.stopPropagation(true) + e.preventDefault() + this.props.onOpenInfoModal(this.props.value) + $('#EditVehicleJourneyModal').modal('show') + false + } + } + > + {this.props.value.published_journey_name && this.props.value.published_journey_name != I18n.t('undefined') ? this.props.value.published_journey_name : '-'} + </a> + </div> <div>{this.props.value.journey_pattern.short_id || '-'}</div> <div>{this.props.value.company ? this.props.value.company.name : '-'}</div> { @@ -115,6 +133,11 @@ export default class VehicleJourney extends Component { <div key={i}>{this.extraHeaderValue(header)}</div> ) } + { this.hasFeature('journey_length_in_vehicle_journeys') && + <div> + {this.journey_length()} + </div> + } { this.hasFeature('purchase_windows') && <div> {purchase_windows.slice(0,3).map((tt, i)=> diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index c6f59ce9d..0bb00e1ea 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -87,16 +87,18 @@ export default class VehicleJourneys extends Component { } toggleTimetables(e) { - $('.table-2entries .detailed-timetables').toggleClass('hidden') - $('.table-2entries .detailed-timetables-bt').toggleClass('active') + let root = $(this.refs['vehicleJourneys']) + root.find('.table-2entries .detailed-timetables').toggleClass('hidden') + root.find('.table-2entries .detailed-timetables-bt').toggleClass('active') this.componentDidUpdate() e.preventDefault() false } togglePurchaseWindows(e) { - $('.table-2entries .detailed-purchase-windows').toggleClass('hidden') - $('.table-2entries .detailed-purchase-windows-bt').toggleClass('active') + let root = $(this.refs['vehicleJourneys']) + root.find('.table-2entries .detailed-purchase-windows').toggleClass('hidden') + root.find('.table-2entries .detailed-purchase-windows-bt').toggleClass('active') this.componentDidUpdate() e.preventDefault() false @@ -186,7 +188,7 @@ export default class VehicleJourneys extends Component { ) } else { return ( - <div className='row'> + <div className='row' ref='vehicleJourneys'> <div className='col-lg-12'> {(this.props.status.fetchSuccess == false) && ( <div className='alert alert-danger mt-sm'> @@ -222,6 +224,11 @@ export default class VehicleJourneys extends Component { <div key={i}>{this.extraHeaderLabel(header)}</div> ) } + { this.hasFeature('journey_length_in_vehicle_journeys') && + <div> + {I18n.attribute_name("vehicle_journey", "journey_length")} + </div> + } { this.hasFeature('purchase_windows') && <div> { detailed_purchase_windows && @@ -288,6 +295,7 @@ export default class VehicleJourneys extends Component { features={this.props.features} onUpdateTime={this.props.onUpdateTime} onSelectVehicleJourney={this.props.onSelectVehicleJourney} + onOpenInfoModal={this.props.onOpenInfoModal} vehicleJourneys={this} disabled={this.isReturn()} allTimeTables={this.allTimeTables()} diff --git a/app/javascript/vehicle_journeys/components/tools/ConstraintExclusionEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/ConstraintExclusionEditVehicleJourney.js new file mode 100644 index 000000000..bab77926f --- /dev/null +++ b/app/javascript/vehicle_journeys/components/tools/ConstraintExclusionEditVehicleJourney.js @@ -0,0 +1,182 @@ +import _ from 'lodash' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import actions from '../../actions' +import ConstraintZoneSelect2 from './select2s/ConstraintZoneSelect2' + + +export default class ConstraintExclusionEditVehicleJourney extends Component { + constructor(props) { + super(props) + this.handleSubmit = this.handleSubmit.bind(this) + this.constraintZoneUrl = this.constraintZoneUrl.bind(this) + this.excluded_constraint_zones = this.excluded_constraint_zones.bind(this) + this.constraint_zones = null + } + + handleSubmit() { + this.props.onConstraintZonesEditVehicleJourney(this.props.modal.modalProps.vehicleJourneys, this.props.modal.modalProps.selectedConstraintZones) + this.props.onModalClose() + $('#ConstraintExclusionEditVehicleJourney').modal('hide') + } + + constraintZoneUrl(contraint_zone) { + return window.constraint_zones_routes + "/" + contraint_zone.id + } + + excluded_constraint_zones() { + let out = [] + this.props.modal.modalProps.selectedConstraintZones.map((id, _)=>{ + this.constraint_zones.map((zone, _)=>{ + if(zone.id == id){ + out.push(zone) + } + }) + }) + return out + } + + fetch_constraint_zones() { + let url = window.constraint_zones_routes + ".json" + fetch(url, { + credentials: 'same-origin', + }).then(response => { + return response.json() + }).then((json) => { + this.constraint_zones = [] + json.map((item, i)=>{ + this.constraint_zones.push( + _.assign({}, item, {text: item.name}) + ) + }) + this.forceUpdate() + }) + } + + render() { + if(this.constraint_zones === null) { + this.fetch_constraint_zones() + return false + } + if(this.props.status.fetchSuccess == true) { + return ( + <li className='st_action'> + <button + type='button' + disabled={(actions.getSelected(this.props.vehicleJourneys).length < 1 || this.props.disabled)} + data-toggle='modal' + data-target='#ConstraintExclusionEditVehicleJourney' + onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))} + title={I18n.t('activerecord.attributes.vehicle_journey.constraint_exclusions')} + > + <span className='fa fa-ban fa-strong'></span> + </button> + + <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='ConstraintExclusionEditVehicleJourney'> + <div className='modal-container'> + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <h4 className='modal-title'>{I18n.t('activerecord.attributes.vehicle_journey.constraint_exclusions')}</h4> + <span type="button" className="close modal-close" data-dismiss="modal">×</span> + </div> + + {(this.props.modal.type == 'constraint_exclusions_edit') && ( + <form> + <div className='modal-body'> + <div className='row'> + <div className='col-lg-12'> + <div className='subform'> + <div className='nested-head'> + <div className='wrapper'> + <div> + <div className='form-group'> + <label className='control-label'>{this.excluded_constraint_zones().length == 0 ? I18n.t('vehicle_journeys.vehicle_journeys_matrix.no_excluded_constraint_zones') : I18n.t('vehicle_journeys.form.excluded_constraint_zones')}</label> + </div> + </div> + <div></div> + </div> + </div> + {this.excluded_constraint_zones().map((contraint_zone, i) => + <div className='nested-fields' key={i}> + <div className='wrapper'> + <div> <a href={this.constraintZoneUrl(contraint_zone)} target="_blank"> + {contraint_zone.name} + </a> </div> + { + this.props.editMode && + <div> + <a + href='#' + title='Supprimer' + className='fa fa-trash remove_fields' + style={{ height: 'auto', lineHeight: 'normal' }} + onClick={(e) => { + e.preventDefault() + this.props.onDeleteConstraintZone(contraint_zone) + }} + ></a> + </div> + } + </div> + </div> + )} + { + this.props.editMode && + <div className='nested-fields'> + <div className='wrapper'> + <div> + <ConstraintZoneSelect2 + data={this.constraint_zones} + values={this.props.modal.modalProps.constraint_exclusions} + placeholder={I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.constraint_zone')} + onSelectConstraintZone={this.props.onSelectConstraintZone} + /> + </div> + </div> + </div> + } + </div> + </div> + </div> + </div> + { + this.props.editMode && + <div className='modal-footer'> + <button + className='btn btn-link' + data-dismiss='modal' + type='button' + onClick={this.props.onModalClose} + > + {I18n.t('cancel')} + </button> + <button + className='btn btn-primary' + type='button' + onClick={this.handleSubmit} + > + {I18n.t('actions.submit')} + </button> + </div> + } + </form> + )} + + </div> + </div> + </div> + </div> + </li> + ) + } else { + return false + } + } +} + +ConstraintExclusionEditVehicleJourney.propTypes = { + onOpenCalendarsEditModal: PropTypes.func.isRequired, + onModalClose: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired +} diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js index f49b51f08..2806708f4 100644 --- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js +++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import actions from '../../actions' import MissionSelect2 from './select2s/MissionSelect2' import CompanySelect2 from './select2s/CompanySelect2' -import CustomFieldsInputs from './CustomFieldsInputs' +import CustomFieldsInputs from '../../../helpers/CustomFieldsInputs' export default class CreateModal extends Component { constructor(props) { @@ -14,7 +14,8 @@ export default class CreateModal extends Component { handleSubmit() { if(!this.props.modal.modalProps.selectedJPModal){ let field = $('#NewVehicleJourneyModal').find(".vjCreateSelectJP") - field.parent().parent().addClass('has-error').children('.help-block').remove() + field.parent().parent().addClass('has-error') + field.parent().children('.help-block').remove() field.parent().append("<span class='small help-block'>" + I18n.t("simple_form.required.text") + "</span>") return } diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js index e4e266c79..60d982845 100644 --- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import actions from '../../actions' import CompanySelect2 from './select2s/CompanySelect2' -import CustomFieldsInputs from './CustomFieldsInputs' +import CustomFieldsInputs from '../../../helpers/CustomFieldsInputs' export default class EditVehicleJourney extends Component { constructor(props) { diff --git a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js index 5d300f70c..ef58916f4 100644 --- a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js @@ -31,13 +31,13 @@ export default class NotesEditVehicleJourney extends Component { type='button' className='btn btn-outline-danger btn-xs' onClick={() => this.props.onToggleFootnoteModal(lf, false)} - ><span className="fa fa-trash"></span> Retirer</button> + ><span className="fa fa-trash"></span>{I18n.t('actions.remove')}</button> } else { return <button type='button' className='btn btn-outline-primary btn-xs' onClick={() => this.props.onToggleFootnoteModal(lf, true)} - ><span className="fa fa-plus"></span> Ajouter</button> + ><span className="fa fa-plus"></span>{I18n.t('actions.add')}</button> } } diff --git a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js index 30c511302..5fc925f4c 100644 --- a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js @@ -44,7 +44,7 @@ export default class PurchaseWindowsEditVehicleJourney extends Component { <div className='modal-dialog'> <div className='modal-content'> <div className='modal-header'> - <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.purchase_windows')}s</h4> + <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.purchase_windows')}</h4> <span type="button" className="close modal-close" data-dismiss="modal">×</span> </div> @@ -95,6 +95,7 @@ export default class PurchaseWindowsEditVehicleJourney extends Component { <div className='wrapper'> <div> <TimetableSelect2 + placeholder={I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.purchase_window')} onSelect2Timetable={this.props.onSelect2Timetable} chunkURL={'/autocomplete_purchase_windows.json'} searchKey={"name_or_objectid_cont_any"} diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js index 9ee2e1849..594140c76 100644 --- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js @@ -97,6 +97,7 @@ export default class TimetablesEditVehicleJourney extends Component { <div className='wrapper'> <div> <TimetableSelect2 + placeholder={I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.timetable')} onSelect2Timetable={this.props.onSelect2Timetable} chunkURL={'/autocomplete_time_tables.json'} searchKey={"unaccented_comment_or_objectid_cont_any"} diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/ConstraintZoneSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/ConstraintZoneSelect2.js new file mode 100644 index 000000000..1caaf2401 --- /dev/null +++ b/app/javascript/vehicle_journeys/components/tools/select2s/ConstraintZoneSelect2.js @@ -0,0 +1,37 @@ +import _ from 'lodash' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Select2 from 'react-select2-wrapper' +import actions from '../../../actions' + +export default class BSelect4 extends Component { + constructor(props) { + super(props) + } + + render() { + return ( + <Select2 + data={this.props.data} + value={this.props.value} + onSelect={(e) => this.props.onSelectConstraintZone(e) } + multiple={false} + ref='constraint_zone_id' + options={{ + allowClear: false, + theme: 'bootstrap', + width: '100%', + placeholder: this.props.placeholder, + language: require('./language'), + minimumInputLength: 1, + escapeMarkup: function (markup) { return markup; }, + templateResult: formatRepo + }} + /> + ) + } +} + +const formatRepo = (props) => { + if(props.text) return props.text +} diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js index d5aad20d0..919853a71 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js @@ -26,7 +26,7 @@ export default class BSelect4 extends Component { allowClear: false, theme: 'bootstrap', width: '100%', - placeholder: I18n.t('vehicle_journeys.vehicle_journeys_matrix.filters.timetable'), + placeholder: this.props.placeholder, language: require('./language'), ajax: { url: origin + path + this.props.chunkURL, diff --git a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js index f21e2c87e..45b061b66 100644 --- a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js +++ b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js @@ -27,7 +27,10 @@ const mapDispatchToProps = (dispatch) => { }, onSelectVehicleJourney: (index) => { dispatch(actions.selectVehicleJourney(index)) - } + }, + onOpenInfoModal: (vj) =>{ + dispatch(actions.openInfoModal(vj)) + }, } } diff --git a/app/javascript/vehicle_journeys/containers/tools/ConstraintExclusionEditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/ConstraintExclusionEditVehicleJourney.js new file mode 100644 index 000000000..76d85dfe6 --- /dev/null +++ b/app/javascript/vehicle_journeys/containers/tools/ConstraintExclusionEditVehicleJourney.js @@ -0,0 +1,37 @@ +import actions from '../../actions' +import { connect } from 'react-redux' +import ConstraintExclusionEditVehicleJourneyComponent from '../../components/tools/ConstraintExclusionEditVehicleJourney' + +const mapStateToProps = (state, ownProps) => { + return { + editMode: state.editMode, + modal: state.modal, + vehicleJourneys: state.vehicleJourneys, + status: state.status, + disabled: ownProps.disabled + } +} + +const mapDispatchToProps = (dispatch) => { + return { + onModalClose: () =>{ + dispatch(actions.closeModal()) + }, + onOpenCalendarsEditModal: (vehicleJourneys) =>{ + dispatch(actions.openConstraintExclusionEditModal(vehicleJourneys)) + }, + onDeleteConstraintZone: (constraint_zone) => { + dispatch(actions.deleteConstraintZone(constraint_zone)) + }, + onConstraintZonesEditVehicleJourney: (vehicleJourneys, constraint_zones) => { + dispatch(actions.editVehicleJourneyConstraintZones(vehicleJourneys, constraint_zones)) + }, + onSelectConstraintZone: (e) => { + dispatch(actions.selectConstraintZone(e.params.data)) + }, + } +} + +const ConstraintExclusionEditVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(ConstraintExclusionEditVehicleJourneyComponent) + +export default ConstraintExclusionEditVehicleJourney diff --git a/app/javascript/vehicle_journeys/reducers/modal.js b/app/javascript/vehicle_journeys/reducers/modal.js index bcfc6ea0b..75ab2f4ca 100644 --- a/app/javascript/vehicle_journeys/reducers/modal.js +++ b/app/javascript/vehicle_journeys/reducers/modal.js @@ -82,6 +82,30 @@ export default function modal(state = {}, action) { }, confirmModal: {} } + case 'EDIT_CONSTRAINT_EXCLUSIONS_VEHICLEJOURNEY_MODAL': + var vehicleJourneys = JSON.parse(JSON.stringify(action.vehicleJourneys)) + let uniqExclusions = [] + vehicleJourneys.map((vj, i) => { + vj.ignored_routing_contraint_zone_ids.map((exclusion, j) =>{ + let found = false + uniqExclusions.map((id, i)=>{ + if(id == parseInt(exclusion)){ + found = true + } + }) + if(!found){ + uniqExclusions.push(parseInt(exclusion)) + } + }) + }) + return { + type: 'constraint_exclusions_edit', + modalProps: { + vehicleJourneys: vehicleJourneys, + selectedConstraintZones: uniqExclusions + }, + confirmModal: {} + } case 'SELECT_CP_EDIT_MODAL': vehicleJourney = _.assign({}, state.modalProps.vehicleJourney, {company: action.selectedItem}) newModalProps = _.assign({}, state.modalProps, {vehicleJourney}) @@ -93,9 +117,28 @@ export default function modal(state = {}, action) { case 'SELECT_TT_CALENDAR_MODAL': newModalProps = _.assign({}, state.modalProps, {selectedTimetable : action.selectedItem}) return _.assign({}, state, {modalProps: newModalProps}) - case 'SELECT_PURCHASE_WINDOW_MODAL': - newModalProps = _.assign({}, state.modalProps, {selectedPurchaseWindow : action.selectedItem}) + case 'SELECT_CONSTRAINT_ZONE_MODAL': + let selectedConstraintZones = state.modalProps.selectedConstraintZones + let already_present = false + selectedConstraintZones.map((zone_id, i)=>{ + if(zone_id == parseInt(action.selectedZone.id)){ + already_present = true + } + }) + if(already_present){ return state } + selectedConstraintZones.push(parseInt(action.selectedZone.id)) + newModalProps = _.assign({}, state.modalProps, {selectedConstraintZones}) return _.assign({}, state, {modalProps: newModalProps}) + case 'DELETE_CONSTRAINT_ZONE_MODAL': + newModalProps = JSON.parse(JSON.stringify(state.modalProps)) + selectedConstraintZones = state.modalProps.selectedConstraintZones.slice(0) + selectedConstraintZones.map((zone_id, i) =>{ + if(zone_id == parseInt(action.constraintZone.id)){ + selectedConstraintZones.splice(i, 1) + } + }) + newModalProps.selectedConstraintZones = selectedConstraintZones + return _.assign({}, state, {modalProps: newModalProps}) case 'ADD_SELECTED_TIMETABLE': if(state.modalProps.selectedTimetable){ newModalProps = JSON.parse(JSON.stringify(state.modalProps)) @@ -164,7 +207,9 @@ export default function modal(state = {}, action) { confirmModal: {} } case 'SELECT_JP_CREATE_MODAL': - newModalProps = _.assign({}, state.modalProps, {selectedJPModal : action.selectedItem}) + let selected = action.selectedItem + delete selected["element"] + newModalProps = _.assign({}, state.modalProps, {selectedJPModal : selected}) return _.assign({}, state, {modalProps: newModalProps}) case 'SHIFT_VEHICLEJOURNEY_MODAL': return { diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index b02c19a69..121be6a00 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -40,6 +40,7 @@ const vehicleJourney= (state = {}, action, keep) => { } let offsetHours = sp.time_zone_offset / 3600 let offsetminutes = sp.time_zone_offset/60 - 60*offsetHours + newVjas = { delta: 0, arrival_time:{ @@ -53,6 +54,7 @@ const vehicleJourney= (state = {}, action, keep) => { if(sp.waiting_time && inJourney){ current_time = actions.addMinutesToTime(current_time, parseInt(sp.waiting_time)) + newVjas.delta = parseInt(sp.waiting_time) } newVjas.departure_time = { @@ -96,6 +98,7 @@ const vehicleJourney= (state = {}, action, keep) => { let lastStop = action.selectedJourneyPattern.stop_areas && action.selectedJourneyPattern.stop_areas[action.selectedJourneyPattern.stop_areas.length - 1] if(lastStop && lastStop.stop_area_short_description.id == sp.id){ newVjas.departure_time = newVjas.arrival_time + newVjas.delta = 0 } if(newVjas.dummy){ @@ -116,6 +119,7 @@ const vehicleJourney= (state = {}, action, keep) => { footnotes: [], time_tables: [], purchase_windows: [], + ignored_routing_contraint_zone_ids: [], vehicle_journey_at_stops: pristineVjasList, selected: false, deletable: false, @@ -232,6 +236,21 @@ export default function vehicleJourneys(state = [], action) { return vj } }) + case 'EDIT_VEHICLEJOURNEYS_CONSTRAINT_ZONES': + let newExclusions = JSON.parse(JSON.stringify(action.zones)) + return state.map((vj,i) =>{ + if(vj.selected){ + let updatedVJ = _.assign({}, vj) + action.vehicleJourneys.map((vjm, j) =>{ + if(vj.objectid == vjm.objectid){ + updatedVJ.ignored_routing_contraint_zone_ids = newExclusions + } + }) + return updatedVJ + }else{ + return vj + } + }) case 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS': let newWindows = JSON.parse(JSON.stringify(action.purchase_windows)) return state.map((vj,i) =>{ diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb index 07926e818..c549a7575 100644 --- a/app/models/calendar/period.rb +++ b/app/models/calendar/period.rb @@ -16,7 +16,7 @@ class Calendar < ApplicationModel alias_method :period_end=, :end= def check_end_greather_than_begin - if self.begin && self.end && self.begin >= self.end + if self.begin && self.end && self.begin > self.end errors.add(:base, I18n.t('calendars.errors.short_period')) end end diff --git a/app/models/chouette/area_type.rb b/app/models/chouette/area_type.rb index e17d2ee8d..4feb5c914 100644 --- a/app/models/chouette/area_type.rb +++ b/app/models/chouette/area_type.rb @@ -34,9 +34,9 @@ class Chouette::AreaType @@options = {} end - def self.options(kind=:all) + def self.options(kind=:all, locale=nil) @@options ||= {} - @@options[kind] ||= self.send(kind).map { |c| find(c) }.map { |t| [ t.label, t.code ] } + @@options[kind] ||= self.send(kind).map { |c| find(c) }.map { |t| [ t.label(locale), t.code ] } end attr_reader :code @@ -48,8 +48,8 @@ class Chouette::AreaType all.index(code) <=> all.index(other.code) end - def label - I18n.translate code, scope: 'area_types.label' + def label locale=nil + I18n.translate code, scope: 'area_types.label', locale: locale end end diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index 830a6a808..8bdf72dfb 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -2,6 +2,7 @@ module Chouette class JourneyPattern < Chouette::TridentActiveRecord has_metadata include ChecksumSupport + include CustomFieldsSupport include JourneyPatternRestrictions include ObjectidSupport @@ -28,6 +29,8 @@ module Chouette values.flatten end + has_checksum_children StopPoint + def self.state_update route, state transaction do state.each do |item| @@ -52,12 +55,19 @@ module Chouette end def self.state_permited_attributes item - { + attrs = { name: item['name'], published_name: item['published_name'], registration_number: item['registration_number'], costs: item['costs'] } + attrs["custom_field_values"] = Hash[ + *(item["custom_fields"] || {}) + .map { |k, v| [k, v["value"]] } + .flatten + ] + + attrs end def self.state_create_instance route, item @@ -78,10 +88,8 @@ module Chouette def state_stop_points_update item item['stop_points'].each do |sp| - exist = stop_area_ids.include?(sp['id']) - next if exist && sp['checked'] - - stop_point = route.stop_points.find_by(stop_area_id: sp['id']) + stop_point = route.stop_points.find_by(stop_area_id: sp['id'], position: sp['position']) + exist = stop_points.include?(stop_point) if !exist && sp['checked'] stop_points << stop_point end @@ -161,18 +169,18 @@ module Chouette 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 finish end full end - def distance_to stop + def distance_between start, stop + return 0 unless start.position < stop.position val = 0 - i = 0 - _end = stop_points.first - while _end != stop + i = stop_points.index(start) + _end = start + while _end && _end != stop i += 1 _start = _end _end = stop_points[i] @@ -181,6 +189,28 @@ module Chouette val end + def distance_to stop + distance_between stop_points.first, stop + end + + def journey_length + i = 0 + j = stop_points.length - 1 + start = stop_points[i] + stop = stop_points[j] + while i < j && start.kind == "non_commercial" + i+= 1 + start = stop_points[i] + end + + while i < j && stop.kind == "non_commercial" + j-= 1 + stop = stop_points[j] + end + return 0 unless start && stop + distance_between start, stop + end + def set_distances distances raise "inconsistent data: #{distances.count} values for #{stop_points.count} stops" unless distances.count == stop_points.count prev = distances[0].to_i diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index 4b5d1a68d..3ef2d8e1d 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -114,5 +114,9 @@ module Chouette def activated? !deactivated end + + def status + activated? ? :activated : :deactivated + end end end diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index e10b106ec..bd7cbb41e 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -21,6 +21,13 @@ module Chouette scope :overlap_dates, ->(date_range) { where('daterange(?, ?) && any (date_ranges)', date_range.first, date_range.last + 1.day) } scope :matching_dates, ->(date_range) { where('ARRAY[daterange(?, ?)] = date_ranges', date_range.first, date_range.last + 1.day) } + # VehicleJourneys include PurchaseWindow checksums in their checksums + # OPTIMIZEME + def update_vehicle_journey_checksums + vehicle_journeys.find_each(&:update_checksum!) + end + after_commit :update_vehicle_journey_checksums + def self.ransackable_scopes(auth_object = nil) [:contains_date] end @@ -35,7 +42,7 @@ module Chouette def checksum_attributes attrs = ['name', 'color', 'referential_id'] - ranges_attrs = date_ranges.map{|r| [r.first, r.last]}.flatten.sort + ranges_attrs = date_ranges.map{|r| [r.min, r.max]}.flatten.sort self.slice(*attrs).values + ranges_attrs end @@ -46,8 +53,9 @@ module Chouette ] end - # def checksum_attributes - # end - + def color + _color = read_attribute(:color) + _color.present? ? _color : nil + end end end diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 14bfa47b6..7a8d043e0 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -7,11 +7,12 @@ module Chouette include ObjectidSupport extend Enumerize - if Rails.env.development? + if ENV["CHOUETTE_ROUTE_POSITION_CHECK"] == "true" || !Rails.env.production? after_commit do positions = stop_points.pluck(:position) + Rails.logger.debug "Check positions in Route #{id} : #{positions.inspect}" if positions.size != positions.uniq.size - raise "DUPLICATED stop_points positions: #{positions}" + raise "DUPLICATED stop_points positions in Route #{id} : #{positions.inspect}" end end end @@ -77,7 +78,13 @@ module Chouette validates_presence_of :published_name validates_presence_of :line validates :wayback, inclusion: { in: self.wayback.values } - after_save :calculate_costs!, if: ->() { TomTom.enabled? } + after_commit :calculate_costs!, + on: [:create, :update], + if: ->() { + # Ensure the call back doesn't run during a referential merge + !referential.in_referential_suite? && + TomTom.enabled? + } def duplicate opposite=false overrides = { @@ -141,10 +148,13 @@ module Chouette values = self.slice(*['name', 'published_name', 'wayback']).values values.tap do |attrs| attrs << self.stop_points.sort_by(&:position).map{|sp| [sp.stop_area.user_objectid, sp.for_boarding, sp.for_alighting]} - attrs << self.routing_constraint_zones.map(&:checksum) + attrs << self.routing_constraint_zones.map(&:checksum).uniq.sort end end + has_checksum_children StopPoint + has_checksum_children RoutingConstraintZone + def geometry points = stop_areas.map(&:to_lat_lng).compact.map do |loc| [loc.lng, loc.lat] diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb index 886eadc6c..671ce2871 100644 --- a/app/models/chouette/routing_constraint_zone.rb +++ b/app/models/chouette/routing_constraint_zone.rb @@ -7,6 +7,13 @@ module Chouette belongs_to :route has_array_of :stop_points, class_name: 'Chouette::StopPoint' + belongs_to_array_in_many :vehicle_journeys, class_name: 'Chouette::VehicleJourney', array_name: :ignored_routing_contraint_zones + + def update_vehicle_journey_checksums + vehicle_journeys.each(&:update_checksum!) + end + after_save :update_vehicle_journey_checksums + validates_presence_of :name, :stop_points, :route_id # validates :stop_point_ids, length: { minimum: 2, too_short: I18n.t('activerecord.errors.models.routing_constraint_zone.attributes.stop_points.not_enough_stop_points') } validate :stop_points_belong_to_route, :not_all_stop_points_selected @@ -30,6 +37,11 @@ module Chouette ] end + def update_route_checksum + route.update_checksum! + end + after_commit :update_route_checksum + def stop_points_belong_to_route return unless route diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index 4ddc7403b..b933e1944 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -436,6 +436,12 @@ module Chouette ActiveSupport::TimeZone[time_zone]&.utc_offset end + def full_time_zone_name + return unless time_zone.present? + return unless ActiveSupport::TimeZone[time_zone].present? + ActiveSupport::TimeZone[time_zone].tzinfo.name + end + def country return unless country_code country = ISO3166::Country[country_code] diff --git a/app/models/chouette/stop_point.rb b/app/models/chouette/stop_point.rb index 1df1a664a..edb0e81fd 100644 --- a/app/models/chouette/stop_point.rb +++ b/app/models/chouette/stop_point.rb @@ -9,15 +9,14 @@ module Chouette include ForAlightingEnumerations include ObjectidSupport - belongs_to :stop_area belongs_to :route, inverse_of: :stop_points + has_many :journey_patterns, through: :route has_many :vehicle_journey_at_stops, :dependent => :destroy has_many :vehicle_journeys, -> {uniq}, :through => :vehicle_journey_at_stops acts_as_list :scope => :route, top_of_list: 0 - validates_presence_of :stop_area validate :stop_area_id_validation def stop_area_id_validation @@ -28,7 +27,7 @@ module Chouette scope :default_order, -> { order("position") } - delegate :name, to: :stop_area + delegate :name, :registration_number, :kind, :area_type, to: :stop_area before_destroy :remove_dependent_journey_pattern_stop_points def remove_dependent_journey_pattern_stop_points diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index b59c95665..29e3808e7 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -16,7 +16,7 @@ module Chouette end ransacker :unaccented_comment, formatter: ->(val){ val.parameterize } do - Arel.sql('unaccent(comment)') + Arel.sql('unaccent(time_tables.comment)') end has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney' @@ -81,6 +81,11 @@ module Chouette chunk.values.delete_if {|dates| dates.count < 2} end + def color + _color = read_attribute(:color) + _color.present? ? _color : nil + end + def convert_continuous_dates_to_periods chunks = self.continuous_dates diff --git a/app/models/chouette/trident_active_record.rb b/app/models/chouette/trident_active_record.rb index 18b7bbf9b..475589e13 100644 --- a/app/models/chouette/trident_active_record.rb +++ b/app/models/chouette/trident_active_record.rb @@ -7,6 +7,10 @@ module Chouette @referential ||= Referential.where(:slug => Apartment::Tenant.current).first! end + def workgroup + referential&.workgroup + end + def hub_restricted? referential.data_format == "hub" end @@ -16,4 +20,4 @@ module Chouette end end -end
\ No newline at end of file +end diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 54aad290c..8a29057e2 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -25,6 +25,7 @@ module Chouette has_and_belongs_to_many :footnotes, :class_name => 'Chouette::Footnote' has_and_belongs_to_many :purchase_windows, :class_name => 'Chouette::PurchaseWindow' + has_array_of :ignored_routing_contraint_zones, class_name: 'Chouette::RoutingConstraintZone' validates_presence_of :route validates_presence_of :journey_pattern @@ -140,9 +141,13 @@ module Chouette attrs << self.published_journey_identifier attrs << self.try(:company).try(:get_objectid).try(:local_id) attrs << self.footnotes.map(&:checksum).sort + vjas = self.vehicle_journey_at_stops vjas += VehicleJourneyAtStop.where(vehicle_journey_id: self.id) unless self.new_record? - attrs << vjas.uniq.sort_by { |s| s.stop_point&.position }.map(&:checksum).sort + attrs << vjas.uniq.sort_by { |s| s.stop_point&.position }.map(&:checksum) + + attrs << self.purchase_windows.map(&:checksum).sort if purchase_windows.present? + attrs << ignored_routing_contraint_zones.map(&:checksum).sort if ignored_routing_contraint_zones.present? end end @@ -244,11 +249,13 @@ module Chouette end def self.state_update route, state + objects = [] transaction do state.each do |item| item.delete('errors') vj = find_by(objectid: item['objectid']) || state_create_instance(route, item) next if item['deletable'] && vj.persisted? && vj.destroy + objects << vj if vj.state_update_vjas?(item['vehicle_journey_at_stops']) vj.update_vjas_from_state(item['vehicle_journey_at_stops']) @@ -276,6 +283,7 @@ module Chouette item['vehicle_journey_at_stops'].map {|vjas| vjas.delete('new_record') } end state.delete_if {|item| item['deletable']} + objects end def self.state_create_instance route, item @@ -293,7 +301,8 @@ module Chouette 'published_journey_identifier', 'published_journey_name', 'journey_pattern_id', - 'company_id' + 'company_id', + 'ignored_routing_contraint_zone_ids' ).to_hash if item['journey_pattern'] diff --git a/app/models/clean_up.rb b/app/models/clean_up.rb index ec47489e9..761fc47be 100644 --- a/app/models/clean_up.rb +++ b/app/models/clean_up.rb @@ -23,17 +23,29 @@ class CleanUp < ApplicationModel end def clean + referential.switch + {}.tap do |result| - processed = send("destroy_time_tables_#{self.date_type}") - if processed - result['time_table'] = processed[:time_tables].try(:count) - result['vehicle_journey'] = processed[:vehicle_journeys].try(:count) - end - result['time_table_date'] = send("destroy_time_tables_dates_#{self.date_type}").try(:count) - result['time_table_period'] = send("destroy_time_tables_periods_#{self.date_type}").try(:count) - self.overlapping_periods.each do |period| - exclude_dates_in_overlapping_period(period) + if date_type + processed = send("destroy_time_tables_#{self.date_type}") + if processed + result['time_table'] = processed[:time_tables].try(:count) + result['vehicle_journey'] = processed[:vehicle_journeys].try(:count) + end + result['time_table_date'] = send("destroy_time_tables_dates_#{self.date_type}").try(:count) + result['time_table_period'] = send("destroy_time_tables_periods_#{self.date_type}").try(:count) + self.overlapping_periods.each do |period| + exclude_dates_in_overlapping_period(period) + end end + + destroy_vehicle_journeys_outside_referential + # Disabled for the moment. See #5372 + # destroy_time_tables_outside_referential + + destroy_vehicle_journeys + destroy_journey_patterns + destroy_routes end end @@ -76,6 +88,30 @@ class CleanUp < ApplicationModel Chouette::TimeTablePeriod.where('period_start > ? AND period_end < ?', self.begin_date, self.end_date).destroy_all end + def destroy_time_tables_outside_referential + # For the moment, only timetable outside of metadatas min/max dates are removed + metadatas_period = referential.metadatas_period + time_tables = Chouette::TimeTable.where('end_date < ? or start_date > ?', metadatas_period.min, metadatas_period.max) + destroy_time_tables(time_tables) + end + + def destroy_vehicle_journeys_outside_referential + line_ids = referential.metadatas.pluck(:line_ids).flatten.uniq + Chouette::VehicleJourney.joins(:route).where(["routes.line_id not in (?)", line_ids]).destroy_all + end + + def destroy_vehicle_journeys + Chouette::VehicleJourney.where("id not in (select distinct vehicle_journey_id from time_tables_vehicle_journeys)").destroy_all + end + + def destroy_journey_patterns + Chouette::JourneyPattern.where("id not in (select distinct journey_pattern_id from vehicle_journeys)").destroy_all + end + + def destroy_routes + Chouette::Route.where("id not in (select distinct route_id from journey_patterns)").destroy_all + end + def overlapping_periods self.end_date = self.begin_date if self.date_type != 'between' Chouette::TimeTablePeriod.where('(period_start, period_end) OVERLAPS (?, ?)', self.begin_date, self.end_date) diff --git a/app/models/compliance_check_message_export.rb b/app/models/compliance_check_message_export.rb index bbaaa8e3f..9b7f90fac 100644 --- a/app/models/compliance_check_message_export.rb +++ b/app/models/compliance_check_message_export.rb @@ -22,7 +22,7 @@ class ComplianceCheckMessageExport end def column_names - ["criticity", "message key", "resource objectid", "link", "message"] + ["criticity", "message_key", "resource_objectid", "link", "message"].map {|c| ComplianceCheckMessage.tmf(c)} end def to_csv(options = {}) diff --git a/app/models/concerns/checksum_support.rb b/app/models/concerns/checksum_support.rb index 92103798e..fe52604bb 100644 --- a/app/models/concerns/checksum_support.rb +++ b/app/models/concerns/checksum_support.rb @@ -12,10 +12,16 @@ module ChecksumSupport module ClassMethods def has_checksum_children klass, opts={} parent_class = self - relation = opts[:relation] || self.model_name.singular + belongs_to = opts[:relation] || self.model_name.singular + has_many = opts[:relation] || self.model_name.plural + + Rails.logger.debug "Define callback in #{klass} to update checksums #{self.model_name} (via #{has_many}/#{belongs_to})" klass.after_save do - parent = self.send(relation) - parent&.update_checksum_without_callbacks! + parents = [] + parents << self.send(belongs_to) if klass.reflections[belongs_to].present? + parents += self.send(has_many) if klass.reflections[has_many].present? + Rails.logger.debug "Request from #{klass.name} checksum updates for #{parents.count} #{parent_class} parent(s)" + parents.compact.each &:update_checksum_without_callbacks! end end end @@ -40,6 +46,7 @@ module ChecksumSupport def current_checksum_source source = checksum_replace_nil_or_empty_values(self.checksum_attributes) + source += self.custom_fields_checksum if self.respond_to?(:custom_fields_checksum) source.map{ |item| if item.kind_of?(Array) item.map{ |x| x.kind_of?(Array) ? "(#{x.join(',')})" : x }.join(',') @@ -56,22 +63,24 @@ module ChecksumSupport def update_checksum if self.checksum_source_changed? self.checksum = Digest::SHA256.new.hexdigest(self.checksum_source) + Rails.logger.debug("Changed #{self.class.name}:#{id} checksum: #{self.checksum}") end end def update_checksum! - set_current_checksum_source - if checksum_source_changed? - update checksum: Digest::SHA256.new.hexdigest(checksum_source) - end + _checksum_source = current_checksum_source + update checksum_source: _checksum_source, checksum: Digest::SHA256.new.hexdigest(_checksum_source) + Rails.logger.debug("Updated #{self.class.name}:#{id} checksum: #{self.checksum}") end def update_checksum_without_callbacks! set_current_checksum_source _checksum = Digest::SHA256.new.hexdigest(checksum_source) + Rails.logger.debug("Compute checksum for #{self.class.name}:#{id} checksum_source:'#{checksum_source}' checksum: #{_checksum}") if _checksum != self.checksum self.checksum = _checksum - self.class.where(id: self.id).update_all(checksum: _checksum) unless self.new_record? + self.class.where(id: self.id).update_all(checksum: _checksum, checksum_source: checksum_source) unless self.new_record? + Rails.logger.debug("Updated #{self.class.name}:#{id} checksum: #{self.checksum}") end end end diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb index 46fc8e73d..6e744d550 100644 --- a/app/models/concerns/custom_fields_support.rb +++ b/app/models/concerns/custom_fields_support.rb @@ -5,18 +5,19 @@ module CustomFieldsSupport validate :custom_fields_values_are_valid after_initialize :initialize_custom_fields - def self.custom_fields workgroup=:all + def self.custom_fields workgroup + return [] unless workgroup fields = CustomField.where(resource_type: self.name.split("::").last) - fields = fields.where(workgroup_id: workgroup&.id) if workgroup != :all + fields = fields.where(workgroup_id: workgroup.id) fields end - def self.custom_fields_definitions workgroup=:all + def self.custom_fields_definitions workgroup Hash[*custom_fields(workgroup).map{|cf| [cf.code, cf]}.flatten] end def method_missing method_name, *args - if method_name =~ /custom_field_*/ && method_name.to_sym != :custom_field_values && !@custom_fields_initialized + if !@custom_fields_initialized && method_name =~ /custom_field_*/ && method_name.to_sym != :custom_field_values initialize_custom_fields send method_name, *args else @@ -24,26 +25,47 @@ module CustomFieldsSupport end end - def custom_fields workgroup=:all - CustomField::Collection.new self, workgroup + def custom_fields deprecated_workgroup=nil + _workgroup = deprecated_workgroup || self.workgroup + CustomField::Collection.new self, _workgroup + end + + def custom_fields_checksum + custom_fields.values.map(&:checksum) end def custom_field_values= vals - out = {} - custom_fields.each do |code, field| - out[code] = field.preprocess_value_for_assignment(vals.symbolize_keys[code.to_sym]) + if custom_fields_initialized? + out = {} + custom_fields.each do |code, field| + out[code] = field.preprocess_value_for_assignment(vals.symbolize_keys[code.to_sym]) + end + @custom_fields_values_initialized = true + else + out = vals end write_attribute :custom_field_values, out end + def custom_fields_initialized? + !!@custom_fields_initialized + end + + def custom_fields_values_initialized? + !!@custom_fields_values_initialized + end + def initialize_custom_fields + return if custom_fields_initialized? return unless self.attributes.has_key?("custom_field_values") + return unless self.workgroup.present? self.custom_field_values ||= {} - custom_fields(:all).values.each &:initialize_custom_field - custom_fields(:all).each do |k, v| + custom_fields.values.each &:initialize_custom_field + custom_fields.each do |k, v| custom_field_values[k] ||= v.default_value end @custom_fields_initialized = true + self.custom_field_values = self.custom_field_values unless custom_fields_values_initialized? end def custom_field_value key @@ -52,7 +74,7 @@ module CustomFieldsSupport private def custom_fields_values_are_valid - custom_fields(:all).values.all?{|cf| cf.valid?} + custom_fields.values.all?{|cf| cf.valid?} end end end diff --git a/app/models/concerns/line_referential_support.rb b/app/models/concerns/line_referential_support.rb index 5eade3557..b77a178eb 100644 --- a/app/models/concerns/line_referential_support.rb +++ b/app/models/concerns/line_referential_support.rb @@ -7,6 +7,10 @@ module LineReferentialSupport alias_method :referential, :line_referential end + def workgroup + line_referential&.workgroup + end + def hub_restricted? false end diff --git a/app/models/concerns/metadata_support.rb b/app/models/concerns/metadata_support.rb index c4bedbcda..182ab8310 100644 --- a/app/models/concerns/metadata_support.rb +++ b/app/models/concerns/metadata_support.rb @@ -66,6 +66,10 @@ module MetadataSupport "#{name}_updated_at".to_sym end + def as_json + @table.as_json + end + def method_missing(mid, *args) out = super(mid, *args) owner.send :write_attribute, attribute_name, @table diff --git a/app/models/concerns/stop_area_referential_support.rb b/app/models/concerns/stop_area_referential_support.rb index f29397b3a..fb9edd7d5 100644 --- a/app/models/concerns/stop_area_referential_support.rb +++ b/app/models/concerns/stop_area_referential_support.rb @@ -7,6 +7,10 @@ module StopAreaReferentialSupport alias_method :referential, :stop_area_referential end + def workgroup + stop_area_referential&.workgroup + end + def hub_restricted? false end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 22118a15a..15aee9a41 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -7,12 +7,8 @@ class CustomField < ApplicationModel validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]} validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}, presence: true - scope :for_workgroup, ->(workgroup){ where workgroup_id: workgroup.id } - - scope :for_workgroup, ->(workgroup){ where workgroup_id: workgroup.id } - class Collection < HashWithIndifferentAccess - def initialize object, workgroup=:all + def initialize object, workgroup=nil vals = object.class.custom_fields(workgroup).map do |v| [v.code, CustomField::Instance.new(object, v, object.custom_field_value(v.code))] end @@ -67,6 +63,10 @@ class CustomField < ApplicationModel @raw_value end + def checksum + @raw_value + end + def input form_helper @input ||= begin klass_name = field_type && "CustomField::Instance::#{field_type.classify}::Input" @@ -147,6 +147,10 @@ class CustomField < ApplicationModel @raw_value&.to_i end + def preprocess_value_for_assignment val + val&.to_i + end + def validate @valid = true return if @raw_value.is_a?(Fixnum) || @raw_value.is_a?(Float) @@ -167,6 +171,10 @@ class CustomField < ApplicationModel end end + def preprocess_value_for_assignment val + val + end + def display_value return unless value k = options["list_values"].is_a?(Hash) ? value.to_s : value.to_i @@ -191,9 +199,10 @@ class CustomField < ApplicationModel custom_field_code = self.code _attr_name = attr_name _uploader_name = uploader_name + _digest_name = digest_name owner.send :define_singleton_method, "read_uploader" do |attr| if attr.to_s == _attr_name - custom_field_values[custom_field_code] + custom_field_values[custom_field_code] && custom_field_values[custom_field_code]["path"] else read_attribute attr end @@ -201,16 +210,28 @@ class CustomField < ApplicationModel owner.send :define_singleton_method, "write_uploader" do |attr, val| if attr.to_s == _attr_name - custom_field_values[custom_field_code] = val + self.custom_field_values[custom_field_code] ||= {} + self.custom_field_values[custom_field_code]["path"] = val + self.custom_field_values[custom_field_code]["digest"] = self.send _digest_name else write_attribute attr, val end end owner.send :define_singleton_method, "#{_attr_name}_will_change!" do + self.send "#{_digest_name}=", nil custom_field_values_will_change! end + owner.send :define_singleton_method, _digest_name do + val = instance_variable_get "@#{_digest_name}" + if val.nil? && (file = send(_uploader_name)).present? + val = CustomField::Instance::Attachment.digest(file) + instance_variable_set "@#{_digest_name}", val + end + val + end + _extension_whitelist = options["extension_whitelist"] owner.send :define_singleton_method, "#{_uploader_name}_extension_whitelist" do @@ -219,7 +240,15 @@ class CustomField < ApplicationModel unless owner.class.uploaders.has_key? _uploader_name.to_sym owner.class.mount_uploader _uploader_name, CustomFieldAttachmentUploader, mount_on: "custom_field_#{code}_raw_value" + owner.class.send :attr_accessor, _digest_name end + + digest = @raw_value && @raw_value["digest"] + owner.send "#{_digest_name}=", digest + end + + def self.digest file + Digest::SHA256.file(file.path).hexdigest end def preprocess_value_for_assignment val @@ -230,6 +259,10 @@ class CustomField < ApplicationModel end end + def checksum + owner.send digest_name + end + def value owner.send "custom_field_#{code}" end @@ -246,6 +279,10 @@ class CustomField < ApplicationModel "custom_field_#{code}" end + def digest_name + "#{uploader_name}_digest" + end + def display_value render_partial end diff --git a/app/models/import/gtfs.rb b/app/models/import/gtfs.rb index 70f448132..a20c468c1 100644 --- a/app/models/import/gtfs.rb +++ b/app/models/import/gtfs.rb @@ -56,12 +56,16 @@ class Import::Gtfs < Import::Base attr_accessor :download_host def download_host - @download_host ||= Rails.application.config.rails_host.gsub("http://","") + @download_host ||= Rails.application.config.rails_host end def local_temp_directory - Rails.application.config.try(:import_temporary_directory) || - Rails.root.join('tmp', 'imports') + @local_temp_directory ||= + begin + directory = Rails.application.config.try(:import_temporary_directory) || Rails.root.join('tmp', 'imports') + FileUtils.mkdir_p directory + directory + end end def local_temp_file(&block) @@ -75,11 +79,20 @@ class Import::Gtfs < Import::Base Rails.application.routes.url_helpers.download_workbench_import_path(workbench, id, token: token_download) end + def download_uri + @download_uri ||= + begin + host = download_host + host = "http://#{host}" unless host =~ %r{https?://} + URI.join(host, download_path) + end + end + def download_local_file local_temp_file do |file| begin - Net::HTTP.start(download_host) do |http| - http.request_get(download_path) do |response| + Net::HTTP.start(download_uri.host, download_uri.port) do |http| + http.request_get(download_uri.request_uri) do |response| response.read_body do |segment| file.write segment end diff --git a/app/models/import/message_export.rb b/app/models/import/message_export.rb index 7a7add08c..7d03783ed 100644 --- a/app/models/import/message_export.rb +++ b/app/models/import/message_export.rb @@ -22,7 +22,7 @@ class Import::MessageExport end def column_names - ["criticity", "message key", "message", "file name", "line", "column"] + ["criticity", "message_key", "message", "filename", "line", "column"].map {|c| Import::Message.tmf(c)} end def to_csv(options = {}) diff --git a/app/models/import/netex.rb b/app/models/import/netex.rb index 93604c5f9..f19fde435 100644 --- a/app/models/import/netex.rb +++ b/app/models/import/netex.rb @@ -10,7 +10,7 @@ class Import::Netex < Import::Base validates_presence_of :parent - def create_referential! + def create_with_referential! self.referential = Referential.new( name: self.name, @@ -19,15 +19,16 @@ class Import::Netex < Import::Base metadatas: [referential_metadata] ) self.referential.save - unless self.referential.valid? + if self.referential.invalid? Rails.logger.info "Can't create referential for import #{self.id}: #{referential.inspect} #{referential.metadatas.inspect} #{referential.errors.messages}" - if referential.metadatas.all?{|m| m.line_ids.empty?} + if referential.metadatas.all?{|m| m.line_ids.present? && m.line_ids.empty?} parent.messages.create criticity: :error, message_key: "referential_creation_missing_lines", message_attributes: {referential_name: referential.name} else parent.messages.create criticity: :error, message_key: "referential_creation", message_attributes: {referential_name: referential.name} end + else + save! end - save! end private @@ -45,7 +46,7 @@ class Import::Netex < Import::Base def referential_metadata metadata = ReferentialMetadata.new - if self.file + if self.file && self.file.path netex_file = STIF::NetexFile.new(self.file.path) frame = netex_file.frames.first diff --git a/app/models/merge.rb b/app/models/merge.rb index 6e2a7036a..be1bbedcb 100644 --- a/app/models/merge.rb +++ b/app/models/merge.rb @@ -50,7 +50,7 @@ class Merge < ApplicationModel new = if workbench.output.current Rails.logger.debug "Clone current output" - Referential.new_from(workbench.output.current, fixme_functional_scope).tap do |clone| + Referential.new_from(workbench.output.current, workbench.organisation).tap do |clone| clone.inline_clone = true end else @@ -135,6 +135,14 @@ class Merge < ApplicationModel referential_stop_points_by_route = referential_stop_points.group_by(&:route_id) + referential_routing_constraint_zones = referential.switch do + referential.routing_constraint_zones.each_with_object(Hash.new { |h,k| h[k] = {}}) do |routing_constraint_zone, hash| + hash[routing_constraint_zone.route_id][routing_constraint_zone.checksum] = routing_constraint_zone + end + end + + referential_routing_constraint_zones_new_ids = {} + new.switch do referential_routes.each do |route| existing_route = new.routes.find_by line_id: route.line_id, checksum: route.checksum @@ -164,11 +172,53 @@ class Merge < ApplicationModel new_route.stop_points.build attributes end + # We need to create StopPoints to known new primary keys + new_route.save! + + old_stop_point_ids = route_stop_points.sort_by(&:position).map(&:id) + new_stop_point_ids = new_route.stop_points.sort_by(&:position).map(&:id) + + stop_point_ids_mapping = Hash[[old_stop_point_ids, new_stop_point_ids].transpose] + + # RoutingConstraintZones + routing_constraint_zones = referential_routing_constraint_zones[route.id] + + routing_constraint_zones.values.each do |routing_constraint_zone| + objectid = new.routing_constraint_zones.where(objectid: routing_constraint_zone.objectid).exists? ? nil : routing_constraint_zone.objectid + stop_point_ids = routing_constraint_zone.stop_point_ids.map { |id| stop_point_ids_mapping[id] }.compact + + if stop_point_ids.size != routing_constraint_zone.stop_point_ids.size + raise "Can't find all required StopPoints for RoutingConstraintZone #{routing_constraint_zone.inspect}" + end + + attributes = routing_constraint_zone.attributes.merge( + id: nil, + route_id: nil, + objectid: objectid, + stop_point_ids: stop_point_ids, + ) + new_route.routing_constraint_zones.build attributes + + end + new_route.save! if new_route.checksum != route.checksum raise "Checksum has changed: \"#{route.checksum}\", \"#{route.checksum_source}\" -> \"#{new_route.checksum}\", \"#{new_route.checksum_source}\"" end + + if new_route.routing_constraint_zones.map(&:checksum).sort != routing_constraint_zones.keys.sort + raise "Checksum has changed in RoutingConstraintZones: \"#{new_route.routing_constraint_zones.map(&:checksum).sort}\" -> \"#{route.routing_constraint_zones.map(&:checksum).sort}\"" + end + + new_route.routing_constraint_zones.each do |new_routing_constraint_zone| + routing_constraint_zone = routing_constraint_zones[new_routing_constraint_zone.checksum] + if routing_constraint_zone + referential_routing_constraint_zones_new_ids[routing_constraint_zone.id] = new_routing_constraint_zone.id + else + raise "Can't find RoutingConstraintZone for checksum #{new_routing_constraint_zone.checksum} into #{routing_constraint_zones.inspect}" + end + end end end end @@ -224,7 +274,7 @@ class Merge < ApplicationModel new_journey_pattern = new.journey_patterns.create! attributes if new_journey_pattern.checksum != journey_pattern.checksum - raise "Checksum has changed for #{journey_pattern.inspect}: \"#{journey_pattern.checksum_source}\" -> \"#{new_journey_pattern.checksum_source}\"" + raise "Checksum has changed for #{journey_pattern.inspect} (to #{new_journey_pattern.inspect}): \"#{journey_pattern.checksum_source}\" -> \"#{new_journey_pattern.checksum_source}\"" end end end @@ -236,17 +286,36 @@ class Merge < ApplicationModel referential.vehicle_journeys.includes(:vehicle_journey_at_stops).all.to_a end + referential_purchase_windows_by_checksum, referential_vehicle_journey_purchase_window_checksums = referential.switch do + purchase_windows_by_checksum = referential.purchase_windows.each_with_object({}) do |purchase_window, hash| + hash[purchase_window.checksum] = purchase_window + end + + vehicle_journey_purchase_window_checksums = Hash.new { |h,k| h[k] = [] } + referential.purchase_windows.joins(:vehicle_journeys).pluck("vehicle_journeys.id", :checksum).each do |vehicle_journey_id, checksum| + vehicle_journey_purchase_window_checksums[vehicle_journey_id] << checksum + end + + [purchase_windows_by_checksum, vehicle_journey_purchase_window_checksums] + end + + new_vehicle_journey_ids = {} + new.switch do referential_vehicle_journeys.each do |vehicle_journey| # find parent journey pattern by checksum # TODO add line_id for security + associated_route_checksum = referential_routes_checksums[vehicle_journey.route_id] associated_journey_pattern_checksum = referential_journey_patterns_checksums[vehicle_journey.journey_pattern_id] - existing_associated_journey_pattern = new.journey_patterns.find_by checksum: associated_journey_pattern_checksum + + existing_associated_route = new.routes.find_by checksum: associated_route_checksum + existing_associated_journey_pattern = existing_associated_route.journey_patterns.find_by checksum: associated_journey_pattern_checksum existing_vehicle_journey = new.vehicle_journeys.find_by journey_pattern_id: existing_associated_journey_pattern.id, checksum: vehicle_journey.checksum if existing_vehicle_journey existing_vehicle_journey.merge_metadata_from vehicle_journey + new_vehicle_journey_ids[vehicle_journey.id] = existing_vehicle_journey.id else objectid = Chouette::VehicleJourney.where(objectid: vehicle_journey.objectid).exists? ? nil : vehicle_journey.objectid attributes = vehicle_journey.attributes.merge( @@ -256,6 +325,7 @@ class Merge < ApplicationModel # all other primary must be changed route_id: existing_associated_journey_pattern.route_id, journey_pattern_id: existing_associated_journey_pattern.id, + ignored_routing_contraint_zone_ids: [] ) new_vehicle_journey = new.vehicle_journeys.build attributes @@ -269,11 +339,41 @@ class Merge < ApplicationModel new_vehicle_journey.vehicle_journey_at_stops.build at_stop_attributes end + # Associate (and create if needed) PurchaseWindows + + referential_vehicle_journey_purchase_window_checksums[vehicle_journey.id].each do |purchase_window_checksum| + associated_purchase_window = new.purchase_windows.find_by(checksum: purchase_window_checksum) + + unless associated_purchase_window + purchase_window = referential_purchase_windows_by_checksum[purchase_window_checksum] + + objectid = new.purchase_windows.where(objectid: purchase_window.objectid).exists? ? nil : purchase_window.objectid + attributes = purchase_window.attributes.merge( + id: nil, + objectid: objectid + ) + new_purchase_window = new.purchase_windows.build attributes + new_purchase_window.save! + + if new_purchase_window.checksum != purchase_window.checksum + raise "Checksum has changed: #{purchase_window.checksum_source} #{new_purchase_window.checksum_source}" + end + + associated_purchase_window = new_purchase_window + end + + new_vehicle_journey.purchase_windows << associated_purchase_window + end + + # Rewrite ignored_routing_contraint_zone_ids + new_vehicle_journey.ignored_routing_contraint_zone_ids = referential_routing_constraint_zones_new_ids.values_at(*vehicle_journey.ignored_routing_contraint_zone_ids).compact new_vehicle_journey.save! if new_vehicle_journey.checksum != vehicle_journey.checksum - raise "Checksum has changed: #{vehicle_journey.checksum_source} #{new_vehicle_journey.checksum_source}" + raise "Checksum has changed: \"#{vehicle_journey.checksum_source}\" \"#{vehicle_journey.checksum}\" -> \"#{new_vehicle_journey.checksum_source}\" \"#{new_vehicle_journey.checksum}\"" end + + new_vehicle_journey_ids[vehicle_journey.id] = new_vehicle_journey.id end end @@ -285,14 +385,14 @@ class Merge < ApplicationModel time_tables_by_id = Hash[referential.time_tables.includes(:dates, :periods).all.to_a.map { |t| [t.id, t] }] time_tables_with_associated_lines = - referential.time_tables.joins(vehicle_journeys: {route: :line}).pluck("lines.id", :id, "vehicle_journeys.checksum") + referential.time_tables.joins(vehicle_journeys: {route: :line}).pluck("lines.id", :id, "vehicle_journeys.id") # Because TimeTables will be modified according metadata periods # we're loading timetables per line (line is associated to a period list) # # line_id: [ { time_table.id, vehicle_journey.checksum } ] time_tables_by_lines = time_tables_with_associated_lines.inject(Hash.new { |h,k| h[k] = [] }) do |hash, row| - hash[row.shift] << {id: row.first, vehicle_journey_checksum: row.second} + hash[row.shift] << {id: row.first, vehicle_journey_id: row.second} hash end @@ -362,7 +462,12 @@ class Merge < ApplicationModel # associate VehicleJourney - associated_vehicle_journey = line.vehicle_journeys.find_by!(checksum: properties[:vehicle_journey_checksum]) + new_vehicle_journey_id = new_vehicle_journey_ids[properties[:vehicle_journey_id]] + unless new_vehicle_journey_id + raise "TimeTable #{existing_time_table.inspect} associated to a not-merged VehicleJourney: #{properties[:vehicle_journey_id]}" + end + + associated_vehicle_journey = line.vehicle_journeys.find(new_vehicle_journey_id) associated_vehicle_journey.time_tables << existing_time_table end end @@ -371,7 +476,7 @@ class Merge < ApplicationModel def save_current output.update current: new, new: nil - output.current.update referential_suite: output + output.current.update referential_suite: output, ready: true referentials.update_all merged_at: created_at, archived_at: created_at end diff --git a/app/models/referential.rb b/app/models/referential.rb index 1794126a2..78b719fab 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -78,31 +78,29 @@ class Referential < ApplicationModel alias_method_chain :save, :table_lock_timeout - if Rails.env.development? - def self.force_register_models_with_checksum - paths = Rails.application.paths['app/models'].to_a - Rails.application.railties.each do |tie| - next unless tie.respond_to? :paths - paths += tie.paths['app/models'].to_a - end + def self.force_register_models_with_checksum + paths = Rails.application.paths['app/models'].to_a + Rails.application.railties.each do |tie| + next unless tie.respond_to? :paths + paths += tie.paths['app/models'].to_a + end - paths.each do |path| - next unless File.directory?(path) - Dir.chdir path do - Dir['**/*.rb'].each do |src| - next if src =~ /^concerns/ - # thanks for inconsistent naming ... - if src == "route_control/zdl_stop_area.rb" - RouteControl::ZDLStopArea - next - end - Rails.logger.info "Loading #{src}" - begin - src[0..-4].classify.safe_constantize - rescue => e - Rails.logger.info "Failed: #{e.message}" - nil - end + paths.each do |path| + next unless File.directory?(path) + Dir.chdir path do + Dir['**/*.rb'].each do |src| + next if src =~ /^concerns/ + # thanks for inconsistent naming ... + if src == "route_control/zdl_stop_area.rb" + RouteControl::ZDLStopArea + next + end + Rails.logger.info "Loading #{src}" + begin + src[0..-4].classify.safe_constantize + rescue => e + Rails.logger.info "Failed: #{e.message}" + nil end end end diff --git a/app/models/referential_cloning.rb b/app/models/referential_cloning.rb index f2c81009a..6e102a807 100644 --- a/app/models/referential_cloning.rb +++ b/app/models/referential_cloning.rb @@ -25,6 +25,11 @@ class ReferentialCloning < ApplicationModel .clone_schema end target_referential.check_migration_count(report) + clean + end + + def clean + CleanUp.new(referential: target_referential).clean end private diff --git a/app/models/referential_metadata.rb b/app/models/referential_metadata.rb index 7a8a01774..a4e6333d7 100644 --- a/app/models/referential_metadata.rb +++ b/app/models/referential_metadata.rb @@ -44,8 +44,8 @@ class ReferentialMetadata < ApplicationModel validate :check_end_greather_than_begin def check_end_greather_than_begin - if self.begin and self.end and self.begin >= self.end - errors.add(:base, I18n.t('referentials.errors.short_period')) + if self.begin and self.end and self.begin > self.end + errors.add(:base, I18n.t('referentials.errors.invalid_period')) end end diff --git a/app/policies/merge_policy.rb b/app/policies/merge_policy.rb index 82eb72e08..154dc63f5 100644 --- a/app/policies/merge_policy.rb +++ b/app/policies/merge_policy.rb @@ -8,8 +8,4 @@ class MergePolicy < ApplicationPolicy def create? user.has_permission?('merges.create') end - - def update? - user.has_permission?('merges.update') - end end diff --git a/app/views/api/v1/journey_patterns/show.rabl b/app/views/api/v1/journey_patterns/show.rabl index aac66b6f3..d02781cfa 100644 --- a/app/views/api/v1/journey_patterns/show.rabl +++ b/app/views/api/v1/journey_patterns/show.rabl @@ -1,7 +1,7 @@ object @journey_pattern extends "api/v1/trident_objects/show" -[:id, :name, :published_name, :registration_number, :comment, :checksum].each do |attr| +[:id, :name, :published_name, :registration_number, :comment, :checksum, :custom_fields].each do |attr| attributes attr, :unless => lambda { |m| m.send( attr).nil?} end diff --git a/app/views/companies/_form.html.slim b/app/views/companies/_form.html.slim index e8b3fcede..4aa08e267 100644 --- a/app/views/companies/_form.html.slim +++ b/app/views/companies/_form.html.slim @@ -9,10 +9,10 @@ = f.input :phone = f.input :fax = f.input :email, as: :email - = f.input :time_zone, include_blank: true + = f.input :time_zone, as: :full_time_zone, include_blank: true = f.input :url = f.input :registration_number, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.company.registration_number")} - - if resource.custom_fields(current_referential.workgroup).any? + - if resource.custom_fields.present? - resource.custom_fields.each do |code, field| = field.input(f).to_s .separator diff --git a/app/views/compliance_control_blocks/_form.html.slim b/app/views/compliance_control_blocks/_form.html.slim index 2e87a877e..e8ae63384 100644 --- a/app/views/compliance_control_blocks/_form.html.slim +++ b/app/views/compliance_control_blocks/_form.html.slim @@ -1,6 +1,12 @@ = simple_form_for [@compliance_control_set, @compliance_control_block], html: { class: 'form-horizontal', id: 'compliance_control_block_form' }, wrapper: :horizontal_form do |f| .row .col-lg-12 + - if @compliance_control_block.errors.has_key? :condition_attributes + .row.condition-attributes-errors + .col-lg-12 + .alert.alert-danger + - @compliance_control_block.errors[:condition_attributes].each do |msg| + p.small = "- #{msg}" .form-group = f.input :transport_mode, as: :select, collection: ComplianceControlBlock.sorted_transport_modes, label: t('activerecord.attributes.compliance_control_blocks.transport_mode'), label_method: lambda {|t| ("<span>" + t("enumerize.transport_mode.#{t}") + "</span>").html_safe } = f.input :transport_submode, as: :select, collection: ComplianceControlBlock.sorted_transport_submodes, label: t('activerecord.attributes.compliance_control_blocks.transport_submode'), label_method: lambda {|t| ("<span>" + t("enumerize.transport_submode.#{t}") + "</span>").html_safe } diff --git a/app/views/journey_patterns_collections/show.html.slim b/app/views/journey_patterns_collections/show.html.slim index b389a1da7..38c7f1b1b 100644 --- a/app/views/journey_patterns_collections/show.html.slim +++ b/app/views/journey_patterns_collections/show.html.slim @@ -20,5 +20,6 @@ | window.perms = #{raw @perms}; | window.features = #{raw @features}; | window.routeCostsUrl = "#{costs_referential_line_route_url(@referential, @route.line, @route, format: :json).html_safe}"; - + | window.custom_fields = #{(@custom_fields.to_json).html_safe}; + = javascript_pack_tag 'journey_patterns/index.js' diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index 3921c8701..abf39c089 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -17,7 +17,11 @@ html lang=I18n.locale | I18n.locale = '#{I18n.locale}' body - = render 'layouts/navigation/main_nav' + nav#main_nav + // Left menu content + = render 'layouts/navigation/main_nav_left' + // Top menu content + = render 'layouts/navigation/main_nav_top' = render 'layouts/flash_messages', flash: flash = render 'layouts/navigation/page_header' = yield diff --git a/app/views/layouts/devise.html.slim b/app/views/layouts/devise.html.slim new file mode 100644 index 000000000..34a3a3f11 --- /dev/null +++ b/app/views/layouts/devise.html.slim @@ -0,0 +1,28 @@ +doctype html +html lang=I18n.locale + head + meta charset="utf-8" + meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" + + = csrf_meta_tag + + title = t('brandname') + + = stylesheet_link_tag 'base' + = stylesheet_link_tag 'application' + + = javascript_pack_tag 'application' + = javascript_include_tag 'application' + = javascript_tag do + | I18n.locale = '#{I18n.locale}' + + body + nav#main_nav + // Top menu content + = render 'layouts/navigation/main_nav_top' + = render 'layouts/flash_messages', flash: flash + = render 'layouts/navigation/page_header' + = yield + + = render 'shared/development_toolbar' + = yield :javascript diff --git a/app/views/layouts/navigation/_main_nav.html.slim b/app/views/layouts/navigation/_main_nav.html.slim deleted file mode 100644 index 806290223..000000000 --- a/app/views/layouts/navigation/_main_nav.html.slim +++ /dev/null @@ -1,6 +0,0 @@ -nav#main_nav - // Left menu content - = render 'layouts/navigation/main_nav_left' - - // Top menu content - = render 'layouts/navigation/main_nav_top' diff --git a/app/views/layouts/navigation/_main_nav_left_content.html.slim b/app/views/layouts/navigation/_main_nav_left_content.html.slim index e69de29bb..0b55578a7 100644 --- a/app/views/layouts/navigation/_main_nav_left_content.html.slim +++ b/app/views/layouts/navigation/_main_nav_left_content.html.slim @@ -0,0 +1,64 @@ + +- current_organisation.workbenches.each do |workbench| + #menu-items.panel-group + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miOne', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + = t('layouts.navbar.current_offer.other') + + #miOne.panel-collapse.collapse + .list-group + = link_to root_path, class: "list-group-item" do + span = t('layouts.navbar.dashboard') + = link_to workbench_output_path(workbench), class: 'list-group-item' do + span = t('layouts.navbar.workbench_outputs.organisation') + = link_to '#', class: 'list-group-item disabled' do + span = t('layouts.navbar.workbench_outputs.workgroup') + + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miTwo', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + - t('activerecord.models.workbench.one').capitalize + + #miTwo.panel-collapse.collapse + .list-group + = link_to workbench_path(workbench), class: "list-group-item" do + span = t('activerecord.models.referential.other').capitalize + = link_to workbench_imports_path(workbench), class: "list-group-item" do + span = t('activerecord.models.import.other').capitalize + = link_to workbench_exports_path(workbench), class: "list-group-item" do + span = t('activerecord.models.export.other').capitalize + = link_to workgroup_calendars_path(workbench.workgroup), class: 'list-group-item' do + span = t('activerecord.models.calendar.other').capitalize + = link_to workbench_compliance_check_sets_path(workbench), class: 'list-group-item' do + span = t('activerecord.models.compliance_check_set.other').capitalize + = link_to compliance_control_sets_path, class: 'list-group-item' do + span = t('activerecord.models.compliance_control_set.other').capitalize + + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miFour', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + = t('layouts.navbar.line_referential') + + #miFour.panel-collapse.collapse + .list-group + = link_to line_referential_lines_path(workbench.line_referential), class: "list-group-item" do + span = Chouette::Line.t.capitalize + = link_to line_referential_networks_path(workbench.line_referential), class: "list-group-item" do + span = Chouette::Network.t.capitalize + = link_to line_referential_companies_path(workbench.line_referential), class: "list-group-item" do + span = Chouette::Company.t.capitalize + + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miFive', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + = t('layouts.navbar.stop_area_referential') + + #miFive.panel-collapse.collapse + .list-group + = link_to stop_area_referential_stop_areas_path(workbench.stop_area_referential), class: "list-group-item" do + span = Chouette::StopArea.t.capitalize diff --git a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim index 02614dcab..9404eeae6 100644 --- a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim +++ b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim @@ -1,109 +1,95 @@ -- @localizationUrl = "#{params[:controller]}##{params[:action]}" - -#menu-items.panel-group - .menu-item.panel - .panel-heading - h4.panel-title - = link_to '#miOne', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do - |Offres courantes - - #miOne.panel-collapse.collapse - .list-group - = link_to root_path, class: "list-group-item #{(@localizationUrl == 'workbenches#index') ? 'active' : ''}" do - span Tableau de bord - = link_to '#', class: 'list-group-item' do - span Offre de mon organisation - = link_to '#', class: 'list-group-item' do - span Offre IDF - - .menu-item.panel - .panel-heading - h4.panel-title - = link_to '#miTwo', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do - |Espace de travail - - #miTwo.panel-collapse.collapse - .list-group - - current_user.workbenches.each do |current_workbench| - = link_to workbench_path(current_workbench), class: "list-group-item #{params[:controller] == 'workbenches' ? 'active' : ''}" do - span Jeux de données - = link_to workbench_imports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do - span Import - = link_to workbench_exports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'exports') ? 'active' : ''}" do - span Export - = link_to workgroup_calendars_path(current_workbench.workgroup), class: 'list-group-item' do - span Modèles de calendrier - = link_to workbench_compliance_check_sets_path(current_workbench), class: 'list-group-item' do - span Rapport de contrôle +- current_organisation.workbenches.each do |workbench| + #menu-items.panel-group + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miOne', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + = t('layouts.navbar.current_offer.other') + + #miOne.panel-collapse.collapse + .list-group + = link_to root_path, class: "list-group-item" do + span = t('layouts.navbar.dashboard') + = link_to workbench_output_path(workbench), class: 'list-group-item' do + span = t('layouts.navbar.workbench_outputs.organisation') + = link_to '#', class: 'list-group-item disabled' do + span = t('layouts.navbar.workbench_outputs.workgroup') + + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miTwo', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + - t('activerecord.models.workbench.one').capitalize + + #miTwo.panel-collapse.collapse + .list-group + = link_to workbench_path(workbench), class: "list-group-item" do + span = t('activerecord.models.referential.other').capitalize + = link_to workbench_imports_path(workbench), class: "list-group-item" do + span = t('activerecord.models.import.other').capitalize + = link_to workbench_exports_path(workbench), class: "list-group-item" do + span = t('activerecord.models.export.other').capitalize + = link_to workgroup_calendars_path(workbench.workgroup), class: 'list-group-item' do + span = t('activerecord.models.calendar.other').capitalize + = link_to workbench_compliance_check_sets_path(workbench), class: 'list-group-item' do + span = t('activerecord.models.compliance_check_set.other').capitalize = link_to compliance_control_sets_path, class: 'list-group-item' do - span Jeux de contrôle + span = t('activerecord.models.compliance_control_set.other').capitalize - .menu-item.panel - .panel-heading - h4.panel-title - = link_to '#miThree', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do - |Données + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miFour', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + = t('layouts.navbar.line_referential') - #miThree.panel-collapse.collapse - - if @referential.try(:id) && respond_to?(:current_referential) + #miFour.panel-collapse.collapse .list-group - .list-group-item - = (current_referential.name).upcase - .list-group - = link_to referential_networks_path(current_referential), class: 'list-group-item' do - span = t('networks.index.title') - - = link_to referential_companies_path(current_referential), class: 'list-group-item' do - span = t('companies.index.title') - - = link_to '#', class: 'list-group-item disabled' do - span Tracés - - = link_to referential_time_tables_path(current_referential), class: 'list-group-item' do - span = t('time_tables.index.title') - - - else - .panel-body - em.text-muted - = "Sélectionnez un jeu de données pour accéder à plus de fonctionnalités" - - .menu-item.panel - .panel-heading - h4.panel-title - = link_to '#miFour', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do - |Synchronisation - - #miFour.panel-collapse.collapse - .list-group - = link_to line_referential_path(1), class: "list-group-item #{(@localizationUrl == 'line_referentials#show') ? 'active' : ''}" do - span Synchronisation iLICO - = link_to stop_area_referential_path(1), class: "list-group-item #{(@localizationUrl == 'stop_area_referentials#show') ? 'active' : ''}" do - span Synchronisation iCAR - - .menu-item.panel - .panel-heading - h4.panel-title - = link_to '#miFive', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do - |Outils - - #miFive.panel-collapse.collapse - .list-group - = link_to Rails.application.config.try(:portal_url), target: '_blank', class: 'list-group-item' do - span - span.fa.fa-2x.fa-circle - |Portail (POSTIF) - - = link_to Rails.application.config.try(:codifligne_url), target: '_blank', class: 'list-group-item' do - span - span.fa.fa-2x.fa-circle - |iLICO - - = link_to Rails.application.config.try(:reflex_url), target: '_blank', class: 'list-group-item' do - span - span.fa.fa-2x.fa-circle - |iCAR - - = link_to '#', target: '_blank', class: 'list-group-item' do - span - span.fa.fa-2x.fa-circle - |Support + = link_to line_referential_path(workbench.line_referential), class: "list-group-item" do + span = t('layouts.navbar.sync_ilico') + = link_to line_referential_lines_path(workbench.line_referential), class: "list-group-item" do + span = Chouette::Line.t.capitalize + = link_to line_referential_networks_path(workbench.line_referential), class: "list-group-item" do + span = Chouette::Network.t.capitalize + = link_to line_referential_companies_path(workbench.line_referential), class: "list-group-item" do + span = Chouette::Company.t.capitalize + + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miFive', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + = t('layouts.navbar.stop_area_referential') + + #miFive.panel-collapse.collapse + .list-group + = link_to stop_area_referential_path(workbench.stop_area_referential), class: "list-group-item" do + span = t('layouts.navbar.sync_icar') + = link_to stop_area_referential_stop_areas_path(workbench.stop_area_referential), class: "list-group-item" do + span = Chouette::StopArea.t.capitalize + + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miSix', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + = t('layouts.navbar.tools') + + #miSix.panel-collapse.collapse + .list-group + = link_to Rails.application.config.try(:portal_url), target: '_blank', class: 'list-group-item' do + span + span.fa.fa-2x.fa-circle + = t('layouts.navbar.portal') + + = link_to Rails.application.config.try(:codifligne_url), target: '_blank', class: 'list-group-item' do + span + span.fa.fa-2x.fa-circle + = t('layouts.navbar.ilico') + + = link_to Rails.application.config.try(:reflex_url), target: '_blank', class: 'list-group-item' do + span + span.fa.fa-2x.fa-circle + = t('layouts.navbar.icar') + + = link_to '#', target: '_blank', class: 'list-group-item' do + span + span.fa.fa-2x.fa-circle + = t('layouts.navbar.support') diff --git a/app/views/line_referentials/show.html.slim b/app/views/line_referentials/show.html.slim index 763eb076e..1f184b6f2 100644 --- a/app/views/line_referentials/show.html.slim +++ b/app/views/line_referentials/show.html.slim @@ -1,47 +1,29 @@ - breadcrumb :line_referential, @line_referential - page_header_content_for @line_referential -- if policy(@line_referential).synchronize? - - content_for :page_header_actions do - = link_to(t('actions.sync'), sync_line_referential_path(@line_referential), method: :post, class: 'btn btn-default') - -- content_for :page_header_content do - .row.mb-md - .col-lg-12.text-right - = link_to line_referential_companies_path(@line_referential), class: 'btn btn-primary' do - = Referential.human_attribute_name(:companies) - em.small = " (#{@line_referential.companies.size})" - = link_to line_referential_networks_path(@line_referential), class: 'btn btn-primary' do - = Referential.human_attribute_name(:networks) - em.small = " (#{@line_referential.networks.size})" - = link_to line_referential_lines_path(@line_referential), class: 'btn btn-primary' do - = Referential.human_attribute_name(:lines) - em.small = " (#{@line_referential.lines.size})" .page_content .container-fluid .row .col-lg-12 - - unless @line_referential.line_referential_syncs.empty? - table.table - thead - tr - th = t('.synchronized') - th = t('.status') - th = t('.message') + = table_builder_2 @line_referential.line_referential_syncs, + [ \ + TableBuilderHelper::Column.new( \ + name: t('.synchronized'), \ + attribute: Proc.new { |sync| line_referential_sync_created_at(sync) }, \ + ), \ + TableBuilderHelper::Column.new( \ + name: t('.status'), \ + attribute: Proc.new { |sync| line_referential_sync_status(sync) }, \ + ), \ + TableBuilderHelper::Column.new( \ + name: t('.message'), \ + attribute: Proc.new { |sync| line_referential_sync_message(sync) }, \ + ), \ + ], + sortable: false, + cls: 'table' - tbody - - @line_referential.line_referential_syncs.each_with_index do |sync, i| - / Display only 10 msgs - - if i < 10 - - unless sync.line_referential_sync_messages.empty? - - sync.line_referential_sync_messages.last.tap do |log| - - if log.criticity = log.criticity - tr - td style='width: 150px' - = l(log.created_at, format: :short_with_time) - td.text-center - .fa.fa-circle class="text-#{criticity_class(log.criticity)}" - td - - data = log.message_attributes.symbolize_keys! - - data[:processing_time] = distance_of_time_in_words(data[:processing_time].to_i) - = t("line_referential_sync.message.#{log.message_key}", log.message_attributes.symbolize_keys!).html_safe + - unless @line_referential.line_referential_syncs.any? + .row.mt-xs + .col-lg-12 + = replacement_msg t('line_referential_syncs.search_no_results') diff --git a/app/views/lines/_filters.html.slim b/app/views/lines/_filters.html.slim index f745d10a4..992d163fe 100644 --- a/app/views/lines/_filters.html.slim +++ b/app/views/lines/_filters.html.slim @@ -8,23 +8,23 @@ .ffg-row .form-group.togglable class=filter_item_class(params[:q], :network_id_eq_any) - = f.label Chouette::Line.human_attribute_name(:network_id), required: false, class: 'control-label' + = f.label Chouette::Line.tmf(:network_id), required: false, class: 'control-label' = f.input :network_id_eq_any, collection: @line_referential.networks.order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} .form-group.togglable class=filter_item_class(params[:q], :company_id_eq_any) - = f.label Chouette::Line.human_attribute_name(:company_id), required: false, class: 'control-label' + = f.label Chouette::Line.tmf(:company_id), required: false, class: 'control-label' = f.input :company_id_eq_any, collection: @line_referential.companies.order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} .form-group.togglable class=filter_item_class(params[:q], :transport_mode_eq_any) - = f.label Chouette::Line.human_attribute_name(:transport_mode), required: false, class: 'control-label' + = f.label Chouette::Line.tmf(:transport_mode), required: false, class: 'control-label' = f.input :transport_mode_eq_any, collection: StifTransportModeEnumerations.sorted_transport_modes, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} .form-group.togglable class=filter_item_class(params[:q], :transport_submode_eq_any) - = f.label Chouette::Line.human_attribute_name(:transport_submode), required: false, class: 'control-label' + = f.label Chouette::Line.tmf(:transport_submode), required: false, class: 'control-label' = f.input :transport_submode_eq_any, collection: StifTransportSubmodeEnumerations.sorted_transport_submodes, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_submode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'} .form-group.togglable class=filter_item_class(params[:q], :status) - = f.label Chouette::Line.human_attribute_name(:state), required: false, class: 'control-label' + = f.label Chouette::Line.tmf(:status), required: false, class: 'control-label' .form-group.checkbox_list = f.simple_fields_for :status do |p| = p.input :activated, diff --git a/app/views/lines/index.html.slim b/app/views/lines/index.html.slim index 9d491ace4..02bb5ec6e 100644 --- a/app/views/lines/index.html.slim +++ b/app/views/lines/index.html.slim @@ -29,9 +29,9 @@ end \ ), \ TableBuilderHelper::Column.new( \ - name: t('activerecord.attributes.line.state'), \ + key: :status, \ class: :state, \ - attribute: Proc.new { |n| line_status(n.deactivated) } \ + attribute: Proc.new { |n| line_status(n.status) } \ ), \ TableBuilderHelper::Column.new( \ key: 'networks.name', \ @@ -57,4 +57,4 @@ - unless @lines.any? .row.mt-xs .col-lg-12 - = replacement_msg t('referential_lines.search_no_results') + = replacement_msg 'referential_lines.search_no_results'.t diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim index 9e1ae6d6f..fae32fb5d 100644 --- a/app/views/lines/show.html.slim +++ b/app/views/lines/show.html.slim @@ -7,13 +7,13 @@ .col-lg-6.col-md-6.col-sm-12.col-xs-12 = definition_list t('metadatas'), { t('objectid') => @line.get_objectid.short_id, - @line.human_attribute_name(:deactivated) => (@line.deactivated? ? t('false') : t('true')), - @line.human_attribute_name(:network_id) => (@line.network.nil? ? t('lines.index.unset') : @line.network.name), - @line.human_attribute_name(:company_id) => (@line.company.nil? ? t('lines.index.unset') : @line.company.name), - @line.human_attribute_name(:secondary_companies) => (@line.secondary_companies.nil? ? t('lines.index.unset') : array_to_html_list(@line.secondary_companies.collect(&:name))), - @line.human_attribute_name(:number) => @line.number, - @line.human_attribute_name(:registration_number) => (@line.registration_number ? @line.registration_number : '-'), - @line.human_attribute_name(:transport_mode) => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), - @line.human_attribute_name(:transport_submode) => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'), - @line.human_attribute_name(:url) => (@line.url ? @line.url : '-'), - @line.human_attribute_name(:seasonal) => (@line.seasonal? ? t('true') : t('false')),} + Chouette::Line.tmf(:status) => line_status(@line.status), + Chouette::Line.tmf(:network_id) => (@line.network.nil? ? t('lines.index.unset') : @line.network.name), + Chouette::Line.tmf(:company_id) => (@line.company.nil? ? t('lines.index.unset') : @line.company.name), + Chouette::Line.tmf(:secondary_companies) => (@line.secondary_companies.nil? ? t('lines.index.unset') : array_to_html_list(@line.secondary_companies.collect(&:name))), + Chouette::Line.tmf(:number) => @line.number, + Chouette::Line.tmf(:registration_number) => (@line.registration_number ? @line.registration_number : '-'), + Chouette::Line.tmf(:transport_mode) => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), + Chouette::Line.tmf(:transport_submode) => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'), + Chouette::Line.tmf(:url) => (@line.url ? @line.url : '-'), + Chouette::Line.tmf(:seasonal) => (@line.seasonal? ? t('true') : t('false')),} diff --git a/app/views/referential_companies/_form.html.slim b/app/views/referential_companies/_form.html.slim index 0e7b20af4..bac6d6694 100644 --- a/app/views/referential_companies/_form.html.slim +++ b/app/views/referential_companies/_form.html.slim @@ -9,7 +9,7 @@ = f.input :phone = f.input :fax = f.input :email, as: :email - = f.input :time_zone, include_blank: true + = f.input :time_zone, as: :full_time_zone, include_blank: true = f.input :url = f.input :registration_number, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.company.registration_number")} - if resource.custom_fields(@referential.workgroup).any? diff --git a/app/views/referential_companies/edit.html.slim b/app/views/referential_companies/edit.html.slim index 95be64aa1..0c9fb1f87 100644 --- a/app/views/referential_companies/edit.html.slim +++ b/app/views/referential_companies/edit.html.slim @@ -1,5 +1,8 @@ - breadcrumb :referential_company, @referential, @company - page_header_content_for @company + .page_content .container-fluid - = render 'form' + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = render 'form' diff --git a/app/views/referential_lines/show.html.slim b/app/views/referential_lines/show.html.slim index 91868a002..e387146d7 100644 --- a/app/views/referential_lines/show.html.slim +++ b/app/views/referential_lines/show.html.slim @@ -7,10 +7,10 @@ .col-lg-6.col-md-6.col-sm-12.col-xs-12 = definition_list t('metadatas'), { t('id_codif') => @line.get_objectid.short_id, - Chouette::Line.tmf('activated') => (@line.deactivated? ? t('false') : t('true')), + Chouette::Line.tmf('status') => line_status(@line.status), Chouette::Line.tmf('network_id') => (@line.network.nil? ? t('lines.index.unset') : link_to(@line.network.name, [@referential, @line.network]) ), Chouette::Line.tmf('company') => (@line.company.nil? ? t('lines.index.unset') : link_to(@line.company.name, [@referential, @line.company]) ), - Chouette::Line.tmf('secondary_company') => (@line.secondary_companies.nil? ? t('lines.index.unset') : @line.secondary_companies.collect(&:name).join(', ')), + Chouette::Line.tmf('secondary_companies') => (@line.secondary_companies.nil? ? t('lines.index.unset') : @line.secondary_companies.collect(&:name).join(', ')), Chouette::Line.tmf('registration_number') => @line.number, Chouette::Line.tmf('published_name') => (@line.registration_number ? @line.registration_number : '-'), Chouette::Line.tmf('transport_mode') => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), diff --git a/app/views/referential_stop_areas/_form.html.slim b/app/views/referential_stop_areas/_form.html.slim index 3921c8bf1..bf416ebb0 100644 --- a/app/views/referential_stop_areas/_form.html.slim +++ b/app/views/referential_stop_areas/_form.html.slim @@ -30,7 +30,7 @@ = form.input :fare_code, as: :number = form.input :nearest_topic_name, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")} = form.input :comment, as: :text, :input_html => { :rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment") } - = form.input :time_zone, :include_blank => true + = form.input :time_zone, as: :full_time_zone, :include_blank => true = form.input :url .pmr_info diff --git a/app/views/referential_stop_areas/show.html.slim b/app/views/referential_stop_areas/show.html.slim index cb04ab7a6..beee0383f 100644 --- a/app/views/referential_stop_areas/show.html.slim +++ b/app/views/referential_stop_areas/show.html.slim @@ -7,10 +7,10 @@ .col-lg-6.col-md-6.col-sm-12.col-xs-12 = definition_list t('metadatas'), { t('id_reflex') => @stop_area.try(:user_objectid), - 'Activé' => (@stop_area.deleted_at ? t('false') : t('true')), - @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment), - @stop_area.human_attribute_name(:stop_area_type) => t("area_types.label.#{@stop_area.stop_area_type}"), - @stop_area.human_attribute_name(:registration_number) => @stop_area.registration_number, - 'Coordonnées' => geo_data(@stop_area, @stop_area_referential), - @stop_area.human_attribute_name(:zip_code) => @stop_area.zip_code, - @stop_area.human_attribute_name(:city_name) => @stop_area.city_name } + Chouette::StopArea.tmf(:status) => stop_area_status(@stop_area), + Chouette::StopArea.tmf(:comment) => @stop_area.try(:comment), + Chouette::StopArea.tmf(:stop_area_type) => t("area_types.label.#{@stop_area.stop_area_type}"), + Chouette::StopArea.tmf(:registration_number) => @stop_area.registration_number, + Chouette::StopArea.tmf(:coordinates) => geo_data(@stop_area, @stop_area_referential), + Chouette::StopArea.tmf(:zip_code) => @stop_area.zip_code, + Chouette::StopArea.tmf(:city_name) => @stop_area.city_name }
\ No newline at end of file diff --git a/app/views/referentials/_period_fields.html.slim b/app/views/referentials/_period_fields.html.slim index 4d2372f7b..b0038c6b3 100644 --- a/app/views/referentials/_period_fields.html.slim +++ b/app/views/referentials/_period_fields.html.slim @@ -8,8 +8,8 @@ .wrapper div - = f.input :begin, as: :date, label: false, wrapper_html: { class: 'date smart_date' } + = f.input :begin, as: :date, label: false, start_year: Date.today.year - 15, end_year: Date.today.year + 15, wrapper_html: { class: 'date smart_date' } div - = f.input :end, as: :date, label: false, wrapper_html: { class: 'date smart_date' } + = f.input :end, as: :date, label: false, start_year: Date.today.year - 15, end_year: Date.today.year + 15, wrapper_html: { class: 'date smart_date' } div = link_to_remove_association '', f, class: 'fa fa-trash', data: { confirm: t('are_you_sure')}, title: t('actions.delete') diff --git a/app/views/referentials/show.html.slim b/app/views/referentials/show.html.slim index b2a079ab4..3cdcff63b 100644 --- a/app/views/referentials/show.html.slim +++ b/app/views/referentials/show.html.slim @@ -40,8 +40,8 @@ end \ ), \ TableBuilderHelper::Column.new( \ - key: :state, \ - attribute: Proc.new { |n| line_status(n.deactivated?) } \ + key: :status, \ + attribute: Proc.new { |n| line_status(n.status) } \ ), \ TableBuilderHelper::Column.new( \ key: :transport_mode, \ diff --git a/app/views/routes/show.html.slim b/app/views/routes/show.html.slim index d4571c173..aea824a89 100644 --- a/app/views/routes/show.html.slim +++ b/app/views/routes/show.html.slim @@ -34,8 +34,9 @@ end \ ), \ TableBuilderHelper::Column.new( \ - name: Chouette::Line.tmf('activated'), \ - attribute: Proc.new { |s| line_status(s.try(:stop_area).deleted_at) } \ + key: :status, \ + name: Chouette::StopArea.tmf('status'), \ + attribute: Proc.new { |s| stop_area_status(s.try(:stop_area)) } \ ), \ TableBuilderHelper::Column.new( \ key: :zip_code, \ diff --git a/app/views/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim index 7538c7fc7..74e607e26 100644 --- a/app/views/stif/dashboards/_dashboard.html.slim +++ b/app/views/stif/dashboards/_dashboard.html.slim @@ -5,8 +5,13 @@ h3.panel-title = t('.organisation') - .panel-body - em.small.text-muted = t('.no_content') + - if @dashboard.workbench.output.referentials.present? + - @dashboard.workbench.output.referentials.first(5).each do |referential| + .list-group + = link_to referential.name, referential_path(referential), class: 'list-group-item' + - else + .panel-body + em.small.text-muted = t('.no_content') .panel.panel-default .panel-heading diff --git a/app/views/stop_area_referentials/show.html.slim b/app/views/stop_area_referentials/show.html.slim index 911006c39..bca89a0f4 100644 --- a/app/views/stop_area_referentials/show.html.slim +++ b/app/views/stop_area_referentials/show.html.slim @@ -1,41 +1,29 @@ - breadcrumb :stop_area_referential, @stop_area_referential -- if policy(@stop_area_referential).synchronize? - - content_for :page_header_actions do - = link_to(t('actions.sync'), sync_stop_area_referential_path(@stop_area_referential), method: :post, class: 'btn btn-default') - -- content_for :page_header_content do - .row.mb-md - .col-lg-12.text-right - = link_to stop_area_referential_stop_areas_path(@stop_area_referential), class: 'btn btn-primary' do - = Referential.human_attribute_name(:stop_areas) - em.small = " (#{@stop_area_referential.stop_areas.count})" - page_header_content_for @stop_area_referential .page_content .container-fluid .row .col-lg-12 - - unless @stop_area_referential.stop_area_referential_syncs.empty? - table.table - thead - tr - th Synchronisé - th Statut - th Message + = table_builder_2 @stop_area_referential.stop_area_referential_syncs, + [ \ + TableBuilderHelper::Column.new( \ + name: t('.synchronized'), \ + attribute: Proc.new { |sync| stop_area_referential_sync_created_at(sync) }, \ + ), \ + TableBuilderHelper::Column.new( \ + name: t('.status'), \ + attribute: Proc.new { |sync| stop_area_referential_sync_status(sync) }, \ + ), \ + TableBuilderHelper::Column.new( \ + name: t('.message'), \ + attribute: Proc.new { |sync| stop_area_referential_sync_message(sync) }, \ + ), \ + ], + sortable: false, + cls: 'table' - tbody - - @stop_area_referential.stop_area_referential_syncs.each_with_index do |sync, i| - / Display only 10 msgs - - if i < 10 - - unless sync.stop_area_referential_sync_messages.empty? - - sync.stop_area_referential_sync_messages.last.tap do |log| - - if log.criticity = log.criticity - tr - td style='width:150px' - = l(log.created_at, format: :short_with_time) - td.text-center - .fa.fa-circle class="text-#{criticity_class(log.criticity)}" - td - - data = log.message_attributes.symbolize_keys! - - data[:processing_time] = distance_of_time_in_words(data[:processing_time].to_i) - = t("stop_area_referential_sync.message.#{log.message_key}", log.message_attributes.symbolize_keys!).html_safe + - unless @stop_area_referential.stop_area_referential_syncs.any? + .row.mt-xs + .col-lg-12 + = replacement_msg t('stop_area_referential_syncs.search_no_results') diff --git a/app/views/stop_areas/_filters.html.slim b/app/views/stop_areas/_filters.html.slim index c698eaaa5..caa264d5e 100644 --- a/app/views/stop_areas/_filters.html.slim +++ b/app/views/stop_areas/_filters.html.slim @@ -15,7 +15,7 @@ = f.input :area_type_eq_any, checked: params[:q] && params[:q][:area_type_eq_any], collection: Chouette::AreaType.options, as: :check_boxes, label: false, label_method: lambda{|w| ("<span>" + w[0] + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } .form-group.togglable class=filter_item_class(params[:q], :status) - = f.label Chouette::StopArea.human_attribute_name(:state), required: false, class: 'control-label' + = f.label Chouette::StopArea.tmf('status'), required: false, class: 'control-label' .form-group.checkbox_list = f.simple_fields_for :status do |p| = p.input :in_creation, diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim index 00f2ad8bb..2ac316632 100644 --- a/app/views/stop_areas/_form.html.slim +++ b/app/views/stop_areas/_form.html.slim @@ -52,7 +52,7 @@ = f.input :fare_code = f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")} = f.input :comment, as: :text, :input_html => {:rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment")} - = f.input :time_zone, :include_blank => true + = f.input :time_zone, as: :full_time_zone, include_blank: true = f.input :url .pmr_info @@ -62,10 +62,10 @@ = f.input :stairs_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true = f.input :lift_availability, as: :select, :collection => [[t("true"), true], [t("false"), false]], :include_blank => true - - if resource.custom_fields(resource.stop_area_referential.workgroup).any? + - if resource.custom_fields.present? .custom_fields h3 = t("stop_areas.stop_area.custom_fields") - - resource.custom_fields(resource.stop_area_referential.workgroup).each do |code, field| + - resource.custom_fields.each do |code, field| = field.input(f).to_s .separator diff --git a/app/views/stop_areas/index.html.slim b/app/views/stop_areas/index.html.slim index fbdb54e02..62b873c36 100644 --- a/app/views/stop_areas/index.html.slim +++ b/app/views/stop_areas/index.html.slim @@ -32,7 +32,7 @@ attribute: 'registration_number' \ ), \ TableBuilderHelper::Column.new( \ - name: Chouette::StopArea.tmf('state'), \ + key: :status, \ attribute: Proc.new { |s| stop_area_status(s) } \ ), \ TableBuilderHelper::Column.new( \ diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim index 851bd9b82..c10d22bfb 100644 --- a/app/views/stop_areas/show.html.slim +++ b/app/views/stop_areas/show.html.slim @@ -10,18 +10,19 @@ - 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!({label_for_country(k, Chouette::StopArea.tmf('name')) => v }) if v.present? + - attributes.merge!({ Chouette::StopArea.tmf('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!({ Chouette::StopArea.tmf('stop_area_type') => Chouette::AreaType.find(@stop_area.area_type).try(:label), + Chouette::StopArea.tmf('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!(Chouette::StopArea.tmf('waiting_time') => @stop_area.waiting_time_text) if has_feature?(:stop_area_waiting_time) - attributes.merge!({ "Coordonnées" => geo_data(@stop_area, @stop_area_referential), - @stop_area.human_attribute_name(:zip_code) => @stop_area.zip_code, - @stop_area.human_attribute_name(:city_name) => @stop_area.city_name, - @stop_area.human_attribute_name(:country_code) => @stop_area.country_code.presence || '-', - t('activerecord.attributes.stop_area.state') => stop_area_status(@stop_area), - @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment), + Chouette::StopArea.tmf('zip_code') => @stop_area.zip_code, + Chouette::StopArea.tmf('city_name') => @stop_area.city_name, + Chouette::StopArea.tmf('country_code') => @stop_area.country_code.presence || '-', + Chouette::StopArea.tmf('time_zone') => @stop_area.time_zone.presence || '-', + Chouette::StopArea.tmf('status') => stop_area_status(@stop_area), + Chouette::StopArea.tmf('comment') => @stop_area.try(:comment), }) - @stop_area.custom_fields.each do |code, field| - attributes.merge!(field.name => field.display_value) diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim index 7fcee545f..2af0e5345 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -33,6 +33,7 @@ | window.all_missions = #{(@all_missions.to_json).html_safe}; | window.custom_fields = #{(@custom_fields.to_json).html_safe}; | window.extra_headers = #{(@extra_headers.to_json).html_safe}; + | window.constraint_zones_routes = "#{url_for([@referential, @route.line, :routing_constraint_zones]).html_safe}"; - if has_feature?(:vehicle_journeys_return_route) = javascript_tag do diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl index d218038a6..3a551f237 100644 --- a/app/views/vehicle_journeys/show.rabl +++ b/app/views/vehicle_journeys/show.rabl @@ -1,6 +1,6 @@ object @vehicle_journey -[:objectid, :published_journey_name, :published_journey_identifier, :company_id, :comment, :checksum, :custom_fields].each do |attr| +[:objectid, :published_journey_name, :published_journey_identifier, :company_id, :comment, :checksum, :custom_fields, :ignored_routing_contraint_zone_ids].each do |attr| attributes attr, :unless => lambda { |m| m.send( attr).nil?} end @@ -17,7 +17,7 @@ child(:route) do |route| end child(:journey_pattern) do |journey_pattern| - attributes :id, :objectid, :name, :published_name + attributes :id, :objectid, :name, :published_name, :journey_length node(:short_id) {journey_pattern.get_objectid.short_id} end diff --git a/app/workers/route_way_cost_worker.rb b/app/workers/route_way_cost_worker.rb index d6bfed592..b62416c3d 100644 --- a/app/workers/route_way_cost_worker.rb +++ b/app/workers/route_way_cost_worker.rb @@ -7,10 +7,11 @@ class RouteWayCostWorker # Prevent recursive worker spawning since this call updates the # `costs` field of the route. - Chouette::Route.skip_callback(:save, :after, :calculate_costs!) - - RouteWayCostCalculator.new(route).calculate! - - Chouette::Route.set_callback(:save, :after, :calculate_costs!) + begin + Chouette::Route.skip_callback(:commit, :after, :calculate_costs!) + RouteWayCostCalculator.new(route).calculate! + ensure + Chouette::Route.set_callback(:commit, :after, :calculate_costs!) + end end end |
