from decimal import Decimal from django.contrib.auth import get_user_model from django.test import TestCase from rest_framework.test import APIClient from apps.fields.models import Field from apps.materials.models import Material, StockTransaction from apps.materials.stock_service import create_reserves_for_plan from apps.plans.models import Crop, Variety from .models import FertilizationEntry, FertilizationPlan, Fertilizer class FertilizationPlanMergeTests(TestCase): def setUp(self): self.client = APIClient() self.user = get_user_model().objects.create_user( username='merge-user', password='secret12345', ) self.client.force_authenticate(user=self.user) crop = Crop.objects.create(name='水稲') self.variety = Variety.objects.create(crop=crop, name='たちはるか特栽') self.field_a = Field.objects.create( name='足川北上', address='高知県高岡郡', area_tan='1.2000', area_m2=1200, owner_name='吉田', group_name='北', display_order=1, ) self.field_b = Field.objects.create( name='足川南', address='高知県高岡郡', area_tan='0.8000', area_m2=800, owner_name='吉田', group_name='南', display_order=2, ) material_a = Material.objects.create( name='高度化成14号', material_type=Material.MaterialType.FERTILIZER, ) material_b = Material.objects.create( name='分げつ一発', material_type=Material.MaterialType.FERTILIZER, ) self.fertilizer_a = Fertilizer.objects.create(name='高度化成14号', material=material_a) self.fertilizer_b = Fertilizer.objects.create(name='分げつ一発', material=material_b) def test_merge_into_moves_entries_and_deletes_empty_source_plan(self): target_plan = FertilizationPlan.objects.create( name='2026年度 たちはるか特栽 元肥', year=2026, variety=self.variety, calc_settings=[{'fertilizer_id': self.fertilizer_a.id, 'method': 'per_tan', 'param': '1.2'}], ) source_plan = FertilizationPlan.objects.create( name='2026年度 たちはるか特栽 施肥計画(品種変更移動)', year=2026, variety=self.variety, calc_settings=[{'fertilizer_id': self.fertilizer_b.id, 'method': 'per_tan', 'param': '0.8'}], ) target_entry = FertilizationEntry.objects.create( plan=target_plan, field=self.field_a, fertilizer=self.fertilizer_a, bags='3.00', actual_bags='1.0000', ) source_entry = FertilizationEntry.objects.create( plan=source_plan, field=self.field_b, fertilizer=self.fertilizer_b, bags='2.00', actual_bags='2.0000', ) create_reserves_for_plan(target_plan) create_reserves_for_plan(source_plan) response = self.client.post( f'/api/fertilizer/plans/{source_plan.id}/merge_into/', {'target_plan_id': target_plan.id}, format='json', ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['moved_entry_count'], 1) self.assertTrue(response.data['deleted_source_plan']) source_entry.refresh_from_db() self.assertEqual(source_entry.plan_id, target_plan.id) self.assertFalse(FertilizationPlan.objects.filter(id=source_plan.id).exists()) target_plan.refresh_from_db() self.assertEqual( target_plan.calc_settings, [ {'fertilizer_id': self.fertilizer_a.id, 'method': 'per_tan', 'param': '1.2'}, {'fertilizer_id': self.fertilizer_b.id, 'method': 'per_tan', 'param': '0.8'}, ], ) reserves = list( StockTransaction.objects.filter( fertilization_plan=target_plan, transaction_type=StockTransaction.TransactionType.RESERVE, ).order_by('material__name') ) self.assertEqual(len(reserves), 2) self.assertEqual( {(reserve.material_id, reserve.quantity) for reserve in reserves}, { (self.fertilizer_a.material_id, Decimal(str(target_entry.bags))), (self.fertilizer_b.material_id, Decimal(str(source_entry.bags))), }, ) def test_merge_into_stops_on_field_fertilizer_conflict(self): target_plan = FertilizationPlan.objects.create( name='2026年度 たちはるか特栽 元肥', year=2026, variety=self.variety, ) source_plan = FertilizationPlan.objects.create( name='2026年度 たちはるか特栽 施肥計画(品種変更移動)', year=2026, variety=self.variety, ) FertilizationEntry.objects.create( plan=target_plan, field=self.field_a, fertilizer=self.fertilizer_a, bags='3.00', ) source_entry = FertilizationEntry.objects.create( plan=source_plan, field=self.field_a, fertilizer=self.fertilizer_a, bags='2.00', ) response = self.client.post( f'/api/fertilizer/plans/{source_plan.id}/merge_into/', {'target_plan_id': target_plan.id}, format='json', ) self.assertEqual(response.status_code, 409) self.assertEqual(len(response.data['conflicts']), 1) source_entry.refresh_from_db() self.assertEqual(source_entry.plan_id, source_plan.id) self.assertTrue(FertilizationPlan.objects.filter(id=source_plan.id).exists())