トイレットペーパーの在庫、どれくらいありますか?
少し前、「大沢たかお祭り」でも話題になっていましたが、
買い物して帰ったら、家になかったはずのマヨが2本買ってた経験、ありませんか?
私、あります。
めちゃくちゃあります。
気づいたら、押し入れに大容量洗剤の詰め替えが5個、
トイレットペーパーはなんと 24ロール × 5パック…
ちゃんと数えたら、来年まで持つやん…ショック!
自分を救うため、自宅在庫管理アプリを作ってみた!
毎回買いすぎて落ち込む自分を救うために、在庫管理アプリを作りました!
しかも、全部Google Apps Script(GAS) で動いてます!
こんな仕組みで作った!
自宅在庫管理アプリはすべてGASで作成しました!!
-
GAS + HTML でWebアプリを作成
-
アイテム一覧と在庫数を表示
-
「使った/補充した」操作をすると、スプレッドシートに即反映
-
在庫が1個になったらメールで通知
-
iPhoneのショートカットに登録して、スマホからもすぐアクセス可能!
GASのコード(抜粋)
code.gs
の一部です。
function doGet() {
return HtmlService.createHtmlOutputFromFile('form'); //好きな名前に変更してください
}
// アイテム一覧と在庫を取得
function getItemsWithStock() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Stock');
const data = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2).getValues(); // アイテム名+在庫
return data.map(row => ({ name: row[0], stock: row[1] }));
}
// 在庫を1つ減らす
function recordUse(itemName) {
return updateStock(itemName, -1);
}
// 在庫を1つ補充する
function recordSupply(itemName) {
return updateStock(itemName, 1);
}
// 在庫更新ロジック
function updateStock(itemName, diff) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Stock');
const data = sheet.getDataRange().getValues();
for (let i = 1; i < data.length; i++) {
if (data[i][0] === itemName) {
const currentStock = data[i][1];
const newStock = Math.max(0, currentStock + diff);
sheet.getRange(i + 1, 2).setValue(newStock);
sheet.getRange(i + 1, 3).setValue(new Date());
if (newStock === 1) {
sendNotification(itemName);
}
return `「${itemName}」の在庫数:${currentStock} → ${newStock}`;
}
}
return "エラー:該当アイテムが見つかりません";
}
function getItemsWithStock() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Stock');
const data = sheet.getRange(2, 1, sheet.getLastRow() - 1, 3).getValues(); // A列~C列
return data.map(row => ({
name: row[0],
stock: row[1],
updated: row[2] ? Utilities.formatDate(new Date(row[2]), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm") : "-"
}));
}
function addNewItem(itemName) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Stock');
const data = sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues().flat();
if (data.includes(itemName)) {
return "すでに存在するアイテムです。";
}
const now = new Date();
sheet.appendRow([itemName, 1, now]);
return "アイテムを追加しました!";
}
// 在庫が1になったらメール通知
function sendNotification(itemName) {
const email = "xxx@test.com"; // ←自分のアドレス
const subject = `在庫警告: ${itemName} の在庫が1になりました`;
const body = `${itemName} の在庫が残り1個です。補充しましょう!!`;
MailApp.sendEmail(email, subject, body);
}
form.html
では、以下のように操作できます。
-
在庫数の表示
-
アイテムの追加
-
「使う」「補充」の操作
-
在庫一覧の更新
<!DOCTYPE html>
<html>
<body>
<h2>在庫管理アプリ</h2>
<label for="itemSelect">アイテム選択:</label>
<select id="itemSelect"></select>
<div id="stockDisplay">在庫数: -</div>
<button onclick="useItem()">1個使う</button>
<button onclick="supplyItem()">1個補充</button>
<h3>🆕 アイテムを追加</h3>
<input type="text" id="newItemName" placeholder="新しいアイテム名を入力" style="width:100%; padding:10px;">
<button onclick="addItem()">アイテムを追加</button>
<p id="result"></p>
<h3>📋 在庫一覧</h3>
<div class="table-wrapper">
<table id="stockTable">
<thead>
<tr>
<th>アイテム名</th>
<th>在庫数</th>
<th>最終更新</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script> // ここで、htmlの操作をGAS側へ送信する
let items = [];
// 初期化
google.script.run.withSuccessHandler(function(result) {
items = result;
const select = document.getElementById("itemSelect");
select.innerHTML = "";
result.forEach(function(item) {
const option = document.createElement("option");
option.value = item.name;
option.text = item.name;
select.appendChild(option);
});
updateStockDisplay();
loadTable(result);
}).getItemsWithStock();
document.getElementById("itemSelect").addEventListener("change", updateStockDisplay);
function updateStockDisplay() {
const selected = document.getElementById("itemSelect").value;
const item = items.find(i => i.name === selected);
document.getElementById("stockDisplay").innerText =
item ? `在庫数: ${item.stock}` : "在庫数: -";
}
function useItem() {
const selected = document.getElementById("itemSelect").value;
google.script.run.withSuccessHandler(function(msg) {
document.getElementById("result").innerText = msg;
refresh(); // 都度リロード
}).recordUse(selected);
}
function supplyItem() {
const selected = document.getElementById("itemSelect").value;
google.script.run.withSuccessHandler(function(msg) {
document.getElementById("result").innerText = msg;
refresh(); // 都度リロード
}).recordSupply(selected);
}
function refresh() {
google.script.run.withSuccessHandler(function(result) {
items = result;
updateStockDisplay();
loadTable(result);
}).getItemsWithStock();
}
function loadTable(data) {
const tbody = document.getElementById("stockTable").querySelector("tbody");
tbody.innerHTML = "";
data.forEach(function(item) {
const row = document.createElement("tr");
row.innerHTML = `
<td>${item.name}</td>
<td>${item.stock}</td>
<td>${item.updated}</td>
`;
tbody.appendChild(row);
});
}
function addItem() {
const itemName = document.getElementById("newItemName").value.trim();
if (!itemName) {
alert("アイテム名を入力してください!");
return;
}
google.script.run.withSuccessHandler(function(msg) {
alert(msg);
document.getElementById("newItemName").value = "";
refresh(); // 更新
}).addNewItem(itemName);
}
</script>
</body>
</html>
工夫した部分
- GASのdoGet()でWebアプリ化し、リンクでアクセス可能に
- データはスプレッドシートをバックエンドとして利用
- 在庫が1になったらメールでアラート(LINE通知も今後やりたい…!)
- モバイルでも使いやすいよう、ショートカット経由で操作可能
⚠ 注意点
・htmlのheadとcssの部分は割愛しました。
・スプレッドシートのシート名は "Stock" にしてください!
実際に使ってみた!
在庫を減らすと…
アイテム追加もできます
一瞬で追加!🎉
「すでに存在するアイテムです」 と表示され、ダブり防止にもなります!
スマホにも設置 ( ̄+ー ̄)
Chromeで開いた状態からショートカットに追加し、
好きなアイコンと色 で保存できます!
改善したい点・今後のアイデア
- スマホ対応(レスポンシブ) ができたら、もっと見やすくて便利
- 通知タイミングの改善
→ 通知後に買いに行く人が多いので、一覧表示をもっと見やすくしたい - アイテム削除機能も欲しい(うっかり追加しすぎ防止に)
- Amazonリンク付きで 「よく買い忘れるもの」 を表示して、タップで即注文できる仕組みもアリかも!
おわりに
とにかく!! 在庫を「感覚」で管理する時代は終わり!
週末の買い出しに在庫を覚えなくていい日々を目指して、
買いすぎ防止・買い忘れ防止、そして心の平穏 のために!
しばらく使ってみて、また改善できたら更新するかもしれません。
ではー!!