feat: price list generator

This commit is contained in:
2024-10-11 20:17:56 +04:00
parent 1eb85279b5
commit c5f7ce651f
5 changed files with 276 additions and 1 deletions

View File

@@ -0,0 +1,88 @@
from collections import defaultdict
from io import BytesIO
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
from weasyprint import HTML, CSS
from constants import ENV, APP_PATH
from enums.service import ServiceType
from models import Service, ServiceCategory
class PriceListPdfGenerator:
def __init__(self, session: AsyncSession):
self._session = session
async def _get_services_data(self):
# Получаем услуги из базы данных, отсортированные по рангу
services = (await (
self._session.scalars(
select(Service)
.options(joinedload(Service.category))
.filter(Service.is_deleted == False)
.order_by(Service.rank)
)
)).all()
# Группируем услуги по типу сервиса и категории
intermediate_result = defaultdict(lambda: defaultdict(list))
for service in services:
intermediate_result[service.service_type][service.category_id].append(service)
# Формируем окончательный результат с сортировкой категорий
final_result = defaultdict(dict)
for service_type, categories_dict in intermediate_result.items():
# Извлекаем уникальные категории
categories = {service.category for services in categories_dict.values() for service in services}
# Определяем функцию сортировки категорий по рангу
def category_sort_key(category):
if service_type == ServiceType.DEAL_SERVICE:
return category.deal_service_rank
else:
return category.product_service_rank
# Сортируем категории по определенному рангу
sorted_categories = sorted(categories, key=category_sort_key)
# Строим словарь категорий в отсортированном порядке
sorted_categories_dict = {}
for category in sorted_categories:
sorted_categories_dict[category.id] = categories_dict[category.id]
final_result[service_type] = sorted_categories_dict
final_final_result = {}
for service_type in [ServiceType.DEAL_SERVICE, ServiceType.PRODUCT_SERVICE]:
final_final_result[service_type] = final_result[service_type]
return dict(final_final_result)
async def _create_price_list_html(self):
categories = await self._session.scalars(select(ServiceCategory))
categories_dict = {category.id: category.name for category in categories}
services_data = await self._get_services_data()
service_type_dict = {
ServiceType.DEAL_SERVICE: "Общие услуги",
ServiceType.PRODUCT_SERVICE: "Услуги фулфилмента",
}
template = ENV.get_template("price-list.html")
result = template.render({
"services_data": services_data,
"categories_dict": categories_dict,
"service_type_dict": service_type_dict,
})
return result
async def create_price_list_pdf(self) -> BytesIO:
doc = await self._create_price_list_html()
pdf_file = BytesIO()
HTML(string=doc).write_pdf(pdf_file, stylesheets=[CSS(APP_PATH + '/static/css/price-list.css')])
return pdf_file

View File

@@ -1,11 +1,13 @@
from io import BytesIO
from typing import Annotated
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Response
from sqlalchemy.ext.asyncio import AsyncSession
import enums.service
from backend.dependecies import SessionDependency
from backend.session import get_session
from generators.price_list_pdf_generator.generator import PriceListPdfGenerator
from schemas.base import BaseEnumListSchema
from schemas.service import *
from services.auth import guest_user, authorized_user
@@ -84,6 +86,17 @@ async def reorder(
return await ServiceService(session).reorder(request)
@service_router.get(
'/price-list',
operation_id='get_price_list',
# dependencies=[Depends(authorized_user)],
)
async def get_price_list(
session: Annotated[AsyncSession, Depends(get_session)],
):
pdf_file: BytesIO = await PriceListPdfGenerator(session).create_price_list_pdf()
return Response(pdf_file.getvalue(), media_type='application/pdf')
# endregion
# region Categories

98
static/css/price-list.css Normal file
View File

@@ -0,0 +1,98 @@
@media print {
#header,
#footer,
#nav {
display: none !important;
}
}
html {
font-family: sans-serif;
}
.doc-container {
margin: -30px 0;
padding: 0 15px;
font-size: 13px;
}
img {
width: 170px;
margin-bottom: 15px;
}
table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
border: solid gray 2px;
border-radius: 15px;
margin-bottom: 10px;
}
thead {
display: table-header-group;
}
tfoot {
display: table-footer-group;
}
td:nth-child(5) {
border-right: 1px solid white;
}
table tbody tr:nth-child(odd) {
background-color: #e5e5e5;
}
tr {
page-break-inside: avoid;
}
td, th {
padding: 10px 13px;
}
td:first-child, th:first-child {
padding-left: 50px;
}
td:last-child, th:last-child {
padding-right: 50px;
}
td:first-child, th:first-child {
text-align: left;
}
td:first-child {
white-space: normal;
word-wrap: break-word;
}
td:not(:first-child), th:not(:first-child) {
text-align: right;
}
td:not(:first-child) {
overflow: hidden;
text-overflow: ellipsis;
}
table tfoot {
border-top: 2px solid black;
}
.category-title {
text-align: center;
background-color: #e5e5e5;
}
.service-type {
text-align: center;
font-size: 27px;
margin-top: 20px;
margin-bottom: 10px;
font-weight: bold;
}

File diff suppressed because one or more lines are too long