Compare commits
	
		
			23 Commits
		
	
	
		
			40abf70430
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 39b0cb2418 | |||
| 241785ab91 | |||
| ba7000f3d9 | |||
| 489d470c7b | |||
| 485e6eacd2 | |||
| 4ebb7ce0fc | |||
| 9c904ae138 | |||
| 6ca3a9372a | |||
| 39087fc223 | |||
| 47ac3908f6 | |||
| d6dc39ef61 | |||
| 4d7e5ded4d | |||
| 7512b3347e | |||
| 7e853c48e7 | |||
| c8a234e27b | |||
| b302feec1d | |||
| 6dea56e3ee | |||
| dc8d9d242b | |||
| 34be5a198d | |||
| c508a3ee33 | |||
| f0b1c030f3 | |||
| 43cd50d5cd | |||
| 347eb9730c | 
							
								
								
									
										1
									
								
								app.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								app.py
									
									
									
									
									
								
							@@ -32,6 +32,7 @@ blueprints = [
 | 
			
		||||
    (routes.application_blueprint, '/application'),
 | 
			
		||||
    (routes.sipro_blueprint, '/sipro'),
 | 
			
		||||
    (routes.admin_blueprint, '/admin'),
 | 
			
		||||
    (routes.balance_blueprint, '/balance'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
for blueprint, url_prefix in blueprints:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,3 +9,9 @@ def to_nested_dict(row):
 | 
			
		||||
            current_level = current_level[part]
 | 
			
		||||
        current_level[keys[-1]] = value
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compile_query_to_plain_sql(query) -> str:
 | 
			
		||||
    return query.statement.compile(compile_kwargs={
 | 
			
		||||
        'literal_binds': True
 | 
			
		||||
    })
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,4 @@
 | 
			
		||||
from database.models import *
 | 
			
		||||
from flask_sqlalchemy import SQLAlchemy
 | 
			
		||||
 | 
			
		||||
db = SQLAlchemy()
 | 
			
		||||
from .models import *
 | 
			
		||||
 
 | 
			
		||||
@@ -8,3 +8,9 @@ class AssemblyState(IntEnum):
 | 
			
		||||
    ALL_PRODUCTS_ASSEMBLED = 2,
 | 
			
		||||
    CONFIRMED = 3,
 | 
			
		||||
    ENDED = 4
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@unique
 | 
			
		||||
class BalanceTransactionType(IntEnum):
 | 
			
		||||
    TOP_UP = 0
 | 
			
		||||
    WITHDRAW = 1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								database/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								database/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
from .basic import *
 | 
			
		||||
from .balance import *
 | 
			
		||||
							
								
								
									
										40
									
								
								database/models/balance.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								database/models/balance.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
from sqlalchemy import Connection, func
 | 
			
		||||
from sqlalchemy.dialects.postgresql import JSONB
 | 
			
		||||
from sqlalchemy.orm import Mapper
 | 
			
		||||
 | 
			
		||||
import database
 | 
			
		||||
from database import db
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BalanceTransaction(db.Model):
 | 
			
		||||
    __tablename__ = 'balance_transactions'
 | 
			
		||||
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True, comment='ID транзакции')
 | 
			
		||||
    type = db.Column(db.Integer, nullable=False, comment='Тип транзакции')
 | 
			
		||||
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 | 
			
		||||
    user = db.relationship('User', backref='transactions')
 | 
			
		||||
    amount = db.Column(db.Double, nullable=False, comment='Сумма транзакции')
 | 
			
		||||
    description = db.Column(db.Text, nullable=False, comment='Описание')
 | 
			
		||||
    json_data = db.Column(JSONB, comment='JSON данные')
 | 
			
		||||
    created_at = db.Column(db.DateTime, nullable=False, comment='Дата и время создания транзакции')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def recalculate_user_balance(user_id: int):
 | 
			
		||||
    user_balance = (database.BalanceTransaction.query.filter_by(user_id=user_id)
 | 
			
		||||
                    .group_by(database.BalanceTransaction.user_id)
 | 
			
		||||
                    .with_entities(func.sum(database.BalanceTransaction.amount))
 | 
			
		||||
                    .scalar()) or 0
 | 
			
		||||
 | 
			
		||||
    (db.session.query(database.User)
 | 
			
		||||
     .filter_by(id=user_id)
 | 
			
		||||
     .update({'balance': user_balance}, synchronize_session='fetch'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@db.event.listens_for(BalanceTransaction, 'after_insert')
 | 
			
		||||
def after_balance_transaction_insert(mapper: Mapper, connection: Connection, target: BalanceTransaction):
 | 
			
		||||
    recalculate_user_balance(target.user_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@db.event.listens_for(BalanceTransaction, 'after_delete')
 | 
			
		||||
def after_balance_transaction_delete(mapper: Mapper, connection: Connection, target: BalanceTransaction):
 | 
			
		||||
    recalculate_user_balance(target.user_id)
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
from flask_sqlalchemy import SQLAlchemy
 | 
			
		||||
 | 
			
		||||
db = SQLAlchemy()
 | 
			
		||||
from database import db
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class User(db.Model):
 | 
			
		||||
@@ -14,6 +12,7 @@ class User(db.Model):
 | 
			
		||||
    is_admin = db.Column(db.Boolean, nullable=False, default=False, server_default='0', comment='Админ ли юзверь')
 | 
			
		||||
 | 
			
		||||
    city_id = db.Column(db.Integer, nullable=False, default='1', server_default='1', comment='ID страны')
 | 
			
		||||
    balance = db.Column(db.Double, nullable=False, default=0, server_default='0', comment='Баланс пользователя')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Assembly(db.Model):
 | 
			
		||||
							
								
								
									
										0
									
								
								queries/balance/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								queries/balance/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										4
									
								
								queries/balance/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								queries/balance/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
from .requests import (
 | 
			
		||||
    get_balance_transactions,
 | 
			
		||||
    get_balance_info
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										29
									
								
								queries/balance/api/requests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								queries/balance/api/requests.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
import database
 | 
			
		||||
from ..schemas.requests import *
 | 
			
		||||
from ..schemas.responses import *
 | 
			
		||||
from ..schemas.entities import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_balance_transactions(data: dict):
 | 
			
		||||
    items_per_page = 5
 | 
			
		||||
    request = GetBalanceTransactionsRequest.model_validate(data)
 | 
			
		||||
    page = max([request.page - 1, 0])
 | 
			
		||||
    query = (database.BalanceTransaction.query
 | 
			
		||||
             .filter(database.BalanceTransaction.user_id == request.user_id)
 | 
			
		||||
             .order_by(database.BalanceTransaction.created_at.desc())
 | 
			
		||||
             .offset(items_per_page * page)
 | 
			
		||||
             .limit(items_per_page)
 | 
			
		||||
             .all())
 | 
			
		||||
    balance_transactions = [BalanceTransaction.model_validate(balance_transaction) for balance_transaction in query]
 | 
			
		||||
    response = GetBalanceTransactionsResponse(balance_transactions=balance_transactions)
 | 
			
		||||
    return response.to_dict()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_balance_info(data: dict):
 | 
			
		||||
    request = GetBalanceInfoRequest.model_validate(data)
 | 
			
		||||
    balance = (database.User.query
 | 
			
		||||
               .filter_by(id=request.user_id)
 | 
			
		||||
               .with_entities(database.User.balance)
 | 
			
		||||
               .scalar())
 | 
			
		||||
    response = GetBalanceInfoResponse(balance=balance)
 | 
			
		||||
    return response.to_dict()
 | 
			
		||||
							
								
								
									
										0
									
								
								queries/balance/schemas/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								queries/balance/schemas/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										13
									
								
								queries/balance/schemas/entities.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								queries/balance/schemas/entities.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from schemas.base import CommonModel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BalanceTransaction(CommonModel):
 | 
			
		||||
    id: int
 | 
			
		||||
    type: int
 | 
			
		||||
    user_id: int
 | 
			
		||||
    amount: float
 | 
			
		||||
    description: str
 | 
			
		||||
    json_data: dict | None = None
 | 
			
		||||
    created_at: datetime
 | 
			
		||||
							
								
								
									
										10
									
								
								queries/balance/schemas/requests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								queries/balance/schemas/requests.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
from schemas.base import CommonModel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GetBalanceTransactionsRequest(CommonModel):
 | 
			
		||||
    page: int
 | 
			
		||||
    user_id: int
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GetBalanceInfoRequest(CommonModel):
 | 
			
		||||
    user_id: int
 | 
			
		||||
							
								
								
									
										12
									
								
								queries/balance/schemas/responses.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								queries/balance/schemas/responses.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from schemas.base import CommonModel
 | 
			
		||||
from .entities import BalanceTransaction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GetBalanceTransactionsResponse(CommonModel):
 | 
			
		||||
    balance_transactions: List[BalanceTransaction]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GetBalanceInfoResponse(CommonModel):
 | 
			
		||||
    balance: float
 | 
			
		||||
@@ -14,4 +14,5 @@ mariadb
 | 
			
		||||
 | 
			
		||||
# Other stuff
 | 
			
		||||
requests
 | 
			
		||||
python-dotenv
 | 
			
		||||
python-dotenv
 | 
			
		||||
pydantic
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
from routes.auth import auth_blueprint
 | 
			
		||||
from routes.orders import orders_blueprint
 | 
			
		||||
from routes.barcode import barcode_blueprint
 | 
			
		||||
from routes.printing import printing_blueprint
 | 
			
		||||
from routes.assembly import assembly_blueprint
 | 
			
		||||
from routes.general import general_blueprint
 | 
			
		||||
from routes.application import application_blueprint
 | 
			
		||||
from routes.sipro import sipro_blueprint
 | 
			
		||||
from routes.admin import admin_blueprint
 | 
			
		||||
from .auth import auth_blueprint
 | 
			
		||||
from .orders import orders_blueprint
 | 
			
		||||
from .barcode import barcode_blueprint
 | 
			
		||||
from .printing import printing_blueprint
 | 
			
		||||
from .assembly import assembly_blueprint
 | 
			
		||||
from .general import general_blueprint
 | 
			
		||||
from .application import application_blueprint
 | 
			
		||||
from .sipro import sipro_blueprint
 | 
			
		||||
from .admin import admin_blueprint
 | 
			
		||||
from .balance import balance_blueprint
 | 
			
		||||
 
 | 
			
		||||
@@ -56,16 +56,41 @@ def get_user(user_id):
 | 
			
		||||
@admin_blueprint.put('/user/<int:user_id>')
 | 
			
		||||
def put_user(user_id):
 | 
			
		||||
    params: dict = request.json
 | 
			
		||||
    password = params.get('password').strip()
 | 
			
		||||
    password = params.get('password')
 | 
			
		||||
    if password:
 | 
			
		||||
        password = password.strip()
 | 
			
		||||
    if password:
 | 
			
		||||
        params['password_hash'] = generate_password_hash(password)
 | 
			
		||||
    del params['password']
 | 
			
		||||
 | 
			
		||||
    if 'password' in params:
 | 
			
		||||
        del params['password']
 | 
			
		||||
    database.db.session.bulk_update_mappings(database.User, [params])
 | 
			
		||||
    database.db.session.commit()
 | 
			
		||||
    user = database.db.session.get(database.User, user_id)
 | 
			
		||||
    return format_user(user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin_blueprint.post('/user')
 | 
			
		||||
def create_user():
 | 
			
		||||
    args: dict = request.json
 | 
			
		||||
    args['password_hash'] = generate_password_hash(args['password'])
 | 
			
		||||
    del args['password']
 | 
			
		||||
    new_user = database.User(**args)
 | 
			
		||||
    database.db.session.add(new_user)
 | 
			
		||||
    database.db.session.flush()
 | 
			
		||||
    database.db.session.commit()
 | 
			
		||||
    return format_user(new_user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin_blueprint.delete('/user/<int:user_id>')
 | 
			
		||||
def delete_user(user_id):
 | 
			
		||||
    user = database.db.session.get(database.User, user_id)
 | 
			
		||||
    if user:
 | 
			
		||||
        database.db.session.delete(user)
 | 
			
		||||
        database.db.session.commit()
 | 
			
		||||
    return '', 200
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin_blueprint.get('/city')
 | 
			
		||||
def get_cities():
 | 
			
		||||
    filters = request.args.get('filter')
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,8 @@ def upload(application_name: str):
 | 
			
		||||
        return {"error": "Invalid form data. There is no file or version field"}, 400
 | 
			
		||||
    version = version.strip()
 | 
			
		||||
    application = (database.Application.query.
 | 
			
		||||
                   filter_by(version=version).
 | 
			
		||||
                   filter_by(version=version,
 | 
			
		||||
                             name=application_name).
 | 
			
		||||
                   with_entities(database.Application.id).first())
 | 
			
		||||
    if application:
 | 
			
		||||
        return {"error": f"Specified version ({version}) already uploaded"}, 400
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,13 @@ import datetime
 | 
			
		||||
 | 
			
		||||
from flask import Blueprint, request, jsonify
 | 
			
		||||
from flask_jwt_extended import get_jwt_identity
 | 
			
		||||
from sqlalchemy.orm import joinedload
 | 
			
		||||
 | 
			
		||||
import database
 | 
			
		||||
from database.enums import AssemblyState
 | 
			
		||||
from routes.utils import jwt_protect_blueprint
 | 
			
		||||
import sipro.api.orders
 | 
			
		||||
import utils.balance
 | 
			
		||||
 | 
			
		||||
assembly_blueprint = jwt_protect_blueprint(Blueprint('assembly', __name__))
 | 
			
		||||
 | 
			
		||||
@@ -17,15 +19,33 @@ def create_assembly():
 | 
			
		||||
        data: dict = request.json
 | 
			
		||||
        order_id: int = data.get('orderId')
 | 
			
		||||
        user_id = get_jwt_identity()
 | 
			
		||||
        existing_assembly = database.Assembly.query.filter_by(order_id=order_id).first()
 | 
			
		||||
        existing_assembly = (database.Assembly.query.
 | 
			
		||||
                             options(joinedload(database.Assembly.user)).
 | 
			
		||||
                             filter_by(order_id=order_id).
 | 
			
		||||
                             first())
 | 
			
		||||
        if existing_assembly:
 | 
			
		||||
            response = {
 | 
			
		||||
                'ok': False,
 | 
			
		||||
                'message': 'Сборка этого товара уже была запущена',
 | 
			
		||||
                'assemblyId': existing_assembly.id,
 | 
			
		||||
                'statusCode': 'ASSEMBLY_ALREADY_EXISTS'
 | 
			
		||||
                'statusCode': 'ASSEMBLY_ALREADY_EXISTS',
 | 
			
		||||
                'userName': existing_assembly.user.login
 | 
			
		||||
            }
 | 
			
		||||
            return jsonify(response)
 | 
			
		||||
 | 
			
		||||
        need_crpt_for_order_response = sipro.api.orders.need_crpt_by_order_id(order_id)
 | 
			
		||||
        need_crpt_value = need_crpt_for_order_response.get('needCrpt')
 | 
			
		||||
        if need_crpt_value:
 | 
			
		||||
            valid_app = request.headers.get('CrptAvailable')
 | 
			
		||||
            if not valid_app:
 | 
			
		||||
                response = {
 | 
			
		||||
                    'ok': False,
 | 
			
		||||
                    'message': 'Для сборки этого заказа необходимо приложение с поддержкой Честного Знака',
 | 
			
		||||
                    'assemblyId': -1,
 | 
			
		||||
                    'statusCode': 'USER_ALREADY_HAS_ACTIVE_ASSEMBLY',
 | 
			
		||||
                }
 | 
			
		||||
                return jsonify(response)
 | 
			
		||||
 | 
			
		||||
        active_assembly = database.Assembly.query.filter(database.Assembly.user_id == user_id,
 | 
			
		||||
                                                         database.Assembly.is_active == True).first()
 | 
			
		||||
        if active_assembly:
 | 
			
		||||
@@ -36,6 +56,22 @@ def create_assembly():
 | 
			
		||||
                'statusCode': 'USER_ALREADY_HAS_ACTIVE_ASSEMBLY'
 | 
			
		||||
            }
 | 
			
		||||
            return jsonify(response)
 | 
			
		||||
        assembled_assembly = (
 | 
			
		||||
            database.Assembly.query
 | 
			
		||||
            .filter(
 | 
			
		||||
                database.Assembly.order_id == order_id,
 | 
			
		||||
                database.Assembly.ended_at != None
 | 
			
		||||
            )
 | 
			
		||||
            .first()
 | 
			
		||||
        )
 | 
			
		||||
        if assembled_assembly:
 | 
			
		||||
            response = {
 | 
			
		||||
                'ok': False,
 | 
			
		||||
                'message': 'Вы уже собирали этот товар',
 | 
			
		||||
                'assemblyId': assembled_assembly.id,
 | 
			
		||||
                'statusCode': 'USER_ALREADY_HAS_ACTIVE_ASSEMBLY',
 | 
			
		||||
            }
 | 
			
		||||
            return jsonify(response)
 | 
			
		||||
        assembly = database.Assembly(user_id=user_id,
 | 
			
		||||
                                     order_id=order_id,
 | 
			
		||||
                                     state=AssemblyState.NOT_STARTED,
 | 
			
		||||
@@ -60,10 +96,9 @@ def create_assembly():
 | 
			
		||||
        return jsonify(response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.post('/close')
 | 
			
		||||
def close_assembly():
 | 
			
		||||
    args = request.json
 | 
			
		||||
    assembly_id = args.get('assemblyId')
 | 
			
		||||
def close_assembly_by_id(
 | 
			
		||||
        assembly_id: int,
 | 
			
		||||
):
 | 
			
		||||
    if not assembly_id or (not isinstance(assembly_id, int)):
 | 
			
		||||
        response = {
 | 
			
		||||
            'ok': False,
 | 
			
		||||
@@ -81,7 +116,25 @@ def close_assembly():
 | 
			
		||||
    assembly.ended_at = datetime.datetime.now()
 | 
			
		||||
    database.db.session.commit()
 | 
			
		||||
    order_id = assembly.order_id
 | 
			
		||||
    return sipro.api.orders.close_order(order_id)
 | 
			
		||||
    sipro_response = sipro.api.orders.close_order(order_id)
 | 
			
		||||
    reward = sipro_response.get('reward')
 | 
			
		||||
    ok = sipro_response.get('ok')
 | 
			
		||||
    if ok:
 | 
			
		||||
        utils.balance.add_top_up(user_id=assembly.user_id,
 | 
			
		||||
                                 amount=reward,
 | 
			
		||||
                                 description=f'Начисление за сборку заказа {order_id}',
 | 
			
		||||
                                 json_data={'order_id': order_id},
 | 
			
		||||
                                 commit=True)
 | 
			
		||||
 | 
			
		||||
    return sipro_response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.post('/close')
 | 
			
		||||
def close_assembly():
 | 
			
		||||
    args = request.json
 | 
			
		||||
    assembly_id = args.get('assemblyId')
 | 
			
		||||
    sipro_response = close_assembly_by_id(int(assembly_id))
 | 
			
		||||
    return sipro_response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.post('/cancel')
 | 
			
		||||
@@ -96,6 +149,14 @@ def cancel_assembly():
 | 
			
		||||
                'message': 'У вас нет активных сборок'
 | 
			
		||||
            }
 | 
			
		||||
            return jsonify(response)
 | 
			
		||||
        order_id = assembly.order_id
 | 
			
		||||
        sipro_response = sipro.api.orders.cancel_order_assembly(order_id)
 | 
			
		||||
        if not sipro_response.get('ok'):
 | 
			
		||||
            response = {
 | 
			
		||||
                'ok': False,
 | 
			
		||||
                'message': f'Ошибка: {sipro_response.get("message")}'
 | 
			
		||||
            }
 | 
			
		||||
            return jsonify(response)
 | 
			
		||||
        database.db.session.delete(assembly)
 | 
			
		||||
        database.db.session.commit()
 | 
			
		||||
        response = {
 | 
			
		||||
@@ -111,6 +172,32 @@ def cancel_assembly():
 | 
			
		||||
        return jsonify(response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.post('/cancelById')
 | 
			
		||||
def cancel_assembly_by_id():
 | 
			
		||||
    try:
 | 
			
		||||
        assembly_id = request.json.get('assemblyId')
 | 
			
		||||
        assembly = database.db.session.get(database.Assembly, assembly_id)
 | 
			
		||||
        if not assembly:
 | 
			
		||||
            response = {
 | 
			
		||||
                'ok': False,
 | 
			
		||||
                'message': f'Сборка {assembly_id} не найдена'
 | 
			
		||||
            }
 | 
			
		||||
            return jsonify(response)
 | 
			
		||||
        database.db.session.delete(assembly)
 | 
			
		||||
        database.db.session.commit()
 | 
			
		||||
        response = {
 | 
			
		||||
            'ok': True,
 | 
			
		||||
            'message': f'Сборка {assembly_id} успешно отменена!'
 | 
			
		||||
        }
 | 
			
		||||
        return jsonify(response)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        response = {
 | 
			
		||||
            'ok': False,
 | 
			
		||||
            'message': f'Неудалось отменить сборку, ошибка: {e}'
 | 
			
		||||
        }
 | 
			
		||||
        return jsonify(response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.get('/hasActive')
 | 
			
		||||
def user_has_active_assembly():
 | 
			
		||||
    user_id = get_jwt_identity()
 | 
			
		||||
@@ -135,6 +222,26 @@ def get_active_assembly():
 | 
			
		||||
    return jsonify(response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.post('/confirmCurrent')
 | 
			
		||||
def confirm_current_assembly():
 | 
			
		||||
    # confirm current assembly for user
 | 
			
		||||
    user_id = get_jwt_identity()
 | 
			
		||||
    assembly = database.Assembly.query.filter(database.Assembly.user_id == user_id,
 | 
			
		||||
                                              database.Assembly.is_active == True).first()
 | 
			
		||||
    if not assembly:
 | 
			
		||||
        response = {
 | 
			
		||||
            'ok': False,
 | 
			
		||||
            'message': 'У вас нет активных сборок'
 | 
			
		||||
        }
 | 
			
		||||
        return jsonify(response)
 | 
			
		||||
    close_assembly_by_id(assembly.id)
 | 
			
		||||
    response = {
 | 
			
		||||
        'ok': True,
 | 
			
		||||
        'message': 'Сборка успешно завершена!',
 | 
			
		||||
    }
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.post('/confirm')
 | 
			
		||||
def confirm_assembly():
 | 
			
		||||
    user_id = get_jwt_identity()
 | 
			
		||||
@@ -179,3 +286,16 @@ def update_assembly_state():
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print('Error while updating')
 | 
			
		||||
        return jsonify(ok=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.get('/needCrpt')
 | 
			
		||||
def need_crpt():
 | 
			
		||||
    order_product_id = request.args.get('orderProductId')
 | 
			
		||||
    return sipro.api.orders.need_crpt(order_product_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@assembly_blueprint.post('/attachCrpt')
 | 
			
		||||
def attach_crpt():
 | 
			
		||||
    order_product_id = request.json.get('orderProductId')
 | 
			
		||||
    crpt = request.json.get('crpt')
 | 
			
		||||
    return sipro.api.orders.attach_crpt(order_product_id, crpt)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								routes/balance.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								routes/balance.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
from flask import Blueprint, request
 | 
			
		||||
from flask_jwt_extended import get_jwt_identity
 | 
			
		||||
from routes.utils import jwt_protect_blueprint
 | 
			
		||||
 | 
			
		||||
import queries.balance.api as api
 | 
			
		||||
 | 
			
		||||
balance_blueprint = jwt_protect_blueprint(Blueprint('balance', __name__))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@balance_blueprint.get('/transactions')
 | 
			
		||||
def get_transactions():
 | 
			
		||||
    data = dict(request.args)
 | 
			
		||||
    if 'user_id' not in data:
 | 
			
		||||
        data['user_id'] = get_jwt_identity()
 | 
			
		||||
    response = api.get_balance_transactions(dict(data))
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@balance_blueprint.get('/info')
 | 
			
		||||
def get_balance_info():
 | 
			
		||||
    data = {'user_id': get_jwt_identity()}
 | 
			
		||||
    response = api.get_balance_info(data)
 | 
			
		||||
    return response
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from flask import Blueprint, jsonify, request
 | 
			
		||||
from flask import Blueprint
 | 
			
		||||
from routes.utils import jwt_protect_blueprint
 | 
			
		||||
import sipro.api.general
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,10 +33,10 @@ def get_orders():
 | 
			
		||||
 | 
			
		||||
@orders_blueprint.get('/getByProductId')
 | 
			
		||||
def get_orders_by_supplier_product_id():
 | 
			
		||||
    #user_id = get_jwt_identity()
 | 
			
		||||
    #city_id = database.db.session.get(database.User, user_id).city_id
 | 
			
		||||
    user_id = get_jwt_identity()
 | 
			
		||||
    city_id = database.db.session.get(database.User, user_id).city_id
 | 
			
		||||
    params = dict(request.args)
 | 
			
		||||
    #params['city'] = city_id
 | 
			
		||||
    params['city'] = city_id
 | 
			
		||||
    return sipro.api.orders.get_orders_from_barcode(params=params)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
from flask import Blueprint, request, send_file
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from flask import Blueprint, request, send_file, jsonify
 | 
			
		||||
from routes.utils import jwt_protect_blueprint
 | 
			
		||||
import sipro.api.printing
 | 
			
		||||
 | 
			
		||||
@@ -9,9 +11,14 @@ printing_blueprint = jwt_protect_blueprint(Blueprint('printing', __name__))
 | 
			
		||||
def get_label():
 | 
			
		||||
    args = request.args
 | 
			
		||||
    order_id = args.get('orderId')
 | 
			
		||||
    data = sipro.api.printing.get_label(order_id)
 | 
			
		||||
    label_format = args.get('format', 'pdf')
 | 
			
		||||
    data = sipro.api.printing.get_label(order_id, label_format=label_format)
 | 
			
		||||
    data.seek(0)
 | 
			
		||||
    return send_file(data,
 | 
			
		||||
                     as_attachment=True,
 | 
			
		||||
                     download_name='label.pdf',
 | 
			
		||||
                     mimetype='application/pdf')
 | 
			
		||||
 | 
			
		||||
    if label_format == 'pdf':
 | 
			
		||||
        return send_file(data,
 | 
			
		||||
                         as_attachment=True,
 | 
			
		||||
                         download_name='label.pdf',
 | 
			
		||||
                         mimetype='application/pdf')
 | 
			
		||||
    data= json.loads(str(data.read(), 'ascii'))
 | 
			
		||||
    return jsonify(data)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										122
									
								
								routes/sipro.py
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								routes/sipro.py
									
									
									
									
									
								
							@@ -1,10 +1,11 @@
 | 
			
		||||
import datetime
 | 
			
		||||
from enum import StrEnum
 | 
			
		||||
 | 
			
		||||
from flask import Blueprint, request
 | 
			
		||||
from sqlalchemy import func
 | 
			
		||||
from flask import Blueprint, request, jsonify
 | 
			
		||||
from sqlalchemy import func, cast, String, Numeric, Float
 | 
			
		||||
 | 
			
		||||
import auxiliary
 | 
			
		||||
import database
 | 
			
		||||
import database.enums
 | 
			
		||||
 | 
			
		||||
sipro_blueprint = Blueprint('sipro', __name__)
 | 
			
		||||
 | 
			
		||||
@@ -51,3 +52,118 @@ def assembly_info():
 | 
			
		||||
    result = query.all()
 | 
			
		||||
    json_result = [auxiliary.to_nested_dict(row) for row in result]
 | 
			
		||||
    return json_result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sipro_blueprint.post('/tableAssemblyStats')
 | 
			
		||||
def table_assembly_stats():
 | 
			
		||||
    filters = request.json
 | 
			
		||||
    date_from = filters.get('from')
 | 
			
		||||
    date_to = filters.get('to')
 | 
			
		||||
 | 
			
		||||
    dated_query = (
 | 
			
		||||
        database.Assembly.query
 | 
			
		||||
        .join(database.User)
 | 
			
		||||
        .filter(
 | 
			
		||||
            database.User.is_admin == False
 | 
			
		||||
        )
 | 
			
		||||
        .group_by(database.User.login, database.Assembly.user_id)
 | 
			
		||||
        .with_entities(
 | 
			
		||||
            database.User.login.label('user_login'),
 | 
			
		||||
            func.count(database.Assembly.user_id).label('assembled'),
 | 
			
		||||
            func.string_agg(func.cast(database.Assembly.order_id, database.db.String), ',').label('order_ids')
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if len(date_from) > 0 and len(date_to) > 0:
 | 
			
		||||
        dated_query = dated_query.filter(
 | 
			
		||||
            database.Assembly.ended_at >= date_from,
 | 
			
		||||
            database.Assembly.ended_at <= date_to
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    json_result = []
 | 
			
		||||
    for row in dated_query.all():
 | 
			
		||||
        json_result.append({
 | 
			
		||||
            'user_login': row.user_login,
 | 
			
		||||
            'assembled': row.assembled,
 | 
			
		||||
            'order_ids': row.order_ids
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    return jsonify(json_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sipro_blueprint.get('/dailyAssemblyData')
 | 
			
		||||
def daily_assembly_stats():
 | 
			
		||||
    query = (
 | 
			
		||||
        database.Assembly.query
 | 
			
		||||
        .join(database.User)
 | 
			
		||||
        .filter(
 | 
			
		||||
            database.Assembly.ended_at >= func.current_date()
 | 
			
		||||
        )
 | 
			
		||||
        .group_by(database.User.login, database.Assembly.user_id)
 | 
			
		||||
        .with_entities(
 | 
			
		||||
            database.User.login.label('user_login'),
 | 
			
		||||
            func.count(database.Assembly.user_id).label('assembled'),
 | 
			
		||||
            func.string_agg(func.cast(database.Assembly.order_id, database.db.String), ',').label('order_ids')
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    json_result = []
 | 
			
		||||
    for row in query.all():
 | 
			
		||||
        json_result.append({
 | 
			
		||||
            'user_login': row.user_login,
 | 
			
		||||
            'assembled': row.assembled,
 | 
			
		||||
            'order_ids': row.order_ids
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    return json_result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sipro_blueprint.get('/users')
 | 
			
		||||
def get_users():
 | 
			
		||||
    query = (database.User.query.
 | 
			
		||||
             with_entities(database.User.id,
 | 
			
		||||
                           database.User.login)
 | 
			
		||||
             .all())
 | 
			
		||||
    response = [{'id': user.id, 'login': user.login} for user in query]
 | 
			
		||||
    return jsonify(response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sipro_blueprint.get('/statistics')
 | 
			
		||||
def get_users_statistics():
 | 
			
		||||
    data: dict = request.args
 | 
			
		||||
    user_id = int(data['userId'])
 | 
			
		||||
    date_from = datetime.datetime.fromisoformat(data['dateFrom'])
 | 
			
		||||
    date_to = datetime.datetime.fromisoformat(data['dateTo']) + datetime.timedelta(hours=24, minutes=59, seconds=59)
 | 
			
		||||
    query = (database.BalanceTransaction.query
 | 
			
		||||
             .filter(database.BalanceTransaction.user_id == user_id,
 | 
			
		||||
                     database.BalanceTransaction.created_at.between(date_from, date_to))
 | 
			
		||||
             .order_by(func.date_trunc('day', database.BalanceTransaction.created_at))
 | 
			
		||||
             .group_by(func.date_trunc('day', database.BalanceTransaction.created_at))
 | 
			
		||||
             .with_entities(func.date_trunc('day', database.BalanceTransaction.created_at).label('date'),
 | 
			
		||||
                            func.cast(func.round(func.cast(func.sum(database.BalanceTransaction.amount), Numeric), 2),
 | 
			
		||||
                                      Float).label(
 | 
			
		||||
                                'value'),
 | 
			
		||||
                            func.string_agg(cast(database.BalanceTransaction.json_data['order_id'], String), ',').label(
 | 
			
		||||
                                'order_ids'))
 | 
			
		||||
             .all())
 | 
			
		||||
    result = [{'date': row.date.isoformat(), 'value': row.value, 'order_ids': row.order_ids} for row in query]
 | 
			
		||||
    return jsonify(result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sipro_blueprint.get('/get-orders-by-date')
 | 
			
		||||
def get_orders_by_date():
 | 
			
		||||
    data: dict = request.args
 | 
			
		||||
    date = datetime.date.fromisoformat(data['date'])
 | 
			
		||||
    query = (
 | 
			
		||||
        database.Assembly.query
 | 
			
		||||
        .filter(
 | 
			
		||||
            func.date_trunc('day', database.Assembly.ended_at) == func.date_trunc('day', date),
 | 
			
		||||
        )
 | 
			
		||||
        .with_entities(
 | 
			
		||||
            func.string_agg(func.cast(database.Assembly.order_id, database.db.String), ',').label('order_ids')
 | 
			
		||||
        )
 | 
			
		||||
        .scalar()
 | 
			
		||||
    )
 | 
			
		||||
    if not query:
 | 
			
		||||
        return jsonify({'order_ids': []})
 | 
			
		||||
    return jsonify({'order_ids': list(map(int, query.split(',')))})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
from enum import unique, IntEnum
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@unique
 | 
			
		||||
class CodeType(IntEnum):
 | 
			
		||||
    BARCODE = 0
 | 
			
		||||
    QRCODE = 1
 | 
			
		||||
    INVALID = 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@unique
 | 
			
		||||
class SearchType(IntEnum):
 | 
			
		||||
    PRODUCT = 0
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
from scanner.enums import CodeType
 | 
			
		||||
from scanner.utils import guess_code_type
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ScannerSearch:
 | 
			
		||||
    def __init__(self, string_value: str):
 | 
			
		||||
        self.string_value = string_value
 | 
			
		||||
        self._code_type = guess_code_type(self.string_value)
 | 
			
		||||
 | 
			
		||||
    def get_code_type(self) -> CodeType:
 | 
			
		||||
        return self._code_type
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
from scanner.enums import CodeType
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def guess_code_type(string_value: str) -> CodeType:
 | 
			
		||||
    if string_value.isdigit():
 | 
			
		||||
        return CodeType.BARCODE
 | 
			
		||||
    if string_value:
 | 
			
		||||
        return CodeType.QRCODE
 | 
			
		||||
    return CodeType.INVALID
 | 
			
		||||
							
								
								
									
										0
									
								
								schemas/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								schemas/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										20
									
								
								schemas/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								schemas/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
from pydantic import BaseModel
 | 
			
		||||
from pydantic.alias_generators import to_camel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommonConfig:
 | 
			
		||||
    alias_generator = to_camel
 | 
			
		||||
    populate_by_name = True
 | 
			
		||||
    from_attributes = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommonModel(BaseModel):
 | 
			
		||||
    class Config(CommonConfig):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def json_serialize(cls, obj):
 | 
			
		||||
        return cls.model_validate(obj).model_dump(by_alias=True)
 | 
			
		||||
 | 
			
		||||
    def to_dict(self, by_alias=True):
 | 
			
		||||
        return self.model_dump(by_alias=by_alias)
 | 
			
		||||
@@ -40,3 +40,29 @@ def get_orders_from_barcode(params: dict):
 | 
			
		||||
    method = f'{router}/getByProductId'
 | 
			
		||||
    response = client.method('GET', method, params=params)
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def need_crpt(order_product_id):
 | 
			
		||||
    method = f'{router}/needCrpt?orderProductId={order_product_id}'
 | 
			
		||||
    response = client.method('GET', method)
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def attach_crpt(order_product_id, crpt):
 | 
			
		||||
    method = f'{router}/attachCrpt'
 | 
			
		||||
    data = {'orderProductId': order_product_id, 'crpt': crpt}
 | 
			
		||||
    response = client.method('POST', method, data=data)
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def need_crpt_by_order_id(order_id):
 | 
			
		||||
    method = f'{router}/needCrptByOrder?orderId={order_id}'
 | 
			
		||||
    response = client.method('GET', method)
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def cancel_order_assembly(order_id):
 | 
			
		||||
    method = f'{router}/cancelOrderAssembly'
 | 
			
		||||
    data = {'orderId': order_id}
 | 
			
		||||
    response = client.method('POST', method, data=data)
 | 
			
		||||
    return response
 | 
			
		||||
@@ -9,8 +9,8 @@ client = get_client()
 | 
			
		||||
router = '/printing'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_label(order_product_id: str) -> BytesIO:
 | 
			
		||||
    method = f'{router}/getLabel?orderId={order_product_id}'
 | 
			
		||||
def get_label(order_product_id: str, label_format:str='pdf') -> BytesIO:
 | 
			
		||||
    method = f'{router}/getLabel?orderId={order_product_id}&format={label_format}'
 | 
			
		||||
    response: Response = client.method('GET', method, raw=True)
 | 
			
		||||
    data = BytesIO(response.content)
 | 
			
		||||
    return data
 | 
			
		||||
							
								
								
									
										23
									
								
								utils/balance.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								utils/balance.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
import database
 | 
			
		||||
 | 
			
		||||
import database.enums
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_top_up(user_id: int,
 | 
			
		||||
               amount: float,
 | 
			
		||||
               description: str,
 | 
			
		||||
               json_data: dict,
 | 
			
		||||
               commit=False):
 | 
			
		||||
    transaction = database.BalanceTransaction(
 | 
			
		||||
        user_id=user_id,
 | 
			
		||||
        type=database.enums.BalanceTransactionType.TOP_UP,
 | 
			
		||||
        amount=amount,
 | 
			
		||||
        description=description,
 | 
			
		||||
        json_data=json_data,
 | 
			
		||||
        created_at=datetime.datetime.now()
 | 
			
		||||
    )
 | 
			
		||||
    database.db.session.add(transaction)
 | 
			
		||||
    if commit:
 | 
			
		||||
        database.db.session.commit()
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
import database
 | 
			
		||||
from database.mariadb import MariadbConnector
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodesSynchronizer:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.mariadb_connector = MariadbConnector()
 | 
			
		||||
 | 
			
		||||
    def synchronize(self):
 | 
			
		||||
        existing_barcodes = database.Barcode.query.all()
 | 
			
		||||
        denco_articles = list(set([barcode.denco_article for barcode in existing_barcodes]))
 | 
			
		||||
 | 
			
		||||
        query_string = 'SELECT product, barcode FROM modx_0_connections WHERE barcode != "" LIMIT 100'
 | 
			
		||||
        for denco_article, barcodes_string in self.mariadb_connector.select(query_string):
 | 
			
		||||
            barcodes = barcodes_string.split(',')
 | 
			
		||||
            print(denco_article, barcodes)
 | 
			
		||||
		Reference in New Issue
	
	Block a user