/
statik2002
/
TorgServer
Обзор
Документация
Войти
/
statik2002
/
TorgServer
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
main/models.py
585 строк
24 KB
statik2002
Add Yandex feed file
09 авг 2026, 10:09
09 авг 2026, 10:09
1745015
Код
Авторство
О чём код?
from xml import etree import pytz from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.conf import settings from django.db.models import Q from django.utils import timezone from smb.SMBConnection import SMBConnection from smb.smb_structs import OperationFailure from datetime import datetime import xml.etree.ElementTree as ET class LogError(models.Model): error = models.CharField(max_length=2000, verbose_name='Сообщение ошибки') data = models.DateTimeField(default=timezone.now, verbose_name='Время ошибки') class Meta: verbose_name = 'Ошибка' verbose_name_plural = 'Ошибки' class Product(models.Model): code1c = models.BigIntegerField(verbose_name='Код из 1С') discount = models.FloatField(verbose_name='Скидка') price = models.FloatField(verbose_name='Цена покупки') quantity = models.FloatField(verbose_name='Кол-во') total = models.FloatField(verbose_name='Итого') name = models.CharField(max_length=255, null=True, blank=True, default='', verbose_name='Наименование') class Meta: verbose_name = 'Товар в чеке' verbose_name_plural = 'Товары в чеках' def __str__(self) -> str: return f'{self.code1c}: {self.name}' class ReceiptInShift(models.Model): discount = models.FloatField(verbose_name='Скидка') is_complete = models.BooleanField(verbose_name='Проведен') payment_type = models.CharField(choices=[('наличные', 1), ('карта', 2)], max_length=50, default=1, verbose_name='Тип оплаты') receipt_discount = models.FloatField(verbose_name='Скидка на чек') receipt_id = models.BigIntegerField(unique=True, verbose_name='ID чека') receipt_time = models.TimeField(verbose_name='Время') total_payment = models.FloatField(verbose_name='Сумма со сидкой') total_without_discount = models.FloatField(verbose_name='Сумма без скидки') products_in_receipt = models.ManyToManyField(Product, default=[], verbose_name='Товары в чеке') class Meta: verbose_name = 'Чек' verbose_name_plural = 'Чеки' def __str__(self) -> str: return ( f'id={self.receipt_id}, Позиций в чеке={self.products_in_receipt.count()},' f'Завершен={self.is_complete}, Тип оплаты={self.payment_type}, Итого={self.total_payment}' ) class WorkingShift(models.Model): document_id = models.BigIntegerField(verbose_name='Номер смены') shift_id = models.BigIntegerField(unique=True, verbose_name='ID смены') shift_date = models.DateField(verbose_name='Дата смены') shift_start_time = models.TimeField(verbose_name='Время открытия смены') receipts = models.ManyToManyField(ReceiptInShift, blank=True, verbose_name='Чеки', related_name='shift') class Meta: verbose_name = 'Смена' verbose_name_plural = 'Смены' ordering = ['-shift_date'] def __str__(self) -> str: return f'{self.shift_date.__str__()} - {self.receipts.count()} items' class ProductInCatalog(models.Model): code1c = models.BigIntegerField(unique=True, verbose_name='Код из 1С') name = models.CharField(max_length=255, verbose_name='Наименование') receipt_name = models.CharField(max_length=500, verbose_name='Наименование в чеке') price = models.FloatField(verbose_name='Цена') quantity = models.FloatField(verbose_name='Кол-во') parent = models.IntegerField(null=True, verbose_name='В каталоге') class Meta: verbose_name = 'Товар каталога' verbose_name_plural = 'Товары каталога' def __str__(self) -> str: return f'{self.code1c}: {self.name}' class Catalog(models.Model): code1c = models.BigIntegerField(unique=True, verbose_name='Код из 1С') name = models.CharField(max_length=255, verbose_name='Наименование') parent = models.IntegerField(null=True, verbose_name='В каталоге') class Meta: verbose_name = 'Каталог' verbose_name_plural = 'Каталоги' def __str__(self) -> str: return f'{self.code1c}: {self.name}' class SailedProduct(models.Model): product = models.ForeignKey(ProductInCatalog, on_delete=models.CASCADE, related_name='sailed_product') quantity = models.FloatField(default=0.0, verbose_name='Количество проданного товара') total = models.FloatField(default=0.0, verbose_name='Продано на сумму') def __str__(self): return f'{self.product.name}, quantity: {self.quantity}, total: {self.total}' class Statistic(models.Model): best_sailed_products_by_total_year = models.ManyToManyField(SailedProduct, related_name='best_sale_total_year') best_sailed_products_by_total_month = models.ManyToManyField(SailedProduct, related_name='best_sale_total_month') best_sailed_products_by_quantity_month = models.ManyToManyField(SailedProduct, related_name='best_sale_quantity_month') best_sailed_products_by_quantity_year = models.ManyToManyField(SailedProduct, related_name='best_sale_quantity_year') stat_date = models.DateTimeField(default=timezone.now) num_items = models.IntegerField(default=20, verbose_name='Кол-во элементов') class Meta: verbose_name = 'Статистика' verbose_name_plural = 'Статистики' def calc_stat(self): current_year = datetime.today().year current_month = datetime.today().month current_date = datetime.now() month_start_date = datetime(day=1, month=current_month, year=current_year) year_start_date = datetime(day=1, month=1, year=current_year) shifts_this_month = WorkingShift.objects.order_by('-shift_date')[ :(current_date - month_start_date).days+1].prefetch_related('receipts') shifts_this_year = WorkingShift.objects.order_by('-shift_date')[ :(current_date - year_start_date).days+1].prefetch_related('receipts') sailed_products_month = [{'product': product, 'total': 0, 'quantity': 0} for product in ProductInCatalog.objects.all()] sailed_products_year = [{'product': product, 'total': 0, 'quantity': 0} for product in ProductInCatalog.objects.all()] for shift in shifts_this_month: for receipt in shift.receipts.all(): for product in receipt.products_in_receipt.all(): sailed_products_index = next( (index for (index, d) in enumerate(sailed_products_month) if d["product"].code1c == product.code1c), None) if sailed_products_index: sailed_products_month[sailed_products_index]['total'] += product.total sailed_products_month[sailed_products_index]['quantity'] += product.quantity for shift in shifts_this_year: for receipt in shift.receipts.all(): for product in receipt.products_in_receipt.all(): sailed_products_index = next( (index for (index, d) in enumerate(sailed_products_year) if d["product"].code1c == product.code1c), None) if sailed_products_index: sailed_products_year[sailed_products_index]['total'] += product.total sailed_products_year[sailed_products_index]['quantity'] += product.quantity sorted_sailed_products_month = sorted( sailed_products_month, key=lambda product: product['total'], reverse=True)[:self.num_items] sorted_sailed_products_year = sorted( sailed_products_year, key=lambda product: product['total'], reverse=True)[:self.num_items] for item in sorted_sailed_products_month: try: product = ProductInCatalog.objects.get(code1c=item.get('product').code1c) new_item = SailedProduct(product=product, quantity=item.get('quantity'), total=item.get('total')) new_item.save() self.best_sailed_products_by_total_month.add(new_item) except ObjectDoesNotExist: error = LogError(error=f'Cant find product with code: {item}') error.save() except Exception as e: error = LogError(error=e) error.save() for item in sorted_sailed_products_year: try: product = ProductInCatalog.objects.get(code1c=item.get("product").code1c) new_item = SailedProduct(product=product, quantity=item.get('quantity'), total=item.get('total')) new_item.save() self.best_sailed_products_by_total_year.add(new_item) except ObjectDoesNotExist: error = LogError(error=f'Cant find product with code: {item}') error.save() except Exception as e: error = LogError(error=e) error.save() class OKSFile(models.Model): name = models.CharField(max_length=100, default='OKS') last_change_time = models.DateTimeField(auto_now_add=True) last_change_elapsed = models.FloatField(default=0) remote_path = models.CharField(max_length=250, default=settings.SMB.get('SMB_OKS_PATH')) shared_folder = models.CharField(max_length=250, default=settings.SMB.get('SMB_SHARED_FOLDER')) username = models.CharField(max_length=100, default=settings.SMB.get('SMB_USERNAME')) password = models.CharField(max_length=100, default=settings.SMB.get('SMB_PASSWORD')) remote_pc_name = models.CharField(max_length=250, default=settings.SMB.get('SMB_REMOTE_NAME')) port = models.IntegerField(default=455) current_pc_name = models.CharField(max_length=250, default='TorgServer') oks_file_path = models.CharField(max_length=100, default='oks_test.txt') class Meta: verbose_name = "Текущая смена" verbose_name_plural = "Текущие смены" def save(self, *args, **kwargs) -> None: self.pk = 1 super().save(*args, **kwargs) def get_file_last_time_change(self) -> float: conn = SMBConnection( username=self.username, my_name=self.current_pc_name, password=self.password, remote_name=self.remote_pc_name, is_direct_tcp=True, use_ntlm_v2=False ) conn.connect(self.remote_pc_name, self.port) if not conn: conn.close() raise try: shared_file = conn.getAttributes(self.shared_folder, self.remote_path, timeout=180) conn.close() #self.last_change_elapsed = shared_file.last_write_time return shared_file.last_write_time except OperationFailure: conn.close() print('Cant get file attributes from remote PC') raise def fetch_oks_file(self) -> int: conn = SMBConnection( username=self.username, my_name=self.current_pc_name, password=self.password, remote_name=self.remote_pc_name, is_direct_tcp=True, use_ntlm_v2=False ) conn.connect(self.remote_pc_name, self.port) if not conn: conn.close() raise with open(self.oks_file_path, 'wb') as oksfile: try: conn.retrieveFile(self.shared_folder, self.remote_path, oksfile) except OperationFailure: conn.close() return 0 conn.close() return 1 def get_data_current_shift(self) -> dict: with open(self.oks_file_path, 'r', encoding="windows-1251") as f: data = f.read() data = data.split('\n') current_shift = {} for line in data: if len(line) < 1: continue record = line.split(';') if datetime.strptime(record[1], '%d.%m.%Y').date().strftime('%Y-%m-%d') != datetime.now().date().strftime( '%Y-%m-%d'): continue if record[3] == '64': current_shift = { 'document_id': int(record[5]), 'shift_id': int(record[13]), 'shift_date': datetime.strptime(record[1], '%d.%m.%Y').date().strftime('%Y-%m-%d'), 'shift_start_time': record[2] } break return current_shift def parse_receipt(self, receipt_block: list) -> dict: is_complete = True close_receipt_line = receipt_block[-1].split(';') if close_receipt_line[3] == '55': # receipt closed receipt_total = float(close_receipt_line[11].replace(',', '.')) receipt_time = close_receipt_line[2] elif close_receipt_line[3] == '56': # receipt canceled is_complete = False receipt_total = -float(close_receipt_line[11].replace(',', '.')) receipt_time = close_receipt_line[2] else: is_complete = False receipt_total = 0 receipt_time = close_receipt_line[2] # print(f'Сумма чека={receipt_total}') receipt_price = 0 receipt_discount = 0 payment_type = 'наличные' products_in_receipt = [] for position in receipt_block: line = position.split(';') if line[3] == '11': receipt_price += float(line[19].replace(',', '.')) position = { 'code1c': int(line[7]), 'price': float(line[9].replace(',', '.')), 'quantity': float(line[10].replace(',', '.')), 'discount': float(line[9].replace(',', '.')) - float(line[14].replace(',', '.')), 'total': float(line[15].replace(',', '.')) } products_in_receipt.append(position) if line[3] == '37': receipt_discount += float(line[11].replace(',', '.')) if line[3] == '40': if line[8] == '1': # print('Оплата наличными') pass if line[8] == '4': payment_type = 'карта' if line[3] == '12': # Сторно receipt_price += float(line[19].replace(',', '.')) shift_position = { 'total_payment': receipt_total, 'total_without_discount': receipt_price, 'payment_type': payment_type, 'discount': receipt_discount, 'receipt_id': int(receipt_block[0].split(';')[5]), 'is_complete': is_complete, 'receipt_discount': receipt_discount, 'products_in_receipt': products_in_receipt, 'receipt_time': receipt_time } return shift_position def parse_oks_file(self) -> list: receipts = [] with open(self.oks_file_path, 'r', encoding="windows-1251") as f: data = f.read() parsed_data = data.split('\n') receipt_block = [] index = 1 while index < len(parsed_data) - 1: line = parsed_data[index] parsed_line = line.split(';') if len(parsed_line) < 2: index += 1 if datetime.strptime(parsed_line[1], '%d.%m.%Y').date().strftime( '%Y-%m-%d') != datetime.now().date().strftime('%Y-%m-%d'): index += 1 continue if parsed_line[3] == '42': # open receipt flag = True receipt_block.append(line) index += 1 while flag: line = parsed_data[index] parsed_line = line.split(';') if parsed_line[3] == '55' or parsed_line[3] == '56': flag = False receipt_block.append(line) receipt = self.parse_receipt(receipt_block) receipt_block = [] receipts.append(receipt) else: receipt_block.append(line) index += 1 index += 1 return receipts class Leftovers(models.Model): name = models.CharField(max_length=100, default='Leftovers') last_change_elapsed = models.FloatField(default=0) remote_path = models.CharField(max_length=250, default=settings.SMB.get('SMB_LEFTOVERS_PATH')) shared_folder = models.CharField(max_length=250, default=settings.SMB.get('SMB_LEFTOVERS_SHARED_FOLDER')) username = models.CharField(max_length=100, default=settings.SMB.get('SMB_LEFTOVERS_USERNAME')) password = models.CharField(max_length=100, default=settings.SMB.get('SMB_LEFTOVERS_USER_PASSWORD')) remote_pc_name = models.CharField(max_length=250, default=settings.SMB.get('SMB_LEFTOVERS_REMOTE_NAME')) port = models.IntegerField(default=445) current_pc_name = models.CharField(max_length=250, default='TorgServer') leftovers_file_path = models.CharField(max_length=100, default='leftovers.txt') class Meta: verbose_name = "Остаток" verbose_name_plural = "Остатки" def save(self, *args, **kwargs) -> None: self.pk = 2 super().save(*args, **kwargs) def get_file_last_time_change(self) -> float: conn = SMBConnection( username=self.username, my_name=self.current_pc_name, password=self.password, remote_name=self.remote_pc_name, is_direct_tcp=True, use_ntlm_v2=False ) conn.connect(self.remote_pc_name, self.port) if not conn: conn.close() raise try: shared_file = conn.getAttributes(self.shared_folder, self.remote_path, timeout=180) conn.close() #self.last_change_elapsed = shared_file.last_write_time return shared_file.last_write_time except OperationFailure: conn.close() print('Cant get file attributes from remote PC') raise def fetch_leftovers_file(self) -> int: conn = SMBConnection( username=self.username, my_name=self.current_pc_name, password=self.password, remote_name=self.remote_pc_name, is_direct_tcp=True, use_ntlm_v2=False ) conn.connect(self.remote_pc_name, self.port) if not conn: conn.close() raise with open(self.leftovers_file_path, 'wb') as leftoversfile: try: conn.retrieveFile(self.shared_folder, self.remote_path, leftoversfile) except OperationFailure: conn.close() return 0 conn.close() return 1 def parse_leftovers(self) -> tuple: with open(self.leftovers_file_path, 'r', encoding='windows-1251') as f: data = f.read().split('\n') start_line = data.index('$$$REPLACEQUANTITYWITHOUTSALE') + 1 end_line = data.index('$$$REPLACEASPECTREMAINSWITHOUTSALE') - 1 leftovers_data = data[start_line:end_line] catalogs = [] products = [] for line in leftovers_data: split_line = line.split(';') if split_line[16] == '0': # This group catalogs.append( { 'code_1c': int(split_line[0]), 'name': split_line[2], 'parent': None if split_line[15] == '' else int(split_line[15]) } ) elif split_line[16] == '1': # This is are product products.append( { 'code1c': int(split_line[0]), 'name': split_line[2], 'receipt_name': split_line[3], 'price': float(split_line[4].replace(',', '.')), 'quantity': float(split_line[5].replace(',', '.')), 'parent': None if split_line[15] == '' else int(split_line[15]) } ) return catalogs, products class YandexFeed(models.Model): file_path = models.CharField(max_length=100, default='yandexfeed.xml') class Meta: verbose_name = "Яндекс фид" verbose_name_plural = "Яндекс фиды" def save(self, *args, **kwargs) -> None: self.pk = 1 super().save(*args, **kwargs) def create_feed_file(self): data_xml = ET.Element('yml_catalog') data_xml.set('date', datetime.now(tz=pytz.UTC).isoformat(timespec='seconds')) # Основные данные shop_element = ET.SubElement(data_xml, 'shop') shop_name = ET.SubElement(shop_element, 'name') shop_name.text = 'Мозаика' shop_company = ET.SubElement(shop_element, 'company') shop_company.text = 'ООО НЕИТ' shop_url = ET.SubElement(shop_element, 'url') shop_url.text = 'https://neit.ru' shop_platform = ET.SubElement(shop_element, 'platform') shop_platform.text = 'custom' # Каталог shop_categories = ET.SubElement(shop_element, 'categories') root = Catalog.objects.get(parent__isnull=True) query = Q(parent=None) catalog = Catalog.objects.filter(query) for catalog_item in catalog: shop_categories_category1 = ET.SubElement(shop_categories, 'category') shop_categories_category1.set('id', str(catalog_item.code1c)) shop_categories_category1.text = catalog_item.name ## Подкаталоги sub_catalog = Catalog.objects.filter(parent=catalog_item.code1c) for sub_catalog_item in sub_catalog: sub_catalog_element = ET.SubElement(shop_categories, 'category') sub_catalog_element.set('id', str(sub_catalog_item.code1c)) sub_catalog_element.set('parentId', str(sub_catalog_item.parent)) sub_catalog_element.text = sub_catalog_item.name # Товары offers = ET.SubElement(shop_element, 'offers') query = Q(quantity__gt=0) products = ProductInCatalog.objects.filter(query) for product in products: offers_offer = ET.SubElement(offers, 'offer') offers_offer.set('id', str(product.code1c)) offers_offer_name = ET.SubElement(offers_offer, 'name') offers_offer_name.text = product.name offers_offer_price = ET.SubElement(offers_offer, 'price') offers_offer_price.text = str(round(product.price, 2)) offers_offer_currency_id = ET.SubElement(offers_offer, 'currencyId') offers_offer_currency_id.text = 'RUR' offers_offer_category_id = ET.SubElement(offers_offer, 'categoryId') offers_offer_category_id.text = str(product.parent) offers_offer_category = ET.SubElement(offers_offer, 'market_category_id') offers_offer_category.text = str(product.parent) offers_offer_count = ET.SubElement(offers_offer, 'count') offers_offer_count.text = str(int(product.quantity)) ET.indent(data_xml) tree = ET.ElementTree(data_xml) tree.write(self.file_path, encoding='utf-8', xml_declaration=True) return