Compare commits
	
		
			54 Commits
		
	
	
		
	
	| 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 | |||
| 40abf70430 | |||
| 430cfad277 | |||
| f1edc1b0b4 | |||
| 8e0cddab4c | |||
| 75a71953f1 | |||
| 448ba351a7 | |||
| 474b9caf22 | |||
| 6683576a70 | |||
| 98a9a2cd61 | |||
| a5b6c572d2 | |||
| c5cd5ded3a | |||
| 4f325898f9 | |||
| 82c67434e2 | |||
| f0fce37a0c | |||
| 52f54d086f | |||
| 07735b5e2c | |||
| b5a3b3f1d1 | |||
| 3468d5e640 | |||
| 0b7a69c691 | |||
| 560a0248a5 | |||
| 7f567954fa | |||
| 21313f053b | |||
| 57bb4fd5e4 | |||
| cba9d37cdb | |||
| cc2d364059 | |||
| 85fc486e9a | |||
| f0b248eb6e | |||
| 9fe15529a8 | |||
| b7a4df1e30 | |||
| 4d98d631f7 | |||
| 1d2ee676ff | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -5,3 +5,6 @@ __pycache__/
 | 
				
			|||||||
.git/
 | 
					.git/
 | 
				
			||||||
.env
 | 
					.env
 | 
				
			||||||
test.*
 | 
					test.*
 | 
				
			||||||
 | 
					test/
 | 
				
			||||||
 | 
					apks/
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
							
								
								
									
										13
									
								
								app.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								app.py
									
									
									
									
									
								
							@@ -20,10 +20,19 @@ migrate = Migrate(app, database.db)
 | 
				
			|||||||
server_session = Session(app)
 | 
					server_session = Session(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# CORS config
 | 
					# CORS config
 | 
				
			||||||
CORS(app, supports_credentials=True)
 | 
					CORS(app, expose_headers=["Content-Range", 'Authorization'], supports_credentials=True)
 | 
				
			||||||
jwt = JWTManager(app)
 | 
					jwt = JWTManager(app)
 | 
				
			||||||
blueprints = [
 | 
					blueprints = [
 | 
				
			||||||
    (routes.auth_blueprint, '/auth')
 | 
					    (routes.auth_blueprint, '/auth'),
 | 
				
			||||||
 | 
					    (routes.orders_blueprint, '/orders'),
 | 
				
			||||||
 | 
					    (routes.barcode_blueprint, '/barcode'),
 | 
				
			||||||
 | 
					    (routes.printing_blueprint, '/printing'),
 | 
				
			||||||
 | 
					    (routes.assembly_blueprint, '/assembly'),
 | 
				
			||||||
 | 
					    (routes.general_blueprint, '/general'),
 | 
				
			||||||
 | 
					    (routes.application_blueprint, '/application'),
 | 
				
			||||||
 | 
					    (routes.sipro_blueprint, '/sipro'),
 | 
				
			||||||
 | 
					    (routes.admin_blueprint, '/admin'),
 | 
				
			||||||
 | 
					    (routes.balance_blueprint, '/balance'),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for blueprint, url_prefix in blueprints:
 | 
					for blueprint, url_prefix in blueprints:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								auxiliary.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								auxiliary.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					def to_nested_dict(row):
 | 
				
			||||||
 | 
					    result = {}
 | 
				
			||||||
 | 
					    for key, value in row._mapping.items():
 | 
				
			||||||
 | 
					        keys = key.split('.')
 | 
				
			||||||
 | 
					        current_level = result
 | 
				
			||||||
 | 
					        for part in keys[:-1]:
 | 
				
			||||||
 | 
					            if part not in current_level:
 | 
				
			||||||
 | 
					                current_level[part] = {}
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
@@ -17,5 +17,5 @@ class FlaskConfig:
 | 
				
			|||||||
    SESSION_PERMANENT = False
 | 
					    SESSION_PERMANENT = False
 | 
				
			||||||
    SESSION_USE_SIGNER = True
 | 
					    SESSION_USE_SIGNER = True
 | 
				
			||||||
    SESSION_REDIS = redis.from_url('redis://127.0.0.1:6379')
 | 
					    SESSION_REDIS = redis.from_url('redis://127.0.0.1:6379')
 | 
				
			||||||
 | 
					    JWT_ACCESS_TOKEN_EXPIRES = 86400
 | 
				
			||||||
    # SQLALCHEMY_ECHO = True
 | 
					    # SQLALCHEMY_ECHO = True
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								constants.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					APP_PATH = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LOGGER_NAME = 'assemblr'
 | 
				
			||||||
 | 
					LOG_FILE = Path(APP_PATH) / Path(f'{LOGGER_NAME}.log')
 | 
				
			||||||
 | 
					MAX_LOG_FILE_SIZE_BYTES = 400 * 1024 ** 2
 | 
				
			||||||
 | 
					APKS_PATH = os.path.join(APP_PATH, 'apks')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if not os.path.isdir(APKS_PATH):
 | 
				
			||||||
 | 
					    os.mkdir(APKS_PATH)
 | 
				
			||||||
@@ -1 +1,4 @@
 | 
				
			|||||||
from database.models import *
 | 
					from flask_sqlalchemy import SQLAlchemy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					db = SQLAlchemy()
 | 
				
			||||||
 | 
					from .models import *
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								database/enums.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								database/enums.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					from enum import unique, IntEnum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@unique
 | 
				
			||||||
 | 
					class AssemblyState(IntEnum):
 | 
				
			||||||
 | 
					    NOT_STARTED = 0,
 | 
				
			||||||
 | 
					    ASSEMBLING_PRODUCTS = 1,
 | 
				
			||||||
 | 
					    ALL_PRODUCTS_ASSEMBLED = 2,
 | 
				
			||||||
 | 
					    CONFIRMED = 3,
 | 
				
			||||||
 | 
					    ENDED = 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@unique
 | 
				
			||||||
 | 
					class BalanceTransactionType(IntEnum):
 | 
				
			||||||
 | 
					    TOP_UP = 0
 | 
				
			||||||
 | 
					    WITHDRAW = 1
 | 
				
			||||||
@@ -1,20 +0,0 @@
 | 
				
			|||||||
from flask_sqlalchemy import SQLAlchemy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
db = SQLAlchemy()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User(db.Model):
 | 
					 | 
				
			||||||
    __tablename__ = 'users'
 | 
					 | 
				
			||||||
    id = db.Column(db.Integer, primary_key=True, comment='ID пользователя')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    login = db.Column(db.String, nullable=False, comment='Логин')
 | 
					 | 
				
			||||||
    password_hash = db.Column(db.String, nullable=False, comment='Пароль')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sipro_id = db.Column(db.Integer, nullable=True, comment='ID пользователя в SIPRO')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Barcode(db.Model):
 | 
					 | 
				
			||||||
    __tablename__ = 'barcodes'
 | 
					 | 
				
			||||||
    id = db.Column(db.Integer, primary_key=True, comment='ID пользователя')
 | 
					 | 
				
			||||||
    denco_article = db.Column(db.Integer, nullable=False, comment='Артикул', index=True)
 | 
					 | 
				
			||||||
    barcode = db.Column(db.String, nullable=False, comment='Баркод', index=True)
 | 
					 | 
				
			||||||
							
								
								
									
										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)
 | 
				
			||||||
							
								
								
									
										46
									
								
								database/models/basic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								database/models/basic.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					from database import db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class User(db.Model):
 | 
				
			||||||
 | 
					    __tablename__ = 'users'
 | 
				
			||||||
 | 
					    id = db.Column(db.Integer, primary_key=True, comment='ID пользователя')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    login = db.Column(db.String, nullable=False, comment='Логин')
 | 
				
			||||||
 | 
					    password_hash = db.Column(db.String, nullable=False, comment='Пароль')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sipro_id = db.Column(db.Integer, nullable=True, comment='ID пользователя в SIPRO')
 | 
				
			||||||
 | 
					    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):
 | 
				
			||||||
 | 
					    __tablename__ = 'assemblies'
 | 
				
			||||||
 | 
					    id = db.Column(db.Integer, primary_key=True, comment='ID сборки')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    created_at = db.Column(db.DateTime, nullable=True, comment='Дата и время начала сборки')
 | 
				
			||||||
 | 
					    ended_at = db.Column(db.DateTime, nullable=True, comment='Дата и время конца сборки')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 | 
				
			||||||
 | 
					    user = db.relationship('User', backref='assemblies')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    order_id = db.Column(db.Integer, nullable=False, comment='ID заказа в базе данных', index=True)
 | 
				
			||||||
 | 
					    is_active = db.Column(db.Boolean, nullable=False, comment='Активная ли сборка')
 | 
				
			||||||
 | 
					    state = db.Column(db.Integer, nullable=False, comment='Состояние сборки')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Barcode(db.Model):
 | 
				
			||||||
 | 
					    __tablename__ = 'barcodes'
 | 
				
			||||||
 | 
					    id = db.Column(db.Integer, primary_key=True, comment='ID пользователя')
 | 
				
			||||||
 | 
					    denco_article = db.Column(db.Integer, nullable=False, comment='Артикул', index=True)
 | 
				
			||||||
 | 
					    barcode = db.Column(db.String, nullable=False, comment='Баркод', index=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Application(db.Model):
 | 
				
			||||||
 | 
					    __tablename__ = 'applications'
 | 
				
			||||||
 | 
					    id = db.Column(db.Integer, primary_key=True, comment='')
 | 
				
			||||||
 | 
					    name = db.Column(db.String(40), nullable=False)
 | 
				
			||||||
 | 
					    version = db.Column(db.String(10), nullable=False)
 | 
				
			||||||
 | 
					    uploaded = db.Column(db.DateTime, nullable=False)
 | 
				
			||||||
 | 
					    filename = db.Column(db.String, nullable=False)
 | 
				
			||||||
							
								
								
									
										34
									
								
								logger.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								logger.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					from logging.handlers import RotatingFileHandler
 | 
				
			||||||
 | 
					from singleton import Singleton
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import constants
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Logger(metaclass=Singleton):
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.logger = logging.getLogger(constants.LOGGER_NAME)
 | 
				
			||||||
 | 
					        self.logger.setLevel(logging.DEBUG)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        file_handler = RotatingFileHandler(constants.LOG_FILE,
 | 
				
			||||||
 | 
					                                           maxBytes=constants.MAX_LOG_FILE_SIZE_BYTES,
 | 
				
			||||||
 | 
					                                           encoding='UTF-8',
 | 
				
			||||||
 | 
					                                           backupCount=1)
 | 
				
			||||||
 | 
					        file_handler.setLevel(logging.DEBUG)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console_handler = logging.StreamHandler()
 | 
				
			||||||
 | 
					        console_handler.setLevel(logging.INFO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formatter = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
 | 
				
			||||||
 | 
					                                      datefmt='%m-%d %H:%M')
 | 
				
			||||||
 | 
					        file_handler.setFormatter(formatter)
 | 
				
			||||||
 | 
					        console_handler.setFormatter(formatter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.logger.addHandler(file_handler)
 | 
				
			||||||
 | 
					        self.logger.addHandler(console_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_logger(self):
 | 
				
			||||||
 | 
					        return self.logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger_instance = Logger().get_logger()
 | 
				
			||||||
							
								
								
									
										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
 | 
				
			||||||
@@ -4,6 +4,7 @@ flask-session
 | 
				
			|||||||
flask-cors
 | 
					flask-cors
 | 
				
			||||||
flask-migrate
 | 
					flask-migrate
 | 
				
			||||||
Flask-JWT-Extended
 | 
					Flask-JWT-Extended
 | 
				
			||||||
 | 
					gunicorn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Working with database
 | 
					# Working with database
 | 
				
			||||||
redis
 | 
					redis
 | 
				
			||||||
@@ -13,4 +14,5 @@ mariadb
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Other stuff
 | 
					# Other stuff
 | 
				
			||||||
requests
 | 
					requests
 | 
				
			||||||
python-dotenv
 | 
					python-dotenv
 | 
				
			||||||
 | 
					pydantic
 | 
				
			||||||
@@ -1 +1,10 @@
 | 
				
			|||||||
from routes.auth import auth_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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										114
									
								
								routes/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								routes/admin.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
				
			|||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import Blueprint, request, make_response, jsonify
 | 
				
			||||||
 | 
					from flask_jwt_extended import get_jwt_identity, verify_jwt_in_request
 | 
				
			||||||
 | 
					from werkzeug.security import generate_password_hash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import database
 | 
				
			||||||
 | 
					import sipro.api.general
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					admin_blueprint = Blueprint('admin', __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin_blueprint.before_request
 | 
				
			||||||
 | 
					def admin_check():
 | 
				
			||||||
 | 
					    if request.method == 'OPTIONS':
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    if not verify_jwt_in_request(optional=True):
 | 
				
			||||||
 | 
					        return {'error': 'Unauthorized'}, 401
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user_id = get_jwt_identity()
 | 
				
			||||||
 | 
					    is_admin = database.db.session.get(database.User, user_id).is_admin
 | 
				
			||||||
 | 
					    if not is_admin:
 | 
				
			||||||
 | 
					        return {'error': 'Unauthorized'}, 401
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def format_user(user: database.User):
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        'id': user.id,
 | 
				
			||||||
 | 
					        'login': user.login,
 | 
				
			||||||
 | 
					        'city_id': user.city_id,
 | 
				
			||||||
 | 
					        'is_admin': user.is_admin
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin_blueprint.get('/ping')
 | 
				
			||||||
 | 
					def ping():
 | 
				
			||||||
 | 
					    return {"response": "pong"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin_blueprint.get('/user')
 | 
				
			||||||
 | 
					def get_users():
 | 
				
			||||||
 | 
					    response = make_response(jsonify(
 | 
				
			||||||
 | 
					        [format_user(user) for user in database.User.query.all()]
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    response.headers['Content-Range'] = 'user 0-1/1'
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin_blueprint.get('/user/<int:user_id>')
 | 
				
			||||||
 | 
					def get_user(user_id):
 | 
				
			||||||
 | 
					    user = database.db.session.get(database.User, user_id)
 | 
				
			||||||
 | 
					    return format_user(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin_blueprint.put('/user/<int:user_id>')
 | 
				
			||||||
 | 
					def put_user(user_id):
 | 
				
			||||||
 | 
					    params: dict = request.json
 | 
				
			||||||
 | 
					    password = params.get('password')
 | 
				
			||||||
 | 
					    if password:
 | 
				
			||||||
 | 
					        password = password.strip()
 | 
				
			||||||
 | 
					    if password:
 | 
				
			||||||
 | 
					        params['password_hash'] = generate_password_hash(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')
 | 
				
			||||||
 | 
					    if filters:
 | 
				
			||||||
 | 
					        filters = json.loads(filters)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        filters = {}
 | 
				
			||||||
 | 
					    cities = sipro.api.general.get_cities()
 | 
				
			||||||
 | 
					    for key, value in filters.items():
 | 
				
			||||||
 | 
					        match key:
 | 
				
			||||||
 | 
					            case 'id':
 | 
				
			||||||
 | 
					                cities = list(filter(lambda city: city['id'] in value, cities))
 | 
				
			||||||
 | 
					    total_cities = len(cities)
 | 
				
			||||||
 | 
					    range_start, range_end = 0, total_cities - 1
 | 
				
			||||||
 | 
					    range_raw = request.args.get('range')
 | 
				
			||||||
 | 
					    if range_raw:
 | 
				
			||||||
 | 
					        range_start, range_end = json.loads(range_raw)
 | 
				
			||||||
 | 
					        cities = cities[range_start:range_end + 1]
 | 
				
			||||||
 | 
					    response = make_response(jsonify(cities))
 | 
				
			||||||
 | 
					    response.headers['Content-Range'] = f'city {range_start}-{range_end}/{total_cities}'
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
							
								
								
									
										71
									
								
								routes/application.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								routes/application.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import os.path
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import Blueprint, request, send_file
 | 
				
			||||||
 | 
					from flask_jwt_extended import verify_jwt_in_request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import database
 | 
				
			||||||
 | 
					from constants import APKS_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					application_blueprint = Blueprint('application', __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@application_blueprint.before_request
 | 
				
			||||||
 | 
					def auth():
 | 
				
			||||||
 | 
					    API_KEY = 'AF9A20DD9264C134CDA0ADACED834368'
 | 
				
			||||||
 | 
					    if request.headers.get('Authorization') != API_KEY and (not verify_jwt_in_request(optional=True)):
 | 
				
			||||||
 | 
					        return {'error': 'Unauthorized'}, 401
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@application_blueprint.get('<string:application_name>/download/<string:version>')
 | 
				
			||||||
 | 
					def download_version(application_name: str, version: str):
 | 
				
			||||||
 | 
					    application = database.Application.query.filter_by(version=version,
 | 
				
			||||||
 | 
					                                                       name=application_name).first()
 | 
				
			||||||
 | 
					    if not application:
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            "error": f"Application version '{version}' not found."
 | 
				
			||||||
 | 
					        }, 404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file_path = os.path.join(APKS_PATH, application.filename)
 | 
				
			||||||
 | 
					    version_safe = version.replace('.', '_')
 | 
				
			||||||
 | 
					    print(file_path)
 | 
				
			||||||
 | 
					    return send_file(file_path,
 | 
				
			||||||
 | 
					                     download_name=f'{application_name}_{version_safe}.apk',
 | 
				
			||||||
 | 
					                     as_attachment=True,
 | 
				
			||||||
 | 
					                     mimetype='application/vnd.android.package-archive')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@application_blueprint.get('<string:application_name>/version')
 | 
				
			||||||
 | 
					def get_version(application_name: str):
 | 
				
			||||||
 | 
					    version = (database.Application.query.
 | 
				
			||||||
 | 
					               filter_by(name=application_name).
 | 
				
			||||||
 | 
					               order_by(database.Application.uploaded.desc()).
 | 
				
			||||||
 | 
					               with_entities(database.Application.version).
 | 
				
			||||||
 | 
					               limit(1).
 | 
				
			||||||
 | 
					               scalar())
 | 
				
			||||||
 | 
					    return {'latest_version': version}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@application_blueprint.post('<string:application_name>/upload')
 | 
				
			||||||
 | 
					def upload(application_name: str):
 | 
				
			||||||
 | 
					    file = request.files.get('file')
 | 
				
			||||||
 | 
					    version = request.form.get('version')
 | 
				
			||||||
 | 
					    if (not file) or (not version):
 | 
				
			||||||
 | 
					        return {"error": "Invalid form data. There is no file or version field"}, 400
 | 
				
			||||||
 | 
					    version = version.strip()
 | 
				
			||||||
 | 
					    application = (database.Application.query.
 | 
				
			||||||
 | 
					                   filter_by(version=version,
 | 
				
			||||||
 | 
					                             name=application_name).
 | 
				
			||||||
 | 
					                   with_entities(database.Application.id).first())
 | 
				
			||||||
 | 
					    if application:
 | 
				
			||||||
 | 
					        return {"error": f"Specified version ({version}) already uploaded"}, 400
 | 
				
			||||||
 | 
					    filename = uuid.uuid4().hex + '.apk'
 | 
				
			||||||
 | 
					    file.save(os.path.join(APKS_PATH, filename))
 | 
				
			||||||
 | 
					    application = database.Application(name=application_name,
 | 
				
			||||||
 | 
					                                       version=version,
 | 
				
			||||||
 | 
					                                       uploaded=datetime.datetime.now(),
 | 
				
			||||||
 | 
					                                       filename=filename)
 | 
				
			||||||
 | 
					    database.db.session.add(application)
 | 
				
			||||||
 | 
					    database.db.session.commit()
 | 
				
			||||||
 | 
					    return {'filename': filename, 'version': version}
 | 
				
			||||||
							
								
								
									
										301
									
								
								routes/assembly.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								routes/assembly.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,301 @@
 | 
				
			|||||||
 | 
					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__))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@assembly_blueprint.post('/create')
 | 
				
			||||||
 | 
					def create_assembly():
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        data: dict = request.json
 | 
				
			||||||
 | 
					        order_id: int = data.get('orderId')
 | 
				
			||||||
 | 
					        user_id = get_jwt_identity()
 | 
				
			||||||
 | 
					        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',
 | 
				
			||||||
 | 
					                '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:
 | 
				
			||||||
 | 
					            response = {
 | 
				
			||||||
 | 
					                'ok': False,
 | 
				
			||||||
 | 
					                'message': 'Вы не можете запустить сборку заказа, так как у вас уже есть активная сборка',
 | 
				
			||||||
 | 
					                'assemblyId': active_assembly.id,
 | 
				
			||||||
 | 
					                '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,
 | 
				
			||||||
 | 
					                                     created_at=datetime.datetime.now(),
 | 
				
			||||||
 | 
					                                     is_active=True)
 | 
				
			||||||
 | 
					        database.db.session.add(assembly)
 | 
				
			||||||
 | 
					        database.db.session.commit()
 | 
				
			||||||
 | 
					        response = {
 | 
				
			||||||
 | 
					            'ok': True,
 | 
				
			||||||
 | 
					            'message': 'Сборка успешно запущена!',
 | 
				
			||||||
 | 
					            'assemblyId': assembly.id,
 | 
				
			||||||
 | 
					            'statusCode': 'CREATED'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        response = {
 | 
				
			||||||
 | 
					            'ok': False,
 | 
				
			||||||
 | 
					            'message': f'Неизвестная ошибка: {e}',
 | 
				
			||||||
 | 
					            'assemblyId': -1,
 | 
				
			||||||
 | 
					            'statusCode': 'INVALID_EXCEPTION'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def close_assembly_by_id(
 | 
				
			||||||
 | 
					        assembly_id: int,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    if not assembly_id or (not isinstance(assembly_id, int)):
 | 
				
			||||||
 | 
					        response = {
 | 
				
			||||||
 | 
					            'ok': False,
 | 
				
			||||||
 | 
					            'message': 'Неверно указан ID сборки'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response)
 | 
				
			||||||
 | 
					    assembly = database.db.session.get(database.Assembly, assembly_id)
 | 
				
			||||||
 | 
					    if not assembly:
 | 
				
			||||||
 | 
					        response = {
 | 
				
			||||||
 | 
					            'ok': False,
 | 
				
			||||||
 | 
					            'message': 'Указанная сборка не найдена'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response)
 | 
				
			||||||
 | 
					    assembly.is_active = False
 | 
				
			||||||
 | 
					    assembly.ended_at = datetime.datetime.now()
 | 
				
			||||||
 | 
					    database.db.session.commit()
 | 
				
			||||||
 | 
					    order_id = assembly.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')
 | 
				
			||||||
 | 
					def cancel_assembly():
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					        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 = {
 | 
				
			||||||
 | 
					            'ok': True,
 | 
				
			||||||
 | 
					            'message': 'Сборка успешно отменена!'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        response = {
 | 
				
			||||||
 | 
					            'ok': False,
 | 
				
			||||||
 | 
					            'message': f'Неизвестная ошибка: {e}'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        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()
 | 
				
			||||||
 | 
					    assemblies_count = database.Assembly.query.filter(database.Assembly.user_id == user_id,
 | 
				
			||||||
 | 
					                                                      database.Assembly.is_active == True).count()
 | 
				
			||||||
 | 
					    return jsonify(has=assemblies_count > 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@assembly_blueprint.get('/getActive')
 | 
				
			||||||
 | 
					def get_active_assembly():
 | 
				
			||||||
 | 
					    user_id = get_jwt_identity()
 | 
				
			||||||
 | 
					    assembly = database.Assembly.query.filter(database.Assembly.user_id == user_id,
 | 
				
			||||||
 | 
					                                              database.Assembly.is_active == True).first()
 | 
				
			||||||
 | 
					    response = {
 | 
				
			||||||
 | 
					        'databaseId': assembly.id,
 | 
				
			||||||
 | 
					        'createdAt': str(assembly.created_at),
 | 
				
			||||||
 | 
					        'endedAt': str(assembly.ended_at),
 | 
				
			||||||
 | 
					        'orderId': assembly.order_id,
 | 
				
			||||||
 | 
					        'isActive': assembly.is_active,
 | 
				
			||||||
 | 
					        'state': assembly.state
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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()
 | 
				
			||||||
 | 
					    args: dict = request.json
 | 
				
			||||||
 | 
					    assembly_id = args.get('assemblyId')
 | 
				
			||||||
 | 
					    if not assembly_id or (not isinstance(assembly_id, int)):
 | 
				
			||||||
 | 
					        response = {
 | 
				
			||||||
 | 
					            'ok': False,
 | 
				
			||||||
 | 
					            'message': 'ID сборки указан неверно'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response)
 | 
				
			||||||
 | 
					    assembly = database.db.session.get(database.Assembly, assembly_id)
 | 
				
			||||||
 | 
					    if not assembly:
 | 
				
			||||||
 | 
					        response = {
 | 
				
			||||||
 | 
					            'ok': False,
 | 
				
			||||||
 | 
					            'message': 'Неудалось найти указанную сборку'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response)
 | 
				
			||||||
 | 
					    if assembly.user_id != user_id:
 | 
				
			||||||
 | 
					        response = {
 | 
				
			||||||
 | 
					            'ok': False,
 | 
				
			||||||
 | 
					            'message': 'Вы не можете закрыть сборку чужого пользователя'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response)
 | 
				
			||||||
 | 
					    order_id = assembly.order_id
 | 
				
			||||||
 | 
					    return sipro.api.orders.ship_order(order_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@assembly_blueprint.post('/updateState')
 | 
				
			||||||
 | 
					def update_assembly_state():
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        args = request.json
 | 
				
			||||||
 | 
					        state = args.get('state')
 | 
				
			||||||
 | 
					        assembly_id = args.get('assemblyId')
 | 
				
			||||||
 | 
					        rows_to_update = [{
 | 
				
			||||||
 | 
					            'id': assembly_id,
 | 
				
			||||||
 | 
					            'state': state
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					        database.db.session.bulk_update_mappings(database.Assembly, rows_to_update)
 | 
				
			||||||
 | 
					        database.db.session.commit()
 | 
				
			||||||
 | 
					        return jsonify(ok=True)
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
from flask import Blueprint, request, jsonify
 | 
					from flask import Blueprint, request, jsonify
 | 
				
			||||||
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
 | 
					from flask_jwt_extended import create_access_token
 | 
				
			||||||
from werkzeug.security import generate_password_hash, check_password_hash
 | 
					from werkzeug.security import generate_password_hash, check_password_hash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from database import User, db
 | 
					from database import User, db
 | 
				
			||||||
@@ -12,7 +12,7 @@ def register_endpoint():
 | 
				
			|||||||
    data = request.json
 | 
					    data = request.json
 | 
				
			||||||
    login = data.get('login')
 | 
					    login = data.get('login')
 | 
				
			||||||
    password = data.get('password')
 | 
					    password = data.get('password')
 | 
				
			||||||
    password_hash = generate_password_hash(password, method='sha256')
 | 
					    password_hash = generate_password_hash(password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new_user = User(login=login,
 | 
					    new_user = User(login=login,
 | 
				
			||||||
                    password_hash=password_hash)
 | 
					                    password_hash=password_hash)
 | 
				
			||||||
@@ -29,16 +29,9 @@ def login_endpoint():
 | 
				
			|||||||
    login = data.get('login')
 | 
					    login = data.get('login')
 | 
				
			||||||
    user = User.query.filter_by(login=login).first()
 | 
					    user = User.query.filter_by(login=login).first()
 | 
				
			||||||
    if not user:
 | 
					    if not user:
 | 
				
			||||||
        return jsonify(ok=False), 401
 | 
					        return jsonify(ok=False, accessToken=''), 401
 | 
				
			||||||
    password = data.get('password')
 | 
					    password = data.get('password')
 | 
				
			||||||
    if not check_password_hash(user.password_hash, password):
 | 
					    if not check_password_hash(user.password_hash, password):
 | 
				
			||||||
        return jsonify(ok=False), 401
 | 
					        return jsonify(ok=False, accessToken=''), 401
 | 
				
			||||||
    access_token = create_access_token(identity=user.id)
 | 
					    access_token = create_access_token(identity=user.id)
 | 
				
			||||||
    return jsonify(access_token=access_token)
 | 
					    return jsonify(ok=True, accessToken=access_token)
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@auth_blueprint.get('/protected')
 | 
					 | 
				
			||||||
@jwt_required()
 | 
					 | 
				
			||||||
def protected_endpoint():
 | 
					 | 
				
			||||||
    print(type(get_jwt_identity()))
 | 
					 | 
				
			||||||
    return 'test'
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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
 | 
				
			||||||
							
								
								
									
										13
									
								
								routes/barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								routes/barcode.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					from flask import Blueprint, jsonify, request
 | 
				
			||||||
 | 
					from routes.utils import jwt_protect_blueprint
 | 
				
			||||||
 | 
					import sipro.api.barcode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					barcode_blueprint = jwt_protect_blueprint(Blueprint('barcode', __name__))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@barcode_blueprint.get('/searchProducts')
 | 
				
			||||||
 | 
					def search_product():
 | 
				
			||||||
 | 
					    args = request.args
 | 
				
			||||||
 | 
					    barcode = args.get('barcode')
 | 
				
			||||||
 | 
					    response = sipro.api.barcode.get_products_by_barcode(barcode)
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
							
								
								
									
										15
									
								
								routes/general.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								routes/general.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					from flask import Blueprint
 | 
				
			||||||
 | 
					from routes.utils import jwt_protect_blueprint
 | 
				
			||||||
 | 
					import sipro.api.general
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					general_blueprint = jwt_protect_blueprint(Blueprint('general', __name__))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@general_blueprint.get('/getShippingWarehouses')
 | 
				
			||||||
 | 
					def get_shipping_warehouses():
 | 
				
			||||||
 | 
					    return sipro.api.general.get_shipping_warehouses()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@general_blueprint.get('/getCities')
 | 
				
			||||||
 | 
					def get_cities():
 | 
				
			||||||
 | 
					    return sipro.api.general.get_cities()
 | 
				
			||||||
							
								
								
									
										47
									
								
								routes/orders.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								routes/orders.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					import database
 | 
				
			||||||
 | 
					from flask import Blueprint, jsonify, request
 | 
				
			||||||
 | 
					from flask_jwt_extended import get_jwt_identity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from routes.utils import jwt_protect_blueprint
 | 
				
			||||||
 | 
					import sipro.api.orders
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					orders_blueprint = jwt_protect_blueprint(Blueprint('orders', __name__))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@orders_blueprint.get('/<int:order_id>')
 | 
				
			||||||
 | 
					def get_order(order_id: int):
 | 
				
			||||||
 | 
					    return jsonify(id=order_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@orders_blueprint.get('/getOrders')
 | 
				
			||||||
 | 
					def get_orders():
 | 
				
			||||||
 | 
					    args = request.args
 | 
				
			||||||
 | 
					    order_by = args.get('orderBy')
 | 
				
			||||||
 | 
					    desc = int(args.get('desc'))
 | 
				
			||||||
 | 
					    page = int(args.get('page'))
 | 
				
			||||||
 | 
					    shipment_date = args.get('shipmentDate')
 | 
				
			||||||
 | 
					    status = args.get('status')
 | 
				
			||||||
 | 
					    shipment_warehouse_id = int(args.get('shipmentWarehouseId'))
 | 
				
			||||||
 | 
					    response = sipro.api.orders.get_orders(order_by,
 | 
				
			||||||
 | 
					                                           desc,
 | 
				
			||||||
 | 
					                                           page,
 | 
				
			||||||
 | 
					                                           shipment_date,
 | 
				
			||||||
 | 
					                                           status,
 | 
				
			||||||
 | 
					                                           shipment_warehouse_id)
 | 
				
			||||||
 | 
					    return jsonify(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@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
 | 
				
			||||||
 | 
					    params = dict(request.args)
 | 
				
			||||||
 | 
					    params['city'] = city_id
 | 
				
			||||||
 | 
					    return sipro.api.orders.get_orders_from_barcode(params=params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@orders_blueprint.get('/getOrderById')
 | 
				
			||||||
 | 
					def get_order_by_id():
 | 
				
			||||||
 | 
					    args = request.args
 | 
				
			||||||
 | 
					    order_id = args.get('orderId')
 | 
				
			||||||
 | 
					    return sipro.api.orders.get_order_by_id(order_id)
 | 
				
			||||||
							
								
								
									
										24
									
								
								routes/printing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								routes/printing.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import Blueprint, request, send_file, jsonify
 | 
				
			||||||
 | 
					from routes.utils import jwt_protect_blueprint
 | 
				
			||||||
 | 
					import sipro.api.printing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					printing_blueprint = jwt_protect_blueprint(Blueprint('printing', __name__))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@printing_blueprint.get('/getLabel')
 | 
				
			||||||
 | 
					def get_label():
 | 
				
			||||||
 | 
					    args = request.args
 | 
				
			||||||
 | 
					    order_id = args.get('orderId')
 | 
				
			||||||
 | 
					    label_format = args.get('format', 'pdf')
 | 
				
			||||||
 | 
					    data = sipro.api.printing.get_label(order_id, label_format=label_format)
 | 
				
			||||||
 | 
					    data.seek(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
							
								
								
									
										169
									
								
								routes/sipro.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								routes/sipro.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,169 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					from enum import StrEnum
 | 
				
			||||||
 | 
					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__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ExpandParam(StrEnum):
 | 
				
			||||||
 | 
					    USER = 'user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@sipro_blueprint.before_request
 | 
				
			||||||
 | 
					def auth():
 | 
				
			||||||
 | 
					    API_KEY = '5D809ED08080B5F204443B31374BD6A5'
 | 
				
			||||||
 | 
					    if request.headers.get('Authorization') != API_KEY:
 | 
				
			||||||
 | 
					        return {'error': 'Unauthorized'}, 401
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@sipro_blueprint.post('/assemblyInfo')
 | 
				
			||||||
 | 
					def assembly_info():
 | 
				
			||||||
 | 
					    params: dict = request.json
 | 
				
			||||||
 | 
					    expand_param = params.get('expand')
 | 
				
			||||||
 | 
					    order_ids = params.get('orderIds')
 | 
				
			||||||
 | 
					    expand_list = expand_param if expand_param else []
 | 
				
			||||||
 | 
					    datetime_format = 'YYYY.MM.DD, HH24:MI'
 | 
				
			||||||
 | 
					    entity_list = [
 | 
				
			||||||
 | 
					        database.Assembly.id.label('id'),
 | 
				
			||||||
 | 
					        database.Assembly.order_id.label('order_id'),
 | 
				
			||||||
 | 
					        func.to_char(database.Assembly.created_at, datetime_format).label('created_at'),
 | 
				
			||||||
 | 
					        func.to_char(database.Assembly.ended_at, datetime_format).label('ended_at'),
 | 
				
			||||||
 | 
					        database.Assembly.is_active.label('is_active'),
 | 
				
			||||||
 | 
					        database.Assembly.state.label('state')
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    query = database.Assembly.query
 | 
				
			||||||
 | 
					    for expand in expand_list:
 | 
				
			||||||
 | 
					        match expand:
 | 
				
			||||||
 | 
					            case ExpandParam.USER:
 | 
				
			||||||
 | 
					                query = query.join(database.User)
 | 
				
			||||||
 | 
					                entity_list.extend([
 | 
				
			||||||
 | 
					                    database.User.id.label('user.id'),
 | 
				
			||||||
 | 
					                    database.User.login.label('user.login'),
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if order_ids:
 | 
				
			||||||
 | 
					        query = query.filter(database.Assembly.order_id.in_(order_ids))
 | 
				
			||||||
 | 
					    query = query.with_entities(*entity_list)
 | 
				
			||||||
 | 
					    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(',')))})
 | 
				
			||||||
							
								
								
									
										12
									
								
								routes/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								routes/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					from functools import wraps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import Blueprint
 | 
				
			||||||
 | 
					from flask_jwt_extended import verify_jwt_in_request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def jwt_protect_blueprint(blueprint) -> Blueprint:
 | 
				
			||||||
 | 
					    @blueprint.before_request
 | 
				
			||||||
 | 
					    def require_token():
 | 
				
			||||||
 | 
					        verify_jwt_in_request()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return blueprint
 | 
				
			||||||
@@ -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)
 | 
				
			||||||
@@ -19,3 +19,7 @@ MY_HOST = os.environ.get('MY_HOST')
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Flask settings
 | 
					# Flask settings
 | 
				
			||||||
SECRET_KEY = os.environ.get('SECRET_KEY')
 | 
					SECRET_KEY = os.environ.get('SECRET_KEY')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Sipro settings
 | 
				
			||||||
 | 
					SIPRO_API_URL = os.environ.get('SIPRO_API_URL')
 | 
				
			||||||
 | 
					SIPRO_API_TOKEN = os.environ.get('SIPRO_API_TOKEN')
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								singleton.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								singleton.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					class Singleton(type):
 | 
				
			||||||
 | 
					    _instances = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __call__(cls, *args, **kwargs):
 | 
				
			||||||
 | 
					        if cls not in cls._instances:
 | 
				
			||||||
 | 
					            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
 | 
				
			||||||
 | 
					        return cls._instances[cls]
 | 
				
			||||||
							
								
								
									
										1
									
								
								sipro/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								sipro/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					from sipro import api
 | 
				
			||||||
							
								
								
									
										0
									
								
								sipro/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								sipro/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										10
									
								
								sipro/api/barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sipro/api/barcode.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					from sipro.api.client import get_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					client = get_client()
 | 
				
			||||||
 | 
					router = '/barcode'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_products_by_barcode(barcode: str) -> list[dict]:
 | 
				
			||||||
 | 
					    method = f'{router}/getProducts?barcode={barcode}'
 | 
				
			||||||
 | 
					    response = client.method('GET', method)
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
							
								
								
									
										42
									
								
								sipro/api/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sipro/api/client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					import requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import settings
 | 
				
			||||||
 | 
					from logger import logger_instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SiproClient:
 | 
				
			||||||
 | 
					    _instance = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __new__(cls, *args, **kwargs):
 | 
				
			||||||
 | 
					        if not cls._instance:
 | 
				
			||||||
 | 
					            cls._instance = super(SiproClient, cls).__new__(cls)
 | 
				
			||||||
 | 
					        return cls._instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, api_url: str, token: str):
 | 
				
			||||||
 | 
					        if not hasattr(self, 'initialized'):
 | 
				
			||||||
 | 
					            self.api_url = api_url
 | 
				
			||||||
 | 
					            self.token = token
 | 
				
			||||||
 | 
					            self.initialized = True
 | 
				
			||||||
 | 
					            logger_instance.info('SiproClient successfully initialized')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def method(self, http_method: str, method: str, data: dict = None, raw=False, params: dict = None):
 | 
				
			||||||
 | 
					        url = self.api_url + '/assemblr' + method
 | 
				
			||||||
 | 
					        headers = {'Authorization': self.token}
 | 
				
			||||||
 | 
					        response = requests.request(http_method, url, headers=headers, json=data, params=params)
 | 
				
			||||||
 | 
					        if raw:
 | 
				
			||||||
 | 
					            return response
 | 
				
			||||||
 | 
					        return response.json()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def ping(self) -> str:
 | 
				
			||||||
 | 
					        return self.method('GET', '/ping').get('response')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sipro_config = {
 | 
				
			||||||
 | 
					    'api_url': settings.SIPRO_API_URL,
 | 
				
			||||||
 | 
					    'token': settings.SIPRO_API_TOKEN,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					sipro_client = SiproClient(**sipro_config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_client() -> SiproClient:
 | 
				
			||||||
 | 
					    return sipro_client
 | 
				
			||||||
							
								
								
									
										12
									
								
								sipro/api/general.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								sipro/api/general.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					from sipro.api.client import get_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					client = get_client()
 | 
				
			||||||
 | 
					router = '/general'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_shipping_warehouses():
 | 
				
			||||||
 | 
					    return client.method('GET', f'{router}/getShippingWarehouses')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_cities():
 | 
				
			||||||
 | 
					    return client.method('GET', f'{router}/getCities')
 | 
				
			||||||
							
								
								
									
										68
									
								
								sipro/api/orders.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								sipro/api/orders.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					from sipro.api.client import get_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					client = get_client()
 | 
				
			||||||
 | 
					router = '/orders'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_orders_by_product_id(supplier_product_id: str) -> list[dict]:
 | 
				
			||||||
 | 
					    method = f'{router}/getByProductId?productId={supplier_product_id}'
 | 
				
			||||||
 | 
					    response = client.method('GET', method)
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_order_by_id(order_id) -> dict:
 | 
				
			||||||
 | 
					    method = f'{router}/getOrderById?orderId={order_id}'
 | 
				
			||||||
 | 
					    response = client.method('GET', method)
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def ship_order(order_id: int) -> dict:
 | 
				
			||||||
 | 
					    method = f'{router}/shipOrder'
 | 
				
			||||||
 | 
					    data = {'orderId': order_id}
 | 
				
			||||||
 | 
					    response = client.method('POST', method, data=data)
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def close_order(order_id: int) -> dict:
 | 
				
			||||||
 | 
					    method = f'{router}/closeOrder'
 | 
				
			||||||
 | 
					    data = {'orderId': order_id}
 | 
				
			||||||
 | 
					    response = client.method('POST', method, data=data)
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_orders(order_by: str, desc: int, page: int, shipment_date: str, status: str, shipment_warehouse_id: int):
 | 
				
			||||||
 | 
					    method = f'{router}/getOrders?orderBy={order_by}&desc={desc}&page={page}&shipmentDate={shipment_date}&status={status}&shipmentWarehouseId={shipment_warehouse_id}'
 | 
				
			||||||
 | 
					    response = client.method('GET', method)
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
							
								
								
									
										16
									
								
								sipro/api/printing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sipro/api/printing.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import send_file
 | 
				
			||||||
 | 
					from requests import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sipro.api.client import get_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					client = get_client()
 | 
				
			||||||
 | 
					router = '/printing'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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,17 +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