feat: end-point for bill of payment generation

This commit is contained in:
2024-09-11 19:08:06 +04:00
parent 49037125c2
commit 50a315ca18
7 changed files with 260 additions and 51 deletions

View File

@@ -5,6 +5,7 @@ uvicorn
uvicorn[standard]
gunicorn
python-multipart
jinja2
# Security
python-jose[cryptography]
@@ -25,4 +26,6 @@ lexorank-py
celery[redis]
celery
# PDF
reportlab
reportlab
weasyprint
number_to_string

View File

@@ -1,6 +1,7 @@
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
from backend.dependecies import SessionDependency, CurrentUserDependency
@@ -8,6 +9,7 @@ from backend.session import get_session
from models import User
from schemas.deal import *
from services.auth import get_current_user, authorized_user, guest_user
from services.billing import BillingService
from services.deal import DealService
deal_router = APIRouter(
@@ -162,6 +164,18 @@ async def create_guest_url(
return DealService(session).create_guest_url(user, request)
@deal_router.get(
'/document/{deal_id}',
operation_id='get_deal_document',
# dependencies=[Depends(authorized_user)],
)
async def get_deal_document(
deal_id: int,
session: Annotated[AsyncSession, Depends(get_session)],
):
pdf_file: BytesIO = await BillingService(session).create_billing_document_pdf(deal_id)
return Response(pdf_file.getvalue(), media_type='application/pdf')
# endregion
# region Deal services

View File

@@ -3,7 +3,7 @@ from typing import Optional
from schemas.base import BaseSchema, OkMessageSchema
# region Entities
class DealBillRequestSchema(BaseSchema):
deal_id: int
created_at: datetime.datetime
@@ -11,7 +11,9 @@ class DealBillRequestSchema(BaseSchema):
pdf_url: Optional[str]
invoice_number: Optional[str]
# endregion
# region Requests
class CreateDealBillRequest(BaseSchema):
deal_id: int
@@ -19,7 +21,9 @@ class CreateDealBillRequest(BaseSchema):
class CancelDealBillRequest(BaseSchema):
deal_id: int
# endregion
# region Responses
class CreateDealBillResponse(OkMessageSchema):
pass
@@ -30,3 +34,5 @@ class CancelDealBillResponse(OkMessageSchema):
class GetDealBillById(BaseSchema):
deal_bill: DealBillRequestSchema
# endregion

View File

@@ -1,19 +1,30 @@
import datetime
import locale
from ast import Bytes
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 external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, BillPaymentInfo, \
DeleteBillRequestSchema
from models import DealBillRequest, Deal
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
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
env = Environment(loader=FileSystemLoader(Path("templates") / Path("documents")))
class BillingService(BaseService):
async def _process_update_details(
@@ -132,3 +143,60 @@ class BillingService(BaseService):
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 for service in services))
deal_price_words = get_string_by_number(deal_price)[0:-10]
template = env.get_template("bill-of-payment.html")
return template.render({
"services": services,
"deal_price": deal_price,
"deal_price_words": deal_price_words,
"deal": deal,
"curr_date": datetime.datetime.now().strftime("%d %B %Y г.")
})
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

BIN
static/icons/denco.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

61
test.py
View File

@@ -1,54 +1,25 @@
# import asyncio
#
# from dict_hash import dict_hash
# from sqlalchemy.ext.asyncio import AsyncSession
#
# from backend.session import session_maker
# from marketplaces import MarketplaceControllerFactory
# from models import Marketplace
# import pickle
# pickle.dumps()
#
# async def main():
# a = "example"
# b = "example"
#
# print(hash(a)) # Хэш для строки "example"
# print(hash(b)) # Хэш для строки "example", будет таким же как и у a
#
# return
# session: AsyncSession = session_maker()
#
# try:
# mp = await session.get(Marketplace, 2)
# if not mp:
# return
# c = MarketplaceControllerFactory.get_controller(session, mp)
# await c.synchronize_products()
# finally:
# await session.close()
#
#
# if __name__ == '__main__':
# loop = asyncio.get_event_loop()
# loop.run_until_complete(main())
import hashlib
import time
import asyncio
from reportlab.rl_settings import autoGenerateMissingTTFName
from sqlalchemy.ext.asyncio import AsyncSession
from decorators.locking import lock, redis_client
from backend.session import session_maker
from services.billing import BillingService
@lock('synchronize_marketplace', include_args_in_key=True)
def test(marketplace_id: int):
print("test")
time.sleep(100)
async def main():
session: AsyncSession = session_maker()
try:
service = BillingService(session)
def main():
test(1)
pdf_file = await service.create_billing_document_pdf(121)
with open("report.pdf", "wb") as f:
f.write(pdf_file.getvalue())
finally:
await session.close()
if __name__ == '__main__':
main()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())