我正在开发一个员工rota系统。对于工资单,我需要根据轮班所涵盖的日期/时间段计算正确的工资率。
如何在不使用冗长条件语句的情况下检查各种日期(周末,假日,工作日)。
给定任何时间范围(班次):
例如。 2015-01-20 15:00 - > 2015-01-21 17:00
对这一时期的细分市场进行分类的最佳(也是最有效的方式)是什么?
我想知道:
任何工作日(星期一)22:00至07:00之间(如有) 星期五)晚上。
期间(如有的话)在星期六的08:00到星期日的22:00之间。
公众假期(如果有的话)的期间(使用 holidays gem)
那么我的两个问题是:
1)知道一个时间段(班次)可以跨越一个周末(虽然我更喜欢一个可以支持多天的解决方案),我该如何计算要比较的日期/时间范围?
2)一旦我确定要比较的时间段(周末,假期等),我如何最好地确定这些时期的交集并确定它们的持续时间?
答案 0 :(得分:0)
我不完全理解您的问题,所以我已经汇总了一些基于对您所面临问题的许多假设的代码。我希望我所解决的一些问题以及我处理这些问题的方式可能对您有所帮助。例如,如果工人在轮班结束时仍在工作,则有必要确定下一轮班次,这可能是第二天。
您会发现我的代码非常粗糙且结构不合理。我有很多临时变量,只是为了帮助你了解正在发生的事情。在真实的应用程序中,您可能希望添加一些类,更多方法等。另外,假设数据将存储在数据库中,您可能希望使用SQL进行某些计算。
首先,我假设是数据。
数据强>
假期列表:
holidays = ["2015:04:05", "2015:04:06"]
每位员工的信息,包括员工的职位分类,关键是员工ID:
employees = {
123 => { name: 'Bob', job: :worker_bee1 },
221 => { name: 'Sue', job: :worker_bee2 }
}
每天有相同日期的团体,每个工作日和当天的工作日期间的工资率相同,除非当天是假日:
day_groups = { weekdays: [1,2,3,4,5], weekends: [6,0] }
每个工作期的信息:
work_periods = {
weekday_graveyard: {
day_group: :weekdays,
start_time: "00:00",
end_time: "08:00" },
weekday_day: {
day_group: :weekdays,
start_time: "08:00",
end_time: "16:00" },
weekday_swing: {
day_group: :weekdays,
start_time: "16:00",
end_time: "24:00" },
weekend: {
day_group: :weekends,
start_time: "00:00",
end_time: "24:00" } }
按工作,透析期,非假期和假期的工资表:
wage_by_job = {
worker_bee1: {
weekday_graveyard: { standard: 30.00, holiday: 60.00 },
weekday_day: { standard: 20.00, holiday: 40.00 },
weekday_swing: { standard: 25.00, holiday: 50.00 },
weekend: { standard: 22.00, holiday: 44.00 }
},
worker_bee2: {
weekday_graveyard: { standard: 32.00, holiday: 64.00 },
weekday_day: { standard: 22.00, holiday: 44.00 },
weekday_swing: { standard: 27.00, holiday: 54.00 },
weekend: { standard: 24.00, holiday: 48.00 }
}
}
所有员工在薪资期间工作的小时数:
shifts_worked = [
{ id: 123, date: "2015:04:03", start_time: "15:30", end_time: "00:15" },
{ id: 221, date: "2015:04:04", start_time: "23:30", end_time: "08:30" },
{ id: 123, date: "2015:04:06", start_time: "08:00", end_time: "16:00" },
{ id: 221, date: "2015:04:06", start_time: "23:00", end_time: "07:00" },
{ id: 123, date: "2015:04:07", start_time: "00:00", end_time: "09:00" }
]
<强>助手强>
require 'set'
require 'date'
def date_str_to_obj(date_str)
Date.strptime(date_str, '%Y:%m:%d')
end
date_str_to_obj("2015:04:04")
#=> #<Date: 2015-04-04 ((2457117j,0s,0n),+0s,2299161j)>
def next_day(day)
(day==6) ? 0 : day+1
end
next_day(6)
#=> 0
将假日转换为日期对象并存储在一组中以便快速查找:
@holiday_set = Set.new(holidays.map { |date_str|
date_str_to_obj(date_str) }.to_set)
#=> #<Set: {#<Date: 2015-04-05 ((2457118j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-04-06 ((2457119j,0s,0n),+0s,2299161j)>}>
def is_holiday?(date)
@holiday_set.include?(date)
end
is_holiday?(date_str_to_obj("2015:04:04"))
#=> false
is_holiday?(date_str_to_obj("2015:04:05"))
#=> true
将每周的每一天映射到day_groups
:
@day_group_by_dow = day_groups.each_with_object({}) { |(period,days),h|
days.each { |d| h[d] = period } }
#=> {1=>:weekdays, 2=>:weekdays, 3=>:weekdays, 4=>:weekdays,
# 5=>:weekdays, 6=>:weekend, 0=>:weekend}
将day_groups
的每个元素映射到一系列工作时段:
@work_periods_by_day_group = work_periods.each_with_object({}) { |(k,g),h|
h.update(g[:day_group]=>[k]) { |_,nwp,owp| nwp+owp } }
#=> {:weekdays=>[:weekday_graveyard, :weekday_day, :weekday_swing],
# :weekend=> [:weekends]}
计算工作时间内的工作时间:
def start_and_end_times_to_hours(start_time, end_time)
(time_to_minutes_after_midnight(end_time) -
time_to_minutes_after_midnight(start_time))/60.0
end
start_and_end_times_to_hours("10:00", "14:30")
#=> 4.5
上一种方法的帮助:
private
def time_to_minutes_after_midnight(time_str)
hrs, mins = time_str.scan(/(\d\d):(\d\d)/).first.map(&:to_i)
60 * hrs + mins
end
public
time_to_minutes_after_midnight("10:00")
#=> 600
time_to_minutes_after_midnight("14:30")
#=> 870
如方法名称所示:
def date_and_time_to_current_period(date, time, work_periods)
day_grp = @day_group_by_dow[date.wday]
periods = @work_periods_by_day_group[day_grp]
periods.find do |per|
p = work_periods[per]
p[:start_time] <= time && time < p[:end_time]
end
end
date_and_time_to_current_period(date_str_to_obj("2015:04:03"),
#=> :weekday_swing
date_and_time_to_current_period(date_str_to_obj("2015:04:04"),
#=> :weekend
最后,另一个不言自明的方法:
def next_period_and_date_by_period_and_date(work_periods, period, date)
end_time = work_periods[period][:end_time]
if end_time == "24:00" # next_day
day_grp = @day_group_by_dow[next_day(date.wday)]
wp = @work_periods_by_day_group[day_grp]
[wp.find { |period| work_periods[period][:start_time]=="00:00" }, date+1]
else # same day
day_grp = work_periods[period][:day_group]
wp = @work_periods_by_day_group[day_grp]
[wp.find { |period| work_periods[period][:start_time]==end_time }, date]
end
end
next_period_and_date_by_period_and_date(work_periods,
:weekday_day, date_str_to_obj("2015:04:03"))
#=> [:weekday_swing, #<Date: 2015-04-03 ((2457116j,0s,0n),+0s,2299161j)>]
next_period_and_date_by_period_and_date(work_periods,
:weekday_swing, date_str_to_obj("2015:04:02"))
#=> [:weekday_graveyard, #<Date: 2015-04-03...+0s,2299161j)>]
next_period_and_date_by_period_and_date(work_periods,
:weekday_swing, date_str_to_obj("2015:04:03"))
#=> [:weekend, #<Date: 2015-04-04 ((2457117j,0s,0n),+0s,2299161j)>]
next_period_and_date_by_period_and_date(work_periods,
:weekday_swing, date_str_to_obj("2015:04:04"))
#=> [:weekend, #<Date: 2015-04-05 ((2457118j,0s,0n),+0s,2299161j)>]
计算工资单
shifts_worked.each_with_object(Hash.new(0.0)) do |shift, payroll|
id = shift[:id]
date = date_str_to_obj(shift[:date])
start_time = shift[:start_time]
end_time = shift[:end_time]
wage_schedule = wage_by_job[employees[id][:job]]
curr_period = date_and_time_to_current_period(date, start_time, work_periods)
while true
end_time_in_period = work_periods[curr_period][:end_time]
end_time_in_period = end_time if
(end_time > start_time && end_time < end_time_in_period)
hours_in_period =
start_and_end_times_to_hours(start_time, end_time_in_period)
wage = wage_schedule[curr_period][is_holiday?(date) ? :holiday : :standard]
payroll[id] += (wage * hours_in_period).round(2)
break if end_time == end_time_in_period
curr_period, date =
next_period_and_date_by_period_and_date(work_periods,
curr_period, date)
start_time = work_periods[curr_period][:start_time]
end
end
#=> {123=>795.5, 221=>698.0}
答案 1 :(得分:0)
我使用了以下gem: https://github.com/fnando/recurrence
我还没有完成假期。
要求
class Requirement < ActiveRecord::Base
# Model with attributes:
# start - datetime
# end - datetime
# is_sleepin - boolean
def duration
self.end - self.start
end
def to_range
self.start..self.end
end
def spans_multiple_days?
self.end.to_date != self.start.to_date
end
end
要求持续时间的细分(转移)
class Breakdown
attr_reader :requirement,
:standard_total_duration,
:weekend_total_duration,
:wakein_total_duration
def initialize(requirement)
@requirement = requirement
@rules = Array.new
@rules << Breakdown::StandardRule.new(self)
@rules << Breakdown::WeekendRule.new(self)
@standard_total_duration = components[:standard].total_duration
@weekend_total_duration = components[:weekend].total_duration
if @requirement.is_sleepin?
@standard_total_duration = 0
@weekend_total_duration = 0
end
# Following is a special set of rules for certain Locations where staff work
# If a requirement is_sleepin? that means duration is not counted so set to 0
if ['Home 1', 'Home 2'].include?(@requirement.home.name.strip) and
@requirement.spans_multiple_days? and not @requirement.is_sleepin?
@rules << Aspirations::Breakdown::WakeinRule.new(self)
@wakein_total_duration = components[:wakein].total_duration
@standard_total_duration = 0
@weekend_total_duration = 0
end
end
def components
@rules.hmap{ |k,v| [ k.to_sym, k ] }
end
end
使用规则来指定轮班持续时间的哪些部分应该分类:
module Breakdown
class Rule
def initialize(breakdown)
@requirement = breakdown.requirement
end
def to_sym
# eg 'Breakdown::StandardRule' becomes :standard
self.class.name.split('::').last.gsub("Rule", "").downcase.to_sym
end
def periods
output = []
occurrences = rule.events.map{ |e| rule_time_range(e) }
occurrences.each do |o|
if (o.max > @requirement.start and @requirement.end > o.min)
output << (o & @requirement.to_range)
end
end
return output
end
def total_duration
periods.reduce(0) { |sum, p| sum + (p.max - p.min).to_i }
end
end
end
规则示例(在本例中为周末规则)
module Breakdown
class WeekendRule < Breakdown::Rule
def period_expansion
# This is an attempt to safely ensure that a weekend period
# is detected even though the start date of the requirement
# may be on Sunday evening, maybe could be just 2 days
4.days
end
def period_range
(@requirement.start.to_date - period_expansion)..(@requirement.end.to_date + period_expansion)
end
def rule
Recurrence.new(:every => :week, :on => :saturday, :starts => period_range.min, :until => period_range.max)
end
def rule_time_range(date)
# Saturday 8am
start = date + Time.parse("08:00").seconds_since_midnight.seconds
_end = (date + 1.day) + Time.parse("22:00").seconds_since_midnight.seconds
start.._end
end
end
end
答案 2 :(得分:0)
一种可能的方法可能是将一周打破到15分钟(取决于您需要多少分辨率)的单个块。而不是有些难以处理的时间范围,你可以使用这些块的集合,Ruby非常好地支持。
按时间顺序编号时间块:
星期一12:00 AM-12:15 AM = 0
星期一12:15 AM-12:30 AM = 1
...
周日11:45 PM-12:00 AM = 671
然后为假期,周末,每个班次等准备一组整数,无论你需要什么。除了假期,这些可能都是常数。
例如,从周六上午8点到周日晚上10点的周末:
周末= [512..663]
同样,将每位员工的出勤率表示为一组。例如,周一上午8点到中午以及周六上午10点到11点工作的人将是:
出勤率= [32..47,520..523]
通过这种方法,您可以使用设置交叉点来计算周末的小时数:
周末出席=周末&amp;出勤