素人が翌日の勤務(シフト)を告げるLINEbotを作った話【12】

今回はフォームを作ります

勤務変更の処理を従業員各自で行ってもらうために、Googleフォームを利用する、ということを前回書きました。今回はフォームの内容を作っていきます。

まず、スプレッドシートのツールタブから「フォームを作成」を選びます。

無題

すると、フォーム作成画面が現れます。表題(testの部分)は「勤務変更」とか「シフト変更」に書き換えましょう。

画像3

スプレッドシートにはフォームの回答が送られるシートが自動的に生成されます。ここは名前を「勤務変更」とします。

画像3

次に質問を書いていきます。

画像4

質問の形式と順番は統一していただきたいのですが、質問文は多少変えてもらって構いません。

①回答者の名前(プルダウン形式・必須)

②シフトが変更される人の名前(プルダウン形式・省略可)

③変更する日の日付(日付形式・必須)

④変更後のシフト(プルダウン形式・必須)

※設問②については、入力の手間を省くため省略可能とします。この後記述するコードで補完できるようにします。

また、設問①,②,④については、現時点で選択肢を入力しなくてもOKです。それはなぜかといいますと、ここにも管理者が楽できるポイントがあるからです。

職場の従業員って、異動や入退職で割と入れ替わりますよね。また①の設問などはユーザーの選択肢だけあれば良いわけですが、従業員の入れ替わりやユーザーのフォロー/ブロックのたびにこの設問に手入力するのは愚の骨頂というものです。せっかくプログラミングをやってるわけですから、ここはコード書いて解決といきたいですね。

コードを書いていきます!

function updateForm() {  
  const form = FormApp.openById("ID************************");
  const items = form.getItems();
  const firstQuestion = items[0];
  const secondQuestion = items[1];
  const fourthQuestion = items[3];
  const userList = userSheet.getRange(2,4,userSheet.getLastRow()).getValues().flat();
  var popIt = userList.pop();
  const staffList = sheet.getRange(2,1,sheet.getLastRow()).getValues().flat();
  popIt = staffList.pop();
  const workList = ["早","日","遅","夜","明","公","有"]
 
  firstQuestion.asListItem().setChoiceValues(userList).setRequired(true);
  secondQuestion.asListItem().setChoiceValues(staffList).setRequired(false);
  fourthQuestion.asListItem().setChoiceValues(workList).setRequired(true);
};

2行目の"ID*************"部には、フォームのIDを入力します。IDはフォーム編集画面上のURL

docs.google.com/forms/d/○○○○○○○/editの、〇〇〇〇の部分です。

まず最初にフォームの内容を取得し、firstQuestion(設問①)にユーザーの一覧を配列で渡しています。ユーザーの一覧は、スプレッドシートの”ユーザー”シートがありますよね。ここからユーザーのデータを取得し、人数が増減しても自動的にフォームの内容も変動する、ということです。

同じようにsecondQuestion(設問②)には従業員の一覧を配列で渡します。従業員の一覧は、”勤務表”シートのA列から取ることができます。従業員が増えたり減ったりしてもこれで大丈夫です。

最後にfourthQuestion(設問④) は、普通に選択肢を入力しても良かったのですが、なんとなく配列を直接書いて渡しています。

const workList = ["早","日","遅","夜","明","公","有"]

この部分ですね。設問①も②も要するにスプレッドシートからデータを引っ張ってきて、[ ]の中に一つずつ並べているんですね。この配列を渡すと、質問の選択肢になるということです。

ちなみに①②は配列の中でも二次元配列で値を取得していて、それを.flat()することで一次元配列化しています。また、なぜかはわかりませんが配列の最後に空白の値が入る現象が起きていて気持ち悪かったので、.pop()してしょうきょしています。

それでは試してみましょう。

画像5

うまく質問が作成できました。選択肢はシート上のリストと同じ並びになっていると思います。また、最後の

  firstQuestion.asListItem().setChoiceValues(userList).setRequired(true);
  secondQuestion.asListItem().setChoiceValues(staffList).setRequired(false);
  fourthQuestion.asListItem().setChoiceValues(workList).setRequired(true);

.setRequiredの部分、ここで「必須」か「省略可」かを指定できます。②(secondQuestion)は省略OKにしたいので、( )内をfalseにします。

この関数もトリガー設定して自動的にフォーム内容が更新されるようにしましょう。設定は

画像6

こんな感じで良いでしょう。スプレッドシート内の値が変更されるたびにフォームの内容も更新されるので、今後一切手をかける必要がありません。

ここまでのコード

const channel_access_token = "トークン";
const headers = {
 "Content-Type": "application/json; charset=UTF-8",
 "Authorization": "Bearer " + channel_access_token
};
const spreadsheet = SpreadsheetApp.openById("ID");
const sheet = spreadsheet.getSheetByName("勤務表");
const userSheet = spreadsheet.getSheetByName("ユーザー");
const logSheet = spreadsheet.getSheetByName("ログ");


function doPost(e) {
const webhookData = JSON.parse(e.postData.contents).events[0];
const eventType = webhookData.type;
const replyToken = webhookData.replyToken;

switch(eventType){
  case "follow":  //友だち追加
    follow(webhookData);
    break;
  case "message":  //メッセージ受信
    messageEvent(webhookData);
    break;
  case "unfollow":  //ブロック
    unfollow(webhookData)
    break;
  case "postback":  //ポストバックイベント
    var postbackData = webhookData.postback.params.date
    var targetDate = new Date(postbackData);
    var obj = getWorkDataFromSheet(targetDate);
    var text = obj.text
    sendLineMessageFromReplyToken(replyToken, text)
    break;
}
}

function follow(webhookData){
const userId = webhookData.source.userId;
const nickname = getUserProfile(userId);
const last_row = userSheet.getLastRow();

userSheet.getRange(last_row+1,2).setValue(nickname);
userSheet.getRange(last_row+1,3).setValue(userId);
userSheet.getDataRange().removeDuplicates([3])  
}


function messageEvent(webhookData){
const userId = webhookData.source.userId;
const nickname = getUserProfile(userId);
const timestamp = new Date(webhookData.timestamp)
const message = webhookData.message.text.split("\n");
const replyToken = webhookData.replyToken;
const whatToDo = message[0];
const name = message[1];
const date = message[2];
const newshift = message[3];
const last_row = userSheet.getLastRow();
const lastRow = logSheet.getLastRow();

logSheet.getRange(lastRow+1,1).setValue(timestamp);
logSheet.getRange(lastRow+1,2).setValue(nickname);
logSheet.getRange(lastRow+1,3).setValue(userId);
logSheet.getRange(lastRow+1,4).setValue(message[0]);
logSheet.getRange(lastRow+1,5).setValue(message[1]);
logSheet.getRange(lastRow+1,6).setValue(message[2]);
logSheet.getRange(lastRow+1,7).setValue(message[3]);

switch (whatToDo) {
   case 'こんにちは':
     sendLineMessageFromReplyToken(replyToken, "さようなら");
     break;
   case '勤務確認':
     datetimePickerAction(replyToken);
     break;  
   default:
     return;
     break;  
}
}

function unfollow(webhookData){
const data = userSheet.getDataRange()
const userId = webhookData.source.userId;
const userFinder = data.createTextFinder(userId).findAll();
for ( var i = 0; i < userFinder.length; i++ ) {
      var userRow = userFinder[i].getRow();
      var user = userFinder[i].offset(0,1).getValue()
  userSheet.deleteRows(userRow);
}
}

function getUserProfile(userId){ 
const Url = 'https://api.line.me/v2/bot/profile/' + userId;
const userProfile = UrlFetchApp.fetch(Url,{
  'headers': {
    'Authorization' :  'Bearer ' + channel_access_token,
  },
})
return JSON.parse(userProfile).displayName;
}

function sendLineMessageFromReplyToken(token, replyText) {
var url = "https://api.line.me/v2/bot/message/reply";
var headers = {
"Content-Type": "application/json; charset=UTF-8",
"Authorization": "Bearer " + channel_access_token
};
var postData = {
"replyToken": token,
"messages": [{
  "type": "text",
  "text": replyText
}]
};
var options = {
"method": "POST",
"headers": headers,
"payload": JSON.stringify(postData)
};
return UrlFetchApp.fetch(url, options);
}

function sendLineMessageUsingBroadcast(text) {
var Url = "https://api.line.me/v2/bot/message/broadcast";
var postData = {
  "messages": [{
  "type": "text",
  "text":  text,
}
            ]};
var options = {
"method": "POST",
"headers": headers,
"payload": JSON.stringify(postData)
};
return UrlFetchApp.fetch(Url, options);
}

function sendLineMessageFromUserId(text,userId) {
var url = "https://api.line.me/v2/bot/message/push";
var postData = {
"to": userId,
"messages": [{
  "type": "text",
  "text":  text,
}]
};
var options = {
"method": "POST",
"headers": headers,
"payload": JSON.stringify(postData)
};
return UrlFetchApp.fetch(url, options);
}

function sendLineMessageToAdmin(text) {
var url = "https://api.line.me/v2/bot/message/push";
var postData = {
"to": "ユーザーID",
"messages": [{
  "type": "text",
  "text":  text,
}]
};
var options = {
"method": "POST",
"headers": headers,
"payload": JSON.stringify(postData)
};
return UrlFetchApp.fetch(url, options);
}

function sendTomorrowsShiftEverynight(){
var today = new Date();
today.setDate(today.getDate()+1)
const obj = getWorkDataFromSheet(today)
const text = obj.text
if(obj.workers == "0" ){
  return;  
}
sendLineMessageUsingBroadcast(text);
}

function getWorkDataFromSheet(targetDate){ 
const week = ["日","月","火","水","木","金","土"];
const lastRow = sheet.getLastRow();
const month = Utilities.formatDate(targetDate, 'JST', 'MM');
const day = Utilities.formatDate(targetDate, 'JST', 'dd');
const date = month + "/" + day 
const dateFinder = sheet.createTextFinder(date).findAll();

  for ( var i = 0; i < dateFinder.length; i++ ) {
      var dateColumn = dateFinder[i].getColumn();
  }

const data = sheet.getRange(1,dateColumn,lastRow);
let earlyTurnList = [];
let dayShiftList = [];
let lateTurnList =[];
let nightShiftList = [];
let afterNightShiftList = [];
let holidayList = [];
let paidVacationList = [];


let earlyTurnFinder = data.createTextFinder('早').findAll();
for ( let i = 0; i < earlyTurnFinder.length; i++ ) {
  let earlyTurn = earlyTurnFinder[i].offset( 0 , - dateColumn + 1).getValue();
  earlyTurnList.push(earlyTurn)
 }

let dayShiftFinder = data.createTextFinder('日').findAll();
for ( let i = 0; i < dayShiftFinder.length; i++ ) {
  let dayShift = dayShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
  dayShiftList.push(dayShift)
 }  

let lateTurnFinder = data.createTextFinder('遅').findAll();
for ( let i = 0; i < lateTurnFinder.length; i++ ) {
  let lateTurn = lateTurnFinder[i].offset( 0 , - dateColumn + 1).getValue();
  lateTurnList.push(lateTurn)
 } 

let nightShiftFinder = data.createTextFinder('夜').findAll();
for ( let i = 0; i < nightShiftFinder.length; i++ ) {
  let nightShift = nightShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
  nightShiftList.push(nightShift)
 }

let afterNightShiftFinder = data.createTextFinder('明').findAll();
for ( let i = 0; i < afterNightShiftFinder.length; i++ ) {
  let afterNightShift = afterNightShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
  afterNightShiftList.push(afterNightShift)
 }

let holidayFinder = data.createTextFinder('公').findAll();
for ( let i = 0; i < holidayFinder.length; i++ ) {
  let holiday = holidayFinder[i].offset( 0 , - dateColumn + 1).getValue();
  holidayList.push(holiday)
 }

let paidVacationFinder = data.createTextFinder('有').findAll();
for ( let i = 0; i < paidVacationFinder.length; i++ ) {
  let paidVacation = paidVacationFinder[i].offset( 0 , - dateColumn + 1).getValue();
  paidVacationList.push(paidVacation)
 }

var text = date +"("+week[targetDate.getDay()]+") 勤務者一覧"+"\n\n" 
if(earlyTurnList.length > 0){
  text = text +  "早番 : " + earlyTurnList +"\n\n"
}  
if(dayShiftList.length > 0){
  text = text +  "日勤 : " + dayShiftList +"\n\n"
}  
if(lateTurnList.length > 0){
  text = text +  "遅番 : " + lateTurnList +"\n\n"
}
if(nightShiftList.length > 0){
  text = text +  "夜勤 : " + nightShiftList +"\n\n"
}
if(afterNightShiftList.length > 0){
  text = text +  "明け : " + afterNightShiftList +"\n\n"
}  
if(holidayList.length + paidVacationList.length > 0){
 text = text +  "公・休・有 : \n" + holidayList+ "," + paidVacationList + "\n\n" ;
 }

if(dayShiftList.length + earlyTurnList.length + lateTurnList.length + nightShiftList.length + afterNightShiftList.length < 1 ){
  var workers = 0; 
}

return  {text: text, workers: workers}; 
}

function datetimePickerAction(token) {
var startDate = sheet.getRange(1,2).getValue()
startDate = Utilities.formatDate(startDate, 'JST', 'yyyy-MM-dd');
var endDate =sheet.getRange(1,sheet.getLastColumn()).getValue();
endDate = Utilities.formatDate(endDate, 'JST' , 'yyyy-MM-dd');
  

UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
  'headers': {
	'Content-Type': 'application/json',
	'Authorization': 'Bearer ' + channel_access_token, 
	            },
	'method': 'POST',
	'payload': JSON.stringify({
	"replyToken": token, 
	"messages": [
				{
			    "type": "template",
				"altText": "勤務確認",
				"template": {
						    "type": "buttons",
						    "title": "勤務確認",
						    "text": "指定日の勤務者を確認できます",
						    "actions": [
							           {
								       "type": "datetimepicker",
								       "label": "日付を選択してください。",
								       "data": "action=settime",
								       "mode": "date",
                                     "min": startDate,
                                     "max": endDate
							           }
						               ]
					        }
				}
			    ],			
		}),
	});
}

function updateForm() {  
  const form = FormApp.openById("ID************************");
  const items = form.getItems();
  const firstQuestion = items[0];
  const secondQuestion = items[1];
  const fourthQuestion = items[3];
  const userList = userSheet.getRange(2,4,userSheet.getLastRow()).getValues().flat();
  var popIt = userList.pop();
  const staffList = sheet.getRange(2,1,sheet.getLastRow()).getValues().flat();
  popIt = staffList.pop();
  const workList = ["早","日","遅","夜","明","公","有"]
 
  firstQuestion.asListItem().setChoiceValues(userList).setRequired(true);
  secondQuestion.asListItem().setChoiceValues(staffList).setRequired(false);
  fourthQuestion.asListItem().setChoiceValues(workList).setRequired(true);
};

次回はフォームが送信されたときの処理を書いていきます。



この記事が気に入ったらサポートをしてみませんか?