diff --git a/backend/apps/plans/migrations/0010_planvarietychange.py b/backend/apps/plans/migrations/0010_planvarietychange.py index 97607d0..4c0c32b 100644 --- a/backend/apps/plans/migrations/0010_planvarietychange.py +++ b/backend/apps/plans/migrations/0010_planvarietychange.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): ('year', models.IntegerField(verbose_name='作付年度')), ('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='変更日時')), ('reason', models.TextField(blank=True, default='', verbose_name='変更理由')), - ('moved_entry_count', models.IntegerField(default=0, verbose_name='移動エントリ数')), + ('fertilizer_moved_entry_count', models.IntegerField(default=0, verbose_name='施肥移動エントリ数')), ('field', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='plan_variety_changes', to='fields.field', verbose_name='圃場')), ('new_variety', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='new_plan_variety_changes', to='plans.variety', verbose_name='変更後品種')), ('old_variety', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='old_plan_variety_changes', to='plans.variety', verbose_name='変更前品種')), diff --git a/backend/apps/plans/models.py b/backend/apps/plans/models.py index 816c456..af20984 100644 --- a/backend/apps/plans/models.py +++ b/backend/apps/plans/models.py @@ -97,7 +97,7 @@ class PlanVarietyChange(models.Model): blank=True, ) reason = models.TextField(blank=True, default='', verbose_name='変更理由') - moved_entry_count = models.IntegerField(default=0, verbose_name='移動エントリ数') + fertilizer_moved_entry_count = models.IntegerField(default=0, verbose_name='施肥移動エントリ数') class Meta: verbose_name = '作付け計画品種変更履歴' diff --git a/backend/apps/plans/services.py b/backend/apps/plans/services.py index 551f018..927d927 100644 --- a/backend/apps/plans/services.py +++ b/backend/apps/plans/services.py @@ -60,11 +60,13 @@ def handle_plan_variety_change(plan: Plan, *, old_variety, new_variety, reason: def process_plan_variety_change(change: PlanVarietyChange): from apps.fertilizer.services import move_unspread_entries_for_variety_change + from .services_rice_transplant import move_rice_transplant_entries_for_variety_change moved_count = move_unspread_entries_for_variety_change(change) - if moved_count != change.moved_entry_count: - change.moved_entry_count = moved_count - change.save(update_fields=['moved_entry_count']) + move_rice_transplant_entries_for_variety_change(change) + if moved_count != change.fertilizer_moved_entry_count: + change.fertilizer_moved_entry_count = moved_count + change.save(update_fields=['fertilizer_moved_entry_count']) return change diff --git a/backend/apps/plans/services_rice_transplant.py b/backend/apps/plans/services_rice_transplant.py new file mode 100644 index 0000000..8392344 --- /dev/null +++ b/backend/apps/plans/services_rice_transplant.py @@ -0,0 +1,46 @@ +from django.db import transaction + +from .models import RiceTransplantEntry, RiceTransplantPlan + + +@transaction.atomic +def move_rice_transplant_entries_for_variety_change(change): + old_variety_id = change.old_variety_id + new_variety = change.new_variety + if old_variety_id is None or new_variety is None: + return 0 + + old_plans = ( + RiceTransplantPlan.objects + .filter( + year=change.year, + variety_id=old_variety_id, + entries__field_id=change.field_id, + ) + .distinct() + .prefetch_related('entries') + ) + + moved_count = 0 + for old_plan in old_plans: + entries_to_move = list( + old_plan.entries.filter(field_id=change.field_id).order_by('id') + ) + if not entries_to_move: + continue + + new_plan = RiceTransplantPlan.objects.create( + name=f'{change.year}年度 {new_variety.name} 田植え計画(品種変更移動)', + year=change.year, + variety=new_variety, + default_seed_grams_per_box=old_plan.default_seed_grams_per_box, + seedling_boxes_per_tan=old_plan.seedling_boxes_per_tan, + notes=old_plan.notes, + ) + + RiceTransplantEntry.objects.filter( + id__in=[entry.id for entry in entries_to_move] + ).update(plan=new_plan) + moved_count += len(entries_to_move) + + return moved_count diff --git a/backend/apps/plans/tests.py b/backend/apps/plans/tests.py index 7e6fb82..9ad5ce3 100644 --- a/backend/apps/plans/tests.py +++ b/backend/apps/plans/tests.py @@ -1,12 +1,20 @@ from django.contrib.auth import get_user_model from django.test import TestCase from rest_framework.test import APIRequestFactory, force_authenticate +from decimal import Decimal from apps.fertilizer.models import FertilizationEntry, FertilizationPlan, Fertilizer from apps.fields.models import Field from apps.materials.models import Material, StockTransaction from apps.materials.stock_service import create_reserves_for_plan -from .models import Crop, Plan, PlanVarietyChange, Variety +from .models import ( + Crop, + Plan, + PlanVarietyChange, + RiceTransplantEntry, + RiceTransplantPlan, + Variety, +) from .serializers import PlanSerializer from .views import PlanViewSet @@ -64,7 +72,7 @@ class PlanVarietyChangeTests(TestCase): self.assertEqual(change.year, 2026) self.assertEqual(change.old_variety_id, self.old_variety.id) self.assertEqual(change.new_variety_id, self.new_variety.id) - self.assertEqual(change.moved_entry_count, 0) + self.assertEqual(change.fertilizer_moved_entry_count, 0) def test_serializer_update_does_not_create_history_without_variety_change(self): serializer = PlanSerializer( @@ -158,7 +166,7 @@ class PlanVarietyChangeTests(TestCase): serializer.save() change = PlanVarietyChange.objects.get(plan=self.plan) - self.assertEqual(change.moved_entry_count, 1) + self.assertEqual(change.fertilizer_moved_entry_count, 1) old_fertilization_plan.refresh_from_db() new_plan = FertilizationPlan.objects.exclude(id=old_fertilization_plan.id).get( @@ -201,3 +209,50 @@ class PlanVarietyChangeTests(TestCase): }, ) self.assertEqual(new_reserves[0].quantity, unspread_entry.bags) + + def test_serializer_update_moves_rice_transplant_entries_for_target_field(self): + old_rice_plan = RiceTransplantPlan.objects.create( + name='2026年度 にこまる 田植え計画', + year=2026, + variety=self.old_variety, + default_seed_grams_per_box='200.00', + seedling_boxes_per_tan='12.00', + notes='旧計画メモ', + ) + target_entry = RiceTransplantEntry.objects.create( + plan=old_rice_plan, + field=self.field, + installed_seedling_boxes='14.40', + seed_grams_per_box='200.00', + ) + other_entry = RiceTransplantEntry.objects.create( + plan=old_rice_plan, + field=self.other_field, + installed_seedling_boxes='9.60', + seed_grams_per_box='200.00', + ) + + serializer = PlanSerializer( + instance=self.plan, + data={'variety': self.new_variety.id}, + partial=True, + ) + self.assertTrue(serializer.is_valid(), serializer.errors) + serializer.save() + + target_entry.refresh_from_db() + other_entry.refresh_from_db() + + new_rice_plan = RiceTransplantPlan.objects.exclude(id=old_rice_plan.id).get( + year=2026, + variety=self.new_variety, + ) + self.assertEqual( + new_rice_plan.name, + f'2026年度 {self.new_variety.name} 田植え計画(品種変更移動)', + ) + self.assertEqual(new_rice_plan.default_seed_grams_per_box, Decimal('200.00')) + self.assertEqual(new_rice_plan.seedling_boxes_per_tan, Decimal('12.00')) + self.assertEqual(new_rice_plan.notes, old_rice_plan.notes) + self.assertEqual(target_entry.plan_id, new_rice_plan.id) + self.assertEqual(other_entry.plan_id, old_rice_plan.id)