diff --git a/generators/deal_pdf_generator/deal_data.py b/generators/deal_pdf_generator/deal_data.py
index c9781b9..8d2f4d9 100644
--- a/generators/deal_pdf_generator/deal_data.py
+++ b/generators/deal_pdf_generator/deal_data.py
@@ -4,16 +4,22 @@ from models import DealProduct, Deal, DealStatusHistory
class DealTechSpecProductData(TypedDict):
- deal_products: List[DealProduct]
+ deal: Deal
+ last_status: DealStatusHistory
total_one_product: int
quantity: int
additional_info: Optional[str]
+ # Поле для группировки товаров с одним артикулом и вывода таблицы [Штрихкод, Размер, Кол-во, Короба]
+ deal_products: List[DealProduct]
+
+ # Поле для группировки товаров из нескольких сделок и вывода таблицы [Склад отгрузки, Кол-во]
+ warehouses_and_quantities: List[Tuple[str, int]]
+
class DealTechSpecData(TypedDict):
- deal: Deal
+ deals: List[Deal]
products: Dict[str, DealTechSpecProductData]
- current_status_str: str
- last_status: DealStatusHistory
- product_images: Tuple[str]
-
+ product_images: Tuple
+ deal_ids_header: str
+ deal_status_str: list[str]
diff --git a/generators/deal_pdf_generator/generator.py b/generators/deal_pdf_generator/generator.py
index 4950cc1..2e93319 100644
--- a/generators/deal_pdf_generator/generator.py
+++ b/generators/deal_pdf_generator/generator.py
@@ -8,13 +8,46 @@ from weasyprint import HTML, CSS
from constants import DEAL_STATUS_STR, ENV, APP_PATH
from generators.deal_pdf_generator.deal_data import DealTechSpecProductData, DealTechSpecData
-from models import Deal, DealProduct, DealService as DealServiceModel, Product
+from models import Deal, DealProduct, DealService as DealServiceModel, Product, DealGroup
from utils.images_fetcher import fetch_images
+# Генерация ключа для группировки deal_product по артикулу и услугам
+def _gen_key_for_product(deal_product: DealProduct) -> str:
+ return f"{deal_product.product.article} - " + ",".join(
+ str(service.service_id) for service in deal_product.services
+ )
+
+
+# Генерация ключа для группировки deal_product из группы сделок по артикулу, сервисам, а также товарам
+def _regen_key_for_product(product_data: DealTechSpecProductData) -> str:
+ if len(product_data['deal_products']) == 0:
+ return ""
+
+ article = product_data['deal_products'][0].product.article
+ services_ids = ",".join(str(service.service_id) for service in product_data['deal_products'][0].services)
+
+ if len(product_data['deal_products']) == 1:
+ products = product_data['deal_products'][0].product_id
+ else:
+ products = ",".join(
+ f"{deal_product.product_id}-{deal_product.quantity}" for deal_product in product_data['deal_products']
+ )
+
+ return f"{article}+{services_ids}+{products}"
+
+
class DealTechSpecPdfGenerator:
def __init__(self, session: AsyncSession):
self._session = session
+ self.deal_doc: DealTechSpecData = {
+ "deals": [],
+ "products": {},
+ "product_images": (),
+ "deal_ids_header": "",
+ "deal_status_str": DEAL_STATUS_STR,
+ }
+ self.deal: Deal
@staticmethod
async def _group_deal_products_by_products(deal_products: List[DealProduct]) -> Dict[str, DealTechSpecProductData]:
@@ -23,15 +56,15 @@ class DealTechSpecPdfGenerator:
for deal_product in deal_products:
# Для группировки по артикулу и услугам
- key = f"{deal_product.product.article} - " + ",".join(
- str(service.service_id) for service in deal_product.services
- )
+ key = _gen_key_for_product(deal_product)
if key not in products:
products[key] = {
+ "deal": deal_product.deal,
"deal_products": [deal_product],
"quantity": deal_product.quantity,
"additional_info": deal_product.product.additional_info,
+ "warehouses_and_quantities": [],
}
else:
products[key]["deal_products"].append(deal_product)
@@ -50,39 +83,72 @@ class DealTechSpecPdfGenerator:
selectinload(Deal.products).selectinload(DealProduct.product).selectinload(Product.barcodes),
selectinload(Deal.services).selectinload(DealServiceModel.service),
selectinload(Deal.status_history),
+ selectinload(Deal.group).selectinload(DealGroup.deals),
joinedload(Deal.client),
joinedload(Deal.shipping_warehouse),
)
)
return deal
+ async def _get_deals_by_group_id(self, group_id: int) -> List[Deal]:
+ group: DealGroup | None = await self._session.scalar(
+ select(DealGroup)
+ .where(DealGroup.id == group_id)
+ .options(
+ selectinload(DealGroup.deals).selectinload(Deal.products).selectinload(DealProduct.services),
+ selectinload(DealGroup.deals).selectinload(Deal.products)
+ .selectinload(DealProduct.product).selectinload(Product.barcodes),
+ selectinload(DealGroup.deals).selectinload(Deal.services).selectinload(DealServiceModel.service),
+ selectinload(DealGroup.deals).selectinload(Deal.status_history),
+ selectinload(DealGroup.deals).selectinload(Deal.group).selectinload(DealGroup.deals),
+ selectinload(DealGroup.deals).joinedload(Deal.client),
+ selectinload(DealGroup.deals).joinedload(Deal.shipping_warehouse),
+ )
+ )
+ return group.deals if group else []
+
+ def _set_deals_ids_header(self):
+ self.deal_doc["deal_ids_header"] = f"ID: {self.deal.id}"
+ if self.deal.group:
+ self.deal_doc["deal_ids_header"] = "ID: " + ", ".join(str(d.id) for d in self.deal.group.deals)
+
async def _create_deal_tech_spec_document_html(self, deal_id: int):
deal = await self._get_deal_by_id(deal_id)
-
if not deal:
return ""
+ self.deal = deal
- products = await self._group_deal_products_by_products(deal.products)
+ self._set_deals_ids_header()
+
+ if deal.group:
+ deals = await self._get_deals_by_group_id(deal.group.id)
+ for d in deals:
+ self.deal_doc["deals"].append(d)
+ grouped_products = await self._group_deal_products_by_products(d.products)
+ for product in grouped_products.values():
+ key = _regen_key_for_product(product)
+ if key not in self.deal_doc["products"]:
+ self.deal_doc["products"][key] = product
+ else:
+ self.deal_doc["products"][key]["quantity"] += product["quantity"]
+ self.deal_doc["products"][key]["warehouses_and_quantities"].append((
+ product["deal"].shipping_warehouse.name, product["quantity"],
+ ))
+ else:
+ self.deal_doc["deals"] = [deal]
+ self.deal_doc["products"] = await self._group_deal_products_by_products(deal.products)
product_urls: List[Optional[str]] = []
- for product in products.values():
+ for product in self.deal_doc["products"].values():
if len(product["deal_products"][0].product.images) > 0:
product_urls.append(product["deal_products"][0].product.images[0].image_url)
else:
product_urls.append(None)
- product_images = await fetch_images(product_urls)
-
- document_deal_data: DealTechSpecData = {
- "deal": deal,
- "products": products,
- "current_status_str": DEAL_STATUS_STR[deal.current_status],
- "last_status": max(deal.status_history, key=lambda status: status.changed_at),
- "product_images": product_images,
- }
+ self.deal_doc["product_images"] = await fetch_images(product_urls)
template = ENV.get_template("deal/deal-tech-spec.html")
- result = template.render({"data": document_deal_data, "sign_place_text": "_" * 22})
+ result = template.render({"data": self.deal_doc, "sign_place_text": "_" * 22})
return result
async def create_deal_tech_spec_pdf(self, deal_id) -> BytesIO:
diff --git a/static/css/deal-tech-spec.css b/static/css/deal-tech-spec.css
index da001ff..3980aab 100644
--- a/static/css/deal-tech-spec.css
+++ b/static/css/deal-tech-spec.css
@@ -29,6 +29,7 @@ hr {
}
.deal-name-id-container {
+ margin-top: 14px;
justify-content: space-between;
display: flex;
align-items: center;
diff --git a/templates/documents/deal/deal-product-services.html b/templates/documents/deal/deal-product-services.html
index 27319e3..d0a5881 100644
--- a/templates/documents/deal/deal-product-services.html
+++ b/templates/documents/deal/deal-product-services.html
@@ -28,8 +28,8 @@
{% if common_product.color %}
Цвет: {{ common_product.color }}
{% endif %}
- {% if product_data.deal_products|length > 1 %}
- {% if common_deal_product.quantity %}
+ {% if product_data.deal_products|length > 1 or product_data.warehouses_and_quantities|length > 1 %}
+ {% if product_data.quantity %}
Суммарное количество: {{ product_data.quantity }}
{% endif %}
{% if product_data.additional_info %}
@@ -80,7 +80,7 @@
- {% if product_data.deal_products|length == 1 and common_deal_product.comment %}
+ {% if product_data.deal_products|length == 1 and common_deal_product.comment %}
@@ -88,9 +88,29 @@
+
+ | {{ common_deal_product.comment }} |
+
+
+
+ {% endif %}
+
+
+ {% if product_data.warehouses_and_quantities|length > 1 %}
+
+
+
+ | Склад отгрузки |
+ Количество |
+
+
+
+ {% for row in product_data.warehouses_and_quantities %}
- | {{ common_deal_product.comment }} |
+ {{ row[0] }} |
+ {{ row[1] }} |
+ {% endfor %}
{% endif %}
diff --git a/templates/documents/deal/deal-services.html b/templates/documents/deal/deal-services.html
index 25ec48f..da1a0b9 100644
--- a/templates/documents/deal/deal-services.html
+++ b/templates/documents/deal/deal-services.html
@@ -1,62 +1,67 @@
-
-
- Название сделки: {{ data.deal.name }}
+
+ {% if index != 0 %}
+
+ {% endif %}
+
+
+ Название сделки: {{ deal.name }}
+
-
-
-
- Дата создания: {{ now().strftime("%d.%m.%Y, %H:%M") }}
-
-
- Текущий статус: {{ data.current_status_str }}
-
-
- {% if data.deal.comment %}
- Комментарий к сделке: {{ data.deal.comment }}
- {% endif %}
-
-
-
-
-
- Клиент: {{ data.deal.client.name }}
-
-
+
+
+ Дата создания: {{ now().strftime("%d.%m.%Y, %H:%M") }}
+
+
+ Текущий статус: {{ data.deal_status_str[deal.current_status] }}
+
+
+ {% if deal.comment %}
+ Комментарий к сделке: {{ deal.comment }}
+ {% endif %}
+
+
+
+
+
+ Клиент: {{ deal.client.name }}
+
+
- Дата отгрузки:
- {% if data.deal.delivery_date %}
- {{ data.deal.delivery_date.strftime("%d.%m.%Y") }}
+ Дата отгрузки:
+ {% if deal.delivery_date %}
+ {{ deal.delivery_date.strftime("%d.%m.%Y") }}
+ {% else %}
+ Не указана
+ {% endif %}
+
+ {% if deal.base_marketplace.name %}
+
Маркетплейс: {{ deal.base_marketplace.name }}
+ {% endif %}
+ {% if deal.shipping_warehouse.name %}
+
Склад отгрузки: {{ deal.shipping_warehouse.name }}
+ {% endif %}
+ {% if deal.services|length > 0 %}
+
+
+
+ | Общие услуги |
+ Количество |
+
+
+
+
+
+ {% for service in deal.services %}
+
+ | {{ service.service.name }} |
+ {{ '{:,}'.format(service.quantity) }} шт. |
+
+ {% endfor %}
+
+
{% else %}
- Не указана
+
{% endif %}
-{% if data.deal.base_marketplace.name %}
-
Маркетплейс: {{ data.deal.base_marketplace.name }}
-{% endif %}
-{% if data.deal.shipping_warehouse.name %}
-
Склад отгрузки: {{ data.deal.shipping_warehouse.name }}
-{% endif %}
-{% if data.deal.services|length > 0 %}
-
-
-
- | Общие услуги |
- Количество |
-
-
-
-
-
- {% for service in data.deal.services %}
-
- | {{ service.service.name }} |
- {{ '{:,}'.format(service.quantity) }} шт. |
-
- {% endfor %}
-
-
-{% else %}
-
-{% endif %}
\ No newline at end of file
diff --git a/templates/documents/deal/deal-tech-spec.html b/templates/documents/deal/deal-tech-spec.html
index 795221e..2e7d2c2 100644
--- a/templates/documents/deal/deal-tech-spec.html
+++ b/templates/documents/deal/deal-tech-spec.html
@@ -29,7 +29,7 @@
font-size: 18px;
}
@top-right {
- content: "ID:{{ data.deal.id }}";
+ content: "{{ data.deal_ids_header }}";
border: solid black 1px;
border-radius: 6px;
padding: 12px 2px;
@@ -44,7 +44,11 @@
- {% include "deal/deal-services.html" %}
+ {% for deal in data.deals %}
+ {% with index = loop.index0 %}
+ {% include "deal/deal-services.html" %}
+ {% endwith %}
+ {% endfor %}
{% for product_data in data.products.values() %}
{% with index = loop.index %}
{% include "deal/deal-product-services.html" %}