1
0
forked from erp-dev/erp
Files
erpnew/stateflow/services.py

926 lines
33 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Stateflow业务逻辑服务层
"""
import copy
import logging
from typing import Iterable, List, Optional, Tuple
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.db.models import Exists, OuterRef, QuerySet
from . import models
logger = logging.getLogger(__name__)
User = get_user_model()
def _can_relink_business_object(business_object: 'models.BusinessObject') -> bool:
"""
内部判断:该 BusinessObject 是否允许“换流程并重建”relink
规则(与 printing.models.PlateOrder/PrintingJob.has_started 保持一致):
- 只要存在未撤销is_cancelled=False的状态记录就视为已开始不允许 relink
"""
if not business_object:
return True
return not business_object.state_logs.filter(is_cancelled=False).exists()
def relink_business_object_for_instance(
*,
instance,
new_process: 'models.Process',
default_name: str | None = None,
default_description: str | None = None,
) -> Optional['models.BusinessObject']:
"""
当业务对象的“流程”发生变更时,重建并重新绑定一个新的 BusinessObject。
设计目标:
- stateflow 层提供统一逻辑(不依赖 printing/shipment 等业务模块)
- 仅在“没有未撤销进度”的情况下允许重建
- 旧 BusinessObject 不删除(未来可通过“无反向引用”视为悬空)
返回:
- BusinessObject 实例relink 成功后的新 BO
- None不允许 relink例如已存在未撤销进度调用方应将其转为 400
注意:
- 若 instance 原本没有 business_object本函数仍会创建并绑定新的 BO
- 本函数只负责 BO 的创建与绑定,不负责写入业务对象自身的 process 字段
"""
if instance is None or getattr(instance, 'pk', None) is None:
return None
if new_process is None:
return None
old_bo = getattr(instance, 'business_object', None)
if old_bo and not _can_relink_business_object(old_bo):
return None
ct = ContentType.objects.get_for_model(instance.__class__)
bo = models.BusinessObject.objects.create(
name=(default_name or f"{instance.__class__.__name__}-{instance.pk}"),
process=new_process,
description=(default_description or ''),
content_type=ct,
object_id=instance.pk,
)
# 绑定到业务对象(业务对象侧通常为 OneToOneField
setattr(instance, 'business_object', bo)
instance.save(update_fields=['business_object'])
return bo
def ensure_business_object_bound_to_instance(
business_object: 'models.BusinessObject',
instance,
*,
default_name: str | None = None,
) -> bool:
"""
确保 BusinessObject 的 GenericForeignKey 绑定到指定实例。
说明:
- 这是一个“自愈”辅助函数,用于修复历史数据/非标准创建路径导致的 content_type/object_id 为空或不一致。
- 仅在调用方明确知道该 BusinessObject 应当绑定到 instance 时使用(例如 PrintingJob.business_object 一对一)。
返回:
- True本次发生了更新并保存
- False无需更新或参数不足business_object/instance/instance.pk 为空)
"""
if not business_object or instance is None:
return False
instance_id = getattr(instance, 'pk', None)
if not instance_id:
return False
ct = ContentType.objects.get_for_model(instance.__class__)
update_fields: list[str] = []
if business_object.content_type_id != ct.id:
business_object.content_type = ct
update_fields.append('content_type')
if business_object.object_id != instance_id:
business_object.object_id = instance_id
update_fields.append('object_id')
if default_name is not None and not (business_object.name or '').strip():
business_object.name = default_name
update_fields.append('name')
if not update_fields:
return False
business_object.save(update_fields=update_fields)
return True
def get_last_completed_state(business_object: 'models.BusinessObject') -> Optional['models.State']:
"""
获取最后完成的状态
返回:最后一个已完成的状态节点,如果没有则返回 None
应用场景:
- 显示"刚完成了什么"
- 查询最后完成状态的参数
- 审计日志
"""
last_log = business_object.state_logs.filter(
is_cancelled=False
).order_by('-completed_at', '-id').first()
return last_log.state if last_log else None
def get_next_pending_state_simple(business_object: 'models.BusinessObject') -> Optional['models.State']:
"""
获取下一个待执行的状态简化版只返回State对象
返回:下一个待执行的状态节点,如果没有则返回 None
应用场景:
- 显示"接下来要做什么"
- 获取下一步需要的参数
- 流程推进前的检查
"""
next_pending = get_next_pending_state(business_object, include_parameters=False)
return next_pending['state'] if next_pending else None
def get_business_object_current_state(business_object: 'models.BusinessObject') -> Optional['models.State']:
"""
获取业务对象的当前状态(可配置模式)
根据 settings.STATEFLOW_CURRENT_STATE_MODE 决定返回:
- 'NEXT' (默认): 下一个待执行的节点
- 'LAST': 最后完成的节点
注意:此函数保持原有接口不变,但行为可配置
推荐:在新代码中直接使用 get_last_completed_state() 或 get_next_pending_state_simple() 以明确语义
NEXT模式的规则
- 返回下一个待执行的节点(第一个未完成的节点)
- 如果所有节点都已完成,返回 None
- 配合进度可以判断具体状态:
- 进度 0% + current_state 非空 → 显示"未开始"
- 进度 > 0% + current_state 非空 → 显示 current_state.name正在进行
- current_state = None → 显示"已完成"
LAST模式的规则
- 返回最后一个已完成的节点
- 如果没有完成任何节点,返回 None
"""
from django.conf import settings
mode = getattr(settings, 'STATEFLOW_CURRENT_STATE_MODE', 'NEXT')
if mode == 'LAST':
return get_last_completed_state(business_object)
else: # 默认 'NEXT'
return get_next_pending_state_simple(business_object)
def get_business_object_state_status(business_object: 'models.BusinessObject', state: 'models.State') -> str:
"""
获取业务对象中某个状态的状态
返回值:
- 'not_started': 未开始
- 'completed': 已完成
注意:由于 current_state 现在表示"最后完成的状态",不再有 'in_progress' 概念
"""
# 检查是否已完成且未被撤销
is_completed = business_object.state_logs.filter(state=state, is_cancelled=False).exists()
if is_completed:
return 'completed'
return 'not_started'
def get_completed_node_ids(business_object: 'models.BusinessObject') -> List[int]:
"""获取订单已完成且未撤销的节点ID列表按完成顺序"""
return list(
business_object.state_logs.filter(is_cancelled=False)
.order_by('completed_at')
.values_list('state_id', flat=True)
)
def get_progress_percentage(business_object: 'models.BusinessObject') -> float:
"""计算订单进度百分比"""
total_nodes = business_object.process.process_nodes.count()
if total_nodes == 0:
return 0.0
completed_count = business_object.state_logs.filter(is_cancelled=False).count()
return (completed_count / total_nodes) * 100
def advance_to_next_state(business_object: 'models.BusinessObject', user, **parameters) -> Tuple[bool, str, Optional['models.StateFlowRecord']]:
"""
将业务对象推进到下一个状态(完成下一个未完成的节点)
参数:
business_object: BusinessObject 实例
user: 操作用户
**parameters: 状态参数(可选)
返回:
tuple: (success: bool, message: str, state_log: StateFlowRecord|None)
- success: 是否成功
- message: 提示信息
- state_log: 创建的状态日志(成功时)
"""
# 业务约束BusinessObject 必须能追溯到真实业务对象,否则状态流转记录没有业务意义。
# DB 允许为空仅为历史兼容;但实际运行中存在“先创建 BO、后绑定到业务模型”的非标准路径。
# 为避免这类数据导致推进失败,这里做一次自愈:
# - 若 BO 未绑定 content_type/object_id或 content_object 解析不到实例)
# - 则尝试通过常见的一对一反向关系(如 printing_job/plate_order推断真实业务对象并补齐绑定
if business_object.content_type_id is None or business_object.object_id is None or business_object.content_object is None:
inferred_instance = None
inferred_default_name = None
# PrintingJob.business_object -> related_name='printing_job'
try:
inferred_instance = getattr(business_object, 'printing_job', None)
if inferred_instance is not None:
inferred_default_name = f"PrintingJob-{getattr(inferred_instance, 'pk', '')}"
except (ObjectDoesNotExist, AttributeError):
inferred_instance = None
# PlateOrder.business_object -> related_name='plate_order'
if inferred_instance is None:
try:
inferred_instance = getattr(business_object, 'plate_order', None)
if inferred_instance is not None:
inferred_default_name = f"PlateOrder-{getattr(inferred_instance, 'pk', '')}"
except (ObjectDoesNotExist, AttributeError):
inferred_instance = None
if inferred_instance is not None:
ensure_business_object_bound_to_instance(
business_object,
inferred_instance,
default_name=inferred_default_name,
)
# GenericForeignKey 可能缓存了旧值,刷新以确保后续 content_object 判断正确
business_object.refresh_from_db(fields=['content_type', 'object_id', 'name'])
if business_object.content_type_id is None or business_object.object_id is None:
return False, "业务对象未绑定关联对象content_type/object_id禁止推进", None
if business_object.content_object is None:
return (
False,
f"业务对象绑定的关联对象不存在:{business_object.content_type.app_label}.{business_object.content_type.model} #{business_object.object_id}",
None,
)
can_advance, reason = can_advance_to_next_state(business_object)
if not can_advance:
return False, reason, None
with transaction.atomic():
# 获取下一个待执行的节点
next_pending = get_next_pending_state(business_object, include_parameters=False)
if not next_pending:
return False, "没有待执行的节点", None
next_state = next_pending['state']
# 验证必填参数
if parameters:
is_valid, missing_params = validate_required_parameters(next_state, **parameters)
if not is_valid:
return False, f"缺失必填参数: {', '.join(missing_params)}", None
else:
# 检查是否有必填参数
required_params = get_required_parameters(next_state)
if required_params.exists():
required_keys = list(required_params.values_list('key', flat=True))
return False, f"缺失必填参数: {', '.join(required_keys)}", None
# 创建状态流转记录
state_log = models.StateFlowRecord.objects.create(
business_object=business_object,
state=next_state,
completed_by=user
)
# 如果提供了参数,创建参数记录
if parameters:
create_parameter_record(state_log, **parameters)
# 检查是否所有状态都已完成
after_advance = get_next_pending_state(business_object, include_parameters=False)
is_process_completed = (after_advance is None)
# 发送状态推进信号(在事务内,确保数据已持久化)
# sender 使用 content_object 的类,以便监听者按类型过滤
from . import signals
content_object = business_object.content_object
sender_class = content_object.__class__ if content_object else None
logger.info(
f'[stateflow] 发送 state_advanced 信号: '
f'sender={sender_class} (module={sender_class.__module__ if sender_class else None}), '
f'business_object_id={business_object.id}, '
f'state={next_state.name}, is_process_completed={is_process_completed}'
)
signals.state_advanced.send(
sender=sender_class,
process_id=business_object.process_id,
business_object_id=business_object.id,
business_object=business_object,
content_object=content_object,
state=next_state,
state_log=state_log,
completed_by=user,
is_process_completed=is_process_completed,
last_completed_state_id=next_state.id,
last_completed_by=user,
)
if is_process_completed:
# 流程全部完成,发送流程完成信号
logger.info(
f'[stateflow] 发送 process_completed 信号: '
f'sender={sender_class}, business_object_id={business_object.id}'
)
signals.process_completed.send(
sender=sender_class,
process_id=business_object.process_id,
business_object_id=business_object.id,
business_object=business_object,
content_object=content_object,
completed_by=user,
last_completed_state_id=next_state.id,
last_completed_by=user,
)
return True, f"流程已完成,最后状态: {next_state.name}", state_log
return True, f"已完成状态: {next_state.name}", state_log
def reset_business_object_progress(business_object: 'models.BusinessObject') -> None:
"""
重置业务对象进度
注意:不删除历史记录,而是标记所有记录为已撤销,并记录撤销时间
这样可以保留完整的操作历史,支持审计需求
"""
from django.utils import timezone
business_object.state_logs.filter(is_cancelled=False).update(
is_cancelled=True,
cancelled_at=timezone.now()
)
# 向后兼容的别名
reset_order_progress = reset_business_object_progress
def step_back_one_state(business_object: 'models.BusinessObject', user) -> Tuple[bool, str]:
"""
回退一步(撤销最后一次完成的状态)
这是 advance_to_next_state 的逆向操作
返回: (是否成功, 消息)
"""
from django.utils import timezone
with transaction.atomic():
# 获取最后一条有效的状态流转记录(按完成时间倒序)
last_record = (
business_object.state_logs
.filter(is_cancelled=False)
.order_by('-completed_at', '-id')
.first()
)
# 如果没有任何有效记录,说明当前没有任何状态流转,无需回退
if not last_record:
return False, "当前没有任何状态流转记录,无法回退"
# 标记最后一条记录为已撤销
last_record.is_cancelled = True
last_record.cancelled_at = timezone.now()
last_record.save(update_fields=['is_cancelled', 'cancelled_at'])
return True, f"已回退状态: {last_record.state.name}"
def get_current_state_parameters(business_object: 'models.BusinessObject') -> List['models.StateParameter']:
"""获取订单当前状态的参数列表"""
current_state = get_business_object_current_state(business_object)
if current_state:
return list(current_state.parameters.all())
return []
def get_display_status(business_object: 'models.BusinessObject') -> str:
"""
获取用于显示的状态文本
规则:
- 如果没有 business_object返回第一个节点名称如果有流程否则返回空字符串
- 如果 current_state 为 None返回"已完成"
- 否则:返回 current_state 的名称
注意:此函数废除了"未开始"的概念,直接显示节点名称
返回值: 状态显示文本(字符串)
"""
if not business_object:
return ''
current_state = get_business_object_current_state(business_object)
if current_state is None:
# 没有待执行的节点,已完成
return '已完成'
# 直接返回当前状态名称,不管进度如何
return current_state.name
def get_overall_status(business_object: 'models.BusinessObject') -> str:
"""
获取业务对象的整体状态
返回值:
- 'in_progress': 进行中(有部分状态已完成,但未完成所有)
- 'completed': 已完成(所有状态都已完成)
注意:废除了 'not_started' 状态进度为0时也返回 'in_progress'
"""
# 检查是否有下一个待执行节点
next_pending = get_next_pending_state(business_object, include_parameters=False)
if next_pending is None:
# 没有待执行节点,说明所有状态都已完成
return 'completed'
return 'in_progress'
def can_advance_to_next_state(business_object: 'models.BusinessObject') -> Tuple[bool, str]:
"""
检查业务对象是否可以推进到下一个状态
返回: (是否可以推进, 原因)
"""
# 检查流程是否有节点
first_node = business_object.process.process_nodes.order_by('order', 'id').first()
if not first_node:
return False, "流程没有任何节点"
# 获取下一个待执行节点
next_pending = get_next_pending_state(business_object, include_parameters=False)
if next_pending is None:
# 没有待执行节点,流程已完成
return False, "流程已完成,无法继续推进"
next_state = next_pending['state']
return True, f"可以推进到: {next_state.name}"
def get_business_object_state_timeline(business_object: 'models.BusinessObject') -> List[dict]:
"""
获取业务对象状态时间线(包括未开始和已完成的状态)
返回格式:
[
{
'state': State对象,
'status': 'not_started' | 'completed' | 'cancelled',
'order': 顺序号,
'completed_at': 完成时间(如果已完成),
'completed_by': 完成人(如果已完成),
'cancelled_at': 撤销时间(如果已撤销),
'is_cancelled': 是否已撤销,
},
...
]
状态判断规则:
- cancelled: 有日志记录且已撤销
- completed: 有日志记录且未撤销
- not_started: 没有日志记录(还未完成)
注意:移除了 'in_progress' 状态,因为在当前设计中:
- current_state 表示"下一个待执行的节点",它还未开始执行,所以是 not_started
- 一个节点要么已完成,要么还未开始,没有"进行中"的中间状态
"""
timeline = []
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
# 构建状态日志映射(包括已撤销的记录)
state_logs_map = {
log.state_id: log
for log in business_object.state_logs.select_related('completed_by')
}
# 获取 current_state下一个待执行的节点
current_state = get_business_object_current_state(business_object)
current_state_id = current_state.id if current_state else None
# 检查是否有任何完成记录
has_any_completed = business_object.state_logs.filter(is_cancelled=False).exists()
for node in process_nodes:
state = node.state
log = state_logs_map.get(state.id)
if log:
if log.is_cancelled:
status = 'cancelled'
else:
status = 'completed'
completed_at = log.completed_at
completed_by = log.completed_by
cancelled_at = log.cancelled_at
is_cancelled = log.is_cancelled
else:
# 未开始的节点
# current_state 是"下一个待执行的节点",它还未开始,所以状态是 not_started
status = 'not_started'
completed_at = None
completed_by = None
cancelled_at = None
is_cancelled = False
timeline.append({
'state': state,
'status': status,
'order': node.order,
'completed_at': completed_at,
'completed_by': completed_by,
'cancelled_at': cancelled_at,
'is_cancelled': is_cancelled,
})
return timeline
def get_next_pending_state(
business_object: 'models.BusinessObject',
include_parameters: bool = True
) -> Optional[dict]:
"""
获取指定业务对象的下一个待执行节点(第一个未完成的节点)
Args:
business_object: 业务对象
include_parameters: 是否包含参数列表,默认 True
Returns:
包含状态信息的字典,如果没有待执行节点则返回 None
{
'state': State对象,
'order': 节点顺序号,
'parameters': [StateParameter列表] (如果 include_parameters=True)
}
"""
# 获取所有节点(按顺序)
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
if not process_nodes.exists():
return None
# 获取已完成且未被撤销的状态ID集合
completed_state_ids = set(
business_object.state_logs.filter(is_cancelled=False).values_list('state_id', flat=True)
)
# 找到第一个未完成的节点
for node in process_nodes:
if node.state_id not in completed_state_ids:
result = {
'state': node.state,
'order': node.order,
}
if include_parameters:
result['parameters'] = list(node.state.parameters.all())
return result
# 所有节点都已完成
return None
def get_all_pending_states(business_object: 'models.BusinessObject') -> List[dict]:
"""
获取指定业务对象所有待执行的节点列表(不包含参数)
Args:
business_object: 业务对象
Returns:
待执行节点列表,每个元素包含:
{
'state': State对象,
'order': 节点顺序号
}
"""
# 获取所有节点
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
# 获取已完成且未被撤销的状态ID集合
completed_state_ids = set(
business_object.state_logs.filter(is_cancelled=False).values_list('state_id', flat=True)
)
# 找到所有未完成的节点
pending_nodes = []
for node in process_nodes:
if node.state_id not in completed_state_ids:
pending_nodes.append({
'state': node.state,
'order': node.order,
})
return pending_nodes
def get_state_parameters(
state: 'models.State',
required_only: bool = False
) -> List['models.StateParameter']:
"""
获取指定节点的所有参数列表
Args:
state: 状态节点
required_only: 是否仅包含必填参数,默认 False
Returns:
参数列表
"""
return state.get_parameters(required_only=required_only)
def get_required_parameters(state: 'models.State'):
"""
获取状态的所有必填参数
参数:
state: State 实例
返回:
QuerySet: 必填的 StateParameter 对象
"""
return state.parameters.filter(is_required=True)
def validate_required_parameters(state: 'models.State', **kwargs) -> Tuple[bool, List[str]]:
"""
验证必填参数是否全部提供
参数:
state: State 实例
**kwargs: 用户提供的参数字典
返回:
tuple: (is_valid: bool, missing_params: list)
- is_valid: 是否通过验证
- missing_params: 缺失的必填参数 key 列表
"""
required_params = get_required_parameters(state)
required_keys = list(required_params.values_list('key', flat=True))
provided_keys = set(kwargs.keys())
missing_keys = [key for key in required_keys if key not in provided_keys]
is_valid = len(missing_keys) == 0
return is_valid, missing_keys
def create_parameter_record(state_log: 'models.StateFlowRecord', remark: str = '', **parameters) -> 'models.StateLogParameterRecord':
"""
为状态流转记录创建参数记录
参数:
state_log: StateFlowRecord 实例
remark: 备注
**parameters: 参数字典(直接保存,不做验证)
返回:
StateLogParameterRecord: 创建的参数记录
"""
record = models.StateLogParameterRecord.objects.create(
state_log=state_log,
parameters=parameters, # 直接保存为 JSON
remark=remark
)
return record
def add_parameters_to_state_log(state_log: 'models.StateFlowRecord', remark: str = '', **parameters) -> 'models.StateLogParameterRecord':
"""
为已有的状态流转记录补充参数支持重复key
参数:
state_log: StateFlowRecord 实例
remark: 备注
**parameters: 参数字典
返回:
StateLogParameterRecord: 创建的参数记录
"""
return create_parameter_record(state_log, remark, **parameters)
def get_process_nodes(business_object: 'models.BusinessObject') -> List[dict]:
"""
获取业务对象所属流程的所有状态节点列表
参数:
business_object: BusinessObject 实例
返回:
节点列表,每个元素包含:
{
'id': ProcessNode ID,
'state': State对象,
'state_id': State ID,
'state_name': State 名称,
'order': 节点顺序号
}
"""
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
return [
{
'id': node.id,
'state': node.state,
'state_id': node.state_id,
'state_name': node.state.name,
'order': node.order,
}
for node in process_nodes
]
def query_business_objects_by_state_status(
*,
state_ids: Iterable[int] | None = None,
status: str = 'completed',
process_id: int | None = None,
base_queryset: QuerySet | None = None,
) -> QuerySet:
"""
按节点状态过滤 BusinessObject 查询集
Args:
state_ids: 目标节点集合(为空时表示任意节点)
status: 过滤状态,支持 completed/not_started/cancelled/in_progress
process_id: 限定流程 ID
base_queryset: 可复用的基础查询集
Returns:
过滤后的 QuerySet
"""
if status not in {'completed', 'not_started', 'cancelled', 'in_progress'}:
raise ValueError('unsupported status value')
qs = base_queryset if base_queryset is not None else models.BusinessObject.objects.all()
state_id_list = list(state_ids) if state_ids is not None else None
if process_id is not None:
qs = qs.filter(process_id=process_id)
if state_id_list:
qs = qs.filter(process__process_nodes__state_id__in=state_id_list)
state_logs = models.StateFlowRecord.objects.filter(business_object_id=OuterRef('pk'))
if state_id_list:
state_logs = state_logs.filter(state_id__in=state_id_list)
qs = qs.annotate(
has_state_log=Exists(state_logs),
has_state_completed=Exists(state_logs.filter(is_cancelled=False)),
has_state_cancelled=Exists(state_logs.filter(is_cancelled=True)),
)
if status == 'completed':
qs = qs.filter(has_state_completed=True)
elif status == 'cancelled':
qs = qs.filter(has_state_cancelled=True)
elif status == 'not_started':
qs = qs.filter(has_state_log=False)
elif status == 'in_progress':
qs = qs.filter(has_state_log=True, has_state_completed=False, has_state_cancelled=False)
return qs.distinct()
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 一致,否则拒绝克隆
2026-01-13 暂停使用:为避免误用导致流程副本错误,暂时禁用此能力。
"""
raise NotImplementedError("clone_business_object is disabled temporarily (2026-01-13)")
# 原始实现(保留供后续恢复参考):
# if source is None:
# raise ValueError("source 不能为空")
# if source.content_type_id is None or source.object_id is None:
# raise ValueError("源对象未绑定关联对象content_type/object_id禁止克隆")
# if expected_content_type_id is None:
# raise ValueError("必须提供 expected_content_type_id")
# 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)
# )
# if source.content_type_id != expected_content_type_id:
# raise ValueError("content_type 与源对象不一致,拒绝克隆")
# if new_object_id is None:
# raise ValueError("必须提供新的 object_id")
# if source.object_id == new_object_id:
# raise ValueError("object_id 必须与源对象不同")
# try:
# source.content_type.get_object_for_this_type(pk=new_object_id)
# except ObjectDoesNotExist:
# raise ValueError(
# f"目标关联对象不存在:{source.content_type.app_label}.{source.content_type.model} #{new_object_id}"
# )
# 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,
# )
# models.BusinessObject.objects.filter(id=cloned.id).update(
# created_at=source.created_at,
# updated_at=source.updated_at,
# )
# 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,
# )
# 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,
# )
# 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,
# )
# cloned.refresh_from_db()
# return cloned