forked from erp-dev/erp
feat: added plate order clone
This commit is contained in:
@@ -1,14 +1,29 @@
|
||||
import datetime
|
||||
|
||||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
from rest_framework import serializers, status, permissions
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from printing import models as printing_models
|
||||
from api_man.serializers import ProductSerializer
|
||||
|
||||
|
||||
class IsPrintingFactory(permissions.BasePermission):
|
||||
"""仅允许印染工厂用户访问(与 api_v1 逻辑保持一致)"""
|
||||
|
||||
message = '您没有访问印染订单的权限'
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if not request.user or not request.user.is_authenticated:
|
||||
return False
|
||||
if hasattr(request.user, 'employee'):
|
||||
return request.user.employee.merchant.type == basic_models.MerchantTypeEnum.FACTORY
|
||||
return False
|
||||
|
||||
|
||||
class PrintingJobV2Serializer(serializers.ModelSerializer):
|
||||
"""v2 独立的印染任务序列化器,包含开单数量"""
|
||||
|
||||
@@ -138,3 +153,210 @@ class PrintingJobByCustomerView(APIView):
|
||||
|
||||
serializer = self.serializer_class(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class PrintingJobBatchAdvancePreviewRequestSerializer(serializers.Serializer):
|
||||
"""批量推进:预览/校验请求"""
|
||||
|
||||
printing_job_ids = serializers.ListField(
|
||||
child=serializers.IntegerField(min_value=1),
|
||||
allow_empty=False,
|
||||
help_text='需要批量推进的 printing_job id 列表',
|
||||
)
|
||||
|
||||
def validate_printing_job_ids(self, value):
|
||||
# 去重保持稳定性(前端可能重复传)
|
||||
deduped = list(dict.fromkeys(value))
|
||||
if not deduped:
|
||||
raise serializers.ValidationError('printing_job_ids 不能为空')
|
||||
return deduped
|
||||
|
||||
|
||||
class PrintingJobBatchAdvanceSubmitRequestSerializer(PrintingJobBatchAdvancePreviewRequestSerializer):
|
||||
"""批量推进:提交请求"""
|
||||
|
||||
parameters = serializers.DictField(
|
||||
child=serializers.JSONField(),
|
||||
required=False,
|
||||
default=dict,
|
||||
help_text='与单条推进接口一致的工艺参数(将作为 **kwargs 传给 stateflow)',
|
||||
)
|
||||
|
||||
|
||||
def _validate_jobs_for_batch_advance(printing_job_ids: list[int]):
|
||||
"""
|
||||
批量推进的核心一致性校验(preview 与 submit 共用)
|
||||
|
||||
规则:
|
||||
1) 所有 id 都存在
|
||||
2) 全部属于同一个 printing_order
|
||||
3) 全部存在 business_object(流程实例)
|
||||
4) 全部具有相同的 next_pending_state(下一待执行节点),否则不允许批量
|
||||
|
||||
注意:这里不做“竞态”处理(preview 后 submit 前状态变化),submit 时会再次调用该函数重新校验。
|
||||
后续如需增强,可在 preview 返回 snapshot token,在 submit 校验 token 以提升用户体验。
|
||||
"""
|
||||
# 查询并校验存在性
|
||||
qs = (
|
||||
printing_models.PrintingJob.objects
|
||||
.select_related('printing_order', 'business_object', 'business_object__process')
|
||||
.filter(id__in=printing_job_ids)
|
||||
)
|
||||
jobs = list(qs)
|
||||
found_ids = {j.id for j in jobs}
|
||||
missing_ids = [str(i) for i in printing_job_ids if i not in found_ids]
|
||||
if missing_ids:
|
||||
raise serializers.ValidationError({'detail': f'以下 printing_job 不存在: {", ".join(missing_ids)}'})
|
||||
|
||||
# 同一订单
|
||||
order_ids = {j.printing_order_id for j in jobs}
|
||||
if len(order_ids) != 1:
|
||||
raise serializers.ValidationError({'detail': '所选明细不属于同一个 printing_order,无法批量推进'})
|
||||
printing_order_id = next(iter(order_ids))
|
||||
printing_order = jobs[0].printing_order
|
||||
|
||||
# 必须有关联流程实例
|
||||
no_bo = [str(j.id) for j in jobs if not j.business_object_id]
|
||||
if no_bo:
|
||||
raise serializers.ValidationError({'detail': f'以下 printing_job 未关联流程实例(business_object),无法推进: {", ".join(no_bo)}'})
|
||||
|
||||
# 计算并校验 next_pending_state 一致
|
||||
from stateflow import services as stateflow_services
|
||||
|
||||
next_infos = []
|
||||
for j in jobs:
|
||||
info = stateflow_services.get_next_pending_state(j.business_object, include_parameters=True)
|
||||
if info is None:
|
||||
next_infos.append((j.id, None))
|
||||
else:
|
||||
next_infos.append((j.id, info))
|
||||
|
||||
# 不能包含“无待执行节点”(流程已完成或无节点)
|
||||
cannot_advance = [str(job_id) for job_id, info in next_infos if info is None]
|
||||
if cannot_advance:
|
||||
raise serializers.ValidationError({'detail': f'以下 printing_job 没有待执行节点(流程已完成或无节点),无法批量推进: {", ".join(cannot_advance)}'})
|
||||
|
||||
# 比对 state_id
|
||||
first_info = next_infos[0][1]
|
||||
target_state = first_info['state']
|
||||
target_order = first_info['order']
|
||||
target_state_id = target_state.id
|
||||
|
||||
diff_jobs = []
|
||||
for job_id, info in next_infos:
|
||||
if info['state'].id != target_state_id:
|
||||
diff_jobs.append(str(job_id))
|
||||
if diff_jobs:
|
||||
raise serializers.ValidationError({'detail': f'所选明细当前待执行节点不一致,无法批量推进(不同节点的 jobs: {", ".join(diff_jobs)})'})
|
||||
|
||||
# 参数定义取目标节点(所有一致)
|
||||
target_parameters = first_info.get('parameters', []) or []
|
||||
|
||||
return {
|
||||
'printing_order': printing_order,
|
||||
'printing_order_id': printing_order_id,
|
||||
'jobs': jobs,
|
||||
'target_state': target_state,
|
||||
'target_order': target_order,
|
||||
'target_parameters': target_parameters,
|
||||
}
|
||||
|
||||
|
||||
class PrintingJobBatchAdvancePreviewView(APIView):
|
||||
"""
|
||||
批量推进:预览
|
||||
|
||||
作用:
|
||||
- 校验 printing_job_ids 是否可批量推进(同订单/同待执行节点)
|
||||
- 返回“下一步待执行节点”的信息及其工艺参数定义,供前端生成批量表单
|
||||
"""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticated, IsPrintingFactory]
|
||||
|
||||
def post(self, request):
|
||||
srz = PrintingJobBatchAdvancePreviewRequestSerializer(data=request.data)
|
||||
srz.is_valid(raise_exception=True)
|
||||
|
||||
data = _validate_jobs_for_batch_advance(srz.validated_data['printing_job_ids'])
|
||||
|
||||
from stateflow.serializers import StateParameterSerializer
|
||||
|
||||
params_srz = StateParameterSerializer(
|
||||
data['target_parameters'],
|
||||
many=True,
|
||||
context={'request': request},
|
||||
)
|
||||
|
||||
return Response({
|
||||
'printing_order_id': data['printing_order_id'],
|
||||
'printing_job_ids': [j.id for j in data['jobs']],
|
||||
'next_state': {
|
||||
'id': data['target_state'].id,
|
||||
'name': data['target_state'].name,
|
||||
'description': data['target_state'].description,
|
||||
'order': data['target_order'],
|
||||
'parameters': params_srz.data,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
class PrintingJobBatchAdvanceSubmitView(APIView):
|
||||
"""
|
||||
批量推进:提交
|
||||
|
||||
规则:全成功/全失败
|
||||
- 任意一个 job 推进失败:整体回滚(不产生任何 stateflow 日志,也不产生批量推进记录)
|
||||
"""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticated, IsPrintingFactory]
|
||||
|
||||
def post(self, request):
|
||||
srz = PrintingJobBatchAdvanceSubmitRequestSerializer(data=request.data)
|
||||
srz.is_valid(raise_exception=True)
|
||||
|
||||
payload = srz.validated_data
|
||||
parameters = payload.get('parameters') or {}
|
||||
|
||||
data = _validate_jobs_for_batch_advance(payload['printing_job_ids'])
|
||||
|
||||
from stateflow import services as stateflow_services
|
||||
|
||||
# 全成功/全失败:用事务包住整个批量推进
|
||||
with transaction.atomic():
|
||||
record = printing_models.PrintingJobBatchAdvanceRecord.objects.create(
|
||||
printing_order=data['printing_order'],
|
||||
state=data['target_state'],
|
||||
created_by=request.user,
|
||||
parameters=parameters,
|
||||
)
|
||||
record.printing_jobs.set(data['jobs'])
|
||||
|
||||
# 逐个复用单条推进逻辑
|
||||
last_message = None
|
||||
for job in data['jobs']:
|
||||
ok, msg, _state_log = stateflow_services.advance_to_next_state(
|
||||
job.business_object,
|
||||
request.user,
|
||||
**parameters
|
||||
)
|
||||
if not ok:
|
||||
# 抛异常触发事务回滚,保证“全部失败”
|
||||
raise serializers.ValidationError({'detail': msg})
|
||||
last_message = msg
|
||||
|
||||
# 返回最新的 job 列表(可用于前端刷新)
|
||||
refreshed_jobs = (
|
||||
printing_models.PrintingJob.objects
|
||||
.select_related('printing_order', 'product')
|
||||
.filter(id__in=[j.id for j in data['jobs']])
|
||||
.order_by('id')
|
||||
)
|
||||
job_srz = PrintingJobV2Serializer(refreshed_jobs, many=True)
|
||||
|
||||
return Response({
|
||||
'detail': last_message or '批量推进成功',
|
||||
'batch_id': record.id,
|
||||
'printing_order_id': data['printing_order_id'],
|
||||
'printing_job_ids': [j.id for j in data['jobs']],
|
||||
'jobs': job_srz.data,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user