diff --git a/enums/base_marketplace.py b/enums/base_marketplace.py
new file mode 100644
index 0000000..a441c59
--- /dev/null
+++ b/enums/base_marketplace.py
@@ -0,0 +1,7 @@
+from enum import StrEnum
+
+
+class BaseMarketplace(StrEnum):
+ WILDBERRIES = 'wb'
+ OZON = 'ozon'
+ YANDEX_MARKET = 'ym'
diff --git a/main.py b/main.py
index 6ce2788..c863c79 100644
--- a/main.py
+++ b/main.py
@@ -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")
diff --git a/models/__init__.py b/models/__init__.py
index 99e304e..e9e05d3 100644
--- a/models/__init__.py
+++ b/models/__init__.py
@@ -8,5 +8,6 @@ from .product import *
from .secondary import *
from .barcode import *
from .shipping_warehouse import *
+from .marketplace import *
configure_mappers()
diff --git a/models/auth.py b/models/auth.py
index 048048a..84c1b60 100644
--- a/models/auth.py
+++ b/models/auth.py
@@ -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'
diff --git a/models/deal.py b/models/deal.py
index 95b71ee..65bbd08 100644
--- a/models/deal.py
+++ b/models/deal.py
@@ -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)
diff --git a/models/marketplace.py b/models/marketplace.py
new file mode 100644
index 0000000..e5d6329
--- /dev/null
+++ b/models/marketplace.py
@@ -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()
diff --git a/models/secondary.py b/models/secondary.py
index 0323df2..10ac177 100644
--- a/models/secondary.py
+++ b/models/secondary.py
@@ -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)"
)
diff --git a/routers/__init__.py b/routers/__init__.py
index df3f9cc..2c8fa1d 100644
--- a/routers/__init__.py
+++ b/routers/__init__.py
@@ -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
diff --git a/routers/marketplace.py b/routers/marketplace.py
new file mode 100644
index 0000000..80f8480
--- /dev/null
+++ b/routers/marketplace.py
@@ -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()
diff --git a/routers/position.py b/routers/position.py
index f99b0e7..d8dba40 100644
--- a/routers/position.py
+++ b/routers/position.py
@@ -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)
diff --git a/schemas/deal.py b/schemas/deal.py
index 3f8e240..9f78782 100644
--- a/schemas/deal.py
+++ b/schemas/deal.py
@@ -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):
diff --git a/schemas/marketplace.py b/schemas/marketplace.py
new file mode 100644
index 0000000..64b3ad7
--- /dev/null
+++ b/schemas/marketplace.py
@@ -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
diff --git a/schemas/position.py b/schemas/position.py
index 3f5f986..7f8e62f 100644
--- a/schemas/position.py
+++ b/schemas/position.py
@@ -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
diff --git a/schemas/user.py b/schemas/user.py
index 345c388..0f3b75b 100644
--- a/schemas/user.py
+++ b/schemas/user.py
@@ -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
diff --git a/services/deal.py b/services/deal.py
index 1a9aac6..637146b 100644
--- a/services/deal.py
+++ b/services/deal.py
@@ -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='Товар успешно обновлен')
diff --git a/services/marketplace.py b/services/marketplace.py
new file mode 100644
index 0000000..ebdca40
--- /dev/null
+++ b/services/marketplace.py
@@ -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
+ )
diff --git a/services/position.py b/services/position.py
index fb91eca..de7eed8 100644
--- a/services/position.py
+++ b/services/position.py
@@ -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))
diff --git a/services/user.py b/services/user.py
index 0cb4471..1d84de9 100644
--- a/services/user.py
+++ b/services/user.py
@@ -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()
diff --git a/static/icons/ozon.png b/static/icons/ozon.png
new file mode 100644
index 0000000..52293bf
Binary files /dev/null and b/static/icons/ozon.png differ
diff --git a/static/icons/ozon.svg b/static/icons/ozon.svg
new file mode 100644
index 0000000..0e2622b
--- /dev/null
+++ b/static/icons/ozon.svg
@@ -0,0 +1,114 @@
+
+
+
diff --git a/static/icons/wb.svg b/static/icons/wb.svg
new file mode 100644
index 0000000..3fc2817
--- /dev/null
+++ b/static/icons/wb.svg
@@ -0,0 +1,16 @@
+
diff --git a/static/icons/ym.png b/static/icons/ym.png
new file mode 100644
index 0000000..a19c517
Binary files /dev/null and b/static/icons/ym.png differ
diff --git a/static/icons/ym.svg b/static/icons/ym.svg
new file mode 100644
index 0000000..b90e1af
--- /dev/null
+++ b/static/icons/ym.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/test.py b/test.py
index 0602cc4..b262575 100644
--- a/test.py
+++ b/test.py
@@ -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))
diff --git a/utils/init_marketplaces.py b/utils/init_marketplaces.py
new file mode 100644
index 0000000..0b6769c
--- /dev/null
+++ b/utils/init_marketplaces.py
@@ -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())
diff --git a/utils/init_roles.py b/utils/init_roles.py
index ba38d37..5058213 100644
--- a/utils/init_roles.py
+++ b/utils/init_roles.py
@@ -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(