feat: вфыв

This commit is contained in:
2024-07-21 10:56:59 +03:00
parent 6b09251141
commit 7c10d8777e
26 changed files with 497 additions and 43 deletions

View File

@@ -0,0 +1,7 @@
from enum import StrEnum
class BaseMarketplace(StrEnum):
WILDBERRIES = 'wb'
OZON = 'ozon'
YANDEX_MARKET = 'ym'

View File

@@ -1,6 +1,9 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import platform
from starlette.staticfiles import StaticFiles
import routers
origins = [
@@ -36,6 +39,9 @@ routers_list = [
routers.position_router,
routers.user_router,
routers.role_router,
routers.marketplace_router,
]
for router in routers_list:
app.include_router(router)
app.mount("/static", StaticFiles(directory="static"), name="static")

View File

@@ -8,5 +8,6 @@ from .product import *
from .secondary import *
from .barcode import *
from .shipping_warehouse import *
from .marketplace import *
configure_mappers()

View File

@@ -1,6 +1,7 @@
from sqlalchemy import BigInteger, Table, ForeignKey, Column
from sqlalchemy.orm import Mapped, mapped_column, relationship
from enums.user import UserRole
from models.base import BaseModel
role_permissions = Table(
@@ -55,7 +56,7 @@ class User(BaseModel):
is_blocked: Mapped[bool] = mapped_column(nullable=False, server_default='0')
is_deleted: Mapped[bool] = mapped_column(nullable=False, server_default='0')
role_key: Mapped[int] = mapped_column(ForeignKey('roles.key'))
role_key: Mapped[int] = mapped_column(ForeignKey('roles.key'), server_default=UserRole.user)
role: Mapped["Role"] = relationship(
'Role',
lazy='joined'

View File

@@ -4,6 +4,7 @@ from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean
from sqlalchemy.orm import relationship, backref, Mapped, mapped_column
from models.base import BaseModel
from .marketplace import BaseMarketplace
from .shipping_warehouse import ShippingWarehouse
@@ -36,9 +37,22 @@ class Deal(BaseModel):
shipping_warehouse_id: Mapped[int] = mapped_column(ForeignKey('shipping_warehouses.id'), nullable=True)
shipping_warehouse: Mapped["ShippingWarehouse"] = relationship()
services = relationship('DealService', back_populates='deal', cascade="all, delete-orphan")
base_marketplace_key: Mapped[str] = mapped_column(ForeignKey("base_marketplaces.key"), nullable=True)
base_marketplace: Mapped["BaseMarketplace"] = relationship(lazy="joined")
products = relationship('DealProduct', back_populates='deal', cascade="all, delete-orphan")
services = relationship(
'DealService',
back_populates='deal',
cascade="all, delete-orphan",
order_by="desc(DealService.service_id)"
)
products = relationship(
'DealProduct',
back_populates='deal',
cascade="all, delete-orphan",
order_by="desc(DealProduct.product_id)"
)
# TODO remake with sequence
lexorank = Column(String, nullable=False, comment='Lexorank', index=True)

11
models/marketplace.py Normal file
View File

@@ -0,0 +1,11 @@
from sqlalchemy.orm import Mapped, mapped_column
from models import BaseModel
class BaseMarketplace(BaseModel):
__tablename__ = 'base_marketplaces'
key: Mapped[str] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column()
icon_url: Mapped[str] = mapped_column()

View File

@@ -1,7 +1,31 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, and_, ForeignKeyConstraint
from sqlalchemy.orm import relationship, foreign, remote
from sqlalchemy import Table, Column, Integer, ForeignKey, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy.orm import relationship
from models.base import metadata, BaseModel
from models.base import BaseModel
deal_product_service_employees = Table(
'deal_product_service_employees',
BaseModel.metadata,
Column('deal_id', primary_key=True),
Column('service_id', primary_key=True),
Column('product_id', primary_key=True),
Column('user_id', ForeignKey('users.id'), primary_key=True),
ForeignKeyConstraint(
['deal_id', 'product_id', 'service_id'],
['deal_product_services.deal_id', 'deal_product_services.product_id', 'deal_product_services.service_id']
)
)
deal_service_employees = Table(
'deal_service_employees',
BaseModel.metadata,
Column('deal_id', primary_key=True),
Column('service_id', primary_key=True),
Column('user_id', ForeignKey('users.id'), primary_key=True),
ForeignKeyConstraint(
['deal_id', 'service_id'],
['deal_services.deal_id', 'deal_services.service_id']
)
)
class DealService(BaseModel):
@@ -16,14 +40,23 @@ class DealService(BaseModel):
service = relationship('Service')
quantity = Column(Integer, nullable=False, comment='Кол-во услуги')
price = Column(Integer, nullable=False,server_default='0', comment='Цена услуги')
price = Column(Integer, nullable=False, server_default='0', comment='Цена услуги')
employees = relationship('User', secondary=deal_service_employees)
__table_args__ = (
UniqueConstraint('deal_id', 'service_id', name='uix_deal_service'),
)
class DealProductService(BaseModel):
__tablename__ = 'deal_product_services'
deal_id = Column(Integer, primary_key=True, nullable=False, comment='ID Сделки')
product_id = Column(Integer, primary_key=True, nullable=False, comment='ID Продукта')
service_id = Column(Integer, ForeignKey('services.id'), primary_key=True, nullable=False, comment='ID Услуги')
price = Column(Integer, nullable=False, comment='Цена услуги')
deal_product = relationship('DealProduct',
@@ -31,9 +64,15 @@ class DealProductService(BaseModel):
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
"DealProductService.product_id == DealProduct.product_id)",
foreign_keys=[deal_id, product_id])
service = relationship('Service',
foreign_keys=[service_id],
lazy='joined')
lazy='joined'
)
employees = relationship('User',
secondary=deal_product_service_employees,
)
__table_args__ = (
ForeignKeyConstraint(
@@ -52,9 +91,11 @@ class DealProduct(BaseModel):
deal = relationship('Deal',
back_populates='products',
foreign_keys=[deal_id])
product = relationship('Product',
lazy='joined',
foreign_keys=[product_id])
product = relationship(
'Product',
lazy='joined',
foreign_keys=[product_id],
)
services = relationship('DealProductService',
back_populates='deal_product',
@@ -62,7 +103,8 @@ class DealProduct(BaseModel):
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
"DealProductService.product_id == DealProduct.product_id)",
foreign_keys=[DealProductService.deal_id, DealProductService.product_id],
lazy='joined'
lazy='joined',
order_by="desc(DealProductService.service_id)"
)

View File

@@ -8,3 +8,4 @@ from .shipping_warehouse import shipping_warehouse_router
from .position import position_router
from .user import user_router
from .role import role_router
from .marketplace import marketplace_router

21
routers/marketplace.py Normal file
View File

@@ -0,0 +1,21 @@
from fastapi import APIRouter
from backend.dependecies import SessionDependency
from schemas.marketplace import *
from services.marketplace import MarketplaceService
marketplace_router = APIRouter(
prefix="/marketplace",
tags=["marketplace"]
)
@marketplace_router.get(
'/base/get-all',
operation_id='get_all_base_marketplaces',
response_model=GetAllBaseMarketplacesResponse
)
async def get_all(
session: SessionDependency
):
return await MarketplaceService(session).get_all_base_marketplaces()

View File

@@ -31,3 +31,15 @@ async def create(
request: CreatePositionRequest
):
return await PositionService(session).create(request)
@position_router.post(
'/delete',
operation_id='delete_position',
response_model=DeletePositionResponse
)
async def delete(
session: SessionDependency,
request: DeletePositionRequest
):
return await PositionService(session).delete(request)

View File

@@ -5,6 +5,7 @@ from pydantic import constr, field_validator
from schemas.base import BaseSchema, OkMessageSchema
from schemas.client import ClientSchema
from schemas.marketplace import BaseMarketplaceSchema
from schemas.product import ProductSchema
from schemas.service import ServiceSchema
from schemas.shipping_warehouse import ShippingWarehouseSchema
@@ -28,17 +29,20 @@ class DealSummary(BaseSchema):
status: int
total_price: int
rank: int
base_marketplace: Optional[BaseMarketplaceSchema] = None
class DealServiceSchema(BaseSchema):
service: ServiceSchema
quantity: int
price: int
employees: List[UserSchema]
class DealProductServiceSchema(BaseSchema):
service: ServiceSchema
price: int
employees: List[UserSchema]
class DealProductSchema(BaseSchema):
@@ -98,6 +102,7 @@ class DealQuickCreateRequest(BaseSchema):
comment: str
acceptance_date: datetime.datetime
shipping_warehouse: constr(strip_whitespace=True)
base_marketplace: BaseMarketplaceSchema
class DealSummaryRequest(BaseSchema):

22
schemas/marketplace.py Normal file
View File

@@ -0,0 +1,22 @@
from typing import List
from schemas.base import BaseSchema
# region Entities
class BaseMarketplaceSchema(BaseSchema):
key: str
name: str
icon_url: str
# endregion
# region Requests
# endregion
# region Responses
class GetAllBaseMarketplacesResponse(BaseSchema):
base_marketplaces: List[BaseMarketplaceSchema]
# endregion

View File

@@ -12,9 +12,17 @@ class CreatePositionRequest(BaseSchema):
data: PositionSchema
class DeletePositionRequest(BaseSchema):
position_key: str
class GetAllPositionsResponse(BaseSchema):
positions: List[PositionSchema]
class CreatePositionResponse(OkMessageSchema):
pass
class DeletePositionResponse(OkMessageSchema):
pass

View File

@@ -19,10 +19,10 @@ class BaseUser(BaseSchema):
is_admin: bool
is_blocked: bool
is_deleted: bool
role_key: str
class UserSchema(BaseUser):
role_key: str
role: RoleSchema
position: Optional[PositionSchema] = None

View File

@@ -4,7 +4,7 @@ import models.secondary
from typing import Union
import models.deal
from fastapi import HTTPException
from sqlalchemy import select, func, update
from sqlalchemy import select, func, update, delete, insert
from sqlalchemy.orm import joinedload, selectinload
from models import User, Service, Client
@@ -103,7 +103,8 @@ class DealService(BaseService):
client_id=client.id,
current_status=DealStatus.CREATED,
lexorank=rank,
shipping_warehouse_id=shipping_warehouse.id
shipping_warehouse_id=shipping_warehouse.id,
base_marketplace_key=request.base_marketplace.key
)
self.session.add(deal)
await self.session.flush()
@@ -183,6 +184,9 @@ class DealService(BaseService):
deal: Deal
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
deadline = last_status.next_status_deadline
base_marketplace = None
if deal.base_marketplace:
base_marketplace = BaseMarketplaceSchema.model_validate(deal.base_marketplace)
summaries.append(
DealSummary(
id=deal.id,
@@ -192,7 +196,8 @@ class DealService(BaseService):
deadline=deadline,
status=last_status.to_status,
total_price=total_price,
rank=rank
rank=rank,
base_marketplace=base_marketplace
)
)
return DealSummaryResponse(summaries=summaries)
@@ -214,8 +219,10 @@ class DealService(BaseService):
joinedload(Deal.client)
.joinedload(Client.details),
selectinload(Deal.services)
.joinedload(models.secondary.DealService.service)
.joinedload(Service.category),
.options(
joinedload(models.secondary.DealService.service).joinedload(Service.category),
selectinload(models.secondary.DealService.employees)
),
selectinload(Deal.products)
.joinedload(models.secondary.DealProduct.product)
.joinedload(models.Product.client),
@@ -224,7 +231,10 @@ class DealService(BaseService):
.joinedload(models.Product.barcodes),
selectinload(Deal.products)
.joinedload(models.secondary.DealProduct.services)
.joinedload(models.secondary.DealProductService.service),
.options(
joinedload(models.secondary.DealProductService.service),
selectinload(models.secondary.DealProductService.employees)
),
selectinload(Deal.status_history)
.joinedload(DealStatusHistory.user),
selectinload(Deal.status_history)
@@ -439,13 +449,43 @@ class DealService(BaseService):
raise HTTPException(status_code=404, detail="Сделка не найдена")
service_dict = request.service.dict()
del service_dict['service']
del service_dict['employees']
service_dict['service_id'] = request.service.service.id
await self.session.execute(
update(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id,
models.secondary.DealService.service_id == request.service.service.id)
.values(**service_dict)
)
# Updating deleting previous employees
delete_stmt = (
delete(
models.deal_service_employees
)
.where(
models.deal_service_employees.c.deal_id == request.deal_id,
models.deal_service_employees.c.service_id == request.service.service.id,
)
)
await self.session.execute(delete_stmt)
await self.session.flush()
insert_data = []
for employee in request.service.employees:
insert_data.append({
'deal_id': request.deal_id,
'service_id': request.service.service.id,
'user_id': employee.id
})
if insert_data:
await self.session.execute(
insert(models.deal_service_employees),
insert_data
)
await self.session.flush()
await self.session.commit()
return DealUpdateServiceQuantityResponse(ok=True, message='Услуга успешно обновлена')
except Exception as e:
@@ -586,6 +626,35 @@ class DealService(BaseService):
# Updating product
deal_product.quantity = request.product.quantity
# Updating deleting old employees
delete_stmt = (
delete(
models.deal_product_service_employees
)
.where(
models.deal_product_service_employees.c.deal_id == request.deal_id,
models.deal_product_service_employees.c.service_id.in_(request_services.union(database_services)),
models.deal_product_service_employees.c.product_id == request.product.product.id
)
)
await self.session.execute(delete_stmt)
await self.session.flush()
insert_data = []
for service in request.product.services:
service: DealProductServiceSchema
for employee in service.employees:
insert_data.append({
'deal_id': request.deal_id,
'service_id': service.service.id,
'product_id': request.product.product.id,
'user_id': employee.id
})
if insert_data:
await self.session.execute(insert(models.deal_product_service_employees),
insert_data)
await self.session.flush()
await self.session.commit()
return DealUpdateProductResponse(ok=True, message='Товар успешно обновлен')

14
services/marketplace.py Normal file
View File

@@ -0,0 +1,14 @@
from sqlalchemy import select
from models import BaseMarketplace
from schemas.marketplace import GetAllBaseMarketplacesResponse
from services.base import BaseService
class MarketplaceService(BaseService):
async def get_all_base_marketplaces(self) -> GetAllBaseMarketplacesResponse:
stmt = (select(BaseMarketplace).order_by(BaseMarketplace.key))
base_marketplaces = (await self.session.scalars(stmt)).all()
return GetAllBaseMarketplacesResponse(
base_marketplaces=base_marketplaces
)

View File

@@ -1,8 +1,8 @@
from typing import Union
from sqlalchemy import select, insert
from sqlalchemy import select, insert, delete
from models import Position
from models import Position, user_position
from schemas.position import *
from services.base import BaseService
@@ -19,7 +19,6 @@ class PositionService(BaseService):
stmt = select(Position).where(Position.key == key)
return await self.session.scalar(stmt)
async def create(self, request: CreatePositionRequest) -> CreatePositionResponse:
try:
if await self.get_by_key(request.data.key):
@@ -30,3 +29,30 @@ class PositionService(BaseService):
return CreatePositionResponse(ok=True, message='Должность успешно создана')
except Exception as e:
return CreatePositionResponse(ok=False, message=str(e))
async def delete(self, request: DeletePositionRequest) -> DeletePositionResponse:
try:
# Prevent deleting if existing on users
stmt = (
select(
user_position
)
.where(
user_position.c.position_key == request.position_key
)
)
if await self.session.scalar(stmt):
return DeletePositionResponse(ok=False, message='Должность привязана к существующим сотрудникам')
delete_stmt = (
delete(
Position
)
.where(
Position.key == request.position_key
)
)
await self.session.execute(delete_stmt)
await self.session.commit()
return DeletePositionResponse(ok=True, message="Должность успешно удалена!")
except Exception as e:
return DeletePositionResponse(ok=False, message=str(e))

View File

@@ -10,7 +10,7 @@ class UserService(BaseService):
stmt = (
select(User)
.order_by(User.id.desc())
# .where(User.is_deleted == False)
.where(User.is_deleted == False)
)
users = (await self.session.scalars(stmt)).all()
users_schemas = [UserSchema.model_validate(user) for user in users]
@@ -25,6 +25,7 @@ class UserService(BaseService):
return UpdateUserResponse(ok=False, message='Указанный пользователь не найден')
base_fields = request.data.model_dump_parent()
stmt = update(User).values(**base_fields).where(User.id == request.data.id)
print(stmt)
await self.session.execute(stmt)
await self.session.flush()

BIN
static/icons/ozon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

114
static/icons/ozon.svg Normal file
View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1417.3 1417.3" style="enable-background:new 0 0 1417.3 1417.3;" xml:space="preserve">
<style type="text/css">
.st0{clip-path:url(#SVGID_00000028286792660517655600000001409180945242151851_);}
.st1{fill:#005BFF;}
.st2{fill:#F91155;}
.st3{fill:none;}
.st4{clip-path:url(#SVGID_00000067924568506772475600000006273407934913832106_);}
.st5{fill:#FFFFFF;}
</style>
<g>
<defs>
<path id="SVGID_1_" d="M1133.9,1417.3H283.5C126.9,1417.3,0,1290.4,0,1133.9V283.5C0,126.9,126.9,0,283.5,0h850.4
c156.6,0,283.5,126.9,283.5,283.5v850.4C1417.3,1290.4,1290.4,1417.3,1133.9,1417.3z"/>
</defs>
<clipPath id="SVGID_00000004505243697409283410000015111137345945702290_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_00000004505243697409283410000015111137345945702290_);">
<rect x="0" class="st1" width="1417.3" height="1417.3"/>
</g>
<g style="clip-path:url(#SVGID_00000004505243697409283410000015111137345945702290_);">
<g>
<g>
<g>
<path class="st2" d="M1267.3-251.6C1188-105.6,1135-29.9,981.2,142.6c-23.5,33.5-51.7,61.4-81.9,87.8
C758.7,353.3,618.4,477.3,477.4,599.6c-41.7,36.2-85.5,69.2-128.7,103.5c-25.7,20.4-56.5,31.6-86.6,41.9
C161.6,779.3,61.7,811.7-38.5,846.8c-45,15.8-89.3,35.2-133.5,53.5c-30.9,12.8-54.9,36.4-78.1,60.5
c-55.9,58.3-86,112.4-134.3,205.5c-0.1-3.2-13.2-290.4-13.2-290.4C-337,798-263.7,736-191,678.8c32-25.2,66.5-45.4,100.8-67
c60.6-38.1,115.2-86,166.9-136.5c49.1-47.8,89.3-103.8,128.7-160.5c62.4-89.9,125.8-179.3,193.2-265.2
c33.8-43.1,73.3-80.5,112.6-117.7c50.4-47.7,157.1-156.4,157.1-156.4c0.4,0,185.6-8.4,185.6-8.4c-5.5,6.4-11.1,16.1-13.2,19.6
C818.3-175.1,794.8-138,774-99c-13.2,24.8-21.8,53.2-33.6,78.9c-23.6,51-52,98.1-93.9,134.5c-13.8,12-30,22.6-46.1,30.6
c-33.5,16.6-62.9,38.7-90.7,63.8c-42.1,38-79.9,80.5-117.5,123.4C333,399.6,277,469.5,223.4,541.9c-5.2,7-11.1,13.6-15.7,21.1
c-2.2,3.6-3,8.3-4.6,12.4l1.4,2.6c5.4-1.9,11.6-3.2,16.4-6.2c18-11,36.8-20.9,53.6-33.6c63.6-48.1,127.5-96.8,189.6-147
c32-25.9,60.1-56.8,91.1-84.2c19.1-16.8,40.8-30.4,60.7-46.2c32.7-25.9,65.1-51.3,96.5-79C760.2,139.8,807.9,96.4,854,52
c96-92.8,77.1-195.3,169.3-292.6L1267.3-251.6z"/>
<path class="st2" d="M1574.1,1214.3c-14.8,15.2-22.3,27.1-38,41.3c-12.7,11.5-28.3,20.5-41.8,31.2
c-49.7,39.4-95.7,88.2-150.8,118.9c-41.6,23.2-84.9,39.5-131.6,44.9c-8.3,1-17.4-0.1-25.6-2.1c-10.7-2.6-14.8-12.3-13.6-23.7
c1.9-17.6,10.8-30.9,21.5-43.6c11.7-13.9,23.5-29.6,37.8-40c33.3-24,59.3-54.3,82-88.6c16.3-24.7,31.1-49.7,47.7-74.2
c20-29.7,46.2-53.3,71.4-77.8c59.9-58,117-120.6,180.3-174.3C1613.3,927.9,1575.5,1203.8,1574.1,1214.3z"/>
<path class="st2" d="M1297.7-253.5c0,0-5.2,13.2-9.9,22.5c-49.9,111.6-94.3,215-158,318.6c-54.8,89.1-111.4,176.3-176.6,257.4
c-22.8,28.4-45.9,57.5-75.2,78.8c-45.9,33.4-91.7,65.6-138,98.3c-65.6,46.3-131.8,92.2-192.4,145.8
c-23.9,21.1-46.7,43.5-65.6,70c-14.5,20.3-28.9,41-45.8,59c-68.7,73.6-144.9,138.5-221.7,202.3
c-79.2,65.7-159,130.6-238.9,195.3c-49.7,40.3-100.4,79-155,111.8c-10.9,6.5-23.1,11.4-34.8,16.1
c-1.8,0.7-114.2,45.5-123.3,53.8c0.7,9.4,10.4,145.5,10.7,147.3c1.6-0.9,33.7-26.7,40.1-31.4c46-34.8,70.1-60.6,120.9-86.2
c57.7-29.1,115.6-56,172.2-87.2c129.6-71.9,257.9-148.6,382.9-228.9c57.9-37.2,115.2-74.5,171.5-114.4
C628.3,927.8,687,870,744.3,809.9c70.1-73.6,138.9-148.7,207.2-224.3c46.6-51.6,92.1-105.2,137.5-158.1
c30.4-35.4,60.8-70.5,89.9-107.1c48.7-61.5,89.8-128.1,132.3-194.4c26.2-40.9,53-81.9,82.5-120.1
c75.3-97.6,143.4-191.8,236.3-283.5L1297.7-253.5z"/>
<path class="st2" d="M259.3,75c-63.3,67-125.1,134.1-187,202.5c-33.7,37.4-64.1,77.7-97.4,115.7
c-59.5,67.9-121.1,135-191.4,190.5c-71.3,56.2-148,113-220.7,167.4c-0.2-4.1-8.8-194.7-9-199c30.8-52.2,61.8-111.3,84.7-168
c13.5-33.5,25.4-68.7,39-102.2c26-64.5,60.1-123.5,110.3-170.5c28-26.3,60.2-44.9,93.3-62.6c99.3-53.2,198.5-107.3,298-160
c59.3-31.5,104.2-86.4,157.1-127.9l209.9-9.5C450.8-140.1,358.6-30.1,259.3,75z"/>
<path class="st2" d="M700.3,1624.9c16.3-15.9,31.6-30.3,46.6-45.6c70.1-71.6,149.5-130.4,227.6-191.3
c16.8-13.1,33.3-27.1,49.2-41.5c5.6-5.3,9.8-11.7,13-18.8c2.1-4.5,3.8-11.8,1.4-14.9c-3.1-4-10.6-8-15.3-6.9
c-11.4,2.4-23.6,5.5-33.4,11.8c-56.4,36.3-112,73.5-167.8,110.6c-19,12.7-37.9,27.3-56.8,40c-34.6,23-65,52.1-90.6,85.7
c-16.3,21.5-31.8,37.3-49.3,57.7c-2.1,2.5-7.8,8-11.7,12.1c-13-0.1-206.9-2.3-210.1-2.3c22.6-14.7,48.2-33.1,57.5-40
c101.1-75.7,183-121.7,291-185.1c95.8-56.3,190.3-115,285.1-173c33-20.2,66.7-40.5,91.8-72.2c16.4-20.7,33.5-40.6,49.4-61.8
c51.3-68.4,102-138.7,153.5-207c32.8-43.5,65.6-86.5,105.2-123.5c28.6-26.8,59.2-51.1,88-77.6c57-52.4,42.1-121.8,147.4-283.3
c58.6-89.9,97.4-119.8,129.1-154.1c20.3-22,79.5-55.6,103.3-73.4c-0.2,14.5-0.7,64.3-0.8,74.5c-43.5,37.3-114,140.9-122,199.1
c-28.7,207.7-218,333.3-314.5,457.5c-45.2,58.4-96.7,109.1-151.7,156.9c-36.1,31.4-71,64-99.8,103.2
c-11.6,15.8-19.4,34.5-30,51.2c-34.8,54.4-70.2,108.8-105.6,162.8c-26.4,40-52.7,80.8-82,118.4
c-34.9,44.8-61.6,75.3-98.7,118.2c-1.8,2.1-8.2,10-13,14.8L700.3,1624.9z"/>
<path class="st2" d="M-388.1,1703.1c107.2-75.9,230.4-139.5,343.3-207.1c51.9-31.1,105.2-60,158.5-88.4
c58.6-31.2,117.3-63.4,177.7-90.1c98.4-43.5,187.1-101.6,269.7-171.9c27.4-23.3,51.9-50.8,76.7-77.4
c52.1-55.9,104.8-112.6,155.7-169.7c50.2-56.3,98.4-114,148-170.9c25.7-29.5,53.7-57,85.6-78.9c7-4.6,15.1-7.2,22.9-9.9
c8.1-2.9,13.6,0.6,11.7,9.3c-3.2,14.7-7.6,30.7-14.5,43.7c-23.5,43.4-47.5,85.4-73.2,127.5c-32,52.5-67.9,103.4-107.4,149.8
c-35.6,41.9-74.4,78.7-118.3,110.7c-30,21.6-58.9,46.3-85.8,72.1c-42,40.8-90.2,72.3-137.7,105.6
c-100.9,70.8-205.9,135.9-310.1,201.1c-108.7,68-193.4,139.2-303,238.9L-388.1,1703.1z"/>
<path class="st2" d="M1630,182.2c73.2-33.4,191.6-134.6,243.3-186.1l-20.6-307c-22.5,18.5-58.2,44-78.7,61.6
c-21.6,18.3-49.1,43.4-68.9,63.7c-81.1,83.3-106.1,123-181.6,211.8c-68.9,81-142.9,163.9-190.5,261.3
c-16.1,33.1-29.8,66.5-46,99.5c-43.4,88.9-93.1,175-141.7,260.8c-35.9,63.3-73.9,124.1-118.9,180.5
c-39.6,49.6-79.1,99.3-119.8,147.9c-27.4,32.8-54.9,65.2-84.8,95.3c-42.3,42.7-90.9,77-140.3,109.9
c-51.2,34.3-102.9,67.4-155.1,100c-63.4,39.6-117.1,92.7-170,145.9c-22.1,22.3-43.9,45.3-67.5,65.6
c-58.6,50.4-111.6,81.2-164,138.7c-4.8,5.2-16.7,17.6-19.2,20.3l253.1-17c10.3-7,26.7-27.9,34.7-37.6
c20.7-31.4,34.1-45.4,52.5-78.2c20.4-36.4,48-65.5,82.9-85.7c27.5-15.9,54-31.9,81.6-47.6c37.8-21.5,78-40.1,113.8-64.7
c72.6-49.9,144.1-102.2,214.3-155.6c65.8-50.2,129.3-103.5,193.7-155.6c22.8-18.4,42.2-38.7,57.3-65
c36.1-63,64.5-130.4,89.4-198.9c19.9-54.7,37.7-110.4,57-165.5c35.6-101.5,84.5-196.1,140-286.5
C1519.2,224.1,1556.9,215.6,1630,182.2z"/>
</g>
</g>
</g>
</g>
<g style="clip-path:url(#SVGID_00000004505243697409283410000015111137345945702290_);">
<rect x="0" class="st3" width="1417.3" height="1417.3"/>
</g>
<g style="clip-path:url(#SVGID_00000004505243697409283410000015111137345945702290_);">
<g>
<defs>
<rect id="SVGID_00000032616369356456210220000012159966553573026438_" width="1417.3" height="1417.3"/>
</defs>
<clipPath id="SVGID_00000108279254641195066740000005803902842077635263_">
<use xlink:href="#SVGID_00000032616369356456210220000012159966553573026438_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_00000108279254641195066740000005803902842077635263_);">
<path class="st5" d="M150.7,947.5c-53.3,22-112.8-13.5-121.1-72.3C24,835.2,46,795.7,82.4,780.7c53.3-22,112.8,13.5,121.1,72.3
C209,893,187,932.5,150.7,947.5z M89.2,697.3C-13.8,714-74.3,822.4-36.7,923c27.8,74.4,104.4,120.2,180.6,107.8
c103-16.6,163.5-125.1,125.9-225.7C242,730.7,165.4,685,89.2,697.3z"/>
<path class="st5" d="M319.4,638.7c-23.2,6.2-35.7,32.5-24.3,55.6c8.3,16.9,28.1,24.7,46.1,19.9l104.3-27.9l-104,260.9
c-3.4,8.6,4.3,17.5,13.2,15.1l254.5-68.2c18-4.8,31.2-21.5,29.9-40.3c-1.7-25.7-25.7-42.2-48.8-36l-127,34l103.9-260.7
c3.4-8.6-4.4-17.6-13.3-15.2L319.4,638.7z"/>
<path class="st5" d="M1335.8,364.8c-16.4,8.4-24,28-19.2,46l33.9,126.3l-247.1-108.4c-8.6-3.8-17.7,4.1-15.2,13.3l69.9,260.8
c4.8,18,21.2,31.2,39.6,30.2c25.7-1.4,42.2-25.7,35.8-49.3l-34.1-127.4l247.1,108.4c8.6,3.8,17.7-4.1,15.2-13.3l-70.1-261.8
C1385.2,365.9,1358.8,353.1,1335.8,364.8z"/>
<path class="st5" d="M805.3,500.1C681.5,533.2,600.8,633.2,625,723.4c24.2,90.2,144.1,136.4,267.8,103.2
c123.7-33.2,204.5-133.1,180.3-223.3C1048.9,513.1,929,466.9,805.3,500.1z M825.7,576.3c85.5-22.9,161.7,5.9,172.7,47
c11,41.1-40.5,104.2-126,127.1c-85.5,22.9-161.7-5.9-172.7-47C688.6,662.2,740.2,599.2,825.7,576.3z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

16
static/icons/wb.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 12C0 5.37258 5.37258 0 12 0H36C42.6274 0 48 5.37258 48 12V36C48 42.6274 42.6274 48 36 48H12C5.37258 48 0 42.6274 0 36V12Z" fill="url(#paint0_linear_11_61)"/>
<path d="M13.85 32.5H10.6958L6 16H8.86965L12.3559 28.8936L16.1268 16H18.6407L22.3878 28.8936L25.8741 16H28.7437L24.0479 32.5H20.8937L17.3837 20.455L13.85 32.5ZM39.6758 23.9436C41.1225 24.6979 42 26.065 42 27.7857C42 29.1293 41.5257 30.2607 40.5533 31.1564C39.581 32.0521 38.4189 32.5 37.0196 32.5H29.9048V16H36.4979C37.8497 16 39.0118 16.4479 39.9367 17.32C40.8853 18.1921 41.3597 19.2764 41.3597 20.5729C41.3597 22.0107 40.7905 23.1186 39.6758 23.9436ZM36.4979 18.5457H32.6321V22.8829H36.4979C37.7074 22.8829 38.6323 21.94 38.6323 20.7143C38.6323 19.4886 37.7074 18.5457 36.4979 18.5457ZM32.6321 29.9543H37.0196C38.2766 29.9543 39.2727 28.9407 39.2727 27.6443C39.2727 26.3479 38.2766 25.3343 37.0196 25.3343H32.6321V29.9543Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_11_61" x1="-0.101122" y1="2.88128" x2="52.4352" y2="9.57311" gradientUnits="userSpaceOnUse">
<stop stop-color="#ED3CCA"/>
<stop offset="0.145833" stop-color="#DF34D2"/>
<stop offset="0.291667" stop-color="#D02BD9"/>
<stop offset="0.432292" stop-color="#BF22E1"/>
<stop offset="0.572917" stop-color="#AE1AE8"/>
<stop offset="0.713542" stop-color="#9A10F0"/>
<stop offset="0.854167" stop-color="#8306F7"/>
<stop offset="1" stop-color="#7C1AF8"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/icons/ym.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

36
static/icons/ym.svg Normal file
View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.9.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 29.9" style="enable-background:new 0 0 30 29.9;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{fill:#FED42B;}
.st2{display:none;fill:#FC3F1D;}
.st3{display:none;fill:#FFFFFF;}
.st4{clip-path:url(#SVGID_00000010272773654270891200000013716528645843490994_);}
</style>
<path class="st0" d="M46,26h3.3l4.6-17.1V26h3.7V3.4h-5.1L47.9,20L43.3,3.4h-5.2V26h3.2V8.9L46,26z M73.1,14.8c0-4-2-5.5-6.1-5.5
c-2.6,0-4.6,0.8-5.8,1.5V14c1-0.8,3.3-1.6,5.3-1.6c1.9,0,2.7,0.7,2.7,2.4v0.9h-0.6c-5.9,0-8.6,2-8.6,5.3s2,5.2,5,5.2
c2.3,0,3.3-0.7,4-1.5h0.2c0,0.4,0.2,1,0.3,1.3h3.8c-0.1-1.3-0.2-2.7-0.2-4C73.1,22,73.1,14.8,73.1,14.8z M69.2,22.1
c-0.5,0.7-1.4,1.3-2.8,1.3c-1.6,0-2.4-1-2.4-2.4c0-1.9,1.3-2.6,4.7-2.6h0.6L69.2,22.1L69.2,22.1z M79.8,9.6h-3.6v21.1h3.9v-6.6
c1,1.5,2.4,2.2,4.1,2.2c3.8,0,6.4-3,6.4-8.5s-2.5-8.5-6.2-8.5c-1.8,0-3.3,0.8-4.4,2.4C80,11.7,79.8,9.6,79.8,9.6z M83.1,23.3
c-2,0-3.1-1.7-3.1-5.4c0-3.8,1.1-5.5,3.3-5.5c2.1,0,3.2,1.7,3.2,5.4C86.5,21.6,85.4,23.3,83.1,23.3z M102.7,26h4.4l-6.2-8.8l5.5-7.6
h-3.9L97,17.2V9.6h-3.9V26H97v-8L102.7,26z M120.1,24.9v-3.2c-1.2,0.8-3.2,1.5-5.1,1.5c-2.8,0-3.9-1.3-4.1-4.1h9.3V17
c0-5.7-2.5-7.8-6.4-7.8c-4.7,0-7,3.6-7,8.6c0,5.7,2.8,8.5,7.7,8.5C117.2,26.3,119,25.7,120.1,24.9z M113.8,12.4
c1.9,0,2.5,1.6,2.5,3.6v0.3h-5.4C111,13.7,112,12.4,113.8,12.4z M134.1,12.7V9.6h-13v3.1h4.6V26h3.9V12.7H134.1z"/>
<path class="st1" d="M15,30c8.3,0,15-6.7,15-15S23.3,0,15,0S0,6.7,0,15C-0.1,23.3,6.7,30,15,30z"/>
<circle class="st2" cx="-17.1" cy="15" r="15"/>
<path class="st3" d="M-14.9,8.4h-1.5c-2.6,0-3.9,1.3-3.9,3.3c0,2.2,0.9,3.3,2.8,4.6l1.5,1.1l-4.3,6.7h-3.5l4.1-6.1
c-2.4-1.7-3.7-3.3-3.7-6.1c0-3.5,2.4-5.9,7-5.9h4.6v18h-3L-14.9,8.4L-14.9,8.4z"/>
<g>
<defs>
<path id="SVGID_1_" d="M15,30c8.3,0,15-6.7,15-15S23.3,0,15,0S0,6.7,0,15C-0.1,23.3,6.7,30,15,30z"/>
</defs>
<clipPath id="SVGID_00000002364318101313429230000004077879184956543148_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<path style="clip-path:url(#SVGID_00000002364318101313429230000004077879184956543148_);" d="M7.6,8.2L-5.6,25.5l3.5,4l9.8-13
l-1,7.1l5.5,1.9l6.7-10.7c-0.3,2-0.8,6.6,3.6,8c6.9,2.1,12.9-10.3,15.7-16.6l-4-2.1c-3.1,6.5-7.9,13.7-9.8,13.2
c-1.9-0.5-0.2-6.6,0.9-10.5V6.7l-6.1-2.1l-7.3,11.9l1-6.5L7.6,8.2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

17
test.py
View File

@@ -1,14 +1,3 @@
from typing import Self
class A:
@classmethod
def test(cls) -> Self:
return cls()
class B(A):
pass
a = B.test()
a = {1, 2, 3}
b = {4, 5, 6}
print(a.union(b))

View File

@@ -0,0 +1,37 @@
import asyncio
from sqlalchemy import insert
from sqlalchemy.ext.asyncio import AsyncSession
import models
from backend.session import session_maker
from enums.base_marketplace import BaseMarketplace
async def main():
session: AsyncSession = session_maker()
marketplaces = [
{
'key': BaseMarketplace.WILDBERRIES,
'name': 'Wildberries',
'icon_url': '/api/static/icons/wb.svg'
},
{
'key': BaseMarketplace.OZON,
'name': 'OZON',
'icon_url': '/api/static/icons/ozon.svg'
},
{
'key': BaseMarketplace.YANDEX_MARKET,
'name': 'Яндекс Маркет',
'icon_url': '/api/static/icons/ym.svg'
}
]
await session.execute(insert(models.BaseMarketplace), marketplaces)
await session.commit()
await session.close()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

View File

@@ -6,14 +6,15 @@ from backend.session import session_maker
from enums.user import UserRole
from models import Role
role_name_dictionary = {
UserRole.admin: "Админ",
UserRole.user: "Базовый пользователь",
UserRole.manager: "Менеджер",
UserRole.employee: "Сотрудник",
}
async def main():
role_name_dictionary = {
UserRole.admin: "Админ",
UserRole.user: "Базовый пользователь",
UserRole.manager: "Менеджер",
UserRole.employee: "Сотрудник",
}
session: AsyncSession = session_maker()
for key, name in role_name_dictionary.items():
role = Role(