Compare commits

...

2 Commits

Author SHA1 Message Date
9a66667317 Merge remote-tracking branch 'origin/master' 2024-09-11 19:08:42 +04:00
50a315ca18 feat: end-point for bill of payment generation 2024-09-11 19:08:06 +04:00
7 changed files with 260 additions and 51 deletions

View File

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

View File

@@ -1,6 +1,7 @@
from io import BytesIO
from typing import Annotated from typing import Annotated
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, Response
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from backend.dependecies import SessionDependency, CurrentUserDependency from backend.dependecies import SessionDependency, CurrentUserDependency
@@ -8,6 +9,7 @@ from backend.session import get_session
from models import User from models import User
from schemas.deal import * from schemas.deal import *
from services.auth import get_current_user, authorized_user, guest_user from services.auth import get_current_user, authorized_user, guest_user
from services.billing import BillingService
from services.deal import DealService from services.deal import DealService
deal_router = APIRouter( deal_router = APIRouter(
@@ -175,6 +177,18 @@ async def create_guest_url(
return DealService(session).create_guest_url(user, request) 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 # endregion
# region Deal services # region Deal services

View File

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

View File

@@ -1,19 +1,30 @@
import datetime import datetime
import locale
from ast import Bytes
from io import BytesIO
from pathlib import Path
from typing import List from typing import List
from fastapi import HTTPException from fastapi import HTTPException
from jinja2 import Environment, FileSystemLoader
from number_to_string import get_string_by_number
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload
from starlette import status from starlette import status
from weasyprint import HTML
import backend.config import backend.config
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \ from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, BillPaymentInfo, \ BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema
DeleteBillRequestSchema from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel
from models import DealBillRequest, Deal
from schemas.billing import * from schemas.billing import *
from services.base import BaseService from services.base import BaseService
from services.deal import DealService 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): class BillingService(BaseService):
async def _process_update_details( async def _process_update_details(
@@ -132,3 +143,60 @@ class BillingService(BaseService):
return CancelDealBillResponse(ok=True, message='Заявка успешно отозвана') return CancelDealBillResponse(ok=True, message='Заявка успешно отозвана')
except Exception as e: except Exception as e:
return CancelDealBillResponse(ok=False, message=str(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 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
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) async def main():
def test(marketplace_id: int): session: AsyncSession = session_maker()
print("test")
time.sleep(100)
try:
service = BillingService(session)
def main(): pdf_file = await service.create_billing_document_pdf(121)
test(1)
with open("report.pdf", "wb") as f:
f.write(pdf_file.getvalue())
finally:
await session.close()
if __name__ == '__main__': if __name__ == '__main__':
main() loop = asyncio.get_event_loop()
loop.run_until_complete(main())