"""跨 app 账目守护信号。 CreditReservation.task 是 OneToOneField(on_delete=CASCADE):AITask 被硬删(用户删项目 → AITask.project CASCADE 级联删任务、或直接删任务)时,预留行会被一并删掉。若该预留 还处于 ACTIVE,account.reserved_balance 里冻结的份额就永远退不回来 —— 这是资金审计 I2「冻结不平」的根因(ZWQ-Test / 演示团队实测漂移 ¥10 / ¥30,且随测试删项目持续增长)。 修法:在 AITask 删除前(pre_delete,级联删除同样触发)把仍 ACTIVE 的预留正常走 release_credit 释放 —— 退还冻结、落 RELEASE 流水,然后再让级联删掉预留行。 release_credit 自带行锁 + 幂等(非 ACTIVE 直接返回),并发安全。 """ from django.db.models.signals import pre_delete from django.dispatch import receiver @receiver(pre_delete, sender="ai.AITask", dispatch_uid="billing.release_reservation_before_task_delete") def release_reservation_before_task_delete(sender, instance, **kwargs): from apps.billing.models import CreditReservation from apps.billing.services.ledger import release_credit reservation = CreditReservation.objects.filter( task=instance, status=CreditReservation.Status.ACTIVE ).first() if reservation is not None: release_credit(reservation=reservation, reason="任务删除,释放未结预留")