テスト 結果 確定取消 API ✅ is_confirmed: false, confirmed_at: null USE トランザクション削除 ✅ current_stock が 27.5→32 に復帰 引当再作成 ✅ reserved_stock = 5.000 に復帰 追加した変更: stock_service.py:81-93 — unconfirm_spreading(): USE削除→確定フラグリセット→引当再作成 fertilizer/views.py — unconfirm アクション(POST /api/fertilizer/plans/{id}/unconfirm/) fertilizer/page.tsx — 一覧に「確定取消」ボタン(確定済み計画のみ表示) FertilizerEditPage.tsx — 編集画面ヘッダーに「確定取消」ボタン + 在庫情報再取得
101 lines
3.0 KiB
Python
101 lines
3.0 KiB
Python
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()
|
|
|
|
if plan.is_confirmed:
|
|
return
|
|
|
|
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')
|