You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 10:30:39