计算未来薪资日期

时间:2016-10-11 02:55:30

标签: javascript date momentjs

我正在尝试使用所需的少量输入生成工资单计划。就目前而言,我已经把它归结为4个输入(SEMI_MONTHLY计划中的5个)。

如果您只是记录他们的日期并且添加14或15,30或该月的最后一天,则工资单很简单。但实际上变得困难的地方是管理假期以及工资单实际运行的时间。

问题

Weekly和BiWeekly周期无法正确处理假期。我试图保持它尽可能的功能,并返回处理而不是对象的简单值。

示例:

generatePaySchedule(payPeriodOptions.WEEKLY, moment('11-04-2016', 'MM-DD-YYYY'), holidays, holidayPayRules.BEFORE)

Fails after 11/10/2016 holiday by setting next pay date as 11/17/2016 instead of the correct 11/18/2016

代码如下(某些ES6已插入)或您可以执行以下codepen

var payPeriodOptions = {       每周:'每周',       BIWEEKLY:'BIWEEKLY',       SEMI_MONTHLY:'SEMI_MONTHLY',       每月:'每月'     };

var holidayPayRules = {
  BEFORE: 'BEFORE',
  AFTER: 'AFTER'
};

const holidays = [
    { name: 'Martin Luther King, Jr. Day', date: moment('01-18-2016', 'MM-DD-YYYY') },
    { name: 'George Washington\'s Birthday', date: moment('02-15-2016', 'MM-DD-YYYY') },
    { name: 'Memorial Day', date: moment('05-30-2016', 'MM-DD-YYYY') },
    { name: 'Independence Day', date: moment('07-04-2016', 'MM-DD-YYYY') },
    { name: 'Labor Day', date: moment('09-05-2016', 'MM-DD-YYYY') },
    { name: 'Columbus Day', date: moment('10-10-2016', 'MM-DD-YYYY') },
    { name: 'Christmas Day', date: moment('12-26-2016', 'MM-DD-YYYY') },
    { name: 'Thanksgiving Day', date: moment('11-24-2016', 'MM-DD-YYYY') },
    { name: 'New Years Day', date: moment('01-01-2016', 'MM-DD-YYYY') },
    { name: 'Veterans Day', date: moment('11-11-2016', 'MM-DD-YYYY') },
    { name: 'New Years Day', date: moment('01-01-2017', 'MM-DD-YYYY') },
  ];

var payDays = [15, 30]; // Optional - Required for payroll period of Semi-Monthly

var weekly = generatePaySchedule(payPeriodOptions.WEEKLY, moment('10-07-2016', 'MM-DD-YYYY'), holidays, holidayPayRules.BEFORE);
var biWeekly = generatePaySchedule(payPeriodOptions.BIWEEKLY, moment('10-07-2016', 'MM-DD-YYYY'), holidays, holidayPayRules.BEFORE);
var semiMonthly = generatePaySchedule(payPeriodOptions.SEMI_MONTHLY, moment('10-15-2016', 'MM-DD-YYYY'), holidays, holidayPayRules.BEFORE, payDays);
var monthly = generatePaySchedule(payPeriodOptions.MONTHLY, moment('10-31-2016', 'MM-DD-YYYY'), holidays, holidayPayRules.BEFORE);

console.log('Weekly: ', weekly);
console.log('Bi-Weekly: ', biWeekly);
console.log('Semi-Monthly: ', semiMonthly);
console.log('Monthly: ', monthly);

/**
 * @function nextPayDate - Calculates the next pay date given a previous pay date
 * @param {string} payrollPeriods - A string representation of payroll periods (WEEKLY|BIWEEKLY|SEMI_MONTHLY|MONTHLY)
 * @param {string} lastPayDay - A date in string format (MM-DD-YYYY), which represents the last pay date.
 * @param {string} holidayPayRule - A string representation of holiday pay rules (BEFORE|AFTER)
 * @param {number[]} [payDays] - An optional array of two numbers representing dates which payroll is run each month
 * @return {string} - A date in string format, which represents the calculated pay date.
 */
function nextPayDate(payrollPeriods, lastPayDay, holidayPayRule, payDays) {
  var d = moment(lastPayDay);
  var dayOfWeek = d.clone().format('dddd');
  var payDate;

  switch (payrollPeriods) {
    case payPeriodOptions.WEEKLY:
      payDate = d.clone().add(1, 'weeks');
      break;
    case payPeriodOptions.BIWEEKLY:
      payDate =  d.clone().add(2, 'weeks');
      break;
    case payPeriodOptions.SEMI_MONTHLY: {

      if(holidayPayRule === holidayPayRules.BEFORE){
        /* 
        If lastPayDay (d) is the same or before the first pay day of the month
        then keep the month the same and change the day to be the second pay day
        of the month, unless that pay day is greater than the days in the the month
        then use the last day of the month (Example: 15, 30 - Feb. 28)
        */
        payDate = (d.isSameOrBefore(d.clone().date(payDays[0])) ?
                   (d.clone().endOf('month').isBefore(d.clone().date(payDays[1])) ? 
                    d.clone().endOf('month') : 
                    d.clone().date(payDays[1])) : 
                   d.clone().date(payDays[0]).add(1, 'months'));
        /* 
        If the last pay date was the previous month because of holiday or weekend
        then set the new date to be the second pay date of that month
        */
        if(d.isAfter(d.clone().date(payDays[1])) && d.date() !== payDays[1]){
          payDate.date(payDays[1]);
        }
      } else {
        payDate = (d.isSameOrAfter(d.clone().date(payDays[0])) && d.date() < payDays[1] ? d.clone().date(payDays[1]) : d.clone().date(payDays[0]).add(1, 'months'));
      } 
      break;
    } 
    case payPeriodOptions.MONTHLY: {
      payDate = d.clone().add(1, 'months').endOf('month');
      break;
    }
  }
  return payDate.format();
}

/**
 * @function checkDate - Checks next pay date if it falls on a weekend or a holiday
 * @param {string} date - A date in string format (MM-DD-YYYY), which represents the calculated pay date.
 * @param {{key: {{name: String, date: String}[]}}} holidays - An object of days with applicable holidays.
 * @param {string} holidayPayRule - A string representation of holiday pay rules (BEFORE|AFTER)
 * @return {string} - A date in string format, which represents the calculated pay date.
 */
function checkDate(date, holidays, holidayPayRule){
  var d = moment(date);
  var dayOfWeek = d.clone().format('dddd');

  if(dayOfWeek === 'Saturday') {
    d = (holidayPayRule === holidayPayRules.AFTER ? d.add(2 ,'days') : d.add(-1 ,'days'));
  } else if(dayOfWeek === 'Sunday'){
    d = (holidayPayRule === holidayPayRules.AFTER ? d.add(1 ,'days') : d.add(-2 ,'days'));
  }

  if (typeof holidays.find(h => h.date.isSame(d, 'day')) !== 'undefined') {
      switch (dayOfWeek) {
        case 'Monday':
          d = (holidayPayRule === holidayPayRules.AFTER ? d.add(1, 'days') : d.add(-3, 'days'));
          break;
        case 'Friday':
          d = (holidayPayRule === holidayPayRules.AFTER ? d.add(3, 'days') : d.add(-1, 'days'));
          break;
        case 'Thursday':
          d = (holidayPayRule === holidayPayRules.AFTER ? d.add(1, 'days') : d.add(-1, 'days'));
          break;
      }
    } 
  return d.format();
}

/**
 * @function generatePaySchedule - Returns a pay schedule for a year
 * @param {string} payrollPeriods - A string representation of payroll periods (WEEKLY|BIWEEKLY|SEMI_MONTHLY|MONTHLY)
 * @param {string} nextPayDay - A date in string format (MM-DD-YYYY), which represents the next (non-holiday) pay date.
 * @param {{key: {{name: String, date: String}[]}}} holidays - An object of days with applicable holidays.
 * @param {string} holidayPayRule - A string representation of holiday pay rules (BEFORE|AFTER)
 * @param {number[]} [payDays] - An optional array of two numbers representing dates which payroll is run each month
 * @return {string[]} - An array of date strings representing the appropriate payroll schedule
 */
function generatePaySchedule(payrollPeriods, nextPayDay, holidays, holidayPayRule, payDays){
  if(payrollPeriods === payPeriodOptions.SEMI_MONTHLY && !payDays){
    console.error('For a Semi-Monthly payroll period, an array of two dates must be passed into generatePaySchedule');
    return;
  }
  var payPeriods = [];
  payPeriods.push(moment(nextPayDay).format('MM-DD-YYYY'));
  var lastPayDay = nextPayDay;

  for (var i = 0; i < 12; i++) {
    var payDates = checkDate(nextPayDate(payrollPeriods, lastPayDay, holidayPayRule, payDays), holidays, holidayPayRule);
    payPeriods.push(moment(payDates).format('MM-DD-YYYY'));
    lastPayDay = payDates;
  }
  return payPeriods;  
}

最终,我认为这可以变成一个片刻插件,并在验证每个骑行时,我打算解决这个问题。

编辑:更好地制定问题

0 个答案:

没有答案