diff --git a/services/billing.py b/services/billing.py index c344170..416190e 100644 --- a/services/billing.py +++ b/services/billing.py @@ -1,6 +1,7 @@ from collections import defaultdict from io import BytesIO from typing import List +from uuid import uuid4 from fastapi import HTTPException from number_to_string import get_string_by_number @@ -16,7 +17,7 @@ from constants import MONTHS, ENV from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \ BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema, \ ProductBillingDocumentPdf, ServiceBillingDocumentPdf -from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel, DealProductService +from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel, DealProductService, DealGroup from schemas.billing import * from services.base import BaseService from services.deal import DealService @@ -170,43 +171,60 @@ class BillingService(BaseService): except Exception as e: return CancelDealBillResponse(ok=False, message=str(e)) - async def _get_products_for_deal(self, deal: Deal) -> tuple[list, list, bool]: - services: list[ServiceBillingDocumentPdf] = [] - products: list[ProductBillingDocumentPdf] = [] + def _gen_key_for_service(self, service: ServiceBillingDocumentPdf) -> str: + return f"{service.name}-{service.price}" + + def _gen_key_for_product(self, product: ProductBillingDocumentPdf) -> str: + article = product.article if product.article else uuid4() + return f"{article}-{product.size}-{product.price}" + + async def _get_products_for_deal(self, deals: list[Deal]) -> tuple[dict, dict, bool]: + services: dict[str, ServiceBillingDocumentPdf] = {} + products: dict[str, ProductBillingDocumentPdf] = {} is_size_needed: bool = False - for product in deal.products: - product_price = 0 - for service in product.services: - services.append( - ServiceBillingDocumentPdf( + for deal in deals: + for product in deal.products: + product_price = 0 + for service in product.services: + service_data = ServiceBillingDocumentPdf( name=f'[{product.product.name}] - {service.service.name}', price=service.price, quantity=product.quantity ) - ) - product_price += service.price - is_size_needed = is_size_needed | bool(product.product.size) - products.append( - ProductBillingDocumentPdf( + key = self._gen_key_for_service(service_data) + if key in services: + services[key].quantity += product.quantity + else: + services[key] = service_data + product_price += service_data.price + is_size_needed = is_size_needed | bool(product.product.size) + product_data = ProductBillingDocumentPdf( article=product.product.article, size=product.product.size if product.product.size else "", price=product_price, quantity=product.quantity, ) - ) - for service in deal.services: - services.append( - ServiceBillingDocumentPdf( - name=f'{service.service.name}', + product_key = self._gen_key_for_product(product_data) + if product_key in products: + products[product_key].quantity += product_data.quantity + else: + products[product_key] = product_data + for service in deal.services: + service_data = ServiceBillingDocumentPdf( + name=service.service.name, price=service.price, quantity=service.quantity ) - ) + key = self._gen_key_for_service(service_data) + if key in services: + services[key].quantity += service_data.quantity + else: + services[key] = service_data return services, products, is_size_needed - async def _create_billing_document_html(self, deal_id: int): + async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]: deal: Deal | None = await self.session.scalar( select(Deal) .where(Deal.id == deal_id) @@ -215,15 +233,38 @@ class BillingService(BaseService): selectinload(Deal.services).selectinload(DealServiceModel.service), joinedload(Deal.shipping_warehouse), joinedload(Deal.client), + selectinload(Deal.group).selectinload(DealGroup.deals), ) ) + 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.services).selectinload(DealServiceModel.service), + selectinload(DealGroup.deals).joinedload(Deal.shipping_warehouse), + selectinload(DealGroup.deals).joinedload(Deal.client), + selectinload(DealGroup.deals).selectinload(Deal.group).selectinload(DealGroup.deals), + ) + ) + return group.deals if group else [] + + async def _create_billing_document_html(self, deal_id: int): + deal = await self._get_deal_by_id(deal_id) if not deal: return "" - (services, products, is_size_needed) = await self._get_products_for_deal(deal) + if deal.group: + deals = await self._get_deals_by_group_id(deal.group.id) + else: + deals = [deal] - deal_price = sum((service.price * service.quantity for service in services)) + (services, products, is_size_needed) = await self._get_products_for_deal(deals) + + deal_price = sum((service.price * service.quantity for service in services.values())) deal_price_words = get_string_by_number(deal_price)[0:-10] deal_price = to_locale_number(deal_price) template = ENV.get_template("bill-of-payment.html") diff --git a/templates/documents/bill-of-payment.html b/templates/documents/bill-of-payment.html index eacc1e0..7236327 100644 --- a/templates/documents/bill-of-payment.html +++ b/templates/documents/bill-of-payment.html @@ -53,7 +53,7 @@
- {% for product in products %} + {% for product in products.values() %}