from decimal import Decimal
import datetime
import logging
import unittest

from django.contrib.auth import get_user_model
from django.utils import timezone
from django.test import TestCase
from rest_framework import status
from rest_framework.test import APIClient

from basic_info import models as basic_models
from stock import models as stock_models, services as stock_services


User = get_user_model()


class CreateStockChangeAPITestCase(TestCase):
    """测试标准库存变动创建 API"""

    def setUp(self):
        self.client = APIClient()

        self.merchant = basic_models.Merchant.objects.create(
            name='测试商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        self.category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='布料',
            product_prefix='FAB',
        )
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='测试布料',
            human_id='FAB-001',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='严谨仓库',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
        )
        self.other_merchant = basic_models.Merchant.objects.create(
            name='其他商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        other_category = basic_models.ProductCategory.objects.create(
            merchant=self.other_merchant,
            name='其他布料',
            product_prefix='FABO',
        )
        self.other_product = basic_models.Product.objects.create(
            merchant=self.other_merchant,
            category=other_category,
            name='外部布料',
            human_id='FAB-999',
            unit=basic_models.ProductUnitEnum.METER,
        )

        self.user = User.objects.create_user(username='strict_user', password='pass123')
        self.employee = basic_models.Employee.objects.create(
            merchant=self.merchant,
            sys_user=self.user,
            name='仓管员',
            mobile='13800138002',
            status=basic_models.EmployeeStatusEnum.ACTIVE,
        )
        self.client.force_authenticate(user=self.user)

    def test_create_stock_change_success(self):
        payload = {
            'type': stock_models.StockChangeTypeEnum.ADD,
            'warehouse': self.warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.PURCHASE,
            'source_id': 10,
            'products': [
                {'product': self.product.id, 'quantity': ['10.50', '5.25']},
            ]
        }

        response = self.client.post('/api/v1/stock-change/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['created_details_count'], 2)
        self.assertEqual(len(response.data['details']), 2)
        self.assertEqual(response.data['stock_change_record']['warehouse'], self.warehouse.id)

        record_id = response.data['stock_change_record']['id']
        record = stock_models.StockChangeRecord.objects.get(id=record_id)
        self.assertEqual(record.details.count(), 2)

    def test_create_stock_change_products_required(self):
        payload = {
            'type': stock_models.StockChangeTypeEnum.ADD,
            'warehouse': self.warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.PURCHASE,
            'source_id': 11,
            'products': [],
        }

        response = self.client.post('/api/v1/stock-change/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data['error'], '产品列表不能为空')

    def test_create_stock_change_product_not_visible(self):
        payload = {
            'type': stock_models.StockChangeTypeEnum.ADD,
            'warehouse': self.warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.PURCHASE,
            'products': [
                {'product': self.other_product.id, 'quantity': ['5.00']},
            ]
        }

        response = self.client.post('/api/v1/stock-change/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertEqual(
            response.data['error'],
            f'产品ID {self.other_product.id} 对当前用户不可见',
        )

    def test_create_stock_change_no_employee_permission_denied(self):
        class DummyUser:
            def __init__(self, username):
                self.username = username
                self.is_authenticated = True

        dummy_user = DummyUser('no_emp_user')
        self.client.force_authenticate(user=dummy_user)
        payload = {
            'type': stock_models.StockChangeTypeEnum.ADD,
            'warehouse': self.warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.PURCHASE,
            'products': [
                {'product': self.product.id, 'quantity': ['5.00']},
            ]
        }

        logging.disable(logging.NOTSET)
        self.addCleanup(logging.disable, logging.CRITICAL)

        with self.assertLogs('api_v1.views.stock_change_views.mixins', level='ERROR') as cm:
            response = self.client.post('/api/v1/stock-change/', payload, format='json')

        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertEqual(response.data['error'], '无权限访问')
        self.assertTrue(any('无员工信息' in msg for msg in cm.output))

    def test_get_stock_change_includes_offset_status_fields(self):
        record = stock_models.StockChangeRecord.objects.create(
            merchant=self.merchant,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse=self.warehouse,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            created_by=self.user,
        )
        detail = stock_models.StockChangeDetail.objects.create(
            merchant=self.merchant,
            stock_change_record=record,
            product=self.product,
            quantity='10',
            unit=self.product.unit,
        )
        snapshot = stock_models.StockSnapshot.objects.create(
            merchant=self.merchant,
            product=self.product,
            warehouse=self.warehouse,
            delta='10',
            quantity_before='0',
            quantity_after='10',
            stock_change_record=record,
            unit=self.product.unit,
            num_of_rolls=1,
            cancelled=True,
            cancelled_at=timezone.now(),
            offset_id=1,
        )

        offset_record = stock_models.StockChangeRecord.objects.create(
            merchant=self.merchant,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse=self.warehouse,
            source_type=stock_models.StockChangeSourceEnum.OFFSET,
            created_by=self.user,
        )
        stock_models.StockSnapshot.objects.create(
            merchant=self.merchant,
            product=self.product,
            warehouse=self.warehouse,
            delta='-10',
            quantity_before='10',
            quantity_after='0',
            stock_change_record=offset_record,
            unit=self.product.unit,
            num_of_rolls=1,
            offset_to=snapshot.id,
            offset_at=timezone.now(),
        )

        response = self.client.get(f'/api/v1/stock-change/{record.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        record_data = response.data['stock_change_record']
        self.assertTrue(record_data['cancelled'])
        self.assertTrue(record_data['is_reversed'])
        self.assertFalse(record_data['is_offset'])

        offset_response = self.client.get(f'/api/v1/stock-change/{offset_record.id}/')
        self.assertEqual(offset_response.status_code, status.HTTP_200_OK)
        offset_data = offset_response.data['stock_change_record']
        self.assertTrue(offset_data['is_offset'])
        self.assertFalse(offset_data['is_reversed'])


class CreateStockChangeRelaxedAPITestCase(TestCase):
    """测试宽松模式库存变动创建 API"""

    def setUp(self):
        self.client = APIClient()

        self.merchant = basic_models.Merchant.objects.create(
            name='宽松商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        self.category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='面料',
            product_prefix='FAB',
        )
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='宽松布料',
            human_id='FAB-100',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.relaxed_warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='宽进宽出仓',
            mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
        )
        self.other_merchant = basic_models.Merchant.objects.create(
            name='RelaxedOther',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        other_category = basic_models.ProductCategory.objects.create(
            merchant=self.other_merchant,
            name='外部面料',
            product_prefix='REL',
        )
        self.other_product = basic_models.Product.objects.create(
            merchant=self.other_merchant,
            category=other_category,
            name='外部布料',
            human_id='REL-002',
            unit=basic_models.ProductUnitEnum.METER,
        )

        self.user = User.objects.create_user(username='relaxed_user', password='pass123')
        self.employee = basic_models.Employee.objects.create(
            merchant=self.merchant,
            sys_user=self.user,
            name='宽松仓管员',
            mobile='13800138003',
            status=basic_models.EmployeeStatusEnum.ACTIVE,
        )
        self.client.force_authenticate(user=self.user)

    def test_create_stock_change_relaxed_success(self):
        payload = {
            'type': stock_models.StockChangeTypeEnum.ADD,
            'warehouse': self.relaxed_warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.PURCHASE,
            'source_id': 20,
            'products': [
                {
                    'product': self.product.id,
                    'quantity': {'value': '12', 'unit_count': '5'}
                }
            ]
        }

        response = self.client.post('/api/v1/stock-change/relaxed/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['created_details_count'], 3)
        record_id = response.data['stock_change_record']['id']
        details = stock_models.StockChangeDetail.objects.filter(stock_change_record_id=record_id)
        self.assertEqual(details.count(), 3)

    def test_create_stock_change_relaxed_missing_required(self):
        payload = {
            'warehouse': self.relaxed_warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.PURCHASE,
            'products': [
                {
                    'product': self.product.id,
                    'quantity': {'value': '10', 'unit_count': '4'}
                }
            ]
        }

        response = self.client.post('/api/v1/stock-change/relaxed/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data['error'], '缺少必要参数')

    def test_create_stock_change_relaxed_product_not_visible(self):
        payload = {
            'type': stock_models.StockChangeTypeEnum.ADD,
            'warehouse': self.relaxed_warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.PURCHASE,
            'products': [
                {
                    'product': self.other_product.id,
                    'quantity': {'value': '8', 'unit_count': '4'}
                }
            ]
        }

        response = self.client.post('/api/v1/stock-change/relaxed/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertEqual(
            response.data['error'],
            f'产品ID {self.other_product.id} 对当前用户不可见',
        )

    def test_create_stock_change_relaxed_no_employee_permission_denied(self):
        class DummyUser:
            def __init__(self, username):
                self.username = username
                self.is_authenticated = True

        dummy_user = DummyUser('relaxed_no_emp')
        self.client.force_authenticate(user=dummy_user)
        payload = {
            'type': stock_models.StockChangeTypeEnum.ADD,
            'warehouse': self.relaxed_warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.PURCHASE,
            'products': [
                {
                    'product': self.product.id,
                    'quantity': {'value': '10', 'unit_count': '5'}
                }
            ]
        }

        logging.disable(logging.NOTSET)
        self.addCleanup(logging.disable, logging.CRITICAL)
        with self.assertLogs('api_v1.views.stock_change_views.mixins', level='ERROR') as cm:
            response = self.client.post('/api/v1/stock-change/relaxed/', payload, format='json')

        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertEqual(response.data['error'], '无权限访问')
        self.assertTrue(any('无员工信息' in msg for msg in cm.output))


class StockChangeRestrictAPITestCase(TestCase):
    """测试严进严出模式的 API"""

    def setUp(self):
        self.client = APIClient()

        self.merchant = basic_models.Merchant.objects.create(
            name='测试商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        self.category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='布料',
            product_prefix='FAB',
        )
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='测试布料',
            human_id='FAB-001',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='严进严出仓',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN_OUT,
        )

        self.user = User.objects.create_user(username='restrict_user', password='pass123')
        self.employee = basic_models.Employee.objects.create(
            merchant=self.merchant,
            sys_user=self.user,
            name='仓管员',
            mobile='13800138000',
            status=basic_models.EmployeeStatusEnum.ACTIVE,
        )
        self.client.force_authenticate(user=self.user)

        # 创建一条入库明细供消耗
        _, details, _ = stock_services.create_stock_change_record_with_details(
            merchant=self.merchant,
            created_by=self.user,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse_id=self.warehouse.id,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=1,
            products=[{'product': self.product.id, 'quantity': [Decimal('12.50')]}],
        )
        self.inbound_detail = details[0]

    def test_restrict_outgoing_consumes_inbound_detail(self):
        payload = {
            'type': stock_models.StockChangeTypeEnum.REMOVE,
            'warehouse': self.warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.SALES,
            'source_id': 2,
            'products': [
                {
                    'product': self.product.id,
                    'consume_with': [self.inbound_detail.id],
                }
            ]
        }

        response = self.client.post('/api/v1/stock-change/restrict/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.inbound_detail.refresh_from_db()
        self.assertTrue(self.inbound_detail.is_consumed)

        detail = response.data['details'][0]
        self.assertEqual(detail['consume_with_id'], self.inbound_detail.id)
        self.assertEqual(
            Decimal(str(detail['quantity'])),
            self.inbound_detail.quantity,
        )

    def test_restrict_outgoing_requires_consume_with(self):
        payload = {
            'type': stock_models.StockChangeTypeEnum.REMOVE,
            'warehouse': self.warehouse.id,
            'source_type': stock_models.StockChangeSourceEnum.SALES,
            'source_id': 3,
            'products': [
                {
                    'product': self.product.id,
                    'quantity': ['10.00'],
                }
            ]
        }

        response = self.client.post('/api/v1/stock-change/restrict/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('consume_with', str(response.data))


class ListStockChangeDetailsAPITestCase(TestCase):
    """测试库存变动明细查询 API"""

    def setUp(self):
        self.client = APIClient()

        self.merchant = basic_models.Merchant.objects.create(
            name='测试商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        self.category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='布料',
            product_prefix='FAB',
        )
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='测试布料',
            human_id='FAB-001',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='测试仓库',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
        )
        
        # 创建另一个产品和仓库用于测试过滤
        self.product2 = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='测试布料2',
            human_id='FAB-002',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.warehouse2 = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='测试仓库2',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
        )

        self.user = User.objects.create_user(username='detail_user', password='pass123')
        self.employee = basic_models.Employee.objects.create(
            merchant=self.merchant,
            sys_user=self.user,
            name='仓管员',
            mobile='13800138001',
            status=basic_models.EmployeeStatusEnum.ACTIVE,
        )
        self.client.force_authenticate(user=self.user)

        # 创建多条库存变动记录和明细用于测试
        self.record1, self.details1, _ = stock_services.create_stock_change_record_with_details(
            merchant=self.merchant,
            created_by=self.user,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse_id=self.warehouse.id,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=1,
            products=[{'product': self.product.id, 'quantity': [Decimal('12.50'), Decimal('8.75')]}],
        )
        
        self.record2, self.details2, _ = stock_services.create_stock_change_record_with_details(
            merchant=self.merchant,
            created_by=self.user,
            type=stock_models.StockChangeTypeEnum.REMOVE,
            warehouse_id=self.warehouse.id,
            source_type=stock_models.StockChangeSourceEnum.SALES,
            source_id=2,
            products=[{'product': self.product.id, 'quantity': [Decimal('5.00')]}],
        )
        
        # 在另一个仓库中创建记录，用于测试仓库过滤
        self.record3, self.details3, _ = stock_services.create_stock_change_record_with_details(
            merchant=self.merchant,
            created_by=self.user,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse_id=self.warehouse2.id,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=3,
            products=[{'product': self.product.id, 'quantity': [Decimal('10.00')]}],
        )
        
        # 在同一个仓库中为另一个产品创建记录，用于测试产品过滤
        self.record4, self.details4, _ = stock_services.create_stock_change_record_with_details(
            merchant=self.merchant,
            created_by=self.user,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse_id=self.warehouse.id,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=4,
            products=[{'product': self.product2.id, 'quantity': [Decimal('15.00')]}],
        )

    def test_list_stock_change_details_success(self):
        """测试成功获取库存变动明细列表"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 默认 direction=inbound，因此仅包含仓库1中产品1的入库明细（record1 的 2 条）
        self.assertEqual(response.data['count'], 2)
        self.assertEqual(len(response.data['results']), 2)
        
        # 检查返回数据结构
        result = response.data['results'][0]
        self.assertIn('id', result)
        self.assertIn('product', result)
        self.assertIn('product_name', result)
        self.assertIn('quantity', result)
        self.assertIn('unit_display', result)
        self.assertIn('stock_change_record', result)
        self.assertIn('record_type', result)
        self.assertIn('record_type_display', result)
        self.assertIn('source_type', result)
        self.assertIn('source_type_display', result)
        self.assertIn('record_created_at', result)
        self.assertIn('record_created_by', result)
        
        # 检查过滤条件
        self.assertEqual(response.data['filters']['warehouse_id'], self.warehouse.id)
        self.assertEqual(response.data['filters']['product_id'], self.product.id)
        
        # 检查结果按创建时间降序排列
        dates = [result['record_created_at'] for result in response.data['results']]
        self.assertEqual(dates, sorted(dates, reverse=True))

    def test_list_stock_change_details_excludes_frozen(self):
        """库存明细列表应过滤已冻结明细"""
        frozen_detail = self.details1[0]
        stock_models.StockFreeze.objects.create(
            merchant=self.merchant,
            product=frozen_detail.product,
            warehouse=self.warehouse,
            stock_detail=frozen_detail,
            quantity=frozen_detail.quantity,
            unit=frozen_detail.unit,
            status=stock_models.StockFreezeStatusEnum.FROZEN,
            frozen_by=self.user,
            frozen_with=1,
        )

        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        returned_ids = [item['id'] for item in response.data['results']]
        self.assertNotIn(frozen_detail.id, returned_ids)
        self.assertEqual(response.data['count'], 1)

    def test_list_stock_change_details_missing_warehouse_id(self):
        """测试缺少warehouse_id参数"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?product_id={self.product.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('缺少必要参数', response.data['error'])


    def test_list_stock_change_details_missing_product_id(self):
        """测试缺少product_id参数"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('缺少必要参数', response.data['error'])
        self.assertIn('product_id', response.data['message'])

    def test_list_stock_change_details_invalid_warehouse_id(self):
        """测试无效的warehouse_id参数"""
        response = self.client.get(
            '/api/v1/stock-change-details/?warehouse_id=invalid&product_id=1'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('参数格式错误', response.data['error'])

    def test_list_stock_change_details_invalid_product_id(self):
        """测试无效的product_id参数"""
        response = self.client.get(
            '/api/v1/stock-change-details/?warehouse_id=1&product_id=invalid'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('参数格式错误', response.data['error'])

    def test_list_stock_change_details_different_warehouse(self):
        """测试查询不同仓库的明细"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse2.id}&product_id={self.product.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 应该只返回仓库2中产品1的明细（只有record3的明细）
        self.assertEqual(response.data['count'], 1)
        self.assertEqual(len(response.data['results']), 1)
        
        # 检查返回的明细属于正确的记录
        result = response.data['results'][0]
        self.assertEqual(result['stock_change_record'], self.record3.id)

    def test_list_stock_change_details_different_product(self):
        """测试查询不同产品的明细"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product2.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 应该只返回仓库1中产品2的明细（只有record4的明细）
        self.assertEqual(response.data['count'], 1)
        self.assertEqual(len(response.data['results']), 1)
        
        # 检查返回的明细属于正确的记录
        result = response.data['results'][0]
        self.assertEqual(result['stock_change_record'], self.record4.id)

    def test_list_stock_change_details_no_results(self):
        """测试查询不存在的仓库或产品组合"""
        # 使用不存在的仓库ID
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id=9999&product_id={self.product.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertIn('对当前用户不可见', response.data['error'])

    def test_list_stock_change_details_unauthenticated(self):
        """测试未认证用户访问"""
        self.client.force_authenticate(user=None)
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_list_stock_change_details_no_employee(self):
        """测试无员工信息的用户访问"""
        # 创建一个没有关联员工的用户
        user_no_employee = User.objects.create_user(username='no_employee', password='pass123')
        self.client.force_authenticate(user=user_no_employee)
        
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertIn('无权限访问', response.data['error'])

    def test_list_stock_change_details_with_direction_inbound(self):
        """测试direction参数为inbound（入库）"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}&direction=inbound'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 应该只返回入库记录的明细（record1的明细）
        self.assertEqual(response.data['count'], 2)  # record1有2条明细
        self.assertEqual(len(response.data['results']), 2)
        
        # 检查过滤条件
        self.assertEqual(response.data['filters']['direction'], 'inbound')
        
        # 检查所有记录都是入库记录
        for result in response.data['results']:
            self.assertEqual(result['record_type'], stock_models.StockChangeTypeEnum.ADD)
            self.assertEqual(result['record_type_display'], '入库')

    def test_list_stock_change_details_with_direction_outbound(self):
        """测试direction参数为outbound（出库）"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}&direction=outbound'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 应该只返回出库记录的明细（record2的明细）
        self.assertEqual(response.data['count'], 1)  # record2有1条明细
        self.assertEqual(len(response.data['results']), 1)
        
        # 检查过滤条件
        self.assertEqual(response.data['filters']['direction'], 'outbound')
        
        # 检查所有记录都是出库记录
        for result in response.data['results']:
            self.assertEqual(result['record_type'], stock_models.StockChangeTypeEnum.REMOVE)
            self.assertEqual(result['record_type_display'], '出库')


class ListStockChangeRecordsAPITestCase(TestCase):
    """测试库存变动记录查询 API 的新增筛选参数"""

    def setUp(self):
        self.client = APIClient()

        self.merchant = basic_models.Merchant.objects.create(
            name='记录商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        self.category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='布料',
            product_prefix='FAB',
        )
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='测试布料',
            human_id='FAB-100',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='记录仓库',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
        )
        self.user = User.objects.create_user(username='record_user', password='pass123')
        self.employee = basic_models.Employee.objects.create(
            merchant=self.merchant,
            sys_user=self.user,
            name='仓管员',
            mobile='13800138009',
            status=basic_models.EmployeeStatusEnum.ACTIVE,
        )
        self.client.force_authenticate(user=self.user)

        self.record_unbound = stock_models.StockChangeRecord.objects.create(
            merchant=self.merchant,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse=self.warehouse,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=None,
            created_by=self.user,
        )
        stock_models.StockChangeDetail.objects.create(
            merchant=self.merchant,
            stock_change_record=self.record_unbound,
            product=self.product,
            quantity='10',
            unit=self.product.unit,
        )

        self.record_bound = stock_models.StockChangeRecord.objects.create(
            merchant=self.merchant,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse=self.warehouse,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=99,
            created_by=self.user,
        )
        stock_models.StockChangeDetail.objects.create(
            merchant=self.merchant,
            stock_change_record=self.record_bound,
            product=self.product,
            quantity='5',
            unit=self.product.unit,
        )

    def test_list_stock_change_records_filter_source_type_and_source_id_isnull(self):
        today = datetime.date.today().strftime('%Y-%m-%d')
        response = self.client.get(
            f'/api/v1/stock-changes/?start={today}&end={today}'
            f'&type={stock_models.StockChangeTypeEnum.ADD}'
            f'&source_type={stock_models.StockChangeSourceEnum.PURCHASE}'
            f'&source_id_isnull=true'
            f'&warehouse={self.warehouse.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        result_ids = [item['id'] for item in response.data['results']]
        self.assertIn(self.record_unbound.id, result_ids)
        self.assertNotIn(self.record_bound.id, result_ids)

    def test_list_stock_change_details_with_invalid_direction(self):
        """测试无效的direction参数"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}&direction=invalid'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('参数格式错误', response.data['error'])
        self.assertIn('direction 参数必须为 inbound 或 outbound', response.data['message'])

    def test_list_stock_change_details_default_direction(self):
        """测试默认direction参数（应该是inbound）"""
        response = self.client.get(
            f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 默认应该是inbound，所以应该只返回入库记录的明细（record1的明细）
        self.assertEqual(response.data['count'], 2)  # record1有2条明细
        self.assertEqual(len(response.data['results']), 2)
        
        # 检查过滤条件
        self.assertEqual(response.data['filters']['direction'], 'inbound')
        
        # 检查所有记录都是入库记录
        for result in response.data['results']:
            self.assertEqual(result['record_type'], stock_models.StockChangeTypeEnum.ADD)
            self.assertEqual(result['record_type_display'], '入库')


class FinishStockChangeAPITestCase(TestCase):
    """测试手动完成库存变动记录的 API"""

    def setUp(self):
        self.client = APIClient()

        self.merchant = basic_models.Merchant.objects.create(
            name='完成测试商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        self.category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='坯布',
            product_prefix='FAB',
        )
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='完成测试面料',
            human_id='FAB-010',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='完成测试仓',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
        )

        self.user = User.objects.create_user(username='finish_user', password='pass123')
        self.employee = basic_models.Employee.objects.create(
            merchant=self.merchant,
            sys_user=self.user,
            name='仓管员',
            mobile='13800138005',
            status=basic_models.EmployeeStatusEnum.ACTIVE,
        )
        self.client.force_authenticate(user=self.user)

        self.record, self.details, _ = stock_services.create_stock_change_record_with_details(
            merchant=self.merchant,
            created_by=self.user,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse_id=self.warehouse.id,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=101,
            products=[{'product': self.product.id, 'quantity': [Decimal('15.00')]}],
        )
        self.finish_url = f'/api/v1/stock-change/{self.record.id}/finish/'

    def test_finish_stock_change_success(self):
        response = self.client.post(self.finish_url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['status'], 'success')
        self.assertTrue(response.data['stock_change_record']['is_finished'])

        self.record.refresh_from_db()
        self.assertTrue(self.record.is_finished)
        inventory = stock_models.Inventory.objects.get(
            product_id=self.product.id,
            warehouse_id=self.warehouse.id,
        )
        self.assertEqual(inventory.quantity, Decimal('15.00'))

    def test_finish_stock_change_idempotent(self):
        # 完成一次
        self.client.post(self.finish_url, format='json')
        # 再次完成应仍然成功
        response = self.client.post(self.finish_url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['status'], 'success')

    def test_finish_stock_change_requires_employee(self):
        user_without_employee = User.objects.create_user(username='finish_no_emp', password='pass123')
        self.client.force_authenticate(user=user_without_employee)

        response = self.client.post(self.finish_url, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertEqual(response.data['error'], '无权限操作')

    def test_finish_stock_change_not_found(self):
        response = self.client.post('/api/v1/stock-change/999999/finish/', format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        self.assertIn('不存在', response.data['error'])

    def test_finish_stock_change_other_merchant_forbidden(self):
        other_merchant = basic_models.Merchant.objects.create(
            name='外部商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        other_category = basic_models.ProductCategory.objects.create(
            merchant=other_merchant,
            name='外部布料',
            product_prefix='OTH',
        )
        other_product = basic_models.Product.objects.create(
            merchant=other_merchant,
            category=other_category,
            name='外部面料',
            human_id='OTH-001',
            unit=basic_models.ProductUnitEnum.METER,
        )
        other_warehouse = basic_models.WareHouse.objects.create(
            merchant=other_merchant,
            name='外部仓',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
        )

        other_record, _, _ = stock_services.create_stock_change_record_with_details(
            merchant=other_merchant,
            created_by=None,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse_id=other_warehouse.id,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=202,
            products=[{'product': other_product.id, 'quantity': [Decimal('5.00')]}],
        )

        response = self.client.post(f'/api/v1/stock-change/{other_record.id}/finish/', format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertIn('无权操作', response.data['error'])


class OffsetStockChangeAPITestCase(TestCase):
    """测试库存红冲占位 API"""

    def setUp(self):
        self.client = APIClient()

        self.merchant = basic_models.Merchant.objects.create(
            name='红冲测试商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        self.category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='红冲面料',
            product_prefix='RED',
        )
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=self.category,
            name='红冲布料',
            human_id='RED-001',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='红冲仓库',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
        )
        self.user = User.objects.create_user(username='offset_user', password='pass123')
        self.employee = basic_models.Employee.objects.create(
            merchant=self.merchant,
            sys_user=self.user,
            name='红冲仓管员',
            mobile='13800138006',
            status=basic_models.EmployeeStatusEnum.ACTIVE,
        )
        self.client.force_authenticate(user=self.user)

        self.record, self.details, _ = stock_services.create_stock_change_record_with_details(
            merchant=self.merchant,
            created_by=self.user,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse_id=self.warehouse.id,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=303,
            products=[{'product': self.product.id, 'quantity': [Decimal('10.00')]}],
        )
        self.offset_url = f'/api/v1/stock-change/{self.record.id}/offset/'

    @unittest.expectedFailure
    def test_offset_stock_change_returns_not_implemented(self):
        payload = {
            'reason': '采购单误入库，需要红冲',
            'request_id': 'req-123',
        }
        response = self.client.post(self.offset_url, payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_501_NOT_IMPLEMENTED)
        self.assertEqual(response.data['error'], 'stock_offset_not_ready')
        self.assertEqual(response.data['record_id'], self.record.id)

    def test_offset_stock_change_requires_reason(self):
        response = self.client.post(self.offset_url, {}, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('reason', response.data)


class TransferStockChangeAPITestCase(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.merchant = basic_models.Merchant.objects.create(
            name='调拨商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        category = basic_models.ProductCategory.objects.create(
            merchant=self.merchant,
            name='调拨布料',
            product_prefix='TRN',
        )
        self.product = basic_models.Product.objects.create(
            merchant=self.merchant,
            category=category,
            name='调拨面料',
            human_id='TRN-001',
            unit=basic_models.ProductUnitEnum.METER,
        )
        self.from_warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='调出仓',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
        )
        self.to_warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='调入仓',
            mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
            type=basic_models.WarehouseTypeEnum.SCATTERED,
        )
        self.relaxed_warehouse = basic_models.WareHouse.objects.create(
            merchant=self.merchant,
            name='宽松仓',
            mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
        )
        self.user = User.objects.create_user(username='transfer_user', password='pass123')
        self.employee = basic_models.Employee.objects.create(
            merchant=self.merchant,
            sys_user=self.user,
            name='调拨员',
            mobile='13800138007',
            status=basic_models.EmployeeStatusEnum.ACTIVE,
        )
        self.client.force_authenticate(user=self.user)

    def test_transfer_success(self):
        payload = {
            'from_warehouse': self.from_warehouse.id,
            'to_warehouse': self.to_warehouse.id,
            'products': [
                {
                    'product_id': self.product.id,
                    'quantities': ['10.50', '5.25'],
                }
            ],
            'remarks': '整仓到散仓',
        }

        response = self.client.post('/api/v1/stock-change/transfer/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['status'], stock_models.TransferOrderStatusEnum.COMPLETED)
        self.assertEqual(response.data['from_warehouse'], self.from_warehouse.id)
        self.assertEqual(response.data['to_warehouse'], self.to_warehouse.id)
        self.assertEqual(len(response.data['items']), 1)
        self.assertEqual(
            Decimal(response.data['items'][0]['total_quantity']),
            Decimal('15.75'),
        )

    def test_transfer_requires_same_mode(self):
        payload = {
            'from_warehouse': self.from_warehouse.id,
            'to_warehouse': self.relaxed_warehouse.id,
            'products': [
                {
                    'product_id': self.product.id,
                    'quantities': ['8.00'],
                }
            ],
        }

        response = self.client.post('/api/v1/stock-change/transfer/', payload, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('模式一致', response.data['error'])
