diff options
| author | Xinhui | 2016-02-22 14:17:18 +0100 | 
|---|---|---|
| committer | Xinhui | 2016-02-22 14:17:18 +0100 | 
| commit | d49f47b4ac1db2cd88b96d830772bb7773924601 (patch) | |
| tree | 191c7b9cda9edf939792e7780df4e7460d685a4a /app/models/chouette/time_table.rb | |
| parent | 633004afc5861a6e8158948ddfecd73bf4dd86a8 (diff) | |
| download | chouette-core-d49f47b4ac1db2cd88b96d830772bb7773924601.tar.bz2 | |
Merge model from ninoxe gem
Diffstat (limited to 'app/models/chouette/time_table.rb')
| -rw-r--r-- | app/models/chouette/time_table.rb | 450 | 
1 files changed, 450 insertions, 0 deletions
diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb new file mode 100644 index 000000000..033c39f1c --- /dev/null +++ b/app/models/chouette/time_table.rb @@ -0,0 +1,450 @@ +class Chouette::TimeTable < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" + +  acts_as_taggable +   +  attr_accessor :monday,:tuesday,:wednesday,:thursday,:friday,:saturday,:sunday   +  attr_accessor :tag_search + +  def self.ransackable_attributes auth_object = nil +    (column_names + ['tag_search']) + _ransackers.keys +  end + +  has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney' + +  has_many :dates, -> {order(:date)}, inverse_of: :time_table, :validate => :true, :class_name => "Chouette::TimeTableDate", :dependent => :destroy +  has_many :periods, -> {order(:period_start)}, inverse_of: :time_table, :validate => :true, :class_name => "Chouette::TimeTablePeriod", :dependent => :destroy + +  after_save :save_shortcuts + +  def self.object_id_key +    "Timetable" +  end + +  accepts_nested_attributes_for :dates, :allow_destroy => :true +  accepts_nested_attributes_for :periods, :allow_destroy => :true + +  validates_presence_of :comment +  validates_associated :dates +  validates_associated :periods + +  def self.start_validity_period +    [Chouette::TimeTable.minimum(:start_date)].compact.min +  end +  def self.end_validity_period +    [Chouette::TimeTable.maximum(:end_date)].compact.max +  end + +  def save_shortcuts +      shortcuts_update +      self.update_column(:start_date, start_date) +      self.update_column(:end_date, end_date) +  end + +  def shortcuts_update(date=nil) +    dates_array = bounding_dates +    #if new_record? +      if dates_array.empty? +        self.start_date=nil +        self.end_date=nil +      else +        self.start_date=dates_array.min +        self.end_date=dates_array.max +      end +    #else +     # if dates_array.empty? +     #   update_attributes :start_date => nil, :end_date => nil +     # else +     #   update_attributes :start_date => dates_array.min, :end_date => dates_array.max +     # end +    #end +  end + +  def validity_out_from_on?(expected_date) +    return false unless self.end_date +    self.end_date <= expected_date +  end +  def validity_out_between?(starting_date, ending_date) +    return false unless self.start_date +    starting_date < self.end_date  && +      self.end_date <= ending_date +  end +  def self.validity_out_from_on?(expected_date,limit=0) +    if limit==0 +      Chouette::TimeTable.where("end_date <= ?", expected_date) +    else +      Chouette::TimeTable.where("end_date <= ?", expected_date).limit( limit) +    end +  end +  def self.validity_out_between?(start_date, end_date,limit=0) +    if limit==0 +      Chouette::TimeTable.where( "? < end_date", start_date).where( "end_date <= ?", end_date) +    else +      Chouette::TimeTable.where( "? < end_date", start_date).where( "end_date <= ?", end_date).limit( limit) +    end +  end + +  # Return days which intersects with the time table dates and periods +  def intersects(days) +    [].tap do |intersect_days| +      days.each do |day| +        intersect_days << day if include_day?(day) +      end +    end +  end + +  def include_day?(day) +    include_in_dates?(day) || include_in_periods?(day) +  end + +  def include_in_dates?(day) +    self.dates.any?{ |d| d.date === day && d.in_out == true } +  end + +  def excluded_date?(day) +    self.dates.any?{ |d| d.date === day && d.in_out == false } +  end + +  def include_in_periods?(day) +    self.periods.any?{ |period| period.period_start <= day && +                                day <= period.period_end && +                                valid_days.include?(day.cwday) && +                                ! excluded_date?(day) } +  end + +  def include_in_overlap_dates?(day) +    return false if self.excluded_date?(day) + +    counter = self.dates.select{ |d| d.date === day}.size + self.periods.select{ |period| period.period_start <= day && day <= period.period_end && valid_days.include?(day.cwday) }.size +    counter <= 1 ? false : true +   end + +  def periods_max_date +    return nil if self.periods.empty? + +    min_start = self.periods.map(&:period_start).compact.min +    max_end = self.periods.map(&:period_end).compact.max +    result = nil + +    if max_end && min_start +      max_end.downto( min_start) do |date| +        if self.valid_days.include?(date.cwday) && !self.excluded_date?(date) +            result = date +            break +        end +      end +    end +    result +  end +  def periods_min_date +    return nil if self.periods.empty? + +    min_start = self.periods.map(&:period_start).compact.min +    max_end = self.periods.map(&:period_end).compact.max +    result = nil + +    if max_end && min_start +      min_start.upto(max_end) do |date| +        if self.valid_days.include?(date.cwday) && !self.excluded_date?(date) +            result = date +            break +        end +      end +    end +    result +  end +  def bounding_dates +    bounding_min = self.dates.select{|d| d.in_out}.map(&:date).compact.min +    bounding_max = self.dates.select{|d| d.in_out}.map(&:date).compact.max + +    unless self.periods.empty? +      bounding_min = periods_min_date if periods_min_date && +          (bounding_min.nil? || (periods_min_date < bounding_min)) + +      bounding_max = periods_max_date if periods_max_date && +          (bounding_max.nil? || (bounding_max < periods_max_date)) +    end + +    [bounding_min, bounding_max].compact +  end + +  def day_by_mask(flag) +    int_day_types & flag == flag +  end + +  def self.day_by_mask(int_day_types,flag) +    int_day_types & flag == flag +  end + + +  def valid_days +    # Build an array with day of calendar week (1-7, Monday is 1). +    [].tap do |valid_days| +      valid_days << 1  if monday +      valid_days << 2  if tuesday +      valid_days << 3  if wednesday +      valid_days << 4  if thursday +      valid_days << 5  if friday +      valid_days << 6  if saturday +      valid_days << 7  if sunday +    end +  end + +  def self.valid_days(int_day_types) +    # Build an array with day of calendar week (1-7, Monday is 1). +    [].tap do |valid_days| +      valid_days << 1  if day_by_mask(int_day_types,4) +      valid_days << 2  if day_by_mask(int_day_types,8) +      valid_days << 3  if day_by_mask(int_day_types,16) +      valid_days << 4  if day_by_mask(int_day_types,32) +      valid_days << 5  if day_by_mask(int_day_types,64) +      valid_days << 6  if day_by_mask(int_day_types,128) +      valid_days << 7  if day_by_mask(int_day_types,256) +    end +  end + +  def monday +    day_by_mask(4) +  end +  def tuesday +    day_by_mask(8) +  end +  def wednesday +    day_by_mask(16) +  end +  def thursday +    day_by_mask(32) +  end +  def friday +    day_by_mask(64) +  end +  def saturday +    day_by_mask(128) +  end +  def sunday +    day_by_mask(256) +  end + +  def set_day(day,flag) +    if day == '1' || day == true +      self.int_day_types |= flag +    else +      self.int_day_types &= ~flag +    end +    shortcuts_update +  end + +  def monday=(day) +    set_day(day,4) +  end +  def tuesday=(day) +    set_day(day,8) +  end +  def wednesday=(day) +    set_day(day,16) +  end +  def thursday=(day) +    set_day(day,32) +  end +  def friday=(day) +    set_day(day,64) +  end +  def saturday=(day) +    set_day(day,128) +  end +  def sunday=(day) +    set_day(day,256) +  end + +  def effective_days_of_period(period,valid_days=self.valid_days) +    days = [] +      period.period_start.upto(period.period_end) do |date| +        if valid_days.include?(date.cwday) && !self.excluded_date?(date) +            days << date +        end +      end +    days +  end + +  def effective_days(valid_days=self.valid_days) +    days=self.effective_days_of_periods(valid_days) +    self.dates.each do |d| +      days |= [d.date] if d.in_out +    end +    days.sort +  end + +  def effective_days_of_periods(valid_days=self.valid_days) +    days = [] +    self.periods.each { |p| days |= self.effective_days_of_period(p,valid_days)} +    days.sort +  end + +  def clone_periods +    periods = [] +    self.periods.each { |p| periods << p.copy} +    periods +  end + +  def included_days +    days = [] +    self.dates.each do |d| +      days |= [d.date] if d.in_out +    end +    days.sort +  end + +  def excluded_days +    days = [] +    self.dates.each do |d| +      days |= [d.date] unless d.in_out +    end +    days.sort +  end + + +  # produce a copy of periods without anyone overlapping or including another +  def optimize_periods +    periods = self.clone_periods +    optimized = [] +    i=0 +    while i < periods.length +      p1 = periods[i] +      optimized << p1 +      j= i+1 +      while j < periods.length +        p2 = periods[j] +        if p1.contains? p2 +          periods.delete p2 +        elsif p1.overlap? p2 +          p1.period_start = [p1.period_start,p2.period_start].min +          p1.period_end = [p1.period_end,p2.period_end].max +          periods.delete p2 +        else +          j += 1 +        end +      end +      i+= 1 +    end +    optimized.sort { |a,b| a.period_start <=> b.period_start} +  end + +  # add a peculiar day or switch it from excluded to included +  def add_included_day(d) +    if self.excluded_date?(d) +       self.dates.each do |date| +         if date.date === d +           date.in_out = true +         end +       end +    elsif !self.include_in_dates?(d) +       self.dates << Chouette::TimeTableDate.new(:date => d, :in_out => true) +    end +  end + +  # merge effective days from another timetable +  def merge!(another_tt) +    transaction do +    # if one tt has no period, just merge lists +    if self.periods.empty? || another_tt.periods.empty? +      if !another_tt.periods.empty? +        # copy periods +        self.periods = another_tt.clone_periods +        # set valid_days +        self.int_day_types = another_tt.int_day_types +      end +      # merge dates +      self.dates ||= [] +      another_tt.included_days.each do |d| +        add_included_day d +      end +    else +      # check if periods can be kept +      common_day_types = self.int_day_types & another_tt.int_day_types & 508 +      # if common day types : merge periods +      if common_day_types != 0 +        periods = self.optimize_periods +        another_periods = another_tt.optimize_periods +        # add not common days of both periods as peculiar days +        self.effective_days_of_periods(self.class.valid_days(self.int_day_types ^ common_day_types)).each do |d| +          self.dates |= [Chouette::TimeTableDate.new(:date => d, :in_out => true)] +        end +        another_tt.effective_days_of_periods(self.class.valid_days(another_tt.int_day_types ^ common_day_types)).each do |d| +          add_included_day d +        end +        # merge periods +        self.periods = periods | another_periods +        self.int_day_types = common_day_types +        self.periods = self.optimize_periods +      else +        # convert all period in days +        self.effective_days_of_periods.each do |d| +          self.dates << Chouette::TimeTableDate.new(:date => d, :in_out => true) unless self.include_in_dates?(d) +        end +        another_tt.effective_days_of_periods.each do |d| +          add_included_day d +        end +      end +    end +    # if remained excluded dates are valid in other tt , remove it from result +    self.dates.each do |date| +      date.in_out = true if date.in_out == false && another_tt.include_day?(date.date) +    end + +    # if peculiar dates are valid in new periods, remove them +    if !self.periods.empty? +      days_in_period = self.effective_days_of_periods +      dates = [] +      self.dates.each do |date| +        dates << date unless date.in_out && days_in_period.include?(date.date) +      end +      self.dates = dates +    end +    self.dates.to_a.sort! { |a,b| a.date <=> b.date} +    self.save! +    end +  end + +  # remove dates form tt which aren't in another_tt +  def intersect!(another_tt) +    transaction do +       +      # transform tt as effective dates and get common ones +      days = another_tt.intersects(self.effective_days) & self.intersects(another_tt.effective_days) +      self.dates.clear +      days.each {|d| self.dates << Chouette::TimeTableDate.new( :date =>d, :in_out => true)} +      self.periods.clear +      self.int_day_types = 0 +      self.dates.to_a.sort! { |a,b| a.date <=> b.date} +      self.save! +    end +  end + + +  def disjoin!(another_tt) +    transaction do +      # remove days from another calendar +      days_to_exclude = self.intersects(another_tt.effective_days) +      days = self.effective_days - days_to_exclude +      self.dates.clear +      self.periods.clear +      self.int_day_types = 0 + +      days.each {|d| self.dates << Chouette::TimeTableDate.new( :date =>d, :in_out => true)} + +      self.dates.to_a.sort! { |a,b| a.date <=> b.date} +      self.periods.to_a.sort! { |a,b| a.period_start <=> b.period_start} +      self.save! +    end +  end + +  def duplicate +    tt = self.deep_clone :include => [:periods, :dates], :except => :object_version +    tt.uniq_objectid +    tt.comment = I18n.t("activerecord.copy", :name => self.comment) +    tt +  end + +end +  | 
