feat: end-point for bill of payment generation
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
BIN
static/icons/denco.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
147
templates/documents/bill-of-payment.html
Normal file
147
templates/documents/bill-of-payment.html
Normal file
File diff suppressed because one or more lines are too long
61
test.py
61
test.py
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user