品種ごとの種子在庫前提まで実装を進めました。
主な変更は、seed 資材種別の追加と Variety.seed_material の導入です。backend/apps/materials/models.py、backend/apps/plans/models.py、backend/apps/plans/serializers.py で、田植え計画が作物在庫ではなく品種に紐づく種子資材の現在庫を参照するように切り替えました。マイグレーションは backend/apps/materials/migrations/0005_material_seed_type.py と backend/apps/plans/migrations/0008_variety_seed_material.py を追加しています。 画面側は、frontend/src/app/materials/page.tsx と frontend/src/app/materials/masters/page.tsx に「種子」タブを追加し、frontend/src/app/allocation/page.tsx の品種管理モーダルで品種ごとに種子在庫資材を設定できるようにしました。田植え計画画面 frontend/src/app/rice-transplant/_components/RiceTransplantEditPage.tsx も、苗箱数 列中心に整理し、種もみkg 列を削除、反当苗箱枚数 の列反映と ≈ / ↩ の四捨五入トグルを施肥計画寄りの操作感に寄せています。仕様書 document/16_マスタードキュメント_田植え計画編.md も更新済みです。 確認できたのは python3 -m py_compile backend/apps/materials/models.py backend/apps/materials/serializers.py backend/apps/plans/models.py backend/apps/plans/serializers.py backend/apps/plans/views.py までです。frontend/node_modules が無いためフロントのビルド確認はまだできていません。Issue #2 にも反映内容をコメント済みです。必要なら次にコミットします。
This commit is contained in:
26
backend/apps/plans/migrations/0008_variety_seed_material.py
Normal file
26
backend/apps/plans/migrations/0008_variety_seed_material.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('materials', '0005_material_seed_type'),
|
||||
('plans', '0007_ricetransplantplan_seedling_boxes_per_tan'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='variety',
|
||||
name='seed_material',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
limit_choices_to={'material_type': 'seed'},
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='varieties',
|
||||
to='materials.material',
|
||||
verbose_name='種子在庫資材',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -29,6 +29,15 @@ class Variety(models.Model):
|
||||
default=0,
|
||||
verbose_name="反当苗箱枚数デフォルト",
|
||||
)
|
||||
seed_material = models.ForeignKey(
|
||||
'materials.Material',
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='varieties',
|
||||
verbose_name='種子在庫資材',
|
||||
blank=True,
|
||||
null=True,
|
||||
limit_choices_to={'material_type': 'seed'},
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "品種マスタ"
|
||||
|
||||
@@ -2,14 +2,24 @@ from decimal import Decimal
|
||||
|
||||
from rest_framework import serializers
|
||||
from apps.fields.models import Field
|
||||
from apps.materials.models import StockTransaction
|
||||
from .models import Crop, Variety, Plan
|
||||
from .models import RiceTransplantEntry, RiceTransplantPlan
|
||||
|
||||
|
||||
class VarietySerializer(serializers.ModelSerializer):
|
||||
seed_material_name = serializers.CharField(source='seed_material.name', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Variety
|
||||
fields = '__all__'
|
||||
fields = [
|
||||
'id',
|
||||
'crop',
|
||||
'name',
|
||||
'default_seedling_boxes_per_tan',
|
||||
'seed_material',
|
||||
'seed_material_name',
|
||||
]
|
||||
|
||||
|
||||
class CropSerializer(serializers.ModelSerializer):
|
||||
@@ -50,7 +60,6 @@ class RiceTransplantEntrySerializer(serializers.ModelSerializer):
|
||||
)
|
||||
planned_boxes = serializers.SerializerMethodField()
|
||||
default_seedling_boxes = serializers.SerializerMethodField()
|
||||
planned_seed_kg = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = RiceTransplantEntry
|
||||
@@ -62,7 +71,6 @@ class RiceTransplantEntrySerializer(serializers.ModelSerializer):
|
||||
'installed_seedling_boxes',
|
||||
'default_seedling_boxes',
|
||||
'planned_boxes',
|
||||
'planned_seed_kg',
|
||||
]
|
||||
|
||||
def get_default_seedling_boxes(self, obj):
|
||||
@@ -73,21 +81,15 @@ class RiceTransplantEntrySerializer(serializers.ModelSerializer):
|
||||
def get_planned_boxes(self, obj):
|
||||
return str(obj.installed_seedling_boxes.quantize(Decimal('0.01')))
|
||||
|
||||
def get_planned_seed_kg(self, obj):
|
||||
seed_kg = (
|
||||
obj.installed_seedling_boxes * obj.plan.default_seed_grams_per_box / Decimal('1000')
|
||||
).quantize(Decimal('0.001'))
|
||||
return str(seed_kg)
|
||||
|
||||
|
||||
class RiceTransplantPlanSerializer(serializers.ModelSerializer):
|
||||
variety_name = serializers.CharField(source='variety.name', read_only=True)
|
||||
crop_name = serializers.CharField(source='variety.crop.name', read_only=True)
|
||||
seed_material_name = serializers.CharField(source='variety.seed_material.name', read_only=True)
|
||||
entries = RiceTransplantEntrySerializer(many=True, read_only=True)
|
||||
field_count = serializers.SerializerMethodField()
|
||||
total_seedling_boxes = serializers.SerializerMethodField()
|
||||
total_seed_kg = serializers.SerializerMethodField()
|
||||
crop_seed_inventory_kg = serializers.SerializerMethodField()
|
||||
variety_seed_inventory_kg = serializers.SerializerMethodField()
|
||||
remaining_seed_kg = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
@@ -102,11 +104,12 @@ class RiceTransplantPlanSerializer(serializers.ModelSerializer):
|
||||
'default_seed_grams_per_box',
|
||||
'seedling_boxes_per_tan',
|
||||
'notes',
|
||||
'seed_material_name',
|
||||
'entries',
|
||||
'field_count',
|
||||
'total_seedling_boxes',
|
||||
'total_seed_kg',
|
||||
'crop_seed_inventory_kg',
|
||||
'variety_seed_inventory_kg',
|
||||
'remaining_seed_kg',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
@@ -117,28 +120,58 @@ class RiceTransplantPlanSerializer(serializers.ModelSerializer):
|
||||
|
||||
def get_total_seedling_boxes(self, obj):
|
||||
total = sum(
|
||||
entry.installed_seedling_boxes
|
||||
for entry in obj.entries.all()
|
||||
(
|
||||
entry.installed_seedling_boxes
|
||||
for entry in obj.entries.all()
|
||||
),
|
||||
Decimal('0'),
|
||||
)
|
||||
return str(total.quantize(Decimal('0.01')))
|
||||
|
||||
def get_total_seed_kg(self, obj):
|
||||
total = sum(
|
||||
(
|
||||
entry.installed_seedling_boxes
|
||||
* obj.default_seed_grams_per_box
|
||||
/ Decimal('1000')
|
||||
)
|
||||
for entry in obj.entries.all()
|
||||
(
|
||||
entry.installed_seedling_boxes
|
||||
* obj.default_seed_grams_per_box
|
||||
/ Decimal('1000')
|
||||
)
|
||||
for entry in obj.entries.all()
|
||||
),
|
||||
Decimal('0'),
|
||||
)
|
||||
return str(total.quantize(Decimal('0.001')))
|
||||
|
||||
def get_crop_seed_inventory_kg(self, obj):
|
||||
return str(obj.variety.crop.seed_inventory_kg)
|
||||
def get_variety_seed_inventory_kg(self, obj):
|
||||
return str(self._get_seed_inventory_kg(obj).quantize(Decimal('0.001')))
|
||||
|
||||
def get_remaining_seed_kg(self, obj):
|
||||
total_seed = Decimal(self.get_total_seed_kg(obj))
|
||||
return str((obj.variety.crop.seed_inventory_kg - total_seed).quantize(Decimal('0.001')))
|
||||
return str((self._get_seed_inventory_kg(obj) - total_seed).quantize(Decimal('0.001')))
|
||||
|
||||
def _get_seed_inventory_kg(self, obj):
|
||||
material = obj.variety.seed_material
|
||||
if material is None:
|
||||
return Decimal('0')
|
||||
|
||||
transactions = list(material.stock_transactions.all())
|
||||
increase = sum(
|
||||
(
|
||||
txn.quantity
|
||||
for txn in transactions
|
||||
if txn.transaction_type in StockTransaction.INCREASE_TYPES
|
||||
),
|
||||
Decimal('0'),
|
||||
)
|
||||
decrease = sum(
|
||||
(
|
||||
txn.quantity
|
||||
for txn in transactions
|
||||
if txn.transaction_type in StockTransaction.DECREASE_TYPES
|
||||
),
|
||||
Decimal('0'),
|
||||
)
|
||||
return increase - decrease
|
||||
|
||||
|
||||
class RiceTransplantPlanWriteSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -19,7 +19,7 @@ class CropViewSet(viewsets.ModelViewSet):
|
||||
|
||||
|
||||
class VarietyViewSet(viewsets.ModelViewSet):
|
||||
queryset = Variety.objects.all()
|
||||
queryset = Variety.objects.select_related('seed_material', 'crop').all()
|
||||
serializer_class = VarietySerializer
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class PlanViewSet(viewsets.ModelViewSet):
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def get_crops_with_varieties(self, request):
|
||||
crops = Crop.objects.prefetch_related('varieties').all()
|
||||
crops = Crop.objects.prefetch_related('varieties__seed_material').all()
|
||||
return Response(CropSerializer(crops, many=True).data)
|
||||
|
||||
|
||||
@@ -142,7 +142,12 @@ class RiceTransplantPlanViewSet(viewsets.ModelViewSet):
|
||||
queryset = RiceTransplantPlan.objects.select_related(
|
||||
'variety',
|
||||
'variety__crop',
|
||||
).prefetch_related('entries', 'entries__field')
|
||||
'variety__seed_material',
|
||||
).prefetch_related(
|
||||
'variety__seed_material__stock_transactions',
|
||||
'entries',
|
||||
'entries__field',
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.queryset
|
||||
|
||||
Reference in New Issue
Block a user