feat: cards, attributes and modules

This commit is contained in:
2025-02-19 14:46:31 +04:00
parent a509a3a586
commit 1af78ce08a
61 changed files with 3212 additions and 2795 deletions

View File

@@ -1,4 +1,3 @@
import logging
from io import BytesIO
from typing import List
from uuid import uuid4
@@ -16,24 +15,24 @@ 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, DealGroup, \
from models import CardBillRequest, Card, CardProduct, CardService as CardServiceModel, CardGroup, \
GroupBillRequest
from schemas.billing import *
from services.base import BaseService
from services.deal import DealService
from services.card import CardsService
from utils.list_utils import to_locale_number
class BillingService(BaseService):
async def _process_deal_update_details(
async def _process_card_update_details(
self,
request: BillStatusUpdateRequest,
):
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id)
if not deal_bill_request:
bill_request = await self._get_card_bill_by_id(request.listener_transaction_id)
if not bill_request:
return
deal_bill_request.pdf_url = request.info.pdf_url
deal_bill_request.invoice_number = request.info.invoice_number
bill_request.pdf_url = request.info.pdf_url
bill_request.invoice_number = request.info.invoice_number
async def _process_group_update_details(
self,
@@ -63,19 +62,19 @@ class BillingService(BaseService):
if not response.ok:
return
if type(request.listener_transaction_id) is int:
await self._process_deal_update_details(request)
await self._process_card_update_details(request)
else:
await self._process_group_update_details(request)
await self.session.commit()
async def _process_deal_update_verification(
async def _process_card_update_verification(
self,
request: BillStatusUpdateRequest
):
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id)
if not deal_bill_request:
card_bill_request = await self._get_card_bill_by_id(request.listener_transaction_id)
if not card_bill_request:
return
deal_bill_request.paid = request.info.payed
card_bill_request.paid = request.info.payed
async def _process_group_update_verification(
self,
@@ -104,7 +103,7 @@ class BillingService(BaseService):
if not response.ok:
return
if type(request.listener_transaction_id) is int:
await self._process_deal_update_verification(request)
await self._process_card_update_verification(request)
else:
await self._process_group_update_verification(request)
await self.session.commit()
@@ -118,37 +117,37 @@ class BillingService(BaseService):
elif request.channel == NotificationChannel.PAYMENT_VERIFICATION:
await self._process_update_verification(request)
async def create_deal_bill_request(self, deal: Deal):
deal_bill_request = DealBillRequest(
deal_id=deal.id,
async def create_card_bill_request(self, card: Card):
card_bill_request = CardBillRequest(
card_id=card.id,
created_at=datetime.datetime.now()
)
self.session.add(deal_bill_request)
deal.is_locked = True
self.session.add(card_bill_request)
card.is_locked = True
await self.session.commit()
async def create_group_bill_request(self, group: DealGroup):
async def create_group_bill_request(self, group: CardGroup):
group_bill_request = GroupBillRequest(
group_id=group.id,
created_at=datetime.datetime.now()
)
self.session.add(group_bill_request)
for deal in group.deals:
deal.is_locked = True
for card in group.cards:
card.is_locked = True
await self.session.commit()
async def create_deal_billing(self, user, request: CreateDealBillRequest) -> CreateDealBillResponse:
async def create_card_billing(self, user, request: CreateCardBillRequest) -> CreateCardBillResponse:
try:
deal_service = DealService(self.session)
card_service = CardsService(self.session)
billing_client = BillingClient(backend.config.BILLING_API_KEY)
basic_deal: Deal = await deal_service.get_by_id(user, request.deal_id, return_raw=True)
if basic_deal.group:
deals = await self._get_deals_by_group_id(basic_deal.group.id)
basic_card: Card = await card_service.get_by_id(user, request.card_id, return_raw=True)
if basic_card.group:
cards = await self._get_cards_by_group_id(basic_card.group.id)
else:
deals = [basic_deal]
cards = [basic_card]
(services, products, is_size_needed) = await self._get_products_for_deal(deals)
(services, products, is_size_needed) = await self._get_products_for_card(cards)
services: dict[str, ServiceBillingDocumentPdf]
products: dict[str, ProductBillingDocumentPdf]
is_size_needed: bool
@@ -163,72 +162,72 @@ class BillingService(BaseService):
)
)
deal = basic_deal
listener_transaction_id = deal.id
if deal.group:
listener_transaction_id = f"group-{basic_deal.group.id}"
inn: str = deal.client.details.inn
card = basic_card
listener_transaction_id = card.id
if card.group:
listener_transaction_id = f"group-{basic_card.group.id}"
inn: str = card.client.details.inn
create_bill_request = CreateBillRequestSchema(
listener_transaction_id=listener_transaction_id,
payer_name=deal.client.name,
payer_name=card.client.name,
payer_inn=inn.strip(),
payer_phone=deal.client.details.phone_number,
payer_phone=card.client.details.phone_number,
items=CreateBillRequestItems(
values=billing_request_values
)
)
create_bill_response = await billing_client.create(create_bill_request)
if not create_bill_response.ok:
return CreateDealBillResponse(ok=create_bill_response.ok, message='Ошибка!')
return CreateCardBillResponse(ok=create_bill_response.ok, message='Ошибка!')
if basic_deal.group:
await self.create_group_bill_request(basic_deal.group)
if basic_card.group:
await self.create_group_bill_request(basic_card.group)
else:
await self.create_deal_bill_request(basic_deal)
await self.create_card_bill_request(basic_card)
return CreateDealBillResponse(ok=create_bill_response.ok, message='Заявка успешно создана!')
return CreateCardBillResponse(ok=create_bill_response.ok, message='Заявка успешно создана!')
except Exception as e:
return CreateDealBillResponse(ok=False, message=str(e))
return CreateCardBillResponse(ok=False, message=str(e))
async def _get_deal_bill_by_id(self, deal_id: int) -> Optional[DealBillRequest]:
return await self.session.scalar(select(DealBillRequest).where(DealBillRequest.deal_id == deal_id))
async def _get_card_bill_by_id(self, card_id: int) -> Optional[CardBillRequest]:
return await self.session.scalar(select(CardBillRequest).where(CardBillRequest.card_id == card_id))
async def _get_group_bill_by_id(self, group_id: int) -> Optional[GroupBillRequest]:
return await self.session.scalar(select(GroupBillRequest).where(GroupBillRequest.group_id == group_id))
async def get_deal_bill_by_id(self, deal_id: int) -> GetDealBillById:
deal_bill = await self._get_deal_bill_by_id(deal_id)
if not deal_bill:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Deal bill was not found')
return GetDealBillById(deal_bill=DealBillRequestSchema.model_validate(deal_bill))
async def get_card_bill_by_id(self, card_id: int) -> GetCardBillById:
bill = await self._get_card_bill_by_id(card_id)
if not bill:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Card bill was not found')
return GetCardBillById(card_bill=CardBillRequestSchema.model_validate(bill))
async def cancel_deal_billing(self, user, request: CancelDealBillRequest) -> CancelDealBillResponse:
async def cancel_card_billing(self, user, request: CancelCardBillRequest) -> CancelCardBillResponse:
try:
deal = await self._get_deal_by_id(request.deal_id)
if not deal:
return CancelDealBillResponse(ok=False, message='Сделка не найдена')
card = await self._get_card_by_id(request.card_id)
if not card:
return CancelCardBillResponse(ok=False, message='Сделка не найдена')
if deal.group:
bill = await self._get_group_bill_by_id(deal.group.id)
if card.group:
bill = await self._get_group_bill_by_id(card.group.id)
if not bill:
return CancelDealBillResponse(ok=False, message='Заявка не найдена')
return CancelCardBillResponse(ok=False, message='Заявка не найдена')
billing_client = BillingClient(backend.config.BILLING_API_KEY)
response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=deal.group.id))
response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=card.group.id))
else:
bill = await self._get_deal_bill_by_id(request.deal_id)
bill = await self._get_card_bill_by_id(request.card_id)
if not bill:
return CancelDealBillResponse(ok=False, message='Заявка не найдена')
return CancelCardBillResponse(ok=False, message='Заявка не найдена')
billing_client = BillingClient(backend.config.BILLING_API_KEY)
response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=request.deal_id))
response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=request.card_id))
if not response.ok:
return CancelDealBillResponse(ok=False, message='Ошибка')
return CancelCardBillResponse(ok=False, message='Ошибка')
await self.session.delete(bill)
await self.session.commit()
return CancelDealBillResponse(ok=True, message='Заявка успешно отозвана')
return CancelCardBillResponse(ok=True, message='Заявка успешно отозвана')
except Exception as e:
return CancelDealBillResponse(ok=False, message=str(e))
return CancelCardBillResponse(ok=False, message=str(e))
def _gen_key_for_service(self, service: ServiceBillingDocumentPdf) -> str:
return f"{service.name}-{service.price}"
@@ -237,13 +236,13 @@ class BillingService(BaseService):
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]:
async def _get_products_for_card(self, cards: list[Card]) -> tuple[dict, dict, bool]:
services: dict[str, ServiceBillingDocumentPdf] = {}
products: dict[str, ProductBillingDocumentPdf] = {}
is_size_needed: bool = False
for deal in deals:
for product in deal.products:
for card in cards:
for product in card.products:
product_price = 0
for service in product.services:
service_data = ServiceBillingDocumentPdf(
@@ -269,7 +268,7 @@ class BillingService(BaseService):
products[product_key].quantity += product_data.quantity
else:
products[product_key] = product_data
for service in deal.services:
for service in card.services:
service_data = ServiceBillingDocumentPdf(
name=service.service.name,
price=service.price,
@@ -283,49 +282,49 @@ class BillingService(BaseService):
return services, products, is_size_needed
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)
async def _get_card_by_id(self, card_id: int) -> Optional[Card]:
card: Card | None = await self.session.scalar(
select(Card)
.where(Card.id == card_id)
.options(
selectinload(Deal.products).selectinload(DealProduct.services),
selectinload(Deal.services).selectinload(DealServiceModel.service),
joinedload(Deal.shipping_warehouse),
joinedload(Deal.client),
selectinload(Deal.group).selectinload(DealGroup.deals),
selectinload(Card.products).selectinload(CardProduct.services),
selectinload(Card.services).selectinload(CardServiceModel.service),
joinedload(Card.shipping_warehouse),
joinedload(Card.client),
selectinload(Card.group).selectinload(CardGroup.cards),
)
)
return deal
return card
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)
async def _get_cards_by_group_id(self, group_id: int) -> List[Card]:
group: CardGroup | None = await self.session.scalar(
select(CardGroup)
.where(CardGroup.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),
selectinload(CardGroup.cards).selectinload(Card.products).selectinload(CardProduct.services),
selectinload(CardGroup.cards).selectinload(Card.services).selectinload(CardServiceModel.service),
selectinload(CardGroup.cards).joinedload(Card.shipping_warehouse),
selectinload(CardGroup.cards).joinedload(Card.client),
selectinload(CardGroup.cards).selectinload(Card.group).selectinload(CardGroup.cards),
)
)
return group.deals if group else []
return group.cards 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:
async def _create_billing_document_html(self, card_id: int):
card = await self._get_card_by_id(card_id)
if not card:
return ""
if deal.group:
deals = await self._get_deals_by_group_id(deal.group.id)
if card.group:
cards = await self._get_cards_by_group_id(card.group.id)
else:
deals = [deal]
cards = [card]
(services, products, is_size_needed) = await self._get_products_for_deal(deals)
(services, products, is_size_needed) = await self._get_products_for_card(cards)
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)
price = sum((service.price * service.quantity for service in services.values()))
price_words = get_string_by_number(price)[0:-10]
price = to_locale_number(price)
template = ENV.get_template("bill-of-payment.html")
now = datetime.datetime.now()
@@ -334,14 +333,14 @@ class BillingService(BaseService):
"products": products,
"services": services,
"is_size_needed": is_size_needed,
"deal_price": deal_price,
"deal_price_words": deal_price_words,
"deal": deal,
"deal_price": price,
"deal_price_words": price_words,
"deal": card,
"curr_date": curr_date
})
async def create_billing_document_pdf(self, deal_id) -> BytesIO:
doc = await self._create_billing_document_html(deal_id)
async def create_billing_document_pdf(self, card_id) -> BytesIO:
doc = await self._create_billing_document_html(card_id)
pdf_file = BytesIO()
HTML(string=doc).write_pdf(pdf_file, stylesheets=[CSS(constants.APP_PATH + '/static/css/bill-of-payment.css')])