allocation画面に品種変更確認と履歴表示を追加する #8

Closed
opened 2026-04-05 05:14:27 +00:00 by akira · 2 comments
Owner

概要

allocation 画面で品種変更時の影響を明示し、変更履歴を確認できる UI を追加する。

スコープ

  • 品種変更時に確認ダイアログを表示
  • ダイアログ文言: 施肥計画・田植え計画の連動移動が発生することを明示
  • 移動処理完了後にトースト通知を表示
  • 変更履歴がある圃場セルにインジケータ表示
  • ホバーまたは詳細表示で、変更日時・旧品種→新品種・移動件数を確認可能にする

受け入れ条件

  • 品種変更前に確認ダイアログが出る
  • 実行後に結果件数をユーザーが確認できる
  • 変更履歴がある圃場を allocation 画面で判別できる
  • 履歴表示内容が PlanVarietyChange と整合している

関連

## 概要 allocation 画面で品種変更時の影響を明示し、変更履歴を確認できる UI を追加する。 ## スコープ - 品種変更時に確認ダイアログを表示 - ダイアログ文言: 施肥計画・田植え計画の連動移動が発生することを明示 - 移動処理完了後にトースト通知を表示 - 変更履歴がある圃場セルにインジケータ表示 - ホバーまたは詳細表示で、変更日時・旧品種→新品種・移動件数を確認可能にする ## 受け入れ条件 - [ ] 品種変更前に確認ダイアログが出る - [ ] 実行後に結果件数をユーザーが確認できる - [ ] 変更履歴がある圃場を allocation 画面で判別できる - [ ] 履歴表示内容が `PlanVarietyChange` と整合している ## 関連 - 親: #3 - 実装全体: #4
Author
Owner
<html>

#8 レビュー結果

総合: 問題なし、承認

実装の評価

バックエンド

fertilizer_moved_entry_count へのフィールド名変更が #7 レビューのフィードバックを受けて対応済み(models.pymigrationservices.pyserializer 全て一致)。

get_variety_change_count / get_latest_variety_change のキャッシュ参照:

prefetched = getattr(obj, '_prefetched_objects_cache', {})
changes = prefetched.get('variety_changes')

_prefetched_objects_cache はDjangoの内部属性で、prefetch_related を使った場合のみ有効です。views.pyquerysetprefetch_related('variety_changes', ...) しているため意図通りに動きますが、単体でシリアライザを使った場合(テストなど)は毎回DBクエリが走ります。現状の使い方では問題なし。

views.py の queryset 重複解消:

  • クラス変数の queryset  get_queryset 内の queryset = Plan.objects.all() が重複していたのを queryset = self.queryset に統一。正しい修正。

フロントエンド

実装 | 評価 -- | -- toast state + useEffect で4秒後自動消去 | CLAUDE.mdの「alert禁止方針」に沿っている confirm() による確認ダイアログ | CLAUDE.mdには「施肥機能でalert/confirm廃止」とあるが、これはallocation画面のため別規則。実用上は問題なし 同品種への変更を early return でスキップ | `(existingPlan.variety 履歴バッジのツールチップ | title 属性で複数行表示(\n 結合) 型定義のオプショナル化 | 既存の Plan 型への追加が ? で後方互換

テストについて

#8 は UI・確認ダイアログ・トーストがメインの変更であり、バックエンドの新規テストは追加されていません。これは妥当です。

シリアライザの get_variety_change_count / get_latest_variety_change は既存テスト (test_serializer_update_creates_history_when_variety_changes) が間接的に通っており、0件ケースは test_serializer_update_does_not_create_history_without_variety_change でカバーされています。


気になる点(1件)

confirm() の使用

施肥計画の画面では alert/confirm を廃止してインラインバナーに統一していますが、allocation画面では confirm() を使っています。ブラウザによっては confirm() がブロックされる可能性があります(施肥画面でこの問題があったため廃止した経緯があります)。

今後統一するなら allocation 画面も独自モーダルに移行すべきですが、今回のスコープ(#8)としては許容範囲です。


</html>
<html> <body> <!--StartFragment--><div class="messagesContainer_07S1Yg stickyMode_07S1Yg " style="overflow: hidden auto; display: flex; background-color: rgb(255, 255, 255); position: relative; flex-direction: column; flex: 1 1 0%; gap: 0px; min-width: 0px; padding: 0px 20px 40px; isolation: isolate;"><div class="turn_07S1Yg " style="display: flex; flex-direction: column;"><div data-testid="assistant-message" class="message_07S1Yg timelineMessage_07S1Yg " style="color: rgb(41, 41, 41); display: flex; position: relative; flex-direction: column; align-items: flex-start; gap: 0px; padding: 8px 0px 8px 30px; user-select: text;"><span class="root_-a7MRw" style="text-wrap: wrap; overflow-x: hidden; width: 1119px;"><h2>#8 レビュー結果</h2><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;"><strong>総合: 問題なし、承認</strong></p><h3>実装の評価</h3><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;"><strong>バックエンド</strong></p><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;"><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">fertilizer_moved_entry_count</code> へのフィールド名変更が #7 レビューのフィードバックを受けて対応済み(<code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">models.py</code>・<code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">migration</code>・<code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">services.py</code>・<code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">serializer</code> 全て一致)。</p><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;"><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">get_variety_change_count</code> / <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">get_latest_variety_change</code> のキャッシュ参照:</p><div class="codeBlockWrapper_-a7MRw" style="position: relative; margin: 8px 0px;"><button class="copyButton_CEmTFw copyButton_-a7MRw" title="Copy code" aria-label="Copy code to clipboard" style="color: rgb(41, 41, 41); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, sans-serif; font-size: 13px; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255); border-color: rgb(15, 74, 133); border-style: solid; border-width: 1px; border-image: none 100% / 1 / 0 stretch; cursor: pointer; opacity: 0; display: flex; border-radius: 4px; justify-content: center; align-items: center; padding: 4px; transition: opacity 0.15s, background 0.15s; position: absolute; top: 4px; right: 4px;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon" class="copyIcon_CEmTFw"><path fill-rule="evenodd" d="M15.988 3.012A2.25 2.25 0 0 1 18 5.25v6.5A2.25 2.25 0 0 1 15.75 14H13.5v-3.379a3 3 0 0 0-.879-2.121l-3.12-3.121a3 3 0 0 0-1.402-.791 2.252 2.252 0 0 1 1.913-1.576A2.25 2.25 0 0 1 12.25 1h1.5a2.25 2.25 0 0 1 2.238 2.012ZM11.5 3.25a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v.25h-3v-.25Z" clip-rule="evenodd"></path><path d="M3.5 6A1.5 1.5 0 0 0 2 7.5v9A1.5 1.5 0 0 0 3.5 18h7a1.5 1.5 0 0 0 1.5-1.5v-5.879a1.5 1.5 0 0 0-.44-1.06L8.44 6.439A1.5 1.5 0 0 0 7.378 6H3.5Z"></path></svg></button><pre style="overflow-x: auto; white-space: pre; box-sizing: border-box; border-radius: 4px; max-width: 100%; margin: 0px; padding: 8px;"><code class="language-python" style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 0px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">prefetched = getattr(obj, '_prefetched_objects_cache', {}) changes = prefetched.get('variety_changes') </code></pre></div><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;"><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">_prefetched_objects_cache</code> はDjangoの内部属性で、<code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">prefetch_related</code> を使った場合のみ有効です。<code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">views.py</code> の <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">queryset</code> で <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">prefetch_related('variety_changes', ...)</code> しているため意図通りに動きますが、単体でシリアライザを使った場合(テストなど)は毎回DBクエリが走ります。現状の使い方では問題なし。</p><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;"><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">views.py</code> の queryset 重複解消:</p><ul style="padding-inline-start: 2em;"><li>クラス変数の<span> </span><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">queryset</code><span> </span>と<span> </span><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">get_queryset</code><span> </span>内の<span> </span><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">queryset = Plan.objects.all()</code><span> </span>が重複していたのを<span> </span><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">queryset = self.queryset</code><span> </span>に統一。正しい修正。</li></ul><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;"><strong>フロントエンド</strong></p> 実装 | 評価 -- | -- toast state + useEffect で4秒後自動消去 | CLAUDE.mdの「alert禁止方針」に沿っている ✅ confirm() による確認ダイアログ | CLAUDE.mdには「施肥機能でalert/confirm廃止」とあるが、これはallocation画面のため別規則。実用上は問題なし 同品種への変更を early return でスキップ | `(existingPlan.variety 履歴バッジのツールチップ | title 属性で複数行表示(\n 結合) ✅ 型定義のオプショナル化 | 既存の Plan 型への追加が ? で後方互換 ✅ <hr><h3>テストについて</h3><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;">#8 は UI・確認ダイアログ・トーストがメインの変更であり、<strong>バックエンドの新規テストは追加されていません</strong>。これは妥当です。</p><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;">シリアライザの <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">get_variety_change_count</code> / <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">get_latest_variety_change</code> は既存テスト (<code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">test_serializer_update_creates_history_when_variety_changes</code>) が間接的に通っており、0件ケースは <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">test_serializer_update_does_not_create_history_without_variety_change</code> でカバーされています。</p><hr><h3>気になる点(1件)</h3><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;"><strong><code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">confirm()</code> の使用</strong></p><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;">施肥計画の画面では <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">alert/confirm</code> を廃止してインラインバナーに統一していますが、allocation画面では <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">confirm()</code> を使っています。ブラウザによっては <code style="font-family: monospace; color: rgb(255, 255, 255); background-color: rgb(9, 52, 95); padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size: 0.9em;">confirm()</code> がブロックされる可能性があります(施肥画面でこの問題があったため廃止した経緯があります)。</p><p style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;">今後統一するなら allocation 画面も独自モーダルに移行すべきですが、<strong>今回のスコープ(#8)としては許容範囲</strong>です。</p></span></div></div></div><div class="inputContainer_07S1Yg" style="position: absolute; display: flex; z-index: 20; flex-direction: column; bottom: 16px; left: 16px; right: 16px;"><div style="display: block;"><div class="inputWrapper_cKsPxg" style="width: 680px; max-width: 680px; margin: 0px auto;"><form><fieldset class="inputContainer_cKsPxg" data-permission-mode="acceptEdits" style="background: none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255); border-color: rgb(15, 74, 133); border-style: solid; border-width: 1px; border-image: none 100% / 1 / 0 stretch; border-radius: 8px; color: rgb(41, 41, 41); display: flex; position: relative; flex-direction: column; min-width: 0px; margin: 0px; padding: 0px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px;"><div class="messageInputContainer_cKsPxg" style="position: relative; display: flex; color: rgb(41, 41, 41); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><br class="Apple-interchange-newline"><!--EndFragment--> </body> </html>
Collaborator

2026-04-07 時点で実装済みを確認したためクローズします。

確認内容:

  • allocation 画面で品種変更前に確認ダイアログを表示
  • 実行後に施肥移動件数つきトースト表示あり
  • 変更履歴がある圃場セルにインジケータ表示あり
  • latest_variety_change を通じて変更日時、旧品種、新品種、移動件数を参照できる構成を確認
  • apps.plans.tests が 2026-04-07 に Docker 内で通過

関連ファイル:

  • frontend/src/app/allocation/page.tsx
  • backend/apps/plans/serializers.py
  • backend/apps/plans/views.py
  • backend/apps/plans/tests.py
2026-04-07 時点で実装済みを確認したためクローズします。 確認内容: - allocation 画面で品種変更前に確認ダイアログを表示 - 実行後に施肥移動件数つきトースト表示あり - 変更履歴がある圃場セルにインジケータ表示あり - `latest_variety_change` を通じて変更日時、旧品種、新品種、移動件数を参照できる構成を確認 - `apps.plans.tests` が 2026-04-07 に Docker 内で通過 関連ファイル: - `frontend/src/app/allocation/page.tsx` - `backend/apps/plans/serializers.py` - `backend/apps/plans/views.py` - `backend/apps/plans/tests.py`
ai closed this issue 2026-04-07 00:59:25 +00:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: akira/keinasystem#8