From 808e5699f9a76b8c6b436f9332e46f92a2e1e92a Mon Sep 17 00:00:00 2001 From: colaftc Date: Wed, 12 Nov 2025 17:08:20 +0800 Subject: [PATCH] feat: added printing module --- api_man/serializers.py | 2 +- api_v1/urls.py | 21 +- api_v1/views/printing/serializers.py | 0 api_v1/views/printing/test_api.py | 400 ++++++++++++++++++ api_v1/views/stateflow/business_object.py | 2 +- api_v1/views/stock_change_views/snapshot.py | 98 +++++ basic_info/admin.py | 2 +- flower/settings.py | 1 + printing/__init__.py | 0 printing/admin.py | 26 ++ printing/apps.py | 7 + printing/migrations/0001_initial.py | 65 +++ ...0002_alter_printingorder_craft_and_more.py | 23 + .../0003_remove_printingjob_status.py | 17 + .../0004_printingorder_is_invalid.py | 18 + .../0005_alter_printingorder_options.py | 17 + printing/migrations/__init__.py | 0 printing/models.py | 73 ++++ printing/serializers.py | 59 +++ printing/services.py | 0 printing/tests.py | 3 + printing/views.py | 227 ++++++++++ stateflow/apps.py | 2 +- ...3_alter_stateflowrecord_unique_together.py | 17 + .../migrations/0014_alter_process_options.py | 17 + stateflow/models.py | 6 +- stateflow/services.py | 34 +- stateflow/tests.py | 4 +- 28 files changed, 1110 insertions(+), 31 deletions(-) create mode 100644 api_v1/views/printing/serializers.py create mode 100644 api_v1/views/printing/test_api.py create mode 100644 api_v1/views/stock_change_views/snapshot.py create mode 100644 printing/__init__.py create mode 100644 printing/admin.py create mode 100644 printing/apps.py create mode 100644 printing/migrations/0001_initial.py create mode 100644 printing/migrations/0002_alter_printingorder_craft_and_more.py create mode 100644 printing/migrations/0003_remove_printingjob_status.py create mode 100644 printing/migrations/0004_printingorder_is_invalid.py create mode 100644 printing/migrations/0005_alter_printingorder_options.py create mode 100644 printing/migrations/__init__.py create mode 100644 printing/models.py create mode 100644 printing/serializers.py create mode 100644 printing/services.py create mode 100644 printing/tests.py create mode 100644 printing/views.py create mode 100644 stateflow/migrations/0013_alter_stateflowrecord_unique_together.py create mode 100644 stateflow/migrations/0014_alter_process_options.py diff --git a/api_man/serializers.py b/api_man/serializers.py index 19eee11..e99a9f9 100644 --- a/api_man/serializers.py +++ b/api_man/serializers.py @@ -40,7 +40,7 @@ class BaseSerializer(serializers.ModelSerializer): class QuickInputSerializer(serializers.ModelSerializer): class Meta: model = basic_models.QuickInput - fields = ['name', 'value', 'group'] + fields = ['id', 'name', 'value', 'group'] class ProductCategorySerializer(BaseSerializer): diff --git a/api_v1/urls.py b/api_v1/urls.py index 8bb1a03..da28afb 100644 --- a/api_v1/urls.py +++ b/api_v1/urls.py @@ -1,15 +1,21 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import stock_change_views, user_info, inventory, product_image, stateflow +from .views.stock_change_views.snapshot import StockSnapshotListView +from printing.views import PrintingOrderViewSet -# 创建 DRF Router -router = DefaultRouter() -router.register(r'states', stateflow.StateViewSet, basename='state') -router.register(r'processes', stateflow.ProcessViewSet, basename='process') -router.register(r'business-objects', stateflow.BusinessObjectViewSet, basename='business-object') +# 创建 DRF Router for Stateflow +stateflow_router = DefaultRouter() +stateflow_router.register(r'states', stateflow.StateViewSet, basename='state') +stateflow_router.register(r'processes', stateflow.ProcessViewSet, basename='process') + +# 创建主 Router +main_router = DefaultRouter() +main_router.register(r'printing-orders', PrintingOrderViewSet, basename='printing-order') urlpatterns = [ # 库存变动相关API + path('stock-snapshots/', StockSnapshotListView.as_view(), name='list_stock_snapshots'), path('stock-changes/', stock_change_views.list_stock_changes, name='list_stock_changes'), path('stock-change/', stock_change_views.create_full_stock_change, name='create_full_stock_change'), path('stock-change//', stock_change_views.get_stock_change, name='get_stock_change'), @@ -29,5 +35,8 @@ urlpatterns = [ path('products//image/', product_image.ProductImageUploadView.as_view(), name='product_image_upload'), # Stateflow API (使用 Router) - path('stateflow/', include(router.urls)), + path('stateflow/', include(stateflow_router.urls)), + + # 主 Router (printing-orders 等) + path('', include(main_router.urls)), ] diff --git a/api_v1/views/printing/serializers.py b/api_v1/views/printing/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/api_v1/views/printing/test_api.py b/api_v1/views/printing/test_api.py new file mode 100644 index 0000000..4f5a9ff --- /dev/null +++ b/api_v1/views/printing/test_api.py @@ -0,0 +1,400 @@ +""" +PrintingOrder API 测试 +""" +from django.test import TestCase +from rest_framework.test import APIClient +from rest_framework import status +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission +from basic_info import models as basic_models +from printing import models as printing_models + +User = get_user_model() + + +class PrintingOrderAPITestCase(TestCase): + """测试 PrintingOrder API""" + + def setUp(self): + self.client = APIClient() + + # 创建商户 + self.merchant = basic_models.Merchant.objects.create( + name='测试印花厂', + type=basic_models.MerchantTypeEnum.FACTORY + ) + + # 创建用户 + self.user = User.objects.create_user( + username='testuser', + password='testpass123', + email='test@example.com' + ) + + # 创建员工并关联商户 + self.employee = basic_models.Employee.objects.create( + sys_user=self.user, + merchant=self.merchant, + name='测试员工', + mobile='13800138000', + job_type=basic_models.EmployeeTypeEnum.PRINTER, + status=basic_models.EmployeeStatusEnum.ACTIVE + ) + + # 创建客户 + self.customer = basic_models.Customer.objects.create( + merchant=self.merchant, + name='测试客户', + mobile='13900139000', + area='测试地区' + ) + + # 认证用户 + self.client.force_authenticate(user=self.user) + + # 给用户添加基础权限 + view_perm = Permission.objects.get(codename='view_printingorder') + add_perm = Permission.objects.get(codename='add_printingorder') + change_perm = Permission.objects.get(codename='change_printingorder') + self.user.user_permissions.add(view_perm, add_perm, change_perm) + + def test_create_printing_order(self): + """测试创建印染订单""" + data = { + 'customer': self.customer.id, + 'fabric': '纯棉布料', + 'width': '150cm', + 'is_urgent': True, + 'area': '广州', + 'address': '白云区xxx', + 'fabric_source': '客户提供', + 'is_fabric_received': False, + 'craft': '活性印花', + 'description': '测试订单描述', + 'outgoing_date': '2025-11-20', + 'printing_warn': '注意颜色', + 'rolling_warn': '注意温度', + 'production_warn': '质量检查' + } + + response = self.client.post('/api/v1/printing-orders/', data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # 检查响应数据 + self.assertIn('fabric', response.data) + self.assertEqual(response.data['fabric'], '纯棉布料') + self.assertEqual(response.data['is_urgent'], True) + + # 验证订单已创建 - 通过fabric查找,因为响应可能不包含id + order = printing_models.PrintingOrder.objects.filter(fabric='纯棉布料').first() + self.assertIsNotNone(order) + self.assertEqual(order.customer.id, self.customer.id) + self.assertEqual(order.fabric, '纯棉布料') + + def test_list_printing_orders(self): + """测试获取订单列表""" + # 创建测试订单 + printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='布料1', + width='150cm' + ) + printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='布料2', + width='160cm' + ) + + response = self.client.get('/api/v1/printing-orders/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + # 不使用分页参数时,返回列表 + self.assertEqual(len(response.data), 2) + + def test_retrieve_printing_order(self): + """测试获取订单详情""" + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm', + craft='活性印花', + description='详情测试' + ) + + response = self.client.get(f'/api/v1/printing-orders/{order.id}/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['fabric'], '测试布料') + self.assertEqual(response.data['craft'], '活性印花') + self.assertIn('customer_name', response.data) + self.assertEqual(response.data['customer_name'], self.customer.name) + + def test_update_printing_order(self): + """测试更新订单""" + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='旧布料', + width='150cm' + ) + + update_data = { + 'customer': self.customer.id, + 'fabric': '新布料', + 'width': '160cm', + 'is_urgent': True, + 'craft': '更新的工艺' + } + + response = self.client.put( + f'/api/v1/printing-orders/{order.id}/', + update_data, + format='json' + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + order.refresh_from_db() + self.assertEqual(order.fabric, '新布料') + self.assertEqual(order.width, '160cm') + self.assertEqual(order.is_urgent, True) + + def test_partial_update_printing_order(self): + """测试部分更新订单""" + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='原布料', + width='150cm', + is_urgent=False + ) + + patch_data = { + 'is_urgent': True, + 'craft': '新工艺' + } + + response = self.client.patch( + f'/api/v1/printing-orders/{order.id}/', + patch_data, + format='json' + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + order.refresh_from_db() + self.assertEqual(order.is_urgent, True) + self.assertEqual(order.craft, '新工艺') + self.assertEqual(order.fabric, '原布料') # 未修改字段保持不变 + + def test_delete_printing_order_forbidden(self): + """测试删除订单被禁用""" + # 添加删除权限以便测试destroy方法的自定义逻辑 + delete_perm = Permission.objects.get(codename='delete_printingorder') + self.user.user_permissions.add(delete_perm) + + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm' + ) + + response = self.client.delete(f'/api/v1/printing-orders/{order.id}/') + self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) + self.assertIn('不支持删除', response.data['detail']) + + # 验证订单仍然存在 + self.assertTrue( + printing_models.PrintingOrder.objects.filter(id=order.id).exists() + ) + + def test_invalidate_printing_order(self): + """测试作废订单""" + # 添加作废权限 + invalidate_perm = Permission.objects.get(codename='can_invalidate_printingorder') + self.user.user_permissions.add(invalidate_perm) + + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm', + is_invalid=False + ) + + response = self.client.post(f'/api/v1/printing-orders/{order.id}/invalidate/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('已作废', response.data['detail']) + + order.refresh_from_db() + self.assertTrue(order.is_invalid) + + def test_invalidate_without_permission(self): + """测试没有权限时作废订单失败""" + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm' + ) + + response = self.client.post(f'/api/v1/printing-orders/{order.id}/invalidate/') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertIn('没有权限', response.data['detail']) + + def test_activate_printing_order(self): + """测试恢复订单""" + # 添加恢复权限 + activate_perm = Permission.objects.get(codename='can_activate_printingorder') + self.user.user_permissions.add(activate_perm) + + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm', + is_invalid=True + ) + + response = self.client.post(f'/api/v1/printing-orders/{order.id}/activate/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('已恢复', response.data['detail']) + + order.refresh_from_db() + self.assertFalse(order.is_invalid) + + def test_activate_without_permission(self): + """测试没有权限时恢复订单失败""" + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm', + is_invalid=True + ) + + response = self.client.post(f'/api/v1/printing-orders/{order.id}/activate/') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertIn('没有权限', response.data['detail']) + + def test_mark_fabric_received(self): + """测试标记布料已收""" + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm', + is_fabric_received=False + ) + + response = self.client.post(f'/api/v1/printing-orders/{order.id}/mark_fabric_received/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('已标记布料已收', response.data['detail']) + + order.refresh_from_db() + self.assertTrue(order.is_fabric_received) + + def test_mark_fabric_received_already_received(self): + """测试重复标记布料已收""" + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm', + is_fabric_received=True + ) + + response = self.client.post(f'/api/v1/printing-orders/{order.id}/mark_fabric_received/') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('已经标记', response.data['detail']) + + def test_filter_by_customer(self): + """测试按客户过滤""" + customer2 = basic_models.Customer.objects.create( + merchant=self.merchant, + name='客户2', + mobile='13900139001' + ) + + printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='布料1', + width='150cm' + ) + printing_models.PrintingOrder.objects.create( + customer=customer2, + fabric='布料2', + width='160cm' + ) + + response = self.client.get(f'/api/v1/printing-orders/?customer={self.customer.id}') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + + def test_filter_by_urgent(self): + """测试按紧急状态过滤""" + printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='布料1', + width='150cm', + is_urgent=True + ) + printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='布料2', + width='160cm', + is_urgent=False + ) + + response = self.client.get('/api/v1/printing-orders/?is_urgent=true') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertTrue(response.data[0]['is_urgent']) + + def test_search_by_fabric(self): + """测试按布料搜索""" + printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='纯棉布料', + width='150cm' + ) + printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='涤纶布料', + width='160cm' + ) + + response = self.client.get('/api/v1/printing-orders/?search=纯棉') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertIn('纯棉', response.data[0]['fabric']) + + def test_ordering(self): + """测试排序""" + order1 = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='布料1', + width='150cm' + ) + order2 = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='布料2', + width='160cm' + ) + + # 按 ID 升序 + response = self.client.get('/api/v1/printing-orders/?ordering=id') + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data + self.assertEqual(results[0]['id'], order1.id) + self.assertEqual(results[1]['id'], order2.id) + + # 按 ID 降序 + response = self.client.get('/api/v1/printing-orders/?ordering=-id') + results = response.data + self.assertEqual(results[0]['id'], order2.id) + self.assertEqual(results[1]['id'], order1.id) + + def test_human_id_generation(self): + """测试 human_id 自动生成""" + from datetime import date + + order = printing_models.PrintingOrder.objects.create( + customer=self.customer, + fabric='测试布料', + width='150cm' + ) + + # human_id 格式: YYYYMMDD000001 + self.assertIsNotNone(order.human_id) + self.assertTrue(len(order.human_id) >= 14) + today = date.today().strftime('%Y%m%d') + self.assertTrue(order.human_id.startswith(today)) diff --git a/api_v1/views/stateflow/business_object.py b/api_v1/views/stateflow/business_object.py index f3e9cc5..15b635a 100644 --- a/api_v1/views/stateflow/business_object.py +++ b/api_v1/views/stateflow/business_object.py @@ -139,7 +139,7 @@ class BusinessObjectViewSet(viewsets.ModelViewSet): def reset(self, request, pk=None): """重置进度""" business_object = self.get_object() - services.reset_order_progress(business_object) + services.reset_business_object_progress(business_object) return Response({ 'success': True, diff --git a/api_v1/views/stock_change_views/snapshot.py b/api_v1/views/stock_change_views/snapshot.py new file mode 100644 index 0000000..9432df3 --- /dev/null +++ b/api_v1/views/stock_change_views/snapshot.py @@ -0,0 +1,98 @@ +from rest_framework.generics import ListAPIView +from rest_framework.pagination import LimitOffsetPagination +from rest_framework import filters, serializers +from django_filters.rest_framework import DjangoFilterBackend +from django_filters import rest_framework as django_filters +from stock import models + + +class StockSnapshotSerializer(serializers.ModelSerializer): + """库存快照序列化器""" + product_name = serializers.CharField(source='product.name', read_only=True) + product_code = serializers.CharField(source='product.code', read_only=True) + warehouse_name = serializers.CharField(source='warehouse.name', read_only=True) + unit_display = serializers.CharField(source='get_unit_display', read_only=True) + stock_change_record_type = serializers.CharField( + source='stock_change_record.get_type_display', + read_only=True + ) + stock_change_record_source = serializers.CharField( + source='stock_change_record.get_source_type_display', + read_only=True + ) + + class Meta: + model = models.StockSnapshot + fields = [ + 'id', 'product', 'product_name', 'product_code', + 'warehouse', 'warehouse_name', + 'delta', 'quantity_before', 'quantity_after', + 'stock_change_record', 'stock_change_record_type', 'stock_change_record_source', + 'unit', 'unit_display', 'num_of_rolls', + 'offset_to', 'offset_at', 'cancelled', 'cancelled_at', 'offset_id', + 'created_at', 'updated_at' + ] + + +class StockSnapshotFilterSet(django_filters.FilterSet): + """库存快照过滤器""" + product_name = django_filters.CharFilter(field_name='product__name', lookup_expr='icontains') + product_code = django_filters.CharFilter(field_name='product__code', lookup_expr='icontains') + warehouse_name = django_filters.CharFilter(field_name='warehouse__name', lookup_expr='icontains') + cancelled = django_filters.BooleanFilter() + has_offset = django_filters.BooleanFilter(method='filter_has_offset') + date_from = django_filters.DateFilter(field_name='created_at', lookup_expr='gte') + date_to = django_filters.DateFilter(field_name='created_at', lookup_expr='lte') + + def filter_has_offset(self, queryset, name, value): + """过滤是否有冲抵记录""" + if value: + return queryset.exclude(offset_to__isnull=True) + else: + return queryset.filter(offset_to__isnull=True) + + class Meta: + model = models.StockSnapshot + fields = [ + 'product', 'warehouse', 'stock_change_record', + 'unit', 'cancelled' + ] + + +class StockSnapshotListView(ListAPIView): + """ + 库存快照列表视图(只读) + + 提供库存变动快照的查询功能 + + 查询参数: + - product: 产品ID + - product_name: 产品名称(模糊查询) + - product_code: 产品编码(模糊查询) + - warehouse: 仓库ID + - warehouse_name: 仓库名称(模糊查询) + - stock_change_record: 关联的库存变动记录ID + - unit: 单位 + - cancelled: 是否已取消(true/false) + - has_offset: 是否有冲抵记录(true/false) + - date_from: 开始日期(格式:YYYY-MM-DD) + - date_to: 结束日期(格式:YYYY-MM-DD) + - search: 全文搜索(产品名称、产品编码、仓库名称) + - ordering: 排序字段 + """ + serializer_class = StockSnapshotSerializer + pagination_class = LimitOffsetPagination + filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] + filterset_class = StockSnapshotFilterSet + search_fields = ['product__name', 'product__code', 'warehouse__name'] + ordering_fields = [ + 'id', 'delta', 'quantity_before', 'quantity_after', + 'created_at', 'updated_at', 'offset_at', 'cancelled_at' + ] + ordering = ['-created_at'] + + def get_queryset(self): + """优化查询,预加载关联数据""" + return models.StockSnapshot.objects.select_related( + 'product', 'warehouse', 'stock_change_record' + ).all() diff --git a/basic_info/admin.py b/basic_info/admin.py index 7c2bfbc..9e9b3f4 100644 --- a/basic_info/admin.py +++ b/basic_info/admin.py @@ -8,7 +8,7 @@ class AdminBase(admin.ModelAdmin): def get_readonly_fields(self, request, obj = ...): readonly_fields = list(super().get_readonly_fields(request, obj)) - if request.user.is_superuser: + if request.user.is_superuser and isinstance(obj, models.Employee) is False: readonly_fields.append('merchant') return readonly_fields diff --git a/flower/settings.py b/flower/settings.py index 7bfc927..a1aca68 100644 --- a/flower/settings.py +++ b/flower/settings.py @@ -106,6 +106,7 @@ INSTALLED_APPS = [ 'stock', 'api_v1', 'api_man', + 'printing', 'stateflow', 'sse', ] diff --git a/printing/__init__.py b/printing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/printing/admin.py b/printing/admin.py new file mode 100644 index 0000000..0937bdd --- /dev/null +++ b/printing/admin.py @@ -0,0 +1,26 @@ +from django.contrib import admin +from . import models + + +class PrintingJobInline(admin.StackedInline): + model = models.PrintingJob + extra = 1 + + +@admin.register(models.PrintingOrder) +class PrintingOrderAdmin(admin.ModelAdmin): + list_display = ( + 'oid', + 'customer_name', + 'created_at', + 'outgoing_date', + ) + inlines = [PrintingJobInline] + + @admin.display(description='客户名称') + def customer_name(self, obj: models.PrintingOrder): + return obj.customer.name + + @admin.display(description='订单编号(虚拟)') + def oid(self, obj: models.PrintingOrder): + return obj.human_id diff --git a/printing/apps.py b/printing/apps.py new file mode 100644 index 0000000..b934494 --- /dev/null +++ b/printing/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class PrintingConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'printing' + verbose_name = '印染管理' diff --git a/printing/migrations/0001_initial.py b/printing/migrations/0001_initial.py new file mode 100644 index 0000000..0927a0c --- /dev/null +++ b/printing/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 5.2.7 on 2025-11-12 07:03 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('basic_info', '0009_product_minimum_quantity'), + ] + + operations = [ + migrations.CreateModel( + name='PrintingOrder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('fabric', models.CharField(max_length=100, verbose_name='面料')), + ('width', models.CharField(max_length=100, verbose_name='幅宽')), + ('is_urgent', models.BooleanField(default=False, verbose_name='紧急')), + ('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')), + ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='地址')), + ('fabric_source', models.CharField(max_length=100, null=True, verbose_name='布料来源')), + ('is_fabric_received', models.BooleanField(default=False, verbose_name='布料是否已收')), + ('craft', models.CharField(max_length=100, null=True, verbose_name='工艺')), + ('description', models.TextField(blank=True, null=True, verbose_name='订单描述')), + ('outgoing_date', models.DateField(blank=True, null=True, verbose_name='出货日期')), + ('curve', models.CharField(blank=True, max_length=100, null=True, verbose_name='曲线')), + ('new_curve', models.CharField(blank=True, max_length=100, null=True, verbose_name='新加曲线')), + ('position', models.TextField(blank=True, null=True, verbose_name='位置')), + ('printing_warn', models.TextField(blank=True, null=True, verbose_name='打印注意事项')), + ('rolling_warn', models.TextField(blank=True, null=True, verbose_name='滚筒注意事项')), + ('production_warn', models.TextField(blank=True, null=True, verbose_name='生产注意事项')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='printing_orders', to='basic_info.customer', verbose_name='客户')), + ], + options={ + 'verbose_name': '印染订单', + 'verbose_name_plural': '印染订单', + }, + ), + migrations.CreateModel( + name='PrintingJob', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('quantity', models.PositiveIntegerField(verbose_name='数量')), + ('unit', models.CharField(max_length=50, verbose_name='单位')), + ('size', models.CharField(max_length=100, verbose_name='一段尺寸')), + ('pieces', models.PositiveIntegerField(verbose_name='件数')), + ('description', models.TextField(blank=True, verbose_name='备注')), + ('status', models.CharField(max_length=50, verbose_name='状态')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='printing_jobs', to='basic_info.product', verbose_name='产品')), + ('printing_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='printing_jobs', to='printing.printingorder', verbose_name='主订单')), + ], + options={ + 'verbose_name': '印染款式明细', + 'verbose_name_plural': '印染款式明细', + }, + ), + ] diff --git a/printing/migrations/0002_alter_printingorder_craft_and_more.py b/printing/migrations/0002_alter_printingorder_craft_and_more.py new file mode 100644 index 0000000..302b66b --- /dev/null +++ b/printing/migrations/0002_alter_printingorder_craft_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.7 on 2025-11-12 07:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('printing', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='printingorder', + name='craft', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='工艺'), + ), + migrations.AlterField( + model_name='printingorder', + name='fabric_source', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='布料来源'), + ), + ] diff --git a/printing/migrations/0003_remove_printingjob_status.py b/printing/migrations/0003_remove_printingjob_status.py new file mode 100644 index 0000000..938fae6 --- /dev/null +++ b/printing/migrations/0003_remove_printingjob_status.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.7 on 2025-11-12 08:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('printing', '0002_alter_printingorder_craft_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='printingjob', + name='status', + ), + ] diff --git a/printing/migrations/0004_printingorder_is_invalid.py b/printing/migrations/0004_printingorder_is_invalid.py new file mode 100644 index 0000000..f424bbc --- /dev/null +++ b/printing/migrations/0004_printingorder_is_invalid.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-11-12 08:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('printing', '0003_remove_printingjob_status'), + ] + + operations = [ + migrations.AddField( + model_name='printingorder', + name='is_invalid', + field=models.BooleanField(default=False, verbose_name='是否作废'), + ), + ] diff --git a/printing/migrations/0005_alter_printingorder_options.py b/printing/migrations/0005_alter_printingorder_options.py new file mode 100644 index 0000000..487d8af --- /dev/null +++ b/printing/migrations/0005_alter_printingorder_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.7 on 2025-11-12 08:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('printing', '0004_printingorder_is_invalid'), + ] + + operations = [ + migrations.AlterModelOptions( + name='printingorder', + options={'permissions': [('can_invalidate_printingorder', '可以作废印染订单'), ('can_activate_printingorder', '可以恢复印染订单')], 'verbose_name': '印染订单', 'verbose_name_plural': '印染订单'}, + ), + ] diff --git a/printing/migrations/__init__.py b/printing/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/printing/models.py b/printing/models.py new file mode 100644 index 0000000..671a494 --- /dev/null +++ b/printing/models.py @@ -0,0 +1,73 @@ +from django.db import models +from flower.common import ModelBase +from basic_info import models as basic_models + + +class PrintingOrder(ModelBase): + """PrintingOrder model representing a printing order.""" + customer = models.ForeignKey( + basic_models.Customer, + on_delete=models.PROTECT, + related_name='printing_orders', + verbose_name='客户', + ) + fabric = models.CharField(max_length=100, verbose_name='面料') + width = models.CharField(max_length=100, verbose_name='幅宽') + is_urgent = models.BooleanField(default=False, verbose_name='紧急') + area = models.CharField(max_length=100, null=True, blank=True, verbose_name='地区') + address = models.CharField(max_length=200, null=True, blank=True, verbose_name='地址') + fabric_source = models.CharField(max_length=100, null=True, blank=True, verbose_name='布料来源') + is_fabric_received = models.BooleanField(default=False, verbose_name='布料是否已收') + craft = models.CharField(max_length=100, null=True, blank=True, verbose_name='工艺') + description = models.TextField(blank=True, null=True, verbose_name='订单描述') + outgoing_date = models.DateField(null=True, blank=True, verbose_name='出货日期') + curve = models.CharField(max_length=100, blank=True, null=True, verbose_name='曲线') + new_curve = models.CharField(max_length=100, blank=True, null=True, verbose_name='新加曲线') + position = models.TextField(blank=True, null=True, verbose_name='位置') + printing_warn = models.TextField(blank=True, null=True, verbose_name='打印注意事项') + rolling_warn = models.TextField(blank=True, null=True, verbose_name='滚筒注意事项') + production_warn = models.TextField(blank=True, null=True, verbose_name='生产注意事项') + is_invalid = models.BooleanField(default=False, verbose_name='是否作废') + + def __str__(self): + return self.human_id + + class Meta: + verbose_name = '印染订单' + verbose_name_plural = '印染订单' + permissions = [ + ('can_invalidate_printingorder', '可以作废印染订单'), + ('can_activate_printingorder', '可以恢复印染订单'), + ] + + @property + def human_id(self) -> str: + return f"{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" + + +class PrintingJob(ModelBase): + """PrintingJob model representing a printing job associated with an order.""" + printing_order = models.ForeignKey( + PrintingOrder, + on_delete=models.CASCADE, + related_name='printing_jobs', + verbose_name='主订单', + ) + product = models.ForeignKey( + basic_models.Product, + on_delete=models.PROTECT, + related_name='printing_jobs', + verbose_name='产品', + ) + quantity = models.PositiveIntegerField(verbose_name='数量') + unit = models.CharField(max_length=50, verbose_name='单位') + size = models.CharField(max_length=100, verbose_name='一段尺寸') + pieces = models.PositiveIntegerField(verbose_name='件数') + description = models.TextField(blank=True, verbose_name='备注') + + def __str__(self): + return self.printing_order.human_id + + class Meta: + verbose_name = '印染款式明细' + verbose_name_plural = '印染款式明细' diff --git a/printing/serializers.py b/printing/serializers.py new file mode 100644 index 0000000..546707f --- /dev/null +++ b/printing/serializers.py @@ -0,0 +1,59 @@ +""" +Printing 序列化器 +""" +from rest_framework import serializers +from printing import models + + +class PrintingOrderListSerializer(serializers.ModelSerializer): + """印染订单列表序列化器""" + customer_name = serializers.CharField(source='customer.name', read_only=True) + customer_phone = serializers.CharField(source='customer.phone', read_only=True) + + class Meta: + model = models.PrintingOrder + fields = [ + 'id', 'human_id', 'customer', 'customer_name', 'customer_phone', + 'fabric', 'width', 'is_urgent', 'area', 'address', + 'is_fabric_received', 'outgoing_date', 'is_invalid', + 'created_at', 'updated_at' + ] + read_only_fields = ['id', 'human_id', 'created_at', 'updated_at'] + + +class PrintingOrderDetailSerializer(serializers.ModelSerializer): + """印染订单详情序列化器""" + customer_name = serializers.CharField(source='customer.name', read_only=True) + customer_phone = serializers.CharField(source='customer.phone', read_only=True) + customer_address = serializers.CharField(source='customer.address', read_only=True) + + class Meta: + model = models.PrintingOrder + fields = [ + 'id', 'human_id', 'customer', 'customer_name', 'customer_phone', 'customer_address', + 'fabric', 'width', 'is_urgent', 'area', 'address', 'fabric_source', + 'is_fabric_received', 'craft', 'description', 'outgoing_date', + 'curve', 'new_curve', 'position', + 'printing_warn', 'rolling_warn', 'production_warn', + 'is_invalid', 'created_at', 'updated_at' + ] + read_only_fields = ['id', 'human_id', 'created_at', 'updated_at'] + + +class PrintingOrderCreateUpdateSerializer(serializers.ModelSerializer): + """印染订单创建/更新序列化器""" + + class Meta: + model = models.PrintingOrder + fields = [ + 'customer', 'fabric', 'width', 'is_urgent', 'area', 'address', + 'fabric_source', 'is_fabric_received', 'craft', 'description', + 'outgoing_date', 'curve', 'new_curve', 'position', + 'printing_warn', 'rolling_warn', 'production_warn', 'is_invalid' + ] + + def validate_customer(self, value): + """验证客户是否存在""" + if not value: + raise serializers.ValidationError("客户不能为空") + return value diff --git a/printing/services.py b/printing/services.py new file mode 100644 index 0000000..e69de29 diff --git a/printing/tests.py b/printing/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/printing/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/printing/views.py b/printing/views.py new file mode 100644 index 0000000..bd7f923 --- /dev/null +++ b/printing/views.py @@ -0,0 +1,227 @@ +""" +Printing API ViewSet +""" +from rest_framework import viewsets, filters, status +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.permissions import BasePermission +from rest_framework.pagination import LimitOffsetPagination +from rest_framework.permissions import DjangoModelPermissions +from django_filters.rest_framework import DjangoFilterBackend +from django_filters import rest_framework as django_filters +from printing import models +from basic_info.models import MerchantTypeEnum + +from printing.serializers import ( + PrintingOrderListSerializer, + PrintingOrderDetailSerializer, + PrintingOrderCreateUpdateSerializer, +) + + +class IsPrintingFactory(BasePermission): + """自定义权限类,允许印染工厂用户访问""" + + message = '您没有访问印染订单的权限' + + def has_permission(self, request, view): + if not request.user.is_authenticated: + return False + if hasattr(request.user, 'employee'): + return request.user.employee.merchant.type == MerchantTypeEnum.FACTORY + return False + + +class HasInvalidatePrintingOrderPermission(BasePermission): + """自定义权限类,检查用户是否有作废印染订单的权限""" + + message = '您没有权限作废订单' + + def has_permission(self, request, view): + if not request.user.is_authenticated: + return False + return request.user.has_perm('printing.can_invalidate_printingorder') + + +class HasActivatePrintingOrderPermission(BasePermission): + """自定义权限类,检查用户是否有恢复印染订单的权限""" + + message = '您没有权限恢复订单' + + def has_permission(self, request, view): + if not request.user.is_authenticated: + return False + return request.user.has_perm('printing.can_activate_printingorder') + + +class PrintingOrderFilterSet(django_filters.FilterSet): + """印染订单过滤器""" + customer_name = django_filters.CharFilter(field_name='customer__name', lookup_expr='icontains') + customer_phone = django_filters.CharFilter(field_name='customer__phone', lookup_expr='icontains') + fabric = django_filters.CharFilter(lookup_expr='icontains') + is_urgent = django_filters.BooleanFilter() + is_fabric_received = django_filters.BooleanFilter() + is_invalid = django_filters.BooleanFilter() + area = django_filters.CharFilter(lookup_expr='icontains') + outgoing_date_from = django_filters.DateFilter(field_name='outgoing_date', lookup_expr='gte') + outgoing_date_to = django_filters.DateFilter(field_name='outgoing_date', lookup_expr='lte') + created_date_from = django_filters.DateFilter(field_name='created_at', lookup_expr='gte') + created_date_to = django_filters.DateFilter(field_name='created_at', lookup_expr='lte') + + class Meta: + model = models.PrintingOrder + fields = ['customer', 'is_urgent', 'is_fabric_received', 'is_invalid'] + + +class PrintingOrderViewSet(viewsets.ModelViewSet): + """ + 印染订单 ViewSet + + 提供印染订单的增改查功能(不支持删除) + + list: 获取印染订单列表 + retrieve: 获取印染订单详情 + create: 创建印染订单 + update: 更新印染订单 + partial_update: 部分更新印染订单 + + 查询参数: + - customer: 客户ID + - customer_name: 客户名称(模糊查询) + - customer_phone: 客户电话(模糊查询) + - fabric: 面料(模糊查询) + - is_urgent: 是否紧急(true/false) + - is_fabric_received: 布料是否已收(true/false) + - is_invalid: 是否作废(true/false) + - area: 地区(模糊查询) + - outgoing_date_from: 出货日期起始 + - outgoing_date_to: 出货日期结束 + - created_date_from: 创建日期起始 + - created_date_to: 创建日期结束 + - search: 全文搜索(客户名称、面料、地区、工艺) + - ordering: 排序字段 + """ + queryset = models.PrintingOrder.objects.all() + permission_classes = [DjangoModelPermissions] + pagination_class = LimitOffsetPagination + filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] + filterset_class = PrintingOrderFilterSet + search_fields = ['customer__name', 'fabric', 'area', 'craft', 'description'] + ordering_fields = [ + 'id', 'created_at', 'updated_at', 'outgoing_date', + 'is_urgent', 'is_fabric_received', 'is_invalid' + ] + ordering = ['-created_at'] + + def get_permissions(self): + permissions = super().get_permissions() + [IsPrintingFactory()] + if self.action == 'invalidate': + permissions.append(HasInvalidatePrintingOrderPermission()) + elif self.action == 'activate': + permissions.append(HasActivatePrintingOrderPermission()) + return permissions + + def get_serializer_class(self): + """根据动作选择序列化器""" + if self.action == 'list': + return PrintingOrderListSerializer + elif self.action in ['create', 'update', 'partial_update']: + return PrintingOrderCreateUpdateSerializer + else: # retrieve + return PrintingOrderDetailSerializer + + def get_queryset(self): + """优化查询""" + queryset = super().get_queryset() + if self.action in ['list', 'retrieve']: + queryset = queryset.select_related('customer') + return queryset + + def destroy(self, request, *args, **kwargs): + """禁用删除操作""" + return Response( + {'detail': '印染订单不支持删除操作,请使用作废功能'}, + status=status.HTTP_405_METHOD_NOT_ALLOWED + ) + + @action(detail=True, methods=['post']) + def invalidate(self, request, pk=None): + """ + 作废订单 + + 需要权限: printing.can_invalidate_printingorder + """ + # 检查权限 + if not request.user.has_perm('printing.can_invalidate_printingorder'): + return Response( + {'detail': '您没有权限作废订单'}, + status=status.HTTP_403_FORBIDDEN + ) + + printing_order = self.get_object() + + if printing_order.is_invalid: + return Response( + {'detail': '该订单已经作废'}, + status=status.HTTP_400_BAD_REQUEST + ) + + printing_order.is_invalid = True + printing_order.save() + + serializer = PrintingOrderDetailSerializer(printing_order) + return Response({ + 'detail': '订单已作废', + 'data': serializer.data + }) + + @action(detail=True, methods=['post']) + def activate(self, request, pk=None): + """ + 恢复订单 + + 需要权限: printing.can_activate_printingorder + """ + # 检查权限 + if not request.user.has_perm('printing.can_activate_printingorder'): + return Response( + {'detail': '您没有权限恢复订单'}, + status=status.HTTP_403_FORBIDDEN + ) + + printing_order = self.get_object() + + if not printing_order.is_invalid: + return Response( + {'detail': '该订单未作废,无需恢复'}, + status=status.HTTP_400_BAD_REQUEST + ) + + printing_order.is_invalid = False + printing_order.save() + + serializer = PrintingOrderDetailSerializer(printing_order) + return Response({ + 'detail': '订单已恢复', + 'data': serializer.data + }) + + @action(detail=True, methods=['post']) + def mark_fabric_received(self, request, pk=None): + """标记布料已收""" + printing_order = self.get_object() + + if printing_order.is_fabric_received: + return Response( + {'detail': '布料已经标记为已收'}, + status=status.HTTP_400_BAD_REQUEST + ) + + printing_order.is_fabric_received = True + printing_order.save() + + serializer = PrintingOrderDetailSerializer(printing_order) + return Response({ + 'detail': '已标记布料已收', + 'data': serializer.data + }) diff --git a/stateflow/apps.py b/stateflow/apps.py index 559c3df..06b06d4 100644 --- a/stateflow/apps.py +++ b/stateflow/apps.py @@ -4,4 +4,4 @@ from django.apps import AppConfig class StateflowConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'stateflow' - verbose_name = '印染流程' + verbose_name = '任务流程' diff --git a/stateflow/migrations/0013_alter_stateflowrecord_unique_together.py b/stateflow/migrations/0013_alter_stateflowrecord_unique_together.py new file mode 100644 index 0000000..d98e65d --- /dev/null +++ b/stateflow/migrations/0013_alter_stateflowrecord_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.7 on 2025-11-12 04:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('stateflow', '0012_alter_businessobject_content_type_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='stateflowrecord', + unique_together=set(), + ), + ] diff --git a/stateflow/migrations/0014_alter_process_options.py b/stateflow/migrations/0014_alter_process_options.py new file mode 100644 index 0000000..530cab8 --- /dev/null +++ b/stateflow/migrations/0014_alter_process_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.7 on 2025-11-12 06:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('stateflow', '0013_alter_stateflowrecord_unique_together'), + ] + + operations = [ + migrations.AlterModelOptions( + name='process', + options={'verbose_name': '流程编排', 'verbose_name_plural': '流程编排'}, + ), + ] diff --git a/stateflow/models.py b/stateflow/models.py index b2e3738..5d02dce 100644 --- a/stateflow/models.py +++ b/stateflow/models.py @@ -72,8 +72,8 @@ class Process(ModelBase): return [item.state for item in process_nodes] class Meta: - verbose_name = '印染流程' - verbose_name_plural = '印染流程' + verbose_name = '流程编排' + verbose_name_plural = '流程编排' class ProcessNode(ModelBase): @@ -155,5 +155,5 @@ class StateFlowRecord(ModelBase): db_table = 'state_flow_record' verbose_name = '状态流转记录' verbose_name_plural = '状态流转记录' - unique_together = [['business_object', 'state']] + # 移除 unique_together 约束,允许同一状态多次记录(支持撤销后重新执行) ordering = ['completed_at'] diff --git a/stateflow/services.py b/stateflow/services.py index eb6e796..0c0f576 100644 --- a/stateflow/services.py +++ b/stateflow/services.py @@ -83,14 +83,18 @@ def get_progress_percentage(business_object: 'models.BusinessObject') -> float: return (completed_count / total_nodes) * 100 -def advance_to_next_state(business_object: 'models.BusinessObject', user: 'User') -> Tuple[bool, str]: +def advance_to_next_state(business_object: 'models.BusinessObject', user) -> Tuple[bool, str]: """ 将订单推进到下一个状态 返回: (是否成功, 消息) """ + can_advance, reason = can_advance_to_next_state(business_object) + if not can_advance: + return False, reason + with transaction.atomic(): - # 获取当前状态 + # 获取当前状态(返回第一个未完成的节点) current_state = get_business_object_current_state(business_object) # 如果当前状态为 None,说明是初始状态(未开始) @@ -108,10 +112,7 @@ def advance_to_next_state(business_object: 'models.BusinessObject', user: 'User' ) return True, f"已完成状态: {first_node.state.name}" - # 检查当前状态是否已完成(且未撤销) - if business_object.state_logs.filter(state=current_state, is_cancelled=False).exists(): - return False, f"当前状态 '{current_state.name}' 已完成" - + # current_state 不为 None 时,它一定是未完成的节点 # 标记当前状态为已完成 models.StateFlowRecord.objects.create( business_object=business_object, @@ -128,12 +129,12 @@ def advance_to_next_state(business_object: 'models.BusinessObject', user: 'User' return True, f"已完成状态: {current_state.name}" -def reset_order_progress(business_object: 'models.BusinessObject') -> None: +def reset_business_object_progress(business_object: 'models.BusinessObject') -> None: """ - 重置订单进度 + 重置业务对象进度 注意:不删除历史记录,而是标记所有记录为已撤销,并记录撤销时间 - 这样可以保留完整的操作历史 + 这样可以保留完整的操作历史,支持审计需求 """ from django.utils import timezone @@ -143,6 +144,10 @@ def reset_order_progress(business_object: 'models.BusinessObject') -> None: ) +# 向后兼容的别名 +reset_order_progress = reset_business_object_progress + + def get_current_state_parameters(business_object: 'models.BusinessObject') -> List['models.StateParameter']: """获取订单当前状态的参数列表""" current_state = get_business_object_current_state(business_object) @@ -188,10 +193,10 @@ def can_advance_to_next_state(business_object: 'models.BusinessObject') -> Tuple current_state = get_business_object_current_state(business_object) - # 如果当前状态为 None + # 如果当前状态为 None,有两种情况: + # 1. 没有完成记录 - 未开始 + # 2. 所有节点已完成 - 已完成 if current_state is None: - # 检查是否所有状态都已完成 - total_nodes = business_object.process.process_nodes.count() completed_count = business_object.state_logs.filter(is_cancelled=False).count() if completed_count == 0: @@ -201,10 +206,7 @@ def can_advance_to_next_state(business_object: 'models.BusinessObject') -> Tuple # 所有状态都已完成 return False, "流程已完成,无法继续推进" - # 检查当前状态是否已完成 - if business_object.state_logs.filter(state=current_state, is_cancelled=False).exists(): - return False, f"当前状态 '{current_state.name}' 已完成,无法重复完成" - + # current_state 不为 None 时,它一定是第一个未完成的节点,可以推进 return True, f"可以推进到: {current_state.name}" diff --git a/stateflow/tests.py b/stateflow/tests.py index 04b0107..a079e1d 100644 --- a/stateflow/tests.py +++ b/stateflow/tests.py @@ -121,7 +121,7 @@ class OrderStateFlowTestCase(TestCase): self.assertEqual(self.business_object.state_logs.count(), 2) # 重置进度 - services.reset_order_progress(self.business_object) + services.reset_business_object_progress(self.business_object) # 日志记录应该还在(不删除) self.assertEqual(self.business_object.state_logs.count(), 2) @@ -147,7 +147,7 @@ class OrderStateFlowTestCase(TestCase): services.advance_to_next_state(self.business_object, self.user) # 重置进度 - services.reset_order_progress(self.business_object) + services.reset_business_object_progress(self.business_object) timeline = self.business_object.get_timeline()