aboutsummaryrefslogtreecommitdiffstats
path: root/app/models/chouette/time_table.rb
diff options
context:
space:
mode:
authorXinhui2016-02-22 14:17:18 +0100
committerXinhui2016-02-22 14:17:18 +0100
commitd49f47b4ac1db2cd88b96d830772bb7773924601 (patch)
tree191c7b9cda9edf939792e7780df4e7460d685a4a /app/models/chouette/time_table.rb
parent633004afc5861a6e8158948ddfecd73bf4dd86a8 (diff)
downloadchouette-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.rb450
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
+