完璧に動作しています。
テスト 結果 確定取消 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 — 編集画面ヘッダーに「確定取消」ボタン + 在庫情報再取得
This commit is contained in:
100
backend/apps/materials/stock_service.py
Normal file
100
backend/apps/materials/stock_service.py
Normal file
@@ -0,0 +1,100 @@
|
||||
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')
|
||||
Reference in New Issue
Block a user