如何正确处理货币价值?

问题描述 投票:0回答:1

我有一个系统,所有交易金额都以整数(分)存储。 到目前为止,该功能运行良好,已记录了超过 2000 万笔交易。

但是,我最近遇到了一个用例,当转换为美分后,等效整数值变为零,从而导致少量问题

# Models
class Wallet(models.Model):
    balance = models.IntegerField()

class WalletTransaction(models.Model):
    wallet = models.ForeignKey(Wallet, on_delete=models.CASCADE)
    amount = models.IntegerField()  # Stored in cents
    transaction_type = models.CharField(max_length=50)

# Updating wallet balance and creating transaction
cost_price = 0.0025 
cost_price_in_cents = cost_price * 100  # Converts to 0.25 cents
wallet = Wallet.objects.get(id=1)
wallet.balance -= cost_price_in_cents
wallet.save()
WalletTransaction.objects.create(wallet=wallet, amount=cost_price_in_cents)

这里余额被扣除0.25,但在钱包交易历史中它是0。

python django
1个回答
0
投票

您应该转换到小数字段。使用当前的解决方案,您可以通过 2 个步骤来完成此操作:

  1. 添加新的小数字段:
class Wallet(models.Model):
    balance = models.IntegerField()
    balance_decimal = models.DecimalField(null=True)

class WalletTransaction(models.Model):
    wallet = models.ForeignKey(Wallet, on_delete=models.CASCADE)
    amount = models.IntegerField()  # Stored in cents
    amount_decimal = models.DecimalField(null=True)
    transaction_type = models.CharField(max_length=50)

并且不要忘记覆盖钱包和交易的逻辑:

cost_price = 0.0025
cost_price_in_cents = cost_price * 100
wallet = Wallet.objects.get(id=1)
wallet.balance -= cost_price_in_cents
wallet.balance_decimal -= cost_price
wallet.save()

WalletTransaction.objects.create(
    wallet=wallet, 
    amount=cost_price_in_cents, 
    amount_decimal=cost_price
)
  1. 开始将所有内容存储为十进制后,您就可以通过迁移进行转换了。这是迁移文件的示例:
from django.db import migrations, transaction

def populate_decimal_fields(apps, schema_editor):
    Wallet = apps.get_model('your_app_name', 'Wallet')
    WalletTransaction = apps.get_model('your_app_name', 'WalletTransaction')

    with transaction.atomic():
        for wallet in Wallet.objects.select_for_update():
            wallet.balance_decimal = wallet.balance/100
            wallet.save(update_fields=["balance_decimal"])

        for transaction in WalletTransaction.objects.select_for_update():
            transaction.amount_decimal = transaction.amount/ 100
            transaction.save(update_fields=["amount_decimal"])

class Migration(migrations.Migration):

    dependencies = [
        ('your_app_name', 'previous_migration_file'),
    ]

    operations = [
        migrations.AddField(
            model_name='wallet',
            name='balance_decimal',
            field=models.DecimalField(max_digits=12, decimal_places=2, null=True),
        ),
        migrations.AddField(
            model_name='wallettransaction',
            name='amount_decimal',
            field=models.DecimalField(max_digits=12, decimal_places=2, null=True),
        ),
        migrations.RunPython(populate_decimal_fields),
        migrations.AlterField(
            model_name='wallet',
            name='balance',
            field=models.IntegerField(null=True),
        ),
        migrations.AlterField(
            model_name='wallettransaction',
            name='amount',
            field=models.IntegerField(null=True),
        ),
    ]

注意:它没有得到很好的优化,因为所有钱包和交易都被 select_for_update 阻止

© www.soinside.com 2019 - 2024. All rights reserved.