# 画面設計書 50-配信送信

## 概要

本ドキュメントは、QuickerSite CMSのバックサイト（管理画面）における「配信送信」画面の設計書です。

### 本画面の処理概要

ニュースレターの実際の配信（メール送信）を実行する画面です。配信編集画面で設定した内容に基づき、選択されたカテゴリの購読者全員にニュースレターを送信します。大量送信によるサーバー負荷を分散するため、一定間隔で指定件数ずつ送信するバッチ処理方式を採用しています。

**業務上の目的・背景**：ニュースレターの一斉送信は、メールサーバーへの負荷が大きいため、分割送信が必要です。本画面では、送信間隔と1回あたりの送信件数を調整でき、META HTTPリフレッシュを使用した自動継続送信により、ブラウザを開いたままにするだけで全配信を完了できます。配信履歴を保存する設定の場合は、開封追跡用のログも同時に作成されます。

**画面へのアクセス方法**：配信編集画面（bs_newsletterMailingEdit.asp）で「続行」ボタンをクリックすると、本画面に遷移します。直接URLでアクセスすることも可能ですが、配信設定が存在しない場合は意味のある操作ができません。

**主要な操作・処理内容**：
1. 送信設定確認：送信先の購読者数とニュースレター名を表示
2. 送信パラメータ設定：1回あたりの送信件数と送信間隔を指定
3. 送信開始：「Send now!」ボタンで配信開始
4. 送信中表示：送信済み件数をリアルタイム表示
5. 送信完了：開始・終了時刻と総送信件数を表示

**画面遷移**：
- 遷移元：配信編集画面（bs_newsletterMailingEdit.asp）
- 遷移先：ニュースレター一覧画面（bs_newsletterList.asp）、配信履歴画面（bs_newsletterMailingHistory.asp）

**権限による表示制御**：secondAdmin.bNewsletter権限がない場合はアクセス不可。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 71 | メーリング作成・送信 | 主機能 | ニュースレターの一斉送信実行 |

## 画面種別

処理実行

## URL/ルーティング

```
/asp/bs_newsletterMailingSend.asp?iNewsletterMailingID={暗号化されたID}  （初期表示）
/asp/bs_newsletterMailingSend.asp?iStart={送信済み件数}&iInterval={件数}&iForceReload={秒}&postback=1&sent=0&iNewsletterMailingID={ID}  （送信中）
/asp/bs_newsletterMailingSend.asp?iStart={総件数}&sent=1&iNewsletterMailingID={ID}  （送信完了）
```

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 入力/出力 | 説明 |
|--------|--------|-----|------|-----------|------|
| 配信ID | iNewsletterMailingID | hidden | 必須 | 入力 | 暗号化された配信ID |
| ポストバックフラグ | postBack | hidden | - | 入力 | 送信処理開始フラグ |
| 送信状態 | sent | hidden | - | 入力 | 0:送信中、1:完了、空:未開始 |
| 送信件数/回 | iInterval | select | - | 入力 | 1回あたりの送信件数（1-100、デフォルト10） |
| 送信間隔 | iForceReload | select | - | 入力 | 次回送信までの秒数（3-600秒、3秒刻み、デフォルト15） |
| 送信開始位置 | iStart | hidden | - | 入力 | 現在の送信済み件数（0から開始） |
| セキュリティコード | QS_secCodeHidden | hidden | - | 入力 | CSRF対策用トークン |

## 表示項目

| 項目名 | 表示位置 | 説明 |
|--------|---------|------|
| 戻るリンク | 上部・下部 | ニュースレター一覧への戻りリンク |
| ニュースレター名 | 確認メッセージ内 | 送信対象のニュースレター名（newsletterMailing.newsletter.sName） |
| 購読者数 | 確認メッセージ内 | 送信対象の総購読者数（total） |
| 送信件数選択 | フォーム | 1-100のドロップダウン |
| 送信間隔選択 | フォーム | 3-600秒（3秒刻み）のドロップダウン |
| 送信ボタン | フォーム | 「Send now!」ボタン |
| 送信中メッセージ | 送信中画面 | 送信済み件数と警告メッセージ |
| 完了メッセージ | 完了画面 | 総送信件数と開始・終了時刻 |
| 配信履歴リンク | 完了画面 | bLog=trueの場合のみ表示 |

## イベント仕様

### 1-送信開始ボタン押下

**処理フロー**：
1. JavaScript confirmで送信確認ダイアログ表示（「Are you sure to send the mailing now?」）
2. 確認後、ボタンのテキストが「Please wait... Do not close this window!」に変更
3. ボタンが無効化（disabled）される
4. フォームがPOSTされる（postBack=true、sent=0）
5. META HTTPリフレッシュタグが生成され、自動継続送信開始

### 2-送信処理（自動継続）

**処理フロー**：
1. postBack=trueの場合、送信処理を実行
2. newsletterMailing.sCategoryをカンマ区切りで分割
3. 各カテゴリの購読者をループ処理
   - tblNewsletterCategorySubscriberからbActive=trueの購読者を取得
   - iStart（開始位置）からiInterval件分を処理
   - 重複メールアドレスはスキップ（allEmailsで管理）
4. bLog=trueの場合、各送信ごとにtblNewsletterLogにレコード追加
   - sKeyにランダム文字列を生成（開封追跡用）
   - sAddImageUrlで追跡用ビーコンURLを生成
5. newsletter.send()でメール送信
6. iStartをインクリメントして次回開始位置を更新
7. 未完了の場合、META HTTPリフレッシュで自動リロード
8. 完了時はsent=1でリダイレクト

### 3-送信完了時の処理

**処理フロー**：
1. session("NLstopTime")に完了時刻を記録
2. bLog=trueの場合
   - newsletterMailing.dSentDateに現在日時を設定
   - newsletterMailing.save()で配信日時を保存
   - 配信履歴画面へのリンクを表示
3. bLog=falseの場合
   - newsletterMailing.remove()で配信設定を削除（履歴不要のため）

### 4-戻るリンク押下

ニュースレター一覧画面（bs_newsletterList.asp）に遷移します。

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 送信処理（bLog=true） | tblNewsletterLog | INSERT | 各購読者への送信ログ追加 |
| 送信完了（bLog=true） | tblNewsletterMailing | UPDATE | 配信日時の更新 |
| 送信完了（bLog=false） | tblNewsletterLog | DELETE | 配信ログ削除 |
| 送信完了（bLog=false） | tblNewsletterMailing | DELETE | 配信設定削除 |

### テーブル別更新項目詳細

#### tblNewsletterLog

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | iSubscriberID | rs("iId") | 購読者ID |
| INSERT | iMailingID | newsletterMailing.iId | 配信ID |
| INSERT | bRead | false | 開封フラグ（初期値false） |
| INSERT | dWhen | null | 開封日時（初期値null） |
| INSERT | sKey | generatePassword | ランダム文字列（追跡用キー） |

#### tblNewsletterMailing

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | dSentDate | now() | 送信完了日時 |

## メッセージ仕様

| メッセージID | 種別 | 表示条件 | メッセージ内容 |
|-------------|------|---------|-----------------|
| (確認) | 確認 | 送信ボタン押下時 | "Are you sure to send the mailing now?" |
| (送信中) | 情報 | 送信処理中 | "{件数} messages sent until now... STILL SENDING! DO NOT CLOSE THIS WINDOW!" |
| (完了) | 成功 | 送信完了時 | "Mailing finished! - {件数} messages are sent!" |
| (開始時刻) | 情報 | 送信完了時 | "Mailing started on: {時刻}" |
| (終了時刻) | 情報 | 送信完了時 | "Mailing completed on: {時刻}" |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| アクセス権限なし | secondAdmin.bNewsletter権限チェックに失敗した場合、エラー表示 |
| 配信IDなし | newsletterMailingインスタンスが空の状態で処理続行（意味のある動作なし） |
| 送信中断（ブラウザ閉じ） | 次回アクセス時にiStartから再開可能（ただし重複送信の可能性あり） |

## 備考

- 送信間隔のデフォルトは10件/15秒（1分間に最大40件程度）
- META HTTP-EQUIVリフレッシュを使用した自動継続方式のため、JavaScript無効でも動作
- allEmailsディクショナリで同一メールアドレスへの重複送信を防止
- 開封追跡はビーコン画像方式（default.asp?pageAction=rlog&k={sKey}&i={iId}）
- session("NLstartTime")、session("NLstopTime")で開始・終了時刻を保持
- 送信完了後、bLog=falseの場合は配信設定ごと削除される（一時的な配信用途）
- convert2()関数で時刻を2桁ゼロ埋め表示
- カテゴリが複数選択されている場合、カンマ区切りで処理

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | newsletterMailing.asp | `asp/includes/newsletterMailing.asp` | cls_newsletterMailingクラス。プロパティ（iId, iNewsletterID, sCategory, bLog, dSentDate）、Newsletter()プロパティ、save()、remove()を確認 |
| 1-2 | newsletter.asp | `asp/includes/newsletter.asp` | cls_newsletterクラスのsend()メソッドを確認（実際のメール送信処理） |

**読解のコツ**: newsletterMailingはニュースレター（テンプレート）とカテゴリ（送信先）を紐付ける中間エンティティです。bLogプロパティが開封追跡の有無を決定します。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | bs_newsletterMailingSend.asp | `asp/bs_newsletterMailingSend.asp` | 画面表示と送信処理のメインファイル。sent値による3つの状態（未開始/送信中/完了）を確認 |

**主要処理フロー**:
1. **4行目**: 権限チェック（logon.hasaccess secondAdmin.bNewsletter）
2. **5-7行目**: 初期化（allEmails辞書、cls_newsletterMailingインスタンス生成）
3. **12-18行目**: 送信パラメータ取得（iInterval、iIntervalS）、デフォルト10件/15秒
4. **25-75行目**: postBack=trueの場合の送信処理ループ
   - **29行目**: 購読者取得SQL（bActive=true）
   - **30-32行目**: 開始時刻記録（session("NLstartTime")）
   - **40-56行目**: bLog=trueの場合のtblNewsletterLog登録
   - **57行目**: newsletter.send()でメール送信
   - **65-72行目**: 完了判定とリダイレクト
5. **76-80行目**: 総購読者数計算（total）
6. **83-91行目**: sent値による表示分岐
   - **87-88行目**: sent=""（未開始）→送信確認フォーム
   - **89行目**: sent="0"（送信中）→進捗表示
   - **89-91行目**: sent="1"（完了）→完了メッセージと履歴リンク

### プログラム呼び出し階層図

```
bs_newsletterMailingSend.asp（画面表示・送信処理）
    │
    ├─ begin.asp（初期化）
    │
    ├─ bs_security.asp（認証チェック）
    │      └─ logon.hasaccess(secondAdmin.bNewsletter)
    │
    ├─ cls_newsletterMailing
    │      ├─ Class_Initialize()
    │      │      └─ pick(decrypt(request("iNewsletterMailingID")))
    │      │
    │      ├─ Newsletter（プロパティ）
    │      │      └─ cls_newsletter.pick(iNewsletterID)
    │      │             └─ send(sName, sEmail, sKey)
    │      │
    │      ├─ save()
    │      │      └─ dSentDate UPDATE
    │      │
    │      └─ remove()
    │             ├─ tblNewsletterLog DELETE
    │             └─ tblNewsletterMailing DELETE
    │
    ├─ customer.NewsletterCategories
    │      └─ カテゴリごとの購読者数取得
    │
    ├─ tblNewsletterCategorySubscriber（SELECT）
    │      └─ bActive=true の購読者を取得
    │
    ├─ tblNewsletterLog（INSERT）
    │      └─ bLog=trueの場合、送信ログ追加
    │             ├─ iSubscriberID
    │             ├─ iMailingID
    │             ├─ bRead=false
    │             ├─ dWhen=null
    │             └─ sKey=generatePassword
    │
    ├─ allEmails（Dictionary）
    │      └─ 重複送信防止用
    │
    └─ includes/footer.asp
```

### データフロー図

```
[入力]                      [処理]                           [出力]

初期表示
├─ iNewsletterMailingID ───▶ pick() ────────────────────────▶ 配信設定読込
│                                │
│                                └─ sCategory（カンマ区切り）
│                                         │
├─ customer.NewsletterCategories ────────▶ total計算
│                                                │
└─ フォーム表示 ◀──────────────────────────────────┘
         │
         ▼
送信開始（postBack=true）
├─ iInterval（件数/回）────▶ stopon計算（iStart+iInterval）
├─ iForceReload（秒）
│
├─ sCategory ────────────────▶ split(",")
│                                  │
│                                  ▼
│                            for each category
│                                  │
│                                  ▼
│                    tblNewsletterCategorySubscriber SELECT
│                         bActive=true
│                                  │
│                                  ▼
│                            while not rs.eof
│                                  │
│                    ┌─────────────┴─────────────┐
│                    │                           │
│              bLog=true                    bLog=false
│                    │                           │
│                    ▼                           │
│         tblNewsletterLog INSERT                │
│         ├─ iSubscriberID                       │
│         ├─ iMailingID                          │
│         ├─ sKey=generatePassword               │
│         └─ sAddImageUrl生成                    │
│                    │                           │
│                    └───────────┬───────────────┘
│                                │
│                                ▼
│                    newsletter.send(sName, sEmail, sKey)
│                                │
│                                ▼
│                         iStart++
│                                │
│                    ┌───────────┴───────────┐
│                    │                       │
│               未完了                     完了
│                    │                       │
│                    ▼                       ▼
│           META REFRESH ───────▶      session("NLstopTime")設定
│           iForceReload秒後              │
│           自動リロード           ┌──────┴──────┐
│                                  │             │
│                            bLog=true     bLog=false
│                                  │             │
│                                  ▼             ▼
│                          save(dSentDate)   remove()
│                                  │             │
│                                  ▼             ▼
└─────────────────────────▶ sent=1リダイレクト
                                        │
                                        ▼
                              完了画面表示
                              ├─ 送信件数
                              ├─ 開始・終了時刻
                              └─ 配信履歴リンク（bLog=trueのみ）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| bs_newsletterMailingSend.asp | `asp/bs_newsletterMailingSend.asp` | ソース | 配信送信画面 |
| bs_newsletterMailingEdit.asp | `asp/bs_newsletterMailingEdit.asp` | ソース | 配信編集画面（遷移元） |
| bs_newsletterMailingHistory.asp | `asp/bs_newsletterMailingHistory.asp` | ソース | 配信履歴画面（遷移先） |
| bs_newsletterList.asp | `asp/bs_newsletterList.asp` | ソース | ニュースレター一覧画面 |
| newsletterMailing.asp | `asp/includes/newsletterMailing.asp` | ソース | 配信クラス定義 |
| newsletter.asp | `asp/includes/newsletter.asp` | ソース | ニュースレタークラス定義（send()メソッド） |
| newsletterCategory.asp | `asp/includes/newsletterCategory.asp` | ソース | カテゴリクラス定義 |
| customer.asp | `asp/includes/customer.asp` | ソース | 顧客クラス（NewsletterCategoriesプロパティ） |
| bs_security.asp | `asp/bs_security.asp` | ソース | 認証・認可処理 |
| begin.asp | `asp/begin.asp` | ソース | 共通初期化処理 |
| header.asp | `asp/includes/header.asp` | テンプレート | 共通ヘッダー |
| footer.asp | `asp/includes/footer.asp` | テンプレート | 共通フッター |
