1
0
forked from erp-dev/erp

feat: new api for plate order (get_plate_order_by_state_status)

This commit is contained in:
2025-12-25 17:18:44 +08:00
parent 2c5174a1ec
commit ff1ea2482d
21 changed files with 936 additions and 58 deletions

View File

@@ -22,6 +22,38 @@ class Command(BaseCommand):
default=0.02,
help='每次明道云请求之间的最小间隔用于限流50qps 建议 >= 0.02',
)
parser.add_argument(
'--sort-direction',
choices=['asc', 'desc'],
default='asc',
help='按 ctime 升序asc或降序desc抓取',
)
parser.add_argument(
'--use-checkpoint',
dest='use_checkpoint',
action='store_true',
default=True,
help='读取 DataSync 游标(默认开启)',
)
parser.add_argument(
'--skip-checkpoint',
dest='use_checkpoint',
action='store_false',
help='忽略 DataSync 游标,从第一页开始',
)
parser.add_argument(
'--update-checkpoint',
dest='update_checkpoint',
action='store_true',
default=True,
help='同步完成后写入 DataSync 记录(默认开启)',
)
parser.add_argument(
'--skip-checkpoint-write',
dest='update_checkpoint',
action='store_false',
help='本次同步不写入 DataSync 记录',
)
def handle(self, *args, **options):
payload = sync_mdy_plate_orders_to_staging(
@@ -31,5 +63,8 @@ class Command(BaseCommand):
with_related=options['with_related'],
max_related_per_type=options['max_related_per_type'],
request_interval_seconds=options['request_interval_seconds'],
use_checkpoint=options['use_checkpoint'],
update_checkpoint=options['update_checkpoint'],
sort_direction=options['sort_direction'],
)
self.stdout.write(self.style.SUCCESS(str(payload)))

View File

@@ -2,6 +2,7 @@ import asyncio
import logging
from datetime import datetime
import time
from typing import Literal
from django.utils import timezone
@@ -93,6 +94,9 @@ def sync_mdy_plate_orders_to_staging(
with_related: bool = True,
max_related_per_type: int = 5,
request_interval_seconds: float = 0.02,
use_checkpoint: bool = True,
update_checkpoint: bool = True,
sort_direction: Literal["asc", "desc"] = "asc",
) -> dict:
"""同步明道云开版数据表到暂存表(含可选跨表关联数据)。
@@ -104,16 +108,29 @@ def sync_mdy_plate_orders_to_staging(
说明:
- max_pages 表示“单次任务最多处理多少页”(不是最大页码)
- max_records 表示“单次任务最多处理多少条记录”0/None 表示不限制)
- use_checkpoint 控制本次是否读取 DataSync 游标
- update_checkpoint 控制本次是否写入 DataSync 记录
- sort_direction 为 `asc`(默认)或 `desc`,决定按时间升/降序抓取
"""
max_records = max_records or 0
sort_direction = (sort_direction or "asc").lower()
if sort_direction not in {"asc", "desc"}:
raise ValueError("sort_direction must be 'asc' or 'desc'")
is_asc = sort_direction == "asc"
last_sync = api_models.DataSync.objects.filter(
table_name=api_models.DataSync.TableName.PLATE_ORDER
).order_by("-created_at").first()
last_sync = None
if use_checkpoint:
last_sync = (
api_models.DataSync.objects.filter(
table_name=api_models.DataSync.TableName.PLATE_ORDER
)
.order_by("-created_at")
.first()
)
last_ctime = last_sync.last_ctime if last_sync else None
last_rowid = last_sync.last_rowid if last_sync else ""
start_page_index = last_sync.page_index if last_sync else 1
start_page_index = last_sync.page_index if last_sync and last_sync.page_index else 1
synced_rows = 0
page_index = max(1, start_page_index)
@@ -133,7 +150,7 @@ def sync_mdy_plate_orders_to_staging(
page=page_index,
page_size=page_size,
sort_id="ctime",
is_asc=True,
is_asc=is_asc,
)
)
if request_interval_seconds:
@@ -156,7 +173,7 @@ def sync_mdy_plate_orders_to_staging(
record_ctime = _parse_mdy_datetime(row.get("ctime"))
# 跳过已同步到的游标
if last_ctime and record_ctime:
if use_checkpoint and last_ctime and record_ctime:
if record_ctime < last_ctime:
continue
if record_ctime == last_ctime and last_rowid and rowid == last_rowid:
@@ -192,11 +209,18 @@ def sync_mdy_plate_orders_to_staging(
record_ctime = _parse_mdy_datetime(row.get("ctime"))
if record_ctime:
if latest_ctime is None or record_ctime > latest_ctime:
if latest_ctime is None:
latest_ctime = record_ctime
latest_rowid = rowid
elif record_ctime == latest_ctime:
latest_rowid = rowid
else:
should_update = (
record_ctime > latest_ctime if is_asc else record_ctime < latest_ctime
)
if should_update:
latest_ctime = record_ctime
latest_rowid = rowid
elif record_ctime == latest_ctime:
latest_rowid = rowid
pages_processed += 1
if hit_max_records:
@@ -209,16 +233,20 @@ def sync_mdy_plate_orders_to_staging(
record_last_ctime = latest_ctime or last_ctime
record_last_rowid = latest_rowid or last_rowid
api_models.DataSync.objects.create(
table_name=api_models.DataSync.TableName.PLATE_ORDER,
page_index=page_index,
page_size=page_size,
synced_rows=synced_rows,
total_count=total_count,
last_ctime=record_last_ctime,
last_rowid=record_last_rowid,
note=f"asc scan; with_related={with_related}",
)
if update_checkpoint:
api_models.DataSync.objects.create(
table_name=api_models.DataSync.TableName.PLATE_ORDER,
page_index=page_index,
page_size=page_size,
synced_rows=synced_rows,
total_count=total_count,
last_ctime=record_last_ctime,
last_rowid=record_last_rowid,
note=(
f"{sort_direction} scan; with_related={with_related}; "
f"use_checkpoint={use_checkpoint}"
),
)
payload = {
"synced_rows": synced_rows,
@@ -227,6 +255,9 @@ def sync_mdy_plate_orders_to_staging(
"total_count": total_count,
"last_ctime": record_last_ctime.isoformat() if record_last_ctime else None,
"with_related": with_related,
"sort_direction": sort_direction,
"use_checkpoint": use_checkpoint,
"update_checkpoint": update_checkpoint,
}
logger.info("明道云开版暂存同步完成: %s", payload)
return payload

View File

@@ -2,6 +2,8 @@
Process API ViewSet
"""
from rest_framework import viewsets, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.pagination import LimitOffsetPagination
from django_filters.rest_framework import DjangoFilterBackend
from django.db.models import Count
@@ -10,6 +12,7 @@ from stateflow.serializers import (
ProcessListSerializer,
ProcessDetailSerializer,
ProcessCreateUpdateSerializer,
StateListSerializer,
)
@@ -57,3 +60,16 @@ class ProcessViewSet(viewsets.ModelViewSet):
queryset = queryset.prefetch_related('process_nodes__state')
return queryset
@action(detail=True, methods=['get'])
def nodes(self, request, pk=None):
"""返回指定流程的所有节点列表"""
process = self.get_object()
states = process.get_nodes()
data = StateListSerializer(states, many=True).data
return Response({
'process_id': process.id,
'process_name': process.name,
'count': len(data),
'nodes': data,
})