fix(core): stabilize editor lifecycle, transactional versions, and runtime config

This commit is contained in:
greebo
2026-03-20 12:38:10 +03:00
parent 0f9c2a1cbd
commit 239b32a246
17 changed files with 1224 additions and 457 deletions

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, Request
from app.core.config import settings
from app.repositories.audit import create_audit_event
@@ -508,6 +508,7 @@ async def delete_draft_group(
@router.patch(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/seats/records/{{seat_record_id}}", response_model=SeatPatchResponse)
async def patch_draft_seat(
request: Request,
scheme_id: str,
seat_record_id: str,
payload: SeatPatchRequest,
@@ -530,14 +531,20 @@ async def patch_draft_seat(
group_id=payload.group_id,
)
raw_json = await request.json()
update_data = {k: v for k, v in payload.model_dump(exclude_unset=True).items() if k in raw_json}
for field in ("seat_id", "sector_id", "group_id"):
if field in update_data and (update_data[field] is None or update_data[field] == ""):
from app.services.api_errors import raise_unprocessable
raise_unprocessable(
code="business_identifier_nullification_forbidden",
message=f"{field} cannot be nullified or explicitly cleared",
)
row = await update_scheme_version_seat_by_record_id(
scheme_version_id=version.scheme_version_id,
seat_record_id=seat_record_id,
seat_id=payload.seat_id,
sector_id=payload.sector_id,
group_id=payload.group_id,
row_label=payload.row_label,
seat_number=payload.seat_number,
**update_data,
)
await create_audit_event(
@@ -569,6 +576,7 @@ async def patch_draft_seat(
@router.post(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/seats/bulk", response_model=BulkSeatPatchResponse)
async def bulk_patch_draft_seats(
request: Request,
scheme_id: str,
payload: BulkSeatPatchRequest,
expected_scheme_version_id: str | None = Query(default=None),
@@ -579,7 +587,20 @@ async def bulk_patch_draft_seats(
expected_scheme_version_id=expected_scheme_version_id,
)
items = [item.model_dump() for item in payload.items]
raw_json = await request.json()
items = []
for i, item in enumerate(payload.items):
item_raw = raw_json.get("items", [])[i] if "items" in raw_json else {}
items.append({k: item.model_dump(exclude_unset=True).get(k) for k in item_raw})
for item in items:
for field in ("seat_id", "sector_id", "group_id"):
if field in item and (item[field] is None or item[field] == ""):
from app.services.api_errors import raise_unprocessable
raise_unprocessable(
code="business_identifier_nullification_forbidden",
message=f"{field} cannot be nullified or explicitly cleared",
)
await validate_bulk_seat_patch_uniqueness(
scheme_version_id=version.scheme_version_id,
items=items,
@@ -625,6 +646,7 @@ async def bulk_patch_draft_seats(
@router.patch(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/sectors/records/{{sector_record_id}}", response_model=SectorPatchResponse)
async def patch_draft_sector(
request: Request,
scheme_id: str,
sector_record_id: str,
payload: SectorPatchRequest,
@@ -642,20 +664,28 @@ async def patch_draft_sector(
new_sector_id=payload.sector_id,
)
raw_json = await request.json()
update_data = {k: v for k, v in payload.model_dump(exclude_unset=True).items() if k in raw_json}
for field in ("sector_id",):
if field in update_data and (update_data[field] is None or update_data[field] == ""):
from app.services.api_errors import raise_unprocessable
raise_unprocessable(
code="business_identifier_nullification_forbidden",
message=f"{field} cannot be nullified or explicitly cleared",
)
row, old_sector_id = await update_scheme_version_sector_by_record_id(
scheme_version_id=version.scheme_version_id,
sector_record_id=sector_record_id,
sector_id=payload.sector_id,
name=payload.name,
)
cascaded_count = await cascade_update_seat_sector_reference(
scheme_version_id=version.scheme_version_id,
old_sector_id=old_sector_id,
new_sector_id=payload.sector_id,
)
repair_result = await repair_structure_references(
scheme_version_id=version.scheme_version_id,
**update_data,
)
cascaded_count = 0
if "sector_id" in update_data and update_data["sector_id"] and update_data["sector_id"] != old_sector_id:
cascaded_count = await cascade_update_seat_sector_reference(
scheme_version_id=version.scheme_version_id,
old_sector_id=old_sector_id,
new_sector_id=update_data["sector_id"],
)
await create_audit_event(
scheme_id=scheme.scheme_id,
@@ -668,7 +698,6 @@ async def patch_draft_sector(
"new_sector_id": payload.sector_id,
"name": payload.name,
"cascaded_seats_count": cascaded_count,
"repair_result": repair_result,
},
)
@@ -683,6 +712,7 @@ async def patch_draft_sector(
@router.patch(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/groups/records/{{group_record_id}}", response_model=GroupPatchResponse)
async def patch_draft_group(
request: Request,
scheme_id: str,
group_record_id: str,
payload: GroupPatchRequest,
@@ -700,20 +730,28 @@ async def patch_draft_group(
new_group_id=payload.group_id,
)
raw_json = await request.json()
update_data = {k: v for k, v in payload.model_dump(exclude_unset=True).items() if k in raw_json}
for field in ("group_id",):
if field in update_data and (update_data[field] is None or update_data[field] == ""):
from app.services.api_errors import raise_unprocessable
raise_unprocessable(
code="business_identifier_nullification_forbidden",
message=f"{field} cannot be nullified or explicitly cleared",
)
row, old_group_id = await update_scheme_version_group_by_record_id(
scheme_version_id=version.scheme_version_id,
group_record_id=group_record_id,
group_id=payload.group_id,
name=payload.name,
)
cascaded_count = await cascade_update_seat_group_reference(
scheme_version_id=version.scheme_version_id,
old_group_id=old_group_id,
new_group_id=payload.group_id,
)
repair_result = await repair_structure_references(
scheme_version_id=version.scheme_version_id,
**update_data,
)
cascaded_count = 0
if "group_id" in update_data and update_data["group_id"] and update_data["group_id"] != old_group_id:
cascaded_count = await cascade_update_seat_group_reference(
scheme_version_id=version.scheme_version_id,
old_group_id=old_group_id,
new_group_id=update_data["group_id"],
)
await create_audit_event(
scheme_id=scheme.scheme_id,
@@ -726,7 +764,6 @@ async def patch_draft_group(
"new_group_id": payload.group_id,
"name": payload.name,
"cascaded_seats_count": cascaded_count,
"repair_result": repair_result,
},
)