init commit
This commit is contained in:
0
lottery/webapp/__init__.py
Normal file
0
lottery/webapp/__init__.py
Normal file
89
lottery/webapp/admin.py
Normal file
89
lottery/webapp/admin.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from django.contrib import admin, messages
|
||||
from django.urls import path, reverse
|
||||
from django.shortcuts import redirect
|
||||
from .models import Client, Invoice, BindingRequest, APISettings
|
||||
from .services import API_SYNC
|
||||
from django.utils import timezone
|
||||
|
||||
@admin.register(Client)
|
||||
class ClientAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'club_card_number', 'telegram_id')
|
||||
change_list_template = "admin/clients_change_list.html"
|
||||
|
||||
def get_urls(self):
|
||||
urls = super().get_urls()
|
||||
custom_urls = [
|
||||
path('sync-clients/', self.admin_site.admin_view(self.sync_clients), name='sync_clients'),
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
def sync_clients(self, request):
|
||||
syncer = API_SYNC()
|
||||
new_count = syncer.sync_clients()
|
||||
self.message_user(request, f"Синхронизировано. Добавлено новых клиентов: {new_count}", level=messages.INFO)
|
||||
return redirect("..")
|
||||
|
||||
|
||||
@admin.register(Invoice)
|
||||
class InvoiceAdmin(admin.ModelAdmin):
|
||||
list_display = ('api_id', 'client_name', 'sum', 'created_at', 'closed_at')
|
||||
change_list_template = "admin/invoices_change_list.html"
|
||||
|
||||
def get_urls(self):
|
||||
urls = super().get_urls()
|
||||
custom_urls = [
|
||||
path('sync-invoices/', self.admin_site.admin_view(self.sync_invoices), name='sync_invoices'),
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
def sync_invoices(self, request):
|
||||
syncer = API_SYNC()
|
||||
count = syncer.sync_invoices()
|
||||
self.message_user(request, f"Синхронизировано. Обновлено/создано записей: {count}", level=messages.INFO)
|
||||
return redirect("..")
|
||||
|
||||
@admin.action(description="Подтвердить выбранные заявки и обновить Telegram ID клиента")
|
||||
def approve_binding_requests(modeladmin, request, queryset):
|
||||
count = 0
|
||||
for binding_request in queryset:
|
||||
if binding_request.status != 'approved':
|
||||
binding_request.status = 'approved'
|
||||
binding_request.processed_at = timezone.now()
|
||||
binding_request.save()
|
||||
# Если у заявки связан клиент, обновляем его Telegram ID
|
||||
if binding_request.client:
|
||||
client = binding_request.client
|
||||
client.telegram_id = binding_request.telegram_chat_id
|
||||
client.save()
|
||||
count += 1
|
||||
modeladmin.message_user(request, f"Подтверждено {count} заявок", level=messages.INFO)
|
||||
|
||||
@admin.register(BindingRequest)
|
||||
class BindingRequestAdmin(admin.ModelAdmin):
|
||||
list_display = ('client_card', 'client', 'telegram_chat_id', 'status', 'created_at')
|
||||
actions = [approve_binding_requests]
|
||||
change_list_template = "admin/bindingrequests_change_list.html"
|
||||
|
||||
def get_urls(self):
|
||||
urls = super().get_urls()
|
||||
custom_urls = [
|
||||
# Пример кастомного URL для синхронизации заявок через API, если требуется
|
||||
path('sync-requests/', self.admin_site.admin_view(self.sync_requests), name='sync_requests'),
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
def sync_requests(self, request):
|
||||
syncer = API_SYNC()
|
||||
count = syncer.sync_binding_requests()
|
||||
self.message_user(request, f"Синхронизировано. Обновлено/создано записей: {count}", level=messages.INFO)
|
||||
return redirect("..")
|
||||
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
# Например, можно добавить количество заявок в контекст:
|
||||
extra_context['total_requests'] = BindingRequest.objects.count()
|
||||
return super().changelist_view(request, extra_context=extra_context)
|
||||
|
||||
@admin.register(APISettings)
|
||||
class APISettingsAdmin(admin.ModelAdmin):
|
||||
list_display = ('api_url', 'api_key')
|
||||
7
lottery/webapp/apps.py
Normal file
7
lottery/webapp/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WebappConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'webapp'
|
||||
verbose_name='Основноая информация'
|
||||
31
lottery/webapp/migrations/0001_initial.py
Normal file
31
lottery/webapp/migrations/0001_initial.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 12:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='APISettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('api_url', models.URLField(verbose_name='API URL')),
|
||||
('api_key', models.CharField(max_length=255, verbose_name='API KEY')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Client',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Имя')),
|
||||
('club_card_number', models.CharField(max_length=100, unique=True, verbose_name='Номер клубной карты')),
|
||||
('telegram_id', models.CharField(blank=True, max_length=50, null=True, verbose_name='Telegram ID')),
|
||||
],
|
||||
),
|
||||
]
|
||||
42
lottery/webapp/migrations/0002_invoice.py
Normal file
42
lottery/webapp/migrations/0002_invoice.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 10:01
|
||||
|
||||
import django.core.validators
|
||||
from decimal import Decimal
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Invoice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('api_id', models.CharField(max_length=255, unique=True, verbose_name='API Invoice ID')),
|
||||
('invoice_type', models.CharField(default='Invoice', max_length=50, verbose_name='Type')),
|
||||
('created_at', models.DateTimeField(verbose_name='Created at')),
|
||||
('closed_at', models.DateTimeField(blank=True, null=True, verbose_name='Closed at')),
|
||||
('ext_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='External ID')),
|
||||
('ext_type', models.CharField(blank=True, max_length=255, null=True, verbose_name='External Type')),
|
||||
('client_api_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='Client API ID')),
|
||||
('client_type', models.CharField(blank=True, max_length=50, null=True, verbose_name='Client Type')),
|
||||
('client_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Client Name')),
|
||||
('client_club_card_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='Club Card Number')),
|
||||
('sum', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='Sum')),
|
||||
('company_number', models.PositiveIntegerField(default=0, verbose_name='Company Number')),
|
||||
('bonus', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='Bonus')),
|
||||
('start_bonus', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='Start Bonus')),
|
||||
('deposit_sum', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='Deposit Sum')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Invoice',
|
||||
'verbose_name_plural': 'Invoices',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 10:52
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0002_invoice'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='client',
|
||||
options={'verbose_name': 'Клиент', 'verbose_name_plural': 'Клиенты'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='invoice',
|
||||
options={'ordering': ['-created_at'], 'verbose_name': 'Счет', 'verbose_name_plural': 'Счета'},
|
||||
),
|
||||
]
|
||||
18
lottery/webapp/migrations/0004_client_bot_admin.py
Normal file
18
lottery/webapp/migrations/0004_client_bot_admin.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-21 03:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0003_alter_client_options_alter_invoice_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='client',
|
||||
name='bot_admin',
|
||||
field=models.BooleanField(default=False, help_text='Является ли пользователь администратором бота'),
|
||||
),
|
||||
]
|
||||
28
lottery/webapp/migrations/0005_bindingrequest.py
Normal file
28
lottery/webapp/migrations/0005_bindingrequest.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-21 03:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0004_client_bot_admin'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BindingRequest',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('telegram_chat_id', models.CharField(help_text='Идентификатор чата Telegram пользователя', max_length=50)),
|
||||
('client_card', models.CharField(help_text='Номер клиентской карты', max_length=100)),
|
||||
('status', models.CharField(choices=[('pending', 'Ожидает проверки'), ('approved', 'Подтверждён'), ('rejected', 'Отклонён')], default='pending', max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('processed_at', models.DateTimeField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Запрос на сопоставление',
|
||||
'verbose_name_plural': 'Запросы на сопоставления',
|
||||
},
|
||||
),
|
||||
]
|
||||
19
lottery/webapp/migrations/0006_bindingrequest_client.py
Normal file
19
lottery/webapp/migrations/0006_bindingrequest_client.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-21 04:15
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0005_bindingrequest'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='bindingrequest',
|
||||
name='client',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='webapp.client'),
|
||||
),
|
||||
]
|
||||
18
lottery/webapp/migrations/0007_client_chat_disabled.py
Normal file
18
lottery/webapp/migrations/0007_client_chat_disabled.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-21 12:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0006_bindingrequest_client'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='client',
|
||||
name='chat_disabled',
|
||||
field=models.BooleanField(default=False, help_text='Если установлено, пользователь не может отправлять сообщения в чат.', verbose_name='Блокировка отправки сообщений'),
|
||||
),
|
||||
]
|
||||
18
lottery/webapp/migrations/0008_invoice_used.py
Normal file
18
lottery/webapp/migrations/0008_invoice_used.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-22 22:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0007_client_chat_disabled'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='invoice',
|
||||
name='used',
|
||||
field=models.BooleanField(default=False, verbose_name='Использован в розыгрыше'),
|
||||
),
|
||||
]
|
||||
0
lottery/webapp/migrations/__init__.py
Normal file
0
lottery/webapp/migrations/__init__.py
Normal file
83
lottery/webapp/models.py
Normal file
83
lottery/webapp/models.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from django.db import models
|
||||
from django.core.validators import MinValueValidator
|
||||
from decimal import Decimal
|
||||
class Client(models.Model):
|
||||
name = models.CharField("Имя", max_length=255)
|
||||
club_card_number = models.CharField("Номер клубной карты", max_length=100, unique=True)
|
||||
telegram_id = models.CharField("Telegram ID", max_length=50, blank=True, null=True)
|
||||
bot_admin = models.BooleanField(default=False, help_text="Является ли пользователь администратором бота")
|
||||
chat_disabled = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="Блокировка отправки сообщений",
|
||||
help_text="Если установлено, пользователь не может отправлять сообщения в чат."
|
||||
)
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.club_card_number})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Клиент"
|
||||
verbose_name_plural = "Клиенты"
|
||||
|
||||
class Invoice(models.Model):
|
||||
# Поля, полученные из API
|
||||
api_id = models.CharField("API Invoice ID", max_length=255, unique=True)
|
||||
invoice_type = models.CharField("Type", max_length=50, default="Invoice")
|
||||
created_at = models.DateTimeField("Created at")
|
||||
closed_at = models.DateTimeField("Closed at", null=True, blank=True)
|
||||
ext_id = models.CharField("External ID", max_length=255, blank=True, null=True)
|
||||
ext_type = models.CharField("External Type", max_length=255, blank=True, null=True)
|
||||
|
||||
# Данные клиента, полученные из объекта "client"
|
||||
client_api_id = models.CharField("Client API ID", max_length=255, blank=True, null=True)
|
||||
client_type = models.CharField("Client Type", max_length=50, blank=True, null=True)
|
||||
client_name = models.CharField("Client Name", max_length=255, blank=True, null=True)
|
||||
client_club_card_number = models.CharField("Club Card Number", max_length=100, blank=True, null=True)
|
||||
|
||||
# Финансовые поля
|
||||
sum = models.DecimalField("Sum", max_digits=10, decimal_places=2, default=Decimal("0.00"),
|
||||
validators=[MinValueValidator(Decimal("0.00"))])
|
||||
company_number = models.PositiveIntegerField("Company Number", default=0)
|
||||
bonus = models.DecimalField("Bonus", max_digits=10, decimal_places=2, null=True, blank=True,
|
||||
validators=[MinValueValidator(Decimal("0.00"))])
|
||||
start_bonus = models.DecimalField("Start Bonus", max_digits=10, decimal_places=2, null=True, blank=True,
|
||||
validators=[MinValueValidator(Decimal("0.00"))])
|
||||
deposit_sum = models.DecimalField("Deposit Sum", max_digits=10, decimal_places=2, default=Decimal("0.00"),
|
||||
validators=[MinValueValidator(Decimal("0.00"))])
|
||||
notes = models.TextField("Notes", blank=True, null=True)
|
||||
used = models.BooleanField(default=False, verbose_name="Использован в розыгрыше")
|
||||
def __str__(self):
|
||||
return f"{self.ext_id} / {self.client_name or 'Unknown Client'} ({self.client_club_card_number})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Счет"
|
||||
verbose_name_plural = "Счета"
|
||||
ordering = ['-created_at']
|
||||
|
||||
|
||||
class APISettings(models.Model):
|
||||
api_url = models.URLField("API URL")
|
||||
api_key = models.CharField("API KEY", max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return f"Настройки API: {self.api_url}"
|
||||
|
||||
class BindingRequest(models.Model):
|
||||
STATUS_CHOICES = (
|
||||
('pending', 'Ожидает проверки'),
|
||||
('approved', 'Подтверждён'),
|
||||
('rejected', 'Отклонён'),
|
||||
)
|
||||
|
||||
telegram_chat_id = models.CharField(max_length=50, help_text="Идентификатор чата Telegram пользователя")
|
||||
client_card = models.CharField(max_length=100, help_text="Номер клиентской карты")
|
||||
client = models.ForeignKey(Client, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
processed_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"КК: {self.client_card} | Статус: {self.get_status_display()}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Запрос на сопоставление"
|
||||
verbose_name_plural = "Запросы на сопоставления"
|
||||
184
lottery/webapp/services.py
Normal file
184
lottery/webapp/services.py
Normal file
@@ -0,0 +1,184 @@
|
||||
import json
|
||||
import logging
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from .models import APISettings, Client, Invoice
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
if not logger.handlers:
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
|
||||
class APIClient:
|
||||
"""
|
||||
Класс для подключения к API и получения данных.
|
||||
Данные API_URL и API_KEY берутся из модели APISettings.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.settings = APISettings.objects.first()
|
||||
if not self.settings:
|
||||
logger.error("Настройки API не сконфигурированы. Заполните модель APISettings.")
|
||||
raise ImproperlyConfigured("Настройки API не сконфигурированы.")
|
||||
self.api_url = self.settings.api_url.rstrip("/")
|
||||
self.api_key = self.settings.api_key
|
||||
self.headers = {"X-API-Key": self.api_key}
|
||||
logger.debug(f"APIClient инициализирован с базовым URL: {self.api_url}")
|
||||
|
||||
def get_clients(self):
|
||||
url = f"{self.api_url}/api/clients"
|
||||
logger.debug(f"Запрос клиентов по адресу: {url} с заголовками: {self.headers}")
|
||||
try:
|
||||
response = requests.get(url, headers=self.headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
logger.debug("Запрос клиентов успешно выполнен. Данные получены.")
|
||||
return data
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Ошибка при запросе клиентов: {e}")
|
||||
return None
|
||||
|
||||
def get_invoices(self):
|
||||
url = f"{self.api_url}/api/invoices"
|
||||
logger.debug(f"Запрос счетов по адресу: {url} с заголовками: {self.headers}")
|
||||
try:
|
||||
response = requests.get(url, headers=self.headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
logger.debug("Запрос счетов успешно выполнен. Данные получены.")
|
||||
return data
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Ошибка при запросе счетов: {e}")
|
||||
return None
|
||||
|
||||
|
||||
class API_SYNC:
|
||||
"""
|
||||
Класс для импорта и обновления данных из API.
|
||||
Методы sync_clients и sync_invoices обновляют существующие записи или создают новые.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.logger = logger
|
||||
|
||||
def sync_clients(self):
|
||||
api_client = APIClient()
|
||||
data = api_client.get_clients()
|
||||
new_or_updated = 0
|
||||
|
||||
if isinstance(data, dict):
|
||||
clients_list = data.get("member", [])
|
||||
self.logger.debug(f"Извлечен список клиентов из ключа 'member', найдено записей: {len(clients_list)}")
|
||||
elif isinstance(data, list):
|
||||
clients_list = data
|
||||
self.logger.debug(f"Получен список клиентов (list), количество записей: {len(clients_list)}")
|
||||
else:
|
||||
self.logger.error("Неожиданный формат данных клиентов от API: %s", type(data))
|
||||
return new_or_updated
|
||||
|
||||
for index, item in enumerate(clients_list, start=1):
|
||||
self.logger.debug(f"Обработка записи клиента {index}: {item}")
|
||||
if isinstance(item, str):
|
||||
try:
|
||||
item = json.loads(item)
|
||||
self.logger.debug(f"Запись клиента {index} успешно преобразована из строки в словарь.")
|
||||
except Exception as e:
|
||||
self.logger.error("Невозможно преобразовать запись клиента %s в словарь: %s; ошибка: %s", index, item, e)
|
||||
continue
|
||||
|
||||
if isinstance(item, dict):
|
||||
club_card_number = item.get("club_card_num")
|
||||
if not club_card_number:
|
||||
self.logger.warning("Запись клиента %s пропущена: отсутствует club_card_num. Запись: %s", index, item)
|
||||
continue
|
||||
|
||||
# Используем update_or_create для обновления существующей записи
|
||||
obj, created = Client.objects.update_or_create(
|
||||
club_card_number=club_card_number,
|
||||
defaults={
|
||||
'name': item.get("name"),
|
||||
'telegram_id': item.get("telegram_id"),
|
||||
}
|
||||
)
|
||||
new_or_updated += 1
|
||||
if created:
|
||||
self.logger.info("Запись клиента %s создана: club_card_num %s.", index, club_card_number)
|
||||
else:
|
||||
self.logger.info("Запись клиента %s обновлена: club_card_num %s.", index, club_card_number)
|
||||
else:
|
||||
self.logger.error("Запись клиента %s имеет неожиданный тип: %s. Значение: %s", index, type(item), item)
|
||||
return new_or_updated
|
||||
|
||||
def sync_invoices(self):
|
||||
api_client = APIClient()
|
||||
data = api_client.get_invoices()
|
||||
new_or_updated = 0
|
||||
|
||||
if isinstance(data, dict):
|
||||
invoices_list = data.get("member", [])
|
||||
self.logger.debug(f"Извлечен список счетов из ключа 'member', найдено записей: {len(invoices_list)}")
|
||||
elif isinstance(data, list):
|
||||
invoices_list = data
|
||||
self.logger.debug(f"Получен список счетов (list), количество записей: {len(invoices_list)}")
|
||||
else:
|
||||
self.logger.error("Неожиданный формат данных счетов от API: %s", type(data))
|
||||
return new_or_updated
|
||||
|
||||
for index, invoice in enumerate(invoices_list, start=1):
|
||||
self.logger.debug(f"Обработка счета {index}: {invoice}")
|
||||
if isinstance(invoice, str):
|
||||
try:
|
||||
invoice = json.loads(invoice)
|
||||
self.logger.debug(f"Счет {index} успешно преобразован из строки в словарь.")
|
||||
except Exception as e:
|
||||
self.logger.error("Невозможно преобразовать счет %s в словарь: %s; ошибка: %s", index, invoice, e)
|
||||
continue
|
||||
if not isinstance(invoice, dict):
|
||||
self.logger.error("Счет %s имеет неожиданный тип: %s", index, type(invoice))
|
||||
continue
|
||||
|
||||
api_id = invoice.get("@id")
|
||||
if not api_id:
|
||||
self.logger.warning("Счет %s пропущен: отсутствует '@id'.", index)
|
||||
continue
|
||||
|
||||
# Извлекаем данные клиента из вложенного объекта
|
||||
client_data = invoice.get("client", {})
|
||||
client_name = client_data.get("name")
|
||||
client_club_card_number = client_data.get("club_card_num")
|
||||
|
||||
# Приводим даты (при необходимости, можно использовать парсинг)
|
||||
created_at = invoice.get("created_at")
|
||||
closed_at = invoice.get("closed_at")
|
||||
if closed_at in [None, "N/A"]:
|
||||
closed_at = None
|
||||
|
||||
defaults = {
|
||||
'invoice_type': invoice.get("@type"),
|
||||
'created_at': created_at,
|
||||
'closed_at': closed_at,
|
||||
'ext_id': invoice.get("ext_id"),
|
||||
'ext_type': invoice.get("ext_type"),
|
||||
'client_name': client_name,
|
||||
'client_club_card_number': client_club_card_number,
|
||||
'sum': invoice.get("sum"),
|
||||
'company_number': invoice.get("comp_num"),
|
||||
'bonus': invoice.get("bonus"),
|
||||
'start_bonus': invoice.get("start_bonus"),
|
||||
'deposit_sum': invoice.get("deposit_sum"),
|
||||
'notes': invoice.get("notes"),
|
||||
}
|
||||
|
||||
obj, created = Invoice.objects.update_or_create(
|
||||
api_id=api_id,
|
||||
defaults=defaults
|
||||
)
|
||||
new_or_updated += 1
|
||||
if created:
|
||||
self.logger.info("Счет %s создан: api_id=%s", index, api_id)
|
||||
else:
|
||||
self.logger.info("Счет %s обновлен: api_id=%s", index, api_id)
|
||||
return new_or_updated
|
||||
3
lottery/webapp/tests.py
Normal file
3
lottery/webapp/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
7
lottery/webapp/urls.py
Normal file
7
lottery/webapp/urls.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
# Другие URL вашего приложения
|
||||
path('filtered-invoices/', views.filtered_invoices, name='filtered_invoices'),
|
||||
]
|
||||
60
lottery/webapp/views.py
Normal file
60
lottery/webapp/views.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.http import require_GET
|
||||
from .models import Invoice
|
||||
import datetime
|
||||
|
||||
@require_GET
|
||||
def filtered_invoices(request):
|
||||
"""
|
||||
Фильтрует счета по начальной/конечной дате и имени клиента,
|
||||
возвращает JSON с полем "member" как список счетов со всеми полями.
|
||||
"""
|
||||
start_date = request.GET.get('start_date')
|
||||
end_date = request.GET.get('end_date')
|
||||
client = request.GET.get('client')
|
||||
|
||||
qs = Invoice.objects.all()
|
||||
|
||||
if start_date:
|
||||
try:
|
||||
start_date_obj = datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
||||
qs = qs.filter(created_at__gte=start_date_obj)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if end_date:
|
||||
try:
|
||||
end_date_obj = datetime.datetime.strptime(end_date, "%Y-%m-%d")
|
||||
qs = qs.filter(created_at__lte=end_date_obj)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if client:
|
||||
qs = qs.filter(client_name__icontains=client)
|
||||
|
||||
invoices = []
|
||||
for invoice in qs:
|
||||
invoices.append({
|
||||
'api_id': invoice.api_id, # Номер счета (API ID)
|
||||
'invoice_type': invoice.invoice_type,
|
||||
'created_at': invoice.created_at.strftime('%Y-%m-%d %H:%M:%S') if invoice.created_at else '',
|
||||
'closed_at': invoice.closed_at.strftime('%Y-%m-%d %H:%M:%S') if invoice.closed_at else 'Не закрыт!',
|
||||
'ext_id': invoice.ext_id,
|
||||
'ext_type': invoice.ext_type,
|
||||
'client': {
|
||||
'name': invoice.client_name,
|
||||
'club_card_num': invoice.client_club_card_number,
|
||||
},
|
||||
'sum': str(invoice.sum),
|
||||
'company_number': invoice.company_number,
|
||||
'bonus': str(invoice.bonus) if invoice.bonus is not None else '',
|
||||
'start_bonus': str(invoice.start_bonus) if invoice.start_bonus is not None else '',
|
||||
'deposit_sum': str(invoice.deposit_sum),
|
||||
'notes': invoice.notes,
|
||||
})
|
||||
|
||||
data = {
|
||||
"member": invoices,
|
||||
"totalItems": qs.count(),
|
||||
}
|
||||
return JsonResponse(data)
|
||||
Reference in New Issue
Block a user