Автоматически вычисляемый столбец на основе данных текущей и предыдущей строк, который будет использоваться для будущих строк SQL, Django

#python #mysql #django #erp

#python #mysql #django #erp

Вопрос:

Справочная информация: инструмент ERP, который будет содержать всю информацию о транзакциях, такую как начальный баланс и сумма транзакции, и конечный баланс пользователя. Начальный баланс текущей строки зависит от конечного баланса предыдущей строки. Итоговый баланс рассчитывается на основе сложения или вычитания начального баланса и суммы транзакции. Этот вычисленный баланс закрытия будет использоваться в качестве начального баланса следующей строки и так далее.

Примечание: информация о транзакции, такая как сумма транзакции, поступает из покупок продуктов, которые есть на складе

Проблема: считайте, что у меня есть 100 записей. По какой-то причине администратор хочет изменить сумму транзакции 1-й записи. Из-за этого мой конечный баланс первой строки изменится. Но проблема в том, что все остальные 99 строк зависят от баланса закрытия первой строки. Как создать таблицу SQL, которая решает эту проблему с данными в зависимости от столбца.

PS: Я использую Django в качестве фреймворка, но необработанный SQL-запрос и объяснение также в некоторой степени решат мою проблему.

Ответ №1:

Вы действительно должны опубликовать свои модели…

Но учитывая базовую структуру, подобную этой:

 from django.db import models, transaction
from django.utils import timezone


class Account(models.Model):
    iban = models.CharField(max_length=34, primary_key=True)

    @property
    def balance(self):
        return self.mutations.last().end


class AccountMutation(models.Model):
    id = models.BigAutoField(primary_key=True)
    account = models.ForeignKey(
        Account, on_delete=models.PROTECT, related_name="mutations"
    )
    start = models.DecimalField(decimal_places=4, max_digits=12)
    timestamp = models.DateTimeField(default=timezone.now)
    amount = models.DecimalField(decimal_places=4, max_digits=12)
    end = models.DecimalField(decimal_places=4, max_digits=12)
    objects = AccountMutationManager()

    class Meta:
        ordering = ("timestamp", "pk")
  

Мы можем реализовать пользовательский менеджер следующим образом:

 class AccountMutationManager(models.Manager):
    @transaction.atomic
    def recalculate(self, from_: "AccountMutation"):
        qs = self.filter(
            account=from_.account, timestamp__gte=from_.timestamp, id__gt=from_.id
        ).select_for_update()
        prev = from_
        for mutation in qs:
            mutation.start = prev.end
            mutation.end = mutation.start   mutation.amount
            mutation.save()
            prev = mutation
  

Конечно, это основано на мутациях на основе временных меток для определения порядка. Если вы связываете транзакции с предыдущим указателем, выбор немного отличается, и вам нужен метод reorder() в случае изменения метки времени. Но это действительно зависит от того, как он хранится.

Как правило, я бы не сохранял начальный и конечный баланс с мутациями, а делал бы их вычисляемыми полями, и каждая учетная запись имела бы начальный баланс для каждого «финансового года», с которого вы начинаете вычисления.

Некоторые пояснения:

  • Мы используем подход «все или ничего» с атомарными транзакциями
  • кроме того, мы блокируем все строки, select_for_update() которые будут затронуты, чтобы одновременно не могли выполняться два пересчета, которые влияют на одни и те же данные.
  • from_ была ли изменена мутация и предполагается, что она имеет правильный конечный баланс