Move unspread fertilization entries on variety change

This commit is contained in:
akira
2026-04-05 16:43:26 +09:00
parent 21fb2323eb
commit 98814299cf
3 changed files with 167 additions and 2 deletions

View File

@@ -3,9 +3,10 @@ from decimal import Decimal
from django.db import transaction from django.db import transaction
from django.db.models import Sum from django.db.models import Sum
from apps.materials.stock_service import create_reserves_for_plan
from apps.materials.models import StockTransaction from apps.materials.models import StockTransaction
from apps.workrecords.services import sync_spreading_work_record from apps.workrecords.services import sync_spreading_work_record
from .models import FertilizationEntry, SpreadingSessionItem from .models import FertilizationEntry, FertilizationPlan, SpreadingSessionItem
def sync_actual_bags_for_pairs(year, field_fertilizer_pairs): def sync_actual_bags_for_pairs(year, field_fertilizer_pairs):
@@ -56,3 +57,51 @@ def sync_stock_uses_for_spreading_session(session):
fertilization_plan=None, fertilization_plan=None,
spreading_item=item, spreading_item=item,
) )
@transaction.atomic
def move_unspread_entries_for_variety_change(change):
moved_count = 0
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 = (
FertilizationPlan.objects
.filter(
year=change.year,
variety_id=old_variety_id,
entries__field_id=change.field_id,
entries__actual_bags__isnull=True,
)
.distinct()
.prefetch_related('entries')
)
for old_plan in old_plans:
entries_to_move = list(
old_plan.entries.filter(
field_id=change.field_id,
actual_bags__isnull=True,
).order_by('id')
)
if not entries_to_move:
continue
new_plan = FertilizationPlan.objects.create(
name=f'{change.year}年度 {new_variety.name} 施肥計画(品種変更移動)',
year=change.year,
variety=new_variety,
calc_settings=old_plan.calc_settings,
)
FertilizationEntry.objects.filter(
id__in=[entry.id for entry in entries_to_move]
).update(plan=new_plan)
create_reserves_for_plan(old_plan)
create_reserves_for_plan(new_plan)
moved_count += len(entries_to_move)
return moved_count

View File

@@ -59,7 +59,12 @@ def handle_plan_variety_change(plan: Plan, *, old_variety, new_variety, reason:
def process_plan_variety_change(change: PlanVarietyChange): def process_plan_variety_change(change: PlanVarietyChange):
"""後続 issue で施肥計画・田植え計画への移動処理を追加する入口。""" from apps.fertilizer.services import move_unspread_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'])
return change return change

View File

@@ -2,7 +2,10 @@ from django.contrib.auth import get_user_model
from django.test import TestCase from django.test import TestCase
from rest_framework.test import APIRequestFactory, force_authenticate from rest_framework.test import APIRequestFactory, force_authenticate
from apps.fertilizer.models import FertilizationEntry, FertilizationPlan, Fertilizer
from apps.fields.models import Field 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, Variety
from .serializers import PlanSerializer from .serializers import PlanSerializer
from .views import PlanViewSet from .views import PlanViewSet
@@ -34,6 +37,15 @@ class PlanVarietyChangeTests(TestCase):
variety=self.old_variety, variety=self.old_variety,
notes='', 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): def test_serializer_update_creates_history_when_variety_changes(self):
serializer = PlanSerializer( serializer = PlanSerializer(
@@ -90,3 +102,102 @@ class PlanVarietyChangeTests(TestCase):
change = PlanVarietyChange.objects.get(plan=self.plan) change = PlanVarietyChange.objects.get(plan=self.plan)
self.assertEqual(change.old_variety_id, self.old_variety.id) self.assertEqual(change.old_variety_id, self.old_variety.id)
self.assertEqual(change.new_variety_id, self.new_variety.id) self.assertEqual(change.new_variety_id, self.new_variety.id)
def test_serializer_update_moves_only_unspread_fertilizer_entries(self):
material_unspread = Material.objects.create(
name='高度化成14号',
material_type=Material.MaterialType.FERTILIZER,
)
material_partial = Material.objects.create(
name='分げつ一発',
material_type=Material.MaterialType.FERTILIZER,
)
fertilizer_unspread = Fertilizer.objects.create(
name='高度化成14号',
material=material_unspread,
)
fertilizer_partial = Fertilizer.objects.create(
name='分げつ一発',
material=material_partial,
)
old_fertilization_plan = FertilizationPlan.objects.create(
name='2026年度 にこまる 元肥',
year=2026,
variety=self.old_variety,
calc_settings=[{'fertilizer_id': fertilizer_unspread.id, 'method': 'per_tan', 'param': '1.0'}],
)
unspread_entry = FertilizationEntry.objects.create(
plan=old_fertilization_plan,
field=self.field,
fertilizer=fertilizer_unspread,
bags='4.00',
actual_bags=None,
)
partial_entry = FertilizationEntry.objects.create(
plan=old_fertilization_plan,
field=self.field,
fertilizer=fertilizer_partial,
bags='3.00',
actual_bags='1.0000',
)
untouched_entry = FertilizationEntry.objects.create(
plan=old_fertilization_plan,
field=self.other_field,
fertilizer=fertilizer_unspread,
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.moved_entry_count, 1)
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)
unspread_entry.refresh_from_db()
partial_entry.refresh_from_db()
untouched_entry.refresh_from_db()
self.assertEqual(unspread_entry.plan_id, new_plan.id)
self.assertEqual(partial_entry.plan_id, old_fertilization_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), 2)
self.assertEqual(len(new_reserves), 1)
self.assertEqual(
{(reserve.material_id, reserve.quantity) for reserve in old_reserves},
{
(material_partial.id, partial_entry.bags),
(material_unspread.id, untouched_entry.bags),
},
)
self.assertEqual(new_reserves[0].quantity, unspread_entry.bags)