/
ilnar_g
/
111
Обзор
Документация
Войти
/
ilnar_g
/
111
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
models.py
392 строки
16 KB
ilnar_g
upload files
26 сен 2025, 01:46
26 сен 2025, 01:46
3fd377a
Код
Авторство
О чём код?
from django.db import models from django.utils import timezone from decimal import Decimal from django.core.validators import MinValueValidator import secrets from django.contrib.auth.hashers import make_password, check_password class Counterparty(models.Model): COUNTERPARTY_TYPE = ( ('individual', 'Физическое лицо'), ('self_employed', 'Самозанятый'), ('entrepreneur', 'ИП'), ('legal', 'Юридическое лицо'), ) type = models.CharField(max_length=20, choices=COUNTERPARTY_TYPE, verbose_name='Тип контрагента') name = models.CharField(max_length=255, verbose_name='Наименование / ФИО') inn = models.CharField(max_length=12, unique=True, verbose_name='ИНН') registration_number = models.PositiveIntegerField(unique=True, verbose_name='Порядковый номер') contact_info = models.CharField(max_length=500, blank=True, verbose_name='Контактная информация') # Token-based аутентификация api_token = models.CharField(max_length=64, unique=True, blank=True, verbose_name='API Токен') telegram_id = models.BigIntegerField(unique=True, blank=True, null=True, verbose_name='Telegram ID') telegram_username = models.CharField(max_length=32, blank=True, verbose_name='Telegram username') # Добавляем поле для пароля password = models.CharField(max_length=128, blank=True, verbose_name='Пароль') is_active = models.BooleanField(default=True, verbose_name='Активный') created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата добавления') class Meta: verbose_name = '3. Контрагент' verbose_name_plural = '3. Контрагенты' ordering = ['registration_number'] def __str__(self): return f"{self.registration_number}. {self.name}" def generate_api_token(self): """Генерация нового API токена""" self.api_token = secrets.token_urlsafe(48) self.save() return self.api_token def revoke_api_token(self): """Отзыв API токена""" self.api_token = '' self.save() def set_password(self, raw_password): """Установка пароля""" self.password = make_password(raw_password) self.save() def check_password(self, raw_password): """Проверка пароля""" return check_password(raw_password, self.password) def has_usable_password(self): """Проверка, установлен ли пароль""" return bool(self.password) def get_auth_info(self): """Информация для аутентификации""" return { 'id': self.id, 'name': self.name, 'inn': self.inn, 'registration_number': self.registration_number, 'telegram_username': self.telegram_username, } class Contract(models.Model): counterparty = models.ForeignKey( Counterparty, on_delete=models.CASCADE, related_name='contracts', verbose_name='Контрагент' ) amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='Сумма договора') conclusion_date = models.DateField(verbose_name='Дата заключения') # Убрал default=timezone.now contract_number = models.CharField( max_length=20, unique=True, blank=True, verbose_name='Номер договора' ) booking_month = models.CharField(max_length=2, verbose_name='Месяц бронирования', blank=True) booking_year = models.CharField(max_length=4, verbose_name='Год бронирования', blank=True) created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') class Meta: verbose_name = '6. Договор' verbose_name_plural = '6. Договоры' ordering = ['-conclusion_date'] def __str__(self): return f"Договор {self.get_contract_number()}" def get_contract_number(self): """Автогенерация номера договора""" if not self.contract_number and self.booking_month and self.booking_year: return f"{self.counterparty.registration_number}/{self.booking_month}{self.booking_year}" return self.contract_number or "Без номера" def save(self, *args, **kwargs): """Автоматически генерируем номер договора при сохранении""" if not self.contract_number and self.booking_month and self.booking_year: self.contract_number = f"{self.counterparty.registration_number}/{self.booking_month}{self.booking_year}" super().save(*args, **kwargs) def get_total_payments(self): return self.payments.aggregate(total=models.Sum('amount'))['total'] or Decimal('0') def get_balance(self): return self.amount - self.get_total_payments() class Payment(models.Model): PAYMENT_METHOD = ( ('sbp', 'СБП'), ('bank_transfer', 'Перечисление на р/с'), ('cash', 'Наличные'), ) contract = models.ForeignKey( Contract, on_delete=models.CASCADE, related_name='payments', verbose_name='Договор' ) amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='Сумма платежа') payment_date = models.DateTimeField(default=timezone.now, verbose_name='Дата и время платежа') method = models.CharField(max_length=20, choices=PAYMENT_METHOD, verbose_name='Способ оплаты') commission = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name='Комиссия') receipt_fee = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name='Стоимость чека') created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = '7. Платёж' verbose_name_plural = '7. Платежи' ordering = ['-payment_date'] def __str__(self): return f"Платёж {self.amount} руб. ({self.get_method_display()})" def save(self, *args, **kwargs): if self.method == 'sbp': self.commission = self.amount * Decimal('0.007') self.receipt_fee = Decimal('14.00') else: self.commission = Decimal('0.00') self.receipt_fee = Decimal('0.00') super().save(*args, **kwargs) class Community(models.Model): """Модель для представления сообществ.""" name = models.CharField(max_length=255, verbose_name='Название сообщества') link = models.URLField(max_length=500, verbose_name='Ссылка на сообщество') is_active = models.BooleanField(default=True, verbose_name='Активно') created_at = models.DateTimeField(auto_now_add=True, verbose_name='Даа создания') class Meta: verbose_name = '1. Сообщество' verbose_name_plural = '1. Сообщества' ordering = ['name'] def __str__(self): return self.name class CommunityTariff(models.Model): """Тарифная сетка для сообщества БЕЗ версионности""" TIME_SLOTS = ( ('06:40', '06:40'), ('09:20', '09:20'), ('12:00', '12:00'), ('14:40', '14:40'), ('17:20', '17:20'), ('20:00', '20:00'), ) community = models.ForeignKey( Community, on_delete=models.CASCADE, related_name='tariffs', verbose_name='Сообщество' ) # УБИРАЕМ tariff_version date_from = models.DateField(default=timezone.now, verbose_name='Дата начала действия') time_slot = models.CharField(max_length=5, choices=TIME_SLOTS, verbose_name='Временной слот') # Цены (оставляем как есть) single_post_price = models.DecimalField( max_digits=10, decimal_places=2, verbose_name='Цена за 1 пост', null=True, blank=True, default=None ) min_3_posts_price = models.DecimalField( max_digits=10, decimal_places=2, verbose_name='Цена от 3 постов', null=True, blank=True, default=None ) min_10_posts_price = models.DecimalField( max_digits=10, decimal_places=2, verbose_name='Цена от 10 постов', null=True, blank=True, default=None ) min_25_posts_price = models.DecimalField( max_digits=10, decimal_places=2, verbose_name='Цена от 25 постов', null=True, blank=True, default=None ) min_50_posts_price = models.DecimalField( max_digits=10, decimal_places=2, verbose_name='Цена от 50 постов', null=True, blank=True, default=None ) min_100_posts_price = models.DecimalField( max_digits=10, decimal_places=2, verbose_name='Цена от 100 постов', null=True, blank=True, default=None ) is_active = models.BooleanField(default=True, verbose_name='Активный тариф') created_at = models.DateTimeField(default=timezone.now, verbose_name='Дата создания') class Meta: verbose_name = '2. Тариф' verbose_name_plural = '2. Тарифы' # Меняем уникальность - теперь только community + time_slot unique_together = ['community', 'time_slot'] ordering = ['community', 'time_slot'] # Убираем сортировку по версии def __str__(self): # Упрощаем строковое представление return f"Тариф {self.community.name} - {self.time_slot}" def save(self, *args, **kwargs): # Упрощаем логику активации if self.is_active and self.date_from: CommunityTariff.objects.filter( community=self.community, time_slot=self.time_slot, is_active=True ).exclude(pk=self.pk).update(is_active=False) super().save(*args, **kwargs) # Остальные методы остаются без изменений def get_current_price(self, posts_count): """Возвращает цену в зависимости от количества постов""" if posts_count >= 100 and self.min_100_posts_price is not None: return self.min_100_posts_price elif posts_count >= 50 and self.min_50_posts_price is not None: return self.min_50_posts_price elif posts_count >= 25 and self.min_25_posts_price is not None: return self.min_25_posts_price elif posts_count >= 10 and self.min_10_posts_price is not None: return self.min_10_posts_price elif posts_count >= 3 and self.min_3_posts_price is not None: return self.min_3_posts_price elif self.single_post_price is not None: return self.single_post_price else: return None def is_zero_tariff(self): """Проверяет, является ли тариф нулевым""" prices_to_check = [ self.single_post_price, self.min_3_posts_price, self.min_10_posts_price, self.min_25_posts_price, self.min_50_posts_price, self.min_100_posts_price ] for price in prices_to_check: if price is not None and price > 0: return False return True class Order(models.Model): """Модель заказов - один заказ может содержать несколько выходов рекламы""" counterparty = models.ForeignKey( Counterparty, on_delete=models.CASCADE, verbose_name='Контрагент', related_name='orders' ) contract = models.ForeignKey( Contract, on_delete=models.SET_NULL, verbose_name='Договор', null=True, blank=True, related_name='orders' ) order_number = models.PositiveIntegerField(verbose_name='Номер заказа', default=0) total_amount = models.DecimalField( max_digits=15, decimal_places=2, default=0, verbose_name='Общая сумма заказа' ) slots_count = models.PositiveIntegerField(default=0, verbose_name='Количество выходов') created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания заказа') class Meta: verbose_name = '4. Заказ' verbose_name_plural = '4. Заказы' ordering = ['-created_at'] def __str__(self): return f"Заказ #{self.order_number} - {self.counterparty.name} - {self.total_amount} руб." class OrderSlot(models.Model): """Отдельная модель для хранения каждого выхода рекламы в рамках заказа""" order = models.ForeignKey( Order, on_delete=models.CASCADE, verbose_name='Заказ', related_name='slots' ) community = models.ForeignKey( Community, on_delete=models.CASCADE, verbose_name='Сообщество' ) date = models.DateField(verbose_name='Дата показа') time_slot = models.CharField(max_length=5, choices=CommunityTariff.TIME_SLOTS, verbose_name='Временной слот') community_tariff = models.ForeignKey( CommunityTariff, on_delete=models.CASCADE, verbose_name='Тариф сообщества', null=True, blank=True ) price = models.DecimalField( max_digits=10, decimal_places=2, verbose_name='Цена выхода' ) is_confirmed = models.BooleanField(default=True, verbose_name='Подтверждено') created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = '5. Выход рекламы' verbose_name_plural = '5. Выходы рекламы' unique_together = ['community', 'date', 'time_slot'] ordering = ['date', 'time_slot'] def __str__(self): return f"{self.community.name} - {self.date} {self.time_slot} - {self.price} руб." class MonthlyCalendar(models.Model): MONTH_CHOICES = [ (1, 'Январь'), (2, 'Февраль'), (3, 'Март'), (4, 'Апрель'), (5, 'Май'), (6, 'Июнь'), (7, 'Июль'), (8, 'Август'), (9, 'Сентябрь'), (10, 'Октябрь'), (11, 'Ноябрь'), (12, 'Декабрь') ] year = models.PositiveIntegerField(verbose_name='Год') month = models.PositiveIntegerField(choices=MONTH_CHOICES, verbose_name='Месяц') is_active = models.BooleanField(default=True, verbose_name='Активный месяц') class Meta: verbose_name = 'Календарь месяца' verbose_name_plural = 'Календарь месяцев' unique_together = ['year', 'month'] ordering = ['year', 'month'] def __str__(self): return f"{self.get_month_display()} {self.year}" def get_days_in_month(self): import calendar return calendar.monthrange(self.year, self.month)[1] def generate_daily_slots(self): from datetime import date days = self.get_days_in_month() time_slots = ['06:40', '09:20', '12:00', '14:40', '17:20', '20:00'] for day in range(1, days + 1): for time_slot in time_slots: Order.objects.get_or_create( date=date(self.year, self.month, day), time_slot=time_slot, defaults={'is_confirmed': False} )