項目は「管理番号(A列)」「受領日(B列)」「金額(C列)」「取引先(D列)」「種別(E列)」「URL(F列)」となります。
▼ ファイルアップロード部分の GAS の説明
トリガーとして「フォーム送信時」を使うため、フォームからスクリプトを作成します。
まずはファイルのアップロード部分の GAS になります。
function getForm(e) {
//回答を取得
let itemResponses = e.response.getItemResponses();
//ファイル名の生成
let date = new Date();
date = Utilities.formatDate(date, "JST", "yyyyMMdd");
let amt = itemResponses[1].getResponse(); //金額
let sup = itemResponses[2].getResponse(); //取引先
let typ = itemResponses[3].getResponse(); //種別
let fileName = date + '_' + sup + '_' + amt + '_' + typ;
//アップロードファイルを取得
let file = itemResponses[0];
//ファイル名の変更
let pdf = DriveApp.getFileById(file.getResponse());
pdf.setName(fileName);
//取引履歴の記録
setTrHis(pdf);
//マイドライブ下にアップロードされたファイルの削除
dltMyFile();
}
//取引履歴の記録
function setTrHis(pdf) {
Utilities.sleep(3 * 1000);
const sp = SpreadsheetApp.openById("スプレッドシート ID");
const formSh = sp.getSheetByName("フォームの回答 1");
const hisSh = sp.getSheetByName("取引履歴");
let formLastRow = formSh.getLastRow();
let hisLastRow = hisSh.getLastRow();
//ファームから回答の取得
let formData = formSh.getRange(formLastRow, 1, 1, 5).getValues();
formData = Array.prototype.concat.apply([], formData); //二次元配列を一次元配列へ
//取引履歴の記録
let hisData = [];
let num = "";
if(hisLastRow == 1) {
num = 1;
} else {
num = hisSh.getRange(hisLastRow, 1).getValue() + 1;
}
hisData.push(num);
hisData.push(Utilities.formatDate(formData[0], "JST", "yyyy/MM/dd"));
hisData.push(formData[2]);
hisData.push(formData[3]);
hisData.push(formData[4]);
hisData.push(pdf.getDownloadUrl());
hisSh.getRange(hisLastRow + 1, 1, 1, 6).setValues([hisData]);
}
//マイドライブ下にアップロードされたファイルの削除
function dltMyFile() {
const myDrive = DriveApp.getRootFolder();
let file = myDrive.getFiles().next();
file.setTrashed(true);
}
※ アップロード当日に検索されない不具合を修正しました。(2022-7-14)
31行目の「スプレッドシート ID」を書き換えてください。
アップロードされたファイルの名前は「日付_取引先_金額_種別.pdf」という形式になります。
指定したフォルダ以外にマイドライブ直下にもファイルがアップロードされるようなのでそちらは dltMyFile 関数で削除(ゴミ箱に移動)しています。マイドライブ直下に他のファイルがある場合はあらかじめ調整してください。
トリガーとして「フォーム送信時」を設定します。
これで、請求書等を受領した際にフォーム経由でドライブにアップロードすることでファイル名が統一フォーマットになり、取引履歴が記録されます。
▼ 検索部分の GAS の説明
次に検索部分の GAS です。取引履歴から検索します。
const sp = SpreadsheetApp.openById("スプレッドシート ID");
const hisSh = sp.getSheetByName("取引履歴");
let result = [];
function doGet() {
let htmlIndex = HtmlService.createTemplateFromFile("index");
htmlIndex.dataNum = dataNum();
htmlIndex.dateList = dateList().join("");
htmlIndex.csmList = csmList().join("");
htmlIndex.result = result;
return htmlIndex.evaluate().setTitle("受領済請求書等検索システム");
}
function doPost(e) {
let y_from = e.parameter.y_from;
let m_from = e.parameter.m_from;
let d_from = e.parameter.d_from;
let y_to = e.parameter.y_to;
let m_to = e.parameter.m_to;
let d_to = e.parameter.d_to;
let amt_low = e.parameters.amt_low;
let amt_up = e.parameters.amt_up;
let csm = e.parameter.csm;
let typ = e.parameter.typ;
let htmlResult = HtmlService.createTemplateFromFile("index");
let rowNum = []; //検索結果行
//全ての行を取得
for(let i = 2; i <= hisSh.getLastRow(); i++) {
rowNum.push(i);
}
//種別→検索順1
if(typ != "全選択") {
let typFinder = hisSh.getRange("E2:E").createTextFinder(typ).findAll();
for(let i in typFinder) {
rowNum.push(typFinder[i].getRow());
}
//重複要素のみ残す
rowNum = rowNum.filter(function (value, index, self) {
return self.indexOf(value) === index && index !== self.lastIndexOf(value);
});
}
//取引先→検索順2
if(csm != "全選択") {
let csmFinder = hisSh.getRange("D2:D").createTextFinder(csm).findAll();
for(let i in csmFinder) {
rowNum.push(csmFinder[i].getRow());
}
//重複要素のみ残す
rowNum = rowNum.filter(function (value, index, self) {
return self.indexOf(value) === index && index !== self.lastIndexOf(value);
});
}
//金額→検索順3
let amt = "";
//下限
if(amt_low != "") {
for(let i in rowNum) {
amt = hisSh.getRange(rowNum[i], 3).getValue();
//下限以下の行を削除
if(amt >= amt_low) {
rowNum.push(rowNum[i]);
}
}
//重複要素のみ残す
rowNum = rowNum.filter(function (value, index, self) {
return self.indexOf(value) === index && index !== self.lastIndexOf(value);
});
}
//上限
if(amt_up != "") {
for(let i in rowNum) {
amt = hisSh.getRange(rowNum[i], 3).getValue();
//上限以上の行を削除
if(amt <= amt_up) {
rowNum.push(rowNum[i]);
}
}
//重複要素のみ残す
rowNum = rowNum.filter(function (value, index, self) {
return self.indexOf(value) === index && index !== self.lastIndexOf(value);
});
}
//期間→検索順4
let dtDate = "";
let date = "";
//期間・始まり
date = new Date(y_from, m_from - 1, d_from);
for(let i in rowNum) {
dtDate = hisSh.getRange(rowNum[i], 2).getValue();
//開始日以前の行を削除
if(dtDate.getTime() >= date.getTime()) {
rowNum.push(rowNum[i]);
}
}
//重複要素のみ残す
rowNum = rowNum.filter(function (value, index, self) {
return self.indexOf(value) === index && index !== self.lastIndexOf(value);
});
//期間・終わり
date = new Date(y_to, m_to - 1, d_to);
for(let i in rowNum) {
dtDate = hisSh.getRange(rowNum[i], 2).getValue();
//終了日以降の行を削除
if(dtDate.getTime() <= date.getTime()) {
rowNum.push(rowNum[i]);
}
}
//重複要素のみ残す
rowNum = rowNum.filter(function (value, index, self) {
return self.indexOf(value) === index && index !== self.lastIndexOf(value);
});
rowNum.sort((a, b) => {return a - b;});
result.push("<p><b>期間: </b>"+ Utilities.formatDate(new Date(y_from, m_from - 1, d_from), "JST", "yyyy/MM/dd") + " から " + Utilities.formatDate(new Date(y_to, m_to - 1, d_to), "JST", "yyyy/MM/dd") + " まで<br><b>金額: </b>" + amt_low + "円 以上 " + amt_up + "円 以下<br><b>取引先: </b>" + csm + "<br><b>種別: </b>" + typ + "</p><br>"); //検索条件
result.push("<table><tr><th>管理番号</th><th>受領日</th><th>金額</th><th>取引先</th><th>種別</th><th>ファイル</th></tr>");
for (let i = 0; i < rowNum.length; i++) {
result.push("<tr><td>" + hisSh.getRange(rowNum[i], 1).getValue() + "</td>");
result.push("<td>" + Utilities.formatDate(new Date(hisSh.getRange(rowNum[i], 2).getValue()), "JST", "yyyy/MM/dd") + "</td><td>");
result.push(Array.prototype.concat.apply([], hisSh.getRange(rowNum[i], 3, 1, 3).getValues()).join("</td><td>"));
result.push("<td><a href='" + hisSh.getRange(rowNum[i], 6).getValue() + "'>ダウンロード</a></td><td>");
result.push("</td></tr>");
}
result.push("</table>");
htmlResult.dataNum = dataNum();
htmlResult.dateList = dateList().join("");
htmlResult.csmList = csmList().join("");
htmlResult.result = result.join("");
return htmlResult.evaluate().setTitle("検索結果 - 受領済請求書等検索システム");
}
//登録数の作成
function dataNum() {
let dataNum = 0;
let lastRow = hisSh.getLastRow();
for(let i = 2; i <= lastRow; i++){
dataNum++;
}
return dataNum;
}
//期間の作成
function dateList() {
let list = [];
let date = "";
//始まり(年)
list.push("<select name='y_from'>");
for(let i = 0; i < 8; i++) {
date = new Date();
date = Utilities.formatDate(new Date(date.setFullYear(date.getFullYear() - i)), "JST", "yyyy");
if(i == 1) {
list.push("<option value='" + date + "' selected>" + date + "</option>");
} else {
list.push("<option value='" + date + "'>" + date + "</option>");
}
}
list.push("</select>" + "年 ");
//始まり(月)
list.push("<select name='m_from'>");
for(let i = 0; i < 12; i++) {
date = new Date();
date = Utilities.formatDate(new Date(date.setFullYear(date.getFullYear() - i)), "JST", "MM");
if((i + 1) == date) {
list.push("<option value='" + (i + 1) + "' selected>" + (i + 1) + "</option>");
} else {
list.push("<option value='" + (i + 1) + "'>" + (i + 1) + "</option>");
}
}
list.push("</select>" + "月 ");
//始まり(日)
list.push("<select name='d_from'>");
for(let i = 0; i < 31; i++) {
date = new Date();
date = Utilities.formatDate(new Date(date.setFullYear(date.getFullYear() - i)), "JST", "dd");
if((i + 1) == date) {
list.push("<option value='" + (i + 1) + "' selected>" + (i + 1) + "</option>");
} else {
list.push("<option value='" + (i + 1) + "'>" + (i + 1) + "</option>");
}
}
list.push("</select>" + "日 から ");
//終わり(年)
list.push("<select name='y_to'>");
for(let i = 0; i < 8; i++) {
date = new Date();
date = Utilities.formatDate(new Date(date.setFullYear(date.getFullYear() - i)), "JST", "yyyy");
if(i == 0) {
list.push("<option value='" + date + "' selected>" + date + "</option>");
} else {
list.push("<option value='" + date + "'>" + date + "</option>");
}
}
list.push("</select>" + "年 ");
//終わり(月)
list.push("<select name='m_to'>");
for(let i = 0; i < 12; i++) {
date = new Date();
date = Utilities.formatDate(new Date(date.setFullYear(date.getFullYear() - i)), "JST", "MM");
if((i + 1) == date) {
list.push("<option value='" + (i + 1) + "' selected>" + (i + 1) + "</option>");
} else {
list.push("<option value='" + (i + 1) + "'>" + (i + 1) + "</option>");
}
}
list.push("</select>" + "月 ");
//終わり(日)
list.push("<select name='d_to'>");
for(let i = 0; i < 31; i++) {
date = new Date();
date = Utilities.formatDate(new Date(date.setFullYear(date.getFullYear() - i)), "JST", "dd");
if((i + 1) == date) {
list.push("<option value='" + (i + 1) + "' selected>" + (i + 1) + "</option>");
} else {
list.push("<option value='" + (i + 1) + "'>" + (i + 1) + "</option>");
}
}
list.push("</select>" + "日 まで");
return list;
}
//取引先の作成
function csmList() {
let list = [];
let lastRow = hisSh.getLastRow();
if(lastRow > 1) {
let csm = hisSh.getRange(2, 4, lastRow -1, 1).getValues(); //全取引先を取得
csm = Array.prototype.concat.apply([], csm); //二次元配列を一次元配列へ
//重複要素を削除
csm = csm.filter(function (value, index, self) {
return self.indexOf(value) === index;
});
for(let i = 0; i < csm.length; i++){
list.push("<option value='" + csm[i].replace("'", "'") + "'>" + csm[i].replace("'", "'") + "</option>"); //シングルクオーテーションがあった場合はHTML特殊文字に変換
}
}
return list;
}
1行目の「スプレッドシート ID」を書き換えてください。
HTML も作ります。ファイル名は index.html とします。
<!DOCTYPE html>
<html>
<head>
<base target="_top"></base>
</head>
<body>
<center><h1><a href="ウェブアプリ URL">受領済請求書等検索システム</a></h1></center>
<p>
登録数: 件
</p>
<form action="ウェブアプリ URL" method="post">
<p><b><期間></b><br />
</p>
<p><b><金額></b><br />
<input name="amt_low" size="15" type="text" />円 以上 <input name="amt_up" size="15" type="text" />円 以下
</p>
<p><b><取引先></b><br />
<select name="csm">
<option value="全選択">全選択</option>
</select>
</p>
<p><b><種別></b><br />
<input checked="checked" name="typ" type="radio" value="全選択" />全選択
<input name="typ" type="radio" value="請求書" />請求書
<input name="typ" type="radio" value="納品書" />納品書
<input name="typ" type="radio" value="見積書" />見積書
<input name="typ" type="radio" value="領収書" />領収書
</p>
<p>
<input type="submit" value="検索" />
</p>
</form>
<p>
</p>
</body>
</html>
7行目 <a> タグ、11行目 <form> タグの「ウェブアプリ URL」を書き換えてください。
ウェブアプリ URL は GAS をウェブアプリとしてデプロイすると得られます。公開はしないのでアクセスできるユーザーは「自分のみ」としておきます。
検索画面はこんな感じになります。