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 import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import platform import platform
from starlette.staticfiles import StaticFiles
import routers import routers
origins = [ origins = [
@@ -36,6 +39,9 @@ routers_list = [
routers.position_router, routers.position_router,
routers.user_router, routers.user_router,
routers.role_router, routers.role_router,
routers.marketplace_router,
] ]
for router in routers_list: for router in routers_list:
app.include_router(router) 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 .secondary import *
from .barcode import * from .barcode import *
from .shipping_warehouse import * from .shipping_warehouse import *
from .marketplace import *
configure_mappers() configure_mappers()

View File

@@ -1,6 +1,7 @@
from sqlalchemy import BigInteger, Table, ForeignKey, Column from sqlalchemy import BigInteger, Table, ForeignKey, Column
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from enums.user import UserRole
from models.base import BaseModel from models.base import BaseModel
role_permissions = Table( role_permissions = Table(
@@ -55,7 +56,7 @@ class User(BaseModel):
is_blocked: Mapped[bool] = mapped_column(nullable=False, server_default='0') is_blocked: Mapped[bool] = mapped_column(nullable=False, server_default='0')
is_deleted: 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: Mapped["Role"] = relationship(
'Role', 'Role',
lazy='joined' 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 sqlalchemy.orm import relationship, backref, Mapped, mapped_column
from models.base import BaseModel from models.base import BaseModel
from .marketplace import BaseMarketplace
from .shipping_warehouse import ShippingWarehouse 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_id: Mapped[int] = mapped_column(ForeignKey('shipping_warehouses.id'), nullable=True)
shipping_warehouse: Mapped["ShippingWarehouse"] = relationship() 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 # TODO remake with sequence
lexorank = Column(String, nullable=False, comment='Lexorank', index=True) 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 import Table, Column, Integer, ForeignKey, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy.orm import relationship, foreign, remote 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): class DealService(BaseModel):
@@ -18,12 +42,21 @@ class DealService(BaseModel):
quantity = Column(Integer, nullable=False, comment='Кол-во услуги') 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): class DealProductService(BaseModel):
__tablename__ = 'deal_product_services' __tablename__ = 'deal_product_services'
deal_id = Column(Integer, primary_key=True, nullable=False, comment='ID Сделки') deal_id = Column(Integer, primary_key=True, nullable=False, comment='ID Сделки')
product_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 Услуги') service_id = Column(Integer, ForeignKey('services.id'), primary_key=True, nullable=False, comment='ID Услуги')
price = Column(Integer, nullable=False, comment='Цена услуги') price = Column(Integer, nullable=False, comment='Цена услуги')
deal_product = relationship('DealProduct', deal_product = relationship('DealProduct',
@@ -31,9 +64,15 @@ class DealProductService(BaseModel):
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, " primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
"DealProductService.product_id == DealProduct.product_id)", "DealProductService.product_id == DealProduct.product_id)",
foreign_keys=[deal_id, product_id]) foreign_keys=[deal_id, product_id])
service = relationship('Service', service = relationship('Service',
foreign_keys=[service_id], foreign_keys=[service_id],
lazy='joined') lazy='joined'
)
employees = relationship('User',
secondary=deal_product_service_employees,
)
__table_args__ = ( __table_args__ = (
ForeignKeyConstraint( ForeignKeyConstraint(
@@ -52,9 +91,11 @@ class DealProduct(BaseModel):
deal = relationship('Deal', deal = relationship('Deal',
back_populates='products', back_populates='products',
foreign_keys=[deal_id]) foreign_keys=[deal_id])
product = relationship('Product', product = relationship(
'Product',
lazy='joined', lazy='joined',
foreign_keys=[product_id]) foreign_keys=[product_id],
)
services = relationship('DealProductService', services = relationship('DealProductService',
back_populates='deal_product', back_populates='deal_product',
@@ -62,7 +103,8 @@ class DealProduct(BaseModel):
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, " primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
"DealProductService.product_id == DealProduct.product_id)", "DealProductService.product_id == DealProduct.product_id)",
foreign_keys=[DealProductService.deal_id, DealProductService.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 .position import position_router
from .user import user_router from .user import user_router
from .role import role_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 request: CreatePositionRequest
): ):
return await PositionService(session).create(request) 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.base import BaseSchema, OkMessageSchema
from schemas.client import ClientSchema from schemas.client import ClientSchema
from schemas.marketplace import BaseMarketplaceSchema
from schemas.product import ProductSchema from schemas.product import ProductSchema
from schemas.service import ServiceSchema from schemas.service import ServiceSchema
from schemas.shipping_warehouse import ShippingWarehouseSchema from schemas.shipping_warehouse import ShippingWarehouseSchema
@@ -28,17 +29,20 @@ class DealSummary(BaseSchema):
status: int status: int
total_price: int total_price: int
rank: int rank: int
base_marketplace: Optional[BaseMarketplaceSchema] = None
class DealServiceSchema(BaseSchema): class DealServiceSchema(BaseSchema):
service: ServiceSchema service: ServiceSchema
quantity: int quantity: int
price: int price: int
employees: List[UserSchema]
class DealProductServiceSchema(BaseSchema): class DealProductServiceSchema(BaseSchema):
service: ServiceSchema service: ServiceSchema
price: int price: int
employees: List[UserSchema]
class DealProductSchema(BaseSchema): class DealProductSchema(BaseSchema):
@@ -98,6 +102,7 @@ class DealQuickCreateRequest(BaseSchema):
comment: str comment: str
acceptance_date: datetime.datetime acceptance_date: datetime.datetime
shipping_warehouse: constr(strip_whitespace=True) shipping_warehouse: constr(strip_whitespace=True)
base_marketplace: BaseMarketplaceSchema
class DealSummaryRequest(BaseSchema): 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 data: PositionSchema
class DeletePositionRequest(BaseSchema):
position_key: str
class GetAllPositionsResponse(BaseSchema): class GetAllPositionsResponse(BaseSchema):
positions: List[PositionSchema] positions: List[PositionSchema]
class CreatePositionResponse(OkMessageSchema): class CreatePositionResponse(OkMessageSchema):
pass pass
class DeletePositionResponse(OkMessageSchema):
pass

View File

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

View File

@@ -4,7 +4,7 @@ import models.secondary
from typing import Union from typing import Union
import models.deal import models.deal
from fastapi import HTTPException 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 sqlalchemy.orm import joinedload, selectinload
from models import User, Service, Client from models import User, Service, Client
@@ -103,7 +103,8 @@ class DealService(BaseService):
client_id=client.id, client_id=client.id,
current_status=DealStatus.CREATED, current_status=DealStatus.CREATED,
lexorank=rank, 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) self.session.add(deal)
await self.session.flush() await self.session.flush()
@@ -183,6 +184,9 @@ class DealService(BaseService):
deal: Deal deal: Deal
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at) last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
deadline = last_status.next_status_deadline deadline = last_status.next_status_deadline
base_marketplace = None
if deal.base_marketplace:
base_marketplace = BaseMarketplaceSchema.model_validate(deal.base_marketplace)
summaries.append( summaries.append(
DealSummary( DealSummary(
id=deal.id, id=deal.id,
@@ -192,7 +196,8 @@ class DealService(BaseService):
deadline=deadline, deadline=deadline,
status=last_status.to_status, status=last_status.to_status,
total_price=total_price, total_price=total_price,
rank=rank rank=rank,
base_marketplace=base_marketplace
) )
) )
return DealSummaryResponse(summaries=summaries) return DealSummaryResponse(summaries=summaries)
@@ -214,8 +219,10 @@ class DealService(BaseService):
joinedload(Deal.client) joinedload(Deal.client)
.joinedload(Client.details), .joinedload(Client.details),
selectinload(Deal.services) selectinload(Deal.services)
.joinedload(models.secondary.DealService.service) .options(
.joinedload(Service.category), joinedload(models.secondary.DealService.service).joinedload(Service.category),
selectinload(models.secondary.DealService.employees)
),
selectinload(Deal.products) selectinload(Deal.products)
.joinedload(models.secondary.DealProduct.product) .joinedload(models.secondary.DealProduct.product)
.joinedload(models.Product.client), .joinedload(models.Product.client),
@@ -224,7 +231,10 @@ class DealService(BaseService):
.joinedload(models.Product.barcodes), .joinedload(models.Product.barcodes),
selectinload(Deal.products) selectinload(Deal.products)
.joinedload(models.secondary.DealProduct.services) .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) selectinload(Deal.status_history)
.joinedload(DealStatusHistory.user), .joinedload(DealStatusHistory.user),
selectinload(Deal.status_history) selectinload(Deal.status_history)
@@ -439,13 +449,43 @@ class DealService(BaseService):
raise HTTPException(status_code=404, detail="Сделка не найдена") raise HTTPException(status_code=404, detail="Сделка не найдена")
service_dict = request.service.dict() service_dict = request.service.dict()
del service_dict['service'] del service_dict['service']
del service_dict['employees']
service_dict['service_id'] = request.service.service.id service_dict['service_id'] = request.service.service.id
await self.session.execute( await self.session.execute(
update(models.secondary.DealService) update(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id, .where(models.secondary.DealService.deal_id == request.deal_id,
models.secondary.DealService.service_id == request.service.service.id) models.secondary.DealService.service_id == request.service.service.id)
.values(**service_dict) .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() await self.session.commit()
return DealUpdateServiceQuantityResponse(ok=True, message='Услуга успешно обновлена') return DealUpdateServiceQuantityResponse(ok=True, message='Услуга успешно обновлена')
except Exception as e: except Exception as e:
@@ -586,6 +626,35 @@ class DealService(BaseService):
# Updating product # Updating product
deal_product.quantity = request.product.quantity 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() await self.session.commit()
return DealUpdateProductResponse(ok=True, message='Товар успешно обновлен') 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 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 schemas.position import *
from services.base import BaseService from services.base import BaseService
@@ -19,7 +19,6 @@ class PositionService(BaseService):
stmt = select(Position).where(Position.key == key) stmt = select(Position).where(Position.key == key)
return await self.session.scalar(stmt) return await self.session.scalar(stmt)
async def create(self, request: CreatePositionRequest) -> CreatePositionResponse: async def create(self, request: CreatePositionRequest) -> CreatePositionResponse:
try: try:
if await self.get_by_key(request.data.key): if await self.get_by_key(request.data.key):
@@ -30,3 +29,30 @@ class PositionService(BaseService):
return CreatePositionResponse(ok=True, message='Должность успешно создана') return CreatePositionResponse(ok=True, message='Должность успешно создана')
except Exception as e: except Exception as e:
return CreatePositionResponse(ok=False, message=str(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 = ( stmt = (
select(User) select(User)
.order_by(User.id.desc()) .order_by(User.id.desc())
# .where(User.is_deleted == False) .where(User.is_deleted == False)
) )
users = (await self.session.scalars(stmt)).all() users = (await self.session.scalars(stmt)).all()
users_schemas = [UserSchema.model_validate(user) for user in users] users_schemas = [UserSchema.model_validate(user) for user in users]
@@ -25,6 +25,7 @@ class UserService(BaseService):
return UpdateUserResponse(ok=False, message='Указанный пользователь не найден') return UpdateUserResponse(ok=False, message='Указанный пользователь не найден')
base_fields = request.data.model_dump_parent() base_fields = request.data.model_dump_parent()
stmt = update(User).values(**base_fields).where(User.id == request.data.id) stmt = update(User).values(**base_fields).where(User.id == request.data.id)
print(stmt)
await self.session.execute(stmt) await self.session.execute(stmt)
await self.session.flush() 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 a = {1, 2, 3}
b = {4, 5, 6}
print(a.union(b))
class A:
@classmethod
def test(cls) -> Self:
return cls()
class B(A):
pass
a = B.test()

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