Move rice transplant entries on variety change

This commit is contained in:
akira
2026-04-05 16:49:03 +09:00
parent 98814299cf
commit 1d5bcc9dd6
5 changed files with 111 additions and 8 deletions

View File

@@ -17,7 +17,7 @@ class Migration(migrations.Migration):
('year', models.IntegerField(verbose_name='作付年度')), ('year', models.IntegerField(verbose_name='作付年度')),
('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='変更日時')), ('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='変更日時')),
('reason', models.TextField(blank=True, default='', 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='圃場')), ('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='変更後品種')), ('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='変更前品種')), ('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='変更前品種')),

View File

@@ -97,7 +97,7 @@ class PlanVarietyChange(models.Model):
blank=True, blank=True,
) )
reason = models.TextField(blank=True, default='', 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='施肥移動エントリ数')
class Meta: class Meta:
verbose_name = '作付け計画品種変更履歴' verbose_name = '作付け計画品種変更履歴'

View File

@@ -60,11 +60,13 @@ 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):
from apps.fertilizer.services import move_unspread_entries_for_variety_change 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) moved_count = move_unspread_entries_for_variety_change(change)
if moved_count != change.moved_entry_count: move_rice_transplant_entries_for_variety_change(change)
change.moved_entry_count = moved_count if moved_count != change.fertilizer_moved_entry_count:
change.save(update_fields=['moved_entry_count']) change.fertilizer_moved_entry_count = moved_count
change.save(update_fields=['fertilizer_moved_entry_count'])
return change return change

View File

@@ -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

View File

@@ -1,12 +1,20 @@
from django.contrib.auth import get_user_model 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 decimal import Decimal
from apps.fertilizer.models import FertilizationEntry, FertilizationPlan, Fertilizer 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.models import Material, StockTransaction
from apps.materials.stock_service import create_reserves_for_plan 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 .serializers import PlanSerializer
from .views import PlanViewSet from .views import PlanViewSet
@@ -64,7 +72,7 @@ class PlanVarietyChangeTests(TestCase):
self.assertEqual(change.year, 2026) self.assertEqual(change.year, 2026)
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)
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): def test_serializer_update_does_not_create_history_without_variety_change(self):
serializer = PlanSerializer( serializer = PlanSerializer(
@@ -158,7 +166,7 @@ class PlanVarietyChangeTests(TestCase):
serializer.save() serializer.save()
change = PlanVarietyChange.objects.get(plan=self.plan) 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() old_fertilization_plan.refresh_from_db()
new_plan = FertilizationPlan.objects.exclude(id=old_fertilization_plan.id).get( 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) 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)