201 lines
8.5 KiB
Python
201 lines
8.5 KiB
Python
from io import BytesIO
|
||
from pathlib import Path
|
||
from typing import List
|
||
|
||
from fastapi import HTTPException
|
||
from jinja2 import Environment, FileSystemLoader
|
||
from number_to_string import get_string_by_number
|
||
from sqlalchemy import select
|
||
from sqlalchemy.orm import selectinload
|
||
from starlette import status
|
||
from weasyprint import HTML
|
||
|
||
import backend.config
|
||
from constants import MONTHS, ENV
|
||
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
|
||
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema
|
||
from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel
|
||
from schemas.billing import *
|
||
from services.base import BaseService
|
||
from services.deal import DealService
|
||
from utils.list_utils import to_locale_number
|
||
|
||
|
||
class BillingService(BaseService):
|
||
async def _process_update_details(
|
||
self,
|
||
request: BillStatusUpdateRequest
|
||
):
|
||
billing_client = BillingClient(backend.config.BILLING_API_KEY)
|
||
notify_received_request = NotifyReceivedBillRequestSchema(
|
||
listener_transaction_id=request.listener_transaction_id,
|
||
channel=NotificationChannel.PAYMENT_DETAILS,
|
||
received=True
|
||
)
|
||
response = await billing_client.notify_received(notify_received_request)
|
||
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id)
|
||
if not response.ok:
|
||
return
|
||
deal_bill_request.pdf_url = request.info.pdf_url
|
||
deal_bill_request.invoice_number = request.info.invoice_number
|
||
await self.session.commit()
|
||
|
||
async def _process_update_verification(
|
||
self,
|
||
request: BillStatusUpdateRequest
|
||
):
|
||
billing_client = BillingClient(backend.config.BILLING_API_KEY)
|
||
notify_received_request = NotifyReceivedBillRequestSchema(
|
||
listener_transaction_id=request.listener_transaction_id,
|
||
channel=NotificationChannel.PAYMENT_VERIFICATION,
|
||
received=True
|
||
)
|
||
response = await billing_client.notify_received(notify_received_request)
|
||
if not response.ok:
|
||
return
|
||
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id)
|
||
if not deal_bill_request:
|
||
return
|
||
deal_bill_request.paid = request.info.payed
|
||
await self.session.commit()
|
||
|
||
async def process_update(
|
||
self,
|
||
request: BillStatusUpdateRequest
|
||
):
|
||
if request.channel == NotificationChannel.PAYMENT_DETAILS:
|
||
await self._process_update_details(request)
|
||
elif request.channel == NotificationChannel.PAYMENT_VERIFICATION:
|
||
await self._process_update_verification(request)
|
||
|
||
async def create_deal_billing(self, user, request: CreateDealBillRequest) -> CreateDealBillResponse:
|
||
try:
|
||
deal_service = DealService(self.session)
|
||
billing_client = BillingClient(backend.config.BILLING_API_KEY)
|
||
|
||
deal: Deal = await deal_service.get_by_id(user, request.deal_id)
|
||
billing_request_values: List[CreateBillingRequestValue] = []
|
||
for product in deal.products:
|
||
for service in product.services:
|
||
billing_request_values.append(
|
||
CreateBillingRequestValue(
|
||
name=f'[{product.product.name}] - {service.service.name}',
|
||
price=service.price,
|
||
amount=product.quantity
|
||
)
|
||
)
|
||
for service in deal.services:
|
||
billing_request_values.append(
|
||
CreateBillingRequestValue(
|
||
name=f'{service.service.name}',
|
||
price=service.price,
|
||
amount=service.quantity
|
||
)
|
||
)
|
||
create_bill_request = CreateBillRequestSchema(
|
||
listener_transaction_id=deal.id,
|
||
payer_name=deal.client.name,
|
||
payer_inn=deal.client.details.inn,
|
||
payer_phone=deal.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='Ошибка!')
|
||
deal_bill_request = DealBillRequest(
|
||
deal_id=request.deal_id,
|
||
created_at=datetime.datetime.now()
|
||
)
|
||
self.session.add(deal_bill_request)
|
||
deal.is_locked = True
|
||
await self.session.commit()
|
||
return CreateDealBillResponse(ok=create_bill_response.ok, message='Заявка успешно создана!')
|
||
except Exception as e:
|
||
return CreateDealBillResponse(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_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 cancel_deal_billing(self, user, request: CancelDealBillRequest) -> CancelDealBillResponse:
|
||
try:
|
||
deal_bill = await self._get_deal_bill_by_id(request.deal_id)
|
||
if not deal_bill:
|
||
return CancelDealBillResponse(ok=False, message='Заявка не найдена')
|
||
billing_client = BillingClient(backend.config.BILLING_API_KEY)
|
||
response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=request.deal_id))
|
||
if not response.ok:
|
||
return CancelDealBillResponse(ok=False, message='Ошибка')
|
||
await self.session.delete(deal_bill)
|
||
await self.session.commit()
|
||
return CancelDealBillResponse(ok=True, message='Заявка успешно отозвана')
|
||
except Exception as e:
|
||
return CancelDealBillResponse(ok=False, message=str(e))
|
||
|
||
async def _get_services_for_deal(self, deal: Deal) -> List[CreateBillingRequestValue]:
|
||
services: List[CreateBillingRequestValue] = []
|
||
|
||
for product in deal.products:
|
||
for service in product.services:
|
||
services.append(
|
||
CreateBillingRequestValue(
|
||
name=f'[{product.product.name}] - {service.service.name}',
|
||
price=service.price,
|
||
amount=product.quantity
|
||
)
|
||
)
|
||
for service in deal.services:
|
||
services.append(
|
||
CreateBillingRequestValue(
|
||
name=f'{service.service.name}',
|
||
price=service.price,
|
||
amount=service.quantity
|
||
)
|
||
)
|
||
|
||
return services
|
||
|
||
async def _create_billing_document_html(self, deal_id: int):
|
||
deal: Deal | None = await self.session.scalar(
|
||
select(Deal)
|
||
.where(Deal.id == deal_id)
|
||
.options(
|
||
selectinload(Deal.products).selectinload(DealProduct.services),
|
||
selectinload(Deal.services).selectinload(DealServiceModel.service),
|
||
)
|
||
)
|
||
|
||
if not deal:
|
||
return ""
|
||
|
||
services = await self._get_services_for_deal(deal)
|
||
|
||
deal_price = sum((service.price * service.amount for service in services))
|
||
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")
|
||
|
||
now = datetime.datetime.now()
|
||
curr_date = f"{now.day} {MONTHS[now.month - 1]} {now.year} г."
|
||
return template.render({
|
||
"services": services,
|
||
"deal_price": deal_price,
|
||
"deal_price_words": deal_price_words,
|
||
"deal": deal,
|
||
"curr_date": curr_date
|
||
})
|
||
|
||
async def create_billing_document_pdf(self, deal_id) -> BytesIO:
|
||
doc = await self._create_billing_document_html(deal_id)
|
||
pdf_file = BytesIO()
|
||
HTML(string=doc).write_pdf(pdf_file)
|
||
|
||
return pdf_file
|