diff options
| author | Robert | 2018-01-05 14:49:41 +0100 |
|---|---|---|
| committer | Robert | 2018-01-05 14:49:41 +0100 |
| commit | 8091fab0fa7887b37748bda9d51bdb094e314789 (patch) | |
| tree | 9b5874856b18a3ebf76de15c6984b66cdaa13763 | |
| parent | 568001f48327422d7684a1cafe1e3687ba5e787a (diff) | |
| parent | c2ca588ec9ec70ccd77690feacd45c7aae56b355 (diff) | |
| download | chouette-core-8091fab0fa7887b37748bda9d51bdb094e314789.tar.bz2 | |
Merge branch 'master' of github.com:af83/stif-boiv
48 files changed, 1065 insertions, 54 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 a62da6353..03967abfa 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 983bf5363..332ddb7b8 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..1608550fb 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,20 @@ 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 + def compliance_check_sets + ComplianceCheckSet.all + end + before_validation :define_default_attributes def define_default_attributes @@ -151,10 +166,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 +323,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 +332,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 diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb index 3f6503308..ce5cf5b0f 100644 --- a/config/breadcrumbs.rb +++ b/config/breadcrumbs.rb @@ -6,6 +6,21 @@ crumb :workbench do |workbench| link workbench.name, workbench_path(workbench) end +crumb :workbench_output do |workbench| + link I18n.t('workbench_outputs.show.title'), workbench_output_path(workbench) + parent :workbench, current_offer_workbench +end + +crumb :merges do |workbench| + link I18n.t('merges.index.title'), workbench_output_path(workbench) + parent :workbench, workbench +end + +crumb :merge do |merge| + link breadcrumb_name(merge), workbench_merge_path(merge.workbench, merge) + parent :merges, merge.workbench +end + crumb :referential do |referential| link breadcrumb_name(referential), referential_path(referential) parent :workbench, current_offer_workbench diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb index 69204a5d7..8becd23c2 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -77,7 +77,8 @@ Apartment.configure do |config| 'ComplianceCheckSet', 'ComplianceCheckBlock', 'ComplianceCheckResource', - 'ComplianceCheckMessage' + 'ComplianceCheckMessage', + 'Merge' ] # use postgres schemas? diff --git a/config/locales/calendars.fr.yml b/config/locales/calendars.fr.yml index 88cb275ff..f9eaf1be5 100644 --- a/config/locales/calendars.fr.yml +++ b/config/locales/calendars.fr.yml @@ -25,28 +25,28 @@ fr: standard_calendars: Calendriers standards standard_calendar: Calendrier standard actions: - new: Ajouter un calendrier - edit: Editer cet calendrier - destroy: Supprimer cet calendrier - destroy_confirm: Etes vous sûr de supprimer cet calendrier ? + new: Ajouter un modèle de calendrier + edit: Editer ce modèle de calendrier + destroy: Supprimer ce modèle de calendrier + destroy_confirm: Etes vous sûr de supprimer ce modèle de calendrier ? errors: overlapped_periods: Une autre période chevauche cette période short_period: "Une période doit être d'une durée de deux jours minimum" index: - title: Calendriers + title: Modèles de calendrier all: Tous shared: Partagées not_shared: Non partagées - search_no_results: Aucun calendrier ne correspond à votre recherche + search_no_results: Aucun modèle de calendrier ne correspond à votre recherche date: Date new: - title: Ajouter un calendrier + title: Ajouter un modèle de calendrier create: - title: Ajouter un calendrier + title: Ajouter un modèle de calendrier edit: - title: Editer le calendrier %{name} + title: Editer le modèle de calendrier %{name} show: - title: Calendrier %{name} + title: Modèle de calendrier %{name} simple_form: labels: calendar: @@ -59,8 +59,8 @@ fr: activerecord: models: calendar: - one: "calendrier" - other: "calendriers" + one: "modèle de calendrier" + other: "modèles de calendrier" attributes: calendar: name: Nom diff --git a/config/locales/merges.yml b/config/locales/merges.yml new file mode 100644 index 000000000..1e2df2459 --- /dev/null +++ b/config/locales/merges.yml @@ -0,0 +1,17 @@ +fr: + merges: + index: + title: "Finalisations de l'offre" + new: + title: "Nouvelle finalisation de l'offre" + activerecord: + models: + merge: "Finalisation de l'offre" + attributes: + merge: + created_at: "Créé le" + started_at: Démarrage + ended_at: Achevé à + status: "Etat" + creator: "Opérateur" + referentials: "Jeux de données" diff --git a/config/locales/workbenches.fr.yml b/config/locales/workbenches.fr.yml index d76255e86..eff53c2d6 100644 --- a/config/locales/workbenches.fr.yml +++ b/config/locales/workbenches.fr.yml @@ -6,6 +6,11 @@ fr: zero: "Aucun jeu de données dans cet espace de travail" one: "1 jeu de données dans cet espace de travail" other: "#{count} jeux de données dans cet espace de travail" + actions: + show_output: "Offre finalisée" + workbench_outputs: + show: + title: "Finalisations de l'offre" activerecord: models: workbench: diff --git a/config/routes.rb b/config/routes.rb index bf796a385..8b5faff03 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,6 +15,9 @@ ChouetteIhm::Application.routes.draw do get :executed, on: :member resources :compliance_checks, only: [:show] end + + resource :output, controller: :workbench_outputs + resources :merges end devise_for :users, :controllers => { diff --git a/db/migrate/20171212152452_create_merges.rb b/db/migrate/20171212152452_create_merges.rb new file mode 100644 index 000000000..7915bd91b --- /dev/null +++ b/db/migrate/20171212152452_create_merges.rb @@ -0,0 +1,16 @@ +class CreateMerges < ActiveRecord::Migration + def change + create_table :merges do |t| + t.bigint :workbench_id, index: true, foreign_key: true + t.bigint :referential_ids, array: true + + t.string :creator + t.string :status + + t.datetime :started_at + t.datetime :ended_at + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d7721d9e6..bc71b0ed1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -538,6 +538,19 @@ ActiveRecord::Schema.define(version: 20171227113809) do add_index "lines", ["registration_number"], name: "lines_registration_number_key", using: :btree add_index "lines", ["secondary_company_ids"], name: "index_lines_on_secondary_company_ids", using: :gin + create_table "merges", id: :bigserial, force: :cascade do |t| + t.integer "workbench_id", limit: 8 + t.integer "referential_ids", limit: 8, array: true + t.string "creator" + t.string "status" + t.datetime "started_at" + t.datetime "ended_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "merges", ["workbench_id"], name: "index_merges_on_workbench_id", using: :btree + create_table "networks", id: :bigserial, force: :cascade do |t| t.string "objectid", null: false t.integer "object_version", limit: 8 diff --git a/lib/line_periods.rb b/lib/line_periods.rb new file mode 100644 index 000000000..c176a7a08 --- /dev/null +++ b/lib/line_periods.rb @@ -0,0 +1,35 @@ +class LinePeriods + + def initialize + @periods_by_line = Hash.new { |h,k| h[k] = [] } + end + + def add(line_id, period) + @periods_by_line[line_id] << period + end + + def each(&block) + @periods_by_line.each do |line_id, periods| + yield line_id, periods + end + end + + def periods(line_id) + @periods_by_line[line_id] + end + + def self.from_metadatas(metadatas) + line_periods = new + + metadatas.each do |metadata| + metadata.line_ids.each do |line_id| + metadata.periodes.each do |period| + line_periods.add(line_id, period) + end + end + end + + line_periods + end + +end diff --git a/lib/range_ext.rb b/lib/range_ext.rb index f1df5e70d..e7e0e903f 100644 --- a/lib/range_ext.rb +++ b/lib/range_ext.rb @@ -1,8 +1,24 @@ class Range def intersection(other) - return nil if (self.max < other.min or other.max < self.min) + return nil unless intersect?(other) [self.min, other.min].max..[self.max, other.max].min end alias_method :&, :intersection + + def intersect?(other) + self.max > other.min and other.max > self.min + end + + def remove(other) + return self if (self.max < other.min or other.max < self.min) + + [].tap do |remaining| + remaining << (self.min..other.min-1) if self.min < other.min + remaining << (other.max+1..self.max) if other.max < self.max + remaining.compact! + end + end + alias_method :-, :remove + end diff --git a/lib/stif/permission_translator.rb b/lib/stif/permission_translator.rb index 4acf42884..9e0feb9b8 100644 --- a/lib/stif/permission_translator.rb +++ b/lib/stif/permission_translator.rb @@ -21,6 +21,7 @@ module Stif calendars footnotes imports + merges journey_patterns referentials routes diff --git a/spec/factories/chouette_time_table.rb b/spec/factories/chouette_time_table.rb index a3ff63b2f..81a08ca2a 100644 --- a/spec/factories/chouette_time_table.rb +++ b/spec/factories/chouette_time_table.rb @@ -25,6 +25,7 @@ FactoryGirl.define do end_date = start_date + 10 end time_table.save_shortcuts + time_table.update_checksum! end end diff --git a/spec/factories/chouette_vehicle_journey.rb b/spec/factories/chouette_vehicle_journey.rb index 5f64bd502..7d63a2e58 100644 --- a/spec/factories/chouette_vehicle_journey.rb +++ b/spec/factories/chouette_vehicle_journey.rb @@ -30,6 +30,7 @@ FactoryGirl.define do :arrival_time => "2000-01-01 #{arrival_time} UTC", :departure_time => "2000-01-01 #{departure_time} UTC") end + vehicle_journey.update_checksum! end factory :vehicle_journey_odd do diff --git a/spec/lib/range_ext_spec.rb b/spec/lib/range_ext_spec.rb index 9c44608b9..eee488c91 100644 --- a/spec/lib/range_ext_spec.rb +++ b/spec/lib/range_ext_spec.rb @@ -1,6 +1,6 @@ require 'range_ext' RSpec.describe Range do - context "intersection" do + describe "#intersection" do it "is nil (sic) for two distinct ranges" do expect( (1..2).intersection(3..4) ).to be_nil end @@ -15,4 +15,53 @@ RSpec.describe Range do expect( (2..4) & (1..3) ).to eq 2..3 end end + + describe "intersect?" do + it 'is true when the given range includes begin' do + expect( (2..4).intersect? (1..3) ).to be_truthy + end + + it 'is true when the given range includes end' do + expect( (2..4).intersect? (3..5) ).to be_truthy + end + + it 'is true when the given range includes both begin and end' do + expect( (2..4).intersect? (1..5) ).to be_truthy + end + + it 'is true when the given range is the same' do + expect( (2..4).intersect? (2..4) ).to be_truthy + end + + it 'is false when the given range is after' do + expect( (2..4).intersect? (5..7) ).to be_falsey + end + + it 'is false when the given range is before' do + expect( (2..4).intersect? (0..2) ).to be_falsey + end + end + + context "remove" do + it "is unchanged when the given range has no intersection" do + expect( (1..2).remove(3..4) ).to eq 1..2 + expect( (3..4).remove(1..2) ).to eq 3..4 + end + + it "is nil for two equal ranges" do + expect( (1..2).remove(1..2) ).to be_empty + end + + it "is the begin of the range when given range intersect the end" do + expect( (5..10).remove(8..15) ).to eq [5..7] + end + + it "is the end of the range when given range intersect the begin" do + expect( (5..10).remove(1..6) ).to eq [7..10] + end + + it "is the two remaing ranges when given range is the middle" do + expect( (1..10).remove(4..6) ).to eq [1..3, 7..10] + end + end end diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb index 677308fc8..d4a726740 100644 --- a/spec/models/chouette/time_table_spec.rb +++ b/spec/models/chouette/time_table_spec.rb @@ -1187,4 +1187,99 @@ end expect(subject.tag_list.size).to eq(2) end end + + describe "#intersect_periods!" do + let(:time_table) { Chouette::TimeTable.new } + let(:periods) do + [ + Date.new(2018, 1, 1)..Date.new(2018, 2, 1), + ] + end + + it "remove a date not included in given periods" do + time_table.dates.build date: Date.new(2017,12,31) + time_table.intersect_periods! periods + expect(time_table.dates).to be_empty + end + + it "keep a date included in given periods" do + time_table.dates.build date: Date.new(2018,1,15) + expect{time_table.intersect_periods! periods}.to_not change(time_table, :dates) + end + + it "remove a period not included in given periods" do + time_table.periods.build period_start: Date.new(2017,12,1), period_end: Date.new(2017,12,31) + time_table.intersect_periods! periods + expect(time_table.periods).to be_empty + end + + it "modify a start period if not included in given periods" do + period = time_table.periods.build period_start: Date.new(2017,12,1), period_end: Date.new(2018,1,15) + time_table.intersect_periods! periods + expect(period.period_start).to eq(Date.new(2018, 1, 1)) + end + + it "modify a end period if not included in given periods" do + period = time_table.periods.build period_start: Date.new(2018,1,15), period_end: Date.new(2018,3,1) + time_table.intersect_periods! periods + expect(period.period_end).to eq(Date.new(2018, 2, 1)) + end + + it "keep a period included in given periods" do + time_table.periods.build period_start: Date.new(2018,1,10), period_end: Date.new(2018,1,20) + expect{time_table.intersect_periods! periods}.to_not change(time_table, :periods) + end + + end + + describe "#remove_periods!" do + let(:time_table) { Chouette::TimeTable.new } + let(:periods) do + [ + Date.new(2018, 1, 1)..Date.new(2018, 2, 1), + ] + end + + it "remove a date included in given periods" do + time_table.dates.build date: Date.new(2018,1,15) + time_table.remove_periods! periods + expect(time_table.dates).to be_empty + end + + it "keep a date not included in given periods" do + time_table.dates.build date: Date.new(2017,12,31) + expect{time_table.remove_periods! periods}.to_not change(time_table, :dates) + end + + it "modify a end period if included in given periods" do + period = time_table.periods.build period_start: Date.new(2017,12,1), period_end: Date.new(2018,1,15) + time_table.remove_periods! periods + expect(period.period_end).to eq(Date.new(2017, 12, 31)) + end + + it "modify a start period if included in given periods" do + period = time_table.periods.build period_start: Date.new(2018,1,15), period_end: Date.new(2018,3,1) + time_table.remove_periods! periods + expect(period.period_start).to eq(Date.new(2018, 2, 2)) + end + + it "remove a period included in given periods" do + time_table.periods.build period_start: Date.new(2018,1,10), period_end: Date.new(2018,1,20) + time_table.remove_periods! periods + expect(time_table.periods).to be_empty + end + + it "split a period including a given period" do + time_table.periods.build period_start: Date.new(2017,12,1), period_end: Date.new(2018,3,1) + time_table.remove_periods! periods + + expected_ranges = [ + Date.new(2017,12,1)..Date.new(2017,12,31), + Date.new(2018,2,2)..Date.new(2018,3,1) + ] + expect(time_table.periods.map(&:range)).to eq(expected_ranges) + end + + end + end diff --git a/spec/models/merge_spec.rb b/spec/models/merge_spec.rb new file mode 100644 index 000000000..92f8f74b1 --- /dev/null +++ b/spec/models/merge_spec.rb @@ -0,0 +1,58 @@ +require "rails_helper" + +RSpec.describe Merge do + + it "should work" do + stop_area_referential = FactoryGirl.create :stop_area_referential + 10.times { FactoryGirl.create :stop_area, stop_area_referential: stop_area_referential } + + line_referential = FactoryGirl.create :line_referential + company = FactoryGirl.create :company, line_referential: line_referential + 10.times { FactoryGirl.create :line, line_referential: line_referential, company: company, network: nil } + + workbench = FactoryGirl.create :workbench, line_referential: line_referential, stop_area_referential: stop_area_referential + + referential_metadata = FactoryGirl.create(:referential_metadata, lines: line_referential.lines.limit(3)) + + referential = FactoryGirl.create :referential, + workbench: workbench, + organisation: workbench.organisation, + metadatas: [referential_metadata] + + factor = 1 + + referential.switch do + line_referential.lines.each do |line| + factor.times do + stop_areas = stop_area_referential.stop_areas.order("random()").limit(5) + FactoryGirl.create :route, line: line, stop_areas: stop_areas, stop_points_count: 0 + end + end + + referential.routes.each do |route| + factor.times do + FactoryGirl.create :journey_pattern, route: route, stop_points: route.stop_points.sample(3) + end + end + + referential.journey_patterns.each do |journey_pattern| + factor.times do + FactoryGirl.create :vehicle_journey, journey_pattern: journey_pattern, company: company + end + end + + shared_time_table = FactoryGirl.create :time_table + + referential.vehicle_journeys.each do |vehicle_journey| + vehicle_journey.time_tables << shared_time_table + + specific_time_table = FactoryGirl.create :time_table + vehicle_journey.time_tables << specific_time_table + end + end + + merge = Merge.create!(workbench: referential.workbench, referentials: [referential, referential]) + merge.merge! + end + +end diff --git a/spec/models/referential_cloning_spec.rb b/spec/models/referential_cloning_spec.rb index 4327c98aa..815e05a67 100644 --- a/spec/models/referential_cloning_spec.rb +++ b/spec/models/referential_cloning_spec.rb @@ -36,40 +36,50 @@ RSpec.describe ReferentialCloning, :type => :model do let(:cloner) { double } - before do - allow(AF83::SchemaCloner).to receive(:new).and_return cloner - allow(cloner).to receive(:clone_schema) - end - it 'creates a schema cloner with source and target schemas and clone schema' do expect(AF83::SchemaCloner).to receive(:new).with(source_referential.slug, target_referential.slug).and_return(cloner) expect(cloner).to receive(:clone_schema) referential_cloning.clone! end + end + + describe '#clone_with_status!' do + let(:referential_cloning) do + ReferentialCloning.new(target_referential: Referential.new(slug: "target")) + end + + before do + allow(referential_cloning).to receive(:clone!) + end + + it 'invokes clone! method' do + expect(referential_cloning).to receive(:clone!) + referential_cloning.clone_with_status! + end context 'when clone_schema is performed without error' do it "should have successful status" do - referential_cloning.clone! + referential_cloning.clone_with_status! expect(referential_cloning.status).to eq("successful") end end context 'when clone_schema raises an error' do it "should have failed status" do - expect(cloner).to receive(:clone_schema).and_raise("#fail") - referential_cloning.clone! + expect(referential_cloning).to receive(:clone!).and_raise("#fail") + referential_cloning.clone_with_status! expect(referential_cloning.status).to eq("failed") end end it "defines started_at" do - referential_cloning.clone! + referential_cloning.clone_with_status! expect(referential_cloning.started_at).not_to be_nil end it "defines ended_at" do - referential_cloning.clone! + referential_cloning.clone_with_status! expect(referential_cloning.ended_at).not_to be_nil end diff --git a/spec/models/referential_spec.rb b/spec/models/referential_spec.rb index 7816e7232..45881333f 100644 --- a/spec/models/referential_spec.rb +++ b/spec/models/referential_spec.rb @@ -125,4 +125,19 @@ describe Referential, :type => :model do end end + context "used in a ReferentialSuite" do + before do + ref.referential_suite_id = 42 + end + + it "return true to in_referential_suite?" do + expect(ref.in_referential_suite?).to be(true) + end + + it "don't use detect_overlapped_referentials in validation" do + expect(ref).to_not receive(:detect_overlapped_referentials) + ref.valid? + end + end + end diff --git a/spec/support/permissions.rb b/spec/support/permissions.rb index dde530871..95afd6c1c 100644 --- a/spec/support/permissions.rb +++ b/spec/support/permissions.rb @@ -18,6 +18,7 @@ module Support calendars footnotes imports + merges journey_patterns referentials routes diff --git a/spec/workers/referential_cloning_worker_spec.rb b/spec/workers/referential_cloning_worker_spec.rb index 2b9a54805..74e83c3b2 100644 --- a/spec/workers/referential_cloning_worker_spec.rb +++ b/spec/workers/referential_cloning_worker_spec.rb @@ -10,7 +10,7 @@ RSpec.describe ReferentialCloningWorker do it "invokes the clone! method of the associated ReferentialCloning" do expect(ReferentialCloning).to receive(:find).with(id).and_return(referential_cloning) - expect(referential_cloning).to receive(:clone!) + expect(referential_cloning).to receive(:clone_with_status!) worker.perform(id) end |
