プログラミングで作る清掃スケジューラ自動化ガイド

カレンダーを見て「え、今週もう終わったの？」って思ったことありませんか？…私もです。  
私はかつて友人と小さな清掃業をやっていました。初めはホワイトボード、次にスプレッドシート…  
でも、手作業では限界があり、ミスやダブルブッキングが多発。そこでソフトウェアに助けを求めました。

![developer.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4099346/6d038d86-3bac-4d97-bdf7-0e4d1979b33c.jpeg)
---

## 目次

1. [自動化のメリット](#メリット)  
2. [基本アーキテクチャ](#アーキテクチャ)  
3. [バックエンド：REST API](#backend)  
4. [フロントエンド：React コンポーネント](#frontend)  
5. [データベース設計](#db)  
6. [外部連携：Google Calendar API](#google-calendar)  
7. [自動リマインダー：Twilio で SMS](#twilio)  
8. [デプロイと Docker](#docker)  
9. [セキュリティと ベストプラクティス](#security)  
10. [まとめ](#まとめ)  

---

## <a name="メリット"></a>1. 自動化のメリット

- **ダブルブッキング防止**：予約ロジックで時間衝突を検知。  
- **効率化**：スタッフへの自動リマインダーでミス激減。  
- **スケーラビリティ**：ビジネス拡大時も設定の調整だけで対応可能。  
- **顧客満足度向上**：ウェブポータルで手軽に再予約やキャンセル操作が可能。  

---

## <a name="アーキテクチャ"></a>2. 基本アーキテクチャ

```
[Browser] ←→ [React SPA] ←→ [Node.js/Express API] ←→ [PostgreSQL]
                                       ↓
                                [Twilio API]
                                       ↓
                           [Google Calendar API]
```

---

## <a name="backend"></a>3. バックエンド：REST API

**Node.js + Express サンプル:**

```javascript
// server.js
import express from 'express';
import bodyParser from 'body-parser';
import { Pool } from 'pg';
import dotenv from 'dotenv';
dotenv.config();

const app = express();
app.use(bodyParser.json());

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

// 予約作成エンドポイント
app.post('/api/bookings', async (req, res) => {
  const { clientName, startTime, duration } = req.body;
  const endTime = new Date(new Date(startTime).getTime() + duration * 3600000);
  const conflict = await pool.query(
    'SELECT * FROM bookings WHERE NOT($1 >= end_time OR $2 <= start_time)',
    [endTime.toISOString(), new Date(startTime).toISOString()]
  );
  if (conflict.rows.length) {
    return res.status(409).json({ error: '時間が重複しています。' });
  }
  const result = await pool.query(
    'INSERT INTO bookings(client_name, start_time, end_time) VALUES($1, $2, $3) RETURNING *',
    [clientName, startTime, endTime]
  );
  res.status(201).json(result.rows[0]);
});

// ポート起動
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
```

---

## <a name="frontend"></a>4. フロントエンド：React コンポーネント

```jsx
// BookingForm.jsx
import React, { useState } from 'react';
import axios from 'axios';

export default function BookingForm() {
  const [date, setDate] = useState('');
  const [duration, setDuration] = useState(2);

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const res = await axios.post('/api/bookings', {
        clientName: 'User',
        startTime: date,
        duration
      });
      alert(`予約完了: ${res.data.start_time} から ${res.data.end_time}`);
    } catch (err) {
      alert(err.response.data.error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>日付・時刻：</label>
      <input type="datetime-local" value={date} onChange={e => setDate(e.target.value)} required />
      <label>所要時間 (h)：</label>
      <input type="number" value={duration} onChange={e => setDuration(+e.target.value)} min="1" />
      <button type="submit">予約</button>
    </form>
  );
}
```

---

## <a name="db"></a>5. データベース設計 (PostgreSQL)

```sql
CREATE TABLE bookings (
  id SERIAL PRIMARY KEY,
  client_name VARCHAR(100) NOT NULL,
  start_time TIMESTAMP NOT NULL,
  end_time TIMESTAMP NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);
```

---

## <a name="google-calendar"></a>6. 外部連携：Google Calendar API

```javascript
import { google } from 'googleapis';
const calendar = google.calendar('v3');

async function addEvent(auth, booking) {
  const event = {
    summary: '清掃サービス',
    start: { dateTime: booking.start_time },
    end: { dateTime: booking.end_time },
  };
  await calendar.events.insert({
    auth,
    calendarId: 'primary',
    resource: event,
  });
}
```

---

## <a name="twilio"></a>7. 自動リマインダー：Twilio で SMS

```javascript
import Twilio from 'twilio';
const client = new Twilio(process.env.TWILIO_SID, process.env.TWILIO_TOKEN);

function sendReminder(phone, message) {
  return client.messages.create({
    body: message,
    from: process.env.TWILIO_FROM,
    to: phone,
  });
}
```

---

## <a name="docker"></a>8. デプロイと Docker

```dockerfile
# Dockerfile
FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["node", "server.js"]
```

---

## <a name="security"></a>9. セキュリティと ベストプラクティス

- 環境変数で機密情報を管理（`.env` ファイルは `.gitignore` に追加）。  
- SQL インジェクション防止のため、パラメータライズドクエリを使用。  
- HTTPS 対応（Let’s Encrypt などで SSL 化）。  
- ユーザー認証・認可（JWT など）。

---

## <a name="まとめ"></a>10. まとめ

清掃業のスケジューリング自動化は、技術を活用することで  
- 作業効率  
- 顧客満足度  
- ビジネスの拡張性  
を飛躍的に向上させます。  
ぜひこのテンプレートを参考に、自分だけの最適なスケジューラを構築してください！

---

## <a name="episode"></a>エピソードひとつ。聞いてください。

高齢のお客様「Doris」さんがいて、「掃除後にバスルームが変な匂いする」と言われました。  
調べたら、スタッフが漂白剤を使っていて、実は彼女アレルギーだったんです…。  
でもその情報、どこにも記録してなかったんですよね。今は、顧客ごとの好みや注意点をアプリにタグ付けしています。  
Dorisさんの[*健康*](https://quickcleanchicago.com/house-cleaning-chicago/)のために、これは大事な一歩でした。