From 5d15ff9f5e5ca0180b208c7339ac80d16c051627 Mon Sep 17 00:00:00 2001 From: "MH.Dmitrii" Date: Fri, 2 Jan 2026 14:43:50 +0300 Subject: [PATCH] api 1.4 --- run.py | 3 +- server/backend/api/nomenclature.py | 30 +++++++++++++++---- server/backend/handlers/ozon_handler.py | 10 +++---- .../handlers/ozon_purchases_handler.py | 10 +++---- .../handlers/ozon_wb_yandex_com_handler.py | 10 +++---- server/backend/handlers/wb_handler.py | 10 +++---- .../backend/handlers/wb_purchases_handler.py | 9 +++--- server/backend/handlers/yandex_handler.py | 9 +++--- server/backend/schemas/pydantic.py | 16 ++++++++++ server/backend/services/excel.py | 2 +- 10 files changed, 73 insertions(+), 36 deletions(-) diff --git a/run.py b/run.py index 9fde7bb..625c7ed 100644 --- a/run.py +++ b/run.py @@ -1,6 +1,6 @@ import server.backend.services.excel as excel from server.backend.services.validating_files import validating -from server.backend.api import companies,contractors,storages +from server.backend.api import companies,contractors,storages, nomenclature #_______________ from pathlib import Path path = Path("./excel_files") @@ -21,6 +21,7 @@ match args.mode: companies.companies() contractors.contractor() storages.storages() + nomenclature.nomenclature(flag=True) case "orgs": print("Режим:", args.mode) companies.companies() diff --git a/server/backend/api/nomenclature.py b/server/backend/api/nomenclature.py index 5c70fab..2e105eb 100644 --- a/server/backend/api/nomenclature.py +++ b/server/backend/api/nomenclature.py @@ -2,6 +2,8 @@ import requests import xml.etree.ElementTree as ET from base64 import b64encode from server.backend.schemas.pydantic import settings +import re +from functools import lru_cache import pandas as pd auth_str = f"{settings.USERNAME}:{settings.PASSWORD}" b64_auth_str = b64encode(auth_str.encode("utf-8")).decode("utf-8") @@ -32,16 +34,32 @@ def parse_contragents(xml: str): properties = entry.find('atom:content',NS).find( 'm:properties', NS) rows.append({ - 'Ref_Key': properties.findtext('d:Ref_Key', default=None, namespaces=NS), - 'Description': properties.findtext('d:Description', default=None, namespaces=NS), - 'Parent_Key': properties.findtext('d:Parent_Key', default=None, namespaces=NS) + 'ref_key': properties.findtext('d:Ref_Key', default=None, namespaces=NS), + 'description': properties.findtext('d:Description', default=None, namespaces=NS), + 'parent_key': properties.findtext('d:Parent_Key', default=None, namespaces=NS) }) df = pd.DataFrame(rows) - df = df[df['Parent_Key'] == 'e0eb911c-03a0-11ef-95bd-fa163e7429d8'] - df['Description'] = df['Description'].str.extract(r'^([^\s(]+)') + df = df[df['parent_key'] == 'e0eb911c-03a0-11ef-95bd-fa163e7429d8'] + df['description'] = df['description'].str.extract(r'^([^\s(]+)') return df except ET.ParseError: raise -def nomenclature(): +@lru_cache(maxsize=1) +def nomenclature(flag=False): xml_data = fetch_contragents() root = parse_contragents(xml_data) + if flag: + root.to_excel("./excel_files/nomenclature.xlsx") + return root +def processing(df): + df2=nomenclature() + result = df.merge( + df2[['description', 'ref_key']], #берутся столбцы из df2 + left_on='arti', #столбец для сравнения в df + right_on='description', #столбец для сравнения в df2 + how='left' #left join для df + ).drop(columns='description') #удаление временного стобца + not_matched = result.loc[result['ref_key'].isna(), 'arti'].unique() + if len(not_matched) > 0: + raise ValueError(f'Не найдены значения: {not_matched}') + return result \ No newline at end of file diff --git a/server/backend/handlers/ozon_handler.py b/server/backend/handlers/ozon_handler.py index a73e395..c367759 100644 --- a/server/backend/handlers/ozon_handler.py +++ b/server/backend/handlers/ozon_handler.py @@ -1,6 +1,6 @@ from pydantic import ValidationError -from server.backend.schemas.pydantic import ExcelInfo,settings -from server.backend.api.nomenclature import nomenclature +from server.backend.schemas.pydantic import ExcelInfo,settings,Translit +from server.backend.api.nomenclature import processing import re def process_sheet(df, real_arti:str, real_quantity:str, real_sum_1:str, real_sum_2:str): @@ -9,11 +9,12 @@ def process_sheet(df, real_arti:str, real_quantity:str, real_sum_1:str, real_sum df[real_sum_1]+=df[real_sum_2] df = df[[real_arti, real_quantity, real_sum_1]].copy() df.rename(columns={real_arti: 'arti', real_quantity: 'counts', real_sum_1: 'price'}, inplace=True) #переименовываем для pydantic - df['arti'] = df['arti'].astype(str).str.extract(f'({settings.PATTERN})', flags=re.IGNORECASE) #arti под regex - + df['arti'] = df['arti'].replace(Translit.TRANSLIT, regex=True) + df['arti'] = df['arti'].astype(str).str.upper().str.extract(f'({settings.PATTERN})') #arti под regex df['price'] = df['price'].astype(float) #Float to Int, if exists df['counts'] = df['counts'].astype(int) #Float to Int, if exists df = df.groupby('arti', as_index=False).agg({'counts': 'sum', 'price': 'sum'}) #groupping + df = processing(df) #vlookup for ref_keys validated_rows, errors = [], [] for i, row in df.iterrows(): #проходит построчно по df, где i - индекс строки, row - данные строки try: @@ -22,7 +23,6 @@ def process_sheet(df, real_arti:str, real_quantity:str, real_sum_1:str, real_sum errors.append((i, e.errors())) #выводит ошибку и пишет номер строки if errors: raise Exception(f"There are some errors with validation in Отчет о реализации, check it ", errors) - return validated_rows def evaluating(dfs): validated_rows_1 = process_sheet(dfs["Отчет о реализации"], real_arti='2',real_quantity='8', real_sum_1='5',real_sum_2='6') # номера столбцов от озона diff --git a/server/backend/handlers/ozon_purchases_handler.py b/server/backend/handlers/ozon_purchases_handler.py index 1000ecb..40b0fef 100644 --- a/server/backend/handlers/ozon_purchases_handler.py +++ b/server/backend/handlers/ozon_purchases_handler.py @@ -1,18 +1,18 @@ from pydantic import ValidationError -from server.backend.schemas.pydantic import ExcelInfo, settings +from server.backend.schemas.pydantic import ExcelInfo, settings,Translit +from server.backend.api.nomenclature import processing import re - def process_sheet(df, real_arti:int, real_quantity:int, real_sum_1:int): df = df[[real_arti, real_quantity, real_sum_1]].copy().dropna() #copy and drop all NA values df = df[(df != 0).all(axis=1)] #drop all 0 values df = df[[real_arti, real_quantity, real_sum_1]].copy() df.rename(columns={real_arti: 'arti', real_quantity: 'counts', real_sum_1: 'price'}, inplace=True) #переименовываем для pydantic - df['arti'] = df['arti'].astype(str).str.extract(f'({settings.PATTERN})', flags=re.IGNORECASE) #arti под regex - + df['arti'] = df['arti'].replace(Translit.TRANSLIT, regex=True) + df['arti'] = df['arti'].astype(str).str.upper().str.extract(f'({settings.PATTERN})') #arti под regex df['price'] = df['price'].astype(float) #Float to Int, if exists df['counts'] = df['counts'].astype(int) #Float to Int, if exists - df = df.groupby('arti', as_index=False).agg({'counts': 'sum', 'price': 'sum'}) #groupping + df = processing(df) #vlookup for ref_keys validated_rows, errors = [], [] for i, row in df.iterrows(): #проходит построчно по df, где i - индекс строки, row - данные строки try: diff --git a/server/backend/handlers/ozon_wb_yandex_com_handler.py b/server/backend/handlers/ozon_wb_yandex_com_handler.py index 7b43127..b0a8039 100644 --- a/server/backend/handlers/ozon_wb_yandex_com_handler.py +++ b/server/backend/handlers/ozon_wb_yandex_com_handler.py @@ -1,19 +1,19 @@ from pydantic import ValidationError -from server.backend.schemas.pydantic import ExcelInfo,settings +from server.backend.schemas.pydantic import ExcelInfo,settings,Translit +from server.backend.api.nomenclature import processing import re - def process_sheet(df, real_arti:str, real_quantity:str, real_sum_1:str): df = df[[real_arti, real_quantity, real_sum_1]].copy().dropna() #copy and drop all NA values df = df[(df != 0).all(axis=1)] #drop all 0 values df = df[[real_arti, real_quantity, real_sum_1]].copy() df.rename(columns={real_arti: 'arti', real_quantity: 'counts', real_sum_1: 'price'}, inplace=True) #переименовываем для pydantic - df['arti'] = df['arti'].astype(str).str.extract(f'({settings.PATTERN})', flags=re.IGNORECASE) #arti под regex - + df['arti'] = df['arti'].replace(Translit.TRANSLIT, regex=True) + df['arti'] = df['arti'].astype(str).str.upper().str.extract(f'({settings.PATTERN})') #arti под regex df['price'] = df['price'].astype(float) #переделка к норм виду и преобразование в float df['counts'] = df['counts'].astype(int) #Float to Int, if exists df = df.groupby('arti', as_index=False).agg({'counts': 'sum', 'price': 'sum'}) #groupping - + df = processing(df) #vlookup for ref_keys validated_rows, errors = [], [] for i, row in df.iterrows(): #проходит построчно по df, где i - индекс строки, row - данные строки try: diff --git a/server/backend/handlers/wb_handler.py b/server/backend/handlers/wb_handler.py index 854753e..adda783 100644 --- a/server/backend/handlers/wb_handler.py +++ b/server/backend/handlers/wb_handler.py @@ -1,19 +1,19 @@ from pydantic import ValidationError -from server.backend.schemas.pydantic import ExcelInfo, settings +from server.backend.schemas.pydantic import ExcelInfo, settings,Translit +from server.backend.api.nomenclature import processing import re - def process_sheet(df, document_type:str): df = df[['Артикул поставщика', 'Тип документа', 'Кол-во', 'Вайлдберриз реализовал Товар (Пр)']].copy().dropna() #copy and drop all NA values df = df[(df != 0).all(axis=1)] #drop all 0 values df = df[df['Тип документа'] == document_type] #фильтруем по типу документа - df = df[['Артикул поставщика', 'Кол-во', 'Вайлдберриз реализовал Товар (Пр)']].copy() df.rename(columns={'Артикул поставщика': 'arti', 'Кол-во': 'counts', 'Вайлдберриз реализовал Товар (Пр)': 'price'}, inplace=True) #переименовываем для pydantic - df['arti'] = df['arti'].astype(str).str.extract(f'({settings.PATTERN})', flags=re.IGNORECASE) #arti под regex - + df['arti'] = df['arti'].replace(Translit.TRANSLIT, regex=True) + df['arti'] = df['arti'].astype(str).str.upper().str.extract(f'({settings.PATTERN})') #arti под regex df['price'] = df['price'].astype(float) #Float to Int, if exists df['counts'] = df['counts'].astype(int) #Float to Int, if exists df = df.groupby('arti', as_index=False).agg({'counts': 'sum', 'price': 'sum'}) + df = processing(df) #vlookup for ref_keys validated_rows, errors = [], [] for i, row in df.iterrows(): #проходит построчно по df, где i - индекс строки, row - данные строки try: diff --git a/server/backend/handlers/wb_purchases_handler.py b/server/backend/handlers/wb_purchases_handler.py index 3ed4952..3f01b04 100644 --- a/server/backend/handlers/wb_purchases_handler.py +++ b/server/backend/handlers/wb_purchases_handler.py @@ -1,5 +1,6 @@ from pydantic import ValidationError -from server.backend.schemas.pydantic import ExcelInfo, settings +from server.backend.schemas.pydantic import ExcelInfo, settings,Translit +from server.backend.api.nomenclature import processing import re def process_sheet(df, real_arti:str, real_quantity:str, real_sum_1:str): @@ -7,12 +8,12 @@ def process_sheet(df, real_arti:str, real_quantity:str, real_sum_1:str): df = df[(df != 0).all(axis=1)] #drop all 0 values df = df[[real_arti, real_quantity, real_sum_1]].copy() df.rename(columns={real_arti: 'arti', real_quantity: 'counts', real_sum_1: 'price'}, inplace=True) #переименовываем для pydantic - df['arti'] = df['arti'].astype(str).str.extract(f'({settings.PATTERN})', flags=re.IGNORECASE) #arti под regex - + df['arti'] = df['arti'].astype(str).str.upper().str.extract(f'({settings.PATTERN})') #arti под regex + df['arti'] = df['arti'].replace(Translit.TRANSLIT, regex=True) df['price'] = df['price'].str.replace(' ', '', regex=False).str.replace(',', '.', regex=False).astype(float) #переделка к норм виду и преобразование в float df['counts'] = df['counts'].astype(int) #Float to Int, if exists df = df.groupby('arti', as_index=False).agg({'counts': 'sum', 'price': 'sum'}) #groupping - + df = processing(df) #vlookup for ref_keys validated_rows, errors = [], [] for i, row in df.iterrows(): #проходит построчно по df, где i - индекс строки, row - данные строки try: diff --git a/server/backend/handlers/yandex_handler.py b/server/backend/handlers/yandex_handler.py index 7044425..17c2332 100644 --- a/server/backend/handlers/yandex_handler.py +++ b/server/backend/handlers/yandex_handler.py @@ -1,5 +1,6 @@ from pydantic import ValidationError -from server.backend.schemas.pydantic import ExcelInfo, settings +from server.backend.schemas.pydantic import ExcelInfo, settings,Translit +from server.backend.api.nomenclature import processing import re def process_sheet(df, multiply_price=1, sheet_name=''): @@ -7,12 +8,12 @@ def process_sheet(df, multiply_price=1, sheet_name=''): df = df[(df != 0).all(axis=1)] #drop all 0 values df['Сумма транзакции, ₽'] *= multiply_price #умножаем на -1 для возвратов df.rename(columns={'Ваш SKU': 'arti', 'Количество, шт.': 'counts', 'Сумма транзакции, ₽': 'price'}, inplace=True) #переименовываем для pydantic - df['arti'] = df['arti'].astype(str).str.extract(f'({settings.PATTERN})', flags=re.IGNORECASE) #regex implemented - + df['arti'] = df['arti'].replace(Translit.TRANSLIT, regex=True) + df['arti'] = df['arti'].astype(str).str.upper().str.extract(f'({settings.PATTERN})') #arti под regex df['price'] = df['price'].astype(float) #To float, if exists df['counts'] = df['counts'].astype(int) #To float, if exists df = df.groupby('arti', as_index=False).agg({'counts': 'sum', 'price': 'sum'}) #groupping - + df = processing(df) #vlookup for ref_keys validated_rows, errors = [], [] for i, row in df.iterrows(): #проходит построчно по df, где i - индекс строки, row - данные строки try: diff --git a/server/backend/schemas/pydantic.py b/server/backend/schemas/pydantic.py index 02ca3fa..4dd42fd 100644 --- a/server/backend/schemas/pydantic.py +++ b/server/backend/schemas/pydantic.py @@ -4,6 +4,7 @@ class ExcelInfo(BaseModel): arti:str = Field(..., min_length=5, max_length=12, description="arti of the clothes") counts:int = Field(..., gt=0, description="the quantity of the clothes") price:float = Field(..., gt=0, description="the price of the clothes") + ref_key:str = Field(..., description="reffering key from db") class ExcelRealization(BaseModel): pass class ExcelReturning(BaseModel): @@ -44,4 +45,19 @@ class Settings(BaseSettings): env_file=".env", env_file_encoding="utf-8" ) +class Translit(): + TRANSLIT = { + 'А': 'A', + 'В': 'B', + 'Е': 'E', + 'К': 'K', + 'М': 'M', + 'Н': 'H', + 'О': 'O', + 'Р': 'P', + 'С': 'C', + 'Т': 'T', + 'Х': 'X', + } + settings = Settings() \ No newline at end of file diff --git a/server/backend/services/excel.py b/server/backend/services/excel.py index 6a5e817..7046ba8 100644 --- a/server/backend/services/excel.py +++ b/server/backend/services/excel.py @@ -64,7 +64,7 @@ class WBPurchasesHandler(BaseHandler): if "Sheet1" not in dfs: raise Exception(f"В файле {self.file_path.name} отсутствуют необходимые листы") validated_data = wb_purchases_handler.evaluating(dfs) - print("Выкупы WB завершены, валидированных строк:", len(validated_data), "Реализация") + print("Выкупы WB завершены, валидированных строк:", len(validated_data), "Реализация", validated_data) class OZONComHandler(BaseHandler): def process(self):