実現したいこと
GASでGoogleフォームで回答入力があったら、回答者宛にメールを自動
返信させたい。
自動返信メールには、フォームの回答データで作成したレーダーチャート
のpdfファイルを添付し、自動送信させたい。
前提
GASは回答が記録されるスプレッドシートのコンテナバインド。
スプレッドシートファイル内には、下記2つのシートが入っている。
1)フォームの回答が記録されるシート
A列、T列、V列は、スプレッドシート上にArrayFormulaで設定
B列~S列はフォーム回答が自動転記される
2)PDFイメージの元となるひな形シート
B8:D8とH2:H5はGASで回答記録シートから転記
関数? は全部で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)の違い(どう使い分ければよいのか)がわからないので教えてください。
沢山質問してしまい申し訳ありません。

回答1件
あなたの回答
tips
プレビュー