first commit

This commit is contained in:
2025-07-24 20:13:47 +03:00
commit 94b7585f8b
175 changed files with 85264 additions and 0 deletions

22
Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM python:3.11-alpine
WORKDIR /app
RUN apk add --no-cache \
build-base \
gcc \
musl-dev \
libffi-dev \
python3-dev \
py3-setuptools \
py3-pip \
py3-wheel \
cargo
RUN pip install --upgrade pip setuptools wheel
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD uvicorn main:app --port 8081 --host=0.0.0.0 --use-colors --reload

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

35
app/api/v1/__init__.py Normal file
View File

@ -0,0 +1,35 @@
from fastapi import APIRouter
from app.api.v1 import (
project, board, client, service, product, barcode, deal,
shipping_warehouse, statistics, user, role, marketplace,
residues, department, position, payroll, status, auth, task, billing, work_shifts, transaction, time_tracking, shipping
)
router = APIRouter()
router.include_router(barcode.router, prefix='/barcode')
router.include_router(deal.router, prefix='/deal')
router.include_router(payroll.router, prefix='/payroll')
router.include_router(product.router, prefix='/product')
router.include_router(service.router, prefix='/service')
router.include_router(auth.router, prefix='/auth')
router.include_router(billing.router, prefix='/billing')
router.include_router(board.router, prefix='/board')
router.include_router(client.router, prefix='/client')
router.include_router(department.router, prefix='/department')
router.include_router(marketplace.router, prefix='/marketplace')
router.include_router(position.router, prefix='/position')
router.include_router(project.router, prefix='/project')
router.include_router(residues.router, prefix='/residues')
router.include_router(role.router, prefix='/role')
router.include_router(shipping.router, prefix='/shipping')
router.include_router(shipping_warehouse.router, prefix='/shipping-warehouse')
router.include_router(statistics.router, prefix='/statistics')
router.include_router(status.router, prefix='/status')
router.include_router(task.router, prefix='/task')
router.include_router(time_tracking.router, prefix='/time-tracking')
router.include_router(transaction.router, prefix='/transaction')
router.include_router(user.router, prefix='/user')
router.include_router(work_shifts.router, prefix='/work-shifts')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

75
app/api/v1/auth.py Normal file
View File

@ -0,0 +1,75 @@
from datetime import datetime, timedelta, timezone
from time import time
import jwt
from fastapi import APIRouter
from app import mongo
from app.config import config
from app.utils.response_util import response
import hashlib
import hmac
router = APIRouter()
def _string_generator(data_incoming):
data = data_incoming.copy()
del data["hash"]
keys = sorted(data.keys())
string_arr = []
for key in keys:
if data[key] is not None:
string_arr.append(key + "=" + str(data[key]))
string_cat = "\n".join(string_arr)
return string_cat
def _data_check(BOT_TOKEN, tg_data):
data_check_string = _string_generator(tg_data)
secret_key = hashlib.sha256(BOT_TOKEN.encode("utf-8")).digest()
secret_key_bytes = secret_key
data_check_string_bytes = bytes(data_check_string, "utf-8")
hmac_string = hmac.new(secret_key_bytes, data_check_string_bytes, hashlib.sha256).hexdigest()
if hmac_string == tg_data["hash"]:
return True
else:
return False
def authorize(telegram_data: dict):
return _data_check(config['BOT_TOKEN'], telegram_data)
def create_access_token(data: dict) -> str:
to_encode = data.copy()
expire = datetime.now(timezone.utc) + timedelta(minutes=int(config["JWT_ACCESS_TOKEN_EXPIRE_MINUTES"]))
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, config["JWT_SECRET_KEY"], algorithm=config["JWT_ALGORITHM"])
return encoded_jwt
@router.post("/login", tags=[""])
async def login(data: dict):
start_time = time()
if not authorize(data):
return response({
"detail": "Ошибка авторизации"
}, start_time=start_time, code=401)
user = await mongo.users_collection.find_one({
"telegramId": data["id"]
})
if not user:
return response({
"detail": "Пользователь не найден"
}, start_time=start_time, code=401)
access_token = create_access_token({
"sub": str(user["id"]),
"role": user["role"]["key"],
})
return response({
"accessToken": access_token
}, start_time=start_time)

View File

@ -0,0 +1,8 @@
from fastapi import APIRouter
from app.api.v1.barcode import barcode, template
router = APIRouter()
router.include_router(barcode.router)
router.include_router(template.router, prefix='/template')

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,9 @@
import json
from time import time
from fastapi import APIRouter
from app.config import config
from app.utils.logger_util import logger
from app.utils.response_util import response
router = APIRouter()

View File

@ -0,0 +1,9 @@
from fastapi import APIRouter
from app.api.v1.barcode.template import template, size, attribute
router = APIRouter()
router.include_router(template.router)
router.include_router(size.router, prefix='/size')
router.include_router(attribute.router, prefix='/attribute')

View File

@ -0,0 +1,93 @@
import json
from time import time
from fastapi import APIRouter
from app.config import config
from app.utils.logger_util import logger
from app.utils.response_util import response
from app import mongo
router = APIRouter()
@router.get("/get-all", tags=[""])
async def template_get_all():
start_time = time()
attributes = await mongo.template_attributes_collection.find({}, {
"_id": False
}).sort("id", mongo.asc).to_list()
return response({
"attributes": attributes
}, start_time=start_time)
@router.post("/create", tags=[""])
async def create(params: dict):
start_time = time()
data = params["attribute"]
data["id"] = await mongo.get_next_id(mongo.template_attributes_collection)
logger.json(data)
try:
await mongo.template_attributes_collection.insert_one(data)
return response({
"message": "Атрибут создан",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)
@router.post("/update", tags=[""])
async def update(params: dict):
start_time = time()
data = params["data"]
logger.json(data)
try:
await mongo.template_attributes_collection.update_one({
"id": data["id"]
}, {
"$set": data
})
return response({
"message": "Данные атрибута обновлены",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)
@router.post("/delete", tags=[""])
async def delete(params: dict):
start_time = time()
attribute_id = params["attributeId"]
logger.json(attribute_id)
try:
await mongo.template_attributes_collection.delete_one({
"id": attribute_id
})
return response({
"message": "Атрибут удален",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)

View File

@ -0,0 +1,93 @@
import json
from time import time
from fastapi import APIRouter
from app.config import config
from app.utils.logger_util import logger
from app.utils.response_util import response
from app import mongo
router = APIRouter()
@router.get("/get-all", tags=[""])
async def get_all():
start_time = time()
sizes = await mongo.template_sizes_collection.find({}, {
"_id": False
}).sort("id", mongo.asc).to_list()
return response({
"sizes": sizes
}, start_time=start_time)
@router.post("/create", tags=[""])
async def create(params: dict):
start_time = time()
data = params["size"]
data["id"] = await mongo.get_next_id(mongo.template_sizes_collection)
logger.json(data)
try:
await mongo.template_sizes_collection.insert_one(data)
return response({
"message": "Размер создан",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)
@router.post("/update", tags=[""])
async def update(params: dict):
start_time = time()
data = params["data"]
logger.json(data)
try:
await mongo.template_sizes_collection.update_one({
"id": data["id"]
}, {
"$set": data
})
return response({
"message": "Данные размера обновлены",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)
@router.post("/delete", tags=[""])
async def delete(params: dict):
start_time = time()
size_id = params["templateId"]
logger.json(size_id)
try:
await mongo.template_sizes_collection.delete_one({
"id": size_id
})
return response({
"message": "Размер удален",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)

View File

@ -0,0 +1,91 @@
import json
from time import time
from fastapi import APIRouter
from app.config import config
from app.utils.logger_util import logger
from app.utils.response_util import response
from app import mongo
router = APIRouter()
@router.get("/get-all", tags=[""])
async def get_all():
start_time = time()
templates = await mongo.templates_collection.find({}, {
"_id": False
}).sort("id", mongo.desc).to_list()
return response({
"templates": templates
}, start_time=start_time)
@router.post("/create", tags=[""])
async def create(data: dict):
start_time = time()
data["id"] = await mongo.get_next_id(mongo.templates_collection)
logger.json(data)
try:
await mongo.templates_collection.insert_one(data)
return response({
"message": "Шаблон создан",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)
@router.post("/update", tags=[""])
async def update(data: dict):
start_time = time()
logger.json(data)
try:
await mongo.templates_collection.update_one({
"id": data["id"]
}, {
"$set": data
})
return response({
"message": "Данные шаблона обновлены",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)
@router.post("/delete", tags=[""])
async def delete(params: dict):
start_time = time()
id = params["id"]
logger.json(id)
try:
await mongo.templates_collection.delete_one({
"id": id
})
return response({
"message": "Шаблон удален",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)

69
app/api/v1/billing.py Normal file
View File

@ -0,0 +1,69 @@
from time import time
from aiohttp import ClientResponseError
from fastapi import APIRouter
from app import mongo
from app.providers import tinkoff
from app.utils.response_util import response
router = APIRouter()
@router.post("/create-deal-bill", tags=[""])
async def create_deal_bill(params: dict):
start_time = time()
deal_id = params["dealId"]
deal = await mongo.deals_collection.find_one({
"id": deal_id
}, {
"_id": False
})
client = await mongo.clients_collection.find_one({
"name": deal["clientName"]
}, {
"_id": False
})
try:
total_price = await mongo.get_deals_total_prices(deal)
bill_data = await tinkoff.create_bill(deal_id, total_price[deal_id], client)
bill_request_data = {
"invoiceId": bill_data["invoiceId"],
"pdfUrl": bill_data["pdfUrl"],
"paid": False
}
await mongo.deals_collection.update_one(
{"id": deal_id},
{"$set": {"billRequest": bill_request_data}}
)
return response({
"ok": True,
"message": "Счёт успешно создан"
}, start_time=start_time)
except ClientResponseError:
return response({
"ok": False,
"message": "Не получилось создать счёт"
}, start_time=start_time)
@router.post("/cancel-deal-bill", tags=[""])
async def cancel_deal_bill(params: dict):
start_time = time()
deal_id = params["dealId"]
await mongo.deals_collection.update_one(
{"id": deal_id},
{"$set": {"billRequest": None}}
)
return response({
"ok": True,
"message": "Счёт успешно отозван"
}, start_time=start_time)

82
app/api/v1/board.py Normal file
View File

@ -0,0 +1,82 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("/{board_id}", tags=[""])
async def get(board_id: int):
start_time = time()
boards = await mongo.boards_collection.find({
"projectId": board_id
}, {
"_id": False
}).to_list()
for board in boards:
board["project"] = await mongo.projects_collection.find({
"id": board["projectId"]
}, {
"_id": False
}).to_list()
board["dealStatuses"] = await mongo.statuses_collection.find({
"boardId": board["id"]
}, {
"_id": False
}).to_list()
return response({
"boards": boards
}, start_time=start_time)
@router.post("/", tags=[""])
async def post(params: dict):
start_time = time()
data = params["board"]
data["id"] = await mongo.get_next_id(mongo.boards_collection)
await mongo.boards_collection.insert_one(data)
return response({
"message": "Доска создана",
"ok": True
}, start_time=start_time)
@router.patch("/", tags=[""])
async def patch(params: dict):
start_time = time()
data = params["board"]
await mongo.boards_collection.update_one({
"id": data["id"]
}, {
"$set": data
})
return response({
"message": "Доска обновлена",
"ok": True
}, start_time=start_time)
@router.delete("/{board_id}", tags=[""])
async def delete(board_id: int):
start_time = time()
await mongo.boards_collection.delete_one({
"id": board_id
})
return response({
"message": "Доска удален",
"ok": True
}, start_time=start_time)

143
app/api/v1/client.py Normal file
View File

@ -0,0 +1,143 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.logger_util import logger
from app.utils.response_util import response
router = APIRouter()
@router.get("/get-all", tags=[""])
async def get_all():
start_time = time()
clients = await mongo.clients_collection.find({}, {
"_id": False
}).sort("id", mongo.desc).to_list()
return response({
"clients": clients
}, start_time=start_time)
@router.get("/search", tags=[""])
async def search(name: str):
start_time = time()
clients = await mongo.clients_collection.find({
"name": {
"$regex": name
}
}, {
"_id": False
}).sort("id", mongo.asc).to_list()
return response({
"clients": clients
}, start_time=start_time)
@router.get("/get/{client_id}", tags=[""])
async def get(client_id: int):
start_time = time()
client = await mongo.clients_collection.find_one({
"id": client_id
}, {
"_id": False
})
boxes = await mongo.client_boxes_collection.find({
"clientId": client_id
}, {
"_id": False
}).to_list()
client["boxes"] = boxes
pallets = await mongo.client_pallets_collection.find({
"clientId": client_id
}, {
"_id": False
}).to_list()
for pallet in pallets:
pallet["boxes"] = await mongo.client_boxes_collection.find({
"palletId": pallet["id"]
}, {
"_id": False
}).to_list()
client["pallets"] = pallets
return response({
"client": client
}, start_time=start_time)
@router.post("/create", tags=[""])
async def create(params: dict):
start_time = time()
data = params["data"]
existing_client = await mongo.clients_collection.find_one({"name": data["name"]})
if existing_client:
return response({
"message": f"Клиент с именем '{data['name']}' уже существует",
"ok": False
}, start_time=start_time)
data["id"] = await mongo.get_next_id(mongo.clients_collection)
await mongo.clients_collection.insert_one(data)
return response({
"message": "Клиент создан",
"ok": True
}, start_time=start_time)
@router.post("/update", tags=[""])
async def update(params: dict):
start_time = time()
data = params["data"]
logger.json(data)
try:
await mongo.clients_collection.update_one({
"id": data["id"]
}, {
"$set": data
})
return response({
"message": "Данные клиента обновлены",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)
@router.post("/delete", tags=[""])
async def delete(params: dict):
start_time = time()
client_id = params["clientId"]
try:
await mongo.clients_collection.delete_one({
"id": client_id
})
return response({
"message": "Клиент удален",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)

View File

@ -0,0 +1,12 @@
from fastapi import APIRouter
from app.api.v1.deal import deal, summaries, employee, services, product, products
router = APIRouter()
router.include_router(deal.router)
router.include_router(summaries.router, prefix='/summaries')
router.include_router(employee.router, prefix='/employee')
router.include_router(services.router, prefix='/services')
router.include_router(product.router, prefix='/product')
router.include_router(products.router, prefix='/products')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

422
app/api/v1/deal/deal.py Normal file
View File

@ -0,0 +1,422 @@
import base64
from datetime import datetime
from io import BytesIO
from time import time
import barcode
from barcode.writer import ImageWriter
from fastapi import APIRouter
from fastapi.responses import Response
from num2words import num2words
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from starlette.requests import Request
from starlette.templating import Jinja2Templates
from app import mongo
from app.utils.response_util import response
router = APIRouter()
templates = Jinja2Templates(directory="assets/templates")
MONTHS_RU = {
1: "января",
2: "февраля",
3: "марта",
4: "апреля",
5: "мая",
6: "июня",
7: "июля",
8: "августа",
9: "сентября",
10: "октября",
11: "ноября",
12: "декабря",
}
def format_date_russian(date: datetime) -> str:
return f"{date.day} {MONTHS_RU[date.month]} {date.year} г."
@router.post("/quickCreate", tags=[""])
async def quick_create_deal(data: dict):
start_time = time()
data["id"] = await mongo.get_next_id(mongo.deals_collection)
data["createdAt"] = mongo.created_at()
status = await mongo.statuses_collection.find_one({"id": data["statusId"]}, {"_id": False})
if not status:
return response({"message": "Статус не найден", "ok": False}, start_time=start_time)
board = await mongo.boards_collection.find_one({"id": status["boardId"]}, {"_id": False})
if not board:
return response({"message": "Доска не найдена", "ok": False}, start_time=start_time)
data["boardId"] = status["boardId"]
data["projectId"] = board["projectId"]
await mongo.deals_collection.insert_one(data)
return response({
"message": "Сделка создана",
"ok": True,
"dealId": data["id"]
}, start_time=start_time)
@router.get("/get/{deal_id}", tags=[""])
async def get_deal(deal_id: int):
start_time = time()
deal = await mongo.deals_collection.find_one({
"id": deal_id
}, {
"_id": False
})
deals = await mongo.additional_deals_data(deal, full=True)
return response(deals[0], start_time=start_time)
@router.post("/update-general-info", tags=[""])
async def update_deal_general_info(params: dict, request: Request):
start_time = time()
data = params["data"]
deal_id = params["dealId"]
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
new_status = await mongo.statuses_collection.find_one({"id": data["statusId"]}, {"_id": False})
if new_status and new_status["name"] == "Завершено":
data["completedAt"] = mongo.created_at()
old_status_id = deal.get("statusId")
await mongo.deals_collection.update_one({
"id": deal_id
}, {
"$set": data
})
if old_status_id != data["statusId"]:
new_entry = {
"changedAt": mongo.created_at(),
"fromStatusId": old_status_id,
"toStatusId": data["statusId"],
"userId": request.state.user["id"],
"comment": ""
}
await mongo.deals_collection.update_one(
{"id": deal_id},
{"$push": {"statusHistory": new_entry}}
)
return response({
"message": "Данные сделки обновлены",
"ok": True
}, start_time=start_time)
@router.post("/delete", tags=[""])
async def delete_deal(params: dict):
start_time = time()
deal_id = params["dealId"]
await mongo.deals_collection.delete_one({"id": deal_id})
return response({
"message": "Сделка удалена",
"ok": True
}, start_time=start_time)
@router.post("/complete", tags=[""])
async def complete_deal(params: dict):
start_time = time()
deal_id = params["dealId"]
await mongo.deals_collection.update_one(
{"id": deal_id},
{"$set": {"isCompleted": True}}
)
return response({
"message": "Сделка завершена",
"ok": True
}, start_time=start_time)
@router.post("/prefill", tags=[""])
async def prefill_deal(params: dict):
start_time = time()
old_deal_id = params["oldDealId"]
new_deal_id = params["newDealId"]
old_deal = await mongo.deals_collection.find_one({"id": old_deal_id}, {"_id": False})
if not old_deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
await mongo.deals_collection.update_one(
{"id": new_deal_id},
{"$set": {
"name": old_deal["name"],
"clientName": old_deal["clientName"],
"services": old_deal.get("services", []),
"products": old_deal.get("products", [])
}}
)
return response({
"message": "Сделка предзаполнена",
"ok": True
}, start_time=start_time)
@router.get("/billing-document/{deal_id}", tags=[""])
async def deal_billing_document(deal_id: int):
pdfmetrics.registerFont(TTFont("Arial", "assets/arial.ttf"))
pdfmetrics.registerFont(TTFont("Arial Bold", "assets/arial_bold.ttf"))
center = ParagraphStyle(name="center", fontName="Arial", fontSize=10, alignment=1)
bold_center = ParagraphStyle(name="bold_center", fontName="Arial Bold", fontSize=10, alignment=1)
title = ParagraphStyle(name="title", fontName="Arial Bold", fontSize=14, alignment=1)
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=20, rightMargin=20, topMargin=30, bottomMargin=20)
elements = []
logo = Image("assets/logo.jpg", width=152, height=56)
logo.hAlign = "LEFT"
elements.append(logo)
elements.append(Spacer(1, 10))
elements.append(Paragraph("Расчет стоимости услуг", title))
elements.append(Spacer(1, 6))
elements.append(Paragraph(f"{deal['id']} от {format_date_russian(datetime.now())}", center))
elements.append(Paragraph("Адрес: 115516, г.Москва, ул. Промышленная 11", center))
elements.append(Paragraph(f"Клиент: {deal.get('clientName', 'Не указан')}", center))
elements.append(Paragraph(f"Маркетплейс: {deal.get('baseMarketplace', {}).get('name', 'Не указан')}", center))
elements.append(Paragraph(f"Склад отгрузки: {deal.get('shippingWarehouse', 'Не указан')}", center))
elements.append(Spacer(1, 10))
total_sum = 0
product_infos = []
table_width = 450
data_1 = [[
Paragraph("Артикул ВБ", bold_center),
Paragraph("Артикул продавца", bold_center),
Paragraph("Размер", bold_center),
Paragraph("Кол-во", bold_center),
Paragraph("Цена", bold_center),
Paragraph("Сумма", bold_center),
]]
for product in deal.get("products", []):
product_data = await mongo.products_collection.find_one({"id": product["productId"]}, {"_id": False})
if not product_data:
continue
article = product_data.get("article", "-")
vendor_code = product_data.get("vendorCode", "-")
size = product_data.get("size", "-")
quantity = product["quantity"]
price = sum(service["price"] * service.get("quantity", 1) for service in product["services"])
amount = quantity * price
total_sum += amount
data_1.append([
Paragraph(str(article), center),
Paragraph(str(vendor_code), center),
Paragraph(str(size or 0), center),
Paragraph(str(quantity or 1), center),
Paragraph(f"{price}", center),
Paragraph(f"{amount}", center),
])
product_infos.append({
"name": product_data.get("name") or "Без названия",
"quantity": product["quantity"],
"services": await get_services(product.get("services", []))
})
table_1 = Table(data_1, colWidths=[table_width / 6] * 5)
table_1.setStyle(TableStyle([
("BOX", (0, 0), (-1, -1), 0.8, colors.black),
("INNERGRID", (0, 0), (-1, -1), 0.4, colors.grey),
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
("ALIGN", (0, 0), (-1, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("ROUNDEDCORNERS", (16, 16, 16, 16)),
]))
elements.append(Paragraph("Товары", bold_center))
elements.append(Spacer(1, 4))
elements.append(table_1)
elements.append(Spacer(1, 12))
data_2 = [[
Paragraph("Товар / Услуга", bold_center),
Paragraph("Кол-во", bold_center),
Paragraph("Цена", bold_center),
Paragraph("Сумма", bold_center),
]]
for product in product_infos:
for service in product["services"]:
quantity = product.get("quantity", 0)
price = service.get("price", 0)
amount = quantity * price
data_2.append([
Paragraph(f"{product['name']} {service['name']}", center),
Paragraph(str(quantity), center),
Paragraph(f"{price}", center),
Paragraph(f"{amount}", center),
])
for service in await get_services(deal.get("services", [])):
quantity = service.get("quantity", 0)
price = service.get("price", 0)
amount = quantity * price
total_sum += amount
data_2.append([
Paragraph(service['name'], center),
Paragraph(str(quantity), center),
Paragraph(f"{price}", center),
Paragraph(f"{amount}", center)
])
table_2 = Table(data_2, colWidths=[
table_width * 0.4, table_width * 0.2, table_width * 0.2, table_width * 0.2
])
table_2.setStyle(TableStyle([
("BOX", (0, 0), (-1, -1), 0.8, colors.black),
("INNERGRID", (0, 0), (-1, -1), 0.4, colors.grey),
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
("ALIGN", (0, 0), (-1, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("ROUNDEDCORNERS", (16, 16, 16, 16)),
]))
elements.append(Paragraph("Услуги", bold_center))
elements.append(Spacer(1, 4))
elements.append(table_2)
elements.append(Spacer(1, 12))
elements.append(Paragraph(f"+79013618377 Ангелина К. (Т-Банк) Итого к оплате: {total_sum:,.0f}".replace(",", " "), bold_center))
elements.append(Paragraph(num2words(total_sum, lang='ru').capitalize() + " рублей", center))
doc.build(elements)
buffer.seek(0)
return Response(buffer.getvalue(), media_type="application/pdf", headers={"Content-Disposition": "inline; filename=billing_document.pdf"})
@router.get("/tech-spec/{deal_id}", tags=[""])
async def deal_tech_spec(request: Request, deal_id: int):
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
status = await mongo.statuses_collection.find_one({"id": deal["statusId"]}, {"_id": False})
deal_status = status.get("name", "Статус не определен") if status else "Статус не найден"
deal_services = await get_services(deal.get("services", []))
deal_data = {
"id": deal["id"],
"name": deal.get("name") or "Без названия",
"status": deal_status,
"createdAt": datetime.strptime(deal["createdAt"], "%Y-%m-%dT%H:%M:%S").strftime("%d.%m.%Y, %H:%M"),
"clientName": deal.get("clientName") or "Не указан",
"deliveryDate": (
datetime.strptime(deal["deliveryDate"], "%Y-%m-%dT%H:%M:%S.%f").strftime("%d.%m.%Y")
if deal.get("deliveryDate") else "Не указана"
),
"baseMarketplace": deal.get("baseMarketplace", {}).get("name") or "Не указан",
"shippingWarehouse": deal.get("shippingWarehouse") or "Не указан",
"products": [],
"services": deal_services
}
for product in deal.get("products", []):
product_data = await mongo.products_collection.find_one({"id": product["productId"]}, {"_id": False})
if not product_data:
continue
barcode_value = product_data.get("barcodes", [None])[0]
barcode_image = generate_barcode_base64(barcode_value) if barcode_value else None
product_services = await get_services(product.get("services", []))
deal_data["products"].append({
"name": product_data.get("name") or "Без названия",
"article": product_data.get("vendorCode") or "Нет артикула",
"imageUrl": product_data.get("imageUrl"),
"size": product_data.get("size") or 0,
"quantity": product.get("quantity") or 0,
"barcode": barcode_image,
"services": product_services,
"comment": product.get("comment")
})
return templates.TemplateResponse("deal_tech_spec.html", {"request": request, "deal": deal_data})
@router.post("/add-kit")
async def add_kits(params: dict):
start_time = time()
deal_id = params["dealId"]
kit_id = params["kitId"]
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
service_kit = await mongo.service_kits_collection.find_one({"id": kit_id})
if not service_kit:
return response({"message": "Комплект не найден", "ok": False}, start_time=start_time)
for service in service_kit.get("services", []):
price = service.get("price", 0)
if service.get("priceRanges"):
price_range = service["priceRanges"][0]
price = price_range.get("price", price)
deal["services"].append({
"serviceId": service["id"],
"price": price,
"isFixedPrice": False,
"quantity": 1
})
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"services": deal["services"]}})
return response({
"message": "Сервисы из комплекта добавлены в сделку",
"ok": True
}, start_time=start_time)
async def get_services(services):
service_list = []
for service in services:
service_data = await mongo.services_collection.find_one({"id": service["serviceId"]}, {"_id": False})
if service_data:
service_list.append({
"name": service_data.get("name") or "Неизвестная услуга",
"quantity": service.get("quantity") or 0,
"price": service.get("price") or 0
})
return service_list
def generate_barcode_base64(barcode_text: str) -> str:
buffer = BytesIO()
code128 = barcode.get("code128", barcode_text, writer=ImageWriter())
code128.write(buffer, {"module_height": 10.0, "font_size": 10, "quiet_zone": 2.0})
buffer.seek(0)
encoded = base64.b64encode(buffer.read()).decode("utf-8")
return f"data:image/png;base64,{encoded}"

View File

@ -0,0 +1,51 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("/available/{user_id}", tags=[""])
async def get(user_id: int):
start_time = time()
employees = await mongo.users_collection.find({}, {
"_id": False
}).sort("id", mongo.asc).to_list()
return response({
"employees": employees
}, start_time=start_time)
@router.post("", tags=[""])
async def add(params: dict):
start_time = time()
params["createdAt"] = mongo.created_at()
deal = await mongo.deals_collection.find_one(
{"id": params["dealId"]},
{"_id": False}
)
employees = deal.get("employees", [])
user_entry = {"userId": params["userId"], "createdAt": params["createdAt"]}
if params["isAssign"]:
if user_entry not in employees:
employees.append(user_entry)
else:
employees = [employee for employee in employees if employee["userId"] != params["userId"]]
await mongo.deals_collection.update_one(
{"id": params["dealId"]},
{"$set": {"employees": employees}}
)
return response({
"message": "Данные обновлены",
"ok": True
}, start_time=start_time)

View File

@ -0,0 +1,82 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.post("/update")
async def update_product(params: dict):
start_time = time()
deal_id = params["dealId"]
product_id = params["product"]["productId"]
quantity = params["product"]["quantity"]
services = params["product"]["services"] = [
{"serviceId": service["service"]["id"], "price": service["price"], "isFixedPrice": service.get("isFixedPrice", False), "quantity": 1}
for service in params["product"]["services"]
]
deal = await mongo.deals_collection.find_one({"id": deal_id})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
products = deal.get("products", [])
for product in products:
if product["productId"] == product_id:
if "comment" in params["product"]:
product["comment"] = params["product"]["comment"]
product["quantity"] = quantity
product["services"] = services
break
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"products": products}})
return response({
"message": "Товар обновлён в сделке",
"ok": True
}, start_time=start_time)
@router.post("/add-kit")
async def add_kits(params: dict):
start_time = time()
deal_id = params["dealId"]
kit_id = params["kitId"]
product_id = params["productId"]
deal = await mongo.deals_collection.find_one({"id": deal_id})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
service_kit = await mongo.service_kits_collection.find_one({"id": kit_id})
if not service_kit:
return response({"message": "Комплект не найден", "ok": False}, start_time=start_time)
kit_services = []
for service in service_kit.get("services", []):
price = service.get("price", 0)
if service.get("priceRanges"):
price_range = service["priceRanges"][0]
price = price_range.get("price", price)
kit_services.append({
"serviceId": service["id"],
"price": price,
"isFixedPrice": False,
"quantity": 1
})
for product in deal.get("products", []):
if product["productId"] == product_id:
product["services"].extend(kit_services)
break
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"products": deal["products"]}})
return response({
"message": "Сервисы из комплекта добавлены в продукт сделки",
"ok": True
}, start_time=start_time)

View File

@ -0,0 +1,57 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.post("/add")
async def add_product(params: dict):
start_time = time()
deal_id = params["dealId"]
product_data = {
"productId": params["product"]["product"]["id"],
"quantity": params["product"]["quantity"],
"services": [
{"serviceId": service["service"]["id"], "price": service["price"], "isFixedPrice": service.get("isFixedPrice", False), "quantity": 1}
for service in params["product"]["services"]
]
}
deal = await mongo.deals_collection.find_one({"id": deal_id})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
products = deal.get("products", [])
products.append(product_data)
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"products": products}})
return response({
"message": "Товар добавлен к сделке",
"ok": True
}, start_time=start_time)
@router.post("/delete")
async def delete_product(params: dict):
start_time = time()
deal_id = params["dealId"]
product_id = params["productId"]
deal = await mongo.deals_collection.find_one({"id": deal_id})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
products = deal.get("products", [])
products = [product for product in products if product["productId"] != product_id]
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"products": products}})
return response({
"message": "Товар удалён из сделки",
"ok": True
}, start_time=start_time)

139
app/api/v1/deal/services.py Normal file
View File

@ -0,0 +1,139 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.post("/add")
async def add_service(params: dict):
start_time = time()
deal_id = params["dealId"]
service_data = {
"serviceId": params["serviceId"],
"quantity": params["quantity"],
"price": params["price"]
}
deal = await mongo.deals_collection.find_one({"id": deal_id})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
services = deal.get("services", [])
services.append(service_data)
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"services": services}})
return response({
"message": "Услуга добавлена к сделке",
"ok": True
}, start_time=start_time)
@router.post("/update")
async def update_service(params: dict):
start_time = time()
deal_id = params["dealId"]
service_id = params["service"]["serviceId"]
deal = await mongo.deals_collection.find_one({"id": deal_id})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
services = deal.get("services", [])
for service in services:
if service["serviceId"] == service_id:
for key, value in params["service"].items():
service[key] = value
break
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"services": services}})
return response({
"message": "Услуга обновлена в сделке",
"ok": True
}, start_time=start_time)
@router.post("/delete")
async def delete_service(params: dict):
start_time = time()
deal_id = params["dealId"]
service_id = params["serviceId"]
deal = await mongo.deals_collection.find_one({"id": deal_id})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
services = deal.get("services", [])
updated_services = [service for service in services if service["serviceId"] != service_id]
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"services": updated_services}})
return response({
"message": "Услуга удалена из сделки",
"ok": True
}, start_time=start_time)
@router.post("/copy")
async def copy_services(params: dict):
start_time = time()
deal_id = params["dealId"]
source_product_id = params["sourceProductId"]
destination_product_ids = params["destinationProductIds"]
deal = await mongo.deals_collection.find_one({"id": deal_id})
if not deal:
return response({
"message": "Сделка не найдена",
"ok": False
}, start_time=start_time)
products = deal.get("products", [])
source_product_services = []
for product in products:
if product["productId"] == source_product_id:
source_product_services = product.get("services", [])
break
source_services_map = {s["serviceId"]: s for s in source_product_services}
service_quantities = {}
for product in products:
if product["productId"] in destination_product_ids:
new_services = []
for service_id, service in source_services_map.items():
new_service = service.copy()
new_services.append(new_service)
service_quantities[service_id] = service_quantities.get(service_id, 0) + product["quantity"]
product["services"] = new_services
for service_id, total_quantity in service_quantities.items():
service_data = await mongo.services_collection.find_one({"id": service_id})
if not service_data or "priceRanges" not in service_data:
continue
price_ranges = sorted(service_data["priceRanges"], key=lambda x: x["fromQuantity"])
new_price = None
for price_range in price_ranges:
if total_quantity >= price_range["fromQuantity"]:
new_price = price_range["price"]
if new_price is not None:
for product in products:
for service in product.get("services", []):
if service["serviceId"] == service_id and not service.get("isFixedPrice", False):
service["price"] = new_price
await mongo.deals_collection.update_one({"id": deal_id}, {"$set": {"products": products}})
return response({
"message": "Услуги скопированы и обновлены",
"ok": True
}, start_time=start_time)

View File

@ -0,0 +1,66 @@
from time import time
from fastapi import APIRouter
from starlette.requests import Request
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("", tags=[""])
async def get(full: bool):
start_time = time()
return response({
"summaries": await mongo.get_all_summaries(full)
}, start_time=start_time)
@router.post("/reorder", tags=[""])
async def reorder(params: dict, request: Request):
start_time = time()
deal_id = params["dealId"]
status_id = params["statusId"]
index = params["index"]
comment = params["comment"]
deal = await mongo.deals_collection.find_one({"id": deal_id}, {"_id": False})
if not deal:
return response({"message": "Сделка не найдена", "ok": False}, start_time=start_time)
update_fields = {
"statusId": status_id,
"index": index
}
new_status = await mongo.statuses_collection.find_one({"id": status_id}, {"_id": False})
if new_status and new_status.get("name") == "Завершено":
update_fields["completedAt"] = mongo.created_at()
old_status_id = deal.get("statusId")
await mongo.deals_collection.update_one(
{"id": deal_id},
{"$set": update_fields}
)
if old_status_id != status_id:
new_entry = {
"changedAt": mongo.created_at(),
"fromStatusId": old_status_id,
"toStatusId": status_id,
"userId": request.state.user["id"],
"comment": comment
}
await mongo.deals_collection.update_one(
{"id": deal_id},
{"$push": {"statusHistory": new_entry}}
)
return response({
"ok": True,
"summaries": await mongo.get_all_summaries(False)
}, start_time=start_time)

198
app/api/v1/department.py Normal file
View File

@ -0,0 +1,198 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("/", tags=[""])
async def get_departments():
start_time = time()
departments = await mongo.departments_collection.find(
{}, {"_id": False}
).sort("id", mongo.asc).to_list(length=None)
async def build_section_tree(parent_section):
users = []
for user in parent_section.get("users", []):
user_data = await mongo.users_collection.find_one({"id": user["userId"]}, {"_id": False})
if user_data:
user["user"] = user_data
users.append(user)
parent_section["users"] = users
children = await mongo.department_sections_collection.find(
{"parentDepartmentSectionId": parent_section["id"]},
{"_id": False}
).sort("id", mongo.asc).to_list(length=None)
parent_section["sections"] = []
for child in children:
await build_section_tree(child)
parent_section["sections"].append(child)
for department in departments:
top_sections = await mongo.department_sections_collection.find(
{
"departmentId": department["id"],
"$or": [{"parentDepartmentSectionId": None}, {"parentDepartmentSectionId": {"$exists": False}}]
},
{"_id": False}
).sort("id", mongo.asc).to_list(length=None)
department["sections"] = []
for section in top_sections:
await build_section_tree(section)
department["sections"].append(section)
return response({
"departments": departments
}, start_time=start_time)
@router.post("/", tags=[""])
async def create_department(params: dict):
start_time = time()
data = params["department"]
data["id"] = await mongo.get_next_id(mongo.departments_collection)
await mongo.departments_collection.insert_one(data)
return response({"message": "Департамент создан", "ok": True}, start_time=start_time)
@router.patch("/", tags=[""])
async def update_department(params: dict):
start_time = time()
data = params["department"]
await mongo.departments_collection.update_one({"id": data["id"]}, {"$set": data})
return response({"message": "Департамент обновлён", "ok": True}, start_time=start_time)
@router.delete("/{department_id}", tags=[""])
async def delete_department(department_id: int):
start_time = time()
sections = await mongo.department_sections_collection.find(
{"departmentId": department_id}, {"_id": False}
).to_list(length=None)
for section in sections:
await delete_section_recursive(section["id"])
await mongo.departments_collection.delete_one({"id": department_id})
return response({"message": "Департамент удалён", "ok": True}, start_time=start_time)
@router.get("/section", tags=[""])
async def get_sections():
start_time = time()
department_sections = await mongo.department_sections_collection.find(
{}, {"_id": False}
).sort("id", mongo.asc).to_list(length=None)
return response({
"departmentSections": department_sections
}, start_time=start_time)
@router.post("/section", tags=[""])
async def create_section(params: dict):
start_time = time()
data = params["section"]
if "departmentId" not in data:
return response({
"message": "Необходимо выбрать департамент",
"ok": False
}, start_time=start_time)
data["id"] = await mongo.get_next_id(mongo.department_sections_collection)
await mongo.department_sections_collection.insert_one(data)
return response({
"message": "Отдел создан",
"ok": True
}, start_time=start_time)
@router.patch("/section", tags=[""])
async def update_section(params: dict):
start_time = time()
data = params["section"]
await mongo.department_sections_collection.update_one({"id": data["id"]}, {"$set": data})
return response({"message": "Отдел обновлен", "ok": True}, start_time=start_time)
@router.delete("/section/{section_id}", tags=[""])
async def delete_section(section_id: int):
start_time = time()
await delete_section_recursive(section_id)
return response({"message": "Отдел удален", "ok": True}, start_time=start_time)
@router.get("/users/{section_id}", tags=[""])
async def get_available_users(section_id: int):
start_time = time()
users = await mongo.users_collection.find(
{"isDeleted": False}, {"_id": False}
).to_list(length=None)
return response({
"users": users
}, start_time=start_time)
@router.post("/users", tags=[""])
async def add_user_to_section(params: dict):
start_time = time()
section_id = params["sectionId"]
user_id = params["userId"]
is_chief = params.get("isChief", False)
await mongo.department_sections_collection.update_one(
{"id": section_id},
{"$addToSet": {"users": {"userId": user_id, "isChief": is_chief}}}
)
return response({
"message": "Пользователь добавлен в секцию",
"ok": True
}, start_time=start_time)
@router.post("/users/delete", tags=[""])
async def delete_user_from_section(params: dict):
start_time = time()
section_id = params["sectionId"]
user_id = params["userId"]
await mongo.department_sections_collection.update_one(
{"id": section_id},
{"$pull": {"users": {"userId": user_id}}}
)
return response({
"message": "Пользователь удалён из секции",
"ok": True
}, start_time=start_time)
async def delete_section_recursive(section_id: int):
children = await mongo.department_sections_collection.find(
{"parentDepartmentSectionId": section_id}, {"_id": False}
).to_list(length=None)
for child in children:
await delete_section_recursive(child["id"])
await mongo.department_sections_collection.delete_one({"id": section_id})

94
app/api/v1/marketplace.py Normal file
View File

@ -0,0 +1,94 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.post("/get", tags=[""])
async def get(data: dict):
start_time = time()
client_id = data["clientId"]
marketplaces = await mongo.marketplaces_collection.find({
"client.id": client_id
}, {
"_id": False
}).to_list()
return response({
"marketplaces": marketplaces
}, start_time=start_time)
@router.get("/base/get-all", tags=[""])
async def base_get_all():
start_time = time()
baseMarketplaces = await mongo.base_marketplaces_collection.find({}, {
"_id": False
}).to_list()
return response({
"baseMarketplaces": baseMarketplaces
}, start_time=start_time)
@router.post("/create", tags=[""])
async def create(params: dict):
start_time = time()
data = params["marketplace"]
data["id"] = await mongo.get_next_id(mongo.marketplaces_collection)
await mongo.marketplaces_collection.insert_one(data)
return response({
"message": "Маркетплейс клиента создан",
"ok": True
}, start_time=start_time)
@router.post("/update", tags=[""])
async def update(params: dict):
start_time = time()
data = params["marketplace"]
try:
await mongo.marketplaces_collection.update_one({
"id": data["id"]
}, {
"$set": data
})
return response({
"message": "Данные маркетплейса клиента обновлены",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)
@router.post("/delete", tags=[""])
async def delete(params: dict):
start_time = time()
marketplace_id = params["marketplaceId"]
try:
await mongo.marketplaces_collection.delete_one({
"id": marketplace_id
})
return response({
"message": "Маркетплейс клиента удален",
"ok": True
}, start_time=start_time)
except Exception as e:
return response({
"message": str(e),
"ok": False
}, start_time=start_time, code=400)

View File

@ -0,0 +1,9 @@
from fastapi import APIRouter
from app.api.v1.payroll import scheme, pay_rate, payment_record
router = APIRouter()
router.include_router(scheme.router, prefix='/scheme')
router.include_router(pay_rate.router, prefix='/pay-rate')
router.include_router(payment_record.router, prefix='/payment-record')

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,57 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("/get-all", tags=[""])
async def get_all_pay_rates():
start_time = time()
pay_rates = await mongo.pay_rates_collection.find(
{}, {"_id": False}
).sort("id", mongo.asc).to_list()
return response({
"payRates": pay_rates
}, start_time=start_time)
@router.post("/create", tags=[""])
async def create_pay_rate(params: dict):
start_time = time()
data = params["data"]
data["id"] = await mongo.get_next_id(mongo.pay_rates_collection)
await mongo.pay_rates_collection.insert_one(data)
return response({
"message": "Тариф успешно создан",
"ok": True
}, start_time=start_time)
@router.post("/update", tags=[""])
async def update_pay_rate(params: dict):
start_time = time()
data = params["data"]
await mongo.pay_rates_collection.update_one({"id": data["id"]}, {"$set": data})
return response({
"message": "Тариф успешно обновлен",
"ok": True
}, start_time=start_time)
@router.post("/delete", tags=[""])
async def delete_pay_rate(params: dict):
start_time = time()
await mongo.pay_rates_collection.delete_one({"id": params["payRateId"]})
return response({
"message": "Тариф успешно удален",
"ok": True
}, start_time=start_time)

View File

@ -0,0 +1,86 @@
from time import time
from fastapi import APIRouter
from starlette.requests import Request
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("/get", tags=[""])
async def get(page: int = 0, items_per_page: int = 10):
start_time = time()
total = await mongo.payment_records_collection.count_documents({})
payment_records = await mongo.payment_records_collection.find({}, {
"_id": False
}).limit(items_per_page).skip(items_per_page * (page - 1)).to_list(length=None)
for payment_record in payment_records:
payment_record["user"] = await mongo.users_collection.find_one({"id": payment_record["userId"]}, {"_id": False})
payment_record["createdByUser"] = await mongo.users_collection.find_one({"id": payment_record["createdByUserId"]}, {"_id": False})
return response({
"paginationInfo": {
"totalItems": total,
"totalPages": round(total / items_per_page)
},
"paymentRecords": payment_records
}, start_time=start_time)
@router.post("/create", tags=[""])
async def create(params: dict, request: Request):
start_time = time()
data = params["data"]
work_units = data.get("workUnits", 0)
user = await mongo.users_collection.find_one({"id": data["user"]["id"]}, {"_id": False})
pay_rate = await mongo.pay_rates_collection.find_one(
{"id": user.get("payRate", {}).get("id")},
{"_id": False}
)
payroll_scheme = pay_rate.get("payrollScheme", {})
total_amount = 0.0
if payroll_scheme.get("key") == "hourly":
overtime_threshold = pay_rate.get("overtimeThreshold", 0)
base_hours = min(work_units, overtime_threshold)
overtime_hours = max(work_units - overtime_threshold, 0)
base_rate = pay_rate.get("baseRate", 0)
overtime_rate = pay_rate.get("overtimeRate", base_rate)
total_amount = round(base_hours * base_rate + overtime_hours * overtime_rate, 2)
elif payroll_scheme.get("key") == "hourly":
base_rate = pay_rate.get("baseRate", 0)
total_amount = base_rate * work_units
data["id"] = await mongo.get_next_id(mongo.payment_records_collection)
data["userId"] = user["id"]
data["createdByUserId"] = request.state.user["id"]
data["createdAt"] = mongo.created_at()
data["amount"] = total_amount
data["payrollScheme"] = payroll_scheme
await mongo.payment_records_collection.insert_one(data)
return response({
"message": "Начисление создано",
"ok": True
}, start_time=start_time)
@router.post("/delete", tags=[""])
async def delete(params: dict):
start_time = time()
await mongo.payment_records_collection.delete_one({"id": params["paymentRecordId"]})
return response({
"message": "Начисление удалено",
"ok": True
}, start_time=start_time)

View File

@ -0,0 +1,21 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("/scheme/get-all", tags=[""])
async def get_all_schemes():
start_time = time()
schemes = await mongo.pay_rate_schemes_collection.find(
{}, {"_id": False}
).sort("id", mongo.asc).to_list()
return response({
"payrollSchemas": schemes
}, start_time=start_time)

View File

@ -0,0 +1,21 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("/get-all", tags=[""])
async def get_all_schemes():
start_time = time()
schemes = await mongo.pay_rate_schemes_collection.find(
{}, {"_id": False}
).sort("id", mongo.asc).to_list()
return response({
"payrollSchemas": schemes
}, start_time=start_time)

46
app/api/v1/position.py Normal file
View File

@ -0,0 +1,46 @@
from time import time
from fastapi import APIRouter
from app import mongo
from app.utils.response_util import response
router = APIRouter()
@router.get("/get-all", tags=[""])
async def get_all():
start_time = time()
positions = await mongo.positions_collection.find({}, {
"_id": False
}).sort("id", mongo.asc).to_list()
return response({
"positions": positions
}, start_time=start_time)
@router.post("/create", tags=[""])
async def create_position(params: dict):
start_time = time()
data = params["data"]
data["id"] = await mongo.get_next_id(mongo.positions_collection)
await mongo.positions_collection.insert_one(data)
return response({
"message": "Должность успешно создана",
"ok": True
}, start_time=start_time)
@router.post("/delete", tags=[""])
async def delete_position(params: dict):
start_time = time()
await mongo.positions_collection.delete_one({"key": params["positionKey"]})
return response({
"message": "Должность успешно удалена",
"ok": True
}, start_time=start_time)

View File

@ -0,0 +1,9 @@
from fastapi import APIRouter
from app.api.v1.product import product, barcode, images
router = APIRouter()
router.include_router(product.router)
router.include_router(barcode.router, prefix='/barcode')
router.include_router(images.router, prefix='/images')

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,246 @@
import base64
import os
import textwrap
from io import BytesIO
from time import time
import fitz
from PyPDF2 import PdfReader, PdfWriter
from fastapi import APIRouter, UploadFile, File
from reportlab.graphics.barcode import code128
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
from starlette.exceptions import HTTPException
from starlette.requests import Request
from app import mongo
from app.utils.response_util import response
router = APIRouter()
STATIC_FOLDER = "static"
BARCODES_FOLDER = os.path.join(STATIC_FOLDER, "barcodes")
os.makedirs(BARCODES_FOLDER, exist_ok=True)
@router.post("/get", tags=[""])
async def get_barcode(params: dict):
start_time = time()
return response({
"barcode": params["barcode"]
}, start_time=start_time)
@router.post("/add", tags=[""])
async def add_barcode(params: dict):
start_time = time()
product = await mongo.products_collection.find_one({
"id": params["productId"]
}, {
"_id": False
})
barcodes = product["barcodes"]
barcodes.append(params["barcode"])
await mongo.products_collection.update_one({
"id": params["productId"]
}, {
"$set": {"barcodes": barcodes}
})
return response({
"message": "Штрихкод добавлен",
"ok": True
}, start_time=start_time)
@router.get("/exists", tags=[""])
async def barcode_exists(product_id: int, barcode: str):
start_time = time()
product = await mongo.products_collection.find_one({
"id": product_id
}, {
"_id": False
})
return response({
"exists": barcode in product["barcodes"]
}, start_time=start_time)
@router.post("/get-pdf", tags=[""])
async def get_barcode_pdf(params: dict):
product_id = params["productId"]
quantity = params['quantity']
pdf_path = os.path.join(BARCODES_FOLDER, f"{product_id}.pdf")
if os.path.exists(pdf_path):
with open(pdf_path, "rb") as file:
existing_pdf = PdfReader(file)
buffer = BytesIO()
new_pdf = PdfWriter()
for _ in range(quantity):
for page_num in range(len(existing_pdf.pages)):
page = existing_pdf.pages[page_num]
new_pdf.add_page(page)
new_pdf.write(buffer)
buffer.seek(0)
return {
"base64String": base64.b64encode(buffer.getvalue()).decode("utf-8"),
"mimeType": "application/pdf"
}
product = await mongo.products_collection.find_one(
{"id": product_id}, {"_id": False}
)
client = await mongo.clients_collection.find_one(
{"id": product["clientId"]}, {"_id": False}
)
barcode_template = await mongo.templates_collection.find_one(
{"id": product["barcodeTemplate"]["id"]} if product["barcodeTemplate"] else {}, {"_id": False}
)
attributes = {
attribute["key"]: attribute["name"]
for attribute in barcode_template["attributes"]
}
buffer = BytesIO()
pdf = canvas.Canvas(buffer, pagesize=(
barcode_template["size"]["width"] * mm,
barcode_template["size"]["height"] * mm
))
pdf.setTitle("Product Barcode Information")
pdfmetrics.registerFont(TTFont('Arial', 'assets/arial.ttf'))
pdfmetrics.registerFont(TTFont('Arial Bold', 'assets/arial_bold.ttf'))
for _ in range(quantity):
name_lines = textwrap.wrap(product["name"], width=24)
text_y = 36 * mm
pdf.setFont("Arial Bold", 9)
for line in name_lines:
pdf.drawCentredString(29 * mm, text_y, line)
text_y -= 3 * mm
pdf.setFont("Arial", 7)
if "article" in attributes:
pdf.drawString(6 * mm, text_y, f"{attributes['article']}: {product['article']}")
text_y -= 3 * mm
if "brand" in attributes and product.get("brand"):
pdf.drawString(6 * mm, text_y, f"{attributes['brand']}: {product['brand']}")
text_y -= 3 * mm
if "client.name" in attributes and client:
pdf.drawString(6 * mm, text_y, client["name"])
text_y -= 3 * mm
if "color" in attributes and product.get("color"):
pdf.drawString(6 * mm, text_y, f"{attributes['color']}: {product['color']}")
text_y -= 3 * mm
if "size" in attributes and product.get("size"):
pdf.drawString(6 * mm, text_y, f"{attributes['size']}: {product['size']}")
text_y -= 3 * mm
barcode = code128.Code128(params["barcode"], barWidth=0.3 * mm, barHeight=10 * mm)
barcode.drawOn(pdf, 5 * mm, text_y - 8 * mm)
pdf.drawCentredString(29 * mm, text_y - 11 * mm, params["barcode"])
pdf.showPage()
pdf.save()
buffer.seek(0)
return {
"base64String": base64.b64encode(buffer.getvalue()).decode("utf-8"),
"mimeType": "application/pdf"
}
@router.post("/upload-image/{product_id}", tags=[""])
async def upload_product_barcode_image(
request: Request,
product_id: int,
upload_file: UploadFile = File(...)
):
start_time = time()
product = await mongo.products_collection.find_one({"id": product_id})
if not product:
raise HTTPException(status_code=404, detail="Product not found")
extension = os.path.splitext(upload_file.filename)[1] or ".pdf"
filename = f"{product_id}{extension}"
file_path = os.path.join(BARCODES_FOLDER, filename)
with open(file_path, "wb") as file:
file.write(await upload_file.read())
document = fitz.open(file_path)
page = document.load_page(0)
pixmap = page.get_pixmap()
output_image_filename = f"{product_id}.png"
output_image_path = os.path.join(BARCODES_FOLDER, output_image_filename)
pixmap.save(output_image_path)
document.close()
barcode_image_url = f"{str(request.base_url).rstrip('/')}/api/files/barcodes/{output_image_filename}"
return response({
"barcodeImageUrl": barcode_image_url,
"message": "Штрихкод успешно загружен!",
"ok": True
}, start_time=start_time)
@router.post("/delete-image/{product_id}", tags=[""])
async def delete_product_barcode_image(product_id: int):
start_time = time()
pdf_path = os.path.join(BARCODES_FOLDER, f"{product_id}.pdf")
png_path = os.path.join(BARCODES_FOLDER, f"{product_id}.png")
if not os.path.exists(pdf_path):
raise HTTPException(status_code=404, detail="Barcode PDF not found")
if os.path.exists(pdf_path):
os.remove(pdf_path)
if os.path.exists(png_path):
os.remove(png_path)
return response({
"message": "Штрихкод успешно удалён!",
"ok": True
}, start_time=start_time)
@router.post("/image/{product_id}", tags=[""])
async def get_product_barcode_image(request: Request, product_id: int):
start_time = time()
image_filename = f"{product_id}.png"
image_path = os.path.join(BARCODES_FOLDER, image_filename)
if not os.path.exists(image_path):
raise HTTPException(status_code=404, detail="Barcode image not found")
barcode_image_url = f"{str(request.base_url).rstrip('/')}/api/files/barcodes/{image_filename}"
return response({
"barcodeImageUrl": barcode_image_url,
"ok": True,
"message": "Штрихкод найден!"
}, start_time=start_time)

View File

@ -0,0 +1,40 @@
import os
from time import time
from fastapi import APIRouter, UploadFile, File, HTTPException, Request
from app import mongo
from app.utils.response_util import response
router = APIRouter()
STATIC_FOLDER = "static"
PRODUCTS_FOLDER = os.path.join(STATIC_FOLDER, "images")
os.makedirs(PRODUCTS_FOLDER, exist_ok=True)
@router.post("/upload/{product_id}", tags=["Products"])
async def upload_product_image(
request: Request,
product_id: int,
upload_file: UploadFile = File(...)
):
start_time = time()
product = await mongo.products_collection.find_one({"id": product_id})
if not product:
raise HTTPException(status_code=404, detail="Product not found")
extension = os.path.splitext(upload_file.filename)[1] or ".jpg"
filename = f"{product_id}{extension}"
file_path = os.path.join(PRODUCTS_FOLDER, filename)
with open(file_path, "wb") as file:
file.write(await upload_file.read())
image_url = f"{str(request.base_url).rstrip('/')}/api/files/images/{filename}"
return response({
"imageUrl": image_url,
"message": "Фото успешно загружено!",
"ok": True
}, start_time=start_time)

Some files were not shown because too many files have changed in this diff Show More