通勤認定エクセルツール対応13 M1 対応2

This commit is contained in:
guanxiangwei
2026-05-28 20:57:47 +09:00
parent 50ef0c74cc
commit 29c9200132
15 changed files with 730 additions and 318 deletions

View File

@@ -25,13 +25,46 @@
- ❌ Forbidden: hardcoded file paths or connection strings. Use config sheet or constants module - ❌ Forbidden: hardcoded file paths or connection strings. Use config sheet or constants module
- ❌ Forbidden: non-English comments (Chinese / Japanese / Korean) - ❌ Forbidden: non-English comments (Chinese / Japanese / Korean)
## Project Structure ### Code Review Checklist
**Before editing**
- Read the entire function from first line to last line (not just the lines you plan to change)
- Find all call sites that reference the function or data structure you are about to modify
**After editing**
- Read the entire function from first line to last line again
- Verify: are all variables declared, are scopes correct, is the logic complete
- Go through every call site one by one to confirm compatibility
**When the user asks "Are you sure?"**
- Actually re-read the relevant code instead of saying "looks fine"
- grep all references and verify them one by one
**Key principle**
- Never skip context — do not look at only the diff and call it done
- The compiler cannot catch logic errors or missing variable declarations — only human review can
- Read the whole function, read all call sites — no exceptions
## Design Document
**Primary reference**: `documents/Tukin_Design_Document.md`
Before editing any sheet class or cache logic, **read the design document first**. It contains:
- Sheet column layouts with HeaderRow/StartRow/StartCol/EndCol per `RefreshSheetDict`
- Cache key/value structures for every CACHE_* constant
- Data flow diagrams for C1 editing cascade and CSV import
- Column layout reference (C1 has 58 columns, C to BG)
- All known issues and their fix status
**Rule**: Do not guess sheet layout or cache structure. Look it up in the design document.
vba/ vba/
AGENTS.md, README.md, .gitignore, LICENSE AGENTS.md, README.md, .gitignore, LICENSE
通勤手当テンプレート2026xxxx.xlsm (latest date version) 通勤手当テンプレート2026xxxx.xlsm (latest date version)
data/ CSV master data (14 files) data/ CSV master data (14 files)
documents/ design docs (3 files) documents/ design docs
Tukin_Design_Document.md — master design doc: sheet layouts, cache architecture, column reference, data flow
checklist-2026-05-27.md — audit checklist (historical)
sql/ DB definitions (4 files) sql/ DB definitions (4 files)
src/sh/ src/sh/
juk/ address module juk/ address module
@@ -55,13 +88,13 @@ vba/
M2.cls (400 lines) - section detail master M2.cls (400 lines) - section detail master
O1.cls (5 lines) - address master O1.cls (5 lines) - address master
O2.cls (6 lines) - sender master (507) O2.cls (6 lines) - sender master (507)
O3.cls (61 lines) - master 225 O3.cls (61 lines) - (220) notification reason
T1.cls (54 lines) - commutation pass master T1.cls (54 lines) - commutation pass master
T2.cls (114 lines) - ticket master T2.cls (114 lines) - ticket master
T3.cls (74 lines) - master 246 T3.cls (74 lines) - master 246
Z1.cls (64 lines) - transport master (222) Z1.cls (64 lines) - transport master (222)
Z2.cls (54 lines) - decision master (223) Z2.cls (54 lines) - decision master (223)
Z3.cls (57 lines) - monthly amount decision master (224) Z3.cls (57 lines) - monthly amount decision master (224)
Z4.cls - master Z4 Z4.cls - (221) route station name
Sheet class prefixes: C=commuter editing, M=section master, O=other, T=commuter route, Z=master config Sheet class prefixes: C=commuter editing, M=section master, O=other, T=commuter route, Z=master config

View File

@@ -1,81 +0,0 @@
# Tukin_C1 ユーザーアクションドキュメント
## 列アクションのマッピング
### C列 (職員番号)
- **トリガー条件**: C列 >= 第7行、内容変化
- **アクション**:
- 内容が空 → `ClearRowData` で一行クリア
- 内容あり → `FillAddressFromO1` で住所ドロップダウン + 4区間の交通機関ドロップダウン生成
---
### 区間1
| 列 | アクション | トリガー条件 | 処理ロジック |
|---|---|---|---|
| **T** (交通機関) | 交通機関ドロップダウン変化 | Column=20 | `CreateZ1StationDropdown` → U列(発)ドロップダウン生成 |
| **U** (利用区間発) | 発ドロップダウン変化 | Column=21 | `CreateM1KukanDDropdown` → V列(着)ドロップダウン生成 |
| **S** (区間コード) | 区間コード入力 | Column=19 | T列ドロップダウン生成 → T列値ありの場合U,Vを填充 + W列(券種)ドロップダウン生成 |
| **W** (券種) | 券種ドロップダウン変化 | Column=23 | `CreateM2CodeDropdown` → X列(コード)ドロップダウン生成 |
---
### 区間2
| 列 | アクション | トリガー条件 | 処理ロジック |
|---|---|---|---|
| **AA** (交通機関) | 交通機関ドロップダウン変化 | Column=27 | `CreateZ1StationDropdown` → AB列(発)ドロップダウン生成 |
| **AB** (利用区間発) | 発ドロップダウン変化 | Column=28 | `CreateM1KukanDDropdown` → AC列(着)ドロップダウン生成 |
| **Z** (区間コード) | 区間コード入力 | Column=26 | AA列ドロップダウン生成 → AA列値ありの場合AB,ACを填充 + AD列(券種)ドロップダウン生成 |
| **AD** (券種) | 券種ドロップダウン変化 | Column=30 | `CreateM2CodeDropdown` → AE列(コード)ドロップダウン生成 |
---
### 区間3
| 列 | アクション | トリガー条件 | 処理ロジック |
|---|---|---|---|
| **AH** (交通機関) | 交通機関ドロップダウン変化 | Column=34 | `CreateZ1StationDropdown` → AI列(発)ドロップダウン生成 |
| **AI** (利用区間発) | 発ドロップダウン変化 | Column=35 | `CreateM1KukanDDropdown` → AJ列(着)ドロップダウン生成 |
| **AG** (区間コード) | 区間コード入力 | Column=33 | AH列ドロップダウン生成 → AH列値ありの場合AI,AJを填充 + AK列(券種)ドロップダウン生成 |
| **AK** (券種) | 券種ドロップダウン変化 | Column=37 | `CreateM2CodeDropdown` → AL列(コード)ドロップダウン生成 |
---
### 区間4
| 列 | アクション | トリガー条件 | 処理ロジック |
|---|---|---|---|
| **AO** (交通機関) | 交通機関ドロップダウン変化 | Column=41 | `CreateZ1StationDropdown` → AP列(発)ドロップダウン生成 |
| **AP** (利用区間発) | 発ドロップダウン変化 | Column=42 | `CreateM1KukanDDropdown` → AQ列(着)ドロップダウン生成 |
| **AN** (区間コード) | 区間コード入力 | Column=40 | AO列ドロップダウン生成 → AO列値ありの場合AP,AQを填充 + AR列(券種)ドロップダウン生成 |
| **AR** (券種) | 券種ドロップダウン変化 | Column=44 | `CreateM2CodeDropdown` → AS列(コード)ドロップダウン生成 |
---
## メソッド一覧
| メソッド名 | 機能 |
|---|---|
| `FillAddressFromO1` | 職員番号(C列)をキーとしてO1キャッシュから住所(I列)ドロップダウン生成 |
| `CreateZ1TransportDropdown` | 交通機関ドロップダウン生成 |
| `CreateZ1StationDropdown` | 交通機関をキーとしてZ1キャッシュから発ドロップダウン生成 |
| `CreateM1KukanDDropdown` | 交通機関+発をキーとしてM1KukanDキャッシュから着ドロップダウン生成 |
| `FillKukanFromM1` | 区間コードをキーとしてM1キャッシュから区間情報(T/U/V等)填充 |
| `CreateM2Dropdown` | 区間コードをキーとして券種ドロップダウン生成 |
| `CreateM2CodeDropdown` | 区間コード+券種をキーとしてコードドロップダウン生成 |
| `ClearRowData` | 一行データクリア |
| `ClearKukanValidation` | 指定列の検証ドロップダウンをクリア |
---
## キャッシュ依存
| キャッシュ | 用途 |
|---|---|
| `o1Cache` | 職員番号 → 住所 |
| `z1Cache` | 交通機関 → 駅 |
| `m1KukanDCache` | 交通機関+発 → 着 |
| `m1Cache` | 区間コード → 区間情報 |
| `m2Cache` | 区間コード+券種 → コード |

View File

@@ -1,71 +0,0 @@
### 届出情報
|列|C列|D列|E列|F列|G列|H列|
|--------|--------|--------|--------|--------|--------|--------|
|ヘッダ|職員番号|事実発生年月日|提出年月日|受理年月日|届出の事由コード|届出の備考|
|データ型|8|日付|日付|日付|Enum|文字列|
### 住所情報
|列|I列|J列|
|--------|--------|--------|
|ヘッダ|住所1|住所2|
|データ型|文字列|文字列|
### 出勤情報
|列|K列|L列|M列|N列|O列|
|--------|--------|--------|--------|--------|--------|
|ヘッダ|運賃改正・法改正年月日|出勤予定日数|往復区分|交替制|算出式|
|データ型|日付|数字|Enum|Enum|文字列|
### 自動車等情報
|列|P列|Q列|R列|
|--------|--------|--------|--------|
|ヘッダ|自動車等使用距離|自動車等支給額|自動車等駐車場代|
|データ型|数字|数字|数字|
### 区間1情報
|列|S列|T列|U列|V列|W列|X列|Y列|
|--------|--------|--------|--------|--------|--------|--------|--------|
|ヘッダ|区間1区間コード|区間1交通機関|区間1発|区間1着|区間1券種|区間1コード|区間1支給開始年月|
|データ型|5|3|文字列|文字列|Enum|3|日付|
### 区間2情報
|列|Z列|AA列|AB列|AC列|AD列|AE列|AF列|
|--------|--------|--------|--------|--------|--------|--------|--------|
|ヘッダ|区間2区間コード|区間2交通機関|区間2発|区間2着|区間2券種|区間2コード|区間2支給開始年月|
|データ型|5|3|文字列|文字列|Enum|3|日付|
### 区間3情報
|列|AG列|AH列|AI列|AJ列|AK列|AL列|AM列|
|--------|--------|--------|--------|--------|--------|--------|--------|
|ヘッダ|区間3区間コード|区間3交通機関|区間3発|区間3着|区間3券種|区間3コード|区間3支給開始年月|
|データ型|5|3|文字列|文字列|Enum|3|日付|
### 区間4情報
|列|AN列|AO列|AP列|AQ列|AR列|AS列|AT列|
|--------|--------|--------|--------|--------|--------|--------|--------|
|ヘッダ|区間4区間コード|区間4交通機関|区間4発|区間4着|区間4券種|区間4コード|区間4支給開始年月|
|データ型|5|3|文字列|文字列|Enum|3|日付|
### 決定事項情報
|列|AU列|AV列|AW列|AX列|
|--------|--------|--------|--------|--------|
|ヘッダ|決定事項区分コード|非該当の理由|非該当者認定簿出力区分|手当月額の決定区分コード|
|データ型|Enum|文字列|Enum|Enum|
### 備考情報
|列|AY列|AZ列|BA列|
|--------|--------|--------|--------|
|ヘッダ|支給の始期|備考|所属コード|
|データ型|日付|文字列|文字列|
### 認定情報
|列|BB列|BC列|
|--------|--------|--------|
|ヘッダ|認定年月日|(各庁の長)官職コード|
|データ型|日付|ENUM|
### エラーメッセージ
|列|BD列|
|--------|--------|
|ヘッダ|エラーメッセージ|
|データ型|文字列|

View File

@@ -1,45 +0,0 @@
# Tukin キャッシュ マッピング
## キャッシュ一覧
### m1Cache
|列|C列|D列|E列|F列|G列|I列|L列|
|--------|--------|--------|--------|--------|--------|--------|--------|
|ヘッダ|区間コード|交通機関区分|交通機関名称|利用区間発名|利用区間着名|券種|運賃|
### m1KukanDCache
|列|D列|F列|G列|
|--------|--------|--------|--------|
|ヘッダ|交通機関区分|利用区間発名|利用区間着名|
### m2Cache
|列|C列|I列|J列|K列|
|--------|--------|--------|--------|--------|
|ヘッダ|区間コード|券種|コード|名称|
### z1Cache (222)交通機関マスタ
|列|C列|D列|
|--------|--------|--------|
|ヘッダ|区分|交通機関名称|
### z2Cache (223)通勤_決定事項区分一覧
|列|C列|D列|
|--------|--------|--------|
|ヘッダ|区分|決定事項|
### z3Cache (224)通勤_手当月額の決定区分一覧
|列|C列|D列|
|--------|--------|--------|
|ヘッダ|区分|手当月額の決定|
### t1Cache
### o1Cache 住所情報
|列|C列|E列|F列|
|--------|--------|--------|--------|
|ヘッダ|職員番号|住所1|住所2|
### o2Cache (507)発信者一覧
|列|C列|D列|
|--------|--------|--------|
|ヘッダ|区分|官職名称|

View File

@@ -0,0 +1,373 @@
# Commuter Allowance Editor — Design Document
## 1. Project Overview
| Item | Value |
|------|-------|
| Application | Excel 2021 (.xlsm) |
| Purpose | Edit commuter certification by referencing master data |
| Module | tuk (通勤 = commutation) |
| Entry Point | メインメニュー (Main Menu sheet) |
---
## 2. Sheet Inventory
### 2.1 Editor Sheets
#### C1 — 通勤手当CSV編集 (Commuter Allowance Editor)
- **HeaderRow**: 6 | **StartRow**: 8 | **StartCol**: C | **EndCol**: BC
- **Encoding**: shift_jis | **HasHeader**: Yes
- **Role**: Main editing sheet — direct cell editing only, no CSV import
- **Key columns**:
- C: 職員番号 (Employee ID) — triggers address + transport dropdowns
- S/AA/AI/AQ: 区間N区間コード (Section N Route Code) — triggers fill from M1 + dropdown cascade
- T/AB/AJ/AR: 区間N交通機関 (Section N Transport Type)
- U/AC/AK/AS: 区間N発 (Section N Departure Station)
- V/AD/AL/AT: 区間N着 (Section N Arrival Station)
- W/AE/AM/AU: 区間N券種 (Section N Ticket Type)
- X/AF/AN/AV: 区間Nコード (Section N Code)
- AY: 決定事項区分コード (Determination Category)
- BB: 手当月額の決定区分コード (Monthly Amount Decision Category)
#### M1 — 区間メンテナンス (Route Maintenance)
- **HeaderRow**: 5 | **StartRow**: 7 | **StartCol**: C | **EndCol**: N
- **Encoding**: shift_jis | **HasHeader**: Yes
- **Role**: Route master — defines routes (code → transport + departure + arrival + fare)
- **Key columns**:
- C: 利用区間コード (Route Code)
- D: 交通機関区分 (Transport Category)
- E: 交通機関名称 (Transport Name)
- F: 利用区間発名 (Departure Station)
- G: 利用区間着名 (Arrival Station)
- I: 運賃 (Fare)
- J: 現金の場合の1箇月運賃 (Monthly Fare Cash)
- K: 連絡 (Connection)
- L: 特別料金区分 (Special Fare Category)
- M: 特別料金券種 (Special Fare Ticket Type)
- N: 特別料金負担額 (Special Fare Burden Amount)
#### M2 — 区間詳細メンテナンス (Route Detail Maintenance)
- **HeaderRow**: 6 | **StartRow**: 8 | **StartCol**: C | **EndCol**: R
- **Encoding**: shift_jis | **HasHeader**: Yes
- **Role**: Route detail master — ticket types and pricing per route code
- **Key columns**:
- C: 利用区間コード (Route Code) — join key with M1
- I: 券種 (Ticket Type: 0=普通, 1=定期券, 2=回数券, 3=プリペイドカード)
- J: コード (Ticket Code)
- K: 名称 (Ticket Name)
- L: 1箇月運賃/販売額 (Monthly Fare / Sales Price)
- M: 定期額/券1(額)/利用額 (Monthly / Ticket1 Amount / Usage Amount)
- N: 定期支給期間/券1(枚)/特別料金 (Monthly Issue Period / Ticket1 Qty / Special Fare)
- O: 特別料金/券2(額) (Special Fare / Ticket2 Amount)
- P: 券2(枚) (Ticket2 Quantity)
- Q: 端数(額) (Fractional Amount)
- R: 特別料金 (Special Fare)
### 2.2 Master / Config Sheets (Z-series)
#### Z1 — (222)交通機関マスタ (Transport Master)
- **HeaderRow**: 5 | **StartRow**: 7 | **StartCol**: C | **EndCol**: I
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Transport type catalog (dropdown source for C1 T/AB/AJ/AR)
- **Columns**: C=区分 (code), D=交通機関名称 (name), E=ポップアップ名称, F=コメント, G=略式名称, H=表示しない, I=略称
#### Z2 — (223)通勤_決定事項区分一覧 (Determination Category List)
- **HeaderRow**: 5 | **StartRow**: 7 | **StartCol**: C | **EndCol**: G
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Determination categories for AY column dropdown
- **Columns**: C=区分 (code), D=画面用名称, E=ポップアップ用名称, F=コメント, G=表示しない
#### Z3 — (224)通勤_手当月額の決定区分一覧 (Monthly Amount Decision Category List)
- **HeaderRow**: 5 | **StartRow**: 7 | **StartCol**: C | **EndCol**: H
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Monthly amount decision categories for BB column dropdown
- **Columns**: C=区分 (code), D=画面用名称, E=ポップアップ用名称, F=コメント, G=表示しない, H=名称2
#### Z4 — (221)利用区間発着名区分 (Route Station Name Category)
- **HeaderRow**: 5 | **StartRow**: 7 | **StartCol**: C | **EndCol**: H
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Station name catalog grouped by line (rosen). Used for M1 F/G column dropdowns.
- **Columns**: C=区分 (station code), D=画面用名称/駅名 (station display name), E=ポップアップ名称, F=コメント/路線名 (line name), G=正式名称, H=表示しない
- **Note**: E column (ポップアップ名称) is populated but currently ignored by code. Code reads F (路線名) and D (駅名) only.
### 2.3 Ticket Master Sheets (T-series)
#### T1 — (244)通勤_定期券名称区分一覧 (Commuter Pass Name Category)
- **HeaderRow**: 5 | **StartRow**: 7 | **StartCol**: C | **EndCol**: G
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Periodic commuter pass types (dropdown source for W/AE/AM/AU)
- **Columns**: C=区分 (code), D=画面用名称, E=ポップアップ用名称, F=コメント, G=表示しない
#### T2 — (245)通勤_回数券名称区分一覧 (Fare Ticket Name Category)
- **HeaderRow**: 5 | **StartRow**: 7 | **StartCol**: C | **EndCol**: M
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Frequency ticket types with pricing breakdown
- **Columns**: C=区分, D=画面用名称, E=ポップアップ用名称, F=コメント, G=表示しない, H=販売額, I=券1(額), J=券1(枚), K=券2(額), L=券2(枚), M=端数額
- **Feature**: ZeroFillCols = H,I,J,K,L,M — when C column (区分) is edited and H~M are empty, they are auto-filled with "0"
#### T3 — (246)通勤_プリペイドカード名称区分一覧 (Prepaid Card Name Category)
- **HeaderRow**: 5 | **StartRow**: 7 | **StartCol**: C | **EndCol**: I
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Prepaid card types with pricing
- **Columns**: C=区分, D=画面用名称, E=ポップアップ用名称, F=コメント, G=表示しない, H=販売額, I=利用額
### 2.4 Other / Reference Sheets
#### O1 — 住所情報 (Address Information)
- **HeaderRow**: 5 | **StartRow**: 6 | **StartCol**: C | **EndCol**: F
- **Encoding**: shift_jis | **HasHeader**: Yes
- **Role**: Employee address lookup (keyed by employee number)
- **Columns**: C=社員番号, D=更新日, E=住所1, F=住所2
#### O2 — (507)発信者一覧 (Sender List)
- **HeaderRow**: 5 | **StartRow**: 6 | **StartCol**: C | **EndCol**: O
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Sender/official name list for dropdown (O2Cache)
- **Columns**: C=区分, D=画面用名称, E=ポップアップ用名称, F=コメント, G=表示しない, H=発信者名称, I=発信者氏名, J=所属区分From), K=所属区分To), L=研究科区分, M=ソート順From), N=ソート順To), O=諸手当認定者区分
#### O3 — (220)通勤手当届出事由区分 (Notification Reason Category)
- **HeaderRow**: 5 | **StartRow**: 6 | **StartCol**: C | **EndCol**: I
- **Encoding**: utf-8 | **HasHeader**: No
- **Role**: Notification reason categories for C1 G column dropdown (Todoke)
- **Columns**: C=区分, D=画面用名称, E=ポップアップ名称, F=コメント, G=表示しない, H=備考, I=からまで
#### Enum — Dropdown Enum Values
- **Role**: Stores all enum values used in dropdown validation
- **Sections** (each keyed by column position):
- Col A (KeyCol=1): 特別料金区分 (tokubetuList: 普通=0, 定期券=1, 回数券=2, プリペイドカード=3)
- Col C (KeyCol=3): 券種 (kenshuList)
- Col F (KeyCol=6): 連絡 (renrakuList)
- Col H (KeyCol=8): 往復区分 (oufukuList: 1=片道, 2=往復)
- Col K (KeyCol=11): 交替制 (koutaiList: 0=非該当, 1=該当)
- Col N (KeyCol=14): 非該当者認定簿出力区分 (higaitouList: 1=出力, 2=非出力)
- Col Q (KeyCol=17): エラーメッセージ (errorList)
#### Caches — Pre-rendered Dropdown Cache
- **Role**: Stores pre-built validation lists for named-range based dropdowns
- **Layout**: Each row is a pre-formatted "code:name" string used as named-range source
---
## 3. Cache Architecture
### 3.1 Cache Dictionary Keys
| Constant | Sheet | Structure | Key | Value |
|----------|-------|-----------|-----|-------|
| `CACHE_Z1` | Z1 | Dict(code → Array(name)) | transport code | Array(transport name) |
| `CACHE_Z2` | Z2 | Dict(code → Array(name)) | determination code | Array(display name) |
| `CACHE_Z3` | Z3 | Dict(code → Array(name)) | decision code | Array(display name) |
| `CACHE_Z4ROSEN` | Z4 | Dict(rosen → Dict(station → True)) | line name | Dict of all stations on that line |
| `CACHE_T1` | T1 | Dict(code → Array(name)) | ticket type code | Array(display name) |
| `CACHE_T2` | T2 | Dict(code → Array(all fields)) | ticket code | Array(C,D,E,F,G,H,I,J,K,L,M) |
| `CACHE_T3` | T3 | Dict(code → Array(all fields)) | card code | Array(C,D,E,F,G,H,I) |
| `CACHE_O1` | O1 | Dict(empNo → Array(addr1, addr2)) | employee number | Array(address1, address2) |
| `CACHE_O2` | O2 | Dict(code → Array(name)) | sender code | Array(official name) |
| `CACHE_O3` | O3 | Dict(code → Array(name)) | reason code | Array(display name) |
| `CACHE_M1` | M1 | Dict(code → Array(all cols)) | route code | Array(C,D,E,F,G,H,I,J,K,L,M,N) |
| `CACHE_M2` | M2 | Dict(code → Dict(ticketType → Array(detail))) | route code | Dict of ticket type → Array(I,J,K,L,M,N,O,P,Q,R) |
### 3.2 Special Enum Caches
| Constant | Source | Key | Value |
|----------|--------|-----|-------|
| `tokubetuList` | Enum sheet Col A | int (0-3) | name string |
| `kenshuList` | Enum sheet Col C | int | name string |
| `renrakuList` | Enum sheet Col F | int | name string |
| `oufukuList` | Enum sheet Col H | code | name string |
| `koutaiList` | Enum sheet Col K | code | name string |
| `higaitouList` | Enum sheet Col N | code | name string |
| `errorList` | Enum sheet Col Q | error code | error message |
### 3.3 m1KukanDCache (Special Derived Cache)
- **Type**: Dict(transportCode & "|" & fromStation → Dict(toStation → True))
- **Purpose**: Fast lookup of valid arrival stations given transport + departure
- **Derivation**: Built from M1 sheet at runtime: groups M1 rows by D(transport) + F(fromStation), keys are G(toStation)
- **Used by**: `BuildZ4StationToDropdown` → C1 M1 KukanD cascade
---
## 4. Module Architecture
### 4.1 Module Summary
| Module | Responsibility |
|--------|---------------|
| `Common_Constants` | Error code constants (1001-1009, 2001+) |
| `Common_Global_Cache` | All cache loading, refresh, and lookup. Sheet config definitions (RefreshSheetDict). Global Scripting.Dictionary objects. |
| `Common_Functions` | CSV helpers (GetCSVHeader, CleanCSVField), validation helpers (CheckRequired, CheckAlphanumeric, etc.), utility functions (FormatDateInput, GetCode, MakeSelect) |
| `Common_Selector` | Dropdown builder functions (BuildTransportList, BuildTodokeList, etc.) and Z4 cascade dropdowns (BuildZ4StationFromDropdown, BuildZ4StationToDropdown) |
| `Common_File_Utils` | CSV file read/write (ReadCSV, WriteCSV), BOM handling |
| `Common_Button` | Button action handlers: CSV_Import, Validation, CSV_Export, Sort, Filter, Fit. RunValidationSilent wrapper. |
| `Common_Shape` | Icon/shape alignment utilities (AlignIconsByCenter) |
### 4.2 Sheet Class Summary
| Class | Sheet | Key Methods | Notes |
|-------|-------|-------------|-------|
| `C1` | C1 | Worksheet_Change, FillAddressFromO1, FillTransportFromM1KukanD, FillDepartureFromM1KukanD, FillArrivalFromM1KukanD, FillKukanFromM1, FillCodeFromM2, ValidateRow | Main editor. No CSV import. |
| `M1` | M1 | Worksheet_Change, Validate, BuildM1StationDropdown | Route master with Z4 cascade dropdowns |
| `M2` | M2 | Worksheet_Change, Validate | Route detail master, ticket pricing |
| `T1` | T1 | Validate | Periodic pass name master |
| `T2` | T2 | Worksheet_Change, Validate | Frequency ticket master with ZeroFillCols feature |
| `T3` | T3 | Validate | Prepaid card master |
| `Z1` | Z1 | Validate | Transport type master |
| `Z2` | Z2 | Validate | Determination category master |
| `Z3` | Z3 | Validate | Monthly amount decision master |
| `Z4` | Z4 | Validate | Station name / line master |
| `O1` | O1 | Validate | Employee address master (empty stub) |
| `O2` | O2 | Validate | Sender list (empty stub) |
| `O3` | O3 | Validate | Notification reason master (empty stub) |
---
## 5. Data Flow
### 5.1 C1 Editing Flow (4 Kukan Sections)
```
User edits C (職員番号)
→ FillAddressFromO1: O1Cache lookup → populate I/J dropdowns
→ BuildTransportList → T/AB/AJ/AR dropdown (Z1Cache)
User selects T (交通機関)
→ BuildZ1StationDropdown → U/AC/AK/AS dropdown (Z1Cache)
User selects U (発)
→ BuildZ4StationFromDropdown → F column (from station) dropdown (Z4RosenCache filtered by transport)
OR: BuildM1KukanDDropdown → V/AD/AL/AT dropdown (m1KukanDCache: transport+from → to stations)
User selects S (区間コード)
→ FillKukanFromM1: M1Cache lookup → fills T/U/V/W/Y with pre-defined values
→ BuildM2Dropdown → W/AE/AM/AU dropdown (M2Cache by route code)
→ BuildM2CodeDropdown → X/AF/AN/AV dropdown (M2Cache by route+券種)
User selects W (券種)
→ BuildM2CodeDropdown → X/AF/AN/AV dropdown (M2Cache: route+券種 → code)
User edits X (コード)
→ FillCodeFromM2: M2Cache lookup → fills AF/AG/AH with M2 details
```
### 5.2 CSV Import Flow
```
CSV_Import_Button clicked
→ User selects CSV file
→ ReadCSV (Common_File_Utils): shift_jis decode, BOM strip
→ GetCSVHeader: extract header row
→ LoadLookup: build/refresh cache for target sheet
→ DO_CSV_Import: parse rows, write to sheet cells
→ RunValidationSilent: call Sheet.Validate per row
→ HandleError on validation failure
```
### 5.3 Validation Flow
```
RunValidationSilent(sheet) → for each data row
→ Sheet.Validate(ws, rowNum, lastDataRow)
→ On error: Err.Raise → caught by HandleError in caller
→ Returns error count
```
---
## 6. Key Constants
### 6.1 CACHE_ Constants (defined in Common_Global_Cache)
```
CACHE_Z4ROSEN = "Z4Rosen"
CACHE_T1 = "T1"
CACHE_T2 = "T2"
CACHE_T3 = "T3"
CACHE_O1 = "O1"
CACHE_O2 = "O2"
CACHE_O3 = "O3"
CACHE_M1 = "M1"
CACHE_M2 = "M2"
```
### 6.2 Validation Error Codes
```
1001: ERR_CACHE_NOT_FOUND
1002: ERR_CACHE_EMPTY
1003: ERR_VALIDATION_FAILED
1004: ERR_CONFIG_NOT_FOUND
1005: ERR_CONFIG_INVALID
1006: ERR_CONFIG_EMPTY_PARAM
1007: ERR_SHEET_MISSING
2001: ERR_VALIDATION
5001-5009: File/CSV errors
```
### 6.3 MakeSelect Format
All dropdown values use `code:name` format:
- Display: `"001:JR 東北線"` → MakeSelect("001", "JR 東北線")
- GetCode("001:JR 東北線") → "001"
- GetDisplay("001:JR 東北線") → "JR 東北線" (via mid/InStr)
---
## 7. Column Layout Reference
### C1 — 58 columns (C to BG)
| Column | Header | Type |
|--------|--------|------|
| C | 職員番号 | string(8) |
| D | 事実発生年月日 | date |
| E | 提出年月日 | date |
| F | 受理年月日 | date |
| G | 届出の事由コード | enum(O3) |
| H | 届出の備考 | string(40) |
| I | 住所1 | string |
| J | 住所2 | string |
| K | 運賃改正・法改正年月日 | date |
| L | 出勤予定日数 | number(2) |
| M | 往復区分 | enum(oufukuList) |
| N | 交替制 | enum(koutaiList) |
| O | 算出式 | string(80) |
| P | 自動車等使用距離 | number(3) |
| Q | 自動車等支給額 | number(6) |
| R | 自動車等駐車場代 | number(6) |
| S~Y | 区間1 (コード/交通/発/着/券種/コード/期間) | mixed |
| Z | 区間1支給開始年月 | date |
| AA~AG | 区間2 | mixed |
| AH | 区間2支給開始年月 | date |
| AI~AM | 区間3 | mixed |
| AN | 区間3支給開始年月 | date |
| AO~AS | 区間4 | mixed |
| AT | 区間4支給開始年月 | date |
| AU | 決定事項区分コード | enum(Z2) |
| AV | 非該当の理由 | string |
| AW | 非該当者認定簿出力区分 | enum(higaitouList) |
| AX | 非該当者認定簿出力区分 (actual) | enum |
| BB | 手当月額の決定区分コード | enum(Z3) |
| BC | 支給の始期 | date |
| BD | 備考 | string |
| BE | 所属コード | string |
| BF | 認定年月日 | date |
| BG | (各庁の長)官職コード | string |
---
## 8. Known Issues / Technical Debt
| # | Severity | Description | Status |
|---|----------|-------------|--------|
| 1 | High | M2.cls: `cacheVal` declared inside If block but used in Select Case outside it | Fixed |
| 2 | High | C1.cls: `kukanCol`, `kukanCode`, `kukanLetter` undeclared in Validate | Fixed |
| 3 | Medium | CJK comments in Common_Shape.bas and C1.cls | Fixed |
| 4 | Medium | Hardcoded path `D:\Project\upds7\vba\` in Import_modules.bas | Fixed |
| 5 | Medium | `sheetConfDict("Z4")` hardcoded key | Fixed |
| 6 | Medium | `sheetName:="M1"` hardcoded in Common_Shape | Fixed |
| 7 | Medium | M1.cls: `"M2"` hardcoded sheet reference | Fixed |
| 8 | Medium | `On Error Resume Next` in 3 cache lookup functions (Common_Global_Cache.bas) | Fixed |
| 9 | Low | T2.cls: CJK in comment `区分` | Fixed |
| 10 | Medium | O1/O2/O3 Validate stubs are empty | Not fixed |

View File

@@ -0,0 +1,184 @@
# tuk Project Audit Issues
Status: Open
Audit date: 2026-05-28
---
## Critical (Runtime Bugs)
### 1. M2.cls:341-356 — cacheVal used outside If block
**Severity:** 🔴 Critical
**File:** `sheet/M2.cls`
**Line:** 341-356
`cacheVal` is declared inside `If cache.Exists(code)` block but used in `Select Case` below regardless:
```vba
If cache.Exists(code) Then
Dim cacheVal As Variant: cacheVal = cache(code)
...
End If
Select Case kenshu
Case "2"
ws.Range("L" & rowNum).Value = Trim(cacheVal(1)) ' bug if code not in cache
```
Fix: move `Dim cacheVal As Variant` before the `If cache.Exists(code)` check.
---
### 2. C1.cls:984-997 — undeclared variables in Validate
**Severity:** 🔴 Critical
**File:** `sheet/C1.cls`
**Line:** 984-997
Three variables used without `Dim` in the kukan duplicate-check loop:
```vba
For kukanIdx = LBound(KUKAN_CODE_COLS) To UBound(KUKAN_CODE_COLS)
kukanCol = KUKAN_CODE_COLS(kukanIdx) ' not declared
kukanCode = Trim(Me.Cells(rowNum, kukanCol).Value)
...
kukanLetter = Split(Me.Cells(1, kukanCol).Address, "$")(1) ' not declared
```
Fix: add `Dim kukanCol As Long, kukanCode As String, kukanLetter As String` at the top of `Validate`.
---
## AGENTS.md Violations
### 3. CJK Comments in Code
**Severity:** 🟡 AGENTS.md violation
**Files:** `module/Common_Shape.bas`, `sheet/C1.cls`
| File | Line | Content |
|------|------|---------|
| `Common_Shape.bas` | 4 | `通用排版引擎(仅调整位置)` |
| `Common_Shape.bas` | 22 | `第一个图标左边对齐B3左边` |
| `Common_Shape.bas` | 44 | `你的专属调用入口` |
| `C1.cls` | 675 | `vals(1) = D列, vals(3) = F列, vals(4) = G列` |
Fix: translate to English per AGENTS.md English-only comments rule.
---
### 4. Hardcoded File Path in Import_modules
**Severity:** 🟡 AGENTS.md violation
**File:** `init_module/Import_modules.bas`
**Line:** 8
```vba
Const PROJECT_PATH As String = "D:\Project\upds7\vba\"
```
Fix: use a config-based path or a constant defined in `Common_Constants.bas`.
---
### 5. Hardcoded Sheet Name in Z4.cls
**Severity:** 🟡 AGENTS.md violation
**File:** `sheet/Z4.cls`
**Line:** 14
```vba
Set ws = ThisWorkbook.Worksheets("Z4")
```
Fix: use `CACHE_Z4` constant instead of the string literal.
---
### 6. Hardcoded "M1" in Common_Shape
**Severity:** 🟡 AGENTS.md violation
**File:** `module/Common_Shape.bas`
**Line:** 47
```vba
sheetName:="M1"
```
Fix: pass as parameter or use constant.
---
## Code Quality
### 7. O1/O2/O3 Validate Stubs
**Severity:** 🟠 Quality
**Files:** `sheet/O1.cls`, `sheet/O2.cls`, `sheet/O3.cls`
All three have `Validate` methods that are empty stubs — just `Exit Sub`. If validation runs on these sheets, all rows pass silently.
Fix: implement proper validation or confirm empty stubs are intentional.
---
### 8. On Error Resume Next — 7 instances
**Severity:** 🟠 Quality
**Files:** `Common_Global_Cache.bas`, `Common_Functions.bas`, `Common_Shape.bas`, `Test_Cache.bas`, `Import_modules.bas`
These silently swallow errors (e.g., "subscript out of range" for missing worksheets):
| File | Line | Target |
|------|------|--------|
| `Common_Global_Cache.bas` | 91 | `ThisWorkbook.Worksheets("M1")` |
| `Common_Global_Cache.bas` | 155 | `ThisWorkbook.Worksheets("M2")` |
| `Common_Global_Cache.bas` | 232 | `ThisWorkbook.Worksheets("M1")` |
| `Common_Functions.bas` | 101 | `ThisWorkbook.Worksheets(sheetName)` |
| `Common_Shape.bas` | 14 | `ThisWorkbook.Worksheets(sheetName)` |
| `Test_Cache.bas` | 25 | `ThisWorkbook.Worksheets("Test_Cache")` |
| `Import_modules.bas` | 82, 126 | `VBProject.VBComponents` |
Fix: use `On Error GoTo ErrHandler` pattern with `Err.Raise` for missing objects, or explicitly check worksheet existence before access.
---
### 9. Fixed Cell Reference in C1 Validate
**Severity:** 🟠 Quality
**File:** `sheet/C1.cls`
**Line:** 1000
```vba
Dim linkCellValue As String: linkCellValue = Me.Cells(3, "H").Value
```
Fixed address `Cells(3, "H")` used as a config switch. No constant or comment explaining its purpose.
Fix: add a named constant or sheetConf entry for this cell.
---
## Resolved / Correct
- All modules have `Option Explicit`
- All `Worksheet_Change` handlers disable `EnableEvents` and use `Finally:` pattern ✅
- All sheet classes have English module headers ✅
- T2 `FillZeroIfEmpty` uses `sheetConf("ZeroFillCols")` with letter strings ✅
- Z4 `LookupZ4RosenCache` uses F col for rosen, D col for station ✅
- `Common_Selector.BuildZ4StationToDropdown` iterates `stationFromDict.Keys` to exclude `stationFrom`
- No `Select`/`Selection`/`ActiveCell` patterns in sheet classes ✅
- All `Validate` methods in Z-series and T-series have proper checkResult pattern ✅
---
## Summary
| Severity | Count |
|----------|-------|
| 🔴 Critical | 2 |
| 🟡 AGENTS.md violation | 4 |
| 🟠 Code quality | 3 |
Priority: Fix #1#2#3#4

View File

@@ -5,9 +5,11 @@ Sub ImportModulesAndSheets_Safe()
Dim fso As Object Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject") Set fso = CreateObject("Scripting.FileSystemObject")
Const PROJECT_PATH As String = "D:\Project\upds7\vba\" Dim basePath As String: basePath = ThisWorkbook.Path
Const MODULE_PATH As String = PROJECT_PATH & "src\sh\tuk\module" If Right(basePath, 1) <> "\" Then basePath = basePath & "\"
Const SHEET_PATH As String = PROJECT_PATH & "src\sh\tuk\sheet" Const PROJECT_PATH As String = basePath
Const MODULE_PATH As String = basePath & "src\sh\tuk\module"
Const SHEET_PATH As String = basePath & "src\sh\tuk\sheet"
' --- Phase 1: Validation --- ' --- Phase 1: Validation ---
Debug.Print "[LOG] Starting validation phase..." Debug.Print "[LOG] Starting validation phase..."

View File

@@ -98,12 +98,9 @@ Function LoadLookup(ByVal sheetName As String, ByVal cacheName As String) As Obj
' --- obtain worksheet --- ' --- obtain worksheet ---
Dim ws As Worksheet Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets(sheetName)
On Error GoTo ErrHandler On Error GoTo ErrHandler
If ws Is Nothing Then Set ws = ThisWorkbook.Worksheets(sheetName)
Err.Raise ERR_SHEET_MISSING, "LoadLookup", "Worksheet '" & sheetName & "' not found." ' ws exists, continue
End If
Dim sheetConf As Object: Set sheetConf = sheetConfDict(cacheName) Dim sheetConf As Object: Set sheetConf = sheetConfDict(cacheName)
Dim startRow As Long: startRow = sheetConf("StartRow") Dim startRow As Long: startRow = sheetConf("StartRow")
@@ -335,12 +332,8 @@ Public Function FormatDateInput(ByVal inputStr As String) As String
Dim s As String: s = Trim(inputStr) Dim s As String: s = Trim(inputStr)
If s = "" Then Exit Function If s = "" Then Exit Function
' Only process pure digit strings ' Handle pure digit strings (YYYYMMDD / YYMMDD)
If Not IsNumeric(s) Then If IsNumeric(s) Then
FormatDateInput = inputStr
Exit Function
End If
Dim yearPart As String, monthPart As String, dayPart As String Dim yearPart As String, monthPart As String, dayPart As String
Dim dateStr As String Dim dateStr As String
@@ -359,14 +352,25 @@ Public Function FormatDateInput(ByVal inputStr As String) As String
Exit Function Exit Function
End If End If
' Build date string and validate
dateStr = yearPart & "-" & monthPart & "-" & dayPart dateStr = yearPart & "-" & monthPart & "-" & dayPart
If IsDate(dateStr) Then If IsDate(dateStr) Then
FormatDateInput = dateStr FormatDateInput = dateStr
Else Else
FormatDateInput = inputStr FormatDateInput = ""
End If End If
Exit Function
End If
' Handle non-numeric date strings (e.g. "2026/05", "2026/5/1", "2026-5-1")
If IsDate(s) Then
Dim d As Date: d = CDate(s)
FormatDateInput = Year(d) & "-" & Right("0" & Month(d), 2) & "-" & Right("0" & Day(d), 2)
Exit Function
End If
' Not a date - return empty string
FormatDateInput = ""
End Function End Function
'Check header edit protection 'Check header edit protection

View File

@@ -19,7 +19,8 @@ Public Const CACHE_T3 As String = "T3"
Public Const CACHE_O1 As String = "O1" Public Const CACHE_O1 As String = "O1"
Public Const CACHE_O2 As String = "O2" Public Const CACHE_O2 As String = "O2"
Public Const CACHE_O3 As String = "O3" Public Const CACHE_O3 As String = "O3"
Public Const CACHE_M1 As String = "M1"
Public Const CACHE_M2 As String = "M2"
Private sheetConfDict As Object Private sheetConfDict As Object
@@ -62,7 +63,7 @@ Public Sub RefreshCache(ByVal cacheName As String)
Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig() Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig()
If cacheName = "M1KukanDCache" Then If cacheName = "M1KukanDCache" Then
Set loadedData = LookupM1KukanCache() Set loadedData = LookupM1KukanCache()
ElseIf cacheName = "M2" Then ElseIf cacheName = CACHE_M2 Then
Set loadedData = LookupM2Cache() Set loadedData = LookupM2Cache()
ElseIf cacheName = CACHE_O1 Then ElseIf cacheName = CACHE_O1 Then
Set loadedData = LookupO1Cache() Set loadedData = LookupO1Cache()
@@ -88,12 +89,9 @@ Private Function LookupM1KukanCache()
On Error GoTo ErrHandler On Error GoTo ErrHandler
Dim ws As Worksheet Dim ws As Worksheet
On Error Resume Next Set ws = ThisWorkbook.Worksheets(CACHE_M1)
Set ws = ThisWorkbook.Worksheets("M1")
On Error GoTo ErrHandler
' ws exists, continue
Dim sheetConf As Object: Set sheetConf = sheetConfDict("M1") Dim sheetConf As Object: Set sheetConf = sheetConfDict(CACHE_M1)
Dim startRow As Long: startRow = sheetConf("StartRow") Dim startRow As Long: startRow = sheetConf("StartRow")
Dim lastRow As Long: lastRow = GetLastDataRowInRange(ws) Dim lastRow As Long: lastRow = GetLastDataRowInRange(ws)
If lastRow < startRow Then If lastRow < startRow Then
@@ -152,12 +150,9 @@ Private Function LookupM2Cache() As Object
On Error GoTo ErrHandler On Error GoTo ErrHandler
Dim ws As Worksheet Dim ws As Worksheet
On Error Resume Next Set ws = ThisWorkbook.Worksheets(CACHE_M2)
Set ws = ThisWorkbook.Worksheets("M2")
On Error GoTo ErrHandler
' ws exists, continue
Dim sheetConf As Object: Set sheetConf = sheetConfDict("M2") Dim sheetConf As Object: Set sheetConf = sheetConfDict(CACHE_M2)
Dim startRow As Long: startRow = sheetConf("StartRow") Dim startRow As Long: startRow = sheetConf("StartRow")
Dim lastRow As Long: lastRow = GetLastDataRowInRange(ws) Dim lastRow As Long: lastRow = GetLastDataRowInRange(ws)
If lastRow < startRow Then If lastRow < startRow Then
@@ -229,10 +224,7 @@ Private Function LookupO1Cache() As Object
On Error GoTo ErrHandler On Error GoTo ErrHandler
Dim ws As Worksheet Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets(CACHE_O1) Set ws = ThisWorkbook.Worksheets(CACHE_O1)
On Error GoTo ErrHandler
' ws exists, continue
Dim sheetConf As Object: Set sheetConf = sheetConfDict(CACHE_O1) Dim sheetConf As Object: Set sheetConf = sheetConfDict(CACHE_O1)
Dim startRow As Long: startRow = sheetConf("StartRow") Dim startRow As Long: startRow = sheetConf("StartRow")
@@ -289,7 +281,7 @@ End Function
' ============================================================ ' ============================================================
' Z4 Rosen Cache - nested dict for M1 E/F/H cascade dropdown ' Z4 Rosen Cache - nested dict for M1 E/F/H cascade dropdown
' Structure: { rosen [F]: { stationFrom [D]: [stationTo E, ...] } } ' Structure: { rosen [F]: { station [D]: True } }
' ============================================================ ' ============================================================
Private Function LookupZ4RosenCache() As Object Private Function LookupZ4RosenCache() As Object
Dim resultCache As Object Dim resultCache As Object
@@ -309,31 +301,25 @@ Private Function LookupZ4RosenCache() As Object
End If End If
Dim r As Long Dim r As Long
For r = startRow To lastRow Dim station As String
Dim rosen As String: rosen = Trim(ws.Cells(r, 6).Value) Dim rosen As String
Dim stationFrom As String: stationFrom = Trim(ws.Cells(r, 4).Value) Dim innerDict As Object
Dim stationTo As String: stationTo = Trim(ws.Cells(r, 5).Value)
If rosen = "" Or stationFrom = "" Then GoTo NextRow3 For r = startRow To lastRow
rosen = Trim(ws.Cells(r, 6).Value)
station = Trim(ws.Cells(r, 4).Value)
If rosen = "" Or station = "" Then GoTo NextRow3
If Not resultCache.Exists(rosen) Then If Not resultCache.Exists(rosen) Then
Dim innerDict As Object
Set innerDict = CreateObject("Scripting.Dictionary") Set innerDict = CreateObject("Scripting.Dictionary")
innerDict.CompareMode = vbTextCompare innerDict.CompareMode = vbTextCompare
resultCache.Add rosen, innerDict resultCache.Add rosen, innerDict
End If End If
Set innerDict = resultCache(rosen) Set innerDict = resultCache(rosen)
If Not innerDict.Exists(stationFrom) Then If Not innerDict.Exists(station) Then
Dim arr As Object innerDict.Add station, True
Set arr = CreateObject("Scripting.Dictionary")
arr.CompareMode = vbTextCompare
innerDict.Add stationFrom, arr
End If
Set arr = innerDict(stationFrom)
If stationTo <> "" And Not arr.Exists(stationTo) Then
arr.Add stationTo, True
End If End If
NextRow3: NextRow3:
@@ -387,7 +373,7 @@ Private Sub RefreshSheetDict()
sheetConf("FilterRow") = 6 sheetConf("FilterRow") = 6
sheetConf("KeyCol") = 3 sheetConf("KeyCol") = 3
sheetConf("ValueCols") = Array(3, 4, 5, 6, 7, 9, 12) sheetConf("ValueCols") = Array(3, 4, 5, 6, 7, 9, 12)
Set sheetConfDict("M1") = sheetConf Set sheetConfDict(CACHE_M1) = sheetConf
Debug.Print "RefreshSheetDict M1 ok." Debug.Print "RefreshSheetDict M1 ok."
' M2 ' M2
@@ -403,7 +389,7 @@ Private Sub RefreshSheetDict()
sheetConf("HeaderColumns") = Array("C", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R") sheetConf("HeaderColumns") = Array("C", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R")
sheetConf("AlwaysQuote") = False sheetConf("AlwaysQuote") = False
sheetConf("FilterRow") = 7 sheetConf("FilterRow") = 7
Set sheetConfDict("M2") = sheetConf Set sheetConfDict(CACHE_M2) = sheetConf
Debug.Print "RefreshSheetDict M2 ok." Debug.Print "RefreshSheetDict M2 ok."
' Z1 ' Z1
@@ -479,7 +465,7 @@ Private Sub RefreshSheetDict()
sheetConf("FilterRow") = 6 sheetConf("FilterRow") = 6
sheetConf("KeyCol") = 3 sheetConf("KeyCol") = 3
sheetConf("ValueCols") = Array(4) sheetConf("ValueCols") = Array(4)
Set sheetConfDict("Z4") = sheetConf Set sheetConfDict(CACHE_Z4) = sheetConf
Debug.Print "RefreshSheetDict Z4 ok." Debug.Print "RefreshSheetDict Z4 ok."
' T1 ' T1
@@ -517,6 +503,7 @@ Private Sub RefreshSheetDict()
sheetConf("FilterRow") = 6 sheetConf("FilterRow") = 6
sheetConf("KeyCol") = 3 sheetConf("KeyCol") = 3
sheetConf("ValueCols") = Array(4, 8, 9, 10, 11, 12, 13) sheetConf("ValueCols") = Array(4, 8, 9, 10, 11, 12, 13)
sheetConf("ZeroFillCols") = Array("H", "I", "J", "K", "L", "M")
Set sheetConfDict(CACHE_T2) = sheetConf Set sheetConfDict(CACHE_T2) = sheetConf
Debug.Print "RefreshSheetDict T2 ok." Debug.Print "RefreshSheetDict T2 ok."
@@ -695,15 +682,15 @@ Public Sub RefreshMasterCache()
End Sub End Sub
Public Sub RefreshKukanCache(ByVal sheetName As String) Public Sub RefreshKukanCache(ByVal sheetName As String)
If sheetName = "M1" Then If sheetName = CACHE_M1 Then
Call RefreshCache("M1") Call RefreshCache(CACHE_M1)
Call RefreshCache("M1KukanDCache") Call RefreshCache("M1KukanDCache")
Call RefreshCache(CACHE_Z4ROSEN) Call RefreshCache(CACHE_Z4ROSEN)
Call WriteCachesSheet("M1") Call WriteCachesSheet(CACHE_M1)
End If End If
If sheetName = "M2" Then If sheetName = CACHE_M2 Then
Call RefreshCache("M2") Call RefreshCache(CACHE_M2)
Call WriteCachesSheet("M2") Call WriteCachesSheet(CACHE_M2)
End If End If
End Sub End Sub
@@ -729,7 +716,7 @@ Public Sub WriteCachesSheet(ByVal cacheName As String)
Case CACHE_T3: colLetter = "G" Case CACHE_T3: colLetter = "G"
Case CACHE_O2: colLetter = "H" Case CACHE_O2: colLetter = "H"
Case CACHE_O3: colLetter = "I" Case CACHE_O3: colLetter = "I"
Case "M1": colLetter = "M" Case CACHE_M1: colLetter = "M"
Case Else: Exit Sub Case Else: Exit Sub
End Select End Select

View File

@@ -264,18 +264,17 @@ Public Sub BuildZ4StationToDropdown(ws As Worksheet, ByVal columnLetter As Strin
If Not z4RosenCache.Exists(rosen) Then Exit Sub If Not z4RosenCache.Exists(rosen) Then Exit Sub
Dim stationFromDict As Object: Set stationFromDict = z4RosenCache(rosen) Dim stationFromDict As Object: Set stationFromDict = z4RosenCache(rosen)
If Not stationFromDict.Exists(stationFrom) Then Exit Sub
Dim stationToDict As Object: Set stationToDict = stationFromDict(stationFrom)
Dim dropdownList As String: dropdownList = "" Dim dropdownList As String: dropdownList = ""
Dim stationTo As Variant Dim s As Variant
For Each stationTo In stationToDict.Keys For Each s In stationFromDict.Keys
If s <> stationFrom Then
If dropdownList = "" Then If dropdownList = "" Then
dropdownList = stationTo dropdownList = s
Else Else
dropdownList = dropdownList & "," & stationTo dropdownList = dropdownList & "," & s
End If End If
Next stationTo End If
Next s
If dropdownList = "" Then Exit Sub If dropdownList = "" Then Exit Sub

View File

@@ -1,7 +1,7 @@
Attribute VB_Name = "Common_Shape" Attribute VB_Name = "Common_Shape"
Option Explicit Option Explicit
' ================= 通用排版引擎(仅调整位置) ================= ' ================= Common Layout Engine (position only) =================
Public Sub AlignIconsByCenter(sheetName As String, anchorAddr As String, _ Public Sub AlignIconsByCenter(sheetName As String, anchorAddr As String, _
iconArr As Variant, gapPt As Double) iconArr As Variant, gapPt As Double)
@@ -10,16 +10,14 @@ Public Sub AlignIconsByCenter(sheetName As String, anchorAddr As String, _
Dim shp As Shape Dim shp As Shape
Dim i As Long Dim i As Long
Dim shapeCount As Long Dim shapeCount As Long
On Error GoTo ErrHandler
On Error Resume Next
Set ws = ThisWorkbook.Worksheets(sheetName) Set ws = ThisWorkbook.Worksheets(sheetName)
On Error GoTo 0 ' ws exists, continue
If ws Is Nothing Then Exit Sub
Set anchor = ws.Range(anchorAddr) Set anchor = ws.Range(anchorAddr)
shapeCount = UBound(iconArr) - LBound(iconArr) + 1 shapeCount = UBound(iconArr) - LBound(iconArr) + 1
' 第一个图标左边对齐B3左边 ' First icon left-aligns to B3 left edge
Dim curX As Double: curX = anchor.Left Dim curX As Double: curX = anchor.Left
Dim prevX As Double: prevX = 0 Dim prevX As Double: prevX = 0
Dim cy As Double: cy = anchor.Top + anchor.Height / 2 Dim cy As Double: cy = anchor.Top + anchor.Height / 2
@@ -39,12 +37,17 @@ Public Sub AlignIconsByCenter(sheetName As String, anchorAddr As String, _
curX = curX + shp.Width + gapPt curX = curX + shp.Width + gapPt
Next i Next i
Application.ScreenUpdating = True Application.ScreenUpdating = True
Exit Sub
ErrHandler:
MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, "AlignIconsByCenter"
Application.ScreenUpdating = True
End Sub End Sub
' ================= 你的专属调用入口 ================= ' ================= Entry point =================
Sub RunAlignForMySheet() Sub RunAlignForMySheet()
AlignIconsByCenter _ AlignIconsByCenter _
sheetName:="M1", _ sheetName:=CACHE_M1, _
anchorAddr:="B3", _ anchorAddr:="B3", _
iconArr:=Array("input", "check", "output", "sort", "filter", "fit", "load"), _ iconArr:=Array("input", "check", "output", "sort", "filter", "fit", "load"), _
gapPt:=10 gapPt:=10

View File

@@ -157,10 +157,16 @@ Private Sub Worksheet_Change(ByVal Target As Range)
idx = GetIdx(Target.Column, DATE_COLS) idx = GetIdx(Target.Column, DATE_COLS)
If idx >= 0 Then If idx >= 0 Then
Dim cellDate As Range Dim cellDate As Range
Dim formattedDate As String
For Each cellDate In Target For Each cellDate In Target
If Trim(cellDate.Value) <> "" Then If Trim(cellDate.Value) <> "" Then
Dim formattedDate As String: formattedDate = FormatDateInput(cellDate.Value) formattedDate = FormatDateInput(cellDate.Value)
cellDate.Value = FormatDateInput(formattedDate) If formattedDate = "" Then
' Invalid date input - clear cell and show message
cellDate.Value = ""
MsgBox "Please enter a valid date (YYYY-MM-DD or YYYY/MM/DD)", vbExclamation, "Invalid Date"
Else
cellDate.Value = formattedDate
If cellDate.Column = 5 Then If cellDate.Column = 5 Then
Dim fCell As Range: Set fCell = Me.Cells(cellDate.Row, 6) Dim fCell As Range: Set fCell = Me.Cells(cellDate.Row, 6)
If Trim(fCell.Value) = "" Then If Trim(fCell.Value) = "" Then
@@ -168,6 +174,7 @@ Private Sub Worksheet_Change(ByVal Target As Range)
End If End If
End If End If
End If End If
End If
Next Next
End If End If
@@ -672,7 +679,7 @@ Private Function FindKukanCodeByStation(ByVal rowNum As Long, ByVal transportCol
Dim code As Variant Dim code As Variant
For Each code In m1Cache.Keys For Each code In m1Cache.Keys
Dim vals As Variant: vals = m1Cache(code) Dim vals As Variant: vals = m1Cache(code)
' vals(1) = D, vals(3) = F, vals(4) = G ' vals(1) = D col, vals(3) = F col, vals(4) = G col
If vals(1) = transportKbn And vals(3) = stationFrom And vals(4) = stationTo Then If vals(1) = transportKbn And vals(3) = stationFrom And vals(4) = stationTo Then
FindKukanCodeByStation = code FindKukanCodeByStation = code
Exit Function Exit Function
@@ -795,12 +802,15 @@ Public Sub Validate(ws As Worksheet, ByVal rowNum As Long, ByVal lastDataRow As
Dim colIndex As Variant Dim colIndex As Variant
For Each colIndex In DATE_COLS() For Each colIndex In DATE_COLS()
Dim cellDate As String: cellDate = Trim(Me.Cells(rowNum, colIndex).Value) Dim cellDate As String: cellDate = Trim(Me.Cells(rowNum, colIndex).Value)
If cellDate <> "" And Not IsDate(cellDate) Then If cellDate <> "" Then
' Require full YYYY-MM-DD format (output of FormatDateInput)
If Len(cellDate) <> 10 Or Mid(cellDate, 5, 1) <> "-" Or Mid(cellDate, 8, 1) <> "-" Then
Dim letter As String: letter = Split(Me.Cells(1, colIndex).Address, "$")(1) Dim letter As String: letter = Split(Me.Cells(1, colIndex).Address, "$")(1)
Me.Cells(rowNum, errorCol).Value = GetErrorMsg("E001", letter & rowNum) Me.Cells(rowNum, errorCol).Value = GetErrorMsg("E001", letter & rowNum)
Me.Range(letter & rowNum).Interior.Color = RGB(255, 0, 0) Me.Range(letter & rowNum).Interior.Color = RGB(255, 0, 0)
Exit Sub Exit Sub
End If End If
End If
Next colIndex Next colIndex
' validate number ' validate number

View File

@@ -51,20 +51,19 @@ Private Sub Worksheet_Change(ByVal Target As Range)
If z4Rosen.Exists(kikanName) Then If z4Rosen.Exists(kikanName) Then
Call BuildZ4StationFromDropdown(Me, "F", cellD.Row, kikanName) Call BuildZ4StationFromDropdown(Me, "F", cellD.Row, kikanName)
Dim fromStations As Object: Set fromStations = z4Rosen(kikanName) Dim stations As Object: Set stations = z4Rosen(kikanName)
Dim fromCell As Range: Set fromCell = Me.Cells(cellD.Row, 6) Dim fromCell As Range: Set fromCell = Me.Cells(cellD.Row, 6)
Dim fromStation As String: fromStation = Trim(fromCell.Value) Dim fromStation As String: fromStation = Trim(fromCell.Value)
If Not fromStations.Exists(fromStation) Or fromStation = "" Then If Not stations.Exists(fromStation) Or fromStation = "" Then
fromCell.ClearContents fromCell.ClearContents
Me.Cells(cellD.Row, 7).ClearContents Me.Cells(cellD.Row, 7).ClearContents
Me.Cells(cellD.Row, 7).Validation.Delete Me.Cells(cellD.Row, 7).Validation.Delete
Else Else
Call BuildZ4StationToDropdown(Me, "G", cellD.Row, kikanName, fromStation) Call BuildZ4StationToDropdown(Me, "G", cellD.Row, kikanName, fromStation)
Dim toStations As Object: Set toStations = fromStations(fromStation)
Dim toCell As Range: Set toCell = Me.Cells(cellD.Row, 7) Dim toCell As Range: Set toCell = Me.Cells(cellD.Row, 7)
Dim toStation As String: toStation = Trim(toCell.Value) Dim toStation As String: toStation = Trim(toCell.Value)
If Not toStations.Exists(fromStation) Or toStation = "" Then If Not stations.Exists(toStation) Or toStation = "" Then
toCell.ClearContents toCell.ClearContents
End If End If
End If End If
@@ -264,9 +263,9 @@ Private Sub ValidateWarn(ws As Worksheet, ByVal lastDataRow As Long)
' Get M2 sheet kukan code list directly ' Get M2 sheet kukan code list directly
Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig() Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig()
Dim m2SheetConf As Object: Set m2SheetConf = sheetConfDict("M2") Dim m2SheetConf As Object: Set m2SheetConf = sheetConfDict(CACHE_M2)
Dim m2StartRow As Long: m2StartRow = m2SheetConf("StartRow") Dim m2StartRow As Long: m2StartRow = m2SheetConf("StartRow")
Dim wsM2 As Worksheet: Set wsM2 = ThisWorkbook.Worksheets("M2") Dim wsM2 As Worksheet: Set wsM2 = ThisWorkbook.Worksheets(CACHE_M2)
Dim lastRowM2 As Long: lastRowM2 = GetLastDataRowInRange(wsM2) Dim lastRowM2 As Long: lastRowM2 = GetLastDataRowInRange(wsM2)
If lastRowM2 < m2StartRow Then If lastRowM2 < m2StartRow Then
exitMsg = "M2 sheet has no data" exitMsg = "M2 sheet has no data"

View File

@@ -23,6 +23,7 @@ Private Sub Worksheet_Change(ByVal Target As Range)
If Trim(cell.Value) = "" Then If Trim(cell.Value) = "" Then
Call ClearDataRow(Me, cell.Row) Call ClearDataRow(Me, cell.Row)
Else Else
Call FillZeroIfEmpty(Me, cell.Row)
Call BuildDisplayDropdown(Me, cell.Row) Call BuildDisplayDropdown(Me, cell.Row)
End If End If
Next Next
@@ -47,6 +48,20 @@ Finally:
Application.EnableEvents = True Application.EnableEvents = True
End Sub End Sub
' Fill H~M with "0" if they are empty when C column (kubun/category) is edited
Private Sub FillZeroIfEmpty(ws As Worksheet, ByVal rowNum As Long)
Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig()
Dim sheetConf As Object: Set sheetConf = sheetConfDict(ws.CodeName)
Dim zeroFillCols As Variant: zeroFillCols = sheetConf("ZeroFillCols")
Dim colLetter As Variant
For Each colLetter In zeroFillCols
If Trim(ws.Range(colLetter & rowNum).Value) = "" Then
ws.Range(colLetter & rowNum).Value = "0"
End If
Next colLetter
End Sub
' Prevent insert/delete row in header area ' Prevent insert/delete row in header area
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean) Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig() Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig()