作付け計画が正しく表示されない #10

Closed
opened 2026-04-05 08:14:05 +00:00 by akira · 4 comments
Owner

現在の状態(なぜOpenか)

調査開始

次にすること(Next Action)

不具合の原因を特定

ブロック要因

なし

問題の概要

http://localhost:3000/allocationにアクセスしたとき、作付け計画が表示されなかった
年度を色々変更していたら、表示されるようになったが、どこの年度にしても、同じデータが表示されるようになった

再現手順

期待する動作

実際の動作

関連

## 現在の状態(なぜOpenか) 調査開始 ## 次にすること(Next Action) 不具合の原因を特定 ## ブロック要因 なし --- ## 問題の概要 http://localhost:3000/allocationにアクセスしたとき、作付け計画が表示されなかった。 年度を色々変更していたら、表示されるようになったが、どこの年度にしても、同じデータが表示されるようになった ## 再現手順 ## 期待する動作 ## 実際の動作 ## 関連
Author
Owner

Claude Codeの見解

でもなぜ、allocationYear=2027なのに2027年のデータじゃなくて2026年のデータが見えるのですか?
Read page.tsx (lines 644-663)
年度セレクタ自体は問題なさそうです。別の角度で確認します。

Bash Check actual plan year values in DB
IN
docker exec keinasystem_db psql -U keinasystem -d keinasystem -c "SELECT id, field_id, year, variety_id FROM plans_plan ORDER BY year, field_id LIMIT 10;"

OUT
id | field_id | year | variety_id
----+----------+------+------------
19 | 3 | 2025 | 20
20 | 4 | 2025 | 14
21 | 5 | 2025 | 14
22 | 6 | 2025 | 16
23 | 7 | 2025 | 16
24 | 8 | 2025 | 20
25 | 9 | 2025 | 16
26 | 10 | 2025 | 22
27 | 11 | 2025 | 14
28 | 12 | 2025 | 22
(10 rows)
Bash Check plan ID ranges by year
IN
docker exec keinasystem_db psql -U keinasystem -d keinasystem -c "SELECT MIN(id), MAX(id), year FROM plans_plan GROUP BY year;"

OUT
min | max | year
-----+-----+------
40 | 78 | 2026
1 | 39 | 2025
(2 rows)
Bash Check copy from previous year logic
IN
grep -n "handleCopyFromPreviousYear|copy.*year|year.*copy|copyFrom" /home/akira/develop/keinasystem/frontend/src/app/allocation/page.tsx | head -20

OUT
461: const handleCopyFromPreviousYear = async () => {
467: const res = await api.post('/plans/copy_from_previous_year/', {
653: onClick={handleCopyFromPreviousYear}
Read page.tsx (lines 462-481)
Bash Find copy_from_previous_year view
IN
grep -n "copy_from_previous_year" /home/akira/develop/keinasystem/backend/apps/plans/views.py

OUT
79: def copy_from_previous_year(self, request):
Read views.py (lines 80-119)
わかりました。

原因はこれです。

本番サーバーで「2027年度を表示した状態で前年度コピー」を実行すると:

from_year = 2026, to_year = 2027
つまり 2026年のデータが2027年としてコピーされた 状態になっています。

本番DBには:

2026年の実データ(39件)
2027年にコピーされた2026年データ(39件)
が両方存在していたはずです。

ローカルのDBには本番からリストアした時点のデータが入っているので、確認します。

Bash Check all plan years in local DB
IN
docker exec keinasystem_db psql -U keinasystem -d keinasystem -c "SELECT year, COUNT(*) FROM plans_plan GROUP BY year ORDER BY year;"

OUT
year | count
------+-------
2025 | 39
2026 | 39
(2 rows)
ローカルには2027年データがないので、本番でその操作が起きていたとすればリストア後に2027年データは削除済みか、別の操作だったことになります。

整理すると:

本番ブラウザで localStorage.allocationYear = 2027 になっていた状態で、2027年を表示していた。その画面には2026年からコピーされた2027年データが見えていた。

それを「2026年として登録したデータ」と認識していたが、実際にDBに入っていたのは year=2027 のレコードだった、というのが最も自然な説明です。

本番DBで確認できますか?

ssh keinafarm
docker exec keinasystem_db psql -U keinasystem -d keinasystem -c "SELECT year, COUNT(*) FROM plans_plan GROUP BY year ORDER BY year;"
いやそんな事はないです。2027のデータは作った事はありません。
しかも最初は

  1. 2026年が選択されていた時、データは何も表示されなかった
  2. 2027年を選択したら、2026年のデータが見えた
  3. 再度2026年のデータを選択したら、データが見えた
  4. それ以降、何年を選択してもデータは見えています。
    その症状はレースコンディションです。

手順①の「2026年でデータが出なかった」時点で /plans/?year=2026 のAPIリクエストが飛んでいましたが、そのレスポンスが返ってくる前に②で2027年に切り替えたと考えられます。

その結果:

year=2027 の画面が表示されている状態で、遅れて返ってきた year=2026 のレスポンスが setPlans() にセットされた
2027年を表示中なのに2026年データが見えた(②の症状)
③で2026年に戻すと正常なフェッチが走り、それ以降は問題なし(④)。

fetchData の実装を確認します。

Bash Check fetchData implementation
IN
grep -n "fetchData|setPlans|AbortController|cancel" /home/akira/develop/keinasystem/frontend/src/app/allocation/page.tsx | head -30

OUT
25: const [plans, setPlans] = useState<Plan[]>([]);
61: fetchData();
67: const fetchData = async (background = false) => {
77: setPlans(plansRes.data);
230: await fetchData(true);
276: await fetchData(true);
296: await fetchData(true);
315: await fetchData(true);
336: await fetchData(true);
361: await fetchData(true);
377: await fetchData(true);
382: await fetchData(true);
399: await fetchData(true);
413: await fetchData(true);
452: await fetchData();
472: await fetchData();
1141: await fetchData(true);
Read page.tsx (lines 57-86)
原因が確定しました。

問題箇所: allocation/page.tsx:73

fetchData は year をクロージャで参照しており、year が変わっても飛行中のリクエストをキャンセルしない。年度を素早く切り替えると、古いレスポンスが後から setPlans() を上書きします。

修正方法: AbortController で年度変更時に前のリクエストをキャンセルします。修正しますか?

じゃ、最初にデータが出なかったのは、逆にレスポンスが返ってくる前だったからですか?
はい、そうです。

ページ読み込み直後はまだ fetchData() のレスポンスが返っていないので、plans = [] の空状態が表示されます。setLoading(false) まで待たずに年度を切り替えてしまったため、その「読み込み中の一瞬の空表示」を「データがない」と見えたわけです。

まとめると:

ページ読み込み → year=2026 で fetchData() 発火(リクエスト飛行中)
レスポンス待ちの間、plans=[] なので 「データなし」に見えた
年度を2027に切り替える → year=2027 で新たな fetchData() 発火
2027のレスポンスが先に返る → plans=[](2027データなし)
遅れて2026のレスポンスが返る → setPlans(2026データ) で上書き
2027を表示中なのに2026のデータが見える

Claude Codeの見解 でもなぜ、allocationYear=2027なのに2027年のデータじゃなくて2026年のデータが見えるのですか? Read [page.tsx](vscode-webview://16rq1inm6b0j357a2dfbqo06696l700d1s8r3qnstio78ehmdsvr/index.html?id=2766802b-0b01-48fe-aa13-01f9e748d311&parentId=9&origin=680cb729-38a3-4f2c-ac81-1de5e0bd75f2&swVersion=4&extensionId=Anthropic.claude-code&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app&remoteAuthority=wsl%2BUbuntu&purpose=webviewView&session=0cfc2b72-248d-4e32-b95a-76df4903732e#) (lines 644-663) 年度セレクタ自体は問題なさそうです。別の角度で確認します。 Bash Check actual plan year values in DB IN docker exec keinasystem_db psql -U keinasystem -d keinasystem -c "SELECT id, field_id, year, variety_id FROM plans_plan ORDER BY year, field_id LIMIT 10;" OUT id | field_id | year | variety_id ----+----------+------+------------ 19 | 3 | 2025 | 20 20 | 4 | 2025 | 14 21 | 5 | 2025 | 14 22 | 6 | 2025 | 16 23 | 7 | 2025 | 16 24 | 8 | 2025 | 20 25 | 9 | 2025 | 16 26 | 10 | 2025 | 22 27 | 11 | 2025 | 14 28 | 12 | 2025 | 22 (10 rows) Bash Check plan ID ranges by year IN docker exec keinasystem_db psql -U keinasystem -d keinasystem -c "SELECT MIN(id), MAX(id), year FROM plans_plan GROUP BY year;" OUT min | max | year -----+-----+------ 40 | 78 | 2026 1 | 39 | 2025 (2 rows) Bash Check copy from previous year logic IN grep -n "handleCopyFromPreviousYear\|copy.*year\|year.*copy\|copyFrom" /home/akira/develop/keinasystem/frontend/src/app/allocation/[page.tsx](vscode-webview://16rq1inm6b0j357a2dfbqo06696l700d1s8r3qnstio78ehmdsvr/index.html?id=2766802b-0b01-48fe-aa13-01f9e748d311&parentId=9&origin=680cb729-38a3-4f2c-ac81-1de5e0bd75f2&swVersion=4&extensionId=Anthropic.claude-code&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app&remoteAuthority=wsl%2BUbuntu&purpose=webviewView&session=0cfc2b72-248d-4e32-b95a-76df4903732e#) | head -20 OUT 461: const handleCopyFromPreviousYear = async () => { 467: const res = await api.post('/plans/copy_from_previous_year/', { 653: onClick={handleCopyFromPreviousYear} Read page.tsx (lines 462-481) Bash Find copy_from_previous_year view IN grep -n "copy_from_previous_year" /home/akira/develop/keinasystem/backend/apps/plans/[views.py](vscode-webview://16rq1inm6b0j357a2dfbqo06696l700d1s8r3qnstio78ehmdsvr/index.html?id=2766802b-0b01-48fe-aa13-01f9e748d311&parentId=9&origin=680cb729-38a3-4f2c-ac81-1de5e0bd75f2&swVersion=4&extensionId=Anthropic.claude-code&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app&remoteAuthority=wsl%2BUbuntu&purpose=webviewView&session=0cfc2b72-248d-4e32-b95a-76df4903732e#) OUT 79: def copy_from_previous_year(self, request): Read views.py (lines 80-119) わかりました。 原因はこれです。 本番サーバーで「2027年度を表示した状態で前年度コピー」を実行すると: from_year = 2026, to_year = 2027 つまり 2026年のデータが2027年としてコピーされた 状態になっています。 本番DBには: 2026年の実データ(39件) 2027年にコピーされた2026年データ(39件) が両方存在していたはずです。 ローカルのDBには本番からリストアした時点のデータが入っているので、確認します。 Bash Check all plan years in local DB IN docker exec keinasystem_db psql -U keinasystem -d keinasystem -c "SELECT year, COUNT(*) FROM plans_plan GROUP BY year ORDER BY year;" OUT year | count ------+------- 2025 | 39 2026 | 39 (2 rows) ローカルには2027年データがないので、本番でその操作が起きていたとすればリストア後に2027年データは削除済みか、別の操作だったことになります。 整理すると: 本番ブラウザで localStorage.allocationYear = 2027 になっていた状態で、2027年を表示していた。その画面には2026年からコピーされた2027年データが見えていた。 それを「2026年として登録したデータ」と認識していたが、実際にDBに入っていたのは year=2027 のレコードだった、というのが最も自然な説明です。 本番DBで確認できますか? ssh keinafarm docker exec keinasystem_db psql -U keinasystem -d keinasystem -c "SELECT year, COUNT(*) FROM plans_plan GROUP BY year ORDER BY year;" いやそんな事はないです。2027のデータは作った事はありません。 しかも最初は 1) 2026年が選択されていた時、データは何も表示されなかった 2) 2027年を選択したら、2026年のデータが見えた 3) 再度2026年のデータを選択したら、データが見えた 4) それ以降、何年を選択してもデータは見えています。 その症状はレースコンディションです。 手順①の「2026年でデータが出なかった」時点で /plans/?year=2026 のAPIリクエストが飛んでいましたが、そのレスポンスが返ってくる前に②で2027年に切り替えたと考えられます。 その結果: year=2027 の画面が表示されている状態で、遅れて返ってきた year=2026 のレスポンスが setPlans() にセットされた 2027年を表示中なのに2026年データが見えた(②の症状) ③で2026年に戻すと正常なフェッチが走り、それ以降は問題なし(④)。 fetchData の実装を確認します。 Bash Check fetchData implementation IN grep -n "fetchData\|setPlans\|AbortController\|cancel" /home/akira/develop/keinasystem/frontend/src/app/allocation/page.tsx | head -30 OUT 25: const [plans, setPlans] = useState<Plan[]>([]); 61: fetchData(); 67: const fetchData = async (background = false) => { 77: setPlans(plansRes.data); 230: await fetchData(true); 276: await fetchData(true); 296: await fetchData(true); 315: await fetchData(true); 336: await fetchData(true); 361: await fetchData(true); 377: await fetchData(true); 382: await fetchData(true); 399: await fetchData(true); 413: await fetchData(true); 452: await fetchData(); 472: await fetchData(); 1141: await fetchData(true); Read [page.tsx](vscode-webview://16rq1inm6b0j357a2dfbqo06696l700d1s8r3qnstio78ehmdsvr/index.html?id=2766802b-0b01-48fe-aa13-01f9e748d311&parentId=9&origin=680cb729-38a3-4f2c-ac81-1de5e0bd75f2&swVersion=4&extensionId=Anthropic.claude-code&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app&remoteAuthority=wsl%2BUbuntu&purpose=webviewView&session=0cfc2b72-248d-4e32-b95a-76df4903732e#) (lines 57-86) 原因が確定しました。 問題箇所: [allocation/page.tsx:73](vscode-webview://16rq1inm6b0j357a2dfbqo06696l700d1s8r3qnstio78ehmdsvr/frontend/src/app/allocation/page.tsx#L73) fetchData は year をクロージャで参照しており、year が変わっても飛行中のリクエストをキャンセルしない。年度を素早く切り替えると、古いレスポンスが後から setPlans() を上書きします。 修正方法: AbortController で年度変更時に前のリクエストをキャンセルします。修正しますか? じゃ、最初にデータが出なかったのは、逆にレスポンスが返ってくる前だったからですか? はい、そうです。 ページ読み込み直後はまだ fetchData() のレスポンスが返っていないので、plans = [] の空状態が表示されます。setLoading(false) まで待たずに年度を切り替えてしまったため、その「読み込み中の一瞬の空表示」を「データがない」と見えたわけです。 まとめると: ページ読み込み → year=2026 で fetchData() 発火(リクエスト飛行中) レスポンス待ちの間、plans=[] なので 「データなし」に見えた 年度を2027に切り替える → year=2027 で新たな fetchData() 発火 2027のレスポンスが先に返る → plans=[](2027データなし) 遅れて2026のレスポンスが返る → setPlans(2026データ) で上書き 2027を表示中なのに2026のデータが見える
Author
Owner

調査結果です。仮説ではなく、直接確認できた証拠だけを整理します。

結論

今回 allocation 画面で作付け計画が表示されない直接原因は、フロントの年度切替ではなく、/api/plans/?year=2026500エラー で落ちていることです。

直接証拠

1. ブラウザ Network タブ

起動直後の http://localhost:3000/allocation で以下を確認しました。

  • fields/?ordering=group_name,display_order,id200
  • crops/200
  • plans/?year=2026500

つまり、作付け計画一覧 API 自体が失敗しています。

2. バックエンドログ

keinasystem_backend のログで、GET /api/plans/?year=2026 に対して以下の例外が出ています。

django.db.utils.ProgrammingError: relation "plans_planvarietychange" does not exist

この例外により、実際に

"GET /api/plans/?year=2026 HTTP/1.1" 500

となっていることを確認しました。

3. Django shell でも同じ失敗を再現

PlanViewSet.listyear=2026 で直接呼んでも、同じく

relation "plans_planvarietychange" does not exist

で落ちることを確認しました。

原因の整理

現在のコードでは PlanViewSetvariety_changesprefetch_related するようになっています。
しかし、ローカル DB には PlanVarietyChange テーブル (plans_planvarietychange) が存在していません。

その結果、/api/plans/ 一覧取得時点で DB 例外が発生し、allocation 画面は作付け計画を表示できません。

補足

  • DB には作付け計画データ自体は存在します
    • 2025: 39件
    • 2026: 39件
  • したがって「データが入っていない」のではなく、「一覧取得 API が 500 で落ちている」のが本質です。

現時点で言える本質原因

PlanVarietyChange の migration 未適用により plans_planvarietychange テーブルが存在せず、/api/plans/ が 500 になっていること

調査結果です。仮説ではなく、直接確認できた証拠だけを整理します。 ## 結論 今回 `allocation` 画面で作付け計画が表示されない直接原因は、フロントの年度切替ではなく、`/api/plans/?year=2026` が **500エラー** で落ちていることです。 ## 直接証拠 ### 1. ブラウザ Network タブ 起動直後の `http://localhost:3000/allocation` で以下を確認しました。 - `fields/?ordering=group_name,display_order,id` → `200` - `crops/` → `200` - `plans/?year=2026` → **`500`** つまり、作付け計画一覧 API 自体が失敗しています。 ### 2. バックエンドログ `keinasystem_backend` のログで、`GET /api/plans/?year=2026` に対して以下の例外が出ています。 ```text django.db.utils.ProgrammingError: relation "plans_planvarietychange" does not exist ``` この例外により、実際に ```text "GET /api/plans/?year=2026 HTTP/1.1" 500 ``` となっていることを確認しました。 ### 3. Django shell でも同じ失敗を再現 `PlanViewSet.list` を `year=2026` で直接呼んでも、同じく ```text relation "plans_planvarietychange" does not exist ``` で落ちることを確認しました。 ## 原因の整理 現在のコードでは `PlanViewSet` が `variety_changes` を `prefetch_related` するようになっています。 しかし、ローカル DB には `PlanVarietyChange` テーブル (`plans_planvarietychange`) が存在していません。 その結果、`/api/plans/` 一覧取得時点で DB 例外が発生し、allocation 画面は作付け計画を表示できません。 ## 補足 - DB には作付け計画データ自体は存在します - `2025: 39件` - `2026: 39件` - したがって「データが入っていない」のではなく、「一覧取得 API が 500 で落ちている」のが本質です。 ## 現時点で言える本質原因 **`PlanVarietyChange` の migration 未適用により `plans_planvarietychange` テーブルが存在せず、`/api/plans/` が 500 になっていること**
Author
Owner
image.png
<img width="1314" alt="image.png" src="attachments/83c1c8db-e0f0-4c5c-8a17-6e68e3e623c5">
222 KiB
akira closed this issue 2026-04-05 08:39:38 +00:00
Author
Owner

ローカル確認結果を追記します。

直接確認できたこと:

  • deploy.sh は本番用 docker-compose.yml を前提としており、ローカルでは traefik-net 外部ネットワーク前提のためそのままでは起動できませんでした
  • ローカル検証環境として正しいのは docker-compose.develop.yml 側でした
  • 開発用 compose で起動後、docker compose -f docker-compose.develop.yml exec backend python manage.py migrate を実行すると plans.0010_planvarietychange... OK になりました
  • その後 /api/plans/?year=2026 は少なくとも 500 ではなくなり、未ログイン状態では 401 を返すことを確認しました
  • さらに、ローカルの /allocation 画面が正常表示されることを確認しました

整理すると、今回のローカル症状の直接原因は PlanVarietyChange migration 未適用で、その解消手順は「本番用 deploy.sh ではなく、開発用 compose で起動した上で migrate を適用すること」でした。

この issue は解消確認までできたためクローズします。

ローカル確認結果を追記します。 直接確認できたこと: - `deploy.sh` は本番用 [docker-compose.yml](/akira/keinasystem/src/branch/main/docker-compose.yml) を前提としており、ローカルでは `traefik-net` 外部ネットワーク前提のためそのままでは起動できませんでした - ローカル検証環境として正しいのは [docker-compose.develop.yml](/akira/keinasystem/src/branch/main/docker-compose.develop.yml) 側でした - 開発用 compose で起動後、`docker compose -f docker-compose.develop.yml exec backend python manage.py migrate` を実行すると `plans.0010_planvarietychange... OK` になりました - その後 `/api/plans/?year=2026` は少なくとも 500 ではなくなり、未ログイン状態では 401 を返すことを確認しました - さらに、ローカルの `/allocation` 画面が正常表示されることを確認しました 整理すると、今回のローカル症状の直接原因は `PlanVarietyChange` migration 未適用で、その解消手順は「本番用 deploy.sh ではなく、開発用 compose で起動した上で migrate を適用すること」でした。 この issue は解消確認までできたためクローズします。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: akira/keinasystem#10