"""
PrintingJob API 测试
"""
from datetime import datetime
from decimal import Decimal

from django.test import TestCase
from django.conf import settings
from django.utils import timezone
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 django.contrib.contenttypes.models import ContentType
from basic_info import models as basic_models
from business import models as business_models
from printing import models as printing_models
from shipment import models as shipment_models
from stateflow import models as stateflow_models

User = get_user_model()


class PrintingJobAPITestCase(TestCase):
    """测试 PrintingJob 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'
        )
        # PrintingJobViewSet 默认做 merchant 隔离；本测试集不关注该隔离逻辑，
        # 设为 superuser 以避免因测试数据未设置 merchant 导致的 404/列表为空。
        self.user.is_superuser = True
        self.user.save(update_fields=['is_superuser'])
        
        # 创建员工并关联商户
        self.employee = basic_models.Employee.objects.create(
            sys_user=self.user,
            merchant=self.merchant,
            name='测试员工',
            mobile='13800138000',
            status=basic_models.EmployeeStatusEnum.ACTIVE
        )
        
        # 创建客户
        self.customer = basic_models.Customer.objects.create(
            merchant=self.merchant,
            name='测试客户',
            mobile='13900139000',
            area='测试地区'
        )
        
        # 创建流程
        self.state1 = stateflow_models.State.objects.create(name='待印染')
        self.state2 = stateflow_models.State.objects.create(name='印染中')
        self.state3 = stateflow_models.State.objects.create(name='已完成')
        
        self.process = stateflow_models.Process.objects.create(name='印染流程')
        self.process.replace_nodes([self.state1, self.state2, self.state3])
        
        # 设置默认流程
        settings.PRINTING_DEFAULT_PROCESS_ID = self.process.id
        
        # 创建印染订单
        self.printing_order = printing_models.PrintingOrder.objects.create(
            customer=self.customer,
            fabric='测试布料',
            width='150cm',
            curve='测试曲线',
            new_curve='测试新曲线',
            process=self.process,
        )
        
        # 创建产品类别
        self.category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='测试类别'
        )
        
        # 创建产品
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='测试产品',
            human_id='TEST001',
            unit=basic_models.ProductUnitEnum.METER
        )
        
        # 认证用户
        self.client.force_authenticate(user=self.user)
        
        # 给用户添加基础权限
        view_perm = Permission.objects.get(codename='view_printingjob')
        add_perm = Permission.objects.get(codename='add_printingjob')
        change_perm = Permission.objects.get(codename='change_printingjob')
        self.user.user_permissions.add(view_perm, add_perm, change_perm)
    
    def test_create_printing_job(self):
        """测试创建印染款式明细"""
        self.printing_order.external_order_id = 'KD20410611'
        self.printing_order.save(update_fields=['external_order_id'])

        data = {
            'printing_order': self.printing_order.id,
            'product': self.product.id,
            'quantity': 100,
            'unit': '米',
            'size': '50*60',
            'pieces': 10,
            'sub_id': 7,
            'description': '测试备注'
        }
        
        response = self.client.post('/api/v1/printing-jobs/', data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['external_order_id'], 'KD20410611')
        self.assertEqual(response.data['sub_id'], 7)
        
        # 新增字段：批量推进记录（稳定输出 key，默认空数组）
        self.assertIn('batch_advance_records', response.data)
        self.assertIsInstance(response.data['batch_advance_records'], list)
        self.assertEqual(len(response.data['batch_advance_records']), 0)
        
        # 验证创建成功
        job = printing_models.PrintingJob.objects.filter(
            printing_order=self.printing_order,
            product=self.product
        ).first()
        self.assertIsNotNone(job)
        self.assertEqual(job.quantity, 100)
        self.assertEqual(job.unit, '米')
        self.assertEqual(job.pieces, 10)
        self.assertEqual(job.sub_id, 7)
        self.assertFalse(job.is_production_completed)

    def test_create_printing_job_ignores_is_production_completed(self):
        """测试创建接口忽略 is_production_completed"""
        data = {
            'printing_order': self.printing_order.id,
            'product': self.product.id,
            'quantity': 100,
            'unit': '米',
            'is_production_completed': True,
        }

        response = self.client.post('/api/v1/printing-jobs/', data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        job = printing_models.PrintingJob.objects.get(id=response.data['id'])
        self.assertFalse(job.is_production_completed)
    
    def test_list_printing_jobs(self):
        """测试获取款式明细列表"""
        # 创建测试数据
        job1 = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10,
            sub_id=101
        )
        job2 = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=200,
            unit='米',
            size='60*70',
            pieces=20,
            sub_id=202
        )
        
        response = self.client.get('/api/v1/printing-jobs/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 2)
        sub_ids_by_id = {item['id']: item['sub_id'] for item in response.data['results']}
        self.assertEqual(sub_ids_by_id[job1.id], 101)
        self.assertEqual(sub_ids_by_id[job2.id], 202)
        
        # 新增字段：批量推进记录（稳定输出 key）
        for item in response.data['results']:
            self.assertIn('customer_name', item)
            self.assertEqual(item['customer_name'], self.customer.name)
            self.assertEqual(item['curve'], '测试曲线')
            self.assertEqual(item['new_curve'], '测试新曲线')
            self.assertIn('batch_advance_records', item)
            self.assertIsInstance(item['batch_advance_records'], list)
            self.assertEqual(len(item['batch_advance_records']), 0)

    def test_filter_printing_jobs_by_customer_name(self):
        """测试按客户名称筛选款式明细列表"""
        other_customer = basic_models.Customer.objects.create(
            merchant=self.merchant,
            name='其他客户',
            mobile='13900139001',
            area='测试地区'
        )
        other_order = printing_models.PrintingOrder.objects.create(
            customer=other_customer,
            fabric='其他布料',
            width='150cm',
            process=self.process,
        )
        matched_job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
        )
        other_job = printing_models.PrintingJob.objects.create(
            printing_order=other_order,
            product=self.product,
            quantity=200,
            unit='米',
        )

        response = self.client.get('/api/v1/printing-jobs/?customer_name=测试客')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        ids = {item['id'] for item in response.data['results']}
        self.assertIn(matched_job.id, ids)
        self.assertNotIn(other_job.id, ids)

    def test_filter_printing_jobs_by_created_at_date_range_includes_whole_to_day(self):
        """测试 created_at_to 传日期时包含整天"""
        tz = timezone.get_current_timezone()
        in_range = timezone.make_aware(datetime(2026, 7, 3, 23, 30, 0), tz)
        out_range = timezone.make_aware(datetime(2026, 7, 4, 0, 0, 0), tz)

        matched_job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
        )
        out_job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=200,
            unit='米',
        )
        printing_models.PrintingJob.objects.filter(id=matched_job.id).update(created_at=in_range)
        printing_models.PrintingJob.objects.filter(id=out_job.id).update(created_at=out_range)

        response = self.client.get(
            '/api/v1/printing-jobs/?created_at_from=2026-07-03&created_at_to=2026-07-03'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        ids = {item['id'] for item in response.data['results']}
        self.assertIn(matched_job.id, ids)
        self.assertNotIn(out_job.id, ids)

    def test_list_printing_jobs_includes_is_sales_order_bound(self):
        """测试列表返回是否已绑定销售单字段，并支持关闭该字段"""
        unbound_job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
        )
        bound_job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=200,
            unit='米',
        )
        warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='测试仓库',
            mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
        )
        sales_order = business_models.SalesOrder.objects.create(
            merchant=self.merchant,
            customer=self.customer,
            sales_date='2026-06-10',
            operator=self.employee,
            warehouse=warehouse,
            status=business_models.SalesOrderStatusEnum.APPROVED,
        )
        business_models.SalesOrderItem.objects.create(
            sales_order=sales_order,
            product=self.product,
            printing_job=bound_job,
            price='10.00',
            quantity='1.00',
            unit='米',
            empty_diff_percent='0.00',
        )
        cancelled_order = business_models.SalesOrder.objects.create(
            merchant=self.merchant,
            customer=self.customer,
            sales_date='2026-06-10',
            operator=self.employee,
            warehouse=warehouse,
            status=business_models.SalesOrderStatusEnum.CANCELLED,
        )
        cancelled_only_job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=300,
            unit='米',
        )
        business_models.SalesOrderItem.objects.create(
            sales_order=cancelled_order,
            product=self.product,
            printing_job=cancelled_only_job,
            price='10.00',
            quantity='1.00',
            unit='米',
            empty_diff_percent='0.00',
        )

        response = self.client.get('/api/v1/printing-jobs/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        bound_by_id = {item['id']: item['is_sales_order_bound'] for item in response.data['results']}
        billed_by_id = {item['id']: item['billed_quantity'] for item in response.data['results']}
        self.assertFalse(bound_by_id[unbound_job.id])
        self.assertTrue(bound_by_id[bound_job.id])
        self.assertFalse(bound_by_id[cancelled_only_job.id])
        self.assertEqual(billed_by_id[unbound_job.id], '0.00')
        self.assertEqual(billed_by_id[bound_job.id], '1.00')
        self.assertEqual(billed_by_id[cancelled_only_job.id], '0.00')

        response = self.client.get('/api/v1/printing-jobs/?include_sales_order_bound=false')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        for item in response.data['results']:
            self.assertNotIn('is_sales_order_bound', item)

    def test_billed_quantity_ignores_cancelled_sales_order_items(self):
        """测试开单数量忽略已作废销售单明细"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
        )
        warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='开单数量测试仓库',
            mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
        )
        approved_order = business_models.SalesOrder.objects.create(
            merchant=self.merchant,
            customer=self.customer,
            sales_date='2026-06-10',
            operator=self.employee,
            warehouse=warehouse,
            status=business_models.SalesOrderStatusEnum.APPROVED,
        )
        cancelled_order = business_models.SalesOrder.objects.create(
            merchant=self.merchant,
            customer=self.customer,
            sales_date='2026-06-10',
            operator=self.employee,
            warehouse=warehouse,
            status=business_models.SalesOrderStatusEnum.CANCELLED,
        )
        for order, quantity in [(approved_order, '7.50'), (cancelled_order, '20.00')]:
            business_models.SalesOrderItem.objects.create(
                sales_order=order,
                product=self.product,
                printing_job=job,
                price='10.00',
                quantity=quantity,
                unit='米',
                empty_diff_percent='0.00',
            )

        self.assertEqual(job.billed_quantity, Decimal('7.50'))

        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['billed_quantity'], '7.50')

    def test_mark_production_completed(self):
        """测试显式标记完成生产接口"""
        job = printing_models.PrintingJob.objects.create(
            merchant=self.merchant,
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            is_production_completed=False,
        )

        response = self.client.post(
            '/api/v1/printing-jobs/mark-production-completed/',
            {'printing_job_id': job.id},
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, {'message': 'OK'})

        job.refresh_from_db()
        self.assertTrue(job.is_production_completed)

    def test_mark_production_completed_requires_printing_job_id(self):
        """测试显式标记完成生产接口缺少 printing_job_id"""
        response = self.client.post(
            '/api/v1/printing-jobs/mark-production-completed/',
            {},
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('printing_job_id', response.data['detail'])

    def test_mark_production_completed_forbidden_for_other_merchant(self):
        """测试不能操作其他商户的 PrintingJob"""
        other_merchant = basic_models.Merchant.objects.create(
            name='其他印花厂',
            type=basic_models.MerchantTypeEnum.FACTORY
        )
        job = printing_models.PrintingJob.objects.create(
            merchant=other_merchant,
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            is_production_completed=False,
        )

        response = self.client.post(
            '/api/v1/printing-jobs/mark-production-completed/',
            {'printing_job_id': job.id},
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertIn('其他商户', response.data['detail'])

        job.refresh_from_db()
        self.assertFalse(job.is_production_completed)

    def test_mark_production_completed_requires_employee(self):
        """测试显式标记完成生产接口需要 employee"""
        no_employee_user = User.objects.create_user(
            username='noemployee',
            password='testpass123',
            email='noemployee@example.com'
        )
        no_employee_user.user_permissions.add(
            Permission.objects.get(codename='view_printingjob'),
            Permission.objects.get(codename='add_printingjob'),
            Permission.objects.get(codename='change_printingjob'),
        )

        self.client.force_authenticate(user=no_employee_user)
        try:
            response = self.client.post(
                '/api/v1/printing-jobs/mark-production-completed/',
                {'printing_job_id': 1},
                format='json'
            )
        finally:
            self.client.force_authenticate(user=self.user)

        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
    
    def test_retrieve_printing_job(self):
        """测试获取款式明细详情"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10,
            description='详情测试'
        )
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['quantity'], 100)
        self.assertEqual(response.data['unit'], '米')
        self.assertIn('product_name', response.data)
        self.assertEqual(response.data['product_name'], self.product.name)
        
        # 新增字段：批量推进记录（稳定输出 key）
        self.assertIn('batch_advance_records', response.data)
        self.assertIsInstance(response.data['batch_advance_records'], list)
        self.assertEqual(len(response.data['batch_advance_records']), 0)

    def test_retrieve_printing_job_includes_is_sales_order_bound(self):
        """测试详情返回是否已绑定销售单字段"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
        )
        warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='详情测试仓库',
            mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
        )
        sales_order = business_models.SalesOrder.objects.create(
            merchant=self.merchant,
            customer=self.customer,
            sales_date='2026-06-10',
            operator=self.employee,
            warehouse=warehouse,
            status=business_models.SalesOrderStatusEnum.APPROVED,
        )
        business_models.SalesOrderItem.objects.create(
            sales_order=sales_order,
            product=self.product,
            printing_job=job,
            price='10.00',
            quantity='1.00',
            unit='米',
            empty_diff_percent='0.00',
        )

        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertTrue(response.data['is_sales_order_bound'])
        self.assertEqual(response.data['billed_quantity'], '1.00')

        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/?include_sales_order_bound=false')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertNotIn('is_sales_order_bound', response.data)

    def test_retrieve_printing_job_ignores_cancelled_sales_order_bound(self):
        """测试详情判断销售单绑定时忽略已作废销售单"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
        )
        warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='详情作废测试仓库',
            mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
        )
        cancelled_order = business_models.SalesOrder.objects.create(
            merchant=self.merchant,
            customer=self.customer,
            sales_date='2026-06-10',
            operator=self.employee,
            warehouse=warehouse,
            status=business_models.SalesOrderStatusEnum.CANCELLED,
        )
        business_models.SalesOrderItem.objects.create(
            sales_order=cancelled_order,
            product=self.product,
            printing_job=job,
            price='10.00',
            quantity='1.00',
            unit='米',
            empty_diff_percent='0.00',
        )

        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertFalse(response.data['is_sales_order_bound'])

    def test_batch_advance_records_in_list_and_detail(self):
        """测试 PrintingJob 序列化输出包含批量推进记录（有记录时返回明细，无记录返回空）"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10,
            description='批量记录测试'
        )

        # 创建一条批量推进记录并关联该 job
        record = printing_models.PrintingJobBatchAdvanceRecord.objects.create(
            printing_order=self.printing_order,
            state=self.state1,
            created_by=self.user,
            parameters={'temperature': '25.5', 'operator': '张三'},
        )
        record.printing_jobs.add(job)

        # list: 应包含 batch_advance_records
        response = self.client.get('/api/v1/printing-jobs/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        target = next(item for item in response.data['results'] if item['id'] == job.id)

        self.assertIn('batch_advance_records', target)
        self.assertEqual(len(target['batch_advance_records']), 1)

        rec = target['batch_advance_records'][0]
        self.assertEqual(rec['id'], record.id)
        self.assertEqual(rec['printing_order'], self.printing_order.id)
        self.assertEqual(rec['state'], self.state1.id)
        self.assertEqual(rec['state_id'], self.state1.id)
        self.assertEqual(rec['state_name'], self.state1.name)
        self.assertEqual(rec['created_by'], self.user.id)
        self.assertEqual(rec['created_by_username'], self.user.username)
        self.assertEqual(rec['created_by_name'], self.employee.name)
        self.assertEqual(rec['parameters']['temperature'], '25.5')
        self.assertEqual(rec['parameters']['operator'], '张三')

        # detail: 同样应包含 batch_advance_records
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('batch_advance_records', response.data)
        self.assertEqual(len(response.data['batch_advance_records']), 1)

    def test_list_printing_jobs_includes_saleitems(self):
        """测试 list 返回包含关联的 shipment.SalesItem 数组"""
        job1 = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        job2 = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=200,
            unit='米',
            size='60*70',
            pieces=20
        )

        item1 = shipment_models.SalesItem.objects.create(
            merchant=self.merchant,
            name='销售品A',
            quantity='88.50',
            unit=shipment_models.UnitChoices.METER,
            created_by=self.user,
            printing_job_id=job1.id,
            customer_id=self.customer.id,
            position='A1-01',
            remark='备注A',
        )
        item2 = shipment_models.SalesItem.objects.create(
            merchant=self.merchant,
            name='销售品B',
            quantity='12.00',
            unit=shipment_models.UnitChoices.PIECE,
            created_by=self.user,
            printing_job_id=job1.id,
            customer_id=self.customer.id,
            position='A1-02',
            remark='备注B',
        )

        response = self.client.get(f'/api/v1/printing-jobs/?printing_order={self.printing_order.id}')
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        data_by_id = {item['id']: item for item in response.data['results']}
        self.assertIn('saleitems', data_by_id[job1.id])
        self.assertEqual(len(data_by_id[job1.id]['saleitems']), 2)
        self.assertEqual([item['id'] for item in data_by_id[job1.id]['saleitems']], [item1.id, item2.id])
        self.assertEqual(data_by_id[job1.id]['saleitems'][0]['printing_job_id'], job1.id)
        self.assertEqual(data_by_id[job1.id]['saleitems'][0]['name'], '销售品A')

        self.assertIn('saleitems', data_by_id[job2.id])
        self.assertEqual(data_by_id[job2.id]['saleitems'], [])

    def test_retrieve_printing_job_includes_saleitems(self):
        """测试 detail 返回包含关联的 shipment.SalesItem 数组"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )

        shipment_models.SalesItem.objects.create(
            merchant=self.merchant,
            name='详情销售品',
            quantity='66.00',
            unit=shipment_models.UnitChoices.METER,
            created_by=self.user,
            printing_job_id=job.id,
            customer_id=self.customer.id,
            position='B2-01',
            remark='详情备注',
        )

        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('saleitems', response.data)
        self.assertEqual(len(response.data['saleitems']), 1)
        self.assertEqual(response.data['saleitems'][0]['printing_job_id'], job.id)
        self.assertEqual(response.data['saleitems'][0]['name'], '详情销售品')
    
    def test_update_printing_job(self):
        """测试更新款式明细"""
        self.printing_order.external_order_id = 'KD20410611'
        self.printing_order.save(update_fields=['external_order_id'])

        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        update_data = {
            'printing_order': self.printing_order.id,
            'product': self.product.id,
            'quantity': 200,
            'unit': '码',
            'size': '60*70',
            'pieces': 20,
            'sub_id': 8,
            'description': '更新后的备注'
        }
        
        response = self.client.put(
            f'/api/v1/printing-jobs/{job.id}/',
            update_data,
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['external_order_id'], 'KD20410611')
        self.assertEqual(response.data['sub_id'], 8)
        
        job.refresh_from_db()
        self.assertEqual(job.quantity, 200)
        self.assertEqual(job.unit, '码')
        self.assertEqual(job.pieces, 20)
        self.assertEqual(job.sub_id, 8)
        self.assertFalse(job.is_production_completed)

    def test_update_printing_job_ignores_is_production_completed(self):
        """测试更新接口忽略 is_production_completed"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            is_production_completed=False,
        )

        response = self.client.patch(
            f'/api/v1/printing-jobs/{job.id}/',
            {'is_production_completed': True, 'quantity': 120},
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        job.refresh_from_db()
        self.assertEqual(job.quantity, 120)
        self.assertFalse(job.is_production_completed)

    def test_cannot_change_printing_order_on_update(self):
        """测试：不允许通过更新接口修改 PrintingJob.printing_order"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
        )

        other_order = printing_models.PrintingOrder.objects.create(
            customer=self.customer,
            fabric='其他布料',
            width='160cm',
        )

        update_data = {
            'printing_order': other_order.id,  # 尝试变更绑定关系
            'product': self.product.id,
            'quantity': 200,
            'unit': '码',
        }

        response = self.client.put(
            f'/api/v1/printing-jobs/{job.id}/',
            update_data,
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('printing_order', response.data)
    
    def test_partial_update_printing_job(self):
        """测试部分更新款式明细"""
        self.printing_order.external_order_id = 'KD20410611'
        self.printing_order.save(update_fields=['external_order_id'])

        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        patch_data = {
            'quantity': 150,
            'pieces': 15,
            'sub_id': None
        }
        
        response = self.client.patch(
            f'/api/v1/printing-jobs/{job.id}/',
            patch_data,
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['external_order_id'], 'KD20410611')
        self.assertIsNone(response.data['sub_id'])
        
        job.refresh_from_db()
        self.assertEqual(job.quantity, 150)
        self.assertEqual(job.pieces, 15)
        self.assertIsNone(job.sub_id)
        self.assertEqual(job.unit, '米')  # 未修改字段保持不变
    
    def test_delete_printing_job_forbidden(self):
        """测试删除款式明细被禁用"""
        # 添加删除权限以便测试destroy方法的自定义逻辑
        delete_perm = Permission.objects.get(codename='delete_printingjob')
        self.user.user_permissions.add(delete_perm)
        
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        response = self.client.delete(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertIn('不支持删除', response.data['detail'])
        
        # 验证明细仍然存在
        self.assertTrue(
            printing_models.PrintingJob.objects.filter(id=job.id).exists()
        )
    
    def test_filter_by_printing_order(self):
        """测试按印染订单过滤"""
        order2 = printing_models.PrintingOrder.objects.create(
            customer=self.customer,
            fabric='其他布料',
            width='160cm'
        )
        
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        printing_models.PrintingJob.objects.create(
            printing_order=order2,
            product=self.product,
            quantity=200,
            unit='米',
            size='60*70',
            pieces=20
        )
        
        response = self.client.get(f'/api/v1/printing-jobs/?printing_order={self.printing_order.id}')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 1)
        self.assertEqual(response.data['results'][0]['printing_order'], self.printing_order.id)
    
    def test_filter_by_product(self):
        """测试按产品过滤"""
        product2 = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='产品2',
            human_id='TEST002',
            unit=basic_models.ProductUnitEnum.METER
        )
        
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=product2,
            quantity=200,
            unit='米',
            size='60*70',
            pieces=20
        )
        
        response = self.client.get(f'/api/v1/printing-jobs/?product={self.product.id}')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 1)
        self.assertEqual(response.data['results'][0]['product'], self.product.id)

    def test_filter_by_external_order_id_and_serializer_field(self):
        """测试按所属订单 external_order_id 过滤，并返回该字段"""
        self.printing_order.external_order_id = 'KD20410611'
        self.printing_order.save(update_fields=['external_order_id'])

        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )

        other_order = printing_models.PrintingOrder.objects.create(
            customer=self.customer,
            fabric='其他布料',
            width='160cm',
            external_order_id='KD20419999'
        )
        printing_models.PrintingJob.objects.create(
            printing_order=other_order,
            product=self.product,
            quantity=200,
            unit='米',
            size='60*70',
            pieces=20
        )

        response = self.client.get('/api/v1/printing-jobs/?external_order_id=KD20410611')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 1)
        self.assertEqual(response.data['results'][0]['id'], job.id)
        self.assertEqual(response.data['results'][0]['external_order_id'], 'KD20410611')

        detail = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(detail.status_code, status.HTTP_200_OK)
        self.assertEqual(detail.data['external_order_id'], 'KD20410611')
        self.assertEqual(detail.data['curve'], '测试曲线')
        self.assertEqual(detail.data['new_curve'], '测试新曲线')
    
    def test_filter_by_quantity_range(self):
        """测试按数量范围过滤"""
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=50,
            unit='米',
            size='50*60',
            pieces=10
        )
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=150,
            unit='米',
            size='60*70',
            pieces=20
        )
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=250,
            unit='米',
            size='70*80',
            pieces=30
        )
        
        response = self.client.get('/api/v1/printing-jobs/?quantity_min=100&quantity_max=200')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 1)
        self.assertEqual(response.data['results'][0]['quantity'], 150)
    
    def test_search_by_product_name(self):
        """测试按产品名称搜索"""
        product2 = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='特殊产品',
            human_id='TEST003',
            unit=basic_models.ProductUnitEnum.METER
        )
        
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=product2,
            quantity=200,
            unit='米',
            size='60*70',
            pieces=20
        )
        
        response = self.client.get('/api/v1/printing-jobs/?search=特殊')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 1)
        self.assertIn('特殊', response.data['results'][0]['product_name'])
    
    def test_ordering(self):
        """测试排序"""
        job1 = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        job2 = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=200,
            unit='米',
            size='60*70',
            pieces=20
        )
        
        # 按数量升序
        response = self.client.get('/api/v1/printing-jobs/?ordering=quantity')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['results'][0]['quantity'], 100)
        self.assertEqual(response.data['results'][1]['quantity'], 200)
        
        # 按数量降序
        response = self.client.get('/api/v1/printing-jobs/?ordering=-quantity')
        self.assertEqual(response.data['results'][0]['quantity'], 200)
        self.assertEqual(response.data['results'][1]['quantity'], 100)
    
    def test_validate_quantity_positive(self):
        """测试数量必须大于0"""
        data = {
            'printing_order': self.printing_order.id,
            'product': self.product.id,
            'quantity': 0,  # 无效数量
            'unit': '米',
            'size': '50*60',
            'pieces': 10
        }
        
        response = self.client.post('/api/v1/printing-jobs/', data, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('quantity', response.data)
    
    def test_validate_pieces_can_be_zero(self):
        """测试件数可以为0或null"""
        data = {
            'printing_order': self.printing_order.id,
            'product': self.product.id,
            'quantity': 100,
            'unit': '米',
            'size': '50*60',
            'pieces': 0  # 允许为0
        }
        
        response = self.client.post('/api/v1/printing-jobs/', data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # 验证创建成功
        job = printing_models.PrintingJob.objects.get(id=response.data['id'])
        self.assertEqual(job.pieces, 0)
    
    def test_create_job_with_business_object(self):
        """测试创建任务自动创建 BusinessObject"""
        data = {
            'printing_order': self.printing_order.id,
            'product': self.product.id,
            'quantity': 100,
            'unit': '米',
            'size': '50*60',
            'pieces': 10
        }
        
        response = self.client.post('/api/v1/printing-jobs/', data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # 验证 BusinessObject 已创建
        job = printing_models.PrintingJob.objects.get(id=response.data['id'])
        self.assertIsNotNone(job.business_object)
        self.assertEqual(job.business_object.process, self.process)
        # 新逻辑：BusinessObject 绑定回 PrintingJob
        self.assertEqual(job.business_object.content_type_id, ContentType.objects.get_for_model(printing_models.PrintingJob).id)
        self.assertEqual(job.business_object.object_id, job.id)
    
    def test_job_status_in_detail(self):
        """测试任务详情包含状态字段"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证状态字段
        self.assertIn('status', response.data)
        self.assertIn('status_id', response.data)
        self.assertIn('is_completed', response.data)
        self.assertIn('is_production_completed', response.data)
        self.assertIn('has_started', response.data)
        self.assertIn('business_object_id', response.data)
        
        # 未推进状态，但有 BusinessObject，当前是初始状态
        self.assertIn(response.data['status'], ['待印染', '未开始'])  # 初始状态或未开始
        self.assertFalse(response.data['is_completed'])
        self.assertFalse(response.data['is_production_completed'])
        self.assertFalse(response.data['has_started'])
    
    def test_job_status_in_list(self):
        """测试任务列表包含状态字段"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        response = self.client.get('/api/v1/printing-jobs/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证列表中的状态字段
        self.assertIn('status', response.data['results'][0])
        self.assertIn('is_completed', response.data['results'][0])
        self.assertIn('is_production_completed', response.data['results'][0])
    
    def test_job_completion_status(self):
        """测试任务完成状态判断"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 推进到最后一个状态（流程有3个节点，需要推进3次）
        from stateflow.services import advance_to_next_state
        advance_to_next_state(business_object, self.user)  # 完成第1个状态
        advance_to_next_state(business_object, self.user)  # 完成第2个状态
        advance_to_next_state(business_object, self.user)  # 完成第3个状态
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证完成状态
        self.assertEqual(response.data['status'], '已完成')
        self.assertTrue(response.data['is_completed'])
        self.assertFalse(response.data['is_production_completed'])
        self.assertTrue(response.data['has_started'])

    def test_list_filter_by_is_production_completed(self):
        """测试按 is_production_completed 过滤 PrintingJob 列表"""
        completed_job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            is_production_completed=True,
        )
        printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=80,
            unit='米',
            is_production_completed=False,
        )

        response = self.client.get('/api/v1/printing-jobs/?is_production_completed=true')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['id'], completed_job.id)
        self.assertTrue(response.data['results'][0]['is_production_completed'])
    
    def test_advance_to_next_state(self):
        """测试推进到下一个状态 API"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 推进到下一个状态
        response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('detail', response.data)
        self.assertIn('data', response.data)
        self.assertIn('已完成状态', response.data['detail'])
        
        # 验证状态已更新
        job.refresh_from_db()
        self.assertTrue(job.has_started)
        self.assertEqual(job.status, self.state2.name)  # 应该是第二个状态（当前进行中的）
    
    def test_advance_to_next_state_without_business_object(self):
        """测试推进到下一个状态 - 没有 BusinessObject 的情况"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('没有关联的流程实例', response.data['detail'])
    
    def test_advance_with_parameters(self):
        """测试推进到下一个状态 - 带参数"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 推进到下一个状态，带参数
        data = {
            'parameters': {
                'temperature': '25.5',
                'operator': '张三',
                'humidity': '60%'
            }
        }
        response = self.client.post(
            f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/',
            data,
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('detail', response.data)
        self.assertIn('data', response.data)
        
        # 验证参数已保存
        job.refresh_from_db()
        state_log = business_object.state_logs.filter(state=self.state1, is_cancelled=False).first()
        self.assertIsNotNone(state_log)
        
        # 验证参数记录
        param_record = state_log.parameter_records.first()
        self.assertIsNotNone(param_record)
        self.assertEqual(param_record.parameters['temperature'], '25.5')
        self.assertEqual(param_record.parameters['operator'], '张三')
        self.assertEqual(param_record.parameters['humidity'], '60%')
    
    def test_advance_with_required_parameters_missing(self):
        """测试推进到下一个状态 - 缺少必填参数"""
        # 创建带必填参数的状态
        param1 = stateflow_models.StateParameter.objects.create(
            key='temperature',
            is_required=True,
            description='温度'
        )
        param2 = stateflow_models.StateParameter.objects.create(
            key='operator',
            is_required=True,
            description='操作员'
        )
        
        state_with_params = stateflow_models.State.objects.create(name='带参数状态')
        state_with_params.parameters.add(param1, param2)
        
        # 创建新流程，第一个状态是带必填参数的
        process_with_params = stateflow_models.Process.objects.create(name='带参数流程')
        process_with_params.replace_nodes([state_with_params, self.state2])
        
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=process_with_params,
        )
        job.business_object = business_object
        job.save()
        
        # 尝试推进但不提供必填参数
        response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('缺失必填参数', response.data['detail'])
        self.assertIn('temperature', response.data['detail'])
        self.assertIn('operator', response.data['detail'])
    
    def test_advance_with_partial_required_parameters(self):
        """测试推进到下一个状态 - 只提供部分必填参数"""
        # 创建带必填参数的状态
        param1 = stateflow_models.StateParameter.objects.create(
            key='temperature',
            is_required=True,
            description='温度'
        )
        param2 = stateflow_models.StateParameter.objects.create(
            key='operator',
            is_required=True,
            description='操作员'
        )
        
        state_with_params = stateflow_models.State.objects.create(name='带参数状态')
        state_with_params.parameters.add(param1, param2)
        
        # 创建新流程
        process_with_params = stateflow_models.Process.objects.create(name='带参数流程')
        process_with_params.replace_nodes([state_with_params, self.state2])
        
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=process_with_params,
        )
        job.business_object = business_object
        job.save()
        
        # 只提供一个必填参数
        data = {
            'parameters': {
                'temperature': '25.5'
            }
        }
        response = self.client.post(
            f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/',
            data,
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('缺失必填参数', response.data['detail'])
        self.assertIn('operator', response.data['detail'])
        self.assertNotIn('temperature', response.data['detail'])  # temperature 已提供
    
    def test_advance_through_all_states(self):
        """测试推进到完成所有状态"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 推进 3 次完成所有状态
        for i in range(3):
            response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
            self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证已完成
        job.refresh_from_db()
        self.assertTrue(job.is_completed)
        self.assertEqual(job.status, '已完成')
    
    def test_step_back_one_state(self):
        """测试回退一个状态 API"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 先推进两步
        from stateflow.services import advance_to_next_state
        advance_to_next_state(business_object, self.user)
        advance_to_next_state(business_object, self.user)
        
        # 回退一步
        response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('detail', response.data)
        self.assertIn('data', response.data)
        self.assertIn('已回退状态', response.data['detail'])
        
        # 验证状态已回退
        job.refresh_from_db()
        self.assertEqual(job.status, self.state2.name)  # 应该回退到第二个状态（当前）
    
    def test_step_back_without_business_object(self):
        """测试回退状态 - 没有 BusinessObject 的情况"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('没有关联的流程实例', response.data['detail'])
    
    def test_step_back_without_records(self):
        """测试回退状态 - 没有状态流转记录的情况"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject 但不推进状态
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('没有任何状态流转记录', response.data['detail'])
    
    def test_completed_states_list(self):
        """测试获取已完成状态列表 API"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 推进两个状态
        from stateflow.services import advance_to_next_state
        advance_to_next_state(business_object, self.user)
        advance_to_next_state(business_object, self.user)
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('count', response.data)
        self.assertIn('results', response.data)
        self.assertEqual(response.data['count'], 2)
        self.assertEqual(len(response.data['results']), 2)
        
        # 验证返回的状态数据
        first_state = response.data['results'][0]
        self.assertIn('state_id', first_state)
        self.assertIn('state_name', first_state)
        self.assertIn('completed_at', first_state)
        self.assertIn('completed_by', first_state)
        self.assertIn('is_cancelled', first_state)
        self.assertFalse(first_state['is_cancelled'])
    
    def test_completed_states_include_cancelled(self):
        """测试获取已完成状态列表 - 包含已撤销的记录"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 推进两个状态
        from stateflow.services import advance_to_next_state, step_back_one_state
        advance_to_next_state(business_object, self.user)
        advance_to_next_state(business_object, self.user)
        
        # 回退一次（会将最后一条记录标记为 cancelled）
        step_back_one_state(business_object, self.user)
        
        # 不包含已撤销的记录（默认）
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 1)
        
        # 包含已撤销的记录
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/?include_cancelled=true')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 2)
        
        # 验证有一条记录是已撤销的
        cancelled_records = [r for r in response.data['results'] if r['is_cancelled']]
        self.assertEqual(len(cancelled_records), 1)
    
    def test_completed_states_empty(self):
        """测试获取已完成状态列表 - 没有完成任何状态"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject 但不推进状态
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 0)
        self.assertEqual(len(response.data['results']), 0)
    
    def test_timeline(self):
        """测试获取流程时间线 API"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 推进一个状态
        from stateflow.services import advance_to_next_state
        advance_to_next_state(business_object, self.user)
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('count', response.data)
        self.assertIn('results', response.data)
        self.assertEqual(response.data['count'], 3)  # 流程有 3 个状态
        self.assertEqual(len(response.data['results']), 3)
        
        # 验证时间线数据
        timeline = response.data['results']
        
        # 第一个状态应该是已完成
        self.assertEqual(timeline[0]['state_name'], self.state1.name)
        self.assertEqual(timeline[0]['status'], 'completed')
        self.assertIsNotNone(timeline[0]['completed_at'])
        self.assertIsNotNone(timeline[0]['completed_by'])
        
        # 第二个状态应该是未开始（因为我们废除了 in_progress 的概念）
        self.assertEqual(timeline[1]['state_name'], self.state2.name)
        self.assertEqual(timeline[1]['status'], 'not_started')
        self.assertIsNone(timeline[1]['completed_at'])
        
        # 第三个状态应该是未开始
        self.assertEqual(timeline[2]['state_name'], self.state3.name)
        self.assertEqual(timeline[2]['status'], 'not_started')
        self.assertIsNone(timeline[2]['completed_at'])
    
    def test_timeline_all_completed(self):
        """测试获取流程时间线 - 所有状态已完成"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 完成所有状态
        from stateflow.services import advance_to_next_state
        advance_to_next_state(business_object, self.user)
        advance_to_next_state(business_object, self.user)
        advance_to_next_state(business_object, self.user)
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证所有状态都是已完成
        timeline = response.data['results']
        for item in timeline:
            self.assertEqual(item['status'], 'completed')
            self.assertIsNotNone(item['completed_at'])
    
    def test_timeline_not_started(self):
        """测试获取流程时间线 - 未开始任何状态"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject 但不推进状态
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证第一个状态是未开始，因为没有推进任何状态
        timeline = response.data['results']
        self.assertEqual(timeline[0]['status'], 'not_started')
        self.assertEqual(timeline[1]['status'], 'not_started')
        self.assertEqual(timeline[2]['status'], 'not_started')
    
    def test_timeline_excludes_cancelled(self):
        """测试获取流程时间线 - 自动过滤已撤销的记录"""
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        # 创建 BusinessObject
        business_object = stateflow_models.BusinessObject.objects.create(
            name=f'PrintingJob-{job.id}',
            process=self.process,
        )
        job.business_object = business_object
        job.save()
        
        # 推进两个状态然后回退一次
        from stateflow.services import advance_to_next_state, step_back_one_state
        advance_to_next_state(business_object, self.user)
        advance_to_next_state(business_object, self.user)
        step_back_one_state(business_object, self.user)
        
        response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证时间线中只有一个已完成的状态（撤销的不显示）
        timeline = response.data['results']
        completed_states = [t for t in timeline if t['status'] == 'completed']
        self.assertEqual(len(completed_states), 1)
        self.assertEqual(completed_states[0]['state_name'], self.state1.name)

    def test_list_printing_jobs_with_product_image_url(self):
        """测试列表接口返回产品图片 URL（mdy_image_url 优先）"""
        # 设置产品的 mdy_image_url
        self.product.mdy_image_url = 'https://example.com/product.jpg'
        self.product.save()
        
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        response = self.client.get('/api/v1/printing-jobs/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证 product_image_url 字段存在且返回正确的 URL
        result = response.data['results'][0]
        self.assertIn('product_image_url', result)
        self.assertEqual(result['product_image_url'], 'https://example.com/product.jpg')

    def test_list_printing_jobs_product_image_url_none(self):
        """测试产品无图片时 product_image_url 返回 None"""
        # 确保产品没有图片
        self.product.mdy_image_url = None
        self.product.image = None
        self.product.description = None
        self.product.save()
        
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        response = self.client.get('/api/v1/printing-jobs/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证 product_image_url 字段存在且为 None
        result = response.data['results'][0]
        self.assertIn('product_image_url', result)
        self.assertIsNone(result['product_image_url'])

    def test_list_printing_jobs_product_image_url_fallback_to_local_image(self):
        """测试产品图片 URL fallback 到本地 image 字段"""
        from django.core.files.uploadedfile import SimpleUploadedFile
        
        # 确保 mdy_image_url 和 description 都为空
        self.product.mdy_image_url = None
        self.product.description = None
        # 设置本地图片（模拟已上传的文件路径）
        self.product.image = 'product_images/test_product.jpg'
        self.product.save()
        
        job = printing_models.PrintingJob.objects.create(
            printing_order=self.printing_order,
            product=self.product,
            quantity=100,
            unit='米',
            size='50*60',
            pieces=10
        )
        
        response = self.client.get('/api/v1/printing-jobs/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 验证 product_image_url 字段存在且包含本地图片路径
        result = response.data['results'][0]
        self.assertIn('product_image_url', result)
        self.assertIsNotNone(result['product_image_url'])
        self.assertIn('product_images/test_product.jpg', result['product_image_url'])
