Django实现投资余额提现、到期校验及余额不更新问题修复
问题描述
以下是Model定义:
实现「投资金额提现」功能时存在两个核心问题:
- 执行
print(investment.investment_return)时显示对应金额已完成扣减,但仪表盘中的*Available Balance(可用余额)*即total_available_balance始终未同步更新 - 需要实现「仅投资到期后才可发起提现」的规则,当前到期时间设置逻辑为
self.due_date = datetime.now() + timedelta(seconds=5)
期望的可用余额计算逻辑为:
total_available_balance = Investment.objects.filter(is_active=False).aggregate( total_available_balance=Sum('investment_return'))['total_available_balance']
现有代码
Model 代码
from django.db import models from django.db.models import Max, Sum, F from datetime import datetime, timedelta class Investment(models.Model): PLAN_CHOICES = ( ("Basic - Daily 2% for 180 Days", "Basic - Daily 2% for 180 Days"), ("Premium - Daily 4% for 360 Days", "Premium - Daily 4% for 360 Days"), ) plan = models.CharField(max_length=100, choices=PLAN_CHOICES, null=True) deposit_amount = models.IntegerField(default=0, null=True) basic_interest = models.IntegerField(default=0, null=True) premium_interest = models.IntegerField(default=0, null=True) investment_return = models.IntegerField(default=0, null=True) withdraw_amount = models.IntegerField(default=0, null=True, blank=True) balance = models.IntegerField(default=0, null=True, blank=True) total_available_balance = models.IntegerField(default=0, null=True, blank=True) locked_balance = models.IntegerField(default=0, null=True, blank=True) investment_id = models.CharField(max_length=10, null=True, blank=True) is_active = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now=True, null=True) due_date = models.DateTimeField(null=True) def save(self, *args, **kwargs): if self.plan == "Basic - Daily 2% for 180 Days": self.basic_interest = self.deposit_amount * 365 * 0.02/2 self.investment_return = self.deposit_amount + self.basic_interest self.due_date = datetime.now() + timedelta(seconds=5) else: self.premium_interest = self.deposit_amount*365*0.04 self.investment_return = self.deposit_amount +self.premium_interest self.due_date = datetime.now() + timedelta(seconds=5) super(Investment, self).save(*args, **kwargs)
Model Forms 代码
from django import forms from .models import Investment class DateInput(forms.DateInput): input_type = 'date' class InvestmentForm(forms.ModelForm): class Meta: model = Investment fields = ['deposit_amount', 'plan'] class WithdrawalForm(forms.ModelForm): class Meta: model = Investment fields = ['withdraw_amount']
Views 代码
from django.shortcuts import get_object_or_404, redirect, render from django.db.models import Sum, F from django.contrib import messages from .forms import ( WithdrawalForm, InvestmentForm, ) from .models import ( Investment, ) def create_investment_view(request): if request.method == 'POST': investment_form = InvestmentForm(request.POST) if investment_form.is_valid(): investment = investment_form.save(commit=False) investment.is_active = True investment.save() messages.success(request, 'your investment of {} is successfull '.format(investment.deposit_amount)) else: messages.success(request, 'your investment is not successfull! Try again.') else: investment_form = InvestmentForm() context = {'investment_form': investment_form} return render(request, 'create-investment.html', context) def create_withdrawal_view(request): if request.method == 'POST': withdraw_form = WithdrawalForm(request.POST) if withdraw_form.is_valid(): investment = withdraw_form.save(commit=False) investment.investment_return -= investment.withdraw_amount investment.save() messages.success(request, 'your Withdrawal is successfull') else: messages.success(request, 'Your withdrawal is unsuccesfull. Try again') else: withdraw_form = WithdrawalForm() context = {'withdraw_form': withdraw_form} return render(request, 'create-withdrawal.html', context) def list_investments(request): investments = Investment.objects.all() total_invested = Investment.objects.aggregate( total_invested=Sum('deposit_amount')) total_withdrawned = Investment.objects.aggregate( total_withdrawned=Sum('withdraw_amount')) locked_total_balance = Investment.objects.filter(is_active=True).aggregate( locked_total_balance=Sum('investment_return'))['locked_total_balance'] total_available_balance = Investment.objects.filter(is_active=False).aggregate( total_available_balance=Sum('investment_return'))['total_available_balance'] context = { 'total_invested': total_invested, 'total_withdrawned': total_withdrawned, 'locked_total_balance': locked_total_balance, 'total_available_balance': total_available_balance, } return render(request, 'list-investments.html', context)
问题原因与修复方案
1. 可用余额不更新核心原因
- 投资到期后没有自动将
is_active字段从True改为False,到期投资始终被归类为锁定状态,不会被计入可用余额统计范围 - 提现逻辑存在本质缺陷:
WithdrawalForm没有绑定已有的投资实例,调用save(commit=False)会生成全新的空Investment对象,扣减操作完全没有作用到真实的到期投资数据上 - 额外逻辑错误:表单校验失败时错误使用了
success级别消息提示,会误导操作判断;created_at字段使用auto_now=True会导致每次修改记录时覆盖创建时间,影响到期时间计算
2. 到期提现限制实现逻辑
在提现流程增加两层强制校验:
- 校验目标投资记录
due_date早于当前时间,且is_active为False(已到期解锁) - 校验提现金额不超过该条记录剩余可提现金额,且数值大于0
修正后核心代码
到期投资自动解锁逻辑(加到仪表盘视图最前端,访问时自动批量解锁到期投资)
from django.utils import timezone # 批量将到期的投资设置为非活跃状态 Investment.objects.filter( is_active=True, due_date__lte=timezone.now() ).update(is_active=False)
修正后的提现视图
def create_withdrawal_view(request): if request.method == 'POST': withdraw_form = WithdrawalForm(request.POST) if withdraw_form.is_valid(): withdraw_amount = withdraw_form.cleaned_data['withdraw_amount'] # 查找当前可提现的最早到期投资,多用户系统需增加user=request.user过滤 investment = Investment.objects.filter( is_active=False, due_date__lte=timezone.now(), investment_return__gt=0 ).order_by('due_date').first() if not investment: messages.error(request, '暂无可用的到期投资可提现') return redirect('create_withdrawal') if withdraw_amount > investment.investment_return: messages.error(request, '提现金额超过当前可提现余额') return redirect('create_withdrawal') # 扣减对应投资的可提现金额,累计已提现金额 investment.investment_return -= withdraw_amount investment.withdraw_amount = (investment.withdraw_amount or 0) + withdraw_amount investment.save() messages.success(request, '提现申请提交成功') return redirect('list_investments') else: messages.error(request, '提现信息校验失败,请重新输入') else: withdraw_form = WithdrawalForm() # 传递当前可提现余额到提现页面 total_available = Investment.objects.filter( is_active=False, investment_return__gt=0 ).aggregate(total=Sum('investment_return'))['total'] or 0 context = {'withdraw_form': withdraw_form, 'available_balance': total_available} return render(request, 'create-withdrawal.html', context)
其他必要修正
将Model中created_at字段的auto_now=True改为auto_now_add=True,避免记录更新时覆盖创建时间;所有时间计算统一使用django.utils.timezone.now()代替原生datetime.now(),避免时区不一致导致的到期判断错误。
created_at = models.DateTimeField(auto_now_add=True, null=True)
内容的提问来源于stack exchange,提问作者Thaddeaus Iorbee
相关产品推荐
相关产品推荐

