質問するログイン新規登録
Google スプレッドシート

Google スプレッドシートは、フリーで利用できる表計算ソフト。Webアプリのためインターネットに接続することで利用できます。チャートやグラフの作成のほか、シートを他のユーザーと共有したり、同時に作業を進めることも可能です。

Google フォーム

Google フォームは、 Google社が提供しているアンケートフォーム作成および集計ができる無料のツール。Googleアカウントがあれば利用が可能です。集計データは、スプレッドシートに収集され、データ分析もできます。

Google Apps Script

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

Q&A

解決済

1回答

838閲覧

【GAS】Googleフォームで回答者に自動返信メールを送信させる際の同時回答時の不具合解消法

donguriko

総合スコア30

Google スプレッドシート

Google スプレッドシートは、フリーで利用できる表計算ソフト。Webアプリのためインターネットに接続することで利用できます。チャートやグラフの作成のほか、シートを他のユーザーと共有したり、同時に作業を進めることも可能です。

Google フォーム

Google フォームは、 Google社が提供しているアンケートフォーム作成および集計ができる無料のツール。Googleアカウントがあれば利用が可能です。集計データは、スプレッドシートに収集され、データ分析もできます。

Google Apps Script

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

1グッド

2クリップ

投稿2023/08/08 14:22

編集2023/08/10 23:09

1

2

実現したいこと

GASでGoogleフォームで回答入力があったら、回答者宛にメールを自動
返信させたい。
自動返信メールには、フォームの回答データで作成したレーダーチャート
のpdfファイルを添付し、自動送信させたい。

前提

GASは回答が記録されるスプレッドシートのコンテナバインド。
スプレッドシートファイル内には、下記2つのシートが入っている。

1)フォームの回答が記録されるシート
A列、T列、V列は、スプレッドシート上にArrayFormulaで設定
B列~S列はフォーム回答が自動転記される

<回答記録シートイメージ>
イメージ説明

2)PDFイメージの元となるひな形シート
B8:D8とH2:H5はGASで回答記録シートから転記

<ひな形シートイメージ> F列以降はレーダーチャートグラフ
イメージ説明

関数? は全部で4つ。
①Onedit(e) メインPGM
・ひな形シートをコピーし、作業用シートを作成。
・作業用シートのシート名をNUMに変更
・作業用シートに回答シートの内容を転記

②exportAsPdf(Num)
・作業用シートの内容をpdf化
・pdfファイルを指定フォルダーに保存
・作業用シートにpdfファイルのファイルIDを転記

③sendEmail_attachFile(Num)
・添付ファイルをつけてメール送信

④回答が記録されるスプレッドシートから作業用シートを削除。

発生している問題・エラーメッセージ

回答が1つずつ時間差で来る場合は正常に動作します。
しかし、2つの回答が同時(僅差)できた場合、2つのうち1つは
正常に処理されるものの、2つ目の回答は処理回答記録シートに回答は
記録されますが、関数①の2つ目の・以降の処理がはじかれてしまい
正常終了されません。
下のエラーを見ると、後からきた方の回答が正しい連番Numを取得できて
いない様に見えます。

実際の運用で、2つ以上の回答が同時(僅差)でくる可能性があります。
はじかれるのを回避する策 または、回答がはじかれた場合、回答者に
回答がはじかれたことを知らせる方法 のアドバイスを教えてほしいです。

(片方がはじかれた時のエラー) Exception: シート名「40」はすでに存在しています。別の名前を入力してください。 at onedit(【合体】フォーム送信時に結果pdfをメール送信:26:9)

該当のソースコード

以下、①~④の関数全文を記載します。(長文ご容赦を)

GAS

1関数① 2function onedit(e) { 3 4const Ss = SpreadsheetApp.getActiveSpreadsheet(); 5const fromSheet = Ss.getSheetByName('フォームの回答 1'); 6const tempSheet = Ss.getSheetByName('ひな形シート'); 7const newSheet = tempSheet.copyTo(Ss); 8const NewSheetName = newSheet.getName(); 9console.log('NewSheetName ' + NewSheetName); 10const toSheet = Ss.getSheetByName(NewSheetName); 11 12//▼フォームの回答シートからデータを取得 13const targetRow = fromSheet.getRange(fromSheet.getMaxRows(),3).getNextDataCell(SpreadsheetApp.Direction.UP).getRow(); // 14console.log('targetRow ' + targetRow); 15const Values = fromSheet.getRange(targetRow,1,1,22).getValues(); //A列~V列を配列で取得 16console.log('Values ',Values); 17 18const Num = Values[0][0] //A列_連番を取得 19const address = Values[0][2] //C列_メアドを取得 20const name = Values[0][18] ; //S列_氏名を取得 21const rythmSum = Values[0][19] ; //T列_リズム計を取得 22const qualitySum = Values[0][20] ;//U列_質計を集計 23const volumeSum = Values[0][21] ; //V列_量計を集計 24 25//▼ひな形シートに取得したデータをコピー 26toSheet.getRange(2,8,1,1).setValue(Num); //作業シートセルH2にNumを転記 27toSheet.setName(Num); 28SpreadsheetApp.flush(); 29toSheet.getRange(2,8,1,1).setValue(Num); //作業シートセルH2にNumを転記 30toSheet.getRange(4,8,1,1).setValue(address); //作業シートセルH5にメアドを転記 31toSheet.getRange(2,2,1,1).setValue(name); //作業シートセルB2にnameを転記 32toSheet.getRange(8,2,1,1).setValue(rythmSum); //作業シートセルB8にrythmSumを転記 33toSheet.getRange(8,3,1,1).setValue(qualitySum); //作業シートセルC8にqualitySumを転記 34toSheet.getRange(8,4,1,1).setValue(volumeSum); //作業シートセルD8にvolumeSumを転記 35SpreadsheetApp.flush(); 36exportAsPdf(Num); 37sendEmail_attachFile(Num); 38delWorkSheet(Num) 39} 40 41関数② 42function exportAsPdf(Num) { 43 const Ss = SpreadsheetApp.getActiveSpreadsheet(); 44 const SsId = Ss.getId(); 45 console.log('SsId ' + SsId); 46 47 const AnsSheet = Ss.getSheetByName('フォームの回答 1'); 48 const sheet = Ss.getSheetByName(Num); 49 console.log('Num ' + Num); 50 //console.log('ActiveSheet ' + sheet); 51 const targetSheetId = sheet.getSheetId(); 52 console.log('targetSheetId ' + targetSheetId); 53 54 //▼保存先フォルダtoFolderの指定 55 //https://drive.google.com/drive/folders/ ~省略~ 56 //ID ~省略~ 57 const toFolder = DriveApp.getFolderById('~省略~'); 58 59 const name = sheet.getRange(2, 2, 1, 1).getValue(); 60 const No = sheet.getRange(2, 8, 1, 1).getValue(); 61 console.log('name ' + name); 62 console.log('No ' + No); 63 64 const fileName = No + "_" + name + "チェック結果"; 65 console.log('fileName ' + fileName); 66 //const tempSheet = Ss.getSheetByName('ひな形シート') 67 sheet.getRange(3, 8, 1, 1).setValue(fileName); //セルH3にファイル名を転記 68 SpreadsheetApp.flush(); 69 70 createPdf(toFolder, SsId, targetSheetId, fileName ,Num); 71} 72function createPdf(toFolder, SsId, targetSheetId, fileName, Num) { 73 74 let baseUrl = "https://docs.google.com/spreadsheets/d/" 75 + SsId 76 + "/export?gid=" 77 + targetSheetId; //どのSsのどのシートをエキスポートするかを指定 78 79 let pdfOptions = "&exportFormat=pdf&format=pdf" 80 + "&size=A4" //用紙サイズ (A4) 81 + "&portrait=false" //用紙の向き true: 縦向き / false: 横向き 82 + "&fitw=true" //ページ幅を用紙にフィットさせるか true: フィットさせる / false: 原寸大 83 + "&top_margin=0.50" //上の余白 84 + "&right_margin=0.50" //右の余白 85 + "&bottom_margin=0.50" //下の余白 86 + "&left_margin=0.50" //左の余白 87 + "&horizontal_alignment=CENTER" //水平方向の位置 88 + "&vertical_alignment=TOP" //垂直方向の位置 89 + "&printtitle=false" //スプレッドシート名の表示有無 90 + "&sheetnames=false" //シート名の表示有無 91 + "&gridlines=false" //グリッドラインの表示有無 92 + "&fzr=false" //固定行の表示有無 93 + "&fzc=false"; //固定列の表示有無 94 95 96 let url = baseUrl + pdfOptions; //pdf変換する際にアクセスするURL 97 console.log('baseUrl ' + baseUrl); 98 console.log('pdfOptions ' + pdfOptions); 99 console.log('url ' + url); 100 101 //アクセストークンを取得する 102 let token = ScriptApp.getOAuthToken(); //pdf変換に必要な情報 103 console.log('token ' + token); 104 105 //headersにアクセストークンを格納する 106 let options = { 107 headers: { 108 'Authorization': 'Bearer ' + token 109 } 110 }; 111 112 //PDFを作成する 113 let response = UrlFetchApp.fetch(url, options); 114 let file = response.getBlob().setName(fileName + '.pdf'); 115 116 //PDFの保存先フォルダー 117 //フォルダーIDは引数のfolderIdを使用 118 let folder = DriveApp.getFolderById('~省略~'); 119 120 //PDFを指定したフォルダに保存する 121 const newFile = folder.createFile(file); 122 const newFileId = newFile.getId(); 123 console.log('newFileId ' + newFileId); 124 125 const Ss = SpreadsheetApp.getActiveSpreadsheet(); 126 const sheet = Ss.getSheetByName(Num); 127 sheet.getRange(5, 8, 1, 1).setValue(newFileId); //セルH5にpdfファイルのファイルIDを転記 128 SpreadsheetApp.flush(); 129} 130 131関数③ 132function sendEmail_attachFile(Num) { 133 134const Ss = SpreadsheetApp.getActiveSpreadsheet(); 135const sheet = Ss.getSheetByName(Num); 136console.log('Num ' + Num); 137 138//▼送信先アドレスの指定 139const recipient = sheet.getRange(4,8,1,1).getValue(); //作業シートセルH4から取得 140console.log('recipient ' + recipient); 141 142//▼メール件名の指定 143const subject = "★テスト★ 【自動送付】チェック結果"; 144 145//▼メール本文の指定 146let body =`●本メールは自動送信メールです。本メールへの返信は行わないでください●\n`; 147 body +=`\n`; 148 body +=`チェックの回答お疲れさまでした。\n`; 149 body +=`あなたのチェック結果を下に添付します。\n`; 150 body +=`自分のチェック結果が添付されているかをご確認ください。\n` ; 151 body +=`\n`; 152 body +=`もし、添付されているファイルに誤りがある場合はお手数ですが\n`; 153 body +=`XXXX XXまでメールでご連絡ください。\n`; 154 body +=`チェック結果は当日ご持参ください。\n`; 155 156//▼添付ファイルattachingの指定 157//▼保存先フォルダtoFolderの指定 158//https://drive.google.com/drive/folders/~省略~ 159//ID ~省略~ 160const fromFolder = DriveApp.getFolderById('~省略~'); 161console.log('fromFolder ' +fromFolder); 162 163//▼取得するファイル名の指定 164//.sendEmailのoptionsにattachFileを指定する 165const targetFileId = sheet.getRange(5,8,1,1).getValue(); //ひな形シートセルH5から取得 166console.log('targetFileId ' + targetFileId); 167let attachFile = DriveApp.getFileById(targetFileId).getBlob(); 168console.log('attachFile ' + attachFile); 169 170let options = { 171 name : '自動送信メール', 172 attachments: attachFile, 173 noReply: true 174 }; 175GmailApp.sendEmail(recipient,subject,body,options); 176SpreadsheetApp.flush(); 177} 178 179関数④ 180function delWorkSheet(Num) { 181 182const Ss = SpreadsheetApp.getActiveSpreadsheet(); 183const sheet = Ss.getSheetByName(Num); 184Ss.deleteSheet(sheet); 185}

試したこと

僅差での回答不具合が発覚する前は、
①Onedit(e)でひな形シートに都度、内容を直接上書きさせていました。
同時回答時の不具合を受けて、回答別に作業用シートを作成すれば、
解消できるのでは? と考え、作業用シートを作成する上記コードに修正
しました。

(2023/8/11追記) 
質問コメントのYAmaGNZさま、YellowGreenさまからのアドバイスを受け、
「シート名が存在するか確認し、存在する場合はもう一行上の行を作業対象
とする」案を取り入れ、do while でコードの修正を試みましたが、知識不足
で躓きました。

「取得した配列内にNumが存在しているか」の確認は以前、別件で質問した
下記質問を参照し、.includes を使用し途中までコードを記載してみました。
が、? 以降のtrueの場合、falseの場合の記載をどのようにdo whileとつなげれば
よいのかが分かりませんでした。
過去質問リンク

●大変申し訳ありませんが、どのようにコードを修正すればよいのか、
教えてください。

GAS

1(以下、トライしてみたコード) 2 ①OnEdit(e) メインPGM に追記する想定です 3 4//▼取得したデータを転記するための作業用シートを作成 5const newSheet = tempSheet.copyTo(Ss); 6const NewSheetName = newSheet.getName(); 7console.log('NewSheetName ' + NewSheetName); 8const toSheet = Ss.getSheetByName(NewSheetName); 9 10//do Whileループでシート名を回答連番に変更 11const allSheets = Ss.getSheets().flat(); //ファイル内のすべてのシートを配列で取得 12const sheetLen = allSheets.length; 13console.log('allSheets ' + allSheets); 14console.log('sheetLen ' + sheetLen); 15 16//forループでシート名を1つずつ取得 17 for (let i=0; i<sheetLen; i++ ){ 18allSheets[i].getSheetName(); 19 20//配列allSheets内にNumが含まれているか確認 21const result = Num.map(Num => allSheets.includes(Num)? ['true']:[false]); //★[]以降の指定、分からない★ 22} 23 24//回答シートで取得した連番がすでに存在している場合 25//★ココからの記載わからない  26//何(仮targetNum)とNumを比較すればよい? resultとNumの比較?? ★ 27//   ↓ 28//targetNum === Num の時は処理実行  29 30do { 31console.log('Num ' + Num); 32console.log('TargetNum ' + TargetNum); 33Num--; //Numの値を1ずつ減らす 34} while (targetNum > 2) //(終了条件)targetNum=2 (継続条件)targetNum>2 35 36toSheet.setName(Num); //シート名を変更 37

補足情報(FW/ツールのバージョンなど)

非エンジニアビギナーです。
トリガー設定、イベントオブジェクトの知識・理解が不十分
なので、非エンジニアビギナーでも理解できるレベルでのアドバイス
をいただけると助かります。

<質問>
基礎の基礎の質問で申し訳ありません。
以下についても教えてほしいです。

①OnEdit(e)のeは引数ですか? <<eはイベントオフジェクトですか?

②トリガー設定で動く関数の場合()内は必ずアルファベットeでないと
ダメですか?

③eはコードの中でeについて宣言? 定義は不要なのですか?

④eの中に何が入っているか、console.log()で確認してみたい場合、
()内にどのようなコードを記載すれば確認できますか?

<質問8/11追記>
⑤上記④のさら問い。
質問コメントでYellowGreenさまご回答いただいた件で「さら問い」です。
今回の場合だと、下のどちらが正解ですか?
自分で両方試してみてください、だと思いますが上で躓いたため、コードが未完成で
まだ自分でconsole.log確認ができず。。。
a) console.log('e.value' + e.value());
b) console.log('e.range.getValue' + e.range.getValue());

⑥基本的なことで申し訳ありません。
a)もb)も.value()で値を取得していそうなのはわかるのですが、
a)とb)の違い(どう使い分ければよいのか)がわからないので教えてください。
沢山質問してしまい申し訳ありません。

y_waiwaiを押しています

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

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

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

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

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

YellowGreen

2023/08/08 16:34 編集

今回はリファレンスがあります。 https://developers.google.com/apps-script/guides/triggers/events?hl=ja こちらはご覧になりましたか? 1) eは引数(仮引数)です。この関数をトリガーで呼び出す側(Googleサーバー側)では引数にイベントオブジェクトを渡します。 2) 引数をeとするのは慣習だと思います。リファレンスにもeとありますね。 eでなくてもeventObjectでも予約語と重複しなければ任意の名称でいいと思います。 3) 引数なので改めての宣言はしません。 4) 渡されるオブジェクトの構造がわかっていれば、値を確認できます。 例えば、 console.log(e.source.getName()); など。
YellowGreen

2023/08/09 09:02 編集

const targetRow = fromSheet.getRange(fromSheet.getMaxRows(),3).getNextDataCell(SpreadsheetApp.Direction.UP).getRow(); const Values = fromSheet.getRange(targetRow,1,1,22).getValues(); const Num = Values[0][0] //A列_連番を取得 としているので、 フォームの回答1シートに二つの回答が記録されるタイミングと 作業シート名の番号取得のタイミングとの関係で、 二つの回答のうちの若干早い方の回答で連番40を取得した直後に 若干遅い方の回答も同じ行から番号を取得してしまうので 早い方の回答で40という名前で作業シートを作成したあとに 再び遅い方の回答で40という名前のシートを作成しようとして エラーになっているのではないでしょうか? 作業ファイル名が連番でなくてもいいのであれば、重複を防ぐため、 const num = new Date().getMilliseconds(); などとしてみるとどうなりますか? ファイル名は 3 桁になりますが... と書きましたが、ファイル名の重複がなくなるだけで 取得するデータは同じ行になってしまいますね。
YellowGreen

2023/08/08 16:20

フォームの送信をトリガーにしているのであれば、 関数名はoneditでない方がいいと思います。 上でお示ししたリファレンスで「シンプルなトリガー」のリンク先に説明がある スプレッドシートの編集時に起動するスクリプトが onEditなので一文字違いで紛らわしい名称になっています。
YAmaGNZ

2023/08/08 22:23

YellowGreenさんも指摘しているように作業対象の行を最後の行としているために 例えば39、40と追加されてoneditが呼び出された時、早いほうも遅いほうも対象を40の行としてしまいシート名が重複することになっているのだと思います。 強引ではありますが、作業用のシートを作成する際にシート名が存在するか確認し、存在する場合はもう一行上の行を作業対象とするとかしてはどうでしょうか。
YellowGreen

2023/08/08 22:54

作業用のシートを作成する際にシート名が存在するか確認し、存在する場合はもう一行上の行を作業対象とするとかしてはどうでしょうか。 念のためですが、 3つの回答がほぼ同時に来たときどうする?4つなら?などにも対応できるようにしなければならないので、do whileループなどの反復処理で行うことになると思います。
donguriko

2023/08/10 23:00 編集

YAmaGNZさま、YellowGreenさま コメント返信ありがとうございます。 「シート名が存在するか確認し、存在する場合はもう一行上の行を作業対象とする」案 を取り入れ、do while でコードの修正を試みましたが、知識不足で躓きました。 上記「試したこと」にトライしたコードを追記させていただきました。 アドバイスをいただけると幸いです。
YellowGreen

2023/08/11 00:52 編集

⑤上記④のさら問い。 このようなご質問を受けなくてもいいようにリファレンスをお示ししたつもりでした。 onEdit(e)のeということなので、 スプレッドシートを手動で編集したときのシンプルトリガーの イベントオブジェクトについての回答になります。 (元のご質問のスクリプトのトリガーはフォームの送信時だと思いますが。) 以下、レファレンスより抜粋 スプレッドシートの編集のイベントオブジェクト(authMode, oldValue,range,source,triggerUid,user,value)のうち value: 編集後の新しいセルの値。編集範囲が 1 つのセルの場合にのみ使用できます。 range: 編集されたセルまたはセルの範囲を表す Range オブジェクト。 なので、 e.valueは、編集されたセルの値が入ります。 e.rangeは、編集されたセル範囲が入ります。 console.log(e.value);//リファレンスのとおりvalueに()はついてません で編集されたセルの値を表示できます。 一旦セル範囲(e.range)を取得してから値を表示する場合は、 console.log(e.range.getValue());//編集された単一セルの値を表示 console.log(e.range.getValues());//コピペなどで編集されたセル範囲の値を二次元配列として表示([[10, 20], [11, 21], [12, 22]]など) console.log(e.range.getA1Notation());//編集されたセル範囲のA1表記を表示('A2:B3'など) などで編集されたセルの値やセル範囲を表示できます。 e.valueは値を取得e.rangeはセル範囲を取得なので、 編集されたセル範囲を操作したいときにe.rangeで範囲を取得し、 続けてそのセル範囲に対する操作を記入します。 例えば、スプレッドシートに function onEdit(e) { e.range.setValue('X'); } というスクリプトを保存してしまうと、 そのスプレッドシートのどこかのセルに何を入力しても 削除してもセルの値がXで上書きされてしまいます。 これで⑥の答えにもなってますでしょうか?
donguriko

2023/08/11 01:17 編集

<コメントが行き違いになりました。申し訳ありません> YellowGreenさま お忙しい中、⑤上記④のさら問いへの回答ありがとうございます。 「console.log(e.value);//リファレンスのとおりvalueに()はついてません」 公式リファレンスを読みこなすことができず、申し訳ありません。 何度も質問して大変申し訳ないのですが、以下の理解であっていますか? イ)今回のケースの場合、onEdit(e)は、フォーム送信時でトリガー設定しているので、  e.rangeは編集されたスプレッドシート範囲なので、フォームの質問数分の列数×1行分の範囲  のこと。 ロ)console.log(e.range.getValues())だと、スプレッドシートに記録された回答1行分の内容が 取得できる。 ハ)console.log(e.range.getRow())をすると、スプレッドシートの何行目にデータ転記したか  取得できる。  なので、e.range.getRow()で取得した行番号をキーに、回答記録シートから回答連番が取得可能。
YellowGreen

2023/08/11 01:12 編集

最初に書いたように onEdit(e)の場合は、スプレッドシートを「手動で」「編集したとき」のトリガーイベントになります。 その前提の上で、リファレンスのとおり編集されたセル範囲(Rangeオブジェクト:通常シートからgetRange()メソッドで取得できるオブジェクト)がe.rangeに入ります。 元のご質問の場合はフォームの回答から自動入力されたシートから関数式で入力されているとのことなので、 編集時のシンプルトリガーは発動しません。 フォームの回答が同時にきた時のご質問なので、 おそらく、「フォームの送信」をトリガーにされていると思います。 その場合は、フォームの送信のトリガーイベントのリファレンスをご確認ください。 なお、くどいようですが、 その場合の関数名はoneditにしない方が紛らわしくなりません。 慣習的には、onFormSubmit(e)か関数の動作を表す名称を使うことが多いようです。
YellowGreen

2023/08/11 01:18

上のコメントの編集後の部分ですが、 イ)違います。 ロ)違います。 ハ)違います。手動で編集した行番号です。 ハ)なので:できません。
YellowGreen

2023/08/11 01:22 編集

理由はあえて書きませんでした。 リファレンスをお示ししています。 イベントオブジェクトのリファレンスの スプレッドシートの項の フォームの送信をご確認ください。
donguriko

2023/08/11 01:22

YellowGreenさま 何度も申し訳ありません。 (誤)onEdit() 手動の場合⇒ (正)onFormSubmit()  フォームから飛んでくる場合という事ですね。 「紛らわしい」の意味がやっと分かりました。 ずっと勘違いして使用しておりました。 教えていただきありがとうございました。
YellowGreen

2023/08/11 01:38 編集

それから、元のご質問のスクリプトでは フォーム送信のイベントオブジェクト e の値は参照していないですよね。 リファレンスを確認されることが前提ですが、 フォーム送信のイベントオブジェクトから値を参照するなら、 const val = e.values; //valueではなくvalues で回答データの配列を取得して、 let rythmSum = 0; let qualitySum = 0; let volumeSum = 0; for (let i = 2; i <= 6; i++) { rythmSum += val[i]; qualitySum += val[i + 5]; volumeSum += val[i + 10]; } などとして直接値を得ることで複数の回答の重複を避ける方法もあるかと思います。
YellowGreen

2023/08/11 07:51 編集

おそらく、今の方法だと、 シートには送信の早い方から順に回答内容が転記され、 スクリプトも送信の早い順に起動するので、 同じ番号のシート名が存在しないうちに連番を取得するのは、 早く起動したスクリプトですが、最終行の連番を取得します。 遅く起動したスクリプトは、同じ番号があるので最終行の一つ上の行を取得します。 でも最終行は遅く送信された回答の番号でその上の行は早く送信された回答の番号です。 やはりフォームの送信トリガーのイベントオブジェクトからメールアドレスを取得して、 シートに転記されたメールアドレスと照合するようにしてみました。 同じメルアドで同時に回答を送信するということは想定していません。 function onFormSubmit(e) { if (!e) { console.log('このスクリプトはエディタから実行できません。フォームから回答を送信してください。'); return; } const eAddress = e.values[1];//フォームの送信イベントオブジェクトのメールアドレス const Ss = SpreadsheetApp.getActiveSpreadsheet(); const fromSheet = Ss.getSheetByName('フォームの回答 1'); //回答のメールアドレスとが一致するC列の行を逆順に探す const lastRow = fromSheet.getRange(fromSheet.getMaxRows(), 3).getNextDataCell(SpreadsheetApp.Direction.UP).getRow(); const mails = fromSheet.getRange('C2:C' + lastRow).getValues().flat(); const index = mails.lastIndexOf(eAddress);//一致する要素のインデックスを最後尾から探索 if (index < 0) {//一致するメアドが無かったとき console.log('回答と一致するメールアドレス(' + eAddress + ')がシートに見つかりません。'); return; } //メールアドレスが回答と一致する行からデータを取得 const Values = fromSheet.getRange(index + 2, 1, 1, 22).getValues().flat(); //A列~V列を配列で取得 const Num = Values[0]//A列_連番を取得 const address = Values[2] //C列_メアドを取得 const name = Values[18]; //S列_氏名を取得 const rythmSum = Values[19]; //T列_リズム計を取得 const qualitySum = Values[20];//U列_質計を集計 const volumeSum = Values[21]; //V列_量計を集計 const tempSheet = Ss.getSheetByName('ひな形シート'); const toSheet = tempSheet.copyTo(Ss); toSheet.setName(Num); SpreadsheetApp.flush(); toSheet.getRange(2, 8, 1, 1).setValue(Num); //作業シートセルH2にNumを転記 toSheet.getRange(4, 8, 1, 1).setValue(address);//作業シートセルH5にメアドを転記 toSheet.getRange(2, 2, 1, 1).setValue(name); //作業シートセルB2にnameを転記 toSheet.getRange(8, 2, 1, 1).setValue(rythmSum); //作業シートセルB8にrythmSumを転記 toSheet.getRange(8, 3, 1, 1).setValue(qualitySum); //作業シートセルC8にqualitySumを転記 toSheet.getRange(8, 4, 1, 1).setValue(volumeSum); //作業シートセルD8にvolumeSumを転記 SpreadsheetApp.flush(); exportAsPdf(Num); sendEmail_attachFile(Num); delWorkSheet(Num) } 追記 自己解決の回答見てませんでした。 eを使わない方法を考えてましたが、 難しいですね。
donguriko

2023/08/11 08:24 編集

YellowGreenさま メルアド照合案のご提示ありがとうございます。 まだ、YellowGreenさまのコードを1つずつ解読させていただいているところで、 理解が追い付いていないので、記載いただいたコードを咀嚼し終えたら改めて お返事させていただきます。 A)ちなみに、 if (index < 0) {//一致するメアドが無かったとき console.log('回答と一致するメールアドレス(' + eAddress + ')がシートに見つかりません。'); return; } でconsole.log となっているところを eAddress宛に本文「回答集中により、正常にPDF生成ができなかったので、自動送信 されているフォーム回答コピーを見ながら、時間をおいて再度フォーム回答を入力してください」 という趣旨のアラートメールを送信させれば、回答者宛に自分の回答がはじかれている ことを回答者に知らせることができる、という理解であっていますか? B)参考  自己解決に記載させていただいたコードですが、アナログな方法でお恥ずかしい限りですが  家族に協力してもらい、せーの、で回答送信をしたところ、たまたまなのか両方の回答が  はじかれませんでした。   スプレッドシートB列のタイムスタンプ時刻は、両回答で秒単位では一致していました。  テスト回数が少ないので、本当に挙動に問題ないかをもう少し試さないとダメかもしれません。
YellowGreen

2023/08/11 08:50

わたしはスマホ2台とiPad1台の3台で動作確認してみました。 なお、引数に書くだけでなく、 今回の自己解決のスクリプトや私がお示しした上のスクリプトなどのようにコード部分でeの要素(例えばe.rangeなど)を参照しているときにご留意いただいた方がいいことが二つあります。 一つは エディタから実行したときは、e がundefinedになっているので要素の参照部分で必ずエラーになります。 なので上のコードの場合は if 文でエラーを回避しています。このことを忘れて、今まで動いていたコードが急にエラーが発生するようになったと質問される方がときどきいらっしゃいます。 二つ目は、 Googleサーバー側がスクリプトに e を渡せずにエラーになることがあります。頻度は多くないですが、e を取得できなかったのでしばらくしてから改めて実行してくださいというようなメッセージがログに残りますが、回答の処理落ちになります。テスト段階だとログを確認するのでわかりますが、わたしは何度か経験してます。
donguriko

2023/08/11 10:43 編集

YellowGreenさま コメントありがとうございます。 基礎の基礎で申し訳ありません。2点確認です。 ●「エディタから実行」 スクリプトが書かれている画面の「実行」というところをクリックしてGASを走らせた 場合のことであってますか? 1コ目のif文の理由が当初よくわからなかったのですが、そういう意味だったのですね。 ●トリガー設定されている関数? のログを見る方法 2つ目のif文で一致するメアドが無かったときの処理の '回答と一致するメールアドレス(' + eAddress + ')がシートに見つかりません。' という ログ上のメッセージ表示は、画面左側の「実行数」と書かれているアイコン内? のログを 開いてみないとわからない、ということですよね? この表示が出るケースは、GAS処理自体はreturnで終了(エラーではなく正常終了扱)になるので、 GASから定期的にくる、エラー通知では引っかからない、であっていますか?
YellowGreen

2023/08/11 23:07 編集

if (index < 0) {//一致するメアドが無かったとき console.log('回答と一致するメールアドレス(' + eAddress + ')がシートに見つかりません。'); return; } の部分ですが、「おまじない」的に書いてます。 コードを書いているときに、このコードはいらないかなと思いながら書いてました。 なぜ書いたかというと習慣です。 インデックス探索(indexOf, findIndexなど)をした後に 戻り値がマイナス(見つからない)かどうかを確認するのが癖になってます。 e.valuesを取得できているのにシートに未記入ということは 実際にはないかな(自信はありませんが...)というのが私の気持ちです。 このようなコードは、テスト段階で、値を取得する列の指定を誤っていたり、 変数名やインデックスの計算を間違えていたりしているなどを見つけるためのもので、 例えて言えば、 const sheet = ss.getSheetByName('シート1'); の後に、 if (!sheet) { console.log('シートが見つかりませんでした。'); return; } と自分が指定したシートの有無を確認しているのに近いかなという印象です。 あるかないかの確認ではなく、あることが前提で探索してます。 なお、ご質問にお答えしますと、 A) console.log となっているところを eAddress宛に本文「回答集中により、正常にPDF生成ができなかったので、自動送信 されているフォーム回答コピーを見ながら、時間をおいて再度フォーム回答を入力してください」 という趣旨のアラートメールを送信させれば、回答者宛に自分の回答がはじかれている ことを回答者に知らせることができる、という理解であっていますか? ↓ 合っています。それが慎重な対応だと思います。 ●トリガー設定されている関数? のログを見る方法 2つ目のif文で一致するメアドが無かったときの処理の '回答と一致するメールアドレス(' + eAddress + ')がシートに見つかりません。' という ログ上のメッセージ表示は、画面左側の「実行数」と書かれているアイコン内? のログを 開いてみないとわからない、ということですよね? ↓ そうですね。テスト段階では頻回に確認しますが、 本番環境に移行してからは確認しなくなりますので。 なお、意図的にエラーを発生させて定期のメール通知に載せるという手法もありますね。
guest

回答1

0

自己解決

質問コメントのレクチャー内容をもとに、
①のメインプログラムのコードを下記のとおり修正し、意図どおりの
動作をさせることができました。
今まで仮引数eについて、さっぱりわからなかったのが、少したけですが
どのようなものなのか見えてきました。
元のコードだと引数としてeを指定しておきながらeを全然使えてませんでしたね。。。
YellowGreenさま、お忙しい中レクチャーありがとうございました。
公式リファレンスに書いてある内容が理解できるように引続き精進を続けたいと思います。

GAS

1function onFormSubmit(e) { 2 const Ss = SpreadsheetApp.getActiveSpreadsheet(); 3 const fromSheet = Ss.getSheetByName('フォームの回答 1'); 4 const tempSheet = Ss.getSheetByName('ひな形シート'); 5 6/* e の確認用メモ ----------------------------------------------------- 7 console.log('e.range.getRow' + e.range.getRow()); //スプシに追記した行情報 8 console.log('e.range.getValues' , e.range.getValues());//スプシに追記された内容(配列として取得) 9 console.log('e.range.getA1Notation' , e.range.getA1Notation());//スプシに追記された範囲 10 const formAns1 = e.range.getValues(); 11 console.log('formAns1' , formAns1); 12 console.log('e.namedValues ' , e.namedValues); //スプシに追記された項目名+値(配列として取得) 13 ------------------------------------------------------------*/ 14 15//▼フォームの回答データを取得 e.range.getValuesでデータを配列ansArrとして取り出す 16//配列ansArrから取り出すデータは下の3つ 17//メアドaddress[0][1] 18//氏名name[0][17] 19//スプシに追記した行番号targetRow 20 const ansArr = e.range.getValues(); 21 const row = e.range.getRow(); //追記行 22 const ansNum = row - 1 //回答連番は2行目から開始 23 const address = ansArr[0][1]; //メアド 24 const name = ansArr[0][17]; //氏名 25 console.log('ansArr ', ansArr); 26 console.log('row ', row); 27 console.log('ansNum ', ansNum); 28 console.log('address ', address); 29 console.log('name ', name); 30 31//▼フォーム上に質問項目がない情報の取得(スプシ上で値設定している項目) 32//回答集約スプシT列~V列の値を配列Valuesとして取得 33 const Values = fromSheet.getRange(row,20,1,3).getValues(); 34 console.log('Values ' , Values); 35 const rythmSum = Values[0][0]; //T列_リズム計を取得(スプシ上でSUM算出) 36 const qualitySum = Values[0][1];//U列_質計を集計(スプシ上でSUM算出) 37 const volumeSum = Values[0][2]; //V列_量計を集計(スプシ上でSUM算出) 38 console.log('rythmSum ' , rythmSum); 39 console.log('qualitySum ' , qualitySum); 40 console.log('volumeSum ' , volumeSum); 41 42 //▼取得したデータを転記するための作業用シートを作成 43 const newSheet = tempSheet.copyTo(Ss); 44 const NewSheetName = newSheet.getName(); 45 console.log('NewSheetName ' + NewSheetName); 46 const toSheet = Ss.getSheetByName(NewSheetName); 47 toSheet.setName(ansNum); //シート名を仮引数eから取得のansNumに変更 48 49 //▼作業用シートに取得したデータをコピー 50 toSheet.getRange(2, 8, 1, 1).setValue(ansNum); //作業シートセルH2にansNumを転記 51 toSheet.getRange(4, 8, 1, 1).setValue(address); //作業シートセルH5にメアドを転記 52 toSheet.getRange(2, 2, 1, 1).setValue(name); //作業シートセルB2にnameを転記 53 toSheet.getRange(8, 2, 1, 1).setValue(rythmSum); //作業シートセルB8にrythmSumを転記 54 toSheet.getRange(8, 3, 1, 1).setValue(qualitySum); //作業シートセルC8にqualitySumを転記 55 toSheet.getRange(8, 4, 1, 1).setValue(volumeSum); //作業シートセルD8にvolumeSumを転記 56 exportAsPdf(ansNum); 57 sendEmail_attachFile(ansNum); 58 delWorkSheet(ansNum) 59} 60

投稿2023/08/11 06:58

編集2023/08/11 07:01
donguriko

総合スコア30

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問