"""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