質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
Google スプレッドシート

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

Google フォーム

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

Gmail

GmailとはGoogleによって提供されているウェブメールのサービスのことです。

Google Apps Script

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

Q&A

解決済

1回答

3434閲覧

Googleフォームで申請・スプレッドシートに集約・Gmailに確認のメール送信と承認

退会済みユーザー

退会済みユーザー

総合スコア0

Google スプレッドシート

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

Google フォーム

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

Gmail

GmailとはGoogleによって提供されているウェブメールのサービスのことです。

Google Apps Script

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

0グッド

0クリップ

投稿2022/04/26 08:16

編集2022/04/26 09:51

出張に関する承認フローをタイトルの流れで作成しようと思っています。
https://tonari-it.com/gas-workflow-url-parameter/
上記の方を参考にし、質問内容や行列の数値などを変更して作成しましたが、下記のようなエラーメッセージが出て実行できません。
『TypeError: Cannot read property 'parameter' of undefined
doGet @ コード.gs:2』
申請のフォームから集約するスプレッドシートを作成しており、上記のエラーが出るたびにコードを書き換えたり試してみたのですが、rangeやvalueやrowやparameterに関するところだけ同じエラーになっています。
大変恐縮ですが、どなたかお力添えいただきたく存じます。
よろしくお願いします。

作成したスクリプトは下記です。

function doGet(e) {
const row = e.parameter.row;
const sheet = SpreadsheetApp.getActiveSheet();
const values = sheet.getRange(row, 1, 1, 12).getValues()[0];
const bodies = generateBodies(values);
const answer = e.parameter.answer;
const result = {
ok: '承認',
ng: '否認'
};

sheet.getRange(row, 12).setValue(result[answer]);

const recipient = bodies.email;
const subject = 出張申請${result[answer]}のお知らせ;
let body = '';
body += 出張申請が${result[answer]}されました。\n\n;
body += bodies.plain;

let html = '';
html += <h1>出張申請${result[answer]}のお知らせ</h1>;
html += <p>以下の出張申請が${result[answer]}されました。</p>;
html += bodies.html;

GmailApp.sendEmail(recipient, subject, body, {htmlBody: html});
html = '';
html += <h1>出張申請の${result[answer]}</h1>;
html += <p>あなたは以下の出張申請を${result[answer]}しました</p>;
html += bodies.html;
return HtmlService.createHtmlOutput(html);
}

function generateBodies(values){
const [number,date,name,reason,organization,place,leeding,lodging,car,freeway] = values;

let plain = '';
plain += ・公文書番号: ${number}\n;
plain += ・日付: ${date}\n;
plain += ・名前: ${name} \n;
plain += ・主張理由: ${reason}\n;
plain += ・主催: ${organization}\n;
plain += ・出張場所: ${place}\n;
plain += ・引率: ${leeding}\n;
plain += ・宿泊: ${lodging}\n;
plain += ・交通手段: ${car}\n;
plain += ・高速の有無: ${freeway}\n\n;
let html = '<ul>';
html += <li>・公文書番号: ${number}</li>;
html += <li>・日付: ${date}</li>;
html += <li>・名前: ${name}</li>;
html += <li>・主張理由: ${reason}</li>;
html += <li>・主催: ${organization}</li>;
html += <li>・出張場所: ${place}</li>;
html += <li>・引率: ${leeding}</li>;
html += <li>・宿泊: ${lodging}</li>;
html += <li>・交通手段: ${car}</li>;
html += <li>・高速の有無: ${freeway}</li>;
html += '</ul>';
return {
email: email,
plain: plain,
html: html
};
}
function sendMessage(e) {

const row = e.range.getRow();
const sheet = e.range.getSheet();
sheet.getRange(row, 12).setValue('確認中');
const bodies = generateBodies(e.values);
let url = 'https://script.~';
url += ?row=${row}&answer=;

const recipient = '担当のメールアドレス';
const subject = '出張申請のお知らせ';
let body = '';
body += '以下の申請があります。\n\n';
body += bodies.plain;
body += '承認する場合は、以下URLをクリックしてください\n';
body += url + 'ok';
body += '否認する場合は、以下URLをクリックしてください\n';
body += url + 'ng';

let html = '';
html += '<h1>出張申請のお知らせ</h1>';
html += '<p>以下の申請があります。</p>';
html += bodies.html;
html += <p>承認する場合は、<a href="${url}ok">[承認]</a>をクリックしてください</p>;
html += <p>否認する場合は、<a href="${url}ng">[否認]</a>をクリックしてください</p>;

GmailApp.sendEmail(recipient, subject, body, {htmlBody: html});

}

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2022/04/26 09:24

そのエラー情報だけだと、どこで発生したエラーかが不明です。 TypeError: Cannot read property 'range' of undefinedのあとに続く数字や英文によって、その情報がわかる場合がありますので、エラーメッセージは省略せず全部記載してください。
退会済みユーザー

退会済みユーザー

2022/04/26 09:44

メッセージありがとうございます。 該当のエラーメッセージですが、全文を記載しました。また、コードを変更したり入れ替えたりする過程で別のものになっておりましたので、そこも変更しております。 旧『TypeError: Cannot read property 'range' of undefined』 ⇒新『TypeError: Cannot read property 'parameter' of undefined doGet @ コード.gs:2』 ご査収・手ほどきの程よろしくおねがいいたします。
退会済みユーザー

退会済みユーザー

2022/04/26 11:30 編集

このエラー(TypeError: Cannot read property 'parameter' of undefined)が発生するのは、どのような操作を行ったときでしょうか? (もし、エディタ上でdoGet関数を実行したときにこのエラーが発生するというのならば、それは仕様であり、本来の使い方とは異なります)
退会済みユーザー

退会済みユーザー

2022/04/26 11:41

スプレッドシートの拡張機能にて、Apps Scriptから上記のコードをずらっと記入して、トリガーを作成し、実行した時にエラーとなります。 これが仰っているエディタ上でのdoGet関数実行にあたるのでしょうか?
退会済みユーザー

退会済みユーザー

2022/04/26 11:53 編集

まず、今回エラーが出ているdoGet関数の正しい「呼び出し方」は、 ・フォームに投稿したときに送信されてくるメール内のURLをクリックする、 という動作です。 そのような動作を行ったときに「TypeError: Cannot read property 'parameter' of undefined doGet @ コード.gs:2」というエラーが発生しているのでしょうか? (ソースやトリガー設定が全部正しいのであれば、フォーム送信後に送られてくるメール内のURLをクリックすることで、doGet関数が自動的によびだされます。 それ以外の動作でdoGet関数を呼び出すことは、元記事でも想定されていません) )
退会済みユーザー

退会済みユーザー

2022/04/26 12:18

いいえおそらくそのような動作は行っていません。質問文のコードを全て一つにまとめてdoGet関数を選択し、実行しているので順番もあったものではないと思います。恥ずかしながら正しい手順を理解しないまま、コピペしております。
退会済みユーザー

退会済みユーザー

2022/04/26 15:32 編集

零から作る場合の手順を記しました。 もしフォームの設定が終わっている場合は、⑩以降が参考になるかもしれません。 確認したところコード自体、一部修正しないとエラーになる部分がありますので追記しています。
退会済みユーザー

退会済みユーザー

2022/04/26 15:55

夜も遅いところを丁寧に詳しい手順まで記載いただき、ありがとうございます。ここ何日かずっと悩んでいたので、目から鱗でした。絶対に自分だけの発想では辿り着けなかったと思います。ありがとうございました。 明日職場で早速試してみようと思います。完成しましたら、また改めてご連絡させてください。
退会済みユーザー

退会済みユーザー

2022/04/27 07:52 編集

提示いただいた手順で実行したところ、「不明なエラーが発生しました。しばらく経ってからお試しください。」とでました。ずっと膠着状態だったので少し光明が見えた気がします。何度か試してみようと思います。 →しばらく経って全く新しいフォームから作ってみたところ「TypeError: Cannot read property 'parameter' of undefined doGet @ コード.gs:2」と最初と同じエラーが出ました。 別件ですが質問があります。 1、実行に移る際デバッグの隣の実行する関数についてはdoGetでいいのでしょうか? 2、トリガーをsendMessageを関数にして作成しましたが、doGetやgenerateBodiesのトリガーは作らなくていいのでしょうか? 3、引用元の方は8個の変数を代入しなければいけないところを、6個に分割代入しているように見えます。これは引用元の方も間違っているのでしょうか? お忙しいところ申し訳ございません。お手隙の時お返事いただければ幸いです。
退会済みユーザー

退会済みユーザー

2022/04/27 11:30

> 最初と同じエラーが出ました。 フォームの送信時にそのエラーが出たということでしょうか? であればdoGetにトリガーを設定してしまっていませんか?doGetにフォーム送信時のトリガーを設定するのは間違いなので、もしそうであればトリガーを修正するか、削除してください。 > 1、実行に移る際デバッグの隣の実行する関数についてはdoGetでいいのでしょうか? →どれでも構いません。 デバッグの隣にあるドロップダウンに指定する関数は、エディタから直接関数を実行する場合に関係するものですが、本件はエディタから直接関数を実行する必要はないため、どの関数が選択されていようが影響はありません。 2、トリガーをsendMessageを関数にして作成しましたが、doGetやgenerateBodiesのトリガーは作らなくていいのでしょうか? →doGetやgenerateBodiesに対してトリガーを設定する必要はないです。もし設定していたらそれは間違いなので、そのトリガーは削除してください。 本件は、sendMessage関数に、「フォーム送信時」のトリガーを設定するだけでよいです。 > 3、引用元の方は8個の変数を代入しなければいけないところを、6個に分割代入しているように見えます。これは引用元の方も間違っているのでしょうか? →いいえ。 記事の最初の方にある、スプレッドシートの画像 ttps://tonari-it.com/wp-content/uploads/071-form-data-680x314.png を見るとわかりますが、フォームの回答を蓄積するシートの1列目に タイムスタンプ、メールアドレス、購買先、品名、単価、数量、Memo の7項目が並んでいます。 タイムスタンプは、フォームを作成すると1列目に必ず設定されます。 メールアドレスは、私の回答②のように、「メールアドレスを自動収集する」にチェックを入れると設定されます。 残り5つ(購買先、品名、単価、数量、Memo)はフォームを編集して作られた項目です。 申請者がフォームを送信すると、トリガーによって、sendMessage関数が自動的に呼ばれます。 sendMessage関数内の const bodies = generateBodies(e.values); のところで generateBodies 関数が呼ばれます。このとき、generateBodies関数の引数 e.values に フォーム送信者が回答した7つの回答項目が入っています。 そして generateBodies 内の const [number,date,name,reason,organization,place,leeding,lodging,car,freeway] = values; のところで、valuesの内容がそれぞれ代入されます。 ここで、分割代入は、前詰めで代入されるというルールがあるため valuesのうち、最後のmemoを除いた6つの項目が、左辺の各変数に(正常に)代入されます。 数の不一致自体は問題ありません(この点私の回答が誤解を生じる表現になっていたため修正します)
退会済みユーザー

退会済みユーザー

2022/04/27 23:08 編集

おそらく私はqnoil様が当初仰っていたような勘違いをしているのかもしれません。デバッグの隣の実行ボタンを押して、きちんと実行完了がされないと、この一連の動作が機能しないのかと思っていました。qnoil様の手順の後(新バージョンにしてデプロイを完了後)にこのスクリプトは完成しているため、後はフォームを送って動作を確認する、という流れでしょうか。 ⇒今朝、フォームを送信すると無事承認までできておりました。完全に私の勘違いで、振り回してしまい申し訳ございませんでした。qnoil様のおかげで誤っている個所や理解につながりました。本当にありがとうございました。
退会済みユーザー

退会済みユーザー

2022/04/28 11:10

ご連絡ありがとうございます。解決したのであれば、ベストアンサーを選んでいただき、この質問をクローズしてください。
guest

回答1

0

ベストアンサー

この件について、ゼロから作る場合の手順を記しました。
特にデプロイの部分は結構難しいと思います。


①フォームを作ります。
イメージ説明

② フォーム編集画面の上にある「質問」「回答」「設定」のうち、一番右の「設定」をクリックし、中の「メールアドレスを収集する」のスイッチをonにしておきます。
イメージ説明

さらに、真ん中の「回答」をクリックし、緑のアイコンをクリックします。
イメージ説明

③「新しいスプレッドシートを作成」を選択し、「作成」をクリックします。
イメージ説明

④ スプレッドシートが表示されるので、メニューから「拡張機能」→「Apps Script」を選択します。
イメージ説明

⑤ エディタに、コードを記入して保存します。
※コードは一部修正する必要があります。
具体的には、generateBodies 関数の2行目は、下記のように、先頭に timeStamp と emailの2つを追加する必要があります。

js

1function generateBodies(values) { 2 const [timeStamp, email, number, date, name, reason, organization, place, leeding, lodging, car, freeway] = values; 3以下略

また、念のためですが、
recipient = '担当のメールアドレス';
は、実在する、承認担当者のメールアドレス(他の人がフォームで申請してきた時に承認する人のメールアドレス)に変えておいてください。
(テスト段階では自分のアドレスで構わないと思いますが)

⑥エディタにコードを全部書いて保存したら、左側に並んでいるアイコンのうち、時計のアイコンをクリックします。
イメージ説明


⑦右下の「トリガーを追加」ボタンをクリックします。
イメージ説明


⑧トリガー設定画面が開くので、
・実行する関数を選択 → sendMessage
・イベントの種類を選択 → フォーム送信時
に設定し、右下の「保存」ボタンをクリックします。
イメージ説明

 

⑨ 左側の<>のマークをクリックしてエディタに戻ります。
イメージ説明


⑩ 右上の「デプロイ」ボタンをクリックし、「新しいデプロイ」を選択します。
イメージ説明


⑪ 歯車のアイコンをクリックし、「ウェブアプリ」を選択します。
イメージ説明


⑫  次のユーザーとして実行→自分
アクセスできるユーザー→全員
に指定して、右下の「デプロイ」ボタンをクリックします。
イメージ説明


⑬ デプロイが更新されるので、上の方に書いてある「バージョン」を覚えておき、
中段のウェブアプリのURLにある「コピー」ボタンをおします(URLがクリップボードにコピーされます)
イメージ説明


⑭ コードに戻って、urlのところを、先ほどコピーしたurlに上書きします。
イメージ説明


⑮ Ctrl+sを押して、コードを保存します(ここで保存し忘れると反映されません)

⑯ もう一度、左上のデプロイボタンをクリックし、「新しいデプロイ」を選択します。
イメージ説明


⑰ 右下の「デプロイ」をクリックします。
イメージ説明


⑱ さらに再度右上のデプロイボタンをクリックしますが、今度はデプロイを管理を選択します。
イメージ説明


⑲ 左に並んでいる「無題」のうち、古い方(先ほど覚えたバージョンのもの)を選択します。
そして、右上の鉛筆ボタンをクリックします。
イメージ説明


⑳ ドロップダウンをクリックして、バージョンを最新バージョンにします。
(一番上の「新バージョン」または、その1つ下の最新バージョンを選択)
イメージ説明



ここまでできたら、右下の「デプロイ」をクリック→「完了」をクリック。


 
※ コードを編集する都度、⑮~⑳の操作によって、バージョンを更新していく必要があります。
そうしないと、コード中の 「url」 に対応するコードが、最新のものになりません。

開発中で何度もデプロイバージョンを変えるのが煩わしい場合は、下記のようにデプロイのテストバージョンを使用することも可能です。
これにより開発中、urlを固定することができます。

・コード保存後、上記⑯で「新しいデプロイ」ではなく、「デプロイをテスト」を選択します。
イメージ説明

・URLをコピーして、完了をクリックします。
イメージ説明

・コピーしたデプロイURLを、コードのurlに上書きして、コードを保存します
イメージ説明

再び右上の「デプロイ」ボタンから「デプロイをテスト」を選択し、完了ボタンを押します
(必須。これを忘れると反映されません。)

あとは、コードを修正・編集して保存する都度、右上の「デプロイ」ボタンから「デプロイをテスト」を選択し、完了ボタンを押す、という操作を繰り返せばよいです。

※本番稼働時では、「デプロイをテスト」ではなく、「新しいデプロイ」でデプロイしてください。

※当然ですが、デプロイする前にコードを上書き保存しないと修正内容が反映されないので、注意してください。


コード修正点について:

⑤でも触れましたが、コードのうち
generateBodies 関数の2行目は、下記のように timeStampと emailの2つを追加する必要があります。

js

1function generateBodies(values) { 2 const [timeStamp, email, number, date, name, reason, organization, place, leeding, lodging, car, freeway] = values; 3以下略

 
理由:
doGet関数の中で

const values = sheet.getRange(row, 1, 1, 12).getValues()[0];

となっているように、valuesは、1行12列の範囲から読み取っている為、12個の要素を持つ配列となっています。
ここで、もし上記手順でフォームを作成しているならば
1列目はタイムスタンプ、2列目はメールアドレスとなっているはずです。(すくなくとも1列目は必ずタイムスタンプとなっているはず)
したがって、generateBodies 関数の冒頭でvaluesを分割代入する際、
1番目にタイムスタンプ、2番目にメールアドレスを追加し、残りをフォームの回答項目とする必要があります。

また 、generateBodies 関数の後半で

js

1 return { 2 email: email, 3 plain: plain, 4 html: html 5 };

となっていることから、元のコードのままだとemail という変数がないというエラーも発生してしまうと思います。

元記事でも、最初の2つは timeStamp と emailになっていることから、
(フォームの設定画面でメール収集を必須にしたうえで)
timeStamp と email も 分割代入の対象に加えるようにします。

投稿2022/04/26 14:47

編集2022/04/27 11:32
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問