diff options
Diffstat (limited to 'app')
28 files changed, 689 insertions, 28 deletions
diff --git a/app/controllers/merges_controller.rb b/app/controllers/merges_controller.rb new file mode 100644 index 000000000..26e2c2e3c --- /dev/null +++ b/app/controllers/merges_controller.rb @@ -0,0 +1,34 @@ +class MergesController < ChouetteController + # include PolicyChecker + + defaults resource_class: Merge + belongs_to :workbench + + respond_to :html + + before_action :set_mergeable_controllers, only: [:new] + + private + + def set_mergeable_controllers + @mergeable_referentials ||= parent.referentials.ready.not_in_referential_suite + Rails.logger.debug "Mergeables: #{@mergeable_referentials.inspect}" + end + + # def build_resource + # @import ||= WorkbenchImport.new(*resource_params) do |import| + # import.workbench = parent + # import.creator = current_user.name + # end + # end + + def merge_params + params.require(:merge).permit( + referentials: [] + # :name, + # :file, + # :type, + # :referential_id + ) + end +end diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 83e3bc56a..436d5ccb5 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -66,7 +66,7 @@ class ReferentialsController < ChouetteController def validate ComplianceControlSetCopyWorker.perform_async(params[:compliance_control_set], params[:id]) flash[:notice] = t('notice.referentials.validate') - redirect_to(referential_path) + redirect_to workbench_compliance_check_sets_path(referential.workbench_id) end def destroy diff --git a/app/controllers/workbench_outputs_controller.rb b/app/controllers/workbench_outputs_controller.rb new file mode 100644 index 000000000..67ed7569e --- /dev/null +++ b/app/controllers/workbench_outputs_controller.rb @@ -0,0 +1,9 @@ +class WorkbenchOutputsController < ChouetteController + respond_to :html, only: [:show] + defaults resource_class: Workbench + + def show + @workbench = current_organisation.workbenches.find params[:workbench_id] + @workbench_merges = @workbench.merges.order("created_at desc").paginate(page: params[:page], per_page: 10) + end +end diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index 5f6281487..7ac2a7ce7 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -69,6 +69,7 @@ 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 != "non renseigné" ? this.props.value.published_journey_name : '-'}</div> + <div>{this.props.value.company ? this.props.value.company.name : '-'}</div> <div>{this.props.value.journey_pattern.short_id || '-'}</div> <div> {time_tables.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 dc480d6b4..0cac0344c 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -117,6 +117,7 @@ export default class VehicleJourneys extends Component { <div className='strong mb-xs'>ID course</div> <div>Nom course</div> <div>ID mission</div> + <div>Transporteur</div> <div>Calendriers</div> { this.hasFeature('purchase_windows') && <div>Calendriers Commerciaux</div> } </div> diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index 367b00449..366fde188 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -27,7 +27,7 @@ module Chouette def checksum_attributes values = self.slice(*['name', 'published_name', 'registration_number']).values - values << self.stop_points.map(&:stop_area).map(&:user_objectid) + values << self.stop_points.sort_by(&:position).map(&:stop_area).map(&:user_objectid) values.flatten end diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index 2d776e94b..389240ec7 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -21,6 +21,7 @@ module Chouette has_many :journey_patterns, :through => :routes has_many :vehicle_journeys, :through => :journey_patterns has_many :routing_constraint_zones, through: :routes + has_many :time_tables, -> { distinct }, :through => :vehicle_journeys has_and_belongs_to_many :group_of_lines, :class_name => 'Chouette::GroupOfLine', :order => 'group_of_lines.name' diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index 74c20f061..db97dd2fa 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -569,5 +569,56 @@ module Chouette tt.comment = I18n.t("activerecord.copy", :name => self.comment) tt end + + def intersect_periods!(mask_periods) + dates.each do |date| + unless mask_periods.any? { |p| p.include? date.date } + dates.delete date + end + end + + periods.each do |period| + mask_periods_with_common_part = mask_periods.select { |p| p.intersect? period.range } + + if mask_periods_with_common_part.empty? + self.periods.delete period + else + mask_periods_with_common_part.each do |mask_period| + intersection = (mask_period & period.range) + period.period_start, period.period_end = intersection.begin, intersection.end + end + end + end + end + + def remove_periods!(removed_periods) + dates.each do |date| + if removed_periods.any? { |p| p.include? date.date } + dates.delete date + end + end + + periods.each do |period| + modified_ranges = removed_periods.inject([period.range]) do |period_ranges, removed_period| + period_ranges.map { |p| p.remove removed_period }.flatten + end + + unless modified_ranges.empty? + modified_ranges.each_with_index do |modified_range, index| + new_period = index == 0 ? period : periods.build + + new_period.period_start, new_period.period_end = + modified_range.min, modified_range.max + end + else + periods.delete period + end + end + end + + def empty? + dates.empty? && periods.empty? + end + end end diff --git a/app/models/chouette/time_table_period.rb b/app/models/chouette/time_table_period.rb index ab3e79d7e..d9b707675 100644 --- a/app/models/chouette/time_table_period.rb +++ b/app/models/chouette/time_table_period.rb @@ -42,5 +42,10 @@ module Chouette def contains?(p) (p.period_start >= self.period_start && p.period_end <= self.period_end) end + + def range + period_start..period_end + 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 d84bebf18..d4dc82a56 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -70,7 +70,7 @@ module Chouette attrs << self.published_journey_identifier attrs << self.try(:company).try(:get_objectid).try(:local_id) attrs << self.footnotes.map(&:checksum).sort - attrs << self.vehicle_journey_at_stops.map(&:checksum).sort + attrs << self.vehicle_journey_at_stops.sort_by { |s| s.stop_point&.position }.map(&:checksum).sort end end diff --git a/app/models/concerns/checksum_support.rb b/app/models/concerns/checksum_support.rb index c95e23bcf..b700ef286 100644 --- a/app/models/concerns/checksum_support.rb +++ b/app/models/concerns/checksum_support.rb @@ -26,4 +26,11 @@ module ChecksumSupport self.checksum = Digest::SHA256.new.hexdigest(self.checksum_source) end end + + def update_checksum! + set_current_checksum_source + if checksum_source_changed? + update checksum: Digest::SHA256.new.hexdigest(checksum_source) + end + end end diff --git a/app/models/generic_attribute_control/min_max.rb b/app/models/generic_attribute_control/min_max.rb index 1c429b9a4..18873b683 100644 --- a/app/models/generic_attribute_control/min_max.rb +++ b/app/models/generic_attribute_control/min_max.rb @@ -2,8 +2,8 @@ module GenericAttributeControl class MinMax < ComplianceControl store_accessor :control_attributes, :minimum, :maximum, :target - validates :minimum, numericality: true, allow_nil: true - validates :maximum, numericality: true, allow_nil: true + validates_numericality_of :minimum, allow_nil: true, greater_than_or_equal_to: 0 + validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 validates :target, presence: true include MinMaxValuesValidation diff --git a/app/models/merge.rb b/app/models/merge.rb new file mode 100644 index 000000000..4cbbd18ab --- /dev/null +++ b/app/models/merge.rb @@ -0,0 +1,426 @@ +class Merge < ActiveRecord::Base + extend Enumerize + + belongs_to :workbench + validates :workbench, presence: true + + enumerize :status, in: %w[new pending successful failed running], default: :new + + has_array_of :referentials, class_name: 'Referential' + + delegate :output, to: :workbench + + after_commit :merge, :on => :create + + def merge + MergeWorker.perform_async(id) + end + + def name + "Dummy" # FIXME + end + + attr_reader :new + + def merge! + update started_at: Time.now, status: :running + + prepare_new + + referentials.each do |referential| + merge_referential referential + end + + save_current + rescue => e + Rails.logger.error "Merge failed: #{e} #{e.backtrace.join("\n")}" + update status: :failed + raise e if Rails.env.test? + ensure + attributes = { ended_at: Time.now } + attributes[:status] = :successful if status == :running + update attributes + end + + def prepare_new + new = + if workbench.output.current + Rails.logger.debug "Clone current output" + Referential.new_from(workbench.output.current, fixme_functional_scope).tap do |clone| + clone.inline_clone = true + end + else + Rails.logger.debug "Create a new output" + # 'empty' one + attributes = { + workbench: workbench, + organisation: workbench.organisation, # TODO could be workbench.organisation by default + name: I18n.t("merges.referential_name"), + } + workbench.output.referentials.new attributes + end + + new.referential_suite = output + new.organisation = workbench.organisation + new.slug = "output_#{workbench.id}_#{Time.now.to_i}" + + unless new.valid? + Rails.logger.error "New referential isn't valid : #{new.errors.inspect}" + end + + new.save! + + output.update new: new + @new = new + end + + def merge_referential(referential) + Rails.logger.debug "Merge #{referential.slug}" + + metadata_merger = MetadatasMerger.new new, referential + metadata_merger.merge + + new.metadatas.delete metadata_merger.empty_metadatas + + new.save! + + line_periods = LinePeriods.from_metadatas(referential.metadatas) + + new.switch do + line_periods.each do |line_id, periods| + Rails.logger.debug "Clean data for #{line_id} #{periods.inspect}" + + new.lines.find(line_id).time_tables.find_each do |time_table| + time_table.remove_periods! periods + unless time_table.empty? + puts "Remove period on #{time_table.inspect}" + time_table.save! + else + puts "Remove TimeTable #{time_table.inspect}" + time_table.destroy + end + end + end + end + + # let's merge data :) + + # Routes + + # Always the same pattern : + # - load models from original Referential + # - load associated datas (children, checksum for associated models) + # - switch to new Referential + # - enumerate loaded models + # - skip model if its checksum exists "in the same line" + # - prepare attributes for a fresh model + # - remove all primary keys + # - compute an ObjectId (TODO) + # - process children models as nested attributes + # - associated other models (by line/checksum) + # - save! and next one + + referential_routes = referential.switch do + referential.routes.all.to_a + end + + referential_routes_checksums = Hash[referential_routes.map { |r| [ r.id, r.checksum ] }] + + referential_stop_points = referential.switch do + referential.stop_points.all.to_a + end + + referential_stop_points_by_route = referential_stop_points.group_by(&:route_id) + + new.switch do + referential_routes.each do |route| + existing_route = new.routes.find_by line_id: route.line_id, checksum: route.checksum + unless existing_route + attributes = route.attributes.merge( + id: nil, + objectid: "merge:route:#{route.checksum}", #FIXME + # line_id is the same + # all other primary must be changed + opposite_route_id: nil #FIXME + ) + new_route = new.routes.build attributes + + route_stop_points = referential_stop_points_by_route[route.id] + + # Stop Points + route_stop_points.each do |stop_point| + attributes = stop_point.attributes.merge( + id: nil, + route_id: nil, + objectid: "merge:stop_point:#{route.checksum}-#{stop_point.position}", #FIXME + ) + + new_route.stop_points.build attributes + end + + new_route.save! + + if new_route.checksum != route.checksum + raise "Checksum has changed: #{route.inspect} #{new_route.inspect}" + end + end + end + end + + # JourneyPatterns + + referential_journey_patterns, referential_journey_patterns_stop_areas_objectids = referential.switch do + journey_patterns = referential.journey_patterns.includes(:stop_points) + + journey_patterns_stop_areas_objectids = Hash[ + journey_patterns.map do |journey_pattern| + [ journey_pattern.id, journey_pattern.stop_points.map(&:stop_area).map(&:objectid)] + end + ] + + [journey_patterns, journey_patterns_stop_areas_objectids] + end + + referential_journey_patterns_checksums = Hash[referential_journey_patterns.map { |j| [ j.id, j.checksum ] }] + + new.switch do + referential_journey_patterns.each do |journey_pattern| + # find parent route by checksum + # TODO add line_id for security + associated_route_checksum = referential_routes_checksums[journey_pattern.route_id] + existing_associated_route = new.routes.find_by checksum: associated_route_checksum + + existing_journey_pattern = new.journey_patterns.find_by route_id: existing_associated_route.id, checksum: journey_pattern.checksum + + unless existing_journey_pattern + attributes = journey_pattern.attributes.merge( + id: nil, + + objectid: "merge:journey_pattern:#{existing_associated_route.checksum}-#{journey_pattern.checksum}", #FIXME + + # all other primary must be changed + route_id: existing_associated_route.id, + + departure_stop_point_id: nil, # FIXME + arrival_stop_point_id: nil + ) + + stop_areas_objectids = referential_journey_patterns_stop_areas_objectids[journey_pattern.id] + + stop_points = existing_associated_route.stop_points.joins(:stop_area).where("stop_areas.objectid": stop_areas_objectids).order(:position) + attributes.merge!(stop_points: stop_points) + + new_journey_pattern = new.journey_patterns.create! attributes + if new_journey_pattern.checksum != journey_pattern.checksum + raise "Checksum has changed: #{journey_pattern.checksum_source} #{new_journey_pattern.checksum_source}" + end + end + end + end + + # Vehicle Journeys + + referential_vehicle_journeys = referential.switch do + referential.vehicle_journeys.includes(:vehicle_journey_at_stops).all.to_a + end + + new.switch do + referential_vehicle_journeys.each do |vehicle_journey| + # find parent journey pattern by checksum + # TODO add line_id for security + 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_vehicle_journey = new.vehicle_journeys.find_by journey_pattern_id: existing_associated_journey_pattern.id, checksum: vehicle_journey.checksum + + unless existing_vehicle_journey + attributes = vehicle_journey.attributes.merge( + id: nil, + + objectid: "merge:vehicle_journey:#{existing_associated_journey_pattern.checksum}-#{vehicle_journey.checksum}", #FIXME + + # all other primary must be changed + route_id: existing_associated_journey_pattern.route_id, + journey_pattern_id: existing_associated_journey_pattern.id, + ) + new_vehicle_journey = new.vehicle_journeys.build attributes + + # Create VehicleJourneyAtStops + + vehicle_journey.vehicle_journey_at_stops.each_with_index do |vehicle_journey_at_stop, index| + at_stop_attributes = vehicle_journey_at_stop.attributes.merge( + id: nil, + stop_point_id: existing_associated_journey_pattern.stop_points[index].id + ) + new_vehicle_journey.vehicle_journey_at_stops.build at_stop_attributes + end + + 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}" + end + end + + end + end + + # Time Tables + + referential_time_tables_by_id, referential_time_tables_with_lines = referential.switch do + 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") + + # 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 + end + + [ time_tables_by_id, time_tables_by_lines ] + end + + new.switch do + referential_time_tables_with_lines.each do |line_id, time_tables_properties| + # Because TimeTables will be modified according metadata periods + # we're loading timetables per line (line is associated to a period list) + line = workbench.line_referential.lines.find(line_id) + + time_tables_properties.each do |properties| + time_table = referential_time_tables_by_id[properties[:id]] + + # we can't test if TimeTable already exist by checksum + # because checksum is modified by intersect_periods! + + attributes = time_table.attributes.merge( + id: nil, + comment: "Ligne #{line.name} - #{time_table.comment}", + calendar_id: nil + ) + candidate_time_table = new.time_tables.build attributes + + time_table.dates.each do |date| + date_attributes = date.attributes.merge( + id: nil, + time_table_id: nil + ) + candidate_time_table.dates.build date_attributes + end + time_table.periods.each do |period| + period_attributes = period.attributes.merge( + id: nil, + time_table_id: nil + ) + candidate_time_table.periods.build period_attributes + end + + candidate_time_table.intersect_periods! line_periods.periods(line_id) + + # FIXME + candidate_time_table.set_current_checksum_source + candidate_time_table.update_checksum + + # after intersect_periods!, the checksum is the expected one + # we can search an existing TimeTable + + existing_time_table = line.time_tables.find_by checksum: candidate_time_table.checksum + + unless existing_time_table + # FIXME use real ObjectId + # Referential id is (temporary) used because the "same" TimeTable can be defined in several merged Referentials + # and checksum are modified by clean/remove_periods! but this temporary object id is constant + candidate_time_table.objectid = "merge:time_table:#{line.id}-#{candidate_time_table.checksum}-#{referential.id}:LOC" + + candidate_time_table.save! + + # Checksum is changed by #intersect_periods + # if new_time_table.checksum != time_table.checksum + # raise "Checksum has changed: #{time_table.checksum_source} #{new_time_table.checksum_source}" + # end + + existing_time_table = candidate_time_table + end + + # associate VehicleJourney + + associated_vehicle_journey = line.vehicle_journeys.find_by!(checksum: properties[:vehicle_journey_checksum]) + associated_vehicle_journey.time_tables << existing_time_table + end + end + end + end + + def save_current + output.update current: new, new: nil + output.current.update referential_suite: output + end + + def fixme_functional_scope + if attribute = workbench.organisation.sso_attributes.try(:[], "functional_scope") + JSON.parse(attribute) + end + end + + def child_change + + end + + class MetadatasMerger + + attr_reader :merge_metadatas, :referential + def initialize(merge_referential, referential) + @merge_metadatas = merge_referential.metadatas + @referential = referential + end + + delegate :metadatas, to: :referential, prefix: :referential + + def merge + referential_metadatas.each do |metadata| + merge_one metadata + end + end + + def merged_line_metadatas(line_id) + merge_metadatas.select do |m| + m.line_ids.include? line_id + end + end + + def merge_one(metadata) + metadata.line_ids.each do |line_id| + line_metadatas = merged_line_metadatas(line_id) + + metadata.periodes.each do |period| + line_metadatas.each do |m| + m.periodes = m.periodes.map do |existing_period| + existing_period.remove period + end.flatten + end + + attributes = { + line_ids: [line_id], + periodes: [period], + referential_source_id: referential.id, + created_at: metadata.created_at # TODO check required dates + } + + # line_metadatas should not contain conflicted metadatas + merge_metadatas << ReferentialMetadata.new(attributes) + end + end + end + + def empty_metadatas + merge_metadatas.select { |m| m.periodes.empty? } + end + + + end + +end diff --git a/app/models/referential.rb b/app/models/referential.rb index 1cdda9e6a..a5d5acbf9 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -61,6 +61,7 @@ class Referential < ActiveRecord::Base scope :include_metadatas_lines, ->(line_ids) { where('referential_metadata.line_ids && ARRAY[?]::bigint[]', line_ids) } scope :order_by_validity_period, ->(dir) { joins(:metadatas).order("unnest(periodes) #{dir}") } scope :order_by_lines, ->(dir) { joins(:metadatas).group("referentials.id").order("sum(array_length(referential_metadata.line_ids,1)) #{dir}") } + scope :not_in_referential_suite, -> { where referential_suite_id: nil } def save_with_table_lock_timeout(options = {}) save_without_table_lock_timeout(options) @@ -144,6 +145,22 @@ class Referential < ActiveRecord::Base Chouette::PurchaseWindow.all end + def routes + Chouette::Route.all + end + + def journey_patterns + Chouette::JourneyPattern.all + end + + def stop_points + Chouette::StopPoint.all + end + + def compliance_check_sets + ComplianceCheckSet.all + end + before_validation :define_default_attributes def define_default_attributes @@ -151,10 +168,22 @@ class Referential < ActiveRecord::Base self.objectid_format ||= workbench.objectid_format if workbench end - def switch + def switch(&block) raise "Referential not created" if new_record? - Apartment::Tenant.switch!(slug) - self + + unless block_given? + Rails.logger.debug "Referential switch to #{slug}" + Apartment::Tenant.switch! slug + self + else + result = nil + Apartment::Tenant.switch slug do + Rails.logger.debug "Referential switch to #{slug}" + result = yield + end + Rails.logger.debug "Referential back" + result + end end def self.new_from(from, functional_scope) @@ -296,7 +325,7 @@ class Referential < ActiveRecord::Base overlapped_referential_ids.present? end - validate :detect_overlapped_referentials + validate :detect_overlapped_referentials, unless: :in_referential_suite? def detect_overlapped_referentials self.class.where(id: overlapped_referential_ids).each do |referential| @@ -305,8 +334,19 @@ class Referential < ActiveRecord::Base end end + def in_referential_suite? + referential_suite_id.present? + end + + attr_accessor :inline_clone def clone_schema - ReferentialCloning.create(source_referential: created_from, target_referential: self) + cloning = ReferentialCloning.new source_referential: created_from, target_referential: self + + if inline_clone + cloning.clone! + else + cloning.save! + end end def create_schema diff --git a/app/models/referential_cloning.rb b/app/models/referential_cloning.rb index 24117e6c8..a2b23e819 100644 --- a/app/models/referential_cloning.rb +++ b/app/models/referential_cloning.rb @@ -8,19 +8,22 @@ class ReferentialCloning < ActiveRecord::Base ReferentialCloningWorker.perform_async(id) end - def clone! + def clone_with_status! run! - - AF83::SchemaCloner - .new(source_referential.slug, target_referential.slug) - .clone_schema - + clone! successful! rescue Exception => e Rails.logger.error "Clone failed : #{e}" + Rails.logger.error e.backtrace.join('\n') failed! end + def clone! + AF83::SchemaCloner + .new(source_referential.slug, target_referential.slug) + .clone_schema + end + private aasm column: :status do diff --git a/app/models/referential_suite.rb b/app/models/referential_suite.rb index 93c2c3f36..4f825628c 100644 --- a/app/models/referential_suite.rb +++ b/app/models/referential_suite.rb @@ -1,7 +1,7 @@ class ReferentialSuite < ActiveRecord::Base belongs_to :new, class_name: 'Referential' validate def validate_consistent_new - return true if new_id.nil? + return true if new_id.nil? || new.nil? return true if new.referential_suite_id == id errors.add(:inconsistent_new, I18n.t('referential_suites.errors.inconsistent_new', name: new.name)) @@ -9,11 +9,11 @@ class ReferentialSuite < ActiveRecord::Base belongs_to :current, class_name: 'Referential' validate def validate_consistent_current - return true if current_id.nil? + return true if current_id.nil? || current.nil? return true if current.referential_suite_id == id errors.add(:inconsistent_current, I18n.t('referential_suites.errors.inconsistent_current', name: current.name)) end - has_many :referentials + has_many :referentials, -> { order "created_at desc" } end diff --git a/app/models/vehicle_journey_control/delta.rb b/app/models/vehicle_journey_control/delta.rb index 077dd6c4a..f061b9fdd 100644 --- a/app/models/vehicle_journey_control/delta.rb +++ b/app/models/vehicle_journey_control/delta.rb @@ -3,7 +3,7 @@ module VehicleJourneyControl store_accessor :control_attributes, :maximum - validates :maximum, numericality: true, allow_nil: true + validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 def self.default_code; "3-VehicleJourney-3" end end diff --git a/app/models/vehicle_journey_control/speed.rb b/app/models/vehicle_journey_control/speed.rb index 14fad9139..e5e331b50 100644 --- a/app/models/vehicle_journey_control/speed.rb +++ b/app/models/vehicle_journey_control/speed.rb @@ -2,8 +2,8 @@ module VehicleJourneyControl class Speed < ComplianceControl store_accessor :control_attributes, :minimum, :maximum - validates :minimum, numericality: true, allow_nil: true - validates :maximum, numericality: true, allow_nil: true + validates_numericality_of :minimum, allow_nil: true, greater_than_or_equal_to: 0 + validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 include MinMaxValuesValidation def self.default_code; "3-VehicleJourney-2" end diff --git a/app/models/workbench.rb b/app/models/workbench.rb index e36589210..3190246ae 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -14,6 +14,7 @@ class Workbench < ActiveRecord::Base has_many :workbench_imports has_many :compliance_check_sets has_many :compliance_control_sets + has_many :merges validates :name, presence: true validates :organisation, presence: true @@ -29,7 +30,7 @@ class Workbench < ActiveRecord::Base if line_ids.empty? Referential.none else - Referential.joins(:metadatas).where(['referential_metadata.line_ids && ARRAY[?]::bigint[]', line_ids]).ready + Referential.joins(:metadatas).where(['referential_metadata.line_ids && ARRAY[?]::bigint[]', line_ids]).ready.not_in_referential_suite end end diff --git a/app/policies/merge_policy.rb b/app/policies/merge_policy.rb new file mode 100644 index 000000000..82eb72e08 --- /dev/null +++ b/app/policies/merge_policy.rb @@ -0,0 +1,15 @@ +class MergePolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def create? + user.has_permission?('merges.create') + end + + def update? + user.has_permission?('merges.update') + end +end diff --git a/app/views/merges/_form.html.slim b/app/views/merges/_form.html.slim new file mode 100644 index 000000000..ff85ad76b --- /dev/null +++ b/app/views/merges/_form.html.slim @@ -0,0 +1,7 @@ += simple_form_for merge, as: :merge, url: workbench_merges_path(workbench), html: {class: 'form-horizontal', id: 'wb_merge_form'}, wrapper: :horizontal_form do |form| + + .row + .col-lg-12 + = form.input :referentials, :collection => @mergeable_referentials, include_blank: false, input_html: { multiple: true, 'data-select2ed': true } + + = form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'wb_merge_form' diff --git a/app/views/merges/new.html.slim b/app/views/merges/new.html.slim new file mode 100644 index 000000000..dab4bdf4e --- /dev/null +++ b/app/views/merges/new.html.slim @@ -0,0 +1,7 @@ +- breadcrumb :merges, @workbench + +.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', merge: @merge, workbench: @workbench diff --git a/app/views/merges/show.html.slim b/app/views/merges/show.html.slim new file mode 100644 index 000000000..579995ebf --- /dev/null +++ b/app/views/merges/show.html.slim @@ -0,0 +1,13 @@ +- breadcrumb :merge, @merge +- page_header_content_for @merge + +.page_content + .container-fluid + .row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + = definition_list t('metadatas'), + { @merge.class.human_attribute_name(:referentials) => @merge.referentials.map(&:name).join(', '), + @merge.class.human_attribute_name(:status) => @merge.status, + @merge.class.human_attribute_name(:created_at) => @merge.created_at, + @merge.class.human_attribute_name(:started_at) => @merge.started_at, + @merge.class.human_attribute_name(:ended_at) => @merge.ended_at } diff --git a/app/views/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim index f3cd01f46..64e7d4f96 100644 --- a/app/views/stif/dashboards/_dashboard.html.slim +++ b/app/views/stif/dashboards/_dashboard.html.slim @@ -56,9 +56,8 @@ .panel.panel-default .panel-heading h3.panel-title.with_actions - div - = t('.calendars') - span.badge.ml-xs = @dashboard.calendars.count if @dashboard.calendars.present? + = I18n.t("calendars.index.title") + span.badge.ml-xs = @dashboard.calendars.count if @dashboard.calendars.present? div = link_to '', calendars_path, class: ' fa fa-chevron-right pull-right', title: t('.see') diff --git a/app/views/workbench_outputs/show.html.slim b/app/views/workbench_outputs/show.html.slim new file mode 100644 index 000000000..67dc6e8d4 --- /dev/null +++ b/app/views/workbench_outputs/show.html.slim @@ -0,0 +1,33 @@ +/ PageHeader + +- breadcrumb :workbench_output, @workbench +- content_for :page_header_title, t('.title') +- content_for :page_header_content do + .row.mb-sm + .col-lg-12.text-right + = link_to t('.see_current_output'), referential_path(@workbench.output.current), class: 'btn btn-primary' if @workbench.output&.current + = link_to t('merges.actions.create'), new_workbench_merge_path(@workbench), class: 'btn btn-primary' + +.page_content + .container-fluid + .row + .col-lg-12 + = table_builder_2 @workbench_merges, + [ \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| import_status(n.status) }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :started_at, \ + attribute: Proc.new { |n| l(n.started_at, format: :long) if n.started_at }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :creator, \ + attribute: 'creator' \ + ) \ + ], + links: [], + cls: 'table has-search' + + = new_pagination @workbench_merges, 'pull-right' diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim index 1c82c34b7..fe0b05330 100644 --- a/app/views/workbenches/show.html.slim +++ b/app/views/workbenches/show.html.slim @@ -6,6 +6,7 @@ - if policy(Referential).create? = link_to t('actions.import'), workbench_imports_path(@workbench), class: 'btn btn-primary' = link_to t('actions.add'), new_referential_path(workbench_id: @workbench), class: 'btn btn-primary' + = link_to t('workbenches.actions.show_output'), workbench_output_path(@workbench), class: 'btn btn-primary' .page_content .container-fluid diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb new file mode 100644 index 000000000..8a085a25a --- /dev/null +++ b/app/workers/merge_worker.rb @@ -0,0 +1,7 @@ +class MergeWorker + include Sidekiq::Worker + + def perform(id) + Merge.find(id).merge! + end +end diff --git a/app/workers/referential_cloning_worker.rb b/app/workers/referential_cloning_worker.rb index e20148055..e24baa90c 100644 --- a/app/workers/referential_cloning_worker.rb +++ b/app/workers/referential_cloning_worker.rb @@ -2,6 +2,6 @@ class ReferentialCloningWorker include Sidekiq::Worker def perform(id) - ReferentialCloning.find(id).clone! + ReferentialCloning.find(id).clone_with_status! end end |
