diff --git a/AGENTS.md b/AGENTS.md index d333cc4..3f5223f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,13 +25,46 @@ - ❌ Forbidden: hardcoded file paths or connection strings. Use config sheet or constants module - ❌ 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/ AGENTS.md, README.md, .gitignore, LICENSE 通勤手当テンプレート2026xxxx.xlsm (latest date version) 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) src/sh/ juk/ address module @@ -55,13 +88,13 @@ vba/ M2.cls (400 lines) - section detail master O1.cls (5 lines) - address master 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 T2.cls (114 lines) - ticket master T3.cls (74 lines) - master 246 Z1.cls (64 lines) - transport master (222) Z2.cls (54 lines) - decision master (223) 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 \ No newline at end of file diff --git a/documents/Tukin_C1_Action.md b/documents/Tukin_C1_Action.md deleted file mode 100644 index e044738..0000000 --- a/documents/Tukin_C1_Action.md +++ /dev/null @@ -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` | 区間コード+券種 → コード | \ No newline at end of file diff --git a/documents/Tukin_C1_Mapping.md b/documents/Tukin_C1_Mapping.md deleted file mode 100644 index 6928e6d..0000000 --- a/documents/Tukin_C1_Mapping.md +++ /dev/null @@ -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列| -|--------|--------| -|ヘッダ|エラーメッセージ| -|データ型|文字列| \ No newline at end of file diff --git a/documents/Tukin_Cache_Mapping.md b/documents/Tukin_Cache_Mapping.md deleted file mode 100644 index 9e1dc54..0000000 --- a/documents/Tukin_Cache_Mapping.md +++ /dev/null @@ -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列| -|--------|--------|--------| -|ヘッダ|区分|官職名称| \ No newline at end of file diff --git a/documents/Tukin_Design_Document.md b/documents/Tukin_Design_Document.md new file mode 100644 index 0000000..4efb865 --- /dev/null +++ b/documents/Tukin_Design_Document.md @@ -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 | diff --git a/src/sh/tuk/AUDIT_ISSUES_2026_05_28.md b/src/sh/tuk/AUDIT_ISSUES_2026_05_28.md new file mode 100644 index 0000000..c6e8101 --- /dev/null +++ b/src/sh/tuk/AUDIT_ISSUES_2026_05_28.md @@ -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 \ No newline at end of file diff --git a/src/sh/tuk/init_module/Import_modules.bas b/src/sh/tuk/init_module/Import_modules.bas index a2886ce..290fcaa 100644 --- a/src/sh/tuk/init_module/Import_modules.bas +++ b/src/sh/tuk/init_module/Import_modules.bas @@ -5,9 +5,11 @@ Sub ImportModulesAndSheets_Safe() Dim fso As Object Set fso = CreateObject("Scripting.FileSystemObject") - Const PROJECT_PATH As String = "D:\Project\upds7\vba\" - Const MODULE_PATH As String = PROJECT_PATH & "src\sh\tuk\module" - Const SHEET_PATH As String = PROJECT_PATH & "src\sh\tuk\sheet" + Dim basePath As String: basePath = ThisWorkbook.Path + If Right(basePath, 1) <> "\" Then basePath = basePath & "\" + 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 --- Debug.Print "[LOG] Starting validation phase..." diff --git a/src/sh/tuk/module/Common_Functions.bas b/src/sh/tuk/module/Common_Functions.bas index 93c3731..b2f3044 100644 --- a/src/sh/tuk/module/Common_Functions.bas +++ b/src/sh/tuk/module/Common_Functions.bas @@ -98,12 +98,9 @@ Function LoadLookup(ByVal sheetName As String, ByVal cacheName As String) As Obj ' --- obtain worksheet --- Dim ws As Worksheet - On Error Resume Next - Set ws = ThisWorkbook.Worksheets(sheetName) On Error GoTo ErrHandler - If ws Is Nothing Then - Err.Raise ERR_SHEET_MISSING, "LoadLookup", "Worksheet '" & sheetName & "' not found." - End If + Set ws = ThisWorkbook.Worksheets(sheetName) + ' ws exists, continue Dim sheetConf As Object: Set sheetConf = sheetConfDict(cacheName) Dim startRow As Long: startRow = sheetConf("StartRow") @@ -334,39 +331,46 @@ End Function Public Function FormatDateInput(ByVal inputStr As String) As String Dim s As String: s = Trim(inputStr) If s = "" Then Exit Function - - ' Only process pure digit strings - If Not IsNumeric(s) Then - FormatDateInput = inputStr + + ' Handle pure digit strings (YYYYMMDD / YYMMDD) + If IsNumeric(s) Then + Dim yearPart As String, monthPart As String, dayPart As String + Dim dateStr As String + + If Len(s) = 8 Then + ' YYYYMMDD format + yearPart = Left(s, 4) + monthPart = Mid(s, 5, 2) + dayPart = Right(s, 2) + ElseIf Len(s) = 6 Then + ' YYMMDD format - add 20 prefix + yearPart = "20" & Left(s, 2) + monthPart = Mid(s, 3, 2) + dayPart = Right(s, 2) + Else + FormatDateInput = inputStr + Exit Function + End If + + dateStr = yearPart & "-" & monthPart & "-" & dayPart + + If IsDate(dateStr) Then + FormatDateInput = dateStr + Else + FormatDateInput = "" + End If Exit Function End If - - Dim yearPart As String, monthPart As String, dayPart As String - Dim dateStr As String - - If Len(s) = 8 Then - ' YYYYMMDD format - yearPart = Left(s, 4) - monthPart = Mid(s, 5, 2) - dayPart = Right(s, 2) - ElseIf Len(s) = 6 Then - ' YYMMDD format - add 20 prefix - yearPart = "20" & Left(s, 2) - monthPart = Mid(s, 3, 2) - dayPart = Right(s, 2) - Else - FormatDateInput = inputStr + + ' 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 - - ' Build date string and validate - dateStr = yearPart & "-" & monthPart & "-" & dayPart - - If IsDate(dateStr) Then - FormatDateInput = dateStr - Else - FormatDateInput = inputStr - End If + + ' Not a date - return empty string + FormatDateInput = "" End Function 'Check header edit protection diff --git a/src/sh/tuk/module/Common_Global_Cache.bas b/src/sh/tuk/module/Common_Global_Cache.bas index 821c7b7..de23a3b 100644 --- a/src/sh/tuk/module/Common_Global_Cache.bas +++ b/src/sh/tuk/module/Common_Global_Cache.bas @@ -19,7 +19,8 @@ Public Const CACHE_T3 As String = "T3" Public Const CACHE_O1 As String = "O1" Public Const CACHE_O2 As String = "O2" 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 @@ -62,7 +63,7 @@ Public Sub RefreshCache(ByVal cacheName As String) Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig() If cacheName = "M1KukanDCache" Then Set loadedData = LookupM1KukanCache() - ElseIf cacheName = "M2" Then + ElseIf cacheName = CACHE_M2 Then Set loadedData = LookupM2Cache() ElseIf cacheName = CACHE_O1 Then Set loadedData = LookupO1Cache() @@ -88,12 +89,9 @@ Private Function LookupM1KukanCache() On Error GoTo ErrHandler Dim ws As Worksheet - On Error Resume Next - Set ws = ThisWorkbook.Worksheets("M1") - On Error GoTo ErrHandler - ' ws exists, continue + Set ws = ThisWorkbook.Worksheets(CACHE_M1) - 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 lastRow As Long: lastRow = GetLastDataRowInRange(ws) If lastRow < startRow Then @@ -152,12 +150,9 @@ Private Function LookupM2Cache() As Object On Error GoTo ErrHandler Dim ws As Worksheet - On Error Resume Next - Set ws = ThisWorkbook.Worksheets("M2") - On Error GoTo ErrHandler - ' ws exists, continue + Set ws = ThisWorkbook.Worksheets(CACHE_M2) - 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 lastRow As Long: lastRow = GetLastDataRowInRange(ws) If lastRow < startRow Then @@ -229,10 +224,7 @@ Private Function LookupO1Cache() As Object On Error GoTo ErrHandler Dim ws As Worksheet - On Error Resume Next Set ws = ThisWorkbook.Worksheets(CACHE_O1) - On Error GoTo ErrHandler - ' ws exists, continue Dim sheetConf As Object: Set sheetConf = sheetConfDict(CACHE_O1) 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 -' Structure: { rosen [F]: { stationFrom [D]: [stationTo E, ...] } } +' Structure: { rosen [F]: { station [D]: True } } ' ============================================================ Private Function LookupZ4RosenCache() As Object Dim resultCache As Object @@ -309,31 +301,25 @@ Private Function LookupZ4RosenCache() As Object End If Dim r As Long - For r = startRow To lastRow - Dim rosen As String: rosen = Trim(ws.Cells(r, 6).Value) - Dim stationFrom As String: stationFrom = Trim(ws.Cells(r, 4).Value) - Dim stationTo As String: stationTo = Trim(ws.Cells(r, 5).Value) + Dim station As String + Dim rosen As String + Dim innerDict As Object - 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 - Dim innerDict As Object Set innerDict = CreateObject("Scripting.Dictionary") innerDict.CompareMode = vbTextCompare resultCache.Add rosen, innerDict End If Set innerDict = resultCache(rosen) - If Not innerDict.Exists(stationFrom) Then - Dim arr As Object - 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 + If Not innerDict.Exists(station) Then + innerDict.Add station, True End If NextRow3: @@ -387,7 +373,7 @@ Private Sub RefreshSheetDict() sheetConf("FilterRow") = 6 sheetConf("KeyCol") = 3 sheetConf("ValueCols") = Array(3, 4, 5, 6, 7, 9, 12) - Set sheetConfDict("M1") = sheetConf + Set sheetConfDict(CACHE_M1) = sheetConf Debug.Print "RefreshSheetDict M1 ok." ' M2 @@ -403,7 +389,7 @@ Private Sub RefreshSheetDict() sheetConf("HeaderColumns") = Array("C", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R") sheetConf("AlwaysQuote") = False sheetConf("FilterRow") = 7 - Set sheetConfDict("M2") = sheetConf + Set sheetConfDict(CACHE_M2) = sheetConf Debug.Print "RefreshSheetDict M2 ok." ' Z1 @@ -479,7 +465,7 @@ Private Sub RefreshSheetDict() sheetConf("FilterRow") = 6 sheetConf("KeyCol") = 3 sheetConf("ValueCols") = Array(4) - Set sheetConfDict("Z4") = sheetConf + Set sheetConfDict(CACHE_Z4) = sheetConf Debug.Print "RefreshSheetDict Z4 ok." ' T1 @@ -517,6 +503,7 @@ Private Sub RefreshSheetDict() sheetConf("FilterRow") = 6 sheetConf("KeyCol") = 3 sheetConf("ValueCols") = Array(4, 8, 9, 10, 11, 12, 13) + sheetConf("ZeroFillCols") = Array("H", "I", "J", "K", "L", "M") Set sheetConfDict(CACHE_T2) = sheetConf Debug.Print "RefreshSheetDict T2 ok." @@ -695,15 +682,15 @@ Public Sub RefreshMasterCache() End Sub Public Sub RefreshKukanCache(ByVal sheetName As String) - If sheetName = "M1" Then - Call RefreshCache("M1") + If sheetName = CACHE_M1 Then + Call RefreshCache(CACHE_M1) Call RefreshCache("M1KukanDCache") Call RefreshCache(CACHE_Z4ROSEN) - Call WriteCachesSheet("M1") + Call WriteCachesSheet(CACHE_M1) End If - If sheetName = "M2" Then - Call RefreshCache("M2") - Call WriteCachesSheet("M2") + If sheetName = CACHE_M2 Then + Call RefreshCache(CACHE_M2) + Call WriteCachesSheet(CACHE_M2) End If End Sub @@ -729,7 +716,7 @@ Public Sub WriteCachesSheet(ByVal cacheName As String) Case CACHE_T3: colLetter = "G" Case CACHE_O2: colLetter = "H" Case CACHE_O3: colLetter = "I" - Case "M1": colLetter = "M" + Case CACHE_M1: colLetter = "M" Case Else: Exit Sub End Select diff --git a/src/sh/tuk/module/Common_Selector.bas b/src/sh/tuk/module/Common_Selector.bas index 7b93ada..d0cac18 100644 --- a/src/sh/tuk/module/Common_Selector.bas +++ b/src/sh/tuk/module/Common_Selector.bas @@ -264,18 +264,17 @@ Public Sub BuildZ4StationToDropdown(ws As Worksheet, ByVal columnLetter As Strin If Not z4RosenCache.Exists(rosen) Then Exit Sub 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 stationTo As Variant - For Each stationTo In stationToDict.Keys - If dropdownList = "" Then - dropdownList = stationTo - Else - dropdownList = dropdownList & "," & stationTo + Dim s As Variant + For Each s In stationFromDict.Keys + If s <> stationFrom Then + If dropdownList = "" Then + dropdownList = s + Else + dropdownList = dropdownList & "," & s + End If End If - Next stationTo + Next s If dropdownList = "" Then Exit Sub diff --git a/src/sh/tuk/module/Common_Shape.bas b/src/sh/tuk/module/Common_Shape.bas index 82ea63c..7a53fdb 100644 --- a/src/sh/tuk/module/Common_Shape.bas +++ b/src/sh/tuk/module/Common_Shape.bas @@ -1,7 +1,7 @@ Attribute VB_Name = "Common_Shape" Option Explicit -' ================= 通用排版引擎(仅调整位置) ================= +' ================= Common Layout Engine (position only) ================= Public Sub AlignIconsByCenter(sheetName As String, anchorAddr As String, _ iconArr As Variant, gapPt As Double) @@ -10,16 +10,14 @@ Public Sub AlignIconsByCenter(sheetName As String, anchorAddr As String, _ Dim shp As Shape Dim i As Long Dim shapeCount As Long - - On Error Resume Next + On Error GoTo ErrHandler Set ws = ThisWorkbook.Worksheets(sheetName) - On Error GoTo 0 - If ws Is Nothing Then Exit Sub + ' ws exists, continue Set anchor = ws.Range(anchorAddr) shapeCount = UBound(iconArr) - LBound(iconArr) + 1 - ' 第一个图标左边对齐B3左边 + ' First icon left-aligns to B3 left edge Dim curX As Double: curX = anchor.Left Dim prevX As Double: prevX = 0 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 Next i Application.ScreenUpdating = True + Exit Sub + +ErrHandler: + MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, "AlignIconsByCenter" + Application.ScreenUpdating = True End Sub -' ================= 你的专属调用入口 ================= +' ================= Entry point ================= Sub RunAlignForMySheet() AlignIconsByCenter _ - sheetName:="M1", _ + sheetName:=CACHE_M1, _ anchorAddr:="B3", _ iconArr:=Array("input", "check", "output", "sort", "filter", "fit", "load"), _ gapPt:=10 diff --git a/src/sh/tuk/sheet/C1.cls b/src/sh/tuk/sheet/C1.cls index b287e73..c1e452b 100644 --- a/src/sh/tuk/sheet/C1.cls +++ b/src/sh/tuk/sheet/C1.cls @@ -157,14 +157,21 @@ Private Sub Worksheet_Change(ByVal Target As Range) idx = GetIdx(Target.Column, DATE_COLS) If idx >= 0 Then Dim cellDate As Range + Dim formattedDate As String For Each cellDate In Target If Trim(cellDate.Value) <> "" Then - Dim formattedDate As String: formattedDate = FormatDateInput(cellDate.Value) - cellDate.Value = FormatDateInput(formattedDate) - If cellDate.Column = 5 Then - Dim fCell As Range: Set fCell = Me.Cells(cellDate.Row, 6) - If Trim(fCell.Value) = "" Then - fCell.Value = formattedDate + formattedDate = FormatDateInput(cellDate.Value) + 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 + Dim fCell As Range: Set fCell = Me.Cells(cellDate.Row, 6) + If Trim(fCell.Value) = "" Then + fCell.Value = formattedDate + End If End If End If End If @@ -672,7 +679,7 @@ Private Function FindKukanCodeByStation(ByVal rowNum As Long, ByVal transportCol Dim code As Variant For Each code In m1Cache.Keys 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 FindKukanCodeByStation = code Exit Function @@ -791,15 +798,18 @@ Public Sub Validate(ws As Worksheet, ByVal rowNum As Long, ByVal lastDataRow As End If Next col - ' validate date + ' validate date Dim colIndex As Variant For Each colIndex In DATE_COLS() Dim cellDate As String: cellDate = Trim(Me.Cells(rowNum, colIndex).Value) - If cellDate <> "" And Not IsDate(cellDate) Then - Dim letter As String: letter = Split(Me.Cells(1, colIndex).Address, "$")(1) - Me.Cells(rowNum, errorCol).Value = GetErrorMsg("E001", letter & rowNum) - Me.Range(letter & rowNum).Interior.Color = RGB(255, 0, 0) - Exit Sub + 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) + Me.Cells(rowNum, errorCol).Value = GetErrorMsg("E001", letter & rowNum) + Me.Range(letter & rowNum).Interior.Color = RGB(255, 0, 0) + Exit Sub + End If End If Next colIndex diff --git a/src/sh/tuk/sheet/M1.cls b/src/sh/tuk/sheet/M1.cls index f788ccf..d6b695b 100644 --- a/src/sh/tuk/sheet/M1.cls +++ b/src/sh/tuk/sheet/M1.cls @@ -51,20 +51,19 @@ Private Sub Worksheet_Change(ByVal Target As Range) If z4Rosen.Exists(kikanName) Then 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 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 Me.Cells(cellD.Row, 7).ClearContents Me.Cells(cellD.Row, 7).Validation.Delete Else 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 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 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 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 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) If lastRowM2 < m2StartRow Then exitMsg = "M2 sheet has no data" diff --git a/src/sh/tuk/sheet/T2.cls b/src/sh/tuk/sheet/T2.cls index 3734f9b..81fd8e4 100644 --- a/src/sh/tuk/sheet/T2.cls +++ b/src/sh/tuk/sheet/T2.cls @@ -23,6 +23,7 @@ Private Sub Worksheet_Change(ByVal Target As Range) If Trim(cell.Value) = "" Then Call ClearDataRow(Me, cell.Row) Else + Call FillZeroIfEmpty(Me, cell.Row) Call BuildDisplayDropdown(Me, cell.Row) End If Next @@ -47,6 +48,20 @@ Finally: Application.EnableEvents = True 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 Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean) Dim sheetConfDict As Object: Set sheetConfDict = GetSheetConfig() diff --git a/通勤手当テンプレート20260525.xlsm b/通勤手当テンプレート20260525.xlsm index 41b372f..ff90b03 100644 Binary files a/通勤手当テンプレート20260525.xlsm and b/通勤手当テンプレート20260525.xlsm differ