first commit

This commit is contained in:
2025-07-24 20:13:47 +03:00
commit 94b7585f8b
175 changed files with 85264 additions and 0 deletions

422
app/api/v1/deal/deal.py Normal file
View File

@ -0,0 +1,422 @@
import base64
from datetime import datetime
from io import BytesIO
from time import time
import barcode
from barcode.writer import ImageWriter
from fastapi import APIRouter
from fastapi.responses import Response
from num2words import num2words
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from starlette.requests import Request
from starlette.templating import Jinja2Templates
from app import mongo
from app.utils.response_util import response
router = APIRouter()
templates = Jinja2Templates(directory="assets/templates")
MONTHS_RU = {
1: "января",
2: "февраля",
3: "марта",
4: "апреля",
5: "мая",
6: "июня",
7: "июля",
8: "августа",
9: "сентября",
10: "октября",
11: "ноября",
12: "декабря",
}
def format_date_russian(date: datetime) -> str:
return f"{date.day} {MONTHS_RU[date.month]} {date.year} г."
@router.post("/quickCreate", tags=[""])
async def quick_create_deal(data: dict):
start_time = time()
data["id"] = await mongo.get_next_id(mongo.deals_collection)
data["createdAt"] = mongo.created_at()
status = await mongo.statuses_collection.find_one({"id": data["statusId"]}, {"_id": False})
if not status:
return response({"message": "Статус не найден", "ok": False}, start_time=start_time)
board = await mongo.boards_collection.find_one({"id": status["boardId"]}, {"_id": False})
if not board:
return response({"message": "Доска не найдена", "ok": False}, start_time=start_time)
data["boardId"] = status["boardId"]
data["projectId"] = board["projectId"]
await mongo.deals_collection.insert_one(data)
return response({
"message": "Сделка создана",
"ok": True,
"dealId": data["id"]
}, start_time=start_time)
@router.get("/get/{deal_id}", tags=[""])
async def get_deal(deal_id: int):
start_time = time()
deal = await mongo.deals_collection.find_one({
"id": deal_id
}, {
"_id": False
})
deals = await mongo.additional_deals_data(deal, full=True)
return response(deals[0], start_time=start_time)
@router.post("/update-general-info", tags=[""])
async def update_deal_general_info(params: dict, request: Request):
start_time = time()
data = params["data"]
deal_id = params["dealId"]
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
new_status = await mongo.statuses_collection.find_one({"id": data["statusId"]}, {"_id": False})
if new_status and new_status["name"] == "Завершено":
data["completedAt"] = mongo.created_at()
old_status_id = deal.get("statusId")
await mongo.deals_collection.update_one({
"id": deal_id
}, {
"$set": data
})
if old_status_id != data["statusId"]:
new_entry = {
"changedAt": mongo.created_at(),
"fromStatusId": old_status_id,
"toStatusId": data["statusId"],
"userId": request.state.user["id"],
"comment": ""
}
await mongo.deals_collection.update_one(
{"id": deal_id},
{"$push": {"statusHistory": new_entry}}
)
return response({
"message": "Данные сделки обновлены",
"ok": True
}, start_time=start_time)
@router.post("/delete", tags=[""])
async def delete_deal(params: dict):
start_time = time()
deal_id = params["dealId"]
await mongo.deals_collection.delete_one({"id": deal_id})
return response({
"message": "Сделка удалена",
"ok": True
}, start_time=start_time)
@router.post("/complete", tags=[""])
async def complete_deal(params: dict):
start_time = time()
deal_id = params["dealId"]
await mongo.deals_collection.update_one(
{"id": deal_id},
{"$set": {"isCompleted": True}}
)
return response({
"message": "Сделка завершена",
"ok": True
}, start_time=start_time)
@router.post("/prefill", tags=[""])
async def prefill_deal(params: dict):
start_time = time()
old_deal_id = params["oldDealId"]
new_deal_id = params["newDealId"]
old_deal = await mongo.deals_collection.find_one({"id": old_deal_id}, {"_id": False})
if not old_deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
await mongo.deals_collection.update_one(
{"id": new_deal_id},
{"$set": {
"name": old_deal["name"],
"clientName": old_deal["clientName"],
"services": old_deal.get("services", []),
"products": old_deal.get("products", [])
}}
)
return response({
"message": "Сделка предзаполнена",
"ok": True
}, start_time=start_time)
@router.get("/billing-document/{deal_id}", tags=[""])
async def deal_billing_document(deal_id: int):
pdfmetrics.registerFont(TTFont("Arial", "assets/arial.ttf"))
pdfmetrics.registerFont(TTFont("Arial Bold", "assets/arial_bold.ttf"))
center = ParagraphStyle(name="center", fontName="Arial", fontSize=10, alignment=1)
bold_center = ParagraphStyle(name="bold_center", fontName="Arial Bold", fontSize=10, alignment=1)
title = ParagraphStyle(name="title", fontName="Arial Bold", fontSize=14, alignment=1)
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=20, rightMargin=20, topMargin=30, bottomMargin=20)
elements = []
logo = Image("assets/logo.jpg", width=152, height=56)
logo.hAlign = "LEFT"
elements.append(logo)
elements.append(Spacer(1, 10))
elements.append(Paragraph("Расчет стоимости услуг", title))
elements.append(Spacer(1, 6))
elements.append(Paragraph(f"{deal['id']} от {format_date_russian(datetime.now())}", center))
elements.append(Paragraph("Адрес: 115516, г.Москва, ул. Промышленная 11", center))
elements.append(Paragraph(f"Клиент: {deal.get('clientName', 'Не указан')}", center))
elements.append(Paragraph(f"Маркетплейс: {deal.get('baseMarketplace', {}).get('name', 'Не указан')}", center))
elements.append(Paragraph(f"Склад отгрузки: {deal.get('shippingWarehouse', 'Не указан')}", center))
elements.append(Spacer(1, 10))
total_sum = 0
product_infos = []
table_width = 450
data_1 = [[
Paragraph("Артикул ВБ", bold_center),
Paragraph("Артикул продавца", bold_center),
Paragraph("Размер", bold_center),
Paragraph("Кол-во", bold_center),
Paragraph("Цена", bold_center),
Paragraph("Сумма", bold_center),
]]
for product in deal.get("products", []):
product_data = await mongo.products_collection.find_one({"id": product["productId"]}, {"_id": False})
if not product_data:
continue
article = product_data.get("article", "-")
vendor_code = product_data.get("vendorCode", "-")
size = product_data.get("size", "-")
quantity = product["quantity"]
price = sum(service["price"] * service.get("quantity", 1) for service in product["services"])
amount = quantity * price
total_sum += amount
data_1.append([
Paragraph(str(article), center),
Paragraph(str(vendor_code), center),
Paragraph(str(size or 0), center),
Paragraph(str(quantity or 1), center),
Paragraph(f"{price}", center),
Paragraph(f"{amount}", center),
])
product_infos.append({
"name": product_data.get("name") or "Без названия",
"quantity": product["quantity"],
"services": await get_services(product.get("services", []))
})
table_1 = Table(data_1, colWidths=[table_width / 6] * 5)
table_1.setStyle(TableStyle([
("BOX", (0, 0), (-1, -1), 0.8, colors.black),
("INNERGRID", (0, 0), (-1, -1), 0.4, colors.grey),
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
("ALIGN", (0, 0), (-1, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("ROUNDEDCORNERS", (16, 16, 16, 16)),
]))
elements.append(Paragraph("Товары", bold_center))
elements.append(Spacer(1, 4))
elements.append(table_1)
elements.append(Spacer(1, 12))
data_2 = [[
Paragraph("Товар / Услуга", bold_center),
Paragraph("Кол-во", bold_center),
Paragraph("Цена", bold_center),
Paragraph("Сумма", bold_center),
]]
for product in product_infos:
for service in product["services"]:
quantity = product.get("quantity", 0)
price = service.get("price", 0)
amount = quantity * price
data_2.append([
Paragraph(f"{product['name']} {service['name']}", center),
Paragraph(str(quantity), center),
Paragraph(f"{price}", center),
Paragraph(f"{amount}", center),
])
for service in await get_services(deal.get("services", [])):
quantity = service.get("quantity", 0)
price = service.get("price", 0)
amount = quantity * price
total_sum += amount
data_2.append([
Paragraph(service['name'], center),
Paragraph(str(quantity), center),
Paragraph(f"{price}", center),
Paragraph(f"{amount}", center)
])
table_2 = Table(data_2, colWidths=[
table_width * 0.4, table_width * 0.2, table_width * 0.2, table_width * 0.2
])
table_2.setStyle(TableStyle([
("BOX", (0, 0), (-1, -1), 0.8, colors.black),
("INNERGRID", (0, 0), (-1, -1), 0.4, colors.grey),
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
("ALIGN", (0, 0), (-1, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("ROUNDEDCORNERS", (16, 16, 16, 16)),
]))
elements.append(Paragraph("Услуги", bold_center))
elements.append(Spacer(1, 4))
elements.append(table_2)
elements.append(Spacer(1, 12))
elements.append(Paragraph(f"+79013618377 Ангелина К. (Т-Банк) Итого к оплате: {total_sum:,.0f}".replace(",", " "), bold_center))
elements.append(Paragraph(num2words(total_sum, lang='ru').capitalize() + " рублей", center))
doc.build(elements)
buffer.seek(0)
return Response(buffer.getvalue(), media_type="application/pdf", headers={"Content-Disposition": "inline; filename=billing_document.pdf"})
@router.get("/tech-spec/{deal_id}", tags=[""])
async def deal_tech_spec(request: Request, deal_id: int):
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
status = await mongo.statuses_collection.find_one({"id": deal["statusId"]}, {"_id": False})
deal_status = status.get("name", "Статус не определен") if status else "Статус не найден"
deal_services = await get_services(deal.get("services", []))
deal_data = {
"id": deal["id"],
"name": deal.get("name") or "Без названия",
"status": deal_status,
"createdAt": datetime.strptime(deal["createdAt"], "%Y-%m-%dT%H:%M:%S").strftime("%d.%m.%Y, %H:%M"),
"clientName": deal.get("clientName") or "Не указан",
"deliveryDate": (
datetime.strptime(deal["deliveryDate"], "%Y-%m-%dT%H:%M:%S.%f").strftime("%d.%m.%Y")
if deal.get("deliveryDate") else "Не указана"
),
"baseMarketplace": deal.get("baseMarketplace", {}).get("name") or "Не указан",
"shippingWarehouse": deal.get("shippingWarehouse") or "Не указан",
"products": [],
"services": deal_services
}
for product in deal.get("products", []):
product_data = await mongo.products_collection.find_one({"id": product["productId"]}, {"_id": False})
if not product_data:
continue
barcode_value = product_data.get("barcodes", [None])[0]
barcode_image = generate_barcode_base64(barcode_value) if barcode_value else None
product_services = await get_services(product.get("services", []))
deal_data["products"].append({
"name": product_data.get("name") or "Без названия",
"article": product_data.get("vendorCode") or "Нет артикула",
"imageUrl": product_data.get("imageUrl"),
"size": product_data.get("size") or 0,
"quantity": product.get("quantity") or 0,
"barcode": barcode_image,
"services": product_services,
"comment": product.get("comment")
})
return templates.TemplateResponse("deal_tech_spec.html", {"request": request, "deal": deal_data})
@router.post("/add-kit")
async def add_kits(params: dict):
start_time = time()
deal_id = params["dealId"]
kit_id = params["kitId"]
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
service_kit = await mongo.service_kits_collection.find_one({"id": kit_id})
if not service_kit:
return response({"message": "Комплект не найден", "ok": False}, start_time=start_time)
for service in service_kit.get("services", []):
price = service.get("price", 0)
if service.get("priceRanges"):
price_range = service["priceRanges"][0]
price = price_range.get("price", price)
deal["services"].append({
"serviceId": service["id"],
"price": price,
"isFixedPrice": False,
"quantity": 1
})
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"services": deal["services"]}})
return response({
"message": "Сервисы из комплекта добавлены в сделку",
"ok": True
}, start_time=start_time)
async def get_services(services):
service_list = []
for service in services:
service_data = await mongo.services_collection.find_one({"id": service["serviceId"]}, {"_id": False})
if service_data:
service_list.append({
"name": service_data.get("name") or "Неизвестная услуга",
"quantity": service.get("quantity") or 0,
"price": service.get("price") or 0
})
return service_list
def generate_barcode_base64(barcode_text: str) -> str:
buffer = BytesIO()
code128 = barcode.get("code128", barcode_text, writer=ImageWriter())
code128.write(buffer, {"module_height": 10.0, "font_size": 10, "quiet_zone": 2.0})
buffer.seek(0)
encoded = base64.b64encode(buffer.read()).decode("utf-8")
return f"data:image/png;base64,{encoded}"