Files
octopus/myproject/orders/models/payment.py
Andrey Smakotin 5ead7fdd2e Реализация системы кошелька клиента для переплат
- Добавлено поле wallet_balance в модель Customer
- Создана модель WalletTransaction для истории операций
- Реализован сервис WalletService с методами:
  * add_overpayment - автоматическое зачисление переплаты
  * pay_with_wallet - оплата заказа из кошелька
  * adjust_balance - ручная корректировка баланса
- Интеграция с Payment.save() для автоматической обработки переплат
- UI для оплаты из кошелька в деталях заказа
- Отображение баланса и долга на странице клиента
- Админка с inline транзакций и запретом ручного создания
- Добавлен способ оплаты account_balance
- Миграция 0004 для customers приложения
2025-11-26 14:47:11 +03:00

153 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from accounts.models import CustomUser
class PaymentMethod(models.Model):
"""
Способ оплаты заказа.
Справочник для управления доступными методами оплаты.
"""
# Код для программного доступа
code = models.SlugField(
unique=True,
max_length=50,
verbose_name="Код способа оплаты",
help_text="Уникальный идентификатор (например: 'cash_to_courier', 'card_to_courier')"
)
# Отображаемое название
name = models.CharField(
max_length=100,
verbose_name="Название способа оплаты"
)
# Описание
description = models.TextField(
blank=True,
verbose_name="Описание",
help_text="Дополнительная информация о способе оплаты"
)
# Активность
is_active = models.BooleanField(
default=True,
verbose_name="Активен",
help_text="Отключенные способы оплаты не отображаются при создании заказа"
)
# Порядок отображения
order = models.PositiveIntegerField(
default=0,
verbose_name="Порядок отображения"
)
# Системный флаг
is_system = models.BooleanField(
default=False,
verbose_name="Системный",
help_text="Системные способы оплаты нельзя удалить через интерфейс"
)
# Аудит
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(
CustomUser,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='created_payment_methods',
verbose_name="Создано"
)
class Meta:
verbose_name = "Способ оплаты"
verbose_name_plural = "Способы оплаты"
ordering = ['order', 'name']
indexes = [
models.Index(fields=['code']),
models.Index(fields=['is_active']),
models.Index(fields=['order']),
]
def __str__(self):
return self.name
class Payment(models.Model):
"""
Платеж по заказу.
Хранит историю всех платежей, включая частичные оплаты.
Поддерживает смешанную оплату (несколько платежей разными способами на один заказ).
"""
order = models.ForeignKey(
'Order',
on_delete=models.CASCADE,
related_name='payments',
verbose_name="Заказ"
)
amount = models.DecimalField(
max_digits=10,
decimal_places=2,
verbose_name="Сумма платежа"
)
payment_method = models.ForeignKey(
'PaymentMethod',
on_delete=models.PROTECT,
related_name='payments',
verbose_name="Способ оплаты",
help_text="Способ оплаты данного платежа"
)
payment_date = models.DateTimeField(
auto_now_add=True,
verbose_name="Дата и время платежа"
)
created_by = models.ForeignKey(
CustomUser,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='payments_created',
verbose_name="Принял платеж"
)
notes = models.TextField(
blank=True,
null=True,
verbose_name="Примечания",
help_text="Дополнительная информация о платеже"
)
class Meta:
verbose_name = "Платеж"
verbose_name_plural = "Платежи"
ordering = ['-payment_date']
indexes = [
models.Index(fields=['order']),
models.Index(fields=['payment_date']),
]
def __str__(self):
return f"Платеж {self.amount} руб. по заказу #{self.order.order_number}"
def save(self, *args, **kwargs):
"""При сохранении платежа обновляем сумму оплаты в заказе"""
super().save(*args, **kwargs)
# Пересчитываем общую сумму оплаты в заказе
self.order.amount_paid = sum(p.amount for p in self.order.payments.all())
self.order.update_payment_status()
# Нормализация переплаты: лишнее в кошелёк, amount_paid = total_amount
try:
from customers.services.wallet_service import WalletService
WalletService.add_overpayment(self.order, self.created_by)
except Exception:
# Если обработка переплаты не удалась, продолжаем без ошибок
pass