Add graphql queries. Update handlers

main
Ernest Litvinenko 2024-07-08 18:35:04 +03:00
parent 7bb4be9211
commit 9d01a17453
14 changed files with 451 additions and 473 deletions

12
.idea/dataSources.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="NETDBS_2@10.2.100.126" uuid="87379102-4799-4d65-9872-24e48c29e1fc">
<driver-ref>firebird</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.firebirdsql.jdbc.FBDriver</jdbc-driver>
<jdbc-url>jdbc:firebirdsql://10.2.100.126:3050/NETDBS_2?lc_ctype=WIN1251</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -4,7 +4,7 @@ from fastapi.security import OAuth2PasswordBearer
from core.config import Config from core.config import Config
from core.model.profile.db import ProfileDB from core.model.profile.db import ProfileDB
from core.storage import profile from core.storage import profile_storage
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/phone") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/phone")
@ -18,4 +18,4 @@ def get_user_from_token(token: str = Depends(oauth2_scheme)) -> ProfileDB:
except jwt.exceptions.InvalidSignatureError as err: except jwt.exceptions.InvalidSignatureError as err:
raise HTTPException(status_code=401, detail=str(err)) raise HTTPException(status_code=401, detail=str(err))
return profile.get_profile_by_id(data['profile_id']) return profile_storage.get_profile_by_id(data['profile_id'])

10
core/model/note/db.py Normal file
View File

@ -0,0 +1,10 @@
from pydantic import BaseModel
class AppNoteDB(BaseModel):
id: int
user_id: int
task_id: int
note_status: int
tip: int
text: str

View File

@ -1,414 +1,241 @@
import functools
import json
from datetime import date, datetime from datetime import date, datetime
from typing import Self, Optional, Union, Any, Callable from typing import Optional
from sqlalchemy import BIGINT, Column, ForeignKey
from firebird.driver import Cursor from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, aliased
from pydantic import BaseModel, Field, Json
from pypika import Query, Table
from pypika.queries import QueryBuilder
from core.database.db import redis_cache_obj
from core.storage.base import BaseStorage
from redis_cache import RedisCache
class __BaseDB(BaseModel, BaseStorage): class __Base(DeclarativeBase):
__tablename__: str pass
__read_only: bool = False
__table: Table | None = None
__state_changed = {}
__query: QueryBuilder | None = None
@classmethod
def cache(cls) -> RedisCache:
return redis_cache_obj()
@classmethod
def get_keys(cls):
return [x for x in cls.__dict__['__annotations__'].keys() if not x.startswith("__")]
@classmethod
def get_table(cls) -> Self:
return Table(cls.__tablename__)
@classmethod
def fetch_one(cls, id: int) -> type[Self]:
t = cls.get_table()
keys = cls.get_keys()
stmt = Query.from_(t).select(*keys).where(getattr(t, keys[0]) == id)
with cls.get_cursor() as cur:
cur: Cursor
data = cur.execute(stmt.get_sql()).fetchone()
return cls(**{key: val for key, val in zip(keys, data)})
@classmethod
def parse_orm(cls, row):
return cls(**{key: val for key, val in zip(cls.get_keys(), row)})
@classmethod
def fetch_all(cls, where_query: QueryBuilder = None) -> list['Self']:
table = cls.get_table()
if not where_query:
stmt = Query.from_(table).select(*cls.get_keys())
else:
stmt = Query.from_(table).select(*cls.get_keys()).where(where_query)
with cls.get_cursor() as cursor:
cursor: Cursor
stmt: QueryBuilder
print(stmt)
data = cursor.execute(stmt.get_sql()).fetchall()
return [cls.parse_orm(d) for d in data]
@classmethod
def fetch_related(cls) -> 'Self':
table = cls.get_table()
cls.__query = Query.from_(table)
return cls
@classmethod
def create(cls, model: Self) -> Self:
if model.__read_only:
raise Exception("This model is read only")
# table = self.get_table()
# data = self.model_dump(mode='json', exclude_none=True, exclude={'id'}, exclude_unset=True)
# stmt = Query.into(table).columns(*[self.get_keys()]).insert(
# *[kwargs[key] for key in self.get_keys() if key in kwargs.keys()])
# with self.get_cursor() as cursor:
# cursor: Cursor
# cursor.execute(stmt.get_sql())
@classmethod
def update(cls, model: Self) -> Self:
if model.__read_only:
raise Exception("This model is read only")
class MPLSTDB(__BaseDB): class MPLSTDB(__Base):
__tablename__: str = "LST" __tablename__ = "LST"
ID_LST: int ID_LST: Mapped[int] = mapped_column(BIGINT, primary_key=True)
LST_ID_VLST: int LST_ID_VLST: Mapped[int]
LST_NAME: str LST_NAME: Mapped[str]
LST_NAME_SH: str LST_NAME_SH: Mapped[str]
@classmethod
def fetch_one(cls, id: int) -> type[Self]:
cache = cls.cache()
@cache.cache()
def wrapper(id):
return super(MPLSTDB, cls).fetch_one(id)
return wrapper(id)
class MPAppTaskDB(__BaseDB): class MPAppTaskDB(__Base):
__tablename__: str = "APP_TASK" __tablename__: str = "APP_TASK"
ID_APP_TASK: int | None = None ID_APP_TASK: Mapped[int] = mapped_column(BIGINT, primary_key=True)
APP_TASK_ID_SOTR: int = None APP_TASK_ID_SOTR: Mapped[int] = mapped_column(BIGINT)
APP_TASK_ID_APP_TASK: int = None APP_TASK_ID_APP_TASK: Mapped[int] = mapped_column(BIGINT, ForeignKey("APP_TASK.ID_APP_TASK"))
APP_TASK_DT_START_PLN: datetime = None APP_TASK_DT_START_PLN: Mapped[datetime]
APP_TASK_DT_END_PLN: datetime = None APP_TASK_DT_END_PLN: Mapped[datetime]
APP_TASK_DT_START_FACT: datetime | None = None APP_TASK_DT_START_FACT: Mapped[datetime | None]
APP_TASK_DT_END_FACT: datetime | None = None APP_TASK_DT_END_FACT: Mapped[datetime | None]
APP_TASK_STATUS: int = None APP_TASK_STATUS: Mapped[int]
APP_TASK_TIP: int = None APP_TASK_TIP: Mapped[int]
APP_TASK_TEXT: str = None APP_TASK_TEXT: Mapped[str]
APP_TASK_DEL: int = None APP_TASK_DEL: Mapped[int]
subtasks: Mapped['MPAppTaskDB'] = relationship('MPAppTaskDB', remote_side=[ID_APP_TASK])
@property
def status(self) -> MPLSTDB:
return MPLSTDB.fetch_one(self.APP_TASK_STATUS)
@property @property
def is_subtask(self) -> bool: def is_subtask(self) -> bool:
return self.ID_APP_TASK != self.APP_TASK_ID_APP_TASK return self.ID_APP_TASK != self.APP_TASK_ID_APP_TASK
@property
def task_type(self) -> MPLSTDB:
return MPLSTDB.fetch_one(self.APP_TASK_TIP)
@property class MPAppEventDB(__Base):
def subtasks(self) -> list['MPAppTaskDB']:
t: MPAppTaskDB = MPAppTaskDB.get_table()
return MPAppTaskDB.fetch_all(
(t.APP_TASK_ID_APP_TASK == self.ID_APP_TASK) & (t.APP_TASK_ID_APP_TASK != t.ID_APP_TASK) & (
t.APP_TASK_DEL == 0))
@property
def events(self) -> list['MPAppEventDB']:
return MPAppEventDB.fetch_all(MPAppEventDB.get_table().APP_EVENT_ID_REC == self.ID_APP_TASK)
@property
def params(self) -> list['MPAppParamDB']:
return MPAppParamDB.fetch_all(MPAppParamDB.get_table().APP_PARAM_ID_REC == self.ID_APP_TASK)
class EventData(__BaseDB):
key: MPLSTDB
value: Any
class MPAppEventDB(__BaseDB):
__tablename__: str = "APP_EVENT" __tablename__: str = "APP_EVENT"
ID_APP_EVENT: int = None ID_APP_EVENT: Mapped[int] = mapped_column(primary_key=True)
APP_EVENT_ID_SOTR: int = None APP_EVENT_ID_SOTR: Mapped[int]
APP_EVENT_ID_REC: int = None APP_EVENT_ID_REC: Mapped[int]
APP_EVENT_VID: int = None APP_EVENT_VID: Mapped[int]
APP_EVENT_TIP: int = None APP_EVENT_TIP: Mapped[int]
APP_EVENT_DT: datetime | None = None APP_EVENT_DT: Mapped[datetime | None]
APP_EVENT_TEXT: str | None = None APP_EVENT_TEXT: Mapped[str | None]
APP_EVENT_DATA: Json APP_EVENT_DATA: Mapped[str]
APP_EVENT_DEL: int = 0 APP_EVENT_DEL: Mapped[int]
@property
def event_type(self) -> MPLSTDB:
return MPLSTDB.fetch_one(self.APP_EVENT_TIP)
@property
def event_data(self) -> list[EventData]:
return [EventData(key=MPLSTDB.fetch_one(key), value=value) for d in list(self.APP_EVENT_DATA) for key, value in
d.items()]
class MPMSTDB(__BaseDB): class MPMSTDB(__Base):
__read_only = True
__tablename__ = "MST" __tablename__ = "MST"
ID_MST: int = Field(default=0) ID_MST: Mapped[int] = mapped_column(primary_key=True)
MST_PR_OTHER: int = Field(default=0) MST_PR_OTHER: Mapped[int]
MST_ID_KG: int = Field(default=0) MST_ID_KG: Mapped[int]
MST_ID_SRV: int = Field(default=0) MST_ID_SRV: Mapped[int]
MST_ID_SETTLEMENT: Optional[int] = Field(default=0) MST_ID_SETTLEMENT: Mapped[Optional[int]]
MST_SID: Optional[str] = Field(default=None) MST_SID: Mapped[Optional[str]]
MST_NAME: Optional[str] = Field(default=None) MST_NAME: Mapped[Optional[str]]
MST_CLI_NAME: Optional[str] = Field(default=None) MST_CLI_NAME: Mapped[Optional[str]]
MST_CODE: int = Field(default=0) MST_CODE: Mapped[int]
MST_CODE_PODR_NDS: int | None = Field(default=0) MST_CODE_PODR_NDS: Mapped[int | None]
MST_CODE_PODR_BN: int | None = Field(default=0) MST_CODE_PODR_BN: Mapped[int | None]
MST_PR_TTNINPUT: int = Field(default=0) MST_PR_TTNINPUT: Mapped[int]
MST_PR_TTNOUTPUT: int = Field(default=0) MST_PR_TTNOUTPUT: Mapped[int]
MST_PR_AEX: int = Field(default=0) MST_PR_AEX: Mapped[int]
MST_PR_AEX_ADR: Optional[int] = Field(default=0) MST_PR_AEX_ADR: Mapped[Optional[int]]
MST_ID_MST_TTNOUTPUT: int = Field(default=0) MST_ID_MST_TTNOUTPUT: Mapped[int]
MST_PR_SORT: int = Field(default=0) MST_PR_SORT: Mapped[int]
MST_PR_PVZ: int = Field(default=0) MST_PR_PVZ: Mapped[int]
MST_PR_VIRT: int = Field(default=0) MST_PR_VIRT: Mapped[int]
MST_PR_INOTHER: int = Field(default=0) MST_PR_INOTHER: Mapped[int]
MST_PR_ZAKG: int = Field(default=0) MST_PR_ZAKG: Mapped[int]
MST_PR_FAR: int = Field(default=0) MST_PR_FAR: Mapped[int]
MST_PR_KKT: int = Field(default=0) MST_PR_KKT: Mapped[int]
MST_PR_CC: int = Field(default=0) MST_PR_CC: Mapped[int]
MST_PR_AS: int = Field(default=0) MST_PR_AS: Mapped[int]
MST_KM: int = Field(default=0) MST_KM: Mapped[int]
MST_MP: int = Field(default=0) MST_MP: Mapped[int]
MST_ID_AGENT_AS: int = Field(default=0) MST_ID_AGENT_AS: Mapped[int]
MST_PR_NOLIM_AS: int = Field(default=0) MST_PR_NOLIM_AS: Mapped[int]
MST_PR_WC_AS: int = Field(default=0) MST_PR_WC_AS: Mapped[int]
MST_PR_TRS: int = Field(default=0) MST_PR_TRS: Mapped[int]
MST_ID_REGION: int = Field(default=0) MST_ID_REGION: Mapped[int]
MST_ADDRESS_CODE: int = Field(default=0) MST_ADDRESS_CODE: Mapped[int]
MST_ID_KLADR_DOM: Optional[int] = Field(default=0) MST_ID_KLADR_DOM: Mapped[Optional[int]]
MST_SHIR: float = Field(default=0.0) MST_SHIR: Mapped[float]
MST_DOLG: float = Field(default=0.0) MST_DOLG: Mapped[float]
MST_ADR_STOR: Optional[str] = Field(default=None) MST_ADR_STOR: Mapped[Optional[str]]
MST_FUNC_MASK: int = Field(default=0) MST_FUNC_MASK: Mapped[int]
MST_ID_SRV_CALL: int = Field(default=0) MST_ID_SRV_CALL: Mapped[int]
MST_ID_MST_CALL: int = Field(default=0) MST_ID_MST_CALL: Mapped[int]
MST_PR_DIRECT: int = Field(default=0) MST_PR_DIRECT: Mapped[int]
MST_NAME_DIRECT: Optional[str] = Field(default=None) MST_NAME_DIRECT: Mapped[Optional[str]]
MST_PR_NOTE: int = Field(default=0) MST_PR_NOTE: Mapped[int]
MST_PR_NOTSITE: int = Field(default=0) MST_PR_NOTSITE: Mapped[int]
MST_PR_GREEN: int = Field(default=0) MST_PR_GREEN: Mapped[int]
MST_PR_GREENORK: int = Field(default=0) MST_PR_GREENORK: Mapped[int]
MST_PR_GREENPRINTER: int = Field(default=0) MST_PR_GREENPRINTER: Mapped[int]
MST_PR_VID_TR_VD: int = Field(default=0) MST_PR_VID_TR_VD: Mapped[int]
MST_PR_BAN_IN: int = Field(default=0) MST_PR_BAN_IN: Mapped[int]
MST_PR_NO_CLIENT_CODES: int = Field(default=0) MST_PR_NO_CLIENT_CODES: Mapped[int]
MST_PR_NO_STTN02: int = Field(default=0) MST_PR_NO_STTN02: Mapped[int]
MST_PR_NO_EEU: int = Field(default=0) MST_PR_NO_EEU: Mapped[int]
MST_VID_CALC_FOBYOM: int = Field(default=0) MST_VID_CALC_FOBYOM: Mapped[int]
MST_TXT: Optional[str] = Field(default=None) MST_TXT: Mapped[Optional[str]]
MST_DEL: int = Field(default=0) MST_DEL: Mapped[int]
MST_CH: Optional[datetime] = Field(default=None) MST_CH: Mapped[Optional[datetime]]
MST_WCH: int = Field(default=0) MST_WCH: Mapped[int]
MST_IMP: Optional[datetime] = Field(default=None) MST_IMP: Mapped[Optional[datetime]]
MST_MPOST: int = Field(default=0) MST_MPOST: Mapped[int]
MST_SEANS: int = Field(default=0) MST_SEANS: Mapped[int]
MST_OWNERMST: int = Field(default=0) MST_OWNERMST: Mapped[int]
MST_CR: Optional[datetime] = Field(default=None) MST_CR: Mapped[Optional[datetime]]
MST_WCR: int = Field(default=0) MST_WCR: Mapped[int]
MST_FIMP: Optional[datetime] = Field(default=None) MST_FIMP: Mapped[Optional[datetime]]
MST_ID_MST_SYNONYM: int = Field(default=0) MST_ID_MST_SYNONYM: Mapped[int]
MST_NAME_OLD: Optional[str] = Field(default=None) MST_NAME_OLD: Mapped[Optional[str]]
MST_SRC_OLD: int = Field(default=0) MST_SRC_OLD: Mapped[int]
MST_UPPERNAME_OLD: Optional[str] = Field(default=None) MST_UPPERNAME_OLD: Mapped[Optional[str]]
MST_TXT_AEX: Optional[str] = Field(default=None) MST_TXT_AEX: Mapped[Optional[str]]
MST_PR_NODOOR_AEX: int = Field(default=0) MST_PR_NODOOR_AEX: Mapped[int]
@classmethod
def fetch_one(cls, id: int) -> type[Self]:
cache = cls.cache()
@cache.cache()
def wrapper(id):
return super(MPMSTDB, cls).fetch_one(id)
return wrapper(id)
class MPTRSDB(__BaseDB): class MPTRSDB(__Base):
__tablename__ = "TRS" __tablename__ = "TRS"
__read_only = True ID_TRS: Mapped[int] = mapped_column(primary_key=True)
ID_TRS: int = Field(default=0) TRS_PR_TEST: Mapped[int]
TRS_PR_TEST: int = Field(default=0) TRS_PR_TEST_ID_SOTR: Mapped[int]
TRS_PR_TEST_ID_SOTR: int = Field(default=0) TRS_PR_TEST_DT: Mapped[Optional[datetime]]
TRS_PR_TEST_DT: Optional[datetime] = Field(default=None) TRS_PR: Mapped[int]
TRS_PR: int = Field(default=0) TRS_PR_UP: Mapped[int]
TRS_PR_UP: int = Field(default=0) TRS_ID_LST_PR: Mapped[int]
TRS_ID_LST_PR: int = Field(default=0) TRS_ID_LST_VID: Mapped[int]
TRS_ID_LST_VID: int = Field(default=0) TRS_ID_LSTU_TIP: Mapped[int]
TRS_ID_LSTU_TIP: int = Field(default=0) TRS_SID: Mapped[Optional[str]]
TRS_SID: Optional[str] = Field(default=None) TRS_SID_GOST: Mapped[Optional[str]]
TRS_SID_GOST: Optional[str] = Field(default=None) TRS_SID_OLD: Mapped[Optional[str]]
TRS_SID_OLD: Optional[str] = Field(default=None) TRS_SRC_OLD: Mapped[int]
TRS_SRC_OLD: int = Field(default=0) TRS_PR_VLAD: Mapped[int]
TRS_PR_VLAD: int = Field(default=0) TRS_ID_AGENT_AS: Mapped[int]
TRS_ID_AGENT_AS: int = Field(default=0) TRS_VES: Mapped[float]
TRS_VES: float = Field(default=0.0) TRS_OBYOM: Mapped[float]
TRS_OBYOM: float = Field(default=0.0) TRS_PR_LTOR: Mapped[int]
TRS_PR_LTOR: int = Field(default=0) TRS_PR_LLEN: Mapped[int]
TRS_PR_LLEN: int = Field(default=0) TRS_PR_LTOP: Mapped[int]
TRS_PR_LTOP: int = Field(default=0) TRS_PR_TEPL: Mapped[int]
TRS_PR_TEPL: int = Field(default=0) TRS_PR_TEPL_WHERE: Mapped[int]
TRS_PR_TEPL_WHERE: int = Field(default=0) TRS_OBYOM_TEPL: Mapped[float]
TRS_OBYOM_TEPL: float = Field(default=0.0) TRS_PR_NOZAGRGRUZ: Mapped[int]
TRS_PR_NOZAGRGRUZ: int = Field(default=0) TRS_CNT_AXIS: Mapped[int]
TRS_CNT_AXIS: int = Field(default=0) TRS_PRIM: Mapped[Optional[str]]
TRS_PRIM: Optional[str] = Field(default=None) TRS_INFO: Mapped[Optional[str]]
TRS_INFO: Optional[str] = Field(default=None) TRS_TARA: Mapped[Optional[float]]
TRS_TARA: Optional[float] = Field(default=0.0) TRS_TYPEPROPERTY: Mapped[int]
TRS_TYPEPROPERTY: int = Field(default=0) TRS_DOGAREND: Mapped[Optional[str]]
TRS_DOGAREND: Optional[str] = Field(default=None) TRS_1C_D_AKT: Mapped[Optional[date]]
TRS_1C_D_AKT: Optional[date] = Field(default=None) TRS_1C_NOMMSG: Mapped[int]
TRS_1C_NOMMSG: int = Field(default=0) TRS_1C_DEL: Mapped[int]
TRS_1C_DEL: int = Field(default=0) TRS_1C_DATEEND: Mapped[Optional[date]]
TRS_1C_DATEEND: Optional[date] = Field(default=None) TRS_DEL: Mapped[int]
TRS_DEL: int = Field(default=0) TRS_CR: Mapped[Optional[datetime]]
TRS_CR: Optional[datetime] = Field(default=None) TRS_WCR: Mapped[int]
TRS_WCR: int = Field(default=0) TRS_CH: Mapped[Optional[datetime]]
TRS_CH: Optional[datetime] = Field(default=None) TRS_WCH: Mapped[int]
TRS_WCH: int = Field(default=0) TRS_OWNERMST: Mapped[int]
TRS_OWNERMST: int = Field(default=0) TRS_SEANS: Mapped[int]
TRS_SEANS: int = Field(default=0) TRS_IMP: Mapped[Optional[datetime]]
TRS_IMP: Optional[datetime] = Field(default=None) TRS_FIMP: Mapped[Optional[datetime]]
TRS_FIMP: Optional[datetime] = Field(default=None) TRS_MPOST: Mapped[int]
TRS_MPOST: int = Field(default=0)
@classmethod
def fetch_one(cls, id: int) -> type[Self]:
cache = cls.cache()
@cache.cache()
def wrapper(id):
return super(MPTRSDB, cls).fetch_one(id)
return wrapper(id)
class MPMarshDB(__BaseDB): class MPMarshDB(__Base):
__read_only = True
__tablename__ = "MARSH" __tablename__ = "MARSH"
ID_MARSH: int = Field(default=0) ID_MARSH: Mapped[int] = mapped_column(primary_key=True)
MARSH_PR: int = Field(default=0) MARSH_PR: Mapped[int]
MARSH_PR_PLAN: int = Field(default=0) MARSH_PR_PLAN: Mapped[int]
MARSH_PR_VLAD: int = Field(default=0) MARSH_PR_VLAD: Mapped[int]
MARSH_PR_DOP: int = Field(default=0) MARSH_PR_DOP: Mapped[int]
MARSH_PR_TEPL: int = Field(default=0) MARSH_PR_TEPL: Mapped[int]
MARSH_KEY_GPREF: Optional[str] = Field(default=None, min_length=1, max_length=16) MARSH_KEY_GPREF: Mapped[Optional[str]]
MARSH_KEY_PREF: Optional[str] = Field(default=None, min_length=1, max_length=32) MARSH_KEY_PREF: Mapped[Optional[str]]
MARSH_NAME: Optional[str] = Field(default=None, min_length=1, max_length=128) MARSH_NAME: Mapped[Optional[str]]
MARSH_D_N: Optional[date] = Field(default=None) MARSH_D_N: Mapped[Optional[date]]
MARSH_D_K: Optional[date] = Field(default=None) MARSH_D_K: Mapped[Optional[date]]
MARSH_ID_MST_OTPR: int = Field(default=0) MARSH_ID_MST_OTPR: Mapped[int]
MARSH_ID_MST_NAZN: int = Field(default=0) MARSH_ID_MST_NAZN: Mapped[int]
MARSH_DAYS_WEEK: int = Field(default=0) MARSH_DAYS_WEEK: Mapped[int]
MARSH_T_OTPR: float = Field(default=0.0) MARSH_T_OTPR: Mapped[float]
MARSH_DATE_OUT: Optional[date] = Field(default=None) MARSH_DATE_OUT: Mapped[Optional[date]]
MARSH_PRICE: Optional[float] = Field(default=0.0) MARSH_PRICE: Mapped[Optional[float]]
MARSH_KM: Optional[float] = Field(default=0.0) MARSH_KM: Mapped[Optional[float]]
MARSH_TXT: Optional[str] = Field(default=None, min_length=1, max_length=512) MARSH_TXT: Mapped[Optional[str]]
MARSH_DEL: int = Field(default=0) MARSH_DEL: Mapped[int]
class MPMarshTRSDB(__BaseDB): class MPMarshTRSDB(__Base):
__tablename__ = "MARSH_TRS" __tablename__ = "MARSH_TRS"
__read_only = True ID_MARSH_TRS: Mapped[int] = mapped_column(primary_key=True)
ID_MARSH_TRS: int = Field(default=0) MARSH_TRS_ID_MARSH: Mapped[int]
MARSH_TRS_ID_MARSH: int = Field(default=0) MARSH_TRS_DATE: Mapped[Optional[date]]
MARSH_TRS_DATE: Optional[date] = Field(default=None) MARSH_TRS_ID_TRS: Mapped[int]
MARSH_TRS_ID_TRS: int = Field(default=0) MARSH_TRS_TRS_PR_COLDONLY: Mapped[Optional[int]]
MARSH_TRS_TRS_PR_COLDONLY: Optional[int] = Field(default=0) MARSH_TRS_ID_PRIC: Mapped[int]
MARSH_TRS_ID_PRIC: int = Field(default=0) MARSH_TRS_PRIC_PR_COLDONLY: Mapped[Optional[int]]
MARSH_TRS_PRIC_PR_COLDONLY: Optional[int] = Field(default=0) MARSH_TRS_ID_SOTR: Mapped[int]
MARSH_TRS_ID_SOTR: int = Field(default=0) MARSH_TRS_DT_DELIVERY: Mapped[Optional[datetime]]
MARSH_TRS_DT_DELIVERY: Optional[datetime] = Field(default=None) MARSH_TRS_PR: Mapped[int]
MARSH_TRS_PR: int = Field(default=0) MARSH_TRS_COMMENT: Mapped[Optional[str]]
MARSH_TRS_COMMENT: Optional[str] = Field(default=None, max_length=4096) MARSH_TRS_TARIFF: Mapped[float]
MARSH_TRS_TARIFF: float = Field(default=0.0) MARSH_TRS_DEL: Mapped[int]
MARSH_TRS_DEL: int = Field(default=0) MARSH_TRS_OWNERMST: Mapped[int]
MARSH_TRS_OWNERMST: int = Field(default=0) MARSH_TRS_MPOST: Mapped[Optional[int]]
MARSH_TRS_MPOST: Optional[int] = Field(default=0) MARSH_TRS_CR: Mapped[Optional[datetime]]
MARSH_TRS_CR: Optional[datetime] = Field(default=None) MARSH_TRS_WCR: Mapped[int]
MARSH_TRS_WCR: int = Field(default=0) MARSH_TRS_IMP: Mapped[Optional[datetime]]
MARSH_TRS_IMP: Optional[datetime] = Field(default=None) MARSH_TRS_CH: Mapped[Optional[datetime]]
MARSH_TRS_CH: Optional[datetime] = Field(default=None) MARSH_TRS_WCH: Mapped[int]
MARSH_TRS_WCH: int = Field(default=0) MARSH_TRS_SEANS: Mapped[int]
MARSH_TRS_SEANS: int = Field(default=0) MARSH_TRS_FIMP: Mapped[Optional[datetime]]
MARSH_TRS_FIMP: Optional[datetime] = Field(default=None)
@property
def marsh(self) -> MPMarshDB:
return MPMarshDB.fetch_one(self.MARSH_TRS_ID_MARSH)
@property
def trs(self) -> MPTRSDB:
return MPTRSDB.fetch_one(self.MARSH_TRS_ID_TRS)
@property
def trailer(self) -> MPTRSDB:
return MPTRSDB.fetch_one(self.MARSH_TRS_ID_PRIC)
class MPAppParamDB(__BaseDB): class MPAppParamDB(__Base):
__tablename__ = "APP_PARAM" __tablename__ = "APP_PARAM"
__read_only = True APP_PARAM_ID_REC: Mapped[int] = mapped_column(primary_key=True)
APP_PARAM_ID_REC: int = Field(default=0) APP_PARAM_STR: Mapped[Optional[str]]
APP_PARAM_STR: Optional[str] = Field(default=None, min_length=1, max_length=1024) APP_PARAM_VID: Mapped[int]
APP_PARAM_VID: int = Field(default=0) APP_PARAM_TIP: Mapped[int]
APP_PARAM_TIP: int = Field(default=0) APP_PARAM_DEL: Mapped[int]
APP_PARAM_DEL: int = Field(default=0) APP_PARAM_CR: Mapped[Optional[datetime]]
APP_PARAM_CR: Optional[datetime] = Field(default=None) APP_PARAM_WCR: Mapped[int]
APP_PARAM_WCR: int = Field(default=0) APP_PARAM_CH: Mapped[Optional[datetime]]
APP_PARAM_CH: Optional[datetime] = Field(default=None) APP_PARAM_WCH: Mapped[int]
APP_PARAM_WCH: int = Field(default=0)
@property
def param_type(self) -> MPLSTDB:
return MPLSTDB.fetch_one(self.APP_PARAM_TIP)
@property
def related_table(self) -> Union['MPMSTDB', 'MPMarshTRSDB', 'MPTRSDB', 'MPMarshDB']: # SET RELATIONS
query = {
"ID_MARSH_TRS": MPMarshTRSDB,
"ID_MST": MPMSTDB,
"ID_TRS": MPTRSDB,
"ID_MARSH": MPMarshDB
}
return query[self.param_type.LST_NAME_SH].fetch_one(self.APP_PARAM_STR)

View File

@ -1,6 +1,10 @@
from .base import BaseStorage
from .profile_storage import Storage as ProfileStorage from .profile_storage import Storage as ProfileStorage
from .task_storage import Storage as TaskStorage from .task_storage import Storage as TaskStorage
from .note_storage import Storage as NoteStorage
base_storage = BaseStorage()
profile_storage = ProfileStorage()
task_storage = TaskStorage()
note_storage = NoteStorage()
profile = ProfileStorage()
task = TaskStorage()

View File

@ -1,6 +1,6 @@
import socket import socket
from contextlib import contextmanager from contextlib import contextmanager
from typing import ContextManager from typing import ContextManager, NewType, TypeVar
import sqlalchemy import sqlalchemy
from firebird.driver import Cursor, Connection from firebird.driver import Cursor, Connection
@ -9,6 +9,11 @@ from sqlalchemy import text
from core.database.db import engine from core.database.db import engine
def row_to_type(self: sqlalchemy.Row):
return type("KeyedROW", (), {key.upper(): val for key, val in self._mapping.items()})
class BaseStorage: class BaseStorage:
_pool = engine _pool = engine
@ -56,3 +61,6 @@ where ID_SEANS = RDB$GET_CONTEXT('USER_SESSION', 'ID_SEANS');
# yield cursor # yield cursor
# finally: # finally:
# cursor.close() # cursor.close()
sqlalchemy.Row.row_to_type = row_to_type

View File

@ -0,0 +1,58 @@
import datetime
import json
from typing import Optional
import sqlalchemy
from sqlalchemy import text
from .base import BaseStorage
from ..model.note.db import AppNoteDB
class Storage(BaseStorage):
def fetch_all_notes_for_user(self, user_id: int):
stmt = text("""
select * from APP_NOTE where APP_NOTE_ID_SOTR = :user_id and APP_NOTE_DEL = 0;
""")
with self.get_session() as session:
session: sqlalchemy.Connection
res = [x.row_to_type() for x in session.execute(stmt, {
"user_id": user_id
}).fetchall()]
return [AppNoteDB(id=row.ID_APP_NOTE, user_id=row.APP_NOTE_ID_SOTR, task_id=row.APP_NOTE_ID_APP_TASK,
note_status=row.APP_NOTE_STATUS, tip=row.APP_NOTE_TIP, text=row.APP_NOTE_TEXT) for row in res]
def update_note(self, id: int, status: int, user_id: int):
stmt = text("""
insert into APP_EVENT (APP_EVENT_DT, APP_EVENT_ID_SOTR, APP_EVENT_TEXT, APP_EVENT_DATA, APP_EVENT_VID, APP_EVENT_ID_REC) values (current_timestamp, :user_id, 'Изменен статус уведомлений', :event_data, 8797, :note_id)
""")
event_data = json.dumps([{"8794": str(status)}], separators=(',', ":"))
with self.get_session() as session:
session: sqlalchemy.Connection
session.execute(stmt, {
"user_id": user_id,
"event_data": event_data,
"note_id": id
})
session.commit()
def create_note(self, user_id: int, note_text: str, time_created: datetime.datetime, task_id: Optional[int] = None):
stmt = text("""
insert into APP_EVENT (APP_EVENT_DT, APP_EVENT_ID_SOTR, APP_EVENT_TEXT, APP_EVENT_DATA, APP_EVENT_VID) values (:time_created, :user_id, :text, :event_data, 8795)
""")
if (task_id is not None):
event_data = json.dumps([{"ID_APP_TASK": str(task_id)}], separators=(",", ":"))
else:
event_data = None
with self.get_session() as session:
session: sqlalchemy.Connection
session.execute(stmt, {
"time_created": time_created,
"user_id": user_id,
"text": note_text,
"event_data": event_data
})
session.commit()

View File

@ -1,13 +1,17 @@
import typing import typing
from dataclasses import field
from core.model.note.db import AppNoteDB
from core.model.task.db import DBAppTask, DBSubTask, DBEvent, DBMarsh, DBTRS, DBMST, Location
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from typing import Union
import strawberry import strawberry
from strawberry.types import Info
from sqlalchemy import select
from core.model.task.enums import MarshTemperatureProperty from core.model.task.enums import StatusEnum
from core.storage import task from core.storage import base_storage, task_storage, note_storage
@strawberry.enum @strawberry.enum
@ -30,87 +34,136 @@ class MarshTemperaturePropertyQl(Enum):
UNDEFINED = 0 UNDEFINED = 0
@strawberry.enum
class MarshTemperaturePropertyQL(Enum):
HOT = 1
COLD = 2
UNDEFINED = 0
@strawberry.type @strawberry.type
class Query: class Query:
@strawberry.field @strawberry.field
def tasks(self, user_id: str) -> list['AppTaskQL']: def tasks(self, user_id: str, is_planned: typing.Optional[bool] = False, is_completed: typing.Optional[bool] = False) -> list['AppTaskQL']:
tasks = task.fetch_tasks_with_subtasks(user_id=int(user_id)) tasks = task_storage.fetch_tasks_with_subtasks(user_id)
returned_list: list['AppTaskQL'] = []
for t in tasks:
updated_task = AppTaskQL(
id=str(t.id),
start_pln=t.start_pln,
end_pln=t.end_pln,
start_fact=t.start_fact,
end_fact=t.end_fact,
status=t.status.value,
task_type=t.task_type.value,
text=t.text,
route=MarshQL(
id=str(t.route.id),
temperature_property=t.route.temperature_property.value,
name=t.route.name,
trailer=TRSQL(
id=str(t.route.trailer.id),
gost=t.route.trailer.gost
) if t.route.trailer else None,
truck=TRSQL(
id=str(t.route.truck.id),
gost=t.route.truck.gost
) if t.route.truck else None,
) if t.route else None,
events=[EventQl(
id=e.id,
type=e.type,
text=e.text,
event_data=e.event_data,
event_datetime=e.event_datetime
) for e in t.events]
)
returned_list.append(updated_task)
return returned_list if is_planned:
return [x for x in tasks if x.status == StatusEnum.NOT_DEFINED]
if is_completed:
return [x for x in tasks if x.status == StatusEnum.COMPLETED]
return tasks
@strawberry.field
def task(self, user_id: str, task_id: typing.Optional[str] = None, is_active: typing.Optional[bool] = None) -> \
typing.Optional[
'AppTaskQL']:
tasks = task_storage.fetch_tasks_with_subtasks(user_id)
if task_id is not None:
try:
return next(t for t in tasks if t.id == int(task_id))
except StopIteration:
return None
if is_active is not None:
try:
return next(t for t in tasks if t.status == StatusEnum.IN_PROGRESS)
except StopIteration:
return None
@strawberry.field
def notes(self, user_id: str) -> list['AppNoteQL']:
return note_storage.fetch_all_notes_for_user(int(user_id))
@strawberry.field
def subtask(self, user_id: str, subtask_id: str) -> typing.Optional['SubtaskQL']:
try:
return next(s for t in task_storage.fetch_tasks_with_subtasks(int(user_id)) for s in t.subtasks if
s.id == int(subtask_id))
except StopIteration:
return None
@strawberry.type @strawberry.experimental.pydantic.type(model=Location)
class AppTaskQL: class LocationQL:
lat: float
lon: float
@strawberry.experimental.pydantic.type(model=DBMST)
class MSTQL:
name: str
location: LocationQL
@strawberry.experimental.pydantic.type(model=DBSubTask)
class SubtaskQL:
id: str id: str
start_pln: datetime start_pln: strawberry.auto
end_pln: datetime end_pln: strawberry.auto
start_fact: datetime | None start_fact: strawberry.auto
end_fact: datetime | None end_fact: strawberry.auto
status: StatusEnumQl status: StatusEnumQl
task_type: TaskTypeEnumQl task_type: int
text: str text: str
events = [] station: typing.Optional[MSTQL] = None
subtasks = []
route = None
@strawberry.type @strawberry.experimental.pydantic.type(model=DBEvent)
class MarshQL: class AppEventQL:
id: str id: str
temperature_property: MarshTemperaturePropertyQl
name: str
trailer: typing.Optional['TRSQL'] = None
truck: typing.Optional['TRSQL'] = None
@strawberry.type
class TRSQL:
id: str | None
gost: str | None
@strawberry.type
class EventQl:
id: int
type: str type: str
text: str text: str
event_data: dict
event_datetime: datetime event_datetime: datetime
@strawberry.experimental.pydantic.type(model=DBTRS)
class TRSQL:
gost: str | None
@strawberry.experimental.pydantic.type(model=DBMarsh)
class AppRouteQL:
temperature_property: MarshTemperaturePropertyQL
name: str
trailer: typing.Optional[TRSQL]
truck: typing.Optional[TRSQL]
@strawberry.experimental.pydantic.type(model=AppNoteDB)
class AppNoteQL:
id: str
user_id: str
task_id: str
note_status: int
tip: int
text: str
@strawberry.experimental.pydantic.type(model=DBAppTask)
class AppTaskQL:
id: str
profile_id: strawberry.auto
start_pln: strawberry.auto
end_pln: strawberry.auto
start_fact: strawberry.auto
end_fact: strawberry.auto
status: StatusEnumQl
task_type: int
text: strawberry.auto
events: list[AppEventQL]
subtasks: list[SubtaskQL]
route: 'AppRouteQL'
@strawberry.field
def active_subtask(self) -> typing.Optional[SubtaskQL]:
try:
return next(x for x in self.subtasks if x.status == x.status.IN_PROGRESS)
except StopIteration:
return None
schema = strawberry.Schema(query=Query) schema = strawberry.Schema(query=Query)

View File

@ -8,7 +8,7 @@ from pydantic_extra_types.phone_numbers import PhoneNumber
from core.config import Config from core.config import Config
from core.errors.auth.errors import profile_not_founded, incorrect_phone_number from core.errors.auth.errors import profile_not_founded, incorrect_phone_number
from core.storage import profile from core.storage import profile_storage
router = APIRouter(prefix="/auth") router = APIRouter(prefix="/auth")
@ -34,19 +34,19 @@ async def get_authorization_code(req: PhoneNumberRequest):
if not 10 <= len(req.phoneNumber) <= 12: if not 10 <= len(req.phoneNumber) <= 12:
incorrect_phone_number() incorrect_phone_number()
p = profile.get_profile_by_phone("".join(char for char in req.phoneNumber if char.isdigit())) p = profile_storage.get_profile_by_phone("".join(char for char in req.phoneNumber if char.isdigit()))
if not p: if not p:
profile_not_founded() profile_not_founded()
return {"code": profile.generate_profile_auth_code(p.id, p.phone_number)} return {"code": profile_storage.generate_profile_auth_code(p.id, p.phone_number)}
@router.post("/phone/code") @router.post("/phone/code")
async def get_access_token(form_data: OAuth2PasswordRequestForm = Depends()) -> Token: async def get_access_token(form_data: OAuth2PasswordRequestForm = Depends()) -> Token:
p = profile.get_profile_by_phone(''.join(char for char in form_data.username if char.isdigit())) p = profile_storage.get_profile_by_phone(''.join(char for char in form_data.username if char.isdigit()))
if not p: if not p:
profile_not_founded() profile_not_founded()
check = profile.check_profile_code(p.id, form_data.password) check = profile_storage.check_profile_code(p.id, form_data.password)
if not check: if not check:
raise HTTPException(status_code=403, detail="Incorrect code") raise HTTPException(status_code=403, detail="Incorrect code")

View File

View File

View File

@ -10,21 +10,21 @@ from core.model.profile.db import ProfileDB
from core.model.task.db import DBAppTask, DBSubTask, DBEvent from core.model.task.db import DBAppTask, DBSubTask, DBEvent
from core.model.task.enums import StatusEnum from core.model.task.enums import StatusEnum
from core.model.task.requests import SetTaskStatusActiveRequest, SetSubtaskStatusRequest, UpdTaskRequest, UpdTaskData from core.model.task.requests import SetTaskStatusActiveRequest, SetSubtaskStatusRequest, UpdTaskRequest, UpdTaskData
from core.storage import task from core.storage import task_storage
from core.model.task.db2 import MPAppTaskDB from core.model.task.db2 import MPAppTaskDB
router = APIRouter(prefix="/tasks", tags=["Tasks and subtasks"]) router = APIRouter(prefix="/tasks", tags=["Tasks and subtasks"])
@router.get("/test", description="Fetch Task for authenticated user") # @router.get("/test", description="Fetch Task for authenticated user")
async def get_task_test(user: ProfileDB = Depends(get_user_from_token)) -> list[MPAppTaskDB]: # async def get_task_test(user: ProfileDB = Depends(get_user_from_token)) -> list[MPAppTaskDB]:
return MPAppTaskDB.fetch_all() # return MPAppTaskDB.fetch_all()
@router.get("", description="Fetch Task for authenticated user") @router.get("", description="Fetch Task for authenticated user")
async def get_tasks(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]: async def get_tasks(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]:
return task.fetch_tasks_with_subtasks(user.id) return task_storage.fetch_tasks_with_subtasks(user.id)
@router.post("") @router.post("")
@ -83,7 +83,7 @@ async def upd_task(req: UpdTaskRequest, user: ProfileDB = Depends(get_user_from_
} }
} }
tasks = task.fetch_tasks_with_subtasks(user.id) tasks = task_storage.fetch_tasks_with_subtasks(user.id)
available_tasks_ids = [x.id for x in tasks] + [sbt.id for t in tasks for sbt in t.subtasks] available_tasks_ids = [x.id for x in tasks] + [sbt.id for t in tasks for sbt in t.subtasks]
req.data.sort(key=lambda u: u.dt) req.data.sort(key=lambda u: u.dt)
@ -123,21 +123,21 @@ async def upd_task(req: UpdTaskRequest, user: ProfileDB = Depends(get_user_from_
except KeyError as exc: except KeyError as exc:
update_task_by_chain_failed(exc) update_task_by_chain_failed(exc)
[task.update_task(event, user.id) for event in req.data] [task_storage.update_task(event, user.id) for event in req.data]
return task.fetch_tasks_with_subtasks(user.id) return task_storage.fetch_tasks_with_subtasks(user.id)
@router.get("/planned") @router.get("/planned")
async def get_planned_tasks(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]: async def get_planned_tasks(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]:
# TODO Rebuild this method to fetch only planned tasks # TODO Rebuild this method to fetch only planned tasks
tasks = task.fetch_tasks_with_subtasks(user_id=user.id) tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id)
return [x for x in tasks if x.status == StatusEnum.NOT_DEFINED] return [x for x in tasks if x.status == StatusEnum.NOT_DEFINED]
@router.get("/active") @router.get("/active")
async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> DBAppTask | dict: async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> DBAppTask | dict:
# TODO Rebuild this method to fetch only active tasks # TODO Rebuild this method to fetch only active tasks
tasks = task.fetch_tasks_with_subtasks(user_id=user.id) tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id)
try: try:
return next(x for x in tasks if x.status == StatusEnum.IN_PROGRESS) return next(x for x in tasks if x.status == StatusEnum.IN_PROGRESS)
except StopIteration: except StopIteration:
@ -147,7 +147,7 @@ async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> DBA
@router.get("/completed") @router.get("/completed")
async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]: async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]:
# TODO Rebuild this method to fetch only active tasks # TODO Rebuild this method to fetch only active tasks
tasks = task.fetch_tasks_with_subtasks(user_id=user.id) tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id)
return [x for x in tasks if x.status == StatusEnum.COMPLETED] return [x for x in tasks if x.status == StatusEnum.COMPLETED]
@ -170,7 +170,7 @@ async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> lis
@router.get("/{task_id}/subtasks") @router.get("/{task_id}/subtasks")
async def get_subtasks(user: ProfileDB = Depends(get_user_from_token), task_id: int = Path()) -> list[DBSubTask]: async def get_subtasks(user: ProfileDB = Depends(get_user_from_token), task_id: int = Path()) -> list[DBSubTask]:
# TODO Rebuild this method to fetch only subtasks # TODO Rebuild this method to fetch only subtasks
tasks = task.fetch_tasks_with_subtasks(user_id=user.id) tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id)
try: try:
t = next(x for x in tasks if x.id == task_id) t = next(x for x in tasks if x.id == task_id)
return t.subtasks return t.subtasks
@ -181,11 +181,11 @@ async def get_subtasks(user: ProfileDB = Depends(get_user_from_token), task_id:
@router.post("/subtask") @router.post("/subtask")
async def set_status_to_subtask(req_data: SetSubtaskStatusRequest, async def set_status_to_subtask(req_data: SetSubtaskStatusRequest,
user: ProfileDB = Depends(get_user_from_token)) -> DBSubTask: user: ProfileDB = Depends(get_user_from_token)) -> DBSubTask:
tasks = task.fetch_tasks_with_subtasks(user_id=user.id) tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id)
try: try:
subtask = next(subtask for t in tasks for subtask in t.subtasks if subtask.id == req_data.subtask_id) subtask = next(subtask for t in tasks for subtask in t.subtasks if subtask.id == req_data.subtask_id)
task.set_subtask_to_completed(subtask_id=subtask.id, profile_id=user.id, dt=req_data.finished_dt) task_storage.set_subtask_to_completed(subtask_id=subtask.id, profile_id=user.id, dt=req_data.finished_dt)
subtask.status = StatusEnum.COMPLETED subtask.status = StatusEnum.COMPLETED
return subtask return subtask
@ -196,7 +196,7 @@ async def set_status_to_subtask(req_data: SetSubtaskStatusRequest,
@router.get("/{task_id}/events") @router.get("/{task_id}/events")
async def get_events(user: ProfileDB = Depends(get_user_from_token), task_id: int = Path()) -> list[DBEvent]: async def get_events(user: ProfileDB = Depends(get_user_from_token), task_id: int = Path()) -> list[DBEvent]:
# TODO Rebuild this method to fetch only events # TODO Rebuild this method to fetch only events
tasks = task.fetch_tasks_with_subtasks(user_id=user.id) tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id)
try: try:
t = next(x for x in tasks if x.id == task_id) t = next(x for x in tasks if x.id == task_id)
return [x for x in t.events if x.type == "Change"] return [x for x in t.events if x.type == "Change"]