Class | Timesheet |
In: |
app/models/timesheet.rb
|
Parent: | ActiveRecord::Base |
File: | timesheet.rb |
(C): | Hipposoft 2008, 2009 |
Purpose: | Describe the behaviour of Timesheet objects. See below for more details. |
07-Jan-2008 (ADH): Created.
DEFAULT_SORT_COLUMN | = | 'year, week_number' |
DEFAULT_SORT_DIRECTION | = | 'DESC' |
DEFAULT_SORT_ORDER | = | "#{ DEFAULT_SORT_COLUMN } #{ DEFAULT_SORT_DIRECTION }" |
Return a range of years allowed for a timesheet. Optionally pass ‘true’ if you want an actual Date object range rather than just a year range.
# File app/models/timesheet.rb, line 60 60: def self.allowed_range( accurate = false ) 61: if ( WorkPacket.significant.count.zero? ) 62: range = ( Date.current.year - 2 )..( Date.current.year + 2 ) 63: else 64: range = self.used_range 65: range = ( range.first - 2 )..( range.last + 2 ) 66: end 67: 68: if ( accurate ) 69: ( Date.new( range.first, 1, 1 ) )..( Date.new( range.last, 12, 31 ) ) 70: else 71: range 72: end 73: end
Class method; as date_for, but pass explicitly the year, week number and day number of interest. If an optional fourth parameter is ‘true’, returns a Date object rather than a string.
# File app/models/timesheet.rb, line 405 405: def self.date_for( year, week_number, day_number, as_date = false ) 406: 407: # Get the date of Monday, week 1 in this timesheet's year. 408: # Add as many days as needed to get to Monday of the week 409: # for this timesheet. 410: 411: date = Timesheet.get_first_week_start( year ) 412: date = date + ( ( week_number - 1 ) * 7 ) 413: 414: # Add in the day number offset. 415: 416: date += TimesheetRow::DAY_ORDER.index( day_number ) 417: 418: # Return in DD-Mth-YYYY format, or as a Date object? 419: 420: if ( as_date ) 421: return date 422: else 423: return date.strftime( '%d-%b-%Y') # Or ISO: '%Y-%m-%d' 424: end 425: end
Get the date of the first day of week 1 in the given year. Note that sometimes, this can be in December the previous year. Works on commercial weeks (Mon->Sun). Returns a Date.
# File app/models/timesheet.rb, line 295 295: def self.get_first_week_start( year ) 296: 297: # Is Jan 1st already in week 1? 298: 299: date = Date.new( year, 1, 1 ) 300: 301: if ( date.cweek == 1 ) 302: 303: # Yes. Check December of the previous year. 304: 305: 31.downto( 25 ) do | day | 306: date = Date.new( year - 1, 12, day ) 307: 308: # If we encounter a date in the previous year which has a week 309: # number > 1, then that's the last week of the previous year. If 310: # we're on Dec 31st that means that week 1 started on Jan 1st, 311: # else in December on 312: 313: if ( date.cweek > 1 ) 314: return ( day == 31 ? Date.new( year, 1, 1 ) : Date.new( year - 1, 12, day + 1 ) ) 315: end 316: end 317: 318: else 319: 320: # No. Walk forward through January until we reach week 1. 321: 322: 2.upto( 7 ) do | day | 323: date = Date.new( year, 1, day ) 324: return date if ( date.cweek == 1 ) 325: end 326: end 327: end
Get the date of the last day of the last week in the given year. Note that sometimes, this can be in January in the following year. Works on commercial weeks (Mon->Sun). Returns a Date.
# File app/models/timesheet.rb, line 333 333: def self.get_last_week_end( year ) 334: 335: # Is Dec 31st already in week 1 for the next year? 336: 337: date = Date.new( year, 12, 31 ) 338: 339: if ( date.cweek == 1 ) 340: 341: # Yes. Check backwards through December to find the last day 342: # in the higher week number. 343: 344: 30.downto( 25 ) do | day | 345: date = Date.new( year, 12, day ) 346: return Date.new( year, 12, day ) if ( date.cweek > 1 ) 347: end 348: 349: else 350: 351: # No. Check January of the following year to find the end 352: # of the highest numbered week. 353: 354: 1.upto( 6 ) do | day | 355: date = Date.new( year + 1, 1, day ) 356: if ( date.cweek == 1 ) 357: return ( day == 1 ? Date.new( year, 12, 31 ) : Date.new( year + 1, 1, day - 1 ) ) 358: end 359: end 360: end 361: end
Get the number of the last commercial week (Mon->Sun) in the given year. This is usually 52, but is 53 for some years.
# File app/models/timesheet.rb, line 366 366: def self.get_last_week_number( year ) 367: 368: # Is Dec 31st already in week 1 for the next year? 369: 370: date = Date.new( year, 12, 31 ) 371: 372: if ( date.cweek == 1 ) 373: 374: # Yes. Check backwards through December to find the last day 375: # in the higher week number. 376: 377: 30.downto( 25 ) do | day | 378: date = Date.new( year, 12, day ) 379: return date.cweek if ( date.cweek > 1 ) 380: end 381: 382: else 383: 384: # No, so we have the highest week already. 385: 386: return date.cweek 387: end 388: end
Return a range of years used by all current timesheets, or the allowed range (see above) if there are no work packets. Optionally pass ‘true’ if you want an actual Date object range rather than just a year range.
# File app/models/timesheet.rb, line 79 79: def self.used_range( accurate = false ) 80: return self.allowed_range( accurate ) if WorkPacket.significant.count.zero? 81: 82: first = WorkPacket.find_earliest_by_tasks() 83: last = WorkPacket.find_latest_by_tasks() 84: 85: if accurate 86: ( first.date.to_date )..( last.date.to_date ) 87: else 88: ( first.date.year )..( last.date.year ) 89: end 90: end
Add a row to the timesheet using the given task object. Does nothing if a row containing that task is already present. The updated timesheet is not saved - the caller must do this.
# File app/models/timesheet.rb, line 251 251: def add_row( task ) 252: unless self.tasks.include?( task ) 253: timesheet_row = TimesheetRow.new 254: timesheet_row.task = task 255: self.timesheet_rows.push( timesheet_row ) 256: end 257: end
Is the given user permitted to update this timesheet?
# File app/models/timesheet.rb, line 137 137: def can_be_modified_by?( user ) 138: return true if ( user.admin? ) 139: return false unless ( self.is_permitted_for?( user ) ) 140: return ( not self.committed ) 141: end
Count the hours across all rows on the given day number; 0 is Sunday, 1-6 Monday to Saturday.
# File app/models/timesheet.rb, line 262 262: def column_sum( day_number ) 263: sum = 0.0 264: 265: # [TODO] Slow. Surely there's a better way...? 266: 267: self.timesheet_rows.each do | timesheet_row | 268: work_packet = WorkPacket.find_by_timesheet_row_id( 269: timesheet_row.id, 270: :conditions => { :day_number => day_number } 271: ) 272: 273: sum += work_packet.worked_hours if work_packet 274: end 275: 276: return sum 277: end
Return a date string representing this timesheet on the given day number. Day numbers are odd - 0 = Sunday at the end of this timesheet‘s week, while 1-6 = Monday at the start of the week through to Saturday inclusive (although this may be changed; see the TimesheetRow model). If an optional second parameter is ‘true’, returns a Date object rather than a string.
# File app/models/timesheet.rb, line 397 397: def date_for( day_number, as_date = false ) 398: Timesheet.date_for( self.year, self.week_number, day_number, as_date ) 399: end
Back-end to editable_week and showable_week. See those functions for details. Call with the next/previous week boolean and pass a block; this is given a timesheet or nil; evaluate ‘true’ to return details on the item or ‘false’ to move on to the next week.
# File app/models/timesheet.rb, line 214 214: def discover_week( nextweek ) 215: year = self.year 216: owner = self.user_id 217: 218: if ( nextweek ) 219: inc = 1 220: week = self.week_number + 1 221: limit = Timesheet.get_last_week_number( year ) + 1 222: 223: return if ( week >= limit ) 224: else 225: inc = -1 226: week = self.week_number - 1 227: limit = 0 228: 229: return if ( week <= limit ) 230: end 231: 232: while ( week != limit ) 233: timesheet = Timesheet.find_by_user_id_and_year_and_week_number( 234: owner, year, week 235: ) 236: 237: if ( yield( timesheet ) ) 238: return { :week_number => week, :timesheet => timesheet } 239: end 240: 241: week += inc 242: end 243: 244: return nil 245: end
Return the next (pass ‘true’) or previous (pass ‘false’) editable week after this one, as a hash with properties ‘week_number’ and ‘timesheet’. The latter will be populated with a timesheet if there is a not committed item in the found week, or nil if the week has no associated timesheet yet. Returns nil altogether if no editable week can be found (e.g. ask for previous from week 1, or all previous weeks have committed timesheets on them).
This operation may involve many database queries so is relatively slow.
# File app/models/timesheet.rb, line 194 194: def editable_week( nextweek ) 195: discover_week( nextweek ) do | timesheet | 196: ( timesheet.nil? or not timesheet.committed ) 197: end 198: end
Instance method that returns an array of all timesheets owned by this user. Pass an optional conditions hash (will be sent in as ":conditions => <given value>").
# File app/models/timesheet.rb, line 147 147: def find_mine( conditions = {} ) 148: Timesheet.find_all_by_user_id( self.user_id, :conditions => conditions ) 149: end
Instance method which returns an array of all committed timesheets owned by this user.
# File app/models/timesheet.rb, line 154 154: def find_mine_committed( conditions = {} ) 155: conditions.merge!( :committed => true ) 156: return find_mine( conditions ) 157: end
Instance method which returns an array of all uncommitted timesheets owned by this user.
# File app/models/timesheet.rb, line 162 162: def find_mine_uncommitted( conditions = {} ) 163: conditions.merge!( :committed => false ) 164: return find_mine( conditions ) 165: end
Is the given user permitted to do anything with this timesheet?
# File app/models/timesheet.rb, line 131 131: def is_permitted_for?( user ) 132: return ( user.privileged? or user.id == self.user.id ) 133: end
As editable_week, but returns weeks for ‘showable’ weeks - that is, only weeks where a timesheet owned by the current user already exists.
# File app/models/timesheet.rb, line 203 203: def showable_week( nextweek ) 204: discover_week( nextweek ) do | timesheet | 205: ( not timesheet.nil? ) 206: end 207: end
Return the date of the first day for this timesheet as a string.
# File app/models/timesheet.rb, line 287 287: def start_day() 288: return self.date_for( TimesheetRow::FIRST_DAY ) 289: end
Count the total number of worked hours in the whole timesheet.
# File app/models/timesheet.rb, line 281 281: def total_sum() 282: return self.work_packets.sum( :worked_hours ) 283: end
Return an array of week numbers which can be assigned to the timesheet. Includes the current timesheet‘s already allocated week.
# File app/models/timesheet.rb, line 171 171: def unused_weeks() 172: timesheets = find_mine( :year => self.year ) 173: used_weeks = timesheets.collect do | hash | 174: hash[ :week_number ] 175: end 176: 177: range = 1..Timesheet.get_last_week_number( self.year ) 178: unused_weeks = ( range.to_a - used_weeks ) 179: unused_weeks.push( self.week_number ) unless ( self.week_number.nil? ) 180: 181: return unused_weeks.sort() 182: end