feat: billing guest access

This commit is contained in:
2024-08-08 07:49:53 +03:00
parent a7c4fabed0
commit 97f835ffde
30 changed files with 682 additions and 140 deletions

View File

@@ -16,3 +16,5 @@ TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN')
SECRET_KEY = os.environ.get('SECRET_KEY') SECRET_KEY = os.environ.get('SECRET_KEY')
S3_API_KEY = os.environ.get('S3_API_KEY') S3_API_KEY = os.environ.get('S3_API_KEY')
BILLING_API_KEY = os.environ.get('BILLING_API_KEY')

View File

@@ -6,9 +6,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.session import get_session from backend.session import get_session
from models import User from models import User
from schemas.base import PaginationSchema from schemas.base import PaginationSchema
from services.auth import get_current_user from services.auth import get_current_user, authorized_user, guest_user
from utils.dependecies import pagination_parameters from utils.dependecies import pagination_parameters
SessionDependency = Annotated[AsyncSession, Depends(get_session)] SessionDependency = Annotated[AsyncSession, Depends(get_session)]
PaginationDependency = Annotated[PaginationSchema, Depends(pagination_parameters)] PaginationDependency = Annotated[PaginationSchema, Depends(pagination_parameters)]
CurrentUserDependency = Annotated[User, Depends(get_current_user)] CurrentUserDependency = Annotated[User, Depends(get_current_user)]
AuthorizedUserDependency = Annotated[User, Depends(authorized_user)]
GuestUserDependency = Annotated[User, Depends(guest_user)]

3
external/billing/__init__.py vendored Normal file
View File

@@ -0,0 +1,3 @@
from .schemas import *
from .enums import *
from .billing_client import BillingClient

27
external/billing/billing_client.py vendored Normal file
View File

@@ -0,0 +1,27 @@
import aiohttp
from .schemas import *
class BillingClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.headers = {
'Authorization': f'Bearer {self.api_key}'
}
self.base_url = 'https://billing.denco.store'
async def _method(self, http_method, method, **kwargs):
async with aiohttp.ClientSession(headers=self.headers) as session:
async with session.request(http_method, self.base_url + method, **kwargs) as response:
return await response.json()
async def create(self, request: CreateBillRequestSchema) -> CreateBillingResponseSchema:
json_data = request.model_dump()
response = await self._method('POST', '/create', json=json_data)
return CreateBillingResponseSchema.model_validate(response)
async def notify_received(self, request: NotifyReceivedBillRequestSchema) -> NotifyReceivedBillResponseSchema:
json_data = request.model_dump()
response = await self._method('POST', '/notify-received', json=json_data)
return NotifyReceivedBillResponseSchema.model_validate(response)

6
external/billing/enums.py vendored Normal file
View File

@@ -0,0 +1,6 @@
from enum import StrEnum
class NotificationChannel(StrEnum):
PAYMENT_DETAILS = 'PAYMENT_DETAILS'
PAYMENT_VERIFICATION = 'PAYMENT_VERIFICATION'

81
external/billing/schemas.py vendored Normal file
View File

@@ -0,0 +1,81 @@
import re
from typing import List
from pydantic import field_validator
from schemas.base import BaseSchema
from .enums import NotificationChannel
from datetime import datetime as dt
from datetime import date
phone_regexp = re.compile(r'^((\+7)([0-9]){10})$')
class CreateBillingRequestValue(BaseSchema):
name: str = ""
unit: str = "Руб"
vat: str = "None"
price: int
amount: int = 1
class CreateBillRequestItems(BaseSchema):
values: List[CreateBillingRequestValue]
class CreateBillRequestSchema(BaseSchema):
listener_transaction_id: int
payer_name: str
payer_inn: int
payer_phone: str | None
items: CreateBillRequestItems
@field_validator("payer_phone", mode="before")
def payer_phone_validator(cls, phone: str) -> str:
if phone is None:
return None
if not phone.startswith("+"):
phone = f"+{phone}"
if phone_regexp.match(phone):
return phone
return None
class NotifyReceivedBillRequestSchema(BaseSchema):
listener_transaction_id: int
channel: NotificationChannel
received: bool
class CreateBillingResponseSchema(BaseSchema):
ok: bool
class NotifyReceivedBillResponseSchema(BaseSchema):
ok: bool
class BillPaymentInfo(BaseSchema):
pdf_url: str
due_date: date
payment_amount: int
invoice_number: str
@field_validator('due_date', mode='plain')
def serialize_due_date(value: str) -> date:
date = dt.strptime(value, '%Y-%m-%d').date()
return date
class BillPaymentStatus(BaseSchema):
payed: bool
class BillStatusUpdateRequest(BaseSchema):
listener_transaction_id: int
channel: NotificationChannel
info: BillPaymentInfo | BillPaymentStatus

View File

@@ -42,6 +42,7 @@ routers_list = [
routers.marketplace_router, routers.marketplace_router,
routers.payroll_router, routers.payroll_router,
routers.time_tracking_router, routers.time_tracking_router,
routers.billing_router
] ]
for router in routers_list: for router in routers_list:
app.include_router(router) app.include_router(router)

View File

@@ -10,5 +10,6 @@ from .barcode import *
from .shipping_warehouse import * from .shipping_warehouse import *
from .marketplace import * from .marketplace import *
from .payroll import * from .payroll import *
from .billing import *
configure_mappers() configure_mappers()

25
models/billing.py Normal file
View File

@@ -0,0 +1,25 @@
import datetime
from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from models import BaseModel
if TYPE_CHECKING:
from models import Deal
class DealBillRequest(BaseModel):
__tablename__ = 'deal_bill_requests'
deal_id: Mapped[int] = mapped_column(ForeignKey('deals.id'),
nullable=False,
primary_key=True,
unique=True)
deal: Mapped['Deal'] = relationship(back_populates='bill_request')
created_at: Mapped[datetime.datetime] = mapped_column(nullable=False)
paid: Mapped[bool] = mapped_column(nullable=False, default=False)
pdf_url: Mapped[str] = mapped_column(nullable=True)
invoice_number: Mapped[str] = mapped_column(nullable=True)

View File

@@ -1,4 +1,5 @@
from enum import IntEnum, unique from enum import IntEnum, unique
from typing import Optional, TYPE_CHECKING
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean
from sqlalchemy.orm import relationship, backref, Mapped, mapped_column from sqlalchemy.orm import relationship, backref, Mapped, mapped_column
@@ -7,6 +8,9 @@ from models.base import BaseModel
from .marketplace import BaseMarketplace from .marketplace import BaseMarketplace
from .shipping_warehouse import ShippingWarehouse from .shipping_warehouse import ShippingWarehouse
if TYPE_CHECKING:
from . import DealBillRequest
@unique @unique
class DealStatus(IntEnum): class DealStatus(IntEnum):
@@ -33,6 +37,7 @@ class Deal(BaseModel):
is_deleted = Column(Boolean, nullable=False, server_default='0', default=False, comment='Удалена') is_deleted = Column(Boolean, nullable=False, server_default='0', default=False, comment='Удалена')
is_completed = Column(Boolean, nullable=False, server_default='0', default=False, comment='Завершена') is_completed = Column(Boolean, nullable=False, server_default='0', default=False, comment='Завершена')
is_locked: Mapped[bool] = mapped_column(default=False, server_default='0')
shipping_warehouse_id: Mapped[int] = mapped_column(ForeignKey('shipping_warehouses.id'), nullable=True) shipping_warehouse_id: Mapped[int] = mapped_column(ForeignKey('shipping_warehouses.id'), nullable=True)
shipping_warehouse: Mapped["ShippingWarehouse"] = relationship() shipping_warehouse: Mapped["ShippingWarehouse"] = relationship()
@@ -58,6 +63,7 @@ class Deal(BaseModel):
lexorank = Column(String, nullable=False, comment='Lexorank', index=True) lexorank = Column(String, nullable=False, comment='Lexorank', index=True)
comment = Column(String, nullable=False, server_default='', comment='Коментарий к заданию') comment = Column(String, nullable=False, server_default='', comment='Коментарий к заданию')
bill_request: Mapped[Optional['DealBillRequest']] = relationship(back_populates='deal', lazy='joined')
class DealStatusHistory(BaseModel): class DealStatusHistory(BaseModel):

View File

@@ -11,3 +11,4 @@ from .role import role_router
from .marketplace import marketplace_router from .marketplace import marketplace_router
from .payroll import payroll_router from .payroll import payroll_router
from .time_tracking import time_tracking_router from .time_tracking import time_tracking_router
from .billing import billing_router

View File

@@ -4,19 +4,14 @@ from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from backend.session import get_session from backend.session import get_session
from schemas.barcode import (GetBarcodeTemplateByIdResponse, from schemas.barcode import *
GetBarcodeTemplateByIdRequest, from services.auth import authorized_user, guest_user
BarcodeTemplateCreateResponse,
BarcodeTemplateCreateRequest, GetAllBarcodeTemplateAttributesResponse,
CreateBarcodeTemplateAttributeResponse, CreateBarcodeTemplateAttributeRequest,
BarcodeTemplateUpdateResponse, BarcodeTemplateUpdateRequest,
GetAllBarcodeTemplatesResponse, BarcodeTemplateDeleteRequest,
BarcodeTemplateDeleteResponse, GetAllBarcodeTemplateSizesResponse)
from services.barcode import BarcodeService from services.barcode import BarcodeService
barcode_router = APIRouter( barcode_router = APIRouter(
prefix='/barcode', prefix='/barcode',
tags=['barcode'], tags=['barcode'],
dependencies=[Depends(guest_user)]
) )

51
routers/billing.py Normal file
View File

@@ -0,0 +1,51 @@
from fastapi import APIRouter
from backend.dependecies import SessionDependency, CurrentUserDependency
from external.billing import BillStatusUpdateRequest
from schemas.billing import *
from services.billing import BillingService
billing_router = APIRouter(
prefix='/billing',
tags=['billing']
)
@billing_router.post(
'/webhook'
)
async def webhook(
request: BillStatusUpdateRequest,
session: SessionDependency
):
try:
await BillingService(session).process_update(request)
return {'ok': True}
except Exception as e:
print(e)
return {'ok': False}
@billing_router.post(
'/create-deal-bill',
operation_id='create_deal_bill',
response_model=CreateDealBillResponse
)
async def create_deal_bill(
session: SessionDependency,
request: CreateDealBillRequest,
user: CurrentUserDependency
):
return await BillingService(session).create_deal_billing(user, request)
@billing_router.get(
'/deal-bill-request/{deal_id}',
response_model=GetDealBillById,
operation_id='get_deal_bill_by_id'
)
async def get_deal_bill_by_id(
deal_id: int,
session: SessionDependency
):
return await BillingService(session).get_deal_bill_by_id(deal_id)

View File

@@ -6,13 +6,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.session import get_session from backend.session import get_session
from models import User from models import User
from schemas.client import * from schemas.client import *
from services.auth import get_current_user from services.auth import get_current_user, authorized_user
from services.client import ClientService from services.client import ClientService
client_router = APIRouter( client_router = APIRouter(
prefix="/client", prefix="/client",
tags=['client'], tags=['client'],
dependencies=[Depends(get_current_user)] dependencies=[Depends(authorized_user)]
) )

View File

@@ -3,22 +3,24 @@ from typing import Annotated
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from backend.dependecies import SessionDependency from backend.dependecies import SessionDependency, CurrentUserDependency
from backend.session import get_session 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 from services.auth import get_current_user, authorized_user, guest_user
from services.deal import DealService from services.deal import DealService
deal_router = APIRouter( deal_router = APIRouter(
prefix='/deal', prefix='/deal',
tags=['deal'], tags=['deal'],
dependencies=[Depends(get_current_user)]
) )
# region Deal # region Deal
@deal_router.post('/create') @deal_router.post(
'/create',
dependencies=[Depends(authorized_user)]
)
async def create( async def create(
request: DealCreateRequest, request: DealCreateRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
@@ -30,7 +32,8 @@ async def create(
@deal_router.post( @deal_router.post(
'/delete', '/delete',
response_model=DealDeleteResponse, response_model=DealDeleteResponse,
operation_id='deleteDeal' operation_id='deleteDeal',
dependencies=[Depends(authorized_user)]
) )
async def delete( async def delete(
request: DealDeleteRequest, request: DealDeleteRequest,
@@ -39,7 +42,11 @@ async def delete(
return await DealService(session).delete(request) return await DealService(session).delete(request)
@deal_router.post('/quickCreate', response_model=DealQuickCreateResponse) @deal_router.post(
'/quickCreate',
response_model=DealQuickCreateResponse,
dependencies=[Depends(authorized_user)]
)
async def quick_create( async def quick_create(
request: DealQuickCreateRequest, request: DealQuickCreateRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
@@ -48,7 +55,11 @@ async def quick_create(
return await DealService(session).quick_create(request, user) return await DealService(session).quick_create(request, user)
@deal_router.post('/changeStatus', response_model=DealChangeStatusResponse) @deal_router.post(
'/changeStatus',
response_model=DealChangeStatusResponse,
dependencies=[Depends(authorized_user)]
)
async def change_status( async def change_status(
request: DealChangeStatusRequest, request: DealChangeStatusRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
@@ -57,10 +68,12 @@ async def change_status(
return await DealService(session).change_status_manual(request, user) return await DealService(session).change_status_manual(request, user)
@deal_router.get('/summaries', @deal_router.get(
response_model=DealSummaryResponse, '/summaries',
operation_id='getDealSummaries' response_model=DealSummaryResponse,
) operation_id='getDealSummaries',
dependencies=[Depends(authorized_user)]
)
async def get_summary( async def get_summary(
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
): ):
@@ -70,7 +83,8 @@ async def get_summary(
@deal_router.post( @deal_router.post(
'/summaries/reorder', '/summaries/reorder',
response_model=DealSummaryResponse, response_model=DealSummaryResponse,
operation_id='reorderDealSummaries' operation_id='reorderDealSummaries',
dependencies=[Depends(authorized_user)]
) )
async def reorder( async def reorder(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
@@ -83,7 +97,8 @@ async def reorder(
@deal_router.get( @deal_router.get(
'/get-all', '/get-all',
response_model=DealGetAllResponse, response_model=DealGetAllResponse,
operation_id='getAllDeals' operation_id='getAllDeals',
dependencies=[Depends(authorized_user)]
) )
async def get_all( async def get_all(
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
@@ -95,19 +110,22 @@ async def get_all(
@deal_router.get( @deal_router.get(
'/get/{deal_id}', '/get/{deal_id}',
response_model=DealSchema, response_model=DealSchema,
operation_id='getDealById' operation_id='getDealById',
dependencies=[Depends(guest_user)]
) )
async def get_deal_by_id( async def get_deal_by_id(
deal_id: int, deal_id: int,
user: CurrentUserDependency,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
): ):
return await DealService(session).get_by_id(deal_id) return await DealService(session).get_by_id(user, deal_id)
@deal_router.post( @deal_router.post(
'/update-general-info', '/update-general-info',
response_model=DealUpdateGeneralInfoResponse, response_model=DealUpdateGeneralInfoResponse,
operation_id='updateDealGeneralInfo' operation_id='updateDealGeneralInfo',
dependencies=[Depends(authorized_user)]
) )
async def update_general_info( async def update_general_info(
request: DealUpdateGeneralInfoRequest, request: DealUpdateGeneralInfoRequest,
@@ -115,16 +133,34 @@ async def update_general_info(
): ):
return await DealService(session).update_general_info(request) return await DealService(session).update_general_info(request)
@deal_router.post( @deal_router.post(
'/add-kit', '/add-kit',
response_model=DealAddKitResponse, response_model=DealAddKitResponse,
operation_id='add_kit_to_deal' operation_id='add_kit_to_deal'
) )
async def add_kit_to_deal( async def add_kit_to_deal(
session:SessionDependency, session: SessionDependency,
request:DealAddKitRequest request: DealAddKitRequest
): ):
return await DealService(session).add_kit_to_deal(request) return await DealService(session).add_kit_to_deal(request)
@deal_router.post(
'/create-guest-url',
response_model=DealCreateGuestUrlResponse,
operation_id='create_deal_guest_url',
dependencies=[Depends(authorized_user)]
)
async def create_guest_url(
session: SessionDependency,
request: DealCreateGuestUrlRequest,
user: CurrentUserDependency
):
return DealService(session).create_guest_url(user, request)
# endregion # endregion
# region Deal services # region Deal services
@@ -132,11 +168,12 @@ async def add_kit_to_deal(
@deal_router.post( @deal_router.post(
'/services/add/multiple', '/services/add/multiple',
response_model=DealAddServicesResponse, response_model=DealAddServicesResponse,
operation_id='addMultipleDealServices' operation_id='addMultipleDealServices',
dependencies=[Depends(guest_user)]
) )
async def services_add( async def services_add(
request: DealAddServicesRequest, request: DealAddServicesRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)],
): ):
return await DealService(session).add_services(request) return await DealService(session).add_services(request)
@@ -144,73 +181,86 @@ async def services_add(
@deal_router.post( @deal_router.post(
'/services/add', '/services/add',
response_model=DealAddServiceResponse, response_model=DealAddServiceResponse,
operation_id='addDealService' operation_id='addDealService',
dependencies=[Depends(guest_user)]
) )
async def services_add( async def services_add(
request: DealAddServiceRequest, request: DealAddServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency
): ):
return await DealService(session).add_service(request) return await DealService(session).add_service(user, request)
@deal_router.post( @deal_router.post(
'/services/update-quantity', '/services/update-quantity',
response_model=DealUpdateServiceQuantityResponse, response_model=DealUpdateServiceQuantityResponse,
operation_id='updateDealServiceQuantity' operation_id='updateDealServiceQuantity',
dependencies=[Depends(guest_user)]
) )
async def services_update_quantity( async def services_update_quantity(
request: DealUpdateServiceQuantityRequest, request: DealUpdateServiceQuantityRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency
): ):
return await DealService(session).update_service_quantity(request) return await DealService(session).update_service_quantity(user, request)
@deal_router.post( @deal_router.post(
'/services/update', '/services/update',
response_model=DealUpdateServiceResponse, response_model=DealUpdateServiceResponse,
operation_id='updateDealService' operation_id='updateDealService',
dependencies=[Depends(guest_user)]
) )
async def services_update( async def services_update(
request: DealUpdateServiceRequest, request: DealUpdateServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency
): ):
return await DealService(session).update_service(request) return await DealService(session).update_service(user, request)
@deal_router.post( @deal_router.post(
'/services/delete', '/services/delete',
response_model=DealDeleteServiceResponse, response_model=DealDeleteServiceResponse,
operation_id='deleteDealService' operation_id='deleteDealService',
dependencies=[Depends(guest_user)]
) )
async def services_delete( async def services_delete(
request: DealDeleteServiceRequest, request: DealDeleteServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency
): ):
return await DealService(session).delete_service(request) return await DealService(session).delete_service(user, request)
@deal_router.post( @deal_router.post(
'/services/delete/multiple', '/services/delete/multiple',
response_model=DealDeleteServicesResponse, response_model=DealDeleteServicesResponse,
operation_id='deleteMultipleDealServices' operation_id='deleteMultipleDealServices',
dependencies=[Depends(guest_user)]
) )
async def services_delete( async def services_delete(
request: DealDeleteServicesRequest, request: DealDeleteServicesRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency
): ):
return await DealService(session).delete_services(request) return await DealService(session).delete_services(user, request)
@deal_router.post( @deal_router.post(
'/services/copy', '/services/copy',
response_model=DealServicesCopyResponse, response_model=DealServicesCopyResponse,
operation_id='copy_product_services' operation_id='copy_product_services',
dependencies=[Depends(guest_user)]
) )
async def services_copy( async def services_copy(
session: SessionDependency, session: SessionDependency,
request: DealServicesCopyRequest request: DealServicesCopyRequest,
user: CurrentUserDependency
): ):
return await DealService(session).copy_services(request) return await DealService(session).copy_services(user, request)
# endregion # endregion
@@ -219,63 +269,84 @@ async def services_copy(
@deal_router.post( @deal_router.post(
'/products/update-quantity', '/products/update-quantity',
response_model=DealUpdateProductQuantityResponse, response_model=DealUpdateProductQuantityResponse,
operation_id='updateDealProductQuantity') operation_id='updateDealProductQuantity',
dependencies=[Depends(guest_user)]
)
async def products_update( async def products_update(
request: DealUpdateProductQuantityRequest, request: DealUpdateProductQuantityRequest,
session: Annotated[AsyncSession, Depends(get_session)]): session: Annotated[AsyncSession, Depends(get_session)],
return await DealService(session).update_product_quantity(request) user: CurrentUserDependency
):
return await DealService(session).update_product_quantity(user, request)
@deal_router.post( @deal_router.post(
'/products/add', '/products/add',
response_model=DealAddProductResponse, response_model=DealAddProductResponse,
operation_id='addDealProduct') operation_id='addDealProduct',
dependencies=[Depends(guest_user)]
)
async def products_add( async def products_add(
request: DealAddProductRequest, request: DealAddProductRequest,
session: Annotated[AsyncSession, Depends(get_session)]): session: Annotated[AsyncSession, Depends(get_session)],
return await DealService(session).add_product(request) user: CurrentUserDependency
):
return await DealService(session).add_product(user, request)
@deal_router.post( @deal_router.post(
'/products/delete', '/products/delete',
response_model=DealDeleteProductResponse, response_model=DealDeleteProductResponse,
operation_id='deleteDealProduct') operation_id='deleteDealProduct',
dependencies=[Depends(guest_user)]
)
async def products_delete( async def products_delete(
request: DealDeleteProductRequest, request: DealDeleteProductRequest,
session: Annotated[AsyncSession, Depends(get_session)]): session: Annotated[AsyncSession, Depends(get_session)],
return await DealService(session).delete_product(request) user: CurrentUserDependency
):
return await DealService(session).delete_product(user, request)
@deal_router.post( @deal_router.post(
'/products/delete/multiple', '/products/delete/multiple',
response_model=DealDeleteProductsResponse, response_model=DealDeleteProductsResponse,
operation_id='deleteMultipleDealProducts') operation_id='deleteMultipleDealProducts',
dependencies=[Depends(guest_user)]
)
async def products_delete( async def products_delete(
request: DealDeleteProductsRequest, request: DealDeleteProductsRequest,
session: Annotated[AsyncSession, Depends(get_session)]): session: Annotated[AsyncSession, Depends(get_session)],
return await DealService(session).delete_products(request) user: CurrentUserDependency
):
return await DealService(session).delete_products(user, request)
@deal_router.post( @deal_router.post(
'/product/update', '/product/update',
response_model=DealUpdateProductResponse, response_model=DealUpdateProductResponse,
operation_id='updateDealProduct' operation_id='updateDealProduct',
dependencies=[Depends(guest_user)]
) )
async def products_update( async def products_update(
request: DealUpdateProductRequest, request: DealUpdateProductRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency
): ):
return await DealService(session).update_product(request) return await DealService(session).update_product(user, request)
@deal_router.post( @deal_router.post(
'/product/add-kit', '/product/add-kit',
response_model=DealProductAddKitResponse, response_model=DealProductAddKitResponse,
operation_id='add_kit_to_deal_product' operation_id='add_kit_to_deal_product',
dependencies=[Depends(guest_user)]
) )
async def add_kit_to_deal_product( async def add_kit_to_deal_product(
session: SessionDependency, session: SessionDependency,
request: DealProductAddKitRequest request: DealProductAddKitRequest,
user: CurrentUserDependency
): ):
return await DealService(session).add_kit_to_deal_product(request) return await DealService(session).add_kit_to_deal_product(user, request)
# endregion # endregion

View File

@@ -1,12 +1,14 @@
from fastapi import APIRouter from fastapi import APIRouter, Depends
from backend.dependecies import SessionDependency from backend.dependecies import SessionDependency
from schemas.marketplace import * from schemas.marketplace import *
from services.auth import authorized_user
from services.marketplace import MarketplaceService from services.marketplace import MarketplaceService
marketplace_router = APIRouter( marketplace_router = APIRouter(
prefix="/marketplace", prefix="/marketplace",
tags=["marketplace"] tags=["marketplace"],
dependencies=[Depends(authorized_user)]
) )

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter from fastapi import APIRouter, Depends
from backend.dependecies import SessionDependency, PaginationDependency, CurrentUserDependency from backend.dependecies import SessionDependency, PaginationDependency, CurrentUserDependency
from schemas.finances import CreatePayRateRequest, UpdatePayRateRequest, DeletePayRateRequest, \ from schemas.finances import CreatePayRateRequest, UpdatePayRateRequest, DeletePayRateRequest, \
@@ -6,11 +6,13 @@ from schemas.finances import CreatePayRateRequest, UpdatePayRateRequest, DeleteP
DeletePayRateResponse DeletePayRateResponse
from schemas.payment_record import GetPaymentRecordsResponse, CreatePaymentRecordResponse, CreatePaymentRecordRequest, \ from schemas.payment_record import GetPaymentRecordsResponse, CreatePaymentRecordResponse, CreatePaymentRecordRequest, \
DeletePaymentRecordResponse, DeletePaymentRecordRequest DeletePaymentRecordResponse, DeletePaymentRecordRequest
from services.auth import authorized_user
from services.payroll import PayrollService from services.payroll import PayrollService
payroll_router = APIRouter( payroll_router = APIRouter(
prefix="/payroll", prefix="/payroll",
tags=["payroll"] tags=["payroll"],
dependencies=[Depends(authorized_user)]
) )

View File

@@ -1,12 +1,14 @@
from fastapi import APIRouter from fastapi import APIRouter, Depends
from backend.dependecies import SessionDependency from backend.dependecies import SessionDependency
from schemas.position import * from schemas.position import *
from services.auth import authorized_user
from services.position import PositionService from services.position import PositionService
position_router = APIRouter( position_router = APIRouter(
prefix="/position", prefix="/position",
tags=["position"] tags=["position"],
dependencies=[Depends(authorized_user)]
) )

View File

@@ -6,18 +6,20 @@ from fastapi import APIRouter, Depends, UploadFile
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
import utils.dependecies import utils.dependecies
from backend.dependecies import CurrentUserDependency
from backend.session import get_session from backend.session import get_session
from schemas.barcode import GetProductBarcodeResponse, GetProductBarcodeRequest, GetProductBarcodePdfResponse, \ from schemas.barcode import GetProductBarcodeResponse, GetProductBarcodeRequest, GetProductBarcodePdfResponse, \
GetProductBarcodePdfRequest GetProductBarcodePdfRequest
from schemas.base import PaginationSchema from schemas.base import PaginationSchema
from schemas.product import * from schemas.product import *
from services.auth import guest_user
from services.barcode import BarcodeService from services.barcode import BarcodeService
from services.product import ProductService from services.product import ProductService
product_router = APIRouter( product_router = APIRouter(
prefix="/product", prefix="/product",
tags=["product"], tags=["product"],
# dependencies=[Depends(get_current_user)] dependencies=[Depends(guest_user)]
) )
@@ -28,7 +30,7 @@ product_router = APIRouter(
) )
async def create_product( async def create_product(
request: ProductCreateRequest, request: ProductCreateRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)],
): ):
return await ProductService(session).create(request) return await ProductService(session).create(request)
@@ -146,9 +148,7 @@ async def get_product_barcode_pdf(
filename=filename, filename=filename,
mime_type='application/pdf' mime_type='application/pdf'
) )
# return StreamingResponse(content=pdf_buffer,
# media_type='application/pdf',
# headers={"Content-Disposition": f"inline; filename={filename}"})
@product_router.post( @product_router.post(

View File

@@ -1,12 +1,14 @@
from fastapi import APIRouter from fastapi import APIRouter, Depends
from backend.dependecies import SessionDependency from backend.dependecies import SessionDependency
from schemas.role import * from schemas.role import *
from services.auth import authorized_user
from services.role import RoleService from services.role import RoleService
role_router = APIRouter( role_router = APIRouter(
prefix='/role', prefix='/role',
tags=['role'] tags=['role'],
dependencies=[Depends(authorized_user)]
) )

View File

@@ -6,22 +6,23 @@ from sqlalchemy.ext.asyncio import AsyncSession
import enums.service import enums.service
from backend.dependecies import SessionDependency from backend.dependecies import SessionDependency
from backend.session import get_session from backend.session import get_session
from schemas.base import BaseEnumSchema, BaseEnumListSchema from schemas.base import BaseEnumListSchema
from schemas.service import * from schemas.service import *
from services.auth import get_current_user from services.auth import guest_user, authorized_user
from services.service import ServiceService from services.service import ServiceService
service_router = APIRouter( service_router = APIRouter(
prefix="/service", prefix="/service",
tags=['service'], tags=['service'],
dependencies=[Depends(get_current_user)]
) )
@service_router.get( @service_router.get(
'/get-all', '/get-all',
response_model=ServiceGetAllResponse, response_model=ServiceGetAllResponse,
operation_id="get_all_services" operation_id="get_all_services",
dependencies=[Depends(guest_user)]
) )
async def get_all( async def get_all(
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
@@ -32,7 +33,8 @@ async def get_all(
@service_router.post( @service_router.post(
'/create', '/create',
response_model=ServiceCreateResponse, response_model=ServiceCreateResponse,
operation_id="create_service" operation_id="create_service",
dependencies=[Depends(authorized_user)]
) )
async def create( async def create(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
@@ -44,7 +46,8 @@ async def create(
@service_router.post( @service_router.post(
'/update', '/update',
response_model=ServiceUpdateResponse, response_model=ServiceUpdateResponse,
operation_id="update_service" operation_id="update_service",
dependencies=[Depends(authorized_user)]
) )
async def update( async def update(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
@@ -56,7 +59,8 @@ async def update(
@service_router.post( @service_router.post(
'/delete', '/delete',
response_model=ServiceDeleteResponse, response_model=ServiceDeleteResponse,
operation_id="delete_service" operation_id="delete_service",
dependencies=[Depends(authorized_user)]
) )
async def delete( async def delete(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
@@ -68,7 +72,8 @@ async def delete(
@service_router.get( @service_router.get(
'/categories/get-all', '/categories/get-all',
response_model=ServiceGetAllCategoriesResponse, response_model=ServiceGetAllCategoriesResponse,
operation_id="get_all_service_categories" operation_id="get_all_service_categories",
dependencies=[Depends(guest_user)]
) )
async def get_all_categories( async def get_all_categories(
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
@@ -79,7 +84,8 @@ async def get_all_categories(
@service_router.post( @service_router.post(
'/categories/create', '/categories/create',
response_model=ServiceCreateCategoryResponse, response_model=ServiceCreateCategoryResponse,
operation_id="create_service_category" operation_id="create_service_category",
dependencies=[Depends(authorized_user)]
) )
async def create_category( async def create_category(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
@@ -91,7 +97,8 @@ async def create_category(
@service_router.get( @service_router.get(
'/types/get-all', '/types/get-all',
response_model=BaseEnumListSchema, response_model=BaseEnumListSchema,
operation_id="get_all_service_types" operation_id="get_all_service_types",
dependencies=[Depends(guest_user)]
) )
async def get_all_service_types( async def get_all_service_types(
): ):
@@ -104,7 +111,8 @@ async def get_all_service_types(
@service_router.get( @service_router.get(
'/kits/get-all', '/kits/get-all',
response_model=GetAllServicesKitsResponse, response_model=GetAllServicesKitsResponse,
operation_id='get_all_services_kits' operation_id='get_all_services_kits',
dependencies=[Depends(guest_user)]
) )
async def get_all_services_kits( async def get_all_services_kits(
session: SessionDependency session: SessionDependency
@@ -115,7 +123,8 @@ async def get_all_services_kits(
@service_router.post( @service_router.post(
'/kits/create', '/kits/create',
response_model=CreateServicesKitResponse, response_model=CreateServicesKitResponse,
operation_id='create_services_kit' operation_id='create_services_kit',
dependencies=[Depends(authorized_user)]
) )
async def create_services_kit( async def create_services_kit(
session: SessionDependency, session: SessionDependency,
@@ -127,7 +136,8 @@ async def create_services_kit(
@service_router.post( @service_router.post(
'/kits/update', '/kits/update',
response_model=UpdateServicesKitResponse, response_model=UpdateServicesKitResponse,
operation_id='update_services_kit' operation_id='update_services_kit',
dependencies=[Depends(authorized_user)]
) )
async def update_services_kit( async def update_services_kit(
session: SessionDependency, session: SessionDependency,

View File

@@ -5,13 +5,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.session import get_session from backend.session import get_session
from schemas.shipping_warehouse import GetAllShippingWarehousesResponse from schemas.shipping_warehouse import GetAllShippingWarehousesResponse
from services.auth import get_current_user from services.auth import authorized_user
from services.shipping_warehouse import ShippingWarehouseService from services.shipping_warehouse import ShippingWarehouseService
shipping_warehouse_router = APIRouter( shipping_warehouse_router = APIRouter(
prefix="/shipping-warehouse", prefix="/shipping-warehouse",
tags=["shipping-warehouse"], tags=["shipping-warehouse"],
dependencies=[Depends(get_current_user)] dependencies=[Depends(authorized_user)]
) )

View File

@@ -1,12 +1,14 @@
from fastapi import APIRouter from fastapi import APIRouter, Depends
from backend.dependecies import SessionDependency, CurrentUserDependency from backend.dependecies import SessionDependency, CurrentUserDependency
from schemas.time_tracking import * from schemas.time_tracking import *
from services.auth import authorized_user
from services.time_tracking import TimeTrackingService from services.time_tracking import TimeTrackingService
time_tracking_router = APIRouter( time_tracking_router = APIRouter(
prefix="/time-tracking", prefix="/time-tracking",
tags=["time-tracking"] tags=["time-tracking"],
dependencies=[Depends(authorized_user)]
) )

View File

@@ -1,12 +1,14 @@
from fastapi import APIRouter from fastapi import APIRouter, Depends
from backend.dependecies import SessionDependency from backend.dependecies import SessionDependency
from schemas.user import * from schemas.user import *
from services.auth import authorized_user
from services.user import UserService from services.user import UserService
user_router = APIRouter( user_router = APIRouter(
prefix="/user", prefix="/user",
tags=["user"] tags=["user"],
dependencies=[Depends(authorized_user)]
) )

24
schemas/billing.py Normal file
View File

@@ -0,0 +1,24 @@
import datetime
from typing import Optional
from schemas.base import BaseSchema, OkMessageSchema
class DealBillRequestSchema(BaseSchema):
deal_id: int
created_at: datetime.datetime
paid: bool
pdf_url: Optional[str]
invoice_number: Optional[str]
class CreateDealBillRequest(BaseSchema):
deal_id: int
class CreateDealBillResponse(OkMessageSchema):
pass
class GetDealBillById(BaseSchema):
deal_bill: DealBillRequestSchema

View File

@@ -4,6 +4,7 @@ from typing import List, Optional, Union
from pydantic import constr, field_validator from pydantic import constr, field_validator
from schemas.base import BaseSchema, OkMessageSchema from schemas.base import BaseSchema, OkMessageSchema
from schemas.billing import DealBillRequestSchema
from schemas.client import ClientSchema from schemas.client import ClientSchema
from schemas.marketplace import BaseMarketplaceSchema from schemas.marketplace import BaseMarketplaceSchema
from schemas.product import ProductSchema from schemas.product import ProductSchema
@@ -71,9 +72,11 @@ class DealSchema(BaseSchema):
status_history: List[DealStatusHistorySchema] status_history: List[DealStatusHistorySchema]
is_deleted: bool is_deleted: bool
is_completed: bool is_completed: bool
is_locked: bool
client: ClientSchema client: ClientSchema
comment: str comment: str
shipping_warehouse: Optional[Union[ShippingWarehouseSchema, str]] = None shipping_warehouse: Optional[Union[ShippingWarehouseSchema, str]] = None
bill_request: Optional[DealBillRequestSchema] = None
class DealGeneralInfoSchema(BaseSchema): class DealGeneralInfoSchema(BaseSchema):
@@ -202,6 +205,10 @@ class DealAddKitRequest(BaseSchema):
kit_id: int kit_id: int
class DealCreateGuestUrlRequest(BaseSchema):
deal_id: int
# endregion Requests # endregion Requests
# region Responses # region Responses
@@ -293,4 +300,8 @@ class DealProductAddKitResponse(OkMessageSchema):
class DealAddKitResponse(OkMessageSchema): class DealAddKitResponse(OkMessageSchema):
pass pass
class DealCreateGuestUrlResponse(OkMessageSchema):
url: str
# endregion Responses # endregion Responses

View File

@@ -19,8 +19,10 @@ oauth2_schema = HTTPBearer()
algorithm = 'HS256' algorithm = 'HS256'
async def get_current_user(session: Annotated[AsyncSession, Depends(get_session)], async def get_current_user(
token: Annotated[HTTPAuthorizationCredentials, Depends(oauth2_schema)]) -> User | None: session: Annotated[AsyncSession, Depends(get_session)],
token: Annotated[HTTPAuthorizationCredentials, Depends(oauth2_schema)]
) -> Union[User, None, dict]:
if not token.credentials: if not token.credentials:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token') raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
try: try:
@@ -28,6 +30,8 @@ async def get_current_user(session: Annotated[AsyncSession, Depends(get_session)
user_id = payload.get('sub') user_id = payload.get('sub')
if not user_id: if not user_id:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid credentials') raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid credentials')
if user_id == 'guest':
return payload
user_id = int(user_id) user_id = int(user_id)
user = await session.get(User, user_id) user = await session.get(User, user_id)
@@ -35,16 +39,26 @@ async def get_current_user(session: Annotated[AsyncSession, Depends(get_session)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid credentials') raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid credentials')
return user return user
except JWTError as e: except JWTError as e:
print(e)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token') raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
async def authorized_user(
user: Annotated[User, Depends(get_current_user)]
):
if type(user) is User:
return user
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
async def guest_user(user: Annotated[User, Depends(get_current_user)]):
if (type(user) is User) or (type(user) is dict):
return user
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
class AuthService(BaseService): class AuthService(BaseService):
@staticmethod @staticmethod
def _generate_jwt_token(user: User) -> str: def _generate_jwt_token(payload: dict) -> str:
payload = {
'sub': str(user.id)
}
return jwt.encode(payload, backend.config.SECRET_KEY, algorithm=algorithm) return jwt.encode(payload, backend.config.SECRET_KEY, algorithm=algorithm)
async def authenticate(self, request: AuthLoginRequest): async def authenticate(self, request: AuthLoginRequest):
@@ -60,5 +74,16 @@ class AuthService(BaseService):
) )
self.session.add(user) self.session.add(user)
await self.session.commit() await self.session.commit()
access_token = self._generate_jwt_token(user) payload = {
'sub': str(user.id)
}
access_token = self._generate_jwt_token(payload)
return AuthLoginResponse(access_token=access_token) return AuthLoginResponse(access_token=access_token)
def create_deal_guest_token(self, deal_id: int):
payload = {
'sub': 'guest',
'deal_id': deal_id
}
return self._generate_jwt_token(payload)

118
services/billing.py Normal file
View File

@@ -0,0 +1,118 @@
import datetime
from typing import List
from fastapi import HTTPException
from sqlalchemy import select
from starlette import status
import backend.config
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, BillPaymentInfo
from models import DealBillRequest, Deal
from schemas.billing import *
from services.base import BaseService
from services.deal import DealService
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))

View File

@@ -1,16 +1,16 @@
import lexorank import lexorank
import models.secondary
from typing import Union
import models.deal
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy import select, func, update, delete, insert from sqlalchemy import select, func, update, delete, insert
from sqlalchemy.orm import joinedload, selectinload from sqlalchemy.orm import joinedload, selectinload
from starlette import status
import models.deal
import models.secondary
from models import User, Service, Client from models import User, Service, Client
from models.deal import * from models.deal import *
from schemas.client import ClientDetailsSchema from schemas.client import ClientDetailsSchema
from schemas.deal import * from schemas.deal import *
from services.auth import AuthService
from services.base import BaseService from services.base import BaseService
from services.client import ClientService from services.client import ClientService
from services.service import ServiceService from services.service import ServiceService
@@ -20,6 +20,15 @@ from services.shipping_warehouse import ShippingWarehouseService
class DealService(BaseService): class DealService(BaseService):
# region Deal # region Deal
@staticmethod
def grant_access(user: Union[models.User, dict], deal_id):
if type(user) is models.User:
return
user_deal_id = user['deal_id']
if int(user_deal_id) != int(deal_id):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]: async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]:
return await self.session.get(Deal, deal_id) return await self.session.get(Deal, deal_id)
@@ -211,7 +220,9 @@ class DealService(BaseService):
result.append(DealSchema.model_validate(deal)) result.append(DealSchema.model_validate(deal))
return DealGetAllResponse(deals=result) return DealGetAllResponse(deals=result)
async def get_by_id(self, deal_id: int) -> DealSchema: async def get_by_id(self, user: Union[models.User, dict], deal_id: int, return_raw=False) -> Union[
DealSchema, models.Deal]:
self.grant_access(user, deal_id)
deal = await self.session.scalar( deal = await self.session.scalar(
select(Deal) select(Deal)
@@ -244,7 +255,8 @@ class DealService(BaseService):
) )
.where(Deal.id == deal_id) .where(Deal.id == deal_id)
) )
if return_raw:
return deal
if not deal: if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена") raise HTTPException(status_code=404, detail="Сделка не найдена")
return DealSchema.model_validate(deal) return DealSchema.model_validate(deal)
@@ -360,11 +372,17 @@ class DealService(BaseService):
except Exception as e: except Exception as e:
return DealAddKitResponse(ok=False, message=str(e)) return DealAddKitResponse(ok=False, message=str(e))
def create_guest_url(self, user: models.User, request: DealCreateGuestUrlRequest) -> DealCreateGuestUrlResponse:
if not user.is_admin:
return DealCreateGuestUrlResponse(ok=False, message='Создать ссылку может только администратор', url="")
access_token = AuthService(self.session).create_deal_guest_token(request.deal_id)
url = f"deals/{request.deal_id}?accessToken={access_token}"
return DealCreateGuestUrlResponse(ok=True, message='Ссылка успешно создана!', url=url)
# endregion # endregion
# region Deal services # region Deal services
async def add_services(self, request: DealAddServicesRequest): async def add_services(self, request: DealAddServicesRequest):
# TODO refactor
deal: Deal = await self.session.scalar( deal: Deal = await self.session.scalar(
select(Deal) select(Deal)
.options(selectinload(Deal.services)) .options(selectinload(Deal.services))
@@ -404,9 +422,13 @@ class DealService(BaseService):
await self.session.commit() await self.session.commit()
return DealAddServicesResponse(ok=True, message='Услуги успешно добавлены') return DealAddServicesResponse(ok=True, message='Услуги успешно добавлены')
async def update_service_quantity(self, async def update_service_quantity(
request: DealUpdateServiceQuantityRequest) -> DealUpdateServiceQuantityResponse: self,
user: Union[models.User, dict],
request: DealUpdateServiceQuantityRequest
) -> DealUpdateServiceQuantityResponse:
try: try:
self.grant_access(user, request.deal_id)
deal_service = await self.session.scalar( deal_service = await self.session.scalar(
select(models.secondary.DealService) select(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id, .where(models.secondary.DealService.deal_id == request.deal_id,
@@ -421,8 +443,12 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealUpdateServiceQuantityResponse(ok=False, message=str(e)) return DealUpdateServiceQuantityResponse(ok=False, message=str(e))
async def add_service(self, request: DealAddServiceRequest) -> DealAddServiceResponse: async def add_service(self,
user: Union[models.User, dict],
request: DealAddServiceRequest
) -> DealAddServiceResponse:
try: try:
self.grant_access(user, request.deal_id)
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id)) deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
if not deal: if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена") raise HTTPException(status_code=404, detail="Сделка не найдена")
@@ -450,8 +476,13 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealAddServiceResponse(ok=False, message=str(e)) return DealAddServiceResponse(ok=False, message=str(e))
async def delete_service(self, request: DealDeleteServiceRequest) -> DealDeleteServiceResponse: async def delete_service(
self,
user: Union[models.User, dict],
request: DealDeleteServiceRequest
) -> DealDeleteServiceResponse:
try: try:
self.grant_access(user, request.deal_id)
deal_service = await self.session.scalar( deal_service = await self.session.scalar(
select(models.secondary.DealService) select(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id, .where(models.secondary.DealService.deal_id == request.deal_id,
@@ -466,8 +497,13 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealDeleteServiceResponse(ok=False, message=str(e)) return DealDeleteServiceResponse(ok=False, message=str(e))
async def delete_services(self, request: DealDeleteServicesRequest) -> DealDeleteServicesResponse: async def delete_services(
self,
user: Union[models.User, dict],
request: DealDeleteServicesRequest
) -> DealDeleteServicesResponse:
try: try:
self.grant_access(user, request)
deal_services = await self.session.scalars( deal_services = await self.session.scalars(
select(models.secondary.DealService) select(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id, .where(models.secondary.DealService.deal_id == request.deal_id,
@@ -481,8 +517,13 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealDeleteServicesResponse(ok=False, message=str(e)) return DealDeleteServicesResponse(ok=False, message=str(e))
async def update_service(self, request: DealUpdateServiceRequest) -> DealUpdateServiceResponse: async def update_service(
self,
user: Union[models.User, dict],
request: DealUpdateServiceRequest
) -> DealUpdateServiceResponse:
try: try:
self.grant_access(user, request.deal_id)
deal_service = await self.session.scalar( deal_service = await self.session.scalar(
select(models.secondary.DealService) select(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id, .where(models.secondary.DealService.deal_id == request.deal_id,
@@ -535,9 +576,13 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealUpdateServiceQuantityResponse(ok=False, message=str(e)) return DealUpdateServiceQuantityResponse(ok=False, message=str(e))
async def copy_services(self, request: DealServicesCopyRequest) -> DealServicesCopyResponse: async def copy_services(
self,
user: Union[models.User, dict],
request: DealServicesCopyRequest
) -> DealServicesCopyResponse:
try: try:
self.grant_access(user, request.deal_id)
source_services_stmt = ( source_services_stmt = (
select( select(
models.DealProductService models.DealProductService
@@ -614,9 +659,13 @@ class DealService(BaseService):
# endregion # endregion
# region Deal products # region Deal products
async def update_product_quantity(self, async def update_product_quantity(
request: DealUpdateProductQuantityRequest) -> DealUpdateProductQuantityResponse: self,
user: Union[models.User, dict],
request: DealUpdateProductQuantityRequest
) -> DealUpdateProductQuantityResponse:
try: try:
self.grant_access(user, request.deal_id)
# check if there is no deal or no product with different exceptions # check if there is no deal or no product with different exceptions
deal_product = await self.session.scalar( deal_product = await self.session.scalar(
select(models.secondary.DealProduct) select(models.secondary.DealProduct)
@@ -632,8 +681,15 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealUpdateProductQuantityResponse(ok=False, message=str(e)) return DealUpdateProductQuantityResponse(ok=False, message=str(e))
async def add_product(self, request: DealAddProductRequest) -> DealAddProductResponse: async def add_product(
self,
user: Union[models.User, dict],
request: DealAddProductRequest
) -> DealAddProductResponse:
try: try:
self.grant_access(user, request.deal_id)
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id)) deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
if not deal: if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена") raise HTTPException(status_code=404, detail="Сделка не найдена")
@@ -670,8 +726,13 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealAddProductResponse(ok=False, message=str(e)) return DealAddProductResponse(ok=False, message=str(e))
async def delete_product(self, request: DealDeleteProductRequest) -> DealDeleteProductResponse: async def delete_product(
self,
user: Union[models.User, dict],
request: DealDeleteProductRequest
) -> DealDeleteProductResponse:
try: try:
self.grant_access(user, request.deal_id)
deal_product = await self.session.scalar( deal_product = await self.session.scalar(
select(models.secondary.DealProduct) select(models.secondary.DealProduct)
.where(models.secondary.DealProduct.deal_id == request.deal_id, .where(models.secondary.DealProduct.deal_id == request.deal_id,
@@ -686,8 +747,13 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealDeleteProductResponse(ok=False, message=str(e)) return DealDeleteProductResponse(ok=False, message=str(e))
async def delete_products(self, request: DealDeleteProductsRequest) -> DealDeleteProductsResponse: async def delete_products(
self,
user: Union[models.User, dict],
request: DealDeleteProductsRequest
) -> DealDeleteProductsResponse:
try: try:
self.grant_access(user, request.deal_id)
deal_products = await self.session.scalars( deal_products = await self.session.scalars(
select(models.secondary.DealProduct) select(models.secondary.DealProduct)
.where(models.secondary.DealProduct.deal_id == request.deal_id, .where(models.secondary.DealProduct.deal_id == request.deal_id,
@@ -701,8 +767,13 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealDeleteProductsResponse(ok=False, message=str(e)) return DealDeleteProductsResponse(ok=False, message=str(e))
async def update_product(self, request: DealUpdateProductRequest): async def update_product(
self,
user: Union[models.User, dict],
request: DealUpdateProductRequest
):
try: try:
self.grant_access(user, request.deal_id)
deal_product: models.DealProduct = await self.session.scalar( deal_product: models.DealProduct = await self.session.scalar(
select(models.secondary.DealProduct) select(models.secondary.DealProduct)
.where(models.secondary.DealProduct.deal_id == request.deal_id, .where(models.secondary.DealProduct.deal_id == request.deal_id,
@@ -781,8 +852,13 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealUpdateProductResponse(ok=False, message=str(e)) return DealUpdateProductResponse(ok=False, message=str(e))
async def add_kit_to_deal_product(self, request: DealProductAddKitRequest) -> DealProductAddKitResponse: async def add_kit_to_deal_product(
self,
user: Union[models.User, dict],
request: DealProductAddKitRequest
) -> DealProductAddKitResponse:
try: try:
self.grant_access(user, request.deal_id)
service_service = ServiceService(self.session) service_service = ServiceService(self.session)
kit = await service_service.get_kit_by_id(request.kit_id) kit = await service_service.get_kit_by_id(request.kit_id)
if not kit: if not kit:

View File

@@ -1,3 +1,5 @@
from typing import Union
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy import select, func, Integer, update from sqlalchemy import select, func, Integer, update
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
@@ -5,25 +7,17 @@ from sqlalchemy.orm import selectinload
import utils.barcodes import utils.barcodes
from backend import config from backend import config
from external.s3_uploader.uploader import S3Uploader from external.s3_uploader.uploader import S3Uploader
from models.product import Product, ProductBarcode, ProductImage from models import User
from models.product import Product, ProductImage
from schemas.base import PaginationSchema from schemas.base import PaginationSchema
from services.base import BaseService
from schemas.product import * from schemas.product import *
from services.base import BaseService
from utils.dependecies import is_valid_pagination from utils.dependecies import is_valid_pagination
class ProductService(BaseService): class ProductService(BaseService):
async def create(self, request: ProductCreateRequest) -> ProductCreateResponse: async def create(self, request: ProductCreateRequest) -> ProductCreateResponse:
# Unique article validation
# existing_product_query = await self.session.execute(
# select(Product)
# .where(Product.client_id == request.client_id,
# Product.article == request.article)
# )
# existing_product = existing_product_query.first()
# if existing_product:
# return ProductCreateResponse(ok=False, message='Товар с таким артикулом уже существует у клиента')
# Creating product # Creating product
product_dict = request.dict() product_dict = request.dict()