ソース

javascript:function reportToHash(e){const tds=[];e.querySelectorAll('td').forEach(function(e){tds.push(e)});const rawDay=tds[1].textContent;const dayInfo=rawDay.split(' ');const week=dayInfo[1].replace(/\(|\)/gi,'');const startTime=tds[2].textContent;const endTime=tds[3].textContent;const breakTime=tds[4].textContent;const description=tds[6].textContent;const rawWorkTime=tds[5].textContent;const weekdaysFlag=(week=='土'||week=='日');const vacationFlag=weekdaysFlag&&rawWorkTime=='';if(vacationFlag||rawWorkTime=='-'){return{"workTime":0,"weekdaysFlag":weekdaysFlag,"vacationFlag":true,"description":description,}}var workTime=0;if(rawWorkTime!=''){const times=rawWorkTime.replace(/(\d{1,2})時間(\d{1,2})分/gi,'$1:$2').split(':');workTime=parseInt(times[0]*60)+parseInt(times[1])}return{"day":dayInfo[0].replace(/日/gi,''),"week":week,"start":startTime,"end":endTime,"breakTime":breakTime,"workTime":workTime,"description":description,"weekdaysFlag":weekdaysFlag,"vacationFlag":false,}}function workTime(){const limitTime=document.querySelector('body > div.main.l-main > div.wrapper > div:nth-child(3) > div.d-flex.flex-wrap.justify-content-between > div.col-12.col-md-6.col-lg-4 > table > tbody > tr > td:nth-child(2)').textContent;minAndMax=limitTime.split('~');return[minAndMax[0].replace(/時間/gi,''),minAndMax[1].replace(/時間/gi,'')]}function minWorkMinutes(){return calM(workTime()[0])}function maxWorkMinutes(){return calM(workTime()[1])}function calM(hour){return hour*60}function calMFromD(day){return day*60*8}function calH(minutes){return minutes/60}function calD(hour){return hour/8}function calDFromM(minutes){return(minutes/60)/8}function pushMessage(messages,title,content){messages.push(title+":"+content.toString())}function main(){var weekdaysCount=0;var vacationCount=0;var workedMinutes=0;var workedCount=0;var totalWorkCount=0;document.querySelectorAll('table.table .report-row').forEach(function(e){const report=reportToHash(e);if(report["vacationFlag"]){vacationCount+=1;if(report["weekdaysFlag"]){weekdaysCount+=1}}else{if(report["workTime"]>0){workedCount+=1;workedMinutes+=report["workTime"]}totalWorkCount+=1}});const scheduledMinutes=calMFromD(totalWorkCount-workedCount);const planWorkMinutes=workedMinutes+scheduledMinutes;var messages=[];messages.push("~~~~~~~~~~~~~~~~~");pushMessage(messages,"稼働(時)",Math.floor(calH(workedMinutes)));pushMessage(messages,"予定稼働(時)",Math.floor(calH(planWorkMinutes)));pushMessage(messages,"稼働数(日)",workedCount+" (残:"+(totalWorkCount-workedCount)+")");pushMessage(messages,"稼働予定数(日)",totalWorkCount);pushMessage(messages,"休日数(日)",vacationCount+" (土・日:"+weekdaysCount+", 有給:"+(vacationCount-weekdaysCount)+")");const overWorkMinutes=(workedMinutes+scheduledMinutes)-maxWorkMinutes();if(overWorkMinutes>=60){pushMessage(messages,"★過労時間(時)",Math.floor(calH(overWorkMinutes)));messages.push("~~~~~~~~~~~~~~~~~");messages.push("(・ω・`三´・ω・). < 上限を超えそう!?");return messages}if(workedMinutes-minWorkMinutes()>=0){messages.push("~~~~~~~~~~~~~~~~~");messages.push("ヾ(○⌒∇⌒○)ノ < 目標達成!!!!!");return messages}const mustWorkMinutes=minWorkMinutes()-workedMinutes;if(mustWorkMinutes<scheduledMinutes){if(scheduledMinutes-mustWorkMinutes<60){pushMessage(messages,"★余裕時間(分)",scheduledMinutes-mustWorkMinutes)}else{messages.push("★余裕時間(時)"+Math.floor(calH(scheduledMinutes-mustWorkMinutes)));if(scheduledMinutes-mustWorkMinutes>60*8){pushMessage(messages,"★休み可能日数",Math.floor(calDFromM(scheduledMinutes-mustWorkMinutes)))}}messages.push("~~~~~~~~~~~~~~~~~");messages.push("( ^∀^) < 残業なしで達成可能!");return messages}const lackMinutes=mustWorkMinutes-scheduledMinutes;if(lackMinutes<60){pushMessage(messages,"◉不足時間(分)",lackMinutes)}else{pushMessage(messages,"◉不足時間(時)",Math.ceil(calH(lackMinutes)));const avarageOverMinutes=Math.ceil(lackMinutes/(totalWorkCount-workedCount));if(avarageOverMinutes<60){pushMessage(messages,"◉平均残業","1時間未満")}else{pushMessage(messages,"◉平均残業(時)",Math.ceil(calH(avarageOverMinutes)*10)/10)}}messages.push("~~~~~~~~~~~~~~~~~");messages.push("( ;´・ω・`) < 残業しなきゃ...");return messages};window.alert(main().join("\n"));

/packer/

/**
 * Do compress at https://javascriptcompressor.com/
 **/
function reportToHash(e) {
  const tds = [];
  e.querySelectorAll('td').forEach(function(e) {
    tds.push(e);
  });

  // 1日 (日)
  const rawDay = tds[1].textContent;
  const dayInfo = rawDay.split(' ');
  const week = dayInfo[1].replace(/\(|\)/gi, '');

  // 09:34
  const startTime = tds[2].textContent;
  // 19:11
  const endTime = tds[3].textContent;
  // 60分
  const breakTime = tds[4].textContent;
  // 資料まとめ
  const description = tds[6].textContent;
  // 8時間30分
  const rawWorkTime = tds[5].textContent;

  const weekdaysFlag = (week == '土' || week == '日');
  const vacationFlag = weekdaysFlag && rawWorkTime == '';
  if(vacationFlag || rawWorkTime == '-') {
    return {
      "workTime": 0,
      "weekdaysFlag": weekdaysFlag,
      "vacationFlag": true,
      "description": description,
    }
  }
  var workTime = 0;
  if(rawWorkTime != '') {
    const times = rawWorkTime.replace(/(\d{1,2})時間(\d{1,2})分/gi, '$1:$2').split(':');
    workTime = parseInt(times[0] * 60) + parseInt(times[1]);
  }

  return {
    "day": dayInfo[0].replace(/日/gi, ''),
    "week": week,
    "start": startTime,
    "end": endTime,
    "breakTime": breakTime,
    "workTime": workTime,
    "description": description,
    "weekdaysFlag": weekdaysFlag,
    "vacationFlag": false,
  }
}

function workTime() {
  // 140時間~180時間
  const limitTime = document.querySelector('body > div.main.l-main > div.wrapper > div:nth-child(3) > div.d-flex.flex-wrap.justify-content-between > div.col-12.col-md-6.col-lg-4 > table > tbody > tr > td:nth-child(2)').textContent;
  minAndMax = limitTime.split('~');
  return [minAndMax[0].replace(/時間/gi, ''), minAndMax[1].replace(/時間/gi, '')];
}
function minWorkMinutes() {
  return calM(workTime()[0]);
}
function maxWorkMinutes() {
  return calM(workTime()[1]);
}

function calM(hour) {
  return hour * 60;
}
function calMFromD(day) {
  return day * 60 * 8;
}
function calH(minutes) {
  return minutes / 60;
}
function calD(hour) {
  return hour / 8;
}
function calDFromM(minutes) {
  return (minutes / 60) / 8;
}
function pushMessage(messages, title, content) {
  messages.push(title + ":" + content.toString());
}

function main() {
  var weekdaysCount = 0;
  var vacationCount = 0;
  var workedMinutes = 0;
  var workedCount = 0;
  var totalWorkCount = 0;
  document.querySelectorAll('table.table .report-row').forEach(function(e) {
    const report = reportToHash(e);
    if(report["vacationFlag"]) {
      vacationCount += 1;
      if(report["weekdaysFlag"]){
        weekdaysCount += 1;
      }
    } else {
      if(report["workTime"] > 0) {
        workedCount += 1;
        workedMinutes += report["workTime"];
      }
      totalWorkCount += 1;
    }
  });

  const scheduledMinutes = calMFromD(totalWorkCount - workedCount);
  const planWorkMinutes = workedMinutes + scheduledMinutes;

  var messages = [];
  messages.push("~~~~~~~~~~~~~~~~~");
  pushMessage(messages, "稼働(時)", Math.floor(calH(workedMinutes)));
  pushMessage(messages, "予定稼働(時)", Math.floor(calH(planWorkMinutes)));
  pushMessage(messages, "稼働数(日)", workedCount + " (残:" + (totalWorkCount - workedCount) + ")");
  pushMessage(messages, "稼働予定数(日)", totalWorkCount);
  pushMessage(messages, "休日数(日)", vacationCount + " (土・日:" + weekdaysCount + ", 有給:" + (vacationCount - weekdaysCount) + ")");

  const overWorkMinutes = (workedMinutes + scheduledMinutes) - maxWorkMinutes();
  if(overWorkMinutes >= 60) {
    pushMessage(messages, "★過労時間(時)", Math.floor(calH(overWorkMinutes)));

    messages.push("~~~~~~~~~~~~~~~~~");
    messages.push("(・ω・`三´・ω・). < 上限を超えそう!?");

    return messages;
  }

  if(workedMinutes - minWorkMinutes() >= 0) {
    messages.push("~~~~~~~~~~~~~~~~~");
    messages.push("ヾ(○⌒∇⌒○)ノ < 目標達成!!!!!");

    return messages;
  }

  const mustWorkMinutes = minWorkMinutes() - workedMinutes;
  if(mustWorkMinutes < scheduledMinutes) {
    if(scheduledMinutes - mustWorkMinutes < 60) {
      pushMessage(messages, "★余裕時間(分)", scheduledMinutes - mustWorkMinutes);
    } else {
      messages.push("★余裕時間(時)" + Math.floor(calH(scheduledMinutes - mustWorkMinutes)));

      if(scheduledMinutes - mustWorkMinutes > 60 * 8) {
        pushMessage(messages, "★休み可能日数", Math.floor(calDFromM(scheduledMinutes - mustWorkMinutes)));
      }
    }

    messages.push("~~~~~~~~~~~~~~~~~");
    messages.push("( ^∀^) < 残業なしで達成可能!");

    return messages;
  }

  const lackMinutes = mustWorkMinutes - scheduledMinutes;
  if(lackMinutes < 60) {
    pushMessage(messages, "◉不足時間(分)", lackMinutes);
  } else {
    pushMessage(messages, "◉不足時間(時)", Math.ceil(calH(lackMinutes)));

    const avarageOverMinutes = Math.ceil(lackMinutes / (totalWorkCount - workedCount));
    if(avarageOverMinutes < 60) {
      pushMessage(messages, "◉平均残業", "1時間未満");
    } else {
      pushMessage(messages, "◉平均残業(時)", Math.ceil(calH(avarageOverMinutes) * 10) / 10);
    }
  }

  messages.push("~~~~~~~~~~~~~~~~~");
  messages.push("( ;´・ω・`) < 残業しなきゃ...");

  return messages;
};

window.alert(main().join("\n"));

実行方法

Crowdtechの勤怠ページで該当スクリプトを実行する。 下記みたいなメッセージが出力される。

~~~~~~~~~~~~~~~~~
休日数: 11 (土・日:9, 有給:2)
稼働時間(時): 105.75
稼働日数: 14 (残:6)
稼働予定数: 20
☆ 余裕時間(時): 13
☆ 休み可能日数: 1
~~~~~~~~~~~~~~~~~
( ^∀^) < 残業なしで達成可能!