69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
"""Scoped child reconciliation. Callers supply the complete writable scope, never global IDs."""
|
|
from datetime import datetime
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from app.core.utils import generate_uuid
|
|
|
|
|
|
def validate_child_ids(items, existing, label):
|
|
"""Validate the whole collection before a caller mutates any ORM instance."""
|
|
by_id = {row.id: row for row in existing}
|
|
seen = set()
|
|
for item in items or []:
|
|
row_id = (getattr(item, 'id', None) or '').strip()
|
|
if not row_id:
|
|
continue
|
|
if row_id in seen:
|
|
raise HTTPException(422, f'{label} id 重复:{row_id}')
|
|
if row_id not in by_id:
|
|
raise HTTPException(422, f'{label} id 无效、已删除或不属于当前主单:{row_id}')
|
|
seen.add(row_id)
|
|
return by_id, seen
|
|
|
|
|
|
def prepare_keyed_rows(existing, values, key_fields, label):
|
|
"""Natural-key relations keep their identities; ambiguous keys must be corrected explicitly."""
|
|
old = {}
|
|
for row in existing:
|
|
key = tuple(getattr(row, field, None) or '' for field in key_fields)
|
|
if key in old:
|
|
raise HTTPException(409, f'{label}存在重复历史记录,请先核对')
|
|
old[key] = row
|
|
seen = set()
|
|
pairs = []
|
|
for value in values:
|
|
key = tuple(value.get(field) or '' for field in key_fields)
|
|
if key in seen:
|
|
raise HTTPException(422, f'{label}不能重复提交')
|
|
seen.add(key)
|
|
row = old.get(key)
|
|
if value.get('id') and (row is None or value['id'] != row.id):
|
|
raise HTTPException(422, f'{label} id 无效或不属于当前主单')
|
|
pairs.append((row, value))
|
|
return pairs, [row for key, row in old.items() if key not in seen]
|
|
|
|
|
|
def apply_keyed_rows(db, model, prepared, *, user_id, dept_id='', order_start=0):
|
|
pairs, removed = prepared
|
|
now = datetime.now()
|
|
result = []
|
|
for idx, (row, value) in enumerate(pairs, start=order_start):
|
|
fields = {key: val for key, val in value.items() if key != 'id'}
|
|
if row is None:
|
|
row = model(id=generate_uuid(), create_by=user_id, create_dept_id=dept_id or '',
|
|
create_time=now, del_flag=0, **fields)
|
|
db.add(row)
|
|
else:
|
|
for key, val in fields.items():
|
|
setattr(row, key, val)
|
|
row.order_num = idx
|
|
row.update_by = user_id
|
|
row.update_time = now
|
|
result.append(row)
|
|
for row in removed:
|
|
row.del_flag = 1
|
|
row.update_by = user_id
|
|
row.update_time = now
|
|
return result
|