diff options
Diffstat (limited to 'app')
70 files changed, 1231 insertions, 216 deletions
diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico Binary files differnew file mode 100644 index 000000000..7029bd04e --- /dev/null +++ b/app/assets/images/favicon.ico diff --git a/app/assets/stylesheets/application.sass b/app/assets/stylesheets/application.sass index 3f8467efe..632ade179 100644 --- a/app/assets/stylesheets/application.sass +++ b/app/assets/stylesheets/application.sass @@ -21,3 +21,6 @@ @import 'modules/import_messages' @import 'flag-icon' + +span.fa + span + margin-left: 0.2em diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass index b13c5fc23..e7aa31fab 100644 --- a/app/assets/stylesheets/components/_forms.sass +++ b/app/assets/stylesheets/components/_forms.sass @@ -507,10 +507,10 @@ table, .table right: 15px top: 50% padding: 0 - margin-top: -13px z-index: 1 + transform: translateY(-50%) - .btn + *:not(.btn-group) > .btn color: $blue font-weight: 700 background-color: transparent @@ -718,6 +718,10 @@ table, .table > .form-group:last-child border-right: none + @for $i from 1 through 99 + &.w#{$i} + display: inline-block + // Form group date .form-group.date .form-inline diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass index 3ff0828ea..9bb19f75f 100644 --- a/app/assets/stylesheets/modules/_vj_collection.sass +++ b/app/assets/stylesheets/modules/_vj_collection.sass @@ -2,7 +2,7 @@ // VJ Collection // //-----------------// -#vehicle_journeys_wip +#vehicle_journeys_wip, .consolidated-view .table-2entries .t2e-head > .td @@ -218,3 +218,95 @@ // Reset default behaviour .form-control border-color: #ccc + +.consolidated-view + $highlighted: #d4ba32 + .togglable + &.ready + transition: all 0.5s + &:not(.open) + min-height: 0 !important + padding: 0 !important + margin: 0 !important + border: none + + .line + & > .head + font-size: 2em + text-transform: capitalize + border-top: 3px solid black + border-bottom: 3px solid black + padding-left: 10px + margin-top: 10px + .routes + .route + background: #F9F9F9 + & > .head + &.highlighted + .pull-right, span.fa + color: $highlighted !important + a + padding: 15px + color: black + font-size: 1.2em + text-transform: capitalize + display: block + text-decoration: none + &:hover + background-color: #F0F0F0 + .fa + color: $red + transition: transform 0.1s + &.active .fa + transform: rotate(180deg) + .pull-right + font-size: 0.9em + text-transform: lowercase + .vehicle-journeys + display: block + overflow: hidden + margin-bottom: 0px + & > * + display: inline-block + & > * + overflow: hidden + min-width: 0 + &.open + margin-bottom: 40px + + .highlighted + background-color: lighten($highlighted, 20%) !important + .disabled + color: #bbb + .t2e-item-list .t2e-item + .th + min-width: 100px + max-width: 150px + & > div + text-overflow: ellipsis + overflow: hidden + white-space: nowrap + .td + text-align: center + &:hover:after + position: absolute + height: 100% + bottom: 0 + left: -10000px + right: -10000px + content: "" + background-color: $red + opacity: 0.1 + z-index: 10 + &.headlined:hover:after + height: 50% + + + .table-2entries > .t2e-head > .td > div > span::after + bottom: -6px !important + + .table.table-2entries .t2e-item-list .t2e-item + background-color: #F9F9F9 + + .table.table-2entries .td > div.headlined::before + border-right: none diff --git a/app/controllers/compliance_check_messages_controller.rb b/app/controllers/compliance_check_messages_controller.rb index 36745981e..db551cca5 100644 --- a/app/controllers/compliance_check_messages_controller.rb +++ b/app/controllers/compliance_check_messages_controller.rb @@ -7,7 +7,7 @@ class ComplianceCheckMessagesController < ChouetteController def index index! do |format| format.csv { - send_data ComplianceCheckMessageExport.new(compliance_check_messages: collection).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true, server_url: request.base_url) , :filename => "compliance_check_set_errors_#{line_code}_#{Date.today.to_s}.csv" + send_data ComplianceCheckMessageExport.new(compliance_check_messages: collection).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true, server_url: request.base_url) , :filename => "#{t('compliance_check_messages.compliance_check_set_errors')}_#{line_code}_#{Time.now.strftime('%d-%m-%Y_%H-%M')}.csv" } end end @@ -22,10 +22,10 @@ class ComplianceCheckMessagesController < ChouetteController end def compliance_check_resource - ComplianceCheckResource.find(params[:compliance_check_resource_id]) + ComplianceCheckResource.find(params[:compliance_check_resource_id]) end - private + private def line_code Chouette::Line.find_by_objectid("#{compliance_check_resource.reference}").get_objectid.local_id diff --git a/app/controllers/import_messages_controller.rb b/app/controllers/import_messages_controller.rb index e9a071177..9f61940a3 100644 --- a/app/controllers/import_messages_controller.rb +++ b/app/controllers/import_messages_controller.rb @@ -9,7 +9,7 @@ class ImportMessagesController < ChouetteController def index index! do |format| format.csv { - send_data Import::MessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Date.today.to_s}.csv" + send_data Import::MessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "#{t('import_messages.import_errors')}_#{@import_resource.name.gsub('.xml', '')}_#{Time.now.strftime("%d-%m-%Y_%H-%M")}.csv" } end end diff --git a/app/controllers/import_resources_controller.rb b/app/controllers/import_resources_controller.rb index 1535fd171..46f8f0337 100644 --- a/app/controllers/import_resources_controller.rb +++ b/app/controllers/import_resources_controller.rb @@ -24,6 +24,15 @@ class ImportResourcesController < ChouetteController @import_resources ||= parent.resources end + def resource + @import ||= Import::Base.find params[:import_id] + @import_resource ||= begin + import_resource = Import::Resource.find params[:id] + raise ActiveRecord::RecordNotFound unless import_resource.import == @import + import_resource + end + end + private def decorate_import_resources(import_resources) diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb index 8d7a723a0..b98d7da8d 100644 --- a/app/controllers/imports_controller.rb +++ b/app/controllers/imports_controller.rb @@ -4,6 +4,7 @@ class ImportsController < ChouetteController include IevInterfaces skip_before_action :authenticate_user!, only: [:download] defaults resource_class: Import::Base, collection_name: 'imports', instance_name: 'import' + before_action :notify_parents def download if params[:token] == resource.token_download @@ -18,7 +19,7 @@ class ImportsController < ChouetteController def index_model Import::Workbench end - + def build_resource @import ||= Import::Workbench.new(*resource_params) do |import| import.workbench = parent @@ -43,4 +44,10 @@ class ImportsController < ChouetteController } ) end + + def notify_parents + if Rails.env.development? + ParentNotifier.new(Import::Base).notify_when_finished + end + end end diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb index 14f7909b9..111d39c2b 100644 --- a/app/controllers/referential_vehicle_journeys_controller.rb +++ b/app/controllers/referential_vehicle_journeys_controller.rb @@ -42,9 +42,9 @@ class ReferentialVehicleJourneysController < ChouetteController @q = @q.ransack(params[:q]) @vehicle_journeys ||= @q.result @vehicle_journeys = parse_order @vehicle_journeys - @vehicle_journeys = @vehicle_journeys.paginate page: params[:page], per_page: params[:per_page] || 10 @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct - + @consolidated = ReferentialConsolidated.new @vehicle_journeys, params + @vehicle_journeys = @vehicle_journeys.paginate page: params[:page], per_page: params[:per_page] || 10 end def parse_order scope diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 8addfbc32..80f954cde 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -136,7 +136,6 @@ class ReferentialsController < ChouetteController def create_resource(referential) referential.organisation = current_organisation - referential.ready = true super end diff --git a/app/controllers/workbenches_controller.rb b/app/controllers/workbenches_controller.rb index 43415ff60..d4dfdebe3 100644 --- a/app/controllers/workbenches_controller.rb +++ b/app/controllers/workbenches_controller.rb @@ -42,7 +42,7 @@ class WorkbenchesController < ChouetteController private def workbench_params - params.require(:workbench).permit(:import_compliance_control_set_id, :merge_compliance_control_set_id) + params.require(:workbench).permit(compliance_control_set_ids: @workbench.workgroup.compliance_control_sets_by_workbench.keys) end def resource diff --git a/app/controllers/workgroups_controller.rb b/app/controllers/workgroups_controller.rb new file mode 100644 index 000000000..3acea248d --- /dev/null +++ b/app/controllers/workgroups_controller.rb @@ -0,0 +1,13 @@ +class WorkgroupsController < ChouetteController + defaults resource_class: Workgroup + + include PolicyChecker + + def show + redirect_to "/" + end + + def workgroup_params + params[:workgroup].permit(workbenches_attributes: [:id, compliance_control_set_ids: @workgroup.compliance_control_sets_by_workgroup.keys]) + end +end diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb index e01987e59..db6261120 100644 --- a/app/decorators/referential_decorator.rb +++ b/app/decorators/referential_decorator.rb @@ -36,7 +36,7 @@ class ReferentialDecorator < AF83::Decorator l.method :put end - instance_decorator.action_link policy: :unarchive, secondary: :show, on: :show do |l| + instance_decorator.action_link policy: :unarchive, secondary: :show do |l| l.content t('actions.unarchive') l.href { h.unarchive_referential_path(object.id) } l.method :put diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 702ca0ffc..7a3f7e719 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -36,7 +36,7 @@ module ApplicationHelper display = policy(object).synchronize? if policy(object).respond_to?(:synchronize?) rescue false if display info = t('last_update', time: l(object.updated_at, format: :short)) - if object.has_metadata? + if object.try(:has_metadata?) author = object.metadata.modifier_username || t('default_whodunnit') info = "#{info} <br/> #{t('whodunnit', author: author)}" end diff --git a/app/helpers/exports_helper.rb b/app/helpers/exports_helper.rb index 2e784ad35..f30a80ed9 100644 --- a/app/helpers/exports_helper.rb +++ b/app/helpers/exports_helper.rb @@ -17,7 +17,7 @@ module ExportsHelper message.message_attributes["text"] else t([message.class.name.underscore.gsub('/', '_').pluralize, message.message_key].join('.'), message.message_attributes&.symbolize_keys || {}) - end + end.html_safe end def fields_for_export_task_format(form) diff --git a/app/helpers/imports_helper.rb b/app/helpers/imports_helper.rb index 140660153..f06d77eca 100644 --- a/app/helpers/imports_helper.rb +++ b/app/helpers/imports_helper.rb @@ -2,33 +2,49 @@ module ImportsHelper # Import statuses helper - def import_status(status) - if %w[new running pending].include? status + def import_status(status, verbose: false, default_status: nil) + status ||= default_status + return unless status + status = status.to_s.downcase + out = if %w[new running pending].include? status content_tag :span, '', class: "fa fa-clock-o" else cls ='' cls = 'success' if status == 'successful' + cls = 'success' if status == 'ok' cls = 'warning' if status == 'warning' - cls = 'danger' if %w[failed aborted canceled].include? status + cls = 'danger' if %w[failed aborted canceled error].include? status content_tag :span, '', class: "fa fa-circle text-#{cls}" end + if verbose + out += content_tag :span do + txt = "imports.status.#{status}".t(fallback: "") + end + end + out end # Compliance check set messages def bootstrap_class_for_message_criticity message_criticity - case message_criticity - when "error" + case message_criticity.downcase + when "error", "aborted" "alert alert-danger" when "warning" "alert alert-warning" when "info" "alert alert-info" + when "ok", "success" + "alert alert-success" else message_criticity.to_s end end + def import_message_content message + export_message_content message + end + ############################## # TO CLEAN!!! ############################## diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index e2aa2e9ea..0b24a9c05 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -153,7 +153,17 @@ module TableBuilderHelper i = columns.index(column) if overhead[i].blank? - if (i > 0) && (overhead[i - 1][:width] > 1) + prev = nil + if i > 0 + (i-1..0).each do |j| + o = overhead[j] + if (j + o[:width].to_i) >= i + prev = o + break + end + end + end + if prev clsArrayH = overhead[i - 1][:cls].split hcont << content_tag(:th, build_column_header( @@ -225,7 +235,7 @@ module TableBuilderHelper if column.linkable? path = column.link_to(item) - link = value.present? && path.present? ? link_to(value, path) : "" + link = value.present? && path.present? ? link_to(value, path) : value if overhead.empty? bcont << content_tag(:td, link, title: 'Voir', class: extra_class) @@ -234,7 +244,17 @@ module TableBuilderHelper i = columns.index(column) if overhead[i].blank? - if (i > 0) && (overhead[i - 1][:width] > 1) + prev = nil + if i > 0 + (i-1..0).each do |j| + o = overhead[j] + if (j + o[:width].to_i) >= i + prev = o + break + end + end + end + if prev clsArrayAlt = overhead[i - 1][:cls].split bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArrayAlt, extra_class)) diff --git a/app/helpers/vehicle_journeys_helper.rb b/app/helpers/vehicle_journeys_helper.rb index 1cc865c62..4d7eb7002 100644 --- a/app/helpers/vehicle_journeys_helper.rb +++ b/app/helpers/vehicle_journeys_helper.rb @@ -69,4 +69,16 @@ module VehicleJourneysHelper ) end + def vehicle_journey_stop_headline prev_sp, sp + if has_feature?(:long_distance_routes) + headline = prev_sp && prev_sp.stop_area.country_code + headline = sp.stop_area.country_code != headline + headline && sp.stop_area.country_name + else + headline = prev_sp && prev_sp.stop_area.city_name + headline = sp.stop_area.city_name != headline + headline && sp.stop_area.city_name + end + end + end diff --git a/app/inputs/full_time_zone_input.rb b/app/inputs/full_time_zone_input.rb index f5d8a9ba2..6138d17f5 100644 --- a/app/inputs/full_time_zone_input.rb +++ b/app/inputs/full_time_zone_input.rb @@ -1,7 +1,20 @@ class FullTimeZoneInput < SimpleForm::Inputs::CollectionSelectInput def collection @collection ||= begin - collection = options.delete(:collection) || ActiveSupport::TimeZone::MAPPING + collection = options.delete(:collection) || begin + coll = {} + + TZInfo::Timezone.all_data_zones.map do |tzinfo| + # v = ActiveSupport::TimeZone.zones_map[k] + # coll.sort_by do |v| + # "(#{v.formatted_offset}) #{v.name}" + # end + next if tzinfo.friendly_identifier =~ /^etc/i + tz = ActiveSupport::TimeZone.new tzinfo.name#, nil, tzinfo + coll[[tz.utc_offset, tzinfo.friendly_identifier(true)]] = ["(#{tz.formatted_offset}) #{tzinfo.friendly_identifier(true)}", tz.name] + end + coll.sort.map(&:last) + end collection.respond_to?(:call) ? collection.call : collection.to_a end end @@ -9,12 +22,8 @@ class FullTimeZoneInput < SimpleForm::Inputs::CollectionSelectInput 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 + label ||= :first value ||= :last - [label, value] end diff --git a/app/javascript/helpers/CustomFieldsInputs.js b/app/javascript/helpers/CustomFieldsInputs.js index 0a57e7566..93611538e 100644 --- a/app/javascript/helpers/CustomFieldsInputs.js +++ b/app/javascript/helpers/CustomFieldsInputs.js @@ -43,7 +43,7 @@ export default class CustomFieldsInputs extends Component { ref={'custom_fields.' + cf.code} className='form-control' disabled={this.props.disabled} - value={cf.value || this.options(cf).default} + value={cf.value || this.options(cf).default || ""} onChange={(e) => {this.props.onUpdate(cf.code, e.target.value); this.forceUpdate()} } /> ) @@ -56,7 +56,7 @@ export default class CustomFieldsInputs extends Component { ref={'custom_fields.' + cf.code} className='form-control' disabled={this.props.disabled} - value={cf.value || this.options(cf).default} + value={cf.value || this.options(cf).default || ""} onChange={(e) => {this.props.onUpdate(cf.code, e.target.value); this.forceUpdate()} } /> ) diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js index 1a6a27da6..9dcd82082 100644 --- a/app/javascript/journey_patterns/reducers/journeyPatterns.js +++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js @@ -18,6 +18,7 @@ const journeyPattern = (state = {}, action) =>{ registration_number: action.data.registration_number.value, stop_points: stopPoints, costs: {}, + custom_fields: action.data.custom_fields, deletable: false } case 'UPDATE_CHECKBOX_VALUE': diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index 60496e0ff..98594083d 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -489,23 +489,28 @@ const actions = { } return 0 }, - getDelta: (vjas) => { + getDelta: (vjas, allowNegative=false) => { let delta = 0 if (vjas.departure_time.hour != '' && vjas.departure_time.minute != '' && vjas.arrival_time.hour != '' && vjas.departure_time.minute != ''){ delta = (parseInt(vjas.departure_time.hour) - parseInt(vjas.arrival_time.hour)) * 60 + (parseInt(vjas.departure_time.minute) - parseInt(vjas.arrival_time.minute)) } + if(!true && delta < 0){ + delta += 24*60 + } vjas.delta = delta return vjas }, adjustSchedule: (action, schedule, enforceConsistency=false) => { // we enforce that the departure time remains after the arrival time - actions.getDelta(schedule) + actions.getDelta(schedule, true) if(enforceConsistency && schedule.delta < 0){ - if(action.isDeparture){ - schedule.arrival_time = schedule.departure_time - } - else{ - schedule.departure_time = schedule.arrival_time + if(schedule.arrival_time.hour < 23 || schedule.departure_time.hour > 0){ + if(action.isDeparture){ + schedule.arrival_time = schedule.departure_time + } + else{ + schedule.departure_time = schedule.arrival_time + } } actions.getDelta(schedule) } diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index f7ae9341f..46d300e6e 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -192,20 +192,16 @@ export default class VehicleJourney extends Component { <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}> <input type='number' - min='00' - max='23' className='form-control' disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}} - onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false, true)}} + onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false, true)}} value={vj.arrival_time['hour']} /> <span>:</span> <input type='number' - min='00' - max='59' className='form-control' disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} @@ -225,8 +221,6 @@ export default class VehicleJourney extends Component { <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}> <input type='number' - min='00' - max='23' className='form-control' disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} @@ -237,8 +231,6 @@ export default class VehicleJourney extends Component { <span>:</span> <input type='number' - min='00' - max='59' className='form-control' disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index 121be6a00..4931ab46e 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -66,10 +66,6 @@ const vehicleJourney= (state = {}, action, keep) => { newVjas.departure_day_offset = 1 newVjas.arrival_day_offset = 1 } - if(current_time.hour + offsetHours < 0){ - newVjas.departure_day_offset = -1 - newVjas.arrival_day_offset = -1 - } } else{ newVjas = { @@ -150,6 +146,16 @@ const vehicleJourney= (state = {}, action, keep) => { return _.assign({}, state, {vehicle_journey_at_stops: shiftedArray}) case 'UPDATE_TIME': let vj, vjas, vjasArray, newSchedule + let val = action.val + if(val != ''){ + val = parseInt(val) + if(action.timeUnit == "minute"){ + val = (val + 60) % 60 + } + else{ + val = (val + 24) % 24 + } + } vjasArray = state.vehicle_journey_at_stops.map((vjas, i) =>{ if(i == action.subIndex){ newSchedule = { @@ -157,13 +163,13 @@ const vehicleJourney= (state = {}, action, keep) => { arrival_time: _.assign({}, vjas.arrival_time) } if (action.isDeparture){ - newSchedule.departure_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) + newSchedule.departure_time[action.timeUnit] = actions.pad(val, action.timeUnit) if(!action.isArrivalsToggled) newSchedule.arrival_time[action.timeUnit] = newSchedule.departure_time[action.timeUnit] newSchedule = actions.adjustSchedule(action, newSchedule, action.enforceConsistency) return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta}) }else{ - newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) + newSchedule.arrival_time[action.timeUnit] = actions.pad(val, action.timeUnit) newSchedule = actions.adjustSchedule(action, newSchedule, action.enforceConsistency) return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta}) } diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 7a8d043e0..949b18d6f 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -72,6 +72,9 @@ module Chouette end end + has_many :time_tables, :through => :vehicle_journeys + has_many :purchase_windows, :through => :vehicle_journeys + accepts_nested_attributes_for :stop_points, :allow_destroy => :true validates_presence_of :name diff --git a/app/models/clean_up.rb b/app/models/clean_up.rb index 0f73e07b2..9cf2389c9 100644 --- a/app/models/clean_up.rb +++ b/app/models/clean_up.rb @@ -16,6 +16,8 @@ class CleanUp < ApplicationModel where(referential_id: referential.id) end + attr_accessor :methods + def end_date_must_be_greater_that_begin_date if self.end_date && self.date_type == 'between' && self.begin_date >= self.end_date errors.add(:base, I18n.t('activerecord.errors.models.clean_up.invalid_period')) @@ -43,17 +45,22 @@ class CleanUp < ApplicationModel end end - destroy_vehicle_journeys_outside_referential + destroy_routes_outside_referential # Disabled for the moment. See #5372 # destroy_time_tables_outside_referential - destroy_vehicle_journeys - destroy_journey_patterns - destroy_routes + # Run caller-specified cleanup methods + run_methods end end end + def run_methods + return if methods.nil? + + methods.each { |method| send(method) } + end + def destroy_time_tables_between time_tables = Chouette::TimeTable.where('end_date < ? AND start_date > ?', self.end_date, self.begin_date) self.destroy_time_tables(time_tables) @@ -100,9 +107,9 @@ class CleanUp < ApplicationModel destroy_time_tables(time_tables) end - def destroy_vehicle_journeys_outside_referential + def destroy_routes_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 + Chouette::Route.where(['line_id not in (?)', line_ids]).destroy_all end def destroy_vehicle_journeys @@ -117,6 +124,12 @@ class CleanUp < ApplicationModel Chouette::Route.where("id not in (select distinct route_id from journey_patterns)").destroy_all end + def destroy_empty + destroy_vehicle_journeys + destroy_journey_patterns + destroy_routes + 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_set.rb b/app/models/compliance_check_set.rb index 8b1dbdd68..f29ff4de5 100644 --- a/app/models/compliance_check_set.rb +++ b/app/models/compliance_check_set.rb @@ -26,6 +26,10 @@ class ComplianceCheckSet < ApplicationModel %w(successful failed warning aborted canceled) end + def successful? + status.to_s == "successful" + end + def self.abort_old where( 'created_at < ? AND status NOT IN (?)', @@ -68,6 +72,11 @@ class ComplianceCheckSet < ApplicationModel end update attributes + import_resource&.next_step + end + + def import_resource + referential&.import_resources.main_resources.last end diff --git a/app/models/concerns/checksum_support.rb b/app/models/concerns/checksum_support.rb index fe52604bb..86bbd1d00 100644 --- a/app/models/concerns/checksum_support.rb +++ b/app/models/concerns/checksum_support.rb @@ -10,19 +10,24 @@ module ChecksumSupport end module ClassMethods + def has_checksum_children klass, opts={} parent_class = self 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 + + child_update_parent = Proc.new do 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 + + klass.after_save &child_update_parent + klass.after_destroy &child_update_parent end end diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb index 6b6621d0c..f5a76f324 100644 --- a/app/models/concerns/custom_fields_support.rb +++ b/app/models/concerns/custom_fields_support.rb @@ -6,7 +6,7 @@ module CustomFieldsSupport after_initialize :initialize_custom_fields def self.custom_fields workgroup - return [] unless workgroup + return CustomField.none unless workgroup fields = CustomField.where(resource_type: self.name.split("::").last) fields = fields.where(workgroup_id: workgroup.id) fields diff --git a/app/models/concerns/iev_interfaces/resource.rb b/app/models/concerns/iev_interfaces/resource.rb index 7f8c3eefd..254f88a33 100644 --- a/app/models/concerns/iev_interfaces/resource.rb +++ b/app/models/concerns/iev_interfaces/resource.rb @@ -4,6 +4,24 @@ module IevInterfaces::Resource included do extend Enumerize enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true - validates_presence_of :name, :resource_type, :reference + validates_presence_of :name, :resource_type + end + + def update_status_from_importer importer_status + self.update status: status_from_importer(importer_status) + end + + def status_from_importer importer_status + return nil unless importer_status.present? + { + new: nil, + pending: nil, + successful: :OK, + warning: :WARNING, + failed: :ERROR, + running: nil, + aborted: :ERROR, + canceled: :ERROR + }[importer_status.to_sym] end end diff --git a/app/models/concerns/iev_interfaces/task.rb b/app/models/concerns/iev_interfaces/task.rb index 6be33734b..e40808009 100644 --- a/app/models/concerns/iev_interfaces/task.rb +++ b/app/models/concerns/iev_interfaces/task.rb @@ -31,6 +31,16 @@ module IevInterfaces::Task before_save :initialize_fields, on: :create after_save :notify_parent + + status.values.each do |s| + define_method "#{s}!" do + update status: s + end + + define_method "#{s}?" do + status&.to_s == s + end + end end module ClassMethods @@ -56,13 +66,14 @@ module IevInterfaces::Task end def notify_parent - return unless self.class.finished_statuses.include?(status) + return false unless self.class.finished_statuses.include?(status) - return unless parent.present? - return if notified_parent_at + return false unless parent.present? + return false if notified_parent_at parent.child_change update_column :notified_parent_at, Time.now + true end def children_succeedeed @@ -94,6 +105,10 @@ module IevInterfaces::Task update attributes end + def successful? + status.to_s == "successful" + end + def child_change return if self.class.finished_statuses.include?(status) update_status @@ -112,9 +127,14 @@ module IevInterfaces::Task def call_boiv_iev Rails.logger.error("Begin IEV call for import") + + # Java code expects tasks in NEW status + # Don't change status before calling iev + Net::HTTP.get iev_callback_url Rails.logger.error("End IEV call for import") rescue Exception => e + aborted! logger.error "IEV server error : #{e.message}" logger.error e.backtrace.inspect end diff --git a/app/models/import/base.rb b/app/models/import/base.rb index f98e359d4..dcd710e58 100644 --- a/app/models/import/base.rb +++ b/app/models/import/base.rb @@ -32,8 +32,13 @@ class Import::Base < ApplicationModel Rails.logger.info "update_referentials for #{inspect}" return unless self.class.finished_statuses.include?(status) - children.each do |import| - import.referential.update(ready: true) if import.referential + # We treat all created referentials in a batch + # If a single fails, we consider they all failed + # Ohana means family ! + if self.successful? + children.map(&:referential).compact.each &:active! + else + children.map(&:referential).compact.each &:failed! end end diff --git a/app/models/import/gtfs.rb b/app/models/import/gtfs.rb index ceb849bd8..9dab11f0e 100644 --- a/app/models/import/gtfs.rb +++ b/app/models/import/gtfs.rb @@ -1,19 +1,37 @@ class Import::Gtfs < Import::Base after_commit :launch_worker, :on => :create + after_commit do + main_resource.update_status_from_importer self.status + true + end + def launch_worker GtfsImportWorker.perform_async id end + def main_resource + @resource ||= parent.resources.find_or_create_by(name: self.name, resource_type: "referential", reference: self.name) if parent + end + + def next_step + main_resource&.next_step + end + + def create_message args + (main_resource || self).messages.build args + end + def import update status: 'running', started_at: Time.now import_without_status update status: 'successful', ended_at: Time.now - referential&.ready! + referential&.active! rescue Exception => e update status: 'failed', ended_at: Time.now Rails.logger.error "Error in GTFS import: #{e} #{e.backtrace.join('\n')}" + create_message criticity: :error, message_key: :full_text, message_attributes: {text: e.message} referential&.failed! ensure notify_parent @@ -35,6 +53,7 @@ class Import::Gtfs < Import::Base workbench_id: workbench.id, metadatas: [referential_metadata] ) + main_resource.update referential: referential if main_resource end def referential_metadata @@ -131,17 +150,21 @@ class Import::Gtfs < Import::Base end def import_agencies + count = 0 Chouette::Company.transaction do source.agencies.each do |agency| company = line_referential.companies.find_or_initialize_by(registration_number: agency.id) company.attributes = { name: agency.name } save_model company + count += 1 end end + create_message criticity: "info", message_key: "gtfs.agencies.imported", message_attributes: {count: count} end def import_stops + count = 0 Chouette::StopArea.transaction do source.stops.each do |stop| stop_area = stop_area_referential.stop_areas.find_or_initialize_by(registration_number: stop.id) @@ -155,11 +178,14 @@ class Import::Gtfs < Import::Base # TODO correct default timezone save_model stop_area + count += 1 end + create_message criticity: "info", message_key: "gtfs.stops.imported", message_attributes: {count: count} end end def import_routes + count = 0 Chouette::Line.transaction do source.routes.each do |route| line = line_referential.lines.find_or_initialize_by(registration_number: route.id) @@ -178,7 +204,9 @@ class Import::Gtfs < Import::Base line.url = route.url save_model line + count += 1 end + create_message criticity: "info", message_key: "gtfs.routes.imported", message_attributes: {count: count} end end @@ -187,6 +215,7 @@ class Import::Gtfs < Import::Base end def import_trips + count = 0 source.trips.each_slice(100) do |slice| slice.each do |trip| Chouette::Route.transaction do @@ -205,18 +234,20 @@ class Import::Gtfs < Import::Base vehicle_journey = journey_pattern.vehicle_journeys.build route: route vehicle_journey.published_journey_name = trip.headsign.presence || trip.id save_model vehicle_journey + count += 1 time_table = referential.time_tables.find_by(id: time_tables_by_service_id[trip.service_id]) if time_tables_by_service_id[trip.service_id] if time_table vehicle_journey.time_tables << time_table else - messages.create! criticity: "warning", message_key: "gtfs.trips.unkown_service_id", message_attributes: {service_id: trip.service_id} + create_message criticity: "warning", message_key: "gtfs.trips.unkown_service_id", message_attributes: {service_id: trip.service_id} end vehicle_journey_by_trip_id[trip.id] = vehicle_journey.id end end end + create_message criticity: "info", message_key: "gtfs.trips.imported", message_attributes: {count: count} end def import_stop_times @@ -262,6 +293,7 @@ class Import::Gtfs < Import::Base end def import_calendars + count = 0 source.calendars.each_slice(500) do |slice| Chouette::TimeTable.transaction do slice.each do |calendar| @@ -272,11 +304,13 @@ class Import::Gtfs < Import::Base time_table.periods.build period_start: calendar.start_date, period_end: calendar.end_date save_model time_table + count += 1 time_tables_by_service_id[calendar.service_id] = time_table.id end end end + create_message criticity: "info", message_key: "gtfs.calendars.imported", message_attributes: {count: count} end def import_calendar_dates @@ -301,10 +335,10 @@ class Import::Gtfs < Import::Base end def notify_parent - return unless parent.present? - return if notified_parent_at - parent.child_change - update_column :notified_parent_at, Time.now + if super + main_resource.update_status_from_importer self.status + next_step + end end end diff --git a/app/models/import/netex.rb b/app/models/import/netex.rb index 49554ee90..753f9128d 100644 --- a/app/models/import/netex.rb +++ b/app/models/import/netex.rb @@ -2,16 +2,39 @@ require 'net/http' class Import::Netex < Import::Base before_destroy :destroy_non_ready_referential - after_commit :call_iev_callback, on: :create + after_commit do + main_resource.update_status_from_importer self.status + true + end before_save do - self.status = 'aborted' unless referential self.referential&.failed! if self.status == 'aborted' || self.status == 'failed' end validates_presence_of :parent + def main_resource + @resource ||= parent.resources.find_or_create_by(name: self.name, resource_type: "referential", reference: self.name) + end + + def notify_parent + if super + main_resource.update_status_from_importer self.status + next_step + end + end + + def next_step + main_resource.next_step + end + + def create_message args + main_resource.messages.create args + end + def create_with_referential! + save unless persisted? + self.referential = Referential.new( name: self.name, @@ -20,15 +43,37 @@ class Import::Netex < Import::Base metadatas: [referential_metadata] ) self.referential.save - if self.referential.invalid? + + if self.referential.valid? + main_resource.update referential: referential + call_iev_callback + save! + else 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.present? && m.line_ids.empty?} - parent.messages.create criticity: :error, message_key: "referential_creation_missing_lines", message_attributes: {referential_name: referential.name} + + if referential.metadatas.all?{|m| m.line_ids.empty? && m.line_ids.empty?} + create_message criticity: :error, message_key: "referential_creation_missing_lines", message_attributes: {referential_name: referential.name} + elsif (overlapped_referential_ids = referential.overlapped_referential_ids).any? + overlapped = Referential.find overlapped_referential_ids.last + create_message( + criticity: :error, + message_key: "referential_creation_overlapping_existing_referential", + message_attributes: { + referential_name: referential.name, + overlapped_name: overlapped.name, + overlapped_url: Rails.application.routes.url_helpers.referential_path(overlapped) + } + ) else - parent.messages.create criticity: :error, message_key: "referential_creation", message_attributes: {referential_name: referential.name} + create_message( + criticity: :error, + message_key: "referential_creation", + message_attributes: {referential_name: referential.name}, + resource_attributes: referential.errors.messages + ) end - else - save! + self.referential = nil + aborted! end end diff --git a/app/models/import/resource.rb b/app/models/import/resource.rb index 1951daacd..43690755d 100644 --- a/app/models/import/resource.rb +++ b/app/models/import/resource.rb @@ -4,5 +4,49 @@ class Import::Resource < ApplicationModel include IevInterfaces::Resource belongs_to :import, class_name: Import::Base + belongs_to :referential has_many :messages, class_name: "Import::Message", foreign_key: :resource_id + + scope :main_resources, ->{ where(resource_type: "referential") } + + def root_import + import = self.import + import = import.parent while import.parent + import + end + + def next_step + if root_import.class == Import::Workbench + + return unless netex_import&.successful? + + workbench.workgroup.import_compliance_control_sets.map do |key, label| + next unless (control_set = workbench.compliance_control_set(key)).present? + compliance_check_set = workbench_import_check_set key + if compliance_check_set.nil? + ComplianceControlSetCopyWorker.perform_async control_set.id, referential_id, root_import.class.name, root_import.id + end + end + end + end + + def workbench + import.workbench + end + + def workgroup + workbench.workgroup + end + + def netex_import + return unless self.resource_type == "referential" + import.children.where(name: self.reference).last + end + + def workbench_import_check_set key + return unless referential.present? + control_set = referential.workbench.compliance_control_set(key) + return unless control_set.present? + referential.compliance_check_sets.where(compliance_control_set_id: control_set.id, referential_id: referential_id).last + end end diff --git a/app/models/import/workbench.rb b/app/models/import/workbench.rb index 124b9b0d8..95d23fe5b 100644 --- a/app/models/import/workbench.rb +++ b/app/models/import/workbench.rb @@ -9,14 +9,18 @@ class Import::Workbench < Import::Base end end + # def main_resource + # @resource ||= resources.find_or_create_by(name: self.name, resource_type: "workbench_import") + # end + def import_gtfs update_column :status, 'running' update_column :started_at, Time.now - Import::Gtfs.create! parent_id: self.id, workbench: workbench, file: File.new(file.path), name: "Import GTFS", creator: "Web service" + Import::Gtfs.create! parent_type: self.class.name, parent_id: self.id, workbench: workbench, file: File.new(file.path), name: "Import GTFS", creator: "Web service" - update_column :status, 'successful' - update_column :ended_at, Time.now + # update_column :status, 'successful' + # update_column :ended_at, Time.now rescue Exception => e Rails.logger.error "Error while processing GTFS file: #{e}" diff --git a/app/models/merge.rb b/app/models/merge.rb index 2824e1f83..aca2f4d4d 100644 --- a/app/models/merge.rb +++ b/app/models/merge.rb @@ -145,12 +145,19 @@ class Merge < ApplicationModel end end + referential_route_opposite_route_ids = referential.switch do + Hash[referential.routes.where('opposite_route_id is not null').pluck(:id, :opposite_route_id)] + end + referential_routing_constraint_zones_new_ids = {} new.switch do + route_ids_mapping = {} + referential_routes.each do |route| existing_route = new.routes.find_by line_id: route.line_id, checksum: route.checksum if existing_route + route_ids_mapping[route.id] = existing_route.id existing_route.merge_metadata_from route else objectid = Chouette::Route.where(objectid: route.objectid).exists? ? nil : route.objectid @@ -159,7 +166,7 @@ class Merge < ApplicationModel objectid: objectid, # line_id is the same # all other primary must be changed - opposite_route_id: nil #FIXME + opposite_route_id: nil # merged after ) new_route = new.routes.build attributes @@ -179,6 +186,8 @@ class Merge < ApplicationModel # We need to create StopPoints to known new primary keys new_route.save! + route_ids_mapping[route.id] = new_route.id + 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) @@ -225,6 +234,20 @@ class Merge < ApplicationModel end end end + + referential_route_opposite_route_ids.each do |route_id, opposite_route_id| + new_route_id = route_ids_mapping[route_id] + new_opposite_route_id = route_ids_mapping[opposite_route_id] + + new_route = nil + if new_route_id && new_opposite_route_id + if new_route = new.routes.find_by(id: new_route_id) + new_route.update_column :opposite_route_id, new_opposite_route_id + end + end + + Rails.logger.warn "Can't merge opposite route for Route #{route_id}" unless new_route + end end # JourneyPatterns diff --git a/app/models/referential.rb b/app/models/referential.rb index 933bc78e3..0c6e71d47 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -26,6 +26,7 @@ class Referential < ApplicationModel has_one :user has_many :api_keys, class_name: 'Api::V1::ApiKey', dependent: :destroy + has_many :import_resources, class_name: 'Import::Resource', dependent: :destroy belongs_to :organisation validates_presence_of :organisation @@ -59,7 +60,6 @@ class Referential < ApplicationModel belongs_to :referential_suite - scope :pending, -> { where(ready: false, failed_at: nil, archived_at: nil) } scope :active, -> { where(ready: true, failed_at: nil, archived_at: nil) } scope :failed, -> { where.not(failed_at: nil) } @@ -271,7 +271,8 @@ class Referential < ApplicationModel stop_area_referential: from.stop_area_referential, created_from: from, objectid_format: from.objectid_format, - metadatas: from.metadatas.map { |m| ReferentialMetadata.new_from(m, organisation) } + metadatas: from.metadatas.map { |m| ReferentialMetadata.new_from(m, organisation) }, + ready: false ) end @@ -322,6 +323,7 @@ class Referential < ApplicationModel before_create :create_schema after_create :clone_schema, if: :created_from + after_create :active!, unless: :created_from before_destroy :destroy_schema before_destroy :destroy_jobs @@ -397,7 +399,7 @@ class Referential < ApplicationModel query = "select distinct(public.referential_metadata.referential_id) FROM public.referential_metadata, unnest(line_ids) line, LATERAL unnest(periodes) period WHERE public.referential_metadata.referential_id - IN (SELECT public.referentials.id FROM public.referentials WHERE referentials.workbench_id = #{workbench_id} and referentials.archived_at is null and referentials.referential_suite_id is null #{not_myself}) + IN (SELECT public.referentials.id FROM public.referentials WHERE referentials.workbench_id = #{workbench_id} and referentials.archived_at is null and referentials.referential_suite_id is null #{not_myself} AND referentials.failed_at IS NULL) AND line in (#{line_ids.join(',')}) and (#{periods_query});" self.class.connection.select_values(query).map(&:to_i) @@ -471,6 +473,7 @@ class Referential < ApplicationModel end def destroy_schema + return unless ActiveRecord::Base.connection.schema_names.include?(slug) Apartment::Tenant.drop slug end diff --git a/app/models/workbench.rb b/app/models/workbench.rb index 1c54e8904..1bca91c56 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -51,6 +51,20 @@ class Workbench < ApplicationModel where(name: DEFAULT_WORKBENCH_NAME).last end + # XXX + # def import_compliance_control_set + # import_compliance_control_set_id && ComplianceControlSet.find(import_compliance_control_set_id) + # end + + def compliance_control_set key + id = (owner_compliance_control_set_ids || {})[key.to_s] + id.present? && ComplianceControlSet.find(id) + end + + def compliance_control_set_ids=(compliance_control_set_ids) + self.owner_compliance_control_set_ids = (owner_compliance_control_set_ids || {}).merge compliance_control_set_ids + end + private def initialize_output diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb index 3e8409634..6bb03c7fa 100644 --- a/app/models/workgroup.rb +++ b/app/models/workgroup.rb @@ -1,6 +1,7 @@ class Workgroup < ApplicationModel belongs_to :line_referential belongs_to :stop_area_referential + belongs_to :owner, class_name: "Organisation" has_many :workbenches has_many :calendars @@ -16,6 +17,8 @@ class Workgroup < ApplicationModel has_many :custom_fields + accepts_nested_attributes_for :workbenches + def custom_fields_definitions Hash[*custom_fields.map{|cf| [cf.code, cf]}.flatten] end @@ -23,4 +26,35 @@ class Workgroup < ApplicationModel def has_export? export_name export_types.include? export_name end + + def all_compliance_control_sets + %i(after_import + after_import_by_workgroup + before_merge + before_merge_by_workgroup + after_merge + after_merge_by_workgroup + automatic_by_workgroup + ) + end + + def compliance_control_sets_by_workgroup + compliance_control_sets_labels all_compliance_control_sets.grep(/by_workgroup$/) + end + + def compliance_control_sets_by_workbench + compliance_control_sets_labels all_compliance_control_sets.grep_v(/by_workgroup$/) + end + + def import_compliance_control_sets + compliance_control_sets_labels all_compliance_control_sets.grep(/^after_import/) + end + + private + def compliance_control_sets_labels(keys) + keys.inject({}) do |h, k| + h[k] = "workgroups.compliance_control_sets.#{k}".t.capitalize + h + end + end end diff --git a/app/policies/workbench_policy.rb b/app/policies/workbench_policy.rb index 7b925e91a..9f2279c38 100644 --- a/app/policies/workbench_policy.rb +++ b/app/policies/workbench_policy.rb @@ -6,6 +6,6 @@ class WorkbenchPolicy < ApplicationPolicy end def update? - true + user.has_permission?('workbenches.update') end end diff --git a/app/policies/workgroup_policy.rb b/app/policies/workgroup_policy.rb new file mode 100644 index 000000000..01914bb51 --- /dev/null +++ b/app/policies/workgroup_policy.rb @@ -0,0 +1,19 @@ +class WorkgroupPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def create? + false + end + + def desrtroy? + false + end + + def update? + record.owner == user.organisation + end +end diff --git a/app/services/referential_consolidated.rb b/app/services/referential_consolidated.rb new file mode 100644 index 000000000..465eab405 --- /dev/null +++ b/app/services/referential_consolidated.rb @@ -0,0 +1,124 @@ +class ReferentialConsolidated + attr_reader :params + + def initialize vehicle_journeys, params + @vehicle_journeys = vehicle_journeys + @params = params + end + + def paginated_lines + @paginated_lines ||= begin + line_ids = @vehicle_journeys.joins(route: :line).pluck('lines.id') + lines = Chouette::Line.where(id: line_ids).order(:name) + lines.paginate page: params[:page], per_page: params[:per_page] || 10 + end + end + + def lines + @lines ||= paginated_lines.to_a.map {|l| Line.new(self, l, @vehicle_journeys, params) } + end + + def _should_highlight? + return false unless params[:q].present? + keys = params[:q].keys - ["stop_areas"] + params[:q].values_at(*keys).each do |value| + if value.is_a?(Hash) + return true if value.values.any?(&:present?) + elsif value.is_a?(Array) + return true if value.any?(&:present?) + else + if value.present? + return true + end + end + end + false + end + + def should_highlight? + if @should_highlight.nil? + @should_highlight = _should_highlight? + end + @should_highlight + end + + class Base + extend Forwardable + attr_reader :params + attr_reader :parent + attr_reader :ar_model + + def initialize parent, ar_model, vehicle_journeys, params + @parent = parent + @ar_model = ar_model + @all_vehicle_journeys = vehicle_journeys + @params = params + end + + def should_highlight? + parent.should_highlight? + end + end + + class Line < Base + delegate name: :ar_model + delegate id: :ar_model + + def routes + @routes ||= begin + ar_model.routes.order(:name).map {|r| Route.new(self, r, @all_vehicle_journeys, params) } + end + end + end + + class Route < Base + def_delegators :ar_model, :name, :id, :time_tables, :purchase_windows, :stop_area_ids + + def vehicle_journeys + @vehicle_journeys ||= begin + ar_model.vehicle_journeys.map {|vj| VehicleJourney.new(self, vj, @all_vehicle_journeys, params) } + end + end + + def highlighted_journeys + @all_vehicle_journeys.joins(:journey_pattern).where(route_id: self.id) + end + + def highlighted_count + highlighted_journeys.count + end + + def highlighted? + matching_stop_areas = params[:q] && params[:q]["stop_areas"] && (params[:q]["stop_areas"].values & self.stop_area_ids.map(&:to_s)).present? + (should_highlight? || matching_stop_areas) && highlighted_journeys.exists? + end + + def stop_points + @stop_points ||= ar_model.stop_points.map {|sp| StopPoint.new(self, sp, @all_vehicle_journeys, params) } + end + end + + class VehicleJourney < Base + def_delegators :ar_model, :id, :published_journey_name, :journey_pattern, :time_tables, :purchase_windows, :vehicle_journey_at_stops, :time_table_ids, :purchase_window_ids, :route + + def highlighted? + should_highlight? && @all_vehicle_journeys.where(id: self.id).exists? + end + + def has_purchase_window? purchase_window + purchase_window_ids.include?(purchase_window.id) + end + + def has_time_table? time_table + time_table_ids.include?(time_table.id) + end + end + + class StopPoint < Base + def_delegators :ar_model, :id, :arrival_time, :departure_time, :name, :stop_area, :stop_area_id + + def highlighted? + params[:q] && params[:q]["stop_areas"] && params[:q]["stop_areas"].values.any?{|v| v.to_s == stop_area_id.to_s} + end + end +end diff --git a/app/services/route_way_cost_calculator.rb b/app/services/route_way_cost_calculator.rb index d41a2e59a..ca47a6772 100644 --- a/app/services/route_way_cost_calculator.rb +++ b/app/services/route_way_cost_calculator.rb @@ -8,5 +8,7 @@ class RouteWayCostCalculator way_costs = TomTom.matrix(way_costs) way_costs = WayCostCollectionJSONSerializer.dump(way_costs) @route.update(costs: way_costs) + rescue TomTom::Errors::MatrixRemoteError => e + Rails.logger.error "TomTom::Matrix server error: #{e}" end end diff --git a/app/views/companies/edit.html.slim b/app/views/companies/edit.html.slim index faa88f829..c85bef0ec 100644 --- a/app/views/companies/edit.html.slim +++ b/app/views/companies/edit.html.slim @@ -1,3 +1,8 @@ - breadcrumb :company, @company - page_header_content_for @company -= render 'form'
\ No newline at end of file + +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = render 'form' diff --git a/app/views/companies/new.html.slim b/app/views/companies/new.html.slim index 1747b8e10..6cc32130d 100644 --- a/app/views/companies/new.html.slim +++ b/app/views/companies/new.html.slim @@ -1,6 +1,7 @@ - breadcrumb :companies, @line_referential + .page_content .container-fluid .row .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 - = render 'form'
\ No newline at end of file + = render 'form' diff --git a/app/views/compliance_check_sets/show.html.slim b/app/views/compliance_check_sets/show.html.slim index 4e1a8e2f9..bf4642b21 100644 --- a/app/views/compliance_check_sets/show.html.slim +++ b/app/views/compliance_check_sets/show.html.slim @@ -9,6 +9,7 @@ = definition_list( t('metadatas'), { I18n.t("compliance_check_sets.show.metadatas.referential") => (@compliance_check_set.referential.nil? ? '' : link_to(@compliance_check_set.referential.name, referential_path(@compliance_check_set.referential)) ), I18n.t("compliance_check_sets.show.metadatas.referential_type") => 'Jeu de données', + I18n.t("compliance_check_sets.show.metadatas.status") => import_status(@compliance_check_set.status, verbose: true), I18n.t("compliance_check_sets.show.metadatas.compliance_check_set_executed") => link_to(@compliance_check_set.name, executed_workbench_compliance_check_set_path(@compliance_check_set.workbench_id, @compliance_check_set)), I18n.t("compliance_check_sets.show.metadatas.compliance_control_owner") => @compliance_check_set.organisation.name, I18n.t("compliance_check_sets.show.metadatas.import") => '' }) diff --git a/app/views/compliance_control_sets/show.html.slim b/app/views/compliance_control_sets/show.html.slim index 729c1ce43..48c03d7c8 100644 --- a/app/views/compliance_control_sets/show.html.slim +++ b/app/views/compliance_control_sets/show.html.slim @@ -15,8 +15,9 @@ = render '/compliance_controls/filters' / compliance controls without block - = render_compliance_control_block - = render_compliance_controls(@direct_compliance_controls) + - unless params[:q].present? && @direct_compliance_controls.nil? + = render_compliance_control_block + = render_compliance_controls(@direct_compliance_controls) / compliance controls with block - if params[:q] && params[:q][:compliance_control_block_id_eq_any].try(:present?) diff --git a/app/views/import_resources/show.html.slim b/app/views/import_resources/show.html.slim new file mode 100644 index 000000000..7fd8b4456 --- /dev/null +++ b/app/views/import_resources/show.html.slim @@ -0,0 +1,52 @@ +- breadcrumb :import_resource, @import_resource + +.page_content.import_messages + .container-fluid + .row + .col-lg-12 + - metadata = { 'Bilan d\'import' => link_to(@import_resource.root_import.name, workbench_import_path(@import_resource.root_import.workbench, @import_resource.root_import) ), + 'Jeu de données associé' => ( @import_resource.referential.present? ? link_to(@import_resource.referential.name, referential_path(@import_resource.referential)) : '-' ) } + - metadata = metadata.update({t('.status') => import_status(@import_resource.status, verbose: true) }) + = definition_list t('metadatas'), metadata + + + .col-lg-12 + .error_messages + = render 'shared/iev_interfaces/messages', messages: @import_resource.messages + + + // XXX + //- if @import_resource.children.present? + - if @import_resource&.netex_import&.resources.present? + .col-lg-12 + h2 = t('.table_title') + .col-lg-12 + = t('.table_explanation') + .col-lg-12 + = table_builder_2 @import_resource.netex_import.resources.where(resource_type: :file), + [ \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + sortable: false, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| import_resource_status(n.status) }, \ + sortable: false, \ + ), \ + TableBuilderHelper::Column.new( \ + name: 'Résultat des tests' , \ + attribute: Proc.new { |n| I18n.t('import_resources.index.metrics', n.metrics.deep_symbolize_keys) }, \ + sortable: false, \ + ), \ + TableBuilderHelper::Column.new( \ + name: 'Téléchargement' , \ + attribute: Proc.new { |n| '<i class="fa fa-download" aria-hidden="true"></i>'.html_safe }, \ + sortable: false, \ + link_to: lambda do |import_resource| \ + workbench_import_import_resource_import_messages_path(import_resource.import.workbench, import_resource.import, import_resource, format: 'csv' ) \ + end \ + ), \ + ], + cls: 'table has-search' diff --git a/app/views/imports/import/_gtf.html.slim b/app/views/imports/import/_gtf.html.slim new file mode 100644 index 000000000..e50b45888 --- /dev/null +++ b/app/views/imports/import/_gtf.html.slim @@ -0,0 +1,43 @@ +- breadcrumb :gtfs_import, @workbench, @import + +.row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + - metadata = {t('.status') => import_status(@import.status, verbose: true) } + - metadata = metadata.update({t('.referential') => @import.referential ? link_to(@import.referential.name, [@import.referential]) : "-" }) + = definition_list t('metadatas'), metadata + +.col-lg-12 + .error_messages + = render 'shared/iev_interfaces/messages', messages: @import.main_resource.messages + +- if @import.resources.any? + .col-lg-12 + = table_builder_2 @import.resources, + [ \ + TableBuilderHelper::Column.new( \ + name: t('imports.show.referential_name'), \ + attribute: 'name', \ + sortable: false, \ + link_to: lambda do |item| \ + referential_path(item.referential) if item.referential.present? \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| import_status(n.status, verbose: true, default_status: :pending) }, \ + sortable: false, \ + link_to: lambda do |item| \ + item.netex_import.present? ? [@import.workbench, item.netex_import] : [@import.workbench, @import, item] \ + end \ + )\ + ], + cls: 'table', + overhead: [ \ + {}, \ + {}, \ + { \ + title: I18n.t('imports.show.summary').html_safe, \ + width: controls.size, \ + cls: 'overheaded-default colspan="2"' \ + } \ + ] diff --git a/app/views/imports/import/_netex.html.slim b/app/views/imports/import/_netex.html.slim new file mode 100644 index 000000000..ab0ed20d7 --- /dev/null +++ b/app/views/imports/import/_netex.html.slim @@ -0,0 +1,45 @@ +- breadcrumb :netex_import, @workbench, @import + +.row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + - metadata = {Import::Base.tmf(:status) => import_status(@import.status, verbose: true) } + - metadata = metadata.update({Import::Base.tmf(:referential) => @import.referential ? link_to(@import.referential.name, [@import.referential]) : "-" }) + = definition_list t('metadatas'), metadata + +.col-lg-12 + .error_messages + = render 'shared/iev_interfaces/messages', messages: @import.main_resource.messages + +- if @import.resources.present? + .col-lg-12 + h2 = t('imports.show.netex.table_title') + .col-lg-12 + = t('imports.show.netex.table_explanation') + .col-lg-12 + = table_builder_2 @import.resources.where(resource_type: :file), + [ \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + sortable: false, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| import_resource_status(n.status) }, \ + sortable: false, \ + ), \ + TableBuilderHelper::Column.new( \ + name: t('imports.show.table.test_results') , \ + attribute: Proc.new { |n| I18n.t('import_resources.index.metrics', n.metrics.deep_symbolize_keys) }, \ + sortable: false, \ + ), \ + TableBuilderHelper::Column.new( \ + name: t('imports.show.table.download') , \ + attribute: Proc.new { |n| '<i class="fa fa-download" aria-hidden="true"></i>'.html_safe }, \ + sortable: false, \ + link_to: lambda do |import_resource| \ + workbench_import_import_resource_import_messages_path(import_resource.import.workbench, import_resource.import, import_resource, format: 'csv' ) \ + end \ + ), \ + ], + cls: 'table has-search' diff --git a/app/views/imports/import/_workbench.html.slim b/app/views/imports/import/_workbench.html.slim new file mode 100644 index 000000000..cbdf604c2 --- /dev/null +++ b/app/views/imports/import/_workbench.html.slim @@ -0,0 +1,55 @@ +- breadcrumb :import, @workbench, @import + +.row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + - metadata = { t('.data_recovery') => '-', t('imports.show.filename') => @import.try(:file_identifier)} + - metadata = metadata.update({Import::Base.tmf(:status) => import_status(@import.status, verbose: true) }) + = definition_list t('metadatas'), metadata + +.col-lg-12 + .error_messages + = render 'shared/iev_interfaces/messages', messages: @import.messages + +ruby: + controls = @workbench.workgroup.import_compliance_control_sets.map do |key, label| + TableBuilderHelper::Column.new( + name: label, + attribute: Proc.new { |n| n.workbench.compliance_control_set(key).present? ? import_status(n.workbench_import_check_set(key)&.status, verbose: true, default_status: (n.status == "ERROR" ? :aborted : :pending)) : '-' }, + sortable: false, + link_to: lambda do |item| + item.workbench_import_check_set(key).present? && [@import.workbench, item.workbench_import_check_set(key)] + end + ) + end + +- if @import.resources.any? + .col-lg-12 + = table_builder_2 @import.resources, + [ \ + TableBuilderHelper::Column.new( \ + name: t('imports.show.referential_name'), \ + attribute: 'name', \ + sortable: false, \ + link_to: lambda do |item| \ + referential_path(item.referential) if item.referential.present? \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| import_status(n.netex_import&.status || n.status, verbose: true, default_status: :pending) }, \ + sortable: false, \ + link_to: lambda do |item| \ + item.netex_import.present? ? [@import.workbench, item.netex_import] : [@import.workbench, @import, item] \ + end \ + ), *controls \ + ], + cls: 'table', + overhead: [ \ + {}, \ + {}, \ + controls.present? ? { \ + title: I18n.t('imports.show.summary').html_safe, \ + width: controls.size, \ + cls: "overheaded-default colspan='#{controls.size}'" \ + } : nil \ + ].compact diff --git a/app/views/imports/show.html.slim b/app/views/imports/show.html.slim index 9d0a6423d..fb83e9a06 100644 --- a/app/views/imports/show.html.slim +++ b/app/views/imports/show.html.slim @@ -1,60 +1,5 @@ -- breadcrumb :import, @workbench, @import - - page_header_content_for @import .page_content .container-fluid - .row - .col-lg-6.col-md-6.col-sm-12.col-xs-12 - = definition_list t('metadatas'), { t('.data_recovery') => '-', t('.filename') => @import.try(:file_identifier)} - - .row - .col-lg-12 - .error_messages - = render 'shared/iev_interfaces/messages', messages: @import.messages - - - if @import.children.any? - .row - .col-lg-12 - = table_builder_2 @import.children, - [ \ - TableBuilderHelper::Column.new( \ - name: t('.referential_name'), \ - attribute: 'name', \ - sortable: false, \ - link_to: lambda do |import| \ - referential_path(import.referential) if import.referential.present? \ - end \ - ), \ - TableBuilderHelper::Column.new( \ - key: :status, \ - attribute: Proc.new { |n| import_status(n.status) }, \ - sortable: false, \ - link_to: lambda do |import| \ - workbench_import_import_resources_path(import.workbench_id, import) \ - end \ - ), \ - TableBuilderHelper::Column.new( \ - name: t('.stif_control'), \ - attribute: '', \ - sortable: false, \ - ), \ - TableBuilderHelper::Column.new( \ - name: t('.organisation_control'), \ - attribute: '', \ - sortable: false, \ - ) \ - ], - cls: 'table', - overhead: [ \ - {}, \ - { \ - title: I18n.t('imports.show.results', count: @import.children_succeedeed, total: @import.children.count), \ - width: 1, \ - cls: "#{@import.import_status_css_class} full-border" \ - }, { \ - title: I18n.t('imports.show.summary').html_safe, \ - width: 2, \ - cls: 'overheaded-default colspan="2"' \ - } \ - ] + = render partial: "imports/#{@import.type.tableize.singularize}" diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index abf39c089..596627996 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -8,6 +8,8 @@ html lang=I18n.locale title = t('brandname') + = favicon_link_tag + = stylesheet_link_tag 'base' = stylesheet_link_tag 'application' 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 0b55578a7..004ea9050 100644 --- a/app/views/layouts/navigation/_main_nav_left_content.html.slim +++ b/app/views/layouts/navigation/_main_nav_left_content.html.slim @@ -15,6 +15,9 @@ span = t('layouts.navbar.workbench_outputs.organisation') = link_to '#', class: 'list-group-item disabled' do span = t('layouts.navbar.workbench_outputs.workgroup') + - if policy(workbench.workgroup).edit? + = link_to [:edit, workbench.workgroup], class: 'list-group-item' do + span = t('layouts.navbar.workbench_outputs.edit_workgroup') .menu-item.panel .panel-heading @@ -36,7 +39,10 @@ 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 - + - if policy(workbench).edit? + = link_to [:edit, workbench], class: 'list-group-item' do + span = t('workbenches.edit.link') + .menu-item.panel .panel-heading h4.panel-title @@ -62,3 +68,18 @@ .list-group = 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.configuration') + + #miSix.panel-collapse.collapse + .list-group + - if policy(workbench).edit? + = link_to [:edit, workbench], class: 'list-group-item' do + span = t('layouts.navbar.workbench_configuration') + - if policy(workbench.workgroup).edit? + = link_to [:edit, workbench.workgroup], class: 'list-group-item' do + span = t('layouts.navbar.workgroup_configuration') 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 9404eeae6..7bea0814e 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 @@ -70,10 +70,25 @@ .panel-heading h4.panel-title = link_to '#miSix', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do - = t('layouts.navbar.tools') + = t('layouts.navbar.configuration') #miSix.panel-collapse.collapse .list-group + - if policy(workbench).edit? + = link_to [:edit, workbench], class: 'list-group-item' do + span = t('layouts.navbar.workbench_configuration') + - if policy(workbench.workgroup).edit? + = link_to [:edit, workbench.workgroup], class: 'list-group-item' do + span = t('layouts.navbar.workgroup_configuration') + + .menu-item.panel + .panel-heading + h4.panel-title + = link_to '#miSeven', data: {toggle: 'collapse', parent: '#menu-items'}, 'aria-expanded' => 'false' do + = t('layouts.navbar.tools') + + #miSeven.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 diff --git a/app/views/referential_vehicle_journeys/_consolidated.html.slim b/app/views/referential_vehicle_journeys/_consolidated.html.slim new file mode 100644 index 000000000..4a1866e8a --- /dev/null +++ b/app/views/referential_vehicle_journeys/_consolidated.html.slim @@ -0,0 +1,49 @@ +.row.consolidated-view + - @consolidated.lines.each do |line| + = render partial: "consolidated_line", object: line + .col-md-12 + = new_pagination @consolidated.paginated_lines, 'pull-right' + +coffee: + $('a.toggle-timetables').click (e)-> + e.preventDefault() + $(e.target).toggleClass 'active' + $(e.target).parents('.table').find('.detailed-timetables').customToggle() + + $('a.toggle-purchase-windows').click (e)-> + e.preventDefault() + $(e.target).toggleClass 'active' + $(e.target).parents('.table').find('.detailed-purchase-windows').customToggle() + + $('.route').find('.togglable.hidden').each (i, e)-> + $e = $(e) + $e.removeClass 'hidden' + e.setAttribute 'data-original-height', $e.innerHeight() + e.setAttribute 'data-original-full-height', $e.outerHeight(true) + $e.css height: 0 + $e.addClass 'ready' + + $('a.toggle-route').click (e)-> + e.preventDefault() + $(e.currentTarget).toggleClass 'active' + tab = $(e.currentTarget).parents('.route').find('.vehicle-journeys') + tab.customToggle() + + $.fn.extend + customToggle: (propagate=true)-> + height = parseFloat this.attr('data-original-height') + fullHeight = parseFloat this.attr('data-original-full-height') + if this.hasClass 'open' + this.css height: 0 + this.removeClass 'open' + this.find('.togglable.open').customToggle(false) + this.find('a.active').removeClass 'active' + if propagate + for parent in this.parents(".togglable.open") + $(parent).height $(parent).height() - fullHeight + else + this.addClass 'open' + this.height height + if propagate + for parent in this.parents(".togglable.open") + $(parent).height $(parent).height() + fullHeight diff --git a/app/views/referential_vehicle_journeys/_consolidated_line.html.slim b/app/views/referential_vehicle_journeys/_consolidated_line.html.slim new file mode 100644 index 000000000..c73c65961 --- /dev/null +++ b/app/views/referential_vehicle_journeys/_consolidated_line.html.slim @@ -0,0 +1,119 @@ +.line.col-md-12 + .head + span + = Chouette::Line.ts + | + strong= consolidated_line.name + .routes + - consolidated_line.routes.each do |route| + .route + .head class="#{route.highlighted? ? 'highlighted' : ''}" + = link_to '#', class: 'toggle-route' do + span.sb.sb-route + | + = Chouette::Route.ts + | + strong= route.name + .pull-right + = route.highlighted_count + | + = Chouette::VehicleJourney.t + | + span.fa.fa-angle-up + .table.table-2entries.vehicle-journeys.hidden.togglable + .t2e-head.w20 + .th + div + strong= Chouette::VehicleJourney.tmf(:id) + div= Chouette::VehicleJourney.tmf(:name) + div= Chouette::VehicleJourney.tmf(:journey_pattern_id) + div + = link_to '#', class: 'toggle-purchase-windows detailed-timetables-bt' do + span.fa.fa-angle-up + = Chouette::PurchaseWindow.t + .detailed-purchase-windows.hidden.togglable + - route.purchase_windows.uniq.each do |tt| + div + p + = link_to [@referential, tt] do + span.fa.fa-calendar style={color: (tt.color ? tt.color : '#4B4B4B')} + | + = tt.name + + p= tt.bounding_dates.split(' ').join(' > ') + div + = link_to '#', class: 'toggle-timetables detailed-timetables-bt' do + span.fa.fa-angle-up + = Chouette::TimeTable.t + + .detailed-timetables.hidden.togglable + - route.time_tables.uniq.each do |tt| + div + p + = link_to [@referential, tt] do + span.fa.fa-calendar style={color: (tt.color ? tt.color : '#4B4B4B')} + | + = tt.display_day_types + + p= tt.bounding_dates.split(' ').join(' > ') + - prev_sp = nil + - route.stop_points.each do |sp| + ruby: + headline = vehicle_journey_stop_headline prev_sp, sp + prev_sp = sp + .td class="#{sp.highlighted? ? 'highlighted' : ''}" + div title="#{sp.stop_area.city_name ? "#{sp.stop_area.city_name} (#{sp.stop_area.zip_code})" : ''}" data-headline=headline class=(headline.present? ? 'headlined' : '') + span + = sp.name + - if sp.stop_area.time_zone_formatted_offset + span.small + | + = "(#{sp.stop_area.time_zone_formatted_offset})" + .t2e-item-list.w80 + div + - route.vehicle_journeys.each do |journey| + .t2e-item class="#{journey.highlighted? ? 'highlighted' : ''}" + .th + div + strong= link_to journey.id, [@referential, journey.route.line, journey.route, :vehicle_journeys] + div + = link_to journey.published_journey_name, [@referential, journey.route.line, journey.route, :vehicle_journeys], title: journey.published_journey_name + div= journey.journey_pattern.get_objectid.short_id + div + - journey.purchase_windows[0..3].each do |tt| + span.vj_tt + = link_to [@referential, tt], target: :blank do + span.fa.fa-calendar style="color: #{tt.color ? tt.color : '#4B4B4B'}" + - if journey.purchase_windows.size > 3 + span.vj_tt = "+ #{journey.purchase_windows.size - 3}" + .detailed-purchase-windows.hidden.togglable + - route.purchase_windows.uniq.each do |tt| + div class=(journey.has_purchase_window?(tt) ? 'active' : 'inactive') + div + - journey.time_tables[0..3].each do |tt| + span.vj_tt + = link_to [@referential, tt], target: :blank do + span.fa.fa-calendar style="color: #{tt.color ? tt.color : '#4B4B4B'}" + - if journey.time_tables.size > 3 + span.vj_tt = "+ #{journey.time_tables.size - 3}" + .detailed-timetables.hidden.togglable + - route.time_tables.uniq.each do |tt| + div class=(journey.has_time_table?(tt) ? 'active' : 'inactive') + + - prev_sp = nil + - route.stop_points.each do |sp| + ruby: + headline = vehicle_journey_stop_headline prev_sp, sp + prev_sp = sp + vjas = journey.vehicle_journey_at_stops.where(stop_point_id: sp.id).last + .td class="#{vjas && sp.highlighted? ? 'highlighted' : ''} #{vjas.nil? ? 'disabled' : ''} #{headline.present? ? 'headlined' : ''}" + div title="#{sp.stop_area.city_name ? "#{sp.stop_area.city_name} (#{sp.stop_area.zip_code})" : ''}" data-headline=headline class=(headline.present? ? 'headlined' : '') + - if vjas.present? + - if vjas.departure_time == vjas.arrival_time + = vjas.departure_time.l(format: "%H:%M") + - else + = vjas.arrival_time.l(format: "%H:%M") + | - + = vjas.departure_time.l(format: "%H:%M") + - else + | 00:00 diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim index a6e289b97..31053c5ba 100644 --- a/app/views/referential_vehicle_journeys/_filters.html.slim +++ b/app/views/referential_vehicle_journeys/_filters.html.slim @@ -1,10 +1,20 @@ = search_form_for @q, url: referential_vehicle_journeys_path(@referential), html: {method: :get}, class: 'form form-filter' do |f| - .ffg-row + input type="hidden" name="display" value=params[:display] + .ffg-row.w85 .input-group.search_bar = f.search_field :published_journey_name_or_objectid_cont, placeholder: t('.published_journey_name_or_objectid'), class: 'form-control' span.input-group-btn button.btn.btn-default#search-btn type='submit' span.fa.fa-search + .ffg-row.w15 + - if has_feature?(:consolidated_offers) + .form-group + .btn-group + = link_to referential_vehicle_journeys_path(@referential, q: params[:q], display: :list), class: 'btn btn-default ' + (params[:display] != "consolidated" ? 'active' : '') do + span.fa.fa-align-justify + = link_to referential_vehicle_journeys_path(@referential, q: params[:q], display: :consolidated), class: 'btn btn-default ' + (params[:display] == "consolidated" ? 'active' : '') do + span.fa.fa-th-large + .ffg-row .form-group.per-page-select = I18n.t("simple_form.per_page") @@ -73,5 +83,5 @@ .actions - = link_to t('actions.erase'), referential_vehicle_journeys_path(@referential), class: 'btn btn-link' + = link_to t('actions.erase'), referential_vehicle_journeys_path(@referential, display: params[:display]), class: 'btn btn-link' = f.submit t('actions.filter'), class: 'btn btn-default' diff --git a/app/views/referential_vehicle_journeys/_list.html.slim b/app/views/referential_vehicle_journeys/_list.html.slim new file mode 100644 index 000000000..74f8238f8 --- /dev/null +++ b/app/views/referential_vehicle_journeys/_list.html.slim @@ -0,0 +1,49 @@ +.row + .col-lg-12 + .select_table + = table_builder_2 @vehicle_journeys, + [ \ + TableBuilderHelper::Column.new( \ + name: t('objectid'), \ + attribute: Proc.new { |n| n.get_objectid.short_id }, \ + sortable: false \ + ), \ + TableBuilderHelper::Column.new( \ + key: :published_journey_name, \ + attribute: 'published_journey_name', \ + link_to: lambda do |vehicle_journey| \ + vehicle_journey.published_journey_name ? referential_line_route_vehicle_journeys_path(@referential, vehicle_journey.route.line, vehicle_journey.route) : '' \ + end, \ + sortable: true \ + ), \ + TableBuilderHelper::Column.new( \ + key: :line, \ + attribute: Proc.new {|v| v.route.line.name}, \ + sortable: true \ + ), \ + TableBuilderHelper::Column.new( \ + key: :route, \ + attribute: Proc.new {|v| v.route.name}, \ + sortable: true \ + ), \ + TableBuilderHelper::Column.new( \ + key: :departure_time, \ + attribute: Proc.new {|v| v.vehicle_journey_at_stops.first&.departure_local }, \ + sortable: true \ + ), \ + [@starting_stop, @ending_stop].compact.map{|stop| \ + TableBuilderHelper::Column.new( \ + attribute: Proc.new {|v| v.vehicle_journey_at_stops.where("stop_points.stop_area_id" => stop.id).last&.arrival_local }, \ + sortable: false, \ + name: stop.name \ + )\ + }, \ + TableBuilderHelper::Column.new( \ + key: :arrival_time, \ + attribute: Proc.new {|v| v.vehicle_journey_at_stops.last&.arrival_local }, \ + sortable: true, \ + ), \ + ].flatten.compact, + cls: 'table has-filter has-search' + + = new_pagination @vehicle_journeys, 'pull-right' diff --git a/app/views/referential_vehicle_journeys/index.html.slim b/app/views/referential_vehicle_journeys/index.html.slim index 00f63cb65..d1d1dae07 100644 --- a/app/views/referential_vehicle_journeys/index.html.slim +++ b/app/views/referential_vehicle_journeys/index.html.slim @@ -9,55 +9,11 @@ = render 'filters' - if @vehicle_journeys.present? - .row - .col-lg-12 - .select_table - = table_builder_2 @vehicle_journeys, - [ \ - TableBuilderHelper::Column.new( \ - name: t('objectid'), \ - attribute: Proc.new { |n| n.get_objectid.short_id }, \ - sortable: false \ - ), \ - TableBuilderHelper::Column.new( \ - key: :published_journey_name, \ - attribute: 'published_journey_name', \ - link_to: lambda do |vehicle_journey| \ - vehicle_journey.published_journey_name ? referential_line_route_vehicle_journeys_path(@referential, vehicle_journey.route.line, vehicle_journey.route) : '' \ - end, \ - sortable: true \ - ), \ - TableBuilderHelper::Column.new( \ - key: :line, \ - attribute: Proc.new {|v| v.route.line.name}, \ - sortable: true \ - ), \ - TableBuilderHelper::Column.new( \ - key: :route, \ - attribute: Proc.new {|v| v.route.name}, \ - sortable: true \ - ), \ - TableBuilderHelper::Column.new( \ - key: :departure_time, \ - attribute: Proc.new {|v| v.vehicle_journey_at_stops.first&.departure_local }, \ - sortable: true \ - ), \ - [@starting_stop, @ending_stop].compact.map{|stop| \ - TableBuilderHelper::Column.new( \ - attribute: Proc.new {|v| v.vehicle_journey_at_stops.where("stop_points.stop_area_id" => stop.id).last&.arrival_local }, \ - sortable: false, \ - name: stop.name \ - )\ - }, \ - TableBuilderHelper::Column.new( \ - key: :arrival_time, \ - attribute: Proc.new {|v| v.vehicle_journey_at_stops.last&.arrival_local }, \ - sortable: true, \ - ), \ - ].flatten.compact, - cls: 'table has-filter has-search' + - if params[:display] == "consolidated" && has_feature?(:consolidated_offers) + = render partial: "consolidated" + - else + = render partial: "list" - = new_pagination @vehicle_journeys, 'pull-right' - unless @vehicle_journeys.any? .row.mt-xs diff --git a/app/views/referentials/_filters.html.slim b/app/views/referentials/_filters.html.slim index 36db5bfb5..ebaefb0f2 100644 --- a/app/views/referentials/_filters.html.slim +++ b/app/views/referentials/_filters.html.slim @@ -14,12 +14,12 @@ - if (network_ids = @referential.lines.pluck(:network_id).uniq.compact).size > 1 .form-group.togglable class=filter_item_class(params[:q], :network_id_eq_any) = f.label t('activerecord.attributes.referential.networks'), required: false, class: 'control-label' - = f.input :network_id_eq_any, collection: network_ids, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{LineReferential.first.networks.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } + = f.input :network_id_eq_any, collection: network_ids, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{Chouette::Network.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } - if (company_ids = @referential.lines.pluck(:company_id).uniq.compact).size > 1 .form-group.togglable class=filter_item_class(params[:q], :company_id_eq_any) = f.label t('activerecord.attributes.referential.companies'), required: false, class: 'control-label' - = f.input :company_id_eq_any, collection: company_ids, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{LineReferential.first.companies.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } + = f.input :company_id_eq_any, collection: company_ids, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{Chouette::Company.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } .actions = link_to t('actions.erase'), @workbench, class: 'btn btn-link' diff --git a/app/views/shared/_development_toolbar.html.slim b/app/views/shared/_development_toolbar.html.slim index 836066b3d..1d45c41d0 100644 --- a/app/views/shared/_development_toolbar.html.slim +++ b/app/views/shared/_development_toolbar.html.slim @@ -1,4 +1,4 @@ -- if Rails.application.config.development_toolbar +- if Rails.application.config.development_toolbar && current_user = modalbox 'development-toolbar' do = form_tag development_toolbar_update_settings_path, authenticity_token: true do .modal-header diff --git a/app/views/workbenches/_form.html.slim b/app/views/workbenches/_form.html.slim index 534a5f378..c7b6fd4f3 100644 --- a/app/views/workbenches/_form.html.slim +++ b/app/views/workbenches/_form.html.slim @@ -1,9 +1,10 @@ -= simple_form_for @workbench, html: { class: 'form-horizontal', id: 'workbench_form' }, wrapper: :horizontal_form do |f| += title_tag t('activerecord.models.compliance_control_set.other') + += simple_form_for @workbench, html: { class: 'form-horizontal', id: 'workbench_form' }, wrapper: :horizontal_form, title: "prout" do |f| .row .col-lg-12 - = f.input :import_compliance_control_set_id, as: :select, collection: current_organisation.compliance_control_sets, value_method: :id - = f.input :merge_compliance_control_set_id, as: :select, collection: current_organisation.compliance_control_sets, value_method: :id - - .separator + = f.fields_for :compliance_control_set_ids do |ff| + - @workbench.workgroup.compliance_control_sets_by_workbench.each do |cc, label| + = ff.input cc, as: :select, collection: current_organisation.compliance_control_sets, value_method: :id, label: label, selected: @workbench.compliance_control_set(cc).try(:id).try(:to_s), include_blank: true = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'workbench_form' diff --git a/app/views/workbenches/edit.html.slim b/app/views/workbenches/edit.html.slim index 893024490..0774610e1 100644 --- a/app/views/workbenches/edit.html.slim +++ b/app/views/workbenches/edit.html.slim @@ -1,4 +1,4 @@ -- breadcrumb @workbench +- breadcrumb :workbench_configure, @workbench - page_header_content_for @workbench .page_content diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim index b0276c5ce..213c9e5f2 100644 --- a/app/views/workbenches/show.html.slim +++ b/app/views/workbenches/show.html.slim @@ -62,7 +62,10 @@ attribute: Proc.new {|w| w.merged_at ? l(w.merged_at, format: :short) : '-'} \ ) \ ], - selectable: ->(ref){ @workbench.referentials.include?(ref) }, + selectable: ->(ref) { \ + @workbench.referentials.include?(ref) && \ + !ref.pending? \ + }, cls: 'table has-filter has-search', action: :index diff --git a/app/views/workgroups/_form.html.slim b/app/views/workgroups/_form.html.slim new file mode 100644 index 000000000..52d1faca8 --- /dev/null +++ b/app/views/workgroups/_form.html.slim @@ -0,0 +1,17 @@ += title_tag t('activerecord.models.compliance_control_set.other') + += simple_form_for @workgroup, html: { class: 'form-horizontal', id: 'workgroup_form' }, wrapper: :horizontal_form do |f| + table.table + thead + th + - @workgroup.compliance_control_sets_by_workgroup.values.each do |cc| + th= cc + - @workgroup.workbenches.each_with_index do |w,i| + tr + th= w.organisation.name + - @workgroup.compliance_control_sets_by_workgroup.keys.each do |cc| + td + = hidden_field_tag "workgroup[workbenches_attributes][#{i}][id]", w.id + = select_tag "workgroup[workbenches_attributes][#{i}][compliance_control_set_ids][#{cc}]", options_from_collection_for_select(current_organisation.compliance_control_sets, :id, :name, w.compliance_control_set(cc).try(:id)), include_blank: true + + = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'workgroup_form' diff --git a/app/views/workgroups/edit.html.slim b/app/views/workgroups/edit.html.slim new file mode 100644 index 000000000..49847acf2 --- /dev/null +++ b/app/views/workgroups/edit.html.slim @@ -0,0 +1,8 @@ +- breadcrumb @workgroup +- page_header_content_for @workgroup + +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + == render 'form' diff --git a/app/workers/compliance_control_set_copy_worker.rb b/app/workers/compliance_control_set_copy_worker.rb index d18bb0c88..b87f5ad8e 100644 --- a/app/workers/compliance_control_set_copy_worker.rb +++ b/app/workers/compliance_control_set_copy_worker.rb @@ -1,8 +1,9 @@ class ComplianceControlSetCopyWorker include Sidekiq::Worker - def perform(control_set_id, referential_id) + def perform(control_set_id, referential_id, parent_type = nil, parent_id = nil) check_set = ComplianceControlSetCopier.new.copy(control_set_id, referential_id) + check_set.update parent_type: parent_type, parent_id: parent_id if parent_type && parent_id begin Net::HTTP.get(URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/validator/new?id=#{check_set.id}")) diff --git a/app/workers/workbench_import_worker/object_state_updater.rb b/app/workers/workbench_import_worker/object_state_updater.rb index 1edc6b9a1..39d5f20b1 100644 --- a/app/workers/workbench_import_worker/object_state_updater.rb +++ b/app/workers/workbench_import_worker/object_state_updater.rb @@ -2,6 +2,11 @@ class WorkbenchImportWorker module ObjectStateUpdater + def resource entry + @_resources ||= {} + @_resources[entry.name] ||= workbench_import.resources.find_or_create_by(name: entry.name, resource_type: "referential") + end + def update_object_state entry, count workbench_import.update( total_steps: count ) update_spurious entry @@ -14,44 +19,48 @@ class WorkbenchImportWorker def update_foreign_lines entry return if entry.foreign_lines.empty? - workbench_import.messages.create( + resource(entry).messages.create( criticity: :error, message_key: 'foreign_lines_in_referential', message_attributes: { 'source_filename' => workbench_import.file.file.file, 'foreign_lines' => entry.foreign_lines.join(', ') }) + resource(entry).update status: :ERROR end def update_spurious entry return if entry.spurious.empty? - workbench_import.messages.create( + resource(entry).messages.create( criticity: :error, message_key: 'inconsistent_zip_file', message_attributes: { 'source_filename' => workbench_import.file.file.file, 'spurious_dirs' => entry.spurious.join(', ') }) + resource(entry).update status: :ERROR end def update_missing_calendar entry return unless entry.missing_calendar - workbench_import.messages.create( + resource(entry).messages.create( criticity: :error, message_key: 'missing_calendar_in_zip_file', message_attributes: { 'source_filename' => entry.name }) + resource(entry).update status: :ERROR end def update_wrong_calendar entry return unless entry.wrong_calendar - workbench_import.messages.create( + resource(entry).messages.create( criticity: :error, message_key: 'wrong_calendar_in_zip_file', message_attributes: { 'source_filename' => entry.name }) + resource(entry).update status: :ERROR end end end |
