

昨日に引き続き、具体的なプランありがとうございます。そうしましたら、スプレッドシートの構成や、利用者、ヘルパー、サービス種別、サービス提供時間、指示欄等などを組み込んだ様式、具体的なGASコード、その他システム構築の際に必要なファクターを全て作ってみてください。

ご提示いただいた要件に基づき、「Googleスプレッドシート + GAS(Google Apps Script)」を用いた実用的かつ特定事業所加算の要件を意識したシステムの設計図、データ構造、および具体的なコード一式を作成しました。このシステムは、サーバー不要・無料で構築でき、スマホからの操作に最適化しています。
1. スプレッドシートの構成(データベース設計)
1枚のスプレッドシートに以下の5つのシートを作成してください。
① Master_Helpers(ヘルパーマスタ)
| ID | 名前 | パスワード |
| H01 | 田中太郎 | 1234 |
| H02 | 佐藤花子 | 5678 |
| …(5名分) |
② Master_Users(利用者マスタ)
| ID | 利用者名 |
| U01 | 山田一郎様 |
| …(25名分) |
③ Shifts(シフト・指示データ)
管理者が入力するメインシートです。
| ShiftID | 日付 | 開始 | 終了 | 利用者名 | ヘルパー名 | サービス種別 | 指示内容 | ステータス |
| :— | :— | :— | :— | :— | :— | :— | :— | :— |
| S001 | 2024/05/20 | 09:00 | 10:00 | 山田一郎様 | 田中太郎 | 居宅・身体 | 水分補給を促してください | 未 |
④ Activity_Logs(実績記録・報告)
GASから自動書き込みされます。
| タイムスタンプ | ShiftID | 利用者名 | ヘルパー名 | 指示に対する報告 | 特記事項 |
| :— | :— | :— | :— | :— | :— |
⑤ Master_Services(サービス種別マスタ)
プルダウン作成用(居宅介護、行動援護、移動支援など)
2. システム構築(GASコード)
スプレッドシートの「拡張機能」>「Apps Script」を開き、以下のコードを貼り付けてください。
Code.gs(サーバーサイド処理)
JavaScript
function doGet() {
return HtmlService.createTemplateFromFile('Index')
.evaluate()
.setTitle('訪問記録システム')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
// ログイン照合
function checkLogin(name, pass) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const data = ss.getSheetByName('Master_Helpers').getDataRange().getValues();
for (let i = 1; i < data.length; i++) {
if (data[i][1] === name && data[i][2].toString() === pass) {
return { success: true, helperName: name };
}
}
return { success: false };
}
// 今日のシフト取得
function getMyShifts(helperName) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const data = ss.getSheetByName('Shifts').getDataRange().getValues();
const today = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd");
return data.filter((row, i) => {
const shiftDate = Utilities.formatDate(new Date(row[1]), "JST", "yyyy/MM/dd");
return i > 0 && row[5] === helperName && shiftDate === today && row[8] === '未';
});
}
// 前回の記録を取得(特定事業所加算用:申し送りの担保)
function getPreviousRecord(userName) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const logs = ss.getSheetByName('Activity_Logs').getDataRange().getValues();
// 逆順で検索
for (let i = logs.length - 1; i >= 1; i--) {
if (logs[i][2] === userName) {
return {
date: Utilities.formatDate(new Date(logs[i][0]), "JST", "MM/dd"),
report: logs[i][4],
note: logs[i][5]
};
}
}
return null;
}
// 報告の送信
function submitReport(reportData) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const logSheet = ss.getSheetByName('Activity_Logs');
const shiftSheet = ss.getSheetByName('Shifts');
// ログに追記
logSheet.appendRow([
new Date(),
reportData.shiftId,
reportData.userName,
reportData.helperName,
reportData.report,
reportData.note
]);
// シフトシートのステータスを「完了」に更新
const shiftData = shiftSheet.getDataRange().getValues();
for (let i = 1; i < shiftData.length; i++) {
if (shiftData[i][0] === reportData.shiftId) {
shiftSheet.getRange(i + 1, 9).setValue('完了');
break;
}
}
return "送信完了しました";
}
Index.html(フロントエンド画面)
HTML ※GASのエディタで「ファイル」+ボタンからHTMLファイルを作成し、名前を Index にしてください。
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<style>
body { background-color: #f5f5f5; padding: 10px; }
.card { margin-bottom: 15px; }
.previous-log { font-size: 0.85rem; background: #fffbeb; padding: 10px; border-left: 4px solid #fbbf24; }
</style>
</head>
<body>
<div id="app" class="container">
<div id="login-screen">
<h1 class="title is-4">ヘルパーログイン</h1>
<input id="login-name" class="input mb-2" type="text" placeholder="名前">
<input id="login-pass" class="input mb-4" type="password" placeholder="パスワード">
<button onclick="login()" class="button is-primary is-fullwidth">ログイン</button>
</div>
<div id="main-screen" style="display:none;">
<h2 class="subtitle">今日のシフト: <span id="display-name"></span></h2>
<div id="shift-list"></div>
</div>
<div id="report-screen" style="display:none;">
<button onclick="showMain()" class="button is-small mb-2">← 戻る</button>
<div class="card">
<div class="card-content">
<p class="is-size-7 has-text-grey">前回の記録(申し送り)</p>
<div id="prev-record" class="previous-log mb-4">読み込み中...</div>
<p class="has-text-weight-bold">サビ児責からの指示:</p>
<p id="inst-text" class="notification is-info is-light mt-1"></p>
<hr>
<div class="field">
<label class="label">指示に対する報告</label>
<textarea id="rep-text" class="textarea" placeholder="指示に対してどう対応したか記入してください"></textarea>
</div>
<div class="field">
<label class="label">特記事項</label>
<textarea id="note-text" class="textarea" placeholder="全体的な様子など"></textarea>
</div>
<button onclick="sendReport()" class="button is-success is-fullwidth">記録を送信(保存)</button>
</div>
</div>
</div>
</div>
<script>
let currentHelper = "";
let currentShift = {};
function login() {
const name = document.getElementById('login-name').value;
const pass = document.getElementById('login-pass').value;
google.script.run.withSuccessHandler(res => {
if(res.success) {
currentHelper = res.helperName;
document.getElementById('display-name').innerText = currentHelper;
showMain();
} else { alert("ログイン失敗"); }
}).checkLogin(name, pass);
}
function showMain() {
document.getElementById('login-screen').style.display = 'none';
document.getElementById('report-screen').style.display = 'none';
document.getElementById('main-screen').style.display = 'block';
loadShifts();
}
function loadShifts() {
google.script.run.withSuccessHandler(shifts => {
let html = "";
shifts.forEach(s => {
html += `<div class="card" onclick='openReport("${s[0]}","${s[4]}","${s[7]}")'>
<div class="card-content">
<p><b>${s[2]}~${s[3]}</b></p>
<p>${s[4]} / ${s[6]}</p>
</div>
</div>`;
});
document.getElementById('shift-list').innerHTML = html || "今日の未完了シフトはありません";
}).getMyShifts(currentHelper);
}
function openReport(id, user, inst) {
currentShift = { id, user };
document.getElementById('main-screen').style.display = 'none';
document.getElementById('report-screen').style.display = 'block';
document.getElementById('inst-text').innerText = inst;
google.script.run.withSuccessHandler(prev => {
document.getElementById('prev-record').innerText = prev ?
`【${prev.date}】${prev.report} / ${prev.note}` : "前回の記録はありません。";
}).getPreviousRecord(user);
}
function sendReport() {
const data = {
shiftId: currentShift.id,
userName: currentShift.user,
helperName: currentHelper,
report: document.getElementById('rep-text').value,
note: document.getElementById('note-text').value
};
google.script.run.withSuccessHandler(() => {
alert("送信しました");
showMain();
}).submitReport(data);
}
</script>
</body>
</html>
3. システム構築の重要ファクター(成功への鍵)
① デプロイとセキュリティ
- デプロイ方法: Apps Scriptエディタ右上の「デプロイ」>「新しいデプロイ」>種類を「ウェブアプリ」に。
- 実行ユーザー: 「自分(管理者)」に設定。
- アクセスできるユーザー: 「Googleアカウントを持つ全員」または社内用Google Workspaceであれば「(組織名)の全員」に。
- URLの共有: 発行されたURLをヘルパーさんのスマホにショートカット作成させます。
② 特定事業所加算の「証拠」としての運用
- 指示のタイミング: 必ずサービス開始時刻より前にスプレッドシートの「指示欄」が入力されている必要があります。
- 報告のタイミング:
Activity_Logsのタイムスタンプを見れば、サービス終了後すぐに入力したかどうかが一目でわかります。 - 印刷保存: 暫定期間が終わる際、スプレッドシートの
Activity_Logsを利用者ごとにフィルタリングして印刷し、サービス提供記録(法定様式)の裏付けとして保管してください。
③ 印刷用テンプレート(スプレッドシート側)
- 別の「印刷用シート」を作成し、
VLOOKUP関数などを使って「利用者名」を選択すると、その月のActivity_Logsから全ての指示と報告が一覧で表示されるようにしておくと、運営基準で求められる「記録の整備」が非常に楽になります。
④ 運用上の注意点
- 個人情報の保護: GASのURLは「知っている人は誰でも入れる」状態になりがちです。簡易パスワード機能を入れていますが、URL自体の管理を徹底してください。
- 自費サービスへの対応: サービス種別に「自費」を入れるだけで、記録方法は同じで対応可能です。
この構成で、まずは5名のヘルパーさんと25名の利用者さんで2〜3ヶ月運用してみてください。現場の「入力が面倒」という声や、サ責の「指示を考えるのが大変」という実態が見えてくるはずですので、それを次の「市販アプリ導入」の際の選定基準に活用してください。



コメント