After Graduate Update

This commit is contained in:
Daniel 2025-11-26 19:31:33 +03:00
parent b92a91ab37
commit c6917dd85e
69 changed files with 7540 additions and 0 deletions

View file

@ -0,0 +1,5 @@
# app/routes/__init__.py
"""
Инициализация маршрутов API
"""
from . import partners, sales, upload, calculations, auth, config

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,45 @@
# app/routes/auth.py
"""
Маршруты API для аутентификации
"""
from fastapi import APIRouter, HTTPException, Depends
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from app.database import db
import bcrypt
router = APIRouter()
security = HTTPBasic()
@router.post("/login")
async def login(credentials: HTTPBasicCredentials = Depends(security)):
"""Аутентификация менеджера"""
try:
result = db.execute_query(
"SELECT manager_id, username, password_hash, full_name FROM managers WHERE username = %s AND is_active = TRUE",
(credentials.username,)
)
if not result:
raise HTTPException(status_code=401, detail="Invalid credentials")
manager = dict(result[0])
stored_hash = manager['password_hash']
# Проверка пароля
if bcrypt.checkpw(credentials.password.encode('utf-8'), stored_hash.encode('utf-8')):
return {
"manager_id": manager['manager_id'],
"username": manager['username'],
"full_name": manager['full_name'],
"authenticated": True
}
else:
raise HTTPException(status_code=401, detail="Invalid credentials")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/verify")
async def verify_token():
"""Проверка валидности токена"""
return {"verified": True}

View file

@ -0,0 +1,43 @@
# app/routes/calculations.py
"""
Маршруты API для расчетов
Соответствует модулю 4 ТЗ по расчету материалов
"""
from fastapi import APIRouter, HTTPException
from app.models import MaterialCalculationRequest, MaterialCalculationResponse
import math
router = APIRouter()
@router.post("/calculate-material", response_model=MaterialCalculationResponse)
async def calculate_material(request: MaterialCalculationRequest):
"""
Расчет количества материала для производства продукции
Соответствует модулю 4 ТЗ
"""
try:
# Валидация входных параметров
if (request.param1 <= 0 or request.param2 <= 0 or
request.product_coeff <= 0 or request.defect_percent < 0):
return MaterialCalculationResponse(
material_quantity=-1,
status="error: invalid parameters"
)
# Расчет количества материала на одну единицу продукции
material_per_unit = request.param1 * request.param2 * request.product_coeff
# Расчет общего количества материала с учетом брака
total_material = material_per_unit * request.quantity
total_material_with_defect = total_material * (1 + request.defect_percent / 100)
# Округление до целого числа в большую сторону
material_quantity = math.ceil(total_material_with_defect)
return MaterialCalculationResponse(
material_quantity=material_quantity,
status="success"
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View file

@ -0,0 +1,32 @@
# app/routes/config.py
"""
Маршруты API для управления конфигурацией
"""
from fastapi import APIRouter, HTTPException
from pathlib import Path
import json
router = APIRouter()
CONFIG_PATH = Path(__file__).parent.parent.parent / "config.json"
@router.get("/")
async def get_config():
"""Получение текущей конфигурации"""
try:
if CONFIG_PATH.exists():
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
return json.load(f)
return {"message": "Config file not found"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error reading config: {str(e)}")
@router.put("/")
async def update_config(config_data: dict):
"""Обновление конфигурации"""
try:
with open(CONFIG_PATH, 'w', encoding='utf-8') as f:
json.dump(config_data, f, indent=4, ensure_ascii=False)
return {"message": "Configuration updated successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error saving config: {str(e)}")

View file

@ -0,0 +1,157 @@
# app/routes/partners.py
"""
Маршруты API для управления партнерами
Соответствует модулям 1-3 ТЗ
"""
from fastapi import APIRouter, HTTPException
from app.database import db
from app.models import Partner, PartnerCreate, PartnerUpdate, DiscountResponse
from decimal import Decimal
router = APIRouter()
@router.get("/")
async def get_partners():
"""
Получение списка всех партнеров
Соответствует требованию просмотра списка партнеров
"""
try:
result = db.execute_query("""
SELECT partner_id, partner_type, company_name, legal_address,
inn, director_name, phone, email, rating, sales_locations
FROM partners
ORDER BY company_name
""")
partners_list = []
for row in result:
partner_dict = dict(row)
# Преобразуем рейтинг к int если нужно
if isinstance(partner_dict.get('rating'), float):
partner_dict['rating'] = int(partner_dict['rating'])
partners_list.append(partner_dict)
return partners_list
except Exception as e:
if "relation \"partners\" does not exist" in str(e):
return []
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{partner_id}")
async def get_partner(partner_id: int):
"""Получение информации о конкретном партнере"""
try:
result = db.execute_query(
"SELECT * FROM partners WHERE partner_id = %s",
(partner_id,)
)
if not result:
raise HTTPException(status_code=404, detail="Partner not found")
partner_data = dict(result[0])
# Преобразуем рейтинг к int если нужно
if isinstance(partner_data.get('rating'), float):
partner_data['rating'] = int(partner_data['rating'])
return partner_data
except Exception as e:
if "relation \"partners\" does not exist" in str(e):
raise HTTPException(status_code=404, detail="Partner not found")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/")
async def create_partner(partner: PartnerCreate):
"""
Создание нового партнера
Включает валидацию данных согласно ТЗ
"""
try:
result = db.execute_query("""
INSERT INTO partners
(partner_type, company_name, legal_address, inn, director_name,
phone, email, rating, sales_locations)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING partner_id
""", (
partner.partner_type, partner.company_name, partner.legal_address,
partner.inn, partner.director_name, partner.phone, partner.email,
partner.rating, partner.sales_locations
))
return {"partner_id": result[0]["partner_id"]}
except Exception as e:
if "duplicate key value violates unique constraint" in str(e):
raise HTTPException(status_code=400, detail="Partner with this INN already exists")
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{partner_id}")
async def update_partner(partner_id: int, partner: PartnerUpdate):
"""
Обновление данных партнера
Соответствует требованию редактирования данных партнера
"""
try:
db.execute_query("""
UPDATE partners SET
partner_type = %s, company_name = %s, legal_address = %s,
inn = %s, director_name = %s, phone = %s, email = %s,
rating = %s, sales_locations = %s
WHERE partner_id = %s
""", (
partner.partner_type, partner.company_name, partner.legal_address,
partner.inn, partner.director_name, partner.phone, partner.email,
partner.rating, partner.sales_locations, partner_id
))
return {"message": "Partner updated successfully"}
except Exception as e:
if "duplicate key value violates unique constraint" in str(e):
raise HTTPException(status_code=400, detail="Partner with this INN already exists")
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{partner_id}")
async def delete_partner(partner_id: int):
"""Удаление партнера"""
try:
db.execute_query(
"DELETE FROM partners WHERE partner_id = %s",
(partner_id,)
)
return {"message": "Partner deleted successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{partner_id}/discount", response_model=DiscountResponse)
async def calculate_partner_discount(partner_id: int):
"""
Расчет скидки для партнера на основе общего количества продаж
Соответствует модулю 2 ТЗ
"""
try:
# Получаем общее количество продаж партнера
result = db.execute_query("""
SELECT COALESCE(SUM(quantity), 0) as total_sales
FROM sales WHERE partner_id = %s
""", (partner_id,))
total_sales = result[0]["total_sales"] if result else Decimal('0')
# Расчет скидки согласно бизнес-правилам ТЗ
if total_sales < 10000:
discount = 0
elif total_sales < 50000:
discount = 5
elif total_sales < 300000:
discount = 10
else:
discount = 15
return DiscountResponse(
partner_id=partner_id,
total_sales=total_sales,
discount_percent=discount
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View file

@ -0,0 +1,64 @@
# app/routes/sales.py
"""
Маршруты API для управления продажами
Соответствует требованиям ТЗ по истории реализации продукции
"""
from fastapi import APIRouter, HTTPException
from app.database import db
from app.models import Sale, SaleCreate
router = APIRouter()
@router.get("/partner/{partner_id}")
async def get_sales_by_partner(partner_id: int):
"""
Получение истории реализации продукции партнером
Соответствует модулю 4 ТЗ
"""
try:
result = db.execute_query("""
SELECT sale_id, partner_id, product_name, quantity, sale_date
FROM sales
WHERE partner_id = %s
ORDER BY sale_date DESC
""", (partner_id,))
return [dict(row) for row in result]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/")
async def get_all_sales():
"""Получение всех продаж с информацией о партнерах"""
try:
result = db.execute_query("""
SELECT s.sale_id, s.partner_id, p.company_name, s.product_name,
s.quantity, s.sale_date
FROM sales s
JOIN partners p ON s.partner_id = p.partner_id
ORDER BY s.sale_date DESC
""")
return [dict(row) for row in result]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/")
async def create_sale(sale: SaleCreate):
"""Создание новой записи о продаже"""
try:
result = db.execute_query("""
INSERT INTO sales (partner_id, product_name, quantity, sale_date)
VALUES (%s, %s, %s, %s)
RETURNING sale_id
""", (sale.partner_id, sale.product_name, sale.quantity, sale.sale_date))
return {"sale_id": result[0]["sale_id"]}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{sale_id}")
async def delete_sale(sale_id: int):
"""Удаление записи о продаже"""
try:
db.execute_query("DELETE FROM sales WHERE sale_id = %s", (sale_id,))
return {"message": "Sale deleted successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View file

@ -0,0 +1,103 @@
# app/routes/upload.py
"""
Маршруты API для загрузки и импорта данных
Соответствует требованиям ТЗ по импорту данных
"""
import pandas as pd
from fastapi import APIRouter, UploadFile, File, HTTPException
from app.database import db
from app.models import UploadResponse
router = APIRouter()
@router.post("/partners")
async def upload_partners(file: UploadFile = File(...)):
"""
Загрузка партнеров из файла
Подготовка данных для импорта согласно ТЗ
"""
try:
if file.filename.endswith('.xlsx'):
df = pd.read_excel(file.file)
elif file.filename.endswith('.csv'):
df = pd.read_csv(file.file)
else:
raise HTTPException(status_code=400, detail="Unsupported file format")
processed = 0
errors = []
for index, row in df.iterrows():
try:
# Валидация и преобразование данных
rating = row.get('rating', 0)
if pd.isna(rating):
rating = 0
db.execute_query("""
INSERT INTO partners
(partner_type, company_name, legal_address, inn, director_name,
phone, email, rating, sales_locations)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (
row.get('partner_type'),
row.get('company_name'),
row.get('legal_address'),
row.get('inn'),
row.get('director_name'),
row.get('phone'),
row.get('email'),
int(rating), # Конвертация в целое число
row.get('sales_locations')
))
processed += 1
except Exception as e:
errors.append(f"Row {index}: {str(e)}")
return UploadResponse(
message="File processed successfully",
processed_rows=processed,
errors=errors
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/sales")
async def upload_sales(file: UploadFile = File(...)):
"""Загрузка продаж из файла"""
try:
if file.filename.endswith('.xlsx'):
df = pd.read_excel(file.file)
elif file.filename.endswith('.csv'):
df = pd.read_csv(file.file)
else:
raise HTTPException(status_code=400, detail="Unsupported file format")
processed = 0
errors = []
for index, row in df.iterrows():
try:
db.execute_query("""
INSERT INTO sales
(partner_id, product_name, quantity, sale_date)
VALUES (%s, %s, %s, %s)
""", (
int(row.get('partner_id')),
row.get('product_name'),
row.get('quantity'),
row.get('sale_date')
))
processed += 1
except Exception as e:
errors.append(f"Row {index}: {str(e)}")
return UploadResponse(
message="File processed successfully",
processed_rows=processed,
errors=errors
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))