Реализована полноценная система оплаты для POS-терминала

Добавлена интеграция оплаты в POS с поддержкой одиночной и смешанной оплаты,
работой с кошельком клиента и автоматическим созданием заказов.

Backend изменения:
- TransactionService: добавлены методы get_available_payment_methods() и create_multiple_payments()
  для фильтрации способов оплаты и атомарного создания нескольких платежей
- POS API: новый endpoint pos_checkout() для создания заказов со статусом "Выполнен"
  с обработкой платежей, освобождением блокировок и очисткой корзины
- Template tags: payment_tags.py для получения способов оплаты в шаблонах

Frontend изменения:
- PaymentWidget: переиспользуемый ES6 класс с поддержкой single/mixed режимов,
  автоматической валидацией и интеграцией с кошельком клиента
- terminal.html: компактное модальное окно (70vw) с оптимизированной компоновкой,
  удален функционал скидок, добавлен показ баланса кошелька
- terminal.js: динамическая загрузка PaymentWidget, интеграция с backend API,
  обработка успешной оплаты и ошибок

Поддерживаемые способы оплаты: наличные, карта, онлайн, баланс счёта.
Смешанная оплата позволяет комбинировать несколько способов в одной транзакции.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-03 15:38:35 +03:00
parent 9dab280def
commit 1cda9086d0
7 changed files with 865 additions and 193 deletions

View File

@@ -235,3 +235,74 @@ class TransactionService:
Decimal: Сумма, которую можно вернуть
"""
return max(order.amount_paid, Decimal('0'))
@staticmethod
def get_available_payment_methods(exclude_codes=None, only_active=True):
"""
Получить список доступных способов оплаты.
Args:
exclude_codes: list[str] - коды для исключения (например ['legal_entity'])
only_active: bool - только активные методы
Returns:
QuerySet[PaymentMethod]
"""
from orders.models import PaymentMethod
qs = PaymentMethod.objects.all()
if only_active:
qs = qs.filter(is_active=True)
if exclude_codes:
qs = qs.exclude(code__in=exclude_codes)
return qs.order_by('order', 'name')
@staticmethod
@transaction.atomic
def create_multiple_payments(order, payments_list, user):
"""
Создать несколько платежей за одну транзакцию (смешанная оплата).
Args:
order: Order
payments_list: list[dict] - [{'payment_method': code_or_object, 'amount': Decimal, 'notes': str}, ...]
user: CustomUser
Returns:
list[Transaction]
Raises:
ValidationError: если сумма превышает amount_due или недостаточно средств
"""
from orders.models import Transaction
transactions = []
total_amount = Decimal('0')
# Валидация общей суммы
for payment_data in payments_list:
amount = _quantize(payment_data['amount'])
if amount <= 0:
raise ValueError(f'Сумма платежа должна быть положительной: {amount}')
total_amount += amount
if total_amount > order.amount_due:
raise ValidationError(
f'Общая сумма платежей ({total_amount}) превышает сумму к оплате ({order.amount_due})'
)
# Создаём транзакции
for payment_data in payments_list:
txn = TransactionService.create_payment(
order=order,
amount=payment_data['amount'],
payment_method=payment_data['payment_method'],
user=user,
notes=payment_data.get('notes')
)
transactions.append(txn)
return transactions