kassa added

This commit is contained in:
2022-01-20 16:28:05 +09:00
parent 5c71aef5bf
commit 831bb26887
10 changed files with 775 additions and 10 deletions

47
kassa/atol.py Normal file
View File

@ -0,0 +1,47 @@
import requests
import json
class Atol:
def __init__(self, token):
self.token = token
# Вызовы функций
self.load_info()
# self.get_token()
def load_info(self):
self.url = "https://online.atol.ru/possystem/v4/"
self.group_id = 'jkhsakha-ru_3289'
def get_headers(self):
headers_dict = {
"Content-type": "application/json",
"charset": "utf-8",
"Token": self.token, }
return headers_dict
def get_request(self, method, url, data):
if method == "post":
r = requests.post(self.url+url, data=json.dumps(data),
headers=self.get_headers())
else:
r = requests.get(self.url+url, headers=self.get_headers())
r.encoding = "utf-8"
return json.loads(r.text)
def get_token(self, login, password):
self.login = login
self.password = password
d = {"login": self.login, "pass": self.password}
self.token, _, self.timestamp = self.get_request(
"post", "getToken", d).values()
return self.token
def set_sell(self, reciept, is_refund):
func = '/sell' if is_refund == 0 else '/sell_refund'
s = self.get_request('post', self.group_id+func, reciept)
return s
def get_reciepts(self, uuid):
r = self.get_request('get', self.group_id+'/' + 'report/'+uuid, None)
return r

View File

53
kassa/cruds/doc.py Normal file
View File

@ -0,0 +1,53 @@
from ast import Dict
from sqlalchemy.orm import Session
import schemas
import models
def create_doc(db: Session, doc: schemas.Doc, external_id: str = None):
d = doc.dict()
external_id = external_id.lower()
doc_query = db.query(models.Doc).filter(
models.Doc.external_id == external_id)
error = d.pop('error', None)
payload = d.pop('payload', None)
warnings = d.pop('warnings', None)
if error:
create_error(db, error, external_id)
if payload:
create_payload(db, payload, external_id)
if doc_query.first():
doc_query.update(values=d)
else:
doc_query = models.Doc(**d)
db.add(doc_query)
db.commit()
def create_error(db: Session, error: schemas.Error, external_id: str = None):
err = error
err['external_id'] = external_id
err_query = db.query(models.Error).filter(
models.Error.external_id == external_id)
if err_query.first():
err_query.update(values=err)
else:
err_query = models.Error(**err, synchronize_session=False)
db.add(err_query)
db.commit()
def create_payload(db: Session, payload: schemas.Payload, external_id: str = None):
pay = payload
payload_query = db.query(models.Payload).filter(
models.Payload.external_id == external_id)
if payload_query.first():
payload_query.update(pay, synchronize_session=False)
else:
pay['external_id'] = external_id
model = models.Payload(**pay)
db.add(model)
db.commit()

0
kassa/cruds/sell.py Normal file
View File

17
kassa/databases.py Normal file
View File

@ -0,0 +1,17 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
Base = declarative_base()
SQL_ALCHEMY_DATABASE_URL_MSSQL = f'mssql+pyodbc://sa:159357@Sanctuary/Atol?driver=SQL+Server'
engine = create_engine(
SQL_ALCHEMY_DATABASE_URL_MSSQL, connect_args={'check_same_thread': False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

15
kassa/kassa.py Normal file
View File

@ -0,0 +1,15 @@
from fastapi import APIRouter, Depends
import databases
import cruds.doc as doc
import schemas
import models
models.Base.metadata.create_all(databases.engine)
router = APIRouter()
get_db = databases.get_db
@router.post('/{external_id}')
def insert_doc(request: schemas.Doc, external_id: str, db=Depends(get_db)):
return doc.create_doc(db, request, external_id)

80
kassa/models.py Normal file
View File

@ -0,0 +1,80 @@
from sqlalchemy.sql.schema import ForeignKey
from sqlalchemy import Column, Integer, String, Numeric, DateTime, Boolean
from sqlalchemy.orm import relationship
from databases import Base
class Payment(Base):
__tablename__ = 'payments'
id = Column('id', Integer, primary_key=True, autoincrement=True)
external_id = Column('external_id', String(length=128))
type = Column('type', Integer)
sum = Column('sum', Numeric(12, 2))
class CorrectionInfoTable(Base):
__tablename__ = 'correction_info'
external_id = Column('external_id', String(length=128), primary_key=True)
type = Column('type', String(length=10))
base_date = Column('base_date', String(length=128))
base_number = Column('base_number', String(length=128))
class Error(Base):
__tablename__ = 'errors'
external_id = Column('external_id', String(length=128), primary_key=True)
error_id = Column('error_id', String(length=128))
code = Column('code', Integer)
text = Column('text', String(length=250))
type = Column('type', String(length=10))
class Doc(Base):
__tablename__ = 'docs'
uuid = Column('uuid', String(length=128), primary_key=True)
timestamp = Column('timestamp', String(length=128))
group_code = Column('group_code', String(length=128))
daemon_code = Column('daemon_code', String(length=128))
device_code = Column('device_code', String(length=128))
external_id = Column('external_id', String(length=128))
callback_url = Column('callback_url', String(length=128))
status = Column('status', String(length=128))
class Atol(Base):
__tablename__ = 'atol_receipts'
uuid = Column('uuid', String(length=128), primary_key=True)
timestamp = Column('timestamp', String(length=128))
external_id = Column('external_id', String(length=128))
status = Column('status', String(length=128))
class Payload(Base):
__tablename__ = 'payloads'
external_id = Column('external_id', String(128), primary_key=True)
fiscal_receipt_number = Column('fiscal_receipt_number', Integer)
shift_number = Column('shift_number', Integer)
receipt_datetime = Column('receipt_datetime', String(length=128))
total = Column('total', Numeric(12, 2))
fn_number = Column('fn_number', String(length=128))
ecr_registration_number = Column(
'ecr_registration_number', String(length=128))
fiscal_document_number = Column('fiscal_document_number', Integer)
fiscal_document_attribute = Column('fiscal_document_attribute', Integer)
fns_site = Column('fns_site', String(length=128))
"""
class Sell(Base):
__tablename__ = 'sells'
id = Column(Integer, primary_key=True, autoincrement=True)
external_id = Column('external_id',String(128))
is_refund = Column('is_refund', Boolean)
service = Column('service', String(length=16))
timestamp = Column('timestamp', DateTime)
"""

319
kassa/new.py Normal file
View File

@ -0,0 +1,319 @@
from time import sleep
from typing import Dict, List, Tuple
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine, Table, Column, engine
from sqlalchemy.orm.session import Session
from sqlalchemy.orm import query, sessionmaker
from sqlalchemy.types import BigInteger, Integer, String, Numeric, Boolean, DateTime
from sqlalchemy.sql.sqltypes import DateTime
from sqlalchemy import desc, cast, case, func
import schemas
import models
import datetime
Base = declarative_base()
metadata = Base.metadata
class PaymentDetails(Base):
__tablename__ = "payment_details"
id = Column(Integer, primary_key=True, autoincrement=True)
external_id = Column(String(36), nullable=False, index=True)
is_refund = Column(Boolean, nullable=False, index=True)
is_taken = Column(Boolean, nullable=False, index=True)
added_date = Column(DateTime, nullable=True,
default=datetime.datetime.now())
# class PaymentDetailsBaseModel(BaseModel):
class DBEngine:
def __init__(self, server, dbname, user, password):
self.dbname = dbname
self.user = user
self.password = password
self.server = server
self.get_mssql_engine()
def get_mssql_engine(self):
query = f'mssql+pyodbc://{self.user}:{self.password}@{self.server}/{self.dbname}?driver=SQL+Server'
self.engine = create_engine(
query, connect_args={'check_same_thread': False})
def get_table(self, tablename: str) -> Table:
self.metadata = Base.metadata
self.metadata.reflect(bind=self.engine)
return Table(tablename, self.metadata, schema=self.dbname+'.dbo', autoload=True, autoload_with=self.engine)
def get_columns(self, tablename: str, columns: List[str] = None, labels: Dict = None) -> List[Column]:
table = self.get_table(tablename)
if not labels:
labels = {}
if columns:
lst = [(table.c.get(column)).label(labels.get(column, column) or column)
for column in columns]
return lst
return table.c.items()
def get_db(self):
db = Session(autocommit=False, autoflush=False, bind=self.engine)
try:
yield db
finally:
db.close()
def get_payment_details(db: Session, engine_class: DBEngine):
pd = engine_class.get_table('payment_details')
external_id, is_refund, is_taken = engine_class.get_columns(
'payment_details', ['external_id', 'is_refund', 'is_taken'])
query = db.query(pd).filter(is_taken == 0).with_entities(
external_id, is_refund, is_taken).distinct().order_by(external_id, desc(is_refund))
return query
def update_payment_details(db: Session, engine_class: DBEngine, payment_details: Dict):
external_id_value = payment_details.get("external_id")
is_refund_value = payment_details.get("is_refund")
is_taken_value = payment_details.get("is_taken")
pd = engine_class.get_table('payment_details')
external_id, is_refund, is_taken = engine_class.get_columns(
'payment_details', ['external_id', 'is_refund', 'is_taken'])
query = db.query(pd).filter(external_id == external_id_value,
is_taken == is_taken_value, is_refund == is_refund_value)
query = query.update({'is_taken': True}, synchronize_session='fetch')
db.commit()
return query
def get_payment(db: Session, engine_class: DBEngine, payment_details: Tuple):
external_id_value, is_refund_value, is_taken_value = payment_details
pd = engine_class.get_table('payment_details')
external_id, is_refund, is_taken = engine_class.get_columns(
'payment_details', ['external_id', 'is_refund', 'is_taken'])
query = db.query(pd).filter(external_id == external_id_value,
is_taken == is_taken_value, is_refund == is_refund_value)
return query
"""
#Место сбора запроса на items с заполнением по справочникам
"""
def get_payment_details_items(db: Session, engine_class: DBEngine, dict_engine: DBEngine, payment_details: Tuple):
payment = get_payment(
db, engine_class, payment_details).subquery('payment')
services = dict_engine.get_table('services')
units = dict_engine.get_table('units')
payment_method = dict_engine.get_table('payment_method')
payment_object = dict_engine.get_table('payment_object')
vats = dict_engine.get_table('payment_object_vat_type')
agents = dict_engine.get_table('agents')
company = dict_engine.get_table('providers')
query = db.query(
payment.c.external_id.label('external_id'),
services.c.sname.label('name'),
units.c["sname"].label('measurement_unit'),
payment.c.price.label('price'),
payment.c.quantity.label('quantity'),
payment.c.date_operation.label('date_operation'),
payment.c.phone.label('phone'),
company.c.inn,
payment.c.summa.label('sum'),
case(
(payment.c.payment_method.in_([5, 6, 7]), 3),
(payment.c.payment_method.in_([1, 2, 3]), 2),
(payment.c.payment_method == 4, 1)
).label("payment_group"),
payment_method.c["name"].label('payment_method'),
payment_object.c["name"].label('payment_object'),
vats.c["name"].label("vat"),
payment.c.agent_type.label('agent_info'),
case((payment.c.agent_type != None, payment.c.supplier_info),
else_=None).label('supplier_info'),
agents.c["inn"].label("supplier_inn"),
agents.c["name"].label("supplier_name")
).select_from(payment)\
.join(services, services.c["id_service"] == payment.c.id_item)\
.join(units, services.c["id_unit"] == units.c["id_unit"])\
.join(payment_method, payment.c.payment_method == payment_method.c["id"])\
.join(payment_object, payment.c.payment_object == payment_object.c["id"])\
.join(vats, payment.c.vat == vats.c["id"])\
.join(company, payment.c.id_company == company.c["id_provider"])\
.join(agents, payment.c.supplier_info == agents.c["id_agent"])
return query
"""
#Функция для заполнения payments
"""
def get_payments(data: List):
d = {}
for row in data:
type_id = row.get("payment_group")
cur_sum = row.get("sum")
d[type_id] = cur_sum + d.get(type_id, 0.0)
res = [{'type': i[0], 'sum': i[1]} for i in d.items()]
return res
def get_token(db: Session, db_dict: DBEngine):
token_dict = db_dict.get_table("vAtolToken")
return token_dict.get("token")
"""
#Функция для заполнения total
"""
def get_total(data: List):
total = 0.0
for i in data:
total += i.get("sum")
return total
"""
#Функция для заполнения items в dicts
"""
def items_convert(data: query):
items = []
external_id = 0
total = 0.0
d = {}
payments = []
phone = ""
inn = 0
for row in data.all():
item = dict(row)
external_id = item.get("external_id")
item["vat"] = get_vat(item.get("vat"))
agent_info = item.pop("agent_info", None)
total += item.get("sum")
type_id = item.get("payment_group")
cur_sum = item.get("sum")
phone = item.get("phone", '+79111111111')
inn = int(item.get("inn"))
d[type_id] = cur_sum + d.get(type_id, 0.0)
if agent_info:
item["agent_info"] = {"type": agent_info}
item["supplier_info"] = {
"inn": item.pop("supplier_inn", None),
"name": item.pop("supplier_name", None)
}
else:
for i in ["supplier_info", 'supplier_inn', 'supplier_name']:
del item[i]
items.append(item)
payments = [{'type': i[0], 'sum': i[1]} for i in d.items()]
client = get_client(phone)
company = get_company(inn)
return external_id, items, payments, client, company, total
def get_company(inn: int):
company = {}
company['inn'] = inn
company['email'] = 'ocnkp@jkhsakha.ru'
company['payment_address'] = 'http://jkhsakha.ru/'
company['sno'] = 'osn'
return company
def get_receipt(data: query):
receipt = {}
external_id = 0
external_id, items, payments, client, company, total = items_convert(
data)
receipt['client'] = client
receipt["payments"] = payments
receipt["company"] = company
receipt['items'] = items
receipt['total'] = total
return external_id, receipt
def get_sell(data: query):
sell = {}
current_datetime = datetime.datetime.now()
external_id, sell['receipt'] = get_receipt(data)
sell["timestamp"] = current_datetime.strftime(
'%d.%m.%Y %H:%M:%S')
sell["external_id"] = external_id
sell["service"] = {
'callback_url': f"http://api.jkhsakha.ru/kassa/{external_id}"
}
return sell
def get_client(phone: str):
return {
'phone': phone
}
def get_vat(vat: str):
return {'type': vat}
def add_atol(db: Session, engine: DBEngine, atol: Dict):
aa = engine.get_table("Atol")
a = aa.insert().values(atol)
db.execute(a)
db.commit()
def add_doc(sell: schemas.Sell, is_refund):
from databases import SessionLocal
from atol import Atol
atol_model = Atol
session = SessionLocal()
a = Atol.set_sell(atol_model, sell, is_refund)
check = models.Atol(**a)
session.add(check)
session.commit()
session.close()
def get_payment():
server = 'Sanctuary'
user = 'sa'
password = '159357'
dbname = 'fz54_details'
db_dicts_name = 'fz54'
db = DBEngine(server, dbname, user, password)
db_dicts = DBEngine(server, db_dicts_name, user, password)
session = Session(autocommit=False, autoflush=False, bind=db.engine)
payment = get_payment_details(session, db).first()
if payment:
payments = get_payment_details_items(session, db, db_dicts, payment)
body = get_sell(payments)
sell = schemas.Sell(**body)
payment = dict(payment)
atol = {
"external_id": payment.get("external_id"),
"is_refund": payment.get("is_refund"),
"is_taken": payment.get("is_taken"),
"body": str(body)
}
add_atol(session, db, atol)
update_payment_details(session, db, payment)
return sell, payment.get("is_refund")
return False, False
if __name__ == "__main__":
while True:
payment, is_refund = get_payment()
if payment == False:
break
sleep(1)
add_doc(sell=payment, is_refund=is_refund)
break

222
kassa/schemas.py Normal file
View File

@ -0,0 +1,222 @@
from ast import Try
from typing import List, Optional, Dict
from pydantic import BaseModel, Field, confloat, constr
from sqlalchemy.sql.sqltypes import Boolean, DateTime
from sqlalchemy.sql.sqltypes import DateTime
class SumNumberTwoFormat(BaseModel):
__root__: Optional[confloat(ge=0.0, le=100000000.0, multiple_of=0.01)]
class NumberPrice(BaseModel):
__root__: confloat(ge=0.0, le=42949673.0, multiple_of=0.01)
class PhoneNumber(BaseModel):
__root__: constr(regex=r'^([^\s\\]{0,17}|\+[^\s\\]{1,18})$')
class NumberTwoFormat(BaseModel):
__root__: confloat(ge=0.0, le=100000000.0, multiple_of=0.01)
class NumberThreeFormat(BaseModel):
__root__: confloat(ge=0.0, le=100000.0, multiple_of=0.001)
class Service(BaseModel):
callback_url: Optional[constr(max_length=256)] = None
class Warnings(BaseModel):
callback_url: str = None
class PayingAgent(BaseModel):
operation: Optional[str] = None
phones: Optional[List[PhoneNumber]] = None
class SupplierInfo(BaseModel):
phones: Optional[List[PhoneNumber]] = None
name: Optional[str] = None
inn: Optional[str] = None
class Config:
orm_mode = True
class ReceivePaymentsOperator(BaseModel):
phones: Optional[List[PhoneNumber]] = None
class MoneyTransferOperator(BaseModel):
phones: Optional[List[PhoneNumber]] = None
name: Optional[str] = None
address: Optional[str] = None
inn: Optional[constr(regex=r'(^[0-9]{10}$)|(^[0-9]{12}$)')] = None
class Company(BaseModel):
email: Optional[constr(max_length=64)] = None
sno: Optional[str] = None
inn: constr(max_length=12)
payment_address: constr(max_length=256)
class Config:
orm_mode = True
class ClientItem(BaseModel):
email: constr(max_length=64) = None
phone: Optional[constr(max_length=64)] = None
class AdditionalUserProps(BaseModel):
name: constr(max_length=64)
value: constr(max_length=256)
class AgentInfo(BaseModel):
type: Optional[str] = None
paying_agent: Optional[PayingAgent] = None
receive_payments_operator: Optional[ReceivePaymentsOperator] = None
money_transfer_operator: Optional[MoneyTransferOperator] = None
class Config:
orm_mode = True
class Error(BaseModel):
error_id: Optional[str] = None
code: int
text: str
type: Optional[str] = None
class Config:
orm_mode = True
class Payload(BaseModel):
fiscal_receipt_number: int
shift_number: int
receipt_datetime: str
total: float
fn_number: str
ecr_registration_number: str
fiscal_document_number: int
fiscal_document_attribute: int
fns_site: str
class Vat(BaseModel):
type: Optional[str] = None
class Config:
orm_mode = True
class CorrectionInfo(BaseModel):
type: str
base_date: str
base_number: str
class Payment(BaseModel):
type: int
sum: SumNumberTwoFormat
class Config:
orm_mode = True
class Item(BaseModel):
name: str
price: NumberPrice
quantity: NumberThreeFormat
sum: SumNumberTwoFormat
measurement_unit: Optional[constr(max_length=16)] = None
payment_method: Optional[str] = None
payment_object: Optional[str] = None
nomenclature_code: Optional[str] = None
vat: Optional[Vat] = None
agent_info: Optional[AgentInfo] = None
supplier_info: Optional[SupplierInfo] = None
user_data: Optional[constr(max_length=64)] = None
excise: Optional[confloat(ge=0.0)] = None
country_code: Optional[constr(
regex=r'^[0-9]*$', min_length=1, max_length=3)] = None
declaration_number: Optional[constr(min_length=1, max_length=32)] = None
class Config:
orm_mode = True
class Receipt(BaseModel):
client: ClientItem
company: Company
agent_info: Optional[AgentInfo] = None
supplier_info: Optional[SupplierInfo] = None
items: List[Item] = Field(..., min_items=1)
payments: List[Payment] = Field(..., max_items=10, min_items=1)
vats: Optional[List[Vat]] = Field(None, max_items=6, min_items=1)
total: NumberTwoFormat
additional_check_props: Optional[constr(max_length=16)] = None
cashier: Optional[constr(max_length=64)] = None
additional_user_props: Optional[AdditionalUserProps] = None
class Config:
orm_mode = True
class Correction(BaseModel):
company: Company
cashier: Optional[constr(max_length=64)] = None
correction_info: CorrectionInfo
payments: List[int] = Field(..., max_items=10, min_items=1)
vats: List[Vat] = Field(..., max_items=6, min_items=1)
class Config:
orm_mode = True
class Doc(BaseModel):
uuid: str
timestamp: str
group_code: str
daemon_code: str
device_code: str
external_id: Optional[str] = None
callback_url: Optional[str] = None
status: Optional[str] = None
error: Error = None
warnings: Optional[Warnings] = None
payload: Payload = None
class Config:
orm_mode = True
class Sell(BaseModel):
external_id: constr(max_length=128)
receipt: Receipt
service: Optional[Service] = None
timestamp: str = None
class Config:
arbitrary_types_allowed = True
class SellCreate(Sell):
is_refund: bool
class SellShow(SellCreate):
id: int
class Correction(BaseModel):
timestamp: str
external_id: constr(max_length=128)
service: Optional[Service] = None

32
main.py
View File

@ -3,14 +3,15 @@ from fastapi.middleware import Middleware
from fastapi.middleware.cors import CORSMiddleware
from kv import kv
from auth import auth
from kassa import kassa
import uvicorn
middleware = [Middleware(
CORSMiddleware,
allow_origins = ['*'],
allow_credentials = True,
allow_methods = ['*'],
allow_headers = ['*'],
allow_origins=['*'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)]
app = FastAPI(middleware=middleware)
@ -18,15 +19,26 @@ app = FastAPI(middleware=middleware)
@app.get('/hello')
async def say_hello():
return { "text": "Hello!" }
return {"text": "Hello!"}
app.include_router(
router = auth.router,
prefix= '/auth',
router=auth.router,
prefix='/auth',
tags=['Авторизация'],
responses={404: {"description": "Not found"}}
)
app.include_router(
router=kv.router,
prefix='/kv',
tags=['Кварплата'],
responses={404: {"description": "Not found"}}
)
app.include_router(
router=kassa.router,
prefix='/kassa',
tags=['Касса Атол'],
responses={404: {"description": "Not found"}}
)
app.include_router(router = kv.router, prefix = '/kv',tags=['Кварплата'], responses={404: {"description": "Not found"}})
if __name__ == "__main__":
uvicorn.run("main:app", host = "0.0.0.0", port = 5000)
uvicorn.run("main:app", host="0.0.0.0", port=5000)