🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

2回答

3350閲覧

【GAS】勤怠管理の出勤時間と退勤時間の登録を別々のアクションで行い、同一の行に登録したい。

YousukeTanaka

総合スコア79

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2021/01/10 12:00

編集2021/01/10 12:10

やりたいこと

現在、勤怠管理システムをGasを使って作成しています。その中で、出勤時間の登録と退勤時間の登録を同じ行で、別々のアクションで行いたいと考えています。

イメージ説明

要件としては、以下のように考えています。
0. 出勤時にスタッフIDを入力し、出勤ボタンを押してもらう。
0. 出勤時の記録が連動したスプレッドシートに反映される。つまり、最終行を取得して、日付(A列)、スタッフID(B列)、出勤時間(C列)にセットされる。
0. 退勤時に、スタッフIDと退勤ボタンを押してもらう。
0. 退勤時の記録が連動したスプレッドシートに反映される。つまり、最終行を取得して、退勤時間(D列)にセットされる。
0. すでにスタッフIDが登録されている場合は、その登録IDと退勤時登録のIDが合致しているかを確認し、間違っていなければ、退勤時間が登録される。
0. 出勤時間の登録を忘れても、退勤ボタンが押されれば、本日の日付、スタッフID、退勤時間が登録される。

問題点

  1. 出勤時の処理と退勤時の処理が別々の行で行われる。これまでは、1回の処理で1行にデータを入れたことがあるが、複数の処理で1行にデータを入れる処理をしたことがなく四苦八苦している。
  2. 出勤時のスタッフIDと退勤時のスタッフIDが同じかどうかのmatch()の使い方が不明。

問題となるコード(特にgasの出勤時の処理と退勤時の処理をご確認ください)

html

1<!DOCTYPE html> 2<html> 3 <head> 4 <base target="_top"> 5 <meta charset="UTF-8"> 6 <meta viewport="width=device-width, initial-scale=1"> 7 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 8 <?!=HtmlService.createHtmlOutputFromFile('css').getContent(); ?> 9 </head> 10 <body> 11 <div class="main"> 12 <div class="container"> 13 <div class="row"> 14 <div class="col-md-8 col-md-offset-2"> 15 <div class="row"> 16 <div class="col-md-6"> 17 <canvas width="300" height="260"> 18 Canvas not supported 19 </canvas> 20 <h1 id="time"></h1> 21 </div> 22 <div class="col-md-6"> 23 <form action="https://script.google.com/macros/s/@@@@/exec" method="post"> 24 <label for="staffId" class="form-label">Staff ID</label> 25 <input type="text" class="form-control mb-3" id="staffId" name="staffId"> 26 <button type="submit" id="attend-btn" class="btn btn-primary mb-3 btn-lg" name="attend" value="出勤時間" onclick="attendTime()">出勤</button> 27 <button type="submit" id="leave-btn" class="btn btn-primary mb-3 btn-lg" name="leave" value="退勤時間" onclick="leaveTime()">退勤</button> 28 </form> 29 </div> 30 </div> 31 </div> 32 </div> 33 </div> 34 </div> 35 <?!= HtmlService.createHtmlOutputFromFile("js").getContent(); ?> 36 </body> 37</html>

javascript

1<script> 2 google.script.run.withSuccessHandler.attendTime(); 3 google.script.run.withSuccessHandler.leaveTime(); 4</script>

gas

1//doGetでindex.htmlを表示する 2function doGet(){ 3 const htmlOutput = HtmlService.createTemplateFromFile("index").evaluate(); 4 return htmlOutput; 5} 6 7 8//doGetでindex.htmlに入力された値を取得してスプシへ移行 9function doPost(e){ 10 //var url ="https://docs.google.com/spreadsheets/d/@@@@@@@@@@@/edit#gid=0"; 11 const ss = SpreadsheetApp.openById('@@@@@'); 12 // スプレッドシートの中のシート名を指定して変数に格納。 13 const recordSheet = ss.getSheetByName('データ'); 14 15 //出勤時の処理 16 const date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd'); 17 const idNumber = e.parameters.staffId; 18 const attend = attendTime(); 19 const array = [date,idNumber,attend]; 20 recordSheet.appendRow(array); 21 22//退勤時の処理 23const lastRow = recordSheet.getDataRange().getLastRow(); //最終行を取得 24const firstColumn = recordSheet.getRange(1, lastRow+1, 1, 1).getValue(); 25const fourthColumn = recordSheet.getRange(4, lastRow+1, 1, 1).getValue(); 26const leave = leaveTime(); 27 28↓この処理が正しいのかが不明。 29if(leaveColumn.isBlank() || (firstColumn.isBlank() && leaveColumn.isBlank())){ 30 recordSheet.getRange(1, lastRow+1, 1, 1).setValue(date); 31 recordSheet.getRange(4, lastRow+1, 1, 1).setValue(leave); 32} 33 34 35//関数 showLastModifide の定義 36function attendTime(){ 37 const attendTime = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'HH:mm'); 38 return attendTime; 39} 40 41//関数 leaveTime の定義 42function leaveTime(){ 43 const leaveTime = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'HH:mm'); 44 return leaveTime; 45}

補足

htmlは、gasの「ウェブアプリケーションとして導入」で公開予定

以上、色々と試行錯誤して、解決に時間がかかっておりましたのでご相談させていただきました。
以前も関連するテーマで質問させていただいております(https://teratail.com/questions/314610)。
不明点などございましたら、ご連絡ください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/01/10 17:14

「色々と試行錯誤」した内容を記載すると、回答者の参考になる場合がありますよ。
papinianus

2021/01/10 20:44 編集

重複質問はシステム上の低評価項目にあたるのでマイナスしました。(その他、ではないので、私の勝手な判断ではないです) やりたいことが全く同じ(コードがかわっていたとしても。みてませんが)だと受け取りました。違うのであれば、どう違うかを質問上具体化してください。
YousukeTanaka

2021/01/12 00:07

承知しました。上記、確認いたしました。作法を教えていただき、ありがとうございました。確かに、同じような質問が2つあるのは不適切と感じました。以後、注意いたします。
guest

回答2

0

  • 夜勤(日付を超える勤務)があり得ないのか
  • 出勤がなく退勤された場合どうするのか
  • 出勤や退勤を何度も押されたらどうするのか

このあたり、多分普通に事務をやっていたら遭遇する事態だと思いますが、どうお考えなのでしょうか。

  • そもそも別の行に書く必要はない
    別の行に書くと、退勤のときだけデータスキャンが発生するので、出勤に比べて実行に時間がかかります。特にpostでやってしまうとユーザ体験が悪いです。
    集計は、別のところでやるべきなので、日付とユーザと出勤/退勤と時刻の4項目からなるデータがあれば十分です。そしてそれは appendRow で実現できます。
    実装が難しいというのはデータ構造的に難しいことをしようとしているので、多くの場合設計がよくないサインです。
    区分や管理IDは集計時に付与すべき事項だと思います。区分や管理IDに何が入るかはわかりませんが、例えば休日出勤や振り替え出勤などを区分に入れるのかな、と想定。

となると、

javascript

1const doGet = () => HtmlService.createTemplateFromFile("index").evaluate(); 2const dateToYMD = (d) => Utilities.formatDate(d, 'Asia/Tokyo', 'yyyy/MM/dd'); 3const dateToHM = (d) => Utilities.formatDate(d, 'Asia/Tokyo', 'HH:mm'); 4 5const attend = (id) => appendRowToDataSheet(id,"出勤"); 6const leave = (id) => appendRowToDataSheet(id, "退勤"); 7const appendRowToDataSheet = (id,action) => { 8 const sheet = SpreadsheetApp.getActive().getSheetByName('データ'); 9 const now = new Date(); 10 const ymd = dateToYMD(now); 11 const hm = dateToHM(now); 12 const data = sheet.getDataRange().getValues(); 13 const existing = data.filter(e=> dateToYMD(e[0]) === ymd && e[1] === id && e[2] === action); 14 if(existing.length > 0) return `既に ${id}さんの${action}の記録があります。`; 15 if(action === "退勤") { 16 const prerequired = data.filter(e=> dateToYMD(e[0]) === ymd && e[1] === id && e[2] === "出勤"); 17 if(prerequired.length < 1) return `${id}さんは${ymd}に出勤していません。`;; 18 } 19 sheet.appendRow([ymd, id, action, hm]); 20 return `${id}さんは${hm}${action}しました。${action === "退勤" ? "おつかれさまでした。" : "本日もよろしくおねがいします。"}`; 21}

javascript

1<!DOCTYPE html> 2<html> 3<head> 4 <base target="_top"> 5 <meta charset="UTF-8"> 6 <meta viewport="width=device-width, initial-scale=1"> 7 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 8 <!--<?!=HtmlService.createHtmlOutputFromFile('css').getContent(); ?>--> 9</head> 10<body> 11<div class="main"> 12 <div class="container"> 13 <div class="row"> 14 <div class="col-md-8 col-md-offset-2"> 15 <div class="row"> 16 <div class="col-md-6"> 17 <canvas width="300" height="260"> 18 Canvas not supported 19 </canvas> 20 <h1 id="time"></h1> 21 </div> 22 <div class="col-md-6"> 23 <form action="#" method="post"> 24 <label for="staffId" class="form-label">Staff ID</label> 25 <input type="text" class="form-control mb-3" id="staffId" name="staffId"> 26 <button type="button" id="attend-btn" class="btn btn-primary mb-3 btn-lg" name="action" value="出勤" onclick="javascript:attend();">出勤</button> 27 <button type="button" id="leave-btn" class="btn btn-primary mb-3 btn-lg" name="action" value="退勤" onclick="javascript:leave();">退勤</button> 28 </form> 29 </div> 30 </div> 31 </div> 32 </div> 33 </div> 34 <textarea id="output" readonly></textarea> 35</div> 36<script> 37 const attend = () => { 38 document.getElementById("output").value = "記録中"; 39 google.script.run.withSuccessHandler(result => { 40 document.getElementById("output").value = result; 41 }).attend(document.getElementById("staffId").value); 42 } 43 const leave = (id) => { 44 document.getElementById("output").value = "記録中"; 45 google.script.run.withSuccessHandler(result => { 46 document.getElementById("output").value = result; 47 }).leave(document.getElementById("staffId").value); 48 } 49</script> 50</body> 51</html>

投稿2021/01/10 20:53

編集2021/01/10 20:56
papinianus

総合スコア12705

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YousukeTanaka

2021/01/12 00:09

上記の件、一度帰宅後に試してみます。取り急ぎ、お礼を申し上げます。
guest

0

  • css を共有してください。こちらで再現させられないコードはデバッグ依頼として不適切です。デバッグ依頼でないなら問題を具体化してください。
  • post に対して return がないので、途中なのかなと思います(コード的にも } がたりてないですし。つまり実行できないです)。その秘匿したい秘密の秘伝の最強のコードの部分で考慮済みだと思いますが、出勤がなく退勤したらどうやって退勤の行を特定したらいいのですかね。私自身もそうですが、タイムカードの付け忘れたなんてよくある話だと思います。

html

1// index.html 2<!DOCTYPE html> 3<html> 4<head> 5 <base target="_top"> 6 <meta charset="UTF-8"> 7 <meta viewport="width=device-width, initial-scale=1"> 8 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 9 <?!=HtmlService.createHtmlOutputFromFile('css').getContent(); ?> 10</head> 11<body> 12<div class="main"> 13 <div class="container"> 14 <div class="row"> 15 <div class="col-md-8 col-md-offset-2"> 16 <div class="row"> 17 <div class="col-md-6"> 18 <canvas width="300" height="260"> 19 Canvas not supported 20 </canvas> 21 <h1 id="time"></h1> 22 </div> 23 <div class="col-md-6"> 24 <form action="https://script.google.com/macros/s/@@@@/exec" method="post"> 25 <label for="staffId" class="form-label">Staff ID</label> 26 <input type="text" class="form-control mb-3" id="staffId" name="staffId"> 27 <button type="submit" id="attend-btn" class="btn btn-primary mb-3 btn-lg" name="action" value="出勤">出勤</button> 28 <button type="submit" id="leave-btn" class="btn btn-primary mb-3 btn-lg" name="action" value="退勤">退勤</button> 29 </form> 30 </div> 31 </div> 32 </div> 33 </div> 34 </div> 35</div> 36</body> 37</html>

javascript

1const doGet = () => HtmlService.createTemplateFromFile("index").evaluate(); 2 3const doPost = (event) => { 4 const sheet = SpreadsheetApp.getActive().getSheetByName('データ'); 5 const now = new Date(); 6 const id = event.parameter.staffId; // or e.parameters.staffId[0]; 7 const action = event.parameter.action; // or e.parameters.action[0]; 8 switch(action) { 9 case "出勤": 10 checkIn(sheet, id, now) 11 break; 12 case "退勤": 13 checkOut(sheet,id,now) 14 break; 15 default: 16 console.log(`不正な操作 ${action} ${now} ${id}`); 17 break; 18 } 19 return HtmlService.createTemplateFromFile("index").evaluate(); 20} 21const checkIn = (sheet, id, date) => { 22 sheet.appendRow([dateToYMD(date), id, dateToHM(date)]); 23} 24const checkOut = (sheet, id, date) => { 25 const data = sheet.getDataRange().getValues(); 26 const ymd = dateToYMD(date); 27 const hm = dateToHM(date); 28 const i = data.reduce((a,c,i) => (dateToYMD(c[0]) === ymd && c[1] === id ? i : a),-1); 29 if(i === -1) { 30 sheet.appendRow([ymd, id, "00:00", hm]); 31 return; 32 } 33 sheet.getRange(i+1,4).setValue(hm); 34} 35const dateToYMD = (d) => Utilities.formatDate(d, 'Asia/Tokyo', 'yyyy/MM/dd'); 36const dateToHM = (d) => Utilities.formatDate(d, 'Asia/Tokyo', 'HH:mm'); 37

投稿2021/01/10 20:47

編集2021/01/10 20:59
papinianus

総合スコア12705

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YousukeTanaka

2021/01/12 00:09

上記の件、一度帰宅後に試してみます。取り急ぎ、お礼を申し上げます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問