from decimal import Decimal, InvalidOperation from django.db import transaction from django.utils import timezone from .models import StockTransaction @transaction.atomic def create_reserves_for_plan(plan): """施肥計画の引当を全置換で作り直す。""" StockTransaction.objects.filter( fertilization_plan=plan, transaction_type=StockTransaction.TransactionType.RESERVE, ).delete() occurred_on = ( plan.updated_at.date() if getattr(plan, 'updated_at', None) else timezone.localdate() ) for entry in plan.entries.select_related('fertilizer__material'): material = getattr(entry.fertilizer, 'material', None) if material is None: continue StockTransaction.objects.create( material=material, transaction_type=StockTransaction.TransactionType.RESERVE, quantity=entry.bags, occurred_on=occurred_on, note=f'施肥計画「{plan.name}」からの引当', fertilization_plan=plan, ) @transaction.atomic def delete_reserves_for_plan(plan): """施肥計画に紐づく引当のみ削除する。""" StockTransaction.objects.filter( fertilization_plan=plan, transaction_type=StockTransaction.TransactionType.RESERVE, ).delete() @transaction.atomic def confirm_spreading(plan, actual_entries): """引当を使用実績へ変換して施肥計画を確定済みにする。""" from apps.fertilizer.models import Fertilizer delete_reserves_for_plan(plan) for entry_data in actual_entries: actual_bags = _to_decimal(entry_data.get('actual_bags')) if actual_bags <= 0: continue fertilizer = ( Fertilizer.objects.select_related('material') .filter(id=entry_data['fertilizer_id']) .first() ) if fertilizer is None or fertilizer.material is None: continue StockTransaction.objects.create( material=fertilizer.material, transaction_type=StockTransaction.TransactionType.USE, quantity=actual_bags, occurred_on=timezone.localdate(), note=f'施肥計画「{plan.name}」散布確定', fertilization_plan=plan, ) plan.is_confirmed = True plan.confirmed_at = timezone.now() plan.save(update_fields=['is_confirmed', 'confirmed_at']) @transaction.atomic def unconfirm_spreading(plan): """散布確定を取り消し、USE トランザクションを削除して引当を再作成する。""" StockTransaction.objects.filter( fertilization_plan=plan, transaction_type=StockTransaction.TransactionType.USE, ).delete() plan.is_confirmed = False plan.confirmed_at = None plan.save(update_fields=['is_confirmed', 'confirmed_at']) create_reserves_for_plan(plan) def _to_decimal(value): try: return Decimal(str(value)) except (InvalidOperation, TypeError, ValueError): return Decimal('0')