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, RiceTransplantEntry, RiceTransplantPlan, Variety, ) from .serializers import PlanSerializer from .views import PlanViewSet class PlanVarietyChangeTests(TestCase): def setUp(self): self.factory = APIRequestFactory() self.user = get_user_model().objects.create_user( username='tester', password='secret12345', ) self.crop = Crop.objects.create(name='水稲') self.old_variety = Variety.objects.create(crop=self.crop, name='にこまる') self.new_variety = Variety.objects.create(crop=self.crop, name='たちはるか特栽') self.field = Field.objects.create( name='足川北上', address='高知県高岡郡', area_tan='1.2000', area_m2=1200, owner_name='吉田', group_name='北', display_order=1, ) self.plan = Plan.objects.create( field=self.field, year=2026, crop=self.crop, variety=self.old_variety, notes='', ) self.other_field = Field.objects.create( name='足川南', address='高知県高岡郡', area_tan='0.8000', area_m2=800, owner_name='吉田', group_name='南', display_order=2, ) def test_serializer_update_creates_history_when_variety_changes(self): serializer = PlanSerializer( instance=self.plan, data={'variety': self.new_variety.id}, partial=True, ) self.assertTrue(serializer.is_valid(), serializer.errors) serializer.save() self.plan.refresh_from_db() self.assertEqual(self.plan.variety_id, self.new_variety.id) change = PlanVarietyChange.objects.get(plan=self.plan) self.assertEqual(change.field_id, self.field.id) 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.fertilizer_moved_entry_count, 0) def test_serializer_update_does_not_create_history_without_variety_change(self): serializer = PlanSerializer( instance=self.plan, data={'notes': 'メモ更新'}, partial=True, ) self.assertTrue(serializer.is_valid(), serializer.errors) serializer.save() self.plan.refresh_from_db() self.assertEqual(self.plan.notes, 'メモ更新') self.assertFalse(PlanVarietyChange.objects.exists()) def test_bulk_update_creates_history_for_existing_plan(self): view = PlanViewSet.as_view({'post': 'bulk_update'}) request = self.factory.post( '/api/plans/bulk_update/', { 'field_ids': [self.field.id], 'year': 2026, 'crop': self.crop.id, 'variety': self.new_variety.id, }, format='json', ) force_authenticate(request, user=self.user) response = view(request) self.assertEqual(response.status_code, 200) self.plan.refresh_from_db() self.assertEqual(self.plan.variety_id, self.new_variety.id) change = PlanVarietyChange.objects.get(plan=self.plan) self.assertEqual(change.old_variety_id, self.old_variety.id) self.assertEqual(change.new_variety_id, self.new_variety.id) def test_serializer_update_moves_all_fertilizer_entries_for_target_field(self): material_target = Material.objects.create( name='高度化成14号', material_type=Material.MaterialType.FERTILIZER, ) material_spread = Material.objects.create( name='分げつ一発', material_type=Material.MaterialType.FERTILIZER, ) fertilizer_target = Fertilizer.objects.create( name='高度化成14号', material=material_target, ) fertilizer_spread = Fertilizer.objects.create( name='分げつ一発', material=material_spread, ) old_fertilization_plan = FertilizationPlan.objects.create( name='2026年度 にこまる 元肥', year=2026, variety=self.old_variety, calc_settings=[{'fertilizer_id': fertilizer_target.id, 'method': 'per_tan', 'param': '1.0'}], ) target_entry = FertilizationEntry.objects.create( plan=old_fertilization_plan, field=self.field, fertilizer=fertilizer_target, bags='4.00', actual_bags=None, ) spread_entry = FertilizationEntry.objects.create( plan=old_fertilization_plan, field=self.field, fertilizer=fertilizer_spread, bags='3.00', actual_bags='1.0000', ) untouched_entry = FertilizationEntry.objects.create( plan=old_fertilization_plan, field=self.other_field, fertilizer=fertilizer_target, bags='2.00', actual_bags=None, ) create_reserves_for_plan(old_fertilization_plan) serializer = PlanSerializer( instance=self.plan, data={'variety': self.new_variety.id}, partial=True, ) self.assertTrue(serializer.is_valid(), serializer.errors) serializer.save() change = PlanVarietyChange.objects.get(plan=self.plan) self.assertEqual(change.fertilizer_moved_entry_count, 2) old_fertilization_plan.refresh_from_db() new_plan = FertilizationPlan.objects.exclude(id=old_fertilization_plan.id).get( year=2026, variety=self.new_variety, ) self.assertEqual( new_plan.name, f'2026年度 {self.new_variety.name} 施肥計画(品種変更移動)', ) self.assertEqual(new_plan.calc_settings, old_fertilization_plan.calc_settings) target_entry.refresh_from_db() spread_entry.refresh_from_db() untouched_entry.refresh_from_db() self.assertEqual(target_entry.plan_id, new_plan.id) self.assertEqual(spread_entry.plan_id, new_plan.id) self.assertEqual(untouched_entry.plan_id, old_fertilization_plan.id) old_reserves = list( StockTransaction.objects.filter( fertilization_plan=old_fertilization_plan, transaction_type=StockTransaction.TransactionType.RESERVE, ).order_by('material__name') ) new_reserves = list( StockTransaction.objects.filter( fertilization_plan=new_plan, transaction_type=StockTransaction.TransactionType.RESERVE, ).order_by('material__name') ) self.assertEqual(len(old_reserves), 1) self.assertEqual(len(new_reserves), 2) self.assertEqual( {(reserve.material_id, reserve.quantity) for reserve in old_reserves}, { (material_target.id, untouched_entry.bags), }, ) self.assertEqual( {(reserve.material_id, reserve.quantity) for reserve in new_reserves}, { (material_target.id, target_entry.bags), (material_spread.id, spread_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)