# zsystem.jp プロジェクト メモ

## 顧客・システム構成
zsystem.jp は現場システム（在庫管理、レンタル商品のサイクル管理＝貸出〜返却）。以下の2つの顧客向けディレクトリに分かれる。

| ディレクトリ | 顧客 | 用途 |
|---|---|---|
| `zsystem.jp/letip/` | メルシーダイキ | 現場（在庫・レンタル）|
| `zsystem.jp/ellio/` | 厚木事業所 | 別顧客プロジェクト |

**関連システム**: メルシーダイキには別サーバーの基幹システム **TZ4** (`34.146.195.144`) が存在し、受発注管理・請求入金管理を担当。zsystem.jp/letip/ の作業時は TZ4 との業務連携・データフローを意識すること。

## SSH接続
```
sshpass -p 'pasokansystem' ssh -o StrictHostKeyChecking=no -o HostKeyAlgorithms=ssh-rsa -p 2222 zadmin@zsystem.jp
```
- ポート: **2222**（22は応答なし）
- HostKeyAlgorithms=ssh-rsa が必須（古いサーバー）
- ファイル転送: `scp` / `rsync` も同じオプションで

## ファイルパス
| 種別 | パス |
|---|---|
| ローカル | `/Users/menou/Library/CloudStorage/Dropbox/www/zsystem.jp/` |
| リモート | `/Users/zadmin/Sites/` |

## ファイル同期
```bash
sshpass -p 'pasokansystem' rsync -avz \
  -e "ssh -o StrictHostKeyChecking=no -o HostKeyAlgorithms=ssh-rsa -p 2222" \
  --exclude="images/" \
  --include="*.php" --include="*.js" --include="*.css" --include="*.html" \
  --include="*/" --exclude="*" \
  "/Users/menou/Library/CloudStorage/Dropbox/www/zsystem.jp/" \
  "zadmin@zsystem.jp:/Users/zadmin/Sites/"
```
- rsync後は必ずパーミッション修正が必要（rsyncがローカルの権限を引き継ぐため）
- `--exclude="images/"` でアップロード画像ディレクトリを保護（リモートのみに存在するデータがあるため）

## パーミッション修正（リモート）
```bash
# ディレクトリ（images/は除外：_wwwの書き込み権限を保護）
find /Users/zadmin/Sites -type d -user zadmin -not -name images -exec chmod 755 {} \;
# ファイル
find /Users/zadmin/Sites -name "*.php" -o -name "*.js" ... | xargs -I{} chmod 644 "{}"
```

## MySQL接続情報

- **zsystem.jp MySQL**: ポート 3306 は外部から接続可能（ローカル開発からも直接接続できる）
- **管理アカウント（root代替）**: host=localhost, user=`maintenanceadmin`, pass=`pasokansystem`
- **TZ4 MySQL**（メルシーダイキ基幹）: host=`34.146.195.144`, user=`extrauser`, pass=`extvuno97531QQ`, db=`tz4r_production`
  - zsystem.jp 経由でも接続可能

### ⚠️ Japanese エンコーディングは **HEX検証してから経路決定** ⚠️
zsystem.jp MySQL 5.0.92 上には **2つの保存経路が混在**しているため、一律ルールで書くと必ず片方で化ける。

**書き込み前の必須手順**: 対象カラムの既存データを HEX で判定
```sql
SELECT HEX(SUBSTRING(col,1,6)) FROM tbl WHERE col!='' LIMIT 1;
-- コメントの場合
SELECT HEX(COLUMN_COMMENT) FROM information_schema.COLUMNS
 WHERE TABLE_SCHEMA='ellio' AND TABLE_NAME='dispatch' LIMIT 1;
```

| 先頭パターン | 保存 | 書き方 |
|---|---|---|
| `E4`〜`EF` 開始（例:`E78FBE`=現） | **raw UTF-8** | `SET NAMES utf8` 必須 |
| `C3 XX C2 XX` パターン | **二重エンコード**（歴史的） | charset 無指定=latin1 |

- 経路①（raw utf8）: zcommon.php の `mysqli_set_charset($conn,'utf8')` 経由で書かれたテーブル。**ellio.dispatch** など新しめのテーブルはこちら
- 経路②（二重エンコード）: 昔の PHP 5.3 が SET NAMES せず書いた歴史的データ

**やらかしたミス**:
- 2026-07-11: 「latin1で扱え」だけを信じて `ellio.dispatch` のコメント追記を charset無指定で流したら phpMyAdmin で化けた。SET NAMES utf8 で再実行して修正（commit 76baf69 → 31c582e）
- 教訓: 同テーブルでも列によって混在する（dispatch_note だけ二重エンコード、他は raw utf8 だった）。**データ列とコメント両方 HEX 確認**する

詳細と判定手順: [~/.claude/projects/-Users-menou/memory/feedback_zsystem_mysql_latin1.md]


## 作業ディレクトリ別ルール

- **`letip/salesstock/inventoryview/`**: ローカル修正のみで完結。サーバーへのデプロイ（scp/rsync）は行わない
- **`ellio/skidorder/`**: **デプロイ禁止**。実運用中の画面であり、開発中のものを現場に見せてはいけない。
  シャーリングタブの追加などはローカルで完結させ、デプロイは明示的な指示があってから行う
  （ローカル確認は `php -S 127.0.0.1:8888 -t zsystem.jp` で本番DB直結のまま動かせる）

## PHPバージョン
| 環境 | バージョン | 備考 |
|---|---|---|
| ローカル (Mac) | **PHP 8.4.7** | 型チェック厳格、`mysql_`関数なし |
| リモート (zsystem.jp) | **PHP 5.3.26** | 2013年製、`mysql_`関数あり、型エラーは警告止まり |

- ローカルで出るエラー（TypeError等）がリモートでは出ないことがある
- ローカルで動作確認してからリモートに反映するのが安全
- リモートはPHP5.3のため新しい構文（`??`, `fn=>`, 型宣言等）は使用不可

## PHP構成
- `zcommon.php`: 全PHPファイルが `require_once('../zcommon.php')` で読み込む共通ライブラリ
- `connectDB()`: シングルトン実装（`static $instance`）。1リクエストで1接続のみ
- DBホスト: `zsystem.jp`（zcommon.php の switch文でURLパスからDB名を判定）
- DBユーザー例: `letip/koukoku-lutip`, `ellio/koukoku-ellio`, `tana/koukoku-tana` など
- `$_REQUEST` にCookieは含まれない（`request_order = "GP"` 設定済み）
- `error_reporting`: E_NOTICE, E_DEPRECATED, E_WARNING, E_STRICT(削除済み) を非表示

## PHP移行済み事項（mysql_ → mysqli_）
- `zsystem.jp/` 以下の全PHPファイルを `mysqli_` 形式に変換済み
- 全関数に `$mysqli = connectDB()` を追加済み（グローバル変数は使わない）
- `zcommon.php` に `mysqli_result()` ヘルパー関数あり（PHP8対応済み）
- `letip/index_old/` は削除済み（旧バックアップ）

## よくあるエラーと対処
| エラー | 原因 | 対処 |
|---|---|---|
| `mysqli_query(): Argument #1 must be of type mysqli, null` | 関数内で `connectDB()` を呼んでいない | 関数の先頭に `$mysqli = connectDB();` を追加 |
| `Object of class mysqli could not be converted to string` | print文字列内にJavaScript関数があり `$mysqli = connectDB()` が誤挿入された | その行を削除 |
| `Allowed memory size exhausted` in connectDB | `connectDB()` 内に `$mysqli = connectDB()` が誤挿入（無限再帰） | 削除してシングルトン実装を確認 |
| 403 Forbidden | ディレクトリが `700` / ファイルが `600` | chmod 755/644 で修正 |
| `E_STRICT is deprecated` | PHP8で `E_STRICT` 定数が廃止 | error_reportingから削除済み |

## PHP8 / PHP5.3 互換性チェックリスト

ローカル(PHP8.4)で動作確認する際、以下のパターンは **PHP8でエラー・PHP5.3では警告止まり** のため要注意。リモートにデプロイする際は **PHP5.3非対応の構文を使わないこと**。

### PHP8で Fatal / TypeError になるもの（PHP5.3は警告止まり）

| パターン | PHP8の挙動 | 対処 |
|---|---|---|
| `mysqli_num_rows(false)` | TypeError（Fatal） | `$result ? mysqli_num_rows($result) : 0` |
| `mb_convert_kana(null, ...)` | TypeError（Deprecated→Fatal） | `?? ''` でnull除去してから渡す |
| `str_replace('x', null, $str)` | Deprecated→TypeError | `?? ''` でnull除去 |
| `$_REQUEST['key']`（未定義キー） | Warning | `$_REQUEST['key'] ?? $_GET['key'] ?? ''` |
| `$_COOKIE['key']`（未定義キー） | Warning | `$_COOKIE['key'] ?? ''` |

### PHP5.3非対応の構文（ローカルのみで使えるが **リモートNG**）

| 構文 | 代替 |
|---|---|
| `??`（null合体演算子） | `isset($x) ? $x : ''` |
| `fn() =>`（アロー関数） | `function() use (...) {}` |
| 型宣言 `int $x`, `string $x` | 型宣言なし |
| `array_map(fn($r)=>..., $arr)` | `array_map(function($r){...}, $arr)` |
| `$obj->method()?->chain()` | null チェックを明示 |

### ローカルでの500エラーのデバッグ手順
1. `zcommon.php` の先頭に `ini_set('display_errors', 1); error_reporting(E_ALL);` を一時追加
2. エラー内容を確認して修正
3. 修正後は必ず debug 行を削除してからリモートに同期

### ループ内クエリのタイムアウト問題
ローカルはリモートMySQLへ**ネットワーク経由**で接続するため、ループ内の個別クエリが積み重なって30秒タイムアウトになりやすい（リモート本番は同一ホスト内接続なので速い）。

**対処パターン**:
- ループ内INSERT → バルクINSERT `VALUES (…),(…),…` に変換
- ループ内UPDATE → `UPDATE … SET col = CASE key WHEN v1 THEN r1 … END WHERE key IN (…)` に変換
- どうしても個別クエリが必要な場合は `set_time_limit(300)` を関数の先頭に追加（ローカルのみの問題）

## AJAX（letip/letip.js）
- `XMLHttpRequest` / `ActiveXObject` → `fetch()` API に変換済み
- `getDataCallback()`, `getData()`, `sendData()`, `beep()` が対象

## ディレクトリ構成（主要）
```
zsystem.jp/
├── zcommon.php       # 共通ライブラリ
├── dbproc.php        # DB処理関数群
├── dbproc2.php       # DB処理関数群2
├── letip/            # letipシステム
│   ├── index.php
│   ├── letip.js
│   ├── i/            # ハンディ入力
│   ├── stat/         # ステータス表示
│   ├── salesstock/   # 販売在庫
│   └── ...
└── ellio/            # ellioシステム
    ├── index.php
    ├── arrival/
    ├── arrivalnew/
    └── ...
```

## TZ4 回収予定連携（実装完了・本番稼働中）

TZ4の納品予定・回収予定を zsystem.jp 側へ連携する機能。**2026-04-15夜 TZ4側デプロイ → letip/collect/ の回収指示画面 実装完了**。

### 連携テーブル（zsystem.jp 側）
- 納品予定: `z_deliveries` / `z_delivery_details`
- 回収予定: `z_withdrawals` / `z_withdrawal_details`
- 共通カラム: `direction_cd`（方向区分）, `result_cd`

### データ仕様・注意点
- データは **2026-04-15デプロイ時刻以降の予定のみ**（過去データ・実績(actuals)は対象外）
- `plan_result_type`: 2=予定 / 3=キャンセルされた予定 / 5=完了した予定
- `deleted=1`: 実削除 OR キャンセル予定(plan_result_type=3)。レコード自体は残る。通常運用では deleted=1 ≒ キャンセル
- `WHERE deleted = 0` でキャンセル・削除を自動除外（plan_result_type=3 は deleted=1 を伴うため出現しない）。collect/ はこの方式で実装済み

### TZ4側 回収予定データの所在（旧 collectold 方式の結合キー、参考）
```
schedules.party_number = z_munit_sales_histories.user_id → z_munit_id → z_munits.id
```
- `schedules`（schedule_kind_id=2=レンタル回収）: 回収スケジュール本体
- `z_munit_sales_histories`: 貸出記録（product_set_state: 0=回収待ち / -1=回収済み）
- `z_munits`: レンタル品1台ごとの現在ステータス
