forked from erp-dev/erp
feat: added plate order clone
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Stateflow业务逻辑服务层
|
||||
"""
|
||||
import copy
|
||||
from typing import List, Optional, Tuple
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
@@ -570,3 +571,122 @@ def get_process_nodes(business_object: 'models.BusinessObject') -> List[dict]:
|
||||
}
|
||||
for node in process_nodes
|
||||
]
|
||||
|
||||
|
||||
def clone_business_object(
|
||||
source: 'models.BusinessObject',
|
||||
*,
|
||||
new_object_id: int,
|
||||
expected_content_type_id: int,
|
||||
) -> 'models.BusinessObject':
|
||||
"""
|
||||
克隆一个 BusinessObject(含所有状态流转记录与工艺参数记录)
|
||||
|
||||
目标:
|
||||
- 不影响原对象
|
||||
- 克隆出“业务数据完全一致”的新对象(process/描述/关联对象/日志/参数等)
|
||||
- 返回新的 BusinessObject 实例
|
||||
|
||||
说明:
|
||||
- 会克隆 source.state_logs 的所有记录(含已撤销记录)
|
||||
- 会克隆每条 StateFlowRecord 下的所有 StateLogParameterRecord(含参数 JSON 与 remark)
|
||||
- 会尽量保持 completed_at / cancelled_at / 参数记录 created_at 与原对象一致
|
||||
- created_at/updated_at 等 ModelBase 字段也会被同步(使用 update 绕过 auto_now*)
|
||||
- **重要**:克隆必须提供新的 object_id(当 source.content_type 非空时),否则克隆与业务绑定无差异,容易造成误用
|
||||
- expected_content_type_id 仅用于校验调用方意图:必须与 source.content_type_id 一致,否则拒绝克隆
|
||||
"""
|
||||
if source is None:
|
||||
raise ValueError("source 不能为空")
|
||||
|
||||
# 重新加载 source,确保拿到完整关系(避免调用方未预取导致 N+1)
|
||||
source = (
|
||||
models.BusinessObject.objects
|
||||
.select_related('process', 'content_type')
|
||||
.prefetch_related(
|
||||
'state_logs__state',
|
||||
'state_logs__completed_by',
|
||||
'state_logs__parameter_records',
|
||||
)
|
||||
.get(id=source.id)
|
||||
)
|
||||
|
||||
# 校验 content_type 一致性(不做额外校验,仅比对 id)
|
||||
if source.content_type_id != expected_content_type_id:
|
||||
raise ValueError("content_type 与源对象不一致,拒绝克隆")
|
||||
|
||||
# 如果源对象未绑定 content_type,则拒绝克隆
|
||||
if source.content_type_id is None:
|
||||
return ValueError("源对象未绑定 content_type,拒绝克隆")
|
||||
else:
|
||||
if new_object_id is None:
|
||||
raise ValueError("必须提供新的 object_id")
|
||||
if source.object_id == new_object_id:
|
||||
raise ValueError("object_id 必须与源对象不同")
|
||||
|
||||
content_type = models.ContentType.objects.get(id=source.content_type_id)
|
||||
actual_object = content_type.model_class().objects.get(id=new_object_id)
|
||||
if actual_object is None:
|
||||
raise ValueError("实际对象不存在,拒绝克隆")
|
||||
|
||||
with transaction.atomic():
|
||||
cloned = models.BusinessObject.objects.create(
|
||||
name=source.name,
|
||||
process=source.process,
|
||||
description=source.description,
|
||||
content_type=source.content_type,
|
||||
object_id=new_object_id,
|
||||
)
|
||||
|
||||
# 同步 BusinessObject 的时间戳字段(保持一致性)
|
||||
models.BusinessObject.objects.filter(id=cloned.id).update(
|
||||
created_at=source.created_at,
|
||||
updated_at=source.updated_at,
|
||||
)
|
||||
|
||||
# 复制 state_logs(按完成时间排序,保证克隆后的序列稳定)
|
||||
source_logs = sorted(
|
||||
list(source.state_logs.all()),
|
||||
key=lambda log: (log.completed_at, log.id),
|
||||
)
|
||||
|
||||
for src_log in source_logs:
|
||||
new_log = models.StateFlowRecord.objects.create(
|
||||
business_object=cloned,
|
||||
state=src_log.state,
|
||||
completed_by=src_log.completed_by,
|
||||
is_cancelled=src_log.is_cancelled,
|
||||
cancelled_at=src_log.cancelled_at,
|
||||
)
|
||||
|
||||
# 同步 StateFlowRecord 的时间戳字段(含 completed_at)
|
||||
models.StateFlowRecord.objects.filter(id=new_log.id).update(
|
||||
completed_at=src_log.completed_at,
|
||||
created_at=src_log.created_at,
|
||||
updated_at=src_log.updated_at,
|
||||
cancelled_at=src_log.cancelled_at,
|
||||
)
|
||||
|
||||
# 复制参数记录(按 created_at 排序)
|
||||
src_param_records = sorted(
|
||||
list(src_log.parameter_records.all()),
|
||||
key=lambda rec: (rec.created_at, rec.id),
|
||||
)
|
||||
|
||||
for src_rec in src_param_records:
|
||||
new_rec = models.StateLogParameterRecord.objects.create(
|
||||
state_log=new_log,
|
||||
parameters=copy.deepcopy(src_rec.parameters),
|
||||
remark=src_rec.remark,
|
||||
)
|
||||
models.StateLogParameterRecord.objects.filter(id=new_rec.id).update(
|
||||
created_at=src_rec.created_at,
|
||||
updated_at=src_rec.updated_at,
|
||||
)
|
||||
|
||||
# 重新加载,确保返回对象的字段与 DB 一致(尤其是时间戳)
|
||||
cloned.refresh_from_db()
|
||||
actual_object.refresh_from_db()
|
||||
|
||||
actual_object.business_object_id = cloned.id
|
||||
actual_object.save()
|
||||
return cloned
|
||||
|
||||
Reference in New Issue
Block a user