Данные из сателитныхх листов в АХР
This commit is contained in:
parent
357dc9efaa
commit
11e496d13c
15
api/alembic/versions/0031_form2_ahr_satellite_sync.py
Normal file
15
api/alembic/versions/0031_form2_ahr_satellite_sync.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from sql_migration import run_sql_file_migration
|
||||||
|
|
||||||
|
|
||||||
|
revision = "0031"
|
||||||
|
down_revision = "0030"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
run_sql_file_migration("0031_form2_ahr_satellite_sync.sql")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
1322
api/alembic/versions/sql/0031_form2_ahr_satellite_sync.sql
Normal file
1322
api/alembic/versions/sql/0031_form2_ahr_satellite_sync.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ import time
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
#
|
#
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
@ -24,8 +24,8 @@ from src.db.models.form_type import FormTypeEnum
|
|||||||
from src.services.budget_form_service import BudgetFormService
|
from src.services.budget_form_service import BudgetFormService
|
||||||
from src.services.project_service import ProjectService
|
from src.services.project_service import ProjectService
|
||||||
from src.services.sheet_service import SheetService
|
from src.services.sheet_service import SheetService
|
||||||
from src.core.errors import BasicAppException, ValidationsError
|
from src.core.errors import BasicAppException, ValidationsError
|
||||||
from src.core.exception_handlers import map_sqlalchemy_error
|
from src.core.exception_handlers import map_sqlalchemy_error
|
||||||
from src.db.session import SessionLocal
|
from src.db.session import SessionLocal
|
||||||
|
|
||||||
from src.services.user_service import UserService
|
from src.services.user_service import UserService
|
||||||
@ -33,6 +33,13 @@ from src.services.user_service import UserService
|
|||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
FORM_2_AHR_SATELLITE_SHEETS = {
|
||||||
|
"AHR_LIMIT",
|
||||||
|
"AHR_RENT",
|
||||||
|
"AHR_UTILITY",
|
||||||
|
"AHR_SECURITY",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def get_db_session(user_id: int | None = None):
|
async def get_db_session(user_id: int | None = None):
|
||||||
@ -215,6 +222,27 @@ class ConnectionManager:
|
|||||||
except:
|
except:
|
||||||
pass # Игнорируем недоступные соединения
|
pass # Игнорируем недоступные соединения
|
||||||
|
|
||||||
|
async def broadcast_to_form_sheet(
|
||||||
|
self,
|
||||||
|
data: dict,
|
||||||
|
form_id: int,
|
||||||
|
sheet: str,
|
||||||
|
):
|
||||||
|
data = data.copy()
|
||||||
|
data["is_self"] = None
|
||||||
|
message = dumps(data, default=_convert_error, ensure_ascii=False)
|
||||||
|
|
||||||
|
for key, connections in self.connections[ConnectionKeyEnum.FORM].items():
|
||||||
|
key_data = dict(key)
|
||||||
|
if key_data.get("form_id") != form_id or key_data.get("sheet") != sheet:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for con_info in connections:
|
||||||
|
try:
|
||||||
|
await con_info.ws.send_text(message)
|
||||||
|
except:
|
||||||
|
pass # Игнорируем недоступные соединения
|
||||||
|
|
||||||
async def send_back(self, data: dict, websocket: WebSocket):
|
async def send_back(self, data: dict, websocket: WebSocket):
|
||||||
data = data.copy()
|
data = data.copy()
|
||||||
data["is_self"] = True
|
data["is_self"] = True
|
||||||
@ -389,26 +417,26 @@ class FormEventProcess:
|
|||||||
event_data=event_data["data"],
|
event_data=event_data["data"],
|
||||||
user=curr_user,
|
user=curr_user,
|
||||||
)
|
)
|
||||||
case "program_updated":
|
case "program_updated":
|
||||||
return await self.__update_form4_program(
|
return await self.__update_form4_program(
|
||||||
event_data=event_data["data"],
|
event_data=event_data["data"],
|
||||||
user=curr_user,
|
user=curr_user,
|
||||||
)
|
)
|
||||||
case "program_deleted":
|
case "program_deleted":
|
||||||
return await self.__delete_form4_program(
|
return await self.__delete_form4_program(
|
||||||
event_data=event_data["data"],
|
event_data=event_data["data"],
|
||||||
user=curr_user,
|
user=curr_user,
|
||||||
)
|
)
|
||||||
case "project_updated":
|
case "project_updated":
|
||||||
return await self.__update_form4_project(
|
return await self.__update_form4_project(
|
||||||
event_data=event_data["data"],
|
event_data=event_data["data"],
|
||||||
user=curr_user,
|
user=curr_user,
|
||||||
)
|
)
|
||||||
case "project_deleted":
|
case "project_deleted":
|
||||||
return await self.__delete_form4_project(
|
return await self.__delete_form4_project(
|
||||||
event_data=event_data["data"],
|
event_data=event_data["data"],
|
||||||
user=curr_user,
|
user=curr_user,
|
||||||
)
|
)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -530,48 +558,48 @@ class FormEventProcess:
|
|||||||
program_id=event_data["program_id"],
|
program_id=event_data["program_id"],
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __update_form4_program(
|
async def __update_form4_program(
|
||||||
self,
|
self,
|
||||||
event_data: dict,
|
event_data: dict,
|
||||||
user: AppUser,
|
user: AppUser,
|
||||||
) -> tuple[int, list[tuple]] | None:
|
) -> tuple[int, list[tuple]] | None:
|
||||||
return await self.sheet_service.update_form4_program(
|
return await self.sheet_service.update_form4_program(
|
||||||
program_id=event_data["program_id"],
|
program_id=event_data["program_id"],
|
||||||
name=event_data["name"],
|
name=event_data["name"],
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __delete_form4_program(
|
async def __delete_form4_program(
|
||||||
self,
|
self,
|
||||||
event_data: dict,
|
event_data: dict,
|
||||||
user: AppUser,
|
user: AppUser,
|
||||||
) -> tuple[int, list[tuple]] | None:
|
) -> tuple[int, list[tuple]] | None:
|
||||||
return await self.sheet_service.delete_form4_program(
|
return await self.sheet_service.delete_form4_program(
|
||||||
program_id=event_data["program_id"],
|
program_id=event_data["program_id"],
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __update_form4_project(
|
async def __update_form4_project(
|
||||||
self,
|
self,
|
||||||
event_data: dict,
|
event_data: dict,
|
||||||
user: AppUser,
|
user: AppUser,
|
||||||
) -> tuple[int, list[tuple]] | None:
|
) -> tuple[int, list[tuple]] | None:
|
||||||
return await self.sheet_service.update_form4_project(
|
return await self.sheet_service.update_form4_project(
|
||||||
project_id=event_data["project_id"],
|
project_id=event_data["project_id"],
|
||||||
name=event_data["name"],
|
name=event_data["name"],
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __delete_form4_project(
|
async def __delete_form4_project(
|
||||||
self,
|
self,
|
||||||
event_data: dict,
|
event_data: dict,
|
||||||
user: AppUser,
|
user: AppUser,
|
||||||
) -> tuple[int, list[tuple]] | None:
|
) -> tuple[int, list[tuple]] | None:
|
||||||
return await self.sheet_service.delete_form4_project(
|
return await self.sheet_service.delete_form4_project(
|
||||||
project_id=event_data["project_id"],
|
project_id=event_data["project_id"],
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __del_row(
|
async def __del_row(
|
||||||
self,
|
self,
|
||||||
@ -739,7 +767,94 @@ class ProjectEventProcess:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
manager = ConnectionManager()
|
manager = ConnectionManager()
|
||||||
|
|
||||||
|
|
||||||
|
def get_satellite_source_row_id(data: dict) -> int | None:
|
||||||
|
event_name = data.get("event")
|
||||||
|
|
||||||
|
if event_name == "cell_updated":
|
||||||
|
return data.get("data", {}).get("line_id")
|
||||||
|
|
||||||
|
if event_name == "row_deleted":
|
||||||
|
return data.get("data", {}).get("row_id")
|
||||||
|
|
||||||
|
if event_name == "row_added":
|
||||||
|
result = data.get("result")
|
||||||
|
if not isinstance(result, dict):
|
||||||
|
return None
|
||||||
|
|
||||||
|
rows = result.get("data") or []
|
||||||
|
for row in rows:
|
||||||
|
if len(row) >= 4 and isinstance(row[3], dict) and row[3].get("id") is not None:
|
||||||
|
return row[3]["id"]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def notify_ahr_about_satellite_change(
|
||||||
|
form_id: int,
|
||||||
|
source_sheet: str,
|
||||||
|
source_row_id: int,
|
||||||
|
source_event: str,
|
||||||
|
user_id: int,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
async with get_db_session(user_id=user_id) as db:
|
||||||
|
ahr_delta = await SheetService(db).get_form2_ahr_satellite_delta(
|
||||||
|
form_id=form_id,
|
||||||
|
source_sheet=source_sheet,
|
||||||
|
source_row_id=source_row_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not ahr_delta:
|
||||||
|
return
|
||||||
|
|
||||||
|
ahr_event = {
|
||||||
|
"event": "cell_updated",
|
||||||
|
"data": {
|
||||||
|
"source_sheet": source_sheet,
|
||||||
|
},
|
||||||
|
"result": ahr_delta,
|
||||||
|
}
|
||||||
|
|
||||||
|
if source_event == "row_added":
|
||||||
|
new_row = next(
|
||||||
|
(
|
||||||
|
row for row in ahr_delta
|
||||||
|
if row[0] == "INPUT"
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if new_row is not None:
|
||||||
|
new_line_id = new_row[3].get("line_id")
|
||||||
|
ahr_event = {
|
||||||
|
"event": "row_added",
|
||||||
|
"data": {
|
||||||
|
"source_sheet": source_sheet,
|
||||||
|
"expense_item_id": new_row[3].get("header", {}).get("expense_item_id"),
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"data": ahr_delta,
|
||||||
|
"new_line_id": new_line_id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await manager.broadcast_to_form_sheet(
|
||||||
|
data=ahr_event,
|
||||||
|
form_id=form_id,
|
||||||
|
sheet="AHR",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Ошибка отправки на лист АХР",
|
||||||
|
extra={
|
||||||
|
"form_id": form_id,
|
||||||
|
"source_sheet": source_sheet,
|
||||||
|
"source_row_id": source_row_id,
|
||||||
|
"source_event": source_event,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _convert_error(o):
|
def _convert_error(o):
|
||||||
@ -910,7 +1025,17 @@ async def process_websocket(
|
|||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
**kwargs_process
|
**kwargs_process
|
||||||
)
|
)
|
||||||
await db.commit()
|
should_refresh_ahr = (
|
||||||
|
con_key == ConnectionKeyEnum.FORM
|
||||||
|
and kwargs_process.get("sheet") in FORM_2_AHR_SATELLITE_SHEETS
|
||||||
|
and data.get("event") in {"cell_updated", "row_added", "row_deleted"}
|
||||||
|
)
|
||||||
|
ahr_source_row_id = (
|
||||||
|
get_satellite_source_row_id(data)
|
||||||
|
if should_refresh_ahr
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
await db.commit()
|
||||||
db_ms = (time.perf_counter() - t0) * 1000
|
db_ms = (time.perf_counter() - t0) * 1000
|
||||||
|
|
||||||
extra = {
|
extra = {
|
||||||
@ -936,10 +1061,10 @@ async def process_websocket(
|
|||||||
"row_added",
|
"row_added",
|
||||||
"program_added",
|
"program_added",
|
||||||
"project_added",
|
"project_added",
|
||||||
"program_updated",
|
"program_updated",
|
||||||
"project_updated",
|
"project_updated",
|
||||||
"program_deleted",
|
"program_deleted",
|
||||||
"project_deleted",
|
"project_deleted",
|
||||||
"row_deleted",
|
"row_deleted",
|
||||||
]:
|
]:
|
||||||
await manager.broadcast_to_all(
|
await manager.broadcast_to_all(
|
||||||
@ -956,23 +1081,34 @@ async def process_websocket(
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if should_refresh_ahr and ahr_source_row_id is not None:
|
||||||
|
asyncio.create_task(
|
||||||
|
notify_ahr_about_satellite_change(
|
||||||
|
form_id=kwargs_process["form_id"],
|
||||||
|
source_sheet=kwargs_process["sheet"],
|
||||||
|
source_row_id=ahr_source_row_id,
|
||||||
|
source_event=data["event"],
|
||||||
|
user_id=user_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
except BasicAppException as e:
|
except BasicAppException as e:
|
||||||
data["error"] = e.description or "Неизвестная ошибка"
|
data["error"] = e.description or "Неизвестная ошибка"
|
||||||
await manager.send_back(
|
await manager.send_back(
|
||||||
data=data,
|
data=data,
|
||||||
websocket=websocket,
|
websocket=websocket,
|
||||||
)
|
)
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
_, code, message, field = map_sqlalchemy_error(e)
|
_, code, message, field = map_sqlalchemy_error(e)
|
||||||
data["error"] = {
|
data["error"] = {
|
||||||
"code": code,
|
"code": code,
|
||||||
"field": field,
|
"field": field,
|
||||||
"message": message,
|
"message": message,
|
||||||
}
|
}
|
||||||
await manager.send_back(
|
await manager.send_back(
|
||||||
data=data,
|
data=data,
|
||||||
websocket=websocket,
|
websocket=websocket,
|
||||||
)
|
)
|
||||||
except SheetValidationError as e:
|
except SheetValidationError as e:
|
||||||
data["error"] = e.message
|
data["error"] = e.message
|
||||||
await manager.send_back(
|
await manager.send_back(
|
||||||
|
|||||||
@ -380,3 +380,31 @@ class SheetRepository:
|
|||||||
)
|
)
|
||||||
).all()
|
).all()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
async def get_form2_ahr_satellite_delta(
|
||||||
|
self,
|
||||||
|
form_id: int,
|
||||||
|
source_sheet: str,
|
||||||
|
source_row_id: int,
|
||||||
|
) -> list[tuple]:
|
||||||
|
return [
|
||||||
|
tuple(r) for r in (
|
||||||
|
await self.db.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
SELECT row_type, depth, sort_order, data
|
||||||
|
FROM v3.get_form2_ahr_satellite_delta(
|
||||||
|
:form_id,
|
||||||
|
:source_sheet,
|
||||||
|
:source_row_id
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"form_id": form_id,
|
||||||
|
"source_sheet": source_sheet,
|
||||||
|
"source_row_id": source_row_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
|||||||
@ -274,3 +274,15 @@ class SheetService:
|
|||||||
row_id=row_id,
|
row_id=row_id,
|
||||||
direction=direction,
|
direction=direction,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def get_form2_ahr_satellite_delta(
|
||||||
|
self,
|
||||||
|
form_id: int,
|
||||||
|
source_sheet: str,
|
||||||
|
source_row_id: int,
|
||||||
|
) -> list[tuple]:
|
||||||
|
return await self.sheet_repo.get_form2_ahr_satellite_delta(
|
||||||
|
form_id=form_id,
|
||||||
|
source_sheet=source_sheet,
|
||||||
|
source_row_id=source_row_id,
|
||||||
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user