diff --git a/api_v1/urls.py b/api_v1/urls.py index b7b76f4..9e754fc 100644 --- a/api_v1/urls.py +++ b/api_v1/urls.py @@ -42,6 +42,8 @@ urlpatterns = [ path('stock-change/relaxed/', stock_change_views.create_relaxed_stock_change, name='create_relaxed_stock_change'), path('stock-change/restrict/', stock_change_views.create_restrict_stock_change, name='create_restrict_stock_change'), path('stock-change//', stock_change_views.get_stock_change, name='get_stock_change'), + path('stock-change//finish/', stock_change_views.finish_stock_change, name='finish_stock_change'), + path('stock-change//offset/', stock_change_views.offset_stock_change, name='offset_stock_change'), path( 'set-merchant-auto-complete-stock-change/', stock_change_views.set_merchant_auto_complete_stock_change, diff --git a/api_v1/views/stock_change_views/__init__.py b/api_v1/views/stock_change_views/__init__.py index ba5cfa2..5cefe01 100644 --- a/api_v1/views/stock_change_views/__init__.py +++ b/api_v1/views/stock_change_views/__init__.py @@ -12,7 +12,9 @@ from .create import ( ) from .list import ListStockChangesView, ListStockChangeDetailsView from .detail import GetStockChangeView +from .finish import FinishStockChangeView from .settings import SetMerchantAutoCompleteView +from .offset import OffsetStockChangeView # 向后兼容:保持原有的函数式接口 create_full_stock_change = CreateStockChangeView.as_view() @@ -21,7 +23,9 @@ create_restrict_stock_change = CreateStockChangeRestrictView.as_view() list_stock_changes = ListStockChangesView.as_view() list_stock_change_details = ListStockChangeDetailsView.as_view() get_stock_change = GetStockChangeView.as_view() +finish_stock_change = FinishStockChangeView.as_view() set_merchant_auto_complete_stock_change = SetMerchantAutoCompleteView.as_view() +offset_stock_change = OffsetStockChangeView.as_view() __all__ = [ 'StockChangeViewMixin', @@ -31,12 +35,16 @@ __all__ = [ 'ListStockChangesView', 'ListStockChangeDetailsView', 'GetStockChangeView', + 'FinishStockChangeView', 'SetMerchantAutoCompleteView', + 'OffsetStockChangeView', 'create_full_stock_change', 'create_relaxed_stock_change', 'create_restrict_stock_change', 'list_stock_changes', 'list_stock_change_details', 'get_stock_change', + 'finish_stock_change', 'set_merchant_auto_complete_stock_change', + 'offset_stock_change', ] diff --git a/api_v1/views/stock_change_views/finish.py b/api_v1/views/stock_change_views/finish.py new file mode 100644 index 0000000..911ee96 --- /dev/null +++ b/api_v1/views/stock_change_views/finish.py @@ -0,0 +1,84 @@ +""" +标记库存变动记录完成的视图 +""" + +from rest_framework import status, views +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +import logging + +from drf_spectacular.utils import extend_schema +from stock import models as stock_models, services as stock_services + +from .mixins import StockChangeViewMixin + +logger = logging.getLogger(__name__) + + +class FinishStockChangeView(StockChangeViewMixin, views.APIView): + """标记库存变动记录为完成""" + + permission_classes = [IsAuthenticated] + + @extend_schema( + tags=['完成出入库'], + responses={200: dict}, + summary='手动完成库存变动记录', + description='根据出入库单ID手动触发库存扣减、快照写入,并标记为完成' + ) + def post(self, request, record_id: int): + if not self.check_employee_permission(request): + return self.permission_error_response('无权限操作') + + merchant_id = self.get_merchant_id(request) + if merchant_id is None: + return self.permission_error_response('无权限操作') + + try: + record = stock_models.StockChangeRecord.objects.select_related( + 'warehouse', 'created_by' + ).get(id=record_id) + except stock_models.StockChangeRecord.DoesNotExist: + return self.not_found_response(f'库存变动记录ID {record_id} 不存在') + + if record.merchant_id != merchant_id: + return self.permission_error_response('无权操作该库存变动记录') + + if not self.validate_warehouse_visibility(record.warehouse_id, request): + return self.permission_error_response('该库存变动记录的仓库对当前用户不可见') + + try: + stock_services.make_stock_change_completed(record) + record.refresh_from_db() + + message = ( + '库存变动记录已标记为完成' + if record.is_finished else + '库存变动记录仍未完成,请检查明细' + ) + status_flag = 'success' if record.is_finished else 'pending' + + logger.info( + '用户 %s 手动完成库存变动记录 %s(状态:%s)', + request.user.username, + record.id, + status_flag, + ) + + return Response({ + 'status': status_flag, + 'message': message, + 'stock_change_record': self.build_record_data(record), + }, status=status.HTTP_200_OK) + except Exception as exc: + logger.error( + '手动完成库存变动记录 %s 失败: %s', + record_id, + str(exc), + exc_info=True, + ) + return Response({ + 'error': '标记库存变动记录失败', + 'message': str(exc), + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + diff --git a/api_v1/views/stock_change_views/offset.py b/api_v1/views/stock_change_views/offset.py new file mode 100644 index 0000000..5b25f0a --- /dev/null +++ b/api_v1/views/stock_change_views/offset.py @@ -0,0 +1,104 @@ +""" +库存红冲(对冲)占位视图 +""" + +from rest_framework import status, views, serializers +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from drf_spectacular.utils import extend_schema, OpenApiResponse + +from stock import models as stock_models, services as stock_services + +from .mixins import StockChangeViewMixin + + +class StockChangeOffsetSerializer(serializers.Serializer): + """红冲请求体验证""" + + reason = serializers.CharField(max_length=255, allow_blank=False) + items = serializers.ListField( + child=serializers.DictField(), + required=False, + allow_empty=True, + ) + request_id = serializers.CharField(required=False, allow_blank=True) + extra_meta = serializers.JSONField(required=False) + + +class OffsetStockChangeView(StockChangeViewMixin, views.APIView): + """针对既有出入库记录发起红冲的占位接口""" + + permission_classes = [IsAuthenticated] + + @extend_schema( + tags=['库存红冲'], + summary='发起库存红冲(占位)', + description=( + '对指定库存变动记录创建红冲请求。当前仅提供占位实现,' + '会返回 501 Not Implemented,待库存红冲逻辑完善后启用。' + ), + request=StockChangeOffsetSerializer, + responses={ + 201: OpenApiResponse( + description='红冲成功(未来实现后返回)' + ), + 400: OpenApiResponse(description='参数或业务校验失败'), + 403: OpenApiResponse(description='无权访问对应出入库记录'), + 404: OpenApiResponse(description='库存变动记录不存在'), + 501: OpenApiResponse(description='功能占位,尚未实现'), + }, + ) + def post(self, request, record_id: int): + if not self.check_employee_permission(request): + return self.permission_error_response('无权限访问') + + merchant = request.user.employee.merchant + + try: + stock_change_record = stock_models.StockChangeRecord.objects.select_related( + 'merchant', 'warehouse' + ).get(id=record_id) + except stock_models.StockChangeRecord.DoesNotExist: + return self.not_found_response(f'库存变动记录ID {record_id} 不存在') + + if stock_change_record.merchant_id != merchant.id: + return self.permission_error_response('无权访问该库存变动记录') + + if not self.validate_warehouse_visibility(stock_change_record.warehouse_id, request): + return self.permission_error_response('该库存变动记录的仓库对当前用户不可见') + + serializer = StockChangeOffsetSerializer(data=request.data or {}) + serializer.is_valid(raise_exception=True) + payload = serializer.validated_data + + service = stock_services.StockFlowService(merchant=merchant, created_by=request.user) + + try: + offset_record, details, detail_count = service.offset_stock_change( + source_record_id=stock_change_record.id, + reason=payload['reason'], + items=payload.get('items'), + request_id=payload.get('request_id'), + extra_meta=payload.get('extra_meta'), + ) + except NotImplementedError as exc: + return Response( + { + 'error': 'stock_offset_not_ready', + 'message': str(exc), + 'record_id': stock_change_record.id, + }, + status=status.HTTP_501_NOT_IMPLEMENTED, + ) + except ValueError as exc: + return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST) + + response_data = { + 'status': 'success', + 'message': '红冲记录已创建', + 'stock_change_record': self.build_record_data(offset_record), + 'details': [self.build_detail_data(detail) for detail in details], + 'created_details_count': detail_count, + } + return Response(response_data, status=status.HTTP_201_CREATED) + diff --git a/api_v1/views/stock_change_views/test_stock_change_api.py b/api_v1/views/stock_change_views/test_stock_change_api.py index aaff3d8..db59e3d 100644 --- a/api_v1/views/stock_change_views/test_stock_change_api.py +++ b/api_v1/views/stock_change_views/test_stock_change_api.py @@ -669,3 +669,188 @@ class ListStockChangeDetailsAPITestCase(TestCase): 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/' + + 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) diff --git a/docs/20251125-stock.md b/docs/20251128-stock.md similarity index 72% rename from docs/20251125-stock.md rename to docs/20251128-stock.md index e1a552f..359387e 100644 --- a/docs/20251125-stock.md +++ b/docs/20251128-stock.md @@ -359,6 +359,133 @@ Warehouses can operate in different modes: } ``` +### 4. Stock Snapshot List + +- **URL**: `GET /api/v1/stock-snapshots/` +- **Description**: Read-only list of `StockSnapshot` entries. Supports `limit/offset` pagination,搜索与过滤。 +- **Query Parameters**: + | 参数 | 说明 | + |------|------| + | `product` / `product_name` / `product_code` | 指定产品或模糊搜索 | + | `warehouse` / `warehouse_name` | 指定仓库或模糊搜索 | + | `stock_change_record` | 关联的出入库记录 ID | + | `unit` | 单位过滤 | + | `cancelled` | `true/false`,是否被标记取消 | + | `has_offset` | `true/false`,筛选是否存在冲抵记录 | + | `date_from` / `date_to` | 创建时间范围(`YYYY-MM-DD`) | + | `search` | 针对产品名、产品编码、仓库名的全文搜索 | + | `ordering` | 排序字段,默认 `-created_at` | + +**Response 示例**: +```json +{ + "count": 2, + "results": [ + { + "id": 901, + "product": 15, + "product_name": "纯棉印花布", + "warehouse": 3, + "warehouse_name": "严进仓", + "delta": "120.00", + "quantity_before": "80.00", + "quantity_after": "200.00", + "stock_change_record": 456, + "stock_change_record_type": "入库", + "stock_change_record_source": "采购", + "unit": 1, + "unit_display": "米", + "num_of_rolls": 3, + "offset_to": null, + "cancelled": false, + "created_at": "2025-11-25T12:05:00Z" + } + ] +} +``` + +### 5. Merchant Auto Complete Setting + +- **URL**: `POST /api/v1/set-merchant-auto-complete-stock-change/` +- **Description**: 为当前商户开启 / 关闭“创建出入库记录后自动完成”功能。开启后,`create_*` 接口在事务内会立即调用 `stock.services.make_stock_change_completed`,自动写入库存与快照。 +- **Request**: +```json +{ + "auto_complete": true +} +``` +- **Response**: +```json +{ + "status": "success" +} +``` + +只有具备员工身份的登录用户才可调用该接口。 + +### 6. Stock Change Completion Behavior + +- **自动完成**:当商户开启 `auto_complete_stock_change` 时,任意模式的创建接口都会立即调用 `make_stock_change_completed`,更新 `Inventory` 并生成 `StockSnapshot`。 +- **手动完成**:当需要补处理或未开启自动完成时,可调用 `POST /api/v1/stock-change//finish/` 手动触发 `make_stock_change_completed`,也可直接在后台脚本里调用服务函数。 +- **状态字段**:`StockChangeRecord.is_finished` / `finished_at` 会在完成时标记,用于列表和详情接口判断记录是否已经生效。 + +### 7. Finish Stock Change Record (Manual) + +- **URL**: `POST /api/v1/stock-change//finish/` +- **Description**: 手动触发指定出入库记录的库存扣减与快照写入,适用于未开启自动完成或需要补处理的场景。仅当前商户下且仓库可见的员工可调用。 +- **Request**: 无需请求体,只需保证 URL 中的 `` 为目标 `StockChangeRecord` 的 ID。 +- **Response**: +```json +{ + "status": "success", + "message": "库存变动记录已标记为完成", + "stock_change_record": { + "id": 17, + "type": 1, + "warehouse": 1, + "warehouse_name": "严谨仓", + "source_type": 1, + "source_type_display": "采购", + "source_id": 123, + "is_finished": true, + "finished_at": "2025-11-25T12:30:00Z", + "created_at": "2025-11-25T11:50:00Z", + "created_by": "stock_admin", + "remarks": null + } +} +``` + +### 8. Stock Change Offset (Red Flush Placeholder) + +- **URL**: `POST /api/v1/stock-change//offset/` +- **Description**: 面向未来的库存红冲入口。当前仅提供占位实现,用于锁定最终接口形态;实际红冲逻辑尚未上线,因此调用会返回 `501 Not Implemented`,方便前端或上游业务在流程编排中预留节点。 +- **Request**: +```json +{ + "reason": "审批错误,需要冲销", + "items": [ + { + "product_id": 1, + "quantities": ["50.00"] + } + ], + "request_id": "po-123-offset", + "extra_meta": { + "source": "purchase_order", + "operator": "warehouse_admin" + } +} +``` +- **Response(当前占位行为)**: +```json +{ + "error": "stock_offset_not_ready", + "message": "库存红冲功能尚未实现", + "record_id": 25 +} +``` + ## Examples ### Example 1: Creating a Stock-in Record (Full Mode) @@ -419,6 +546,12 @@ curl -X POST http://example.com/api/v1/stock-change/restrict/ \ }' ``` +### Example 4: Finish a Stock Change Record Manually +```bash +curl -X POST http://example.com/api/v1/stock-change/17/finish/ \ + -H "Authorization: Bearer your_token" +``` + ## Error Handling ### Common Error Responses diff --git a/stock/services.py b/stock/services.py index 5e7b49d..7fa8451 100644 --- a/stock/services.py +++ b/stock/services.py @@ -381,6 +381,30 @@ class StockFlowService: products=products, ) + def offset_stock_change( + self, + *, + source_record_id: int, + reason: str, + items: List[Dict[str, Any]] | None = None, + request_id: str | None = None, + extra_meta: Dict[str, Any] | None = None, + ) -> Tuple[models.StockChangeRecord, List[models.StockChangeDetail], int]: + """ + 创建针对既有库存变动记录的红冲(对冲)占位接口。 + + Args: + source_record_id: 需要被红冲的库存变动记录 ID。 + reason: 红冲原因,必须明确记录以便审计。 + items: 可选的部分红冲明细(支持宽进/严进等结构);为空时表示全量对冲。 + request_id: 幂等键;同一 request_id 的请求应视为一次操作。 + extra_meta: 额外的上下文信息(触发来源、操作者备注等)。 + + Returns: + 新生成的反向 `StockChangeRecord`、其明细列表以及明细数量。 + """ + raise NotImplementedError('库存红冲功能尚未实现') + def _get_and_validate_warehouse(self, warehouse_id: int) -> basic_models.WareHouse: if not warehouse_id: raise ValueError('必须提供仓库ID') diff --git a/stock/tests.py b/stock/tests.py index 8794aa8..a1da2cb 100644 --- a/stock/tests.py +++ b/stock/tests.py @@ -1,12 +1,10 @@ from django.core.exceptions import ValidationError from django.test import TestCase -from django.utils import timezone from decimal import Decimal from unittest.mock import patch, MagicMock import logging -from basic_info.models import Product, WareHouse, Supplier, ProductUnitEnum, ProductCategory, Merchant, MerchantTypeEnum, WareHouseModeEnum -from business.models import PurchaseOrder +from basic_info.models import Product, WareHouse, ProductUnitEnum, ProductCategory, Merchant, MerchantTypeEnum, WareHouseModeEnum from . import models, services @@ -49,14 +47,6 @@ class StockServicesTestCase(TestCase): location="备用仓库地址" ) - # 创建供应商 - cls.supplier = Supplier.objects.create( - merchant=cls.merchant, - name="测试供应商", - contact="张三", - mobile="13800138000" - ) - # 创建产品 cls.product_fabric_a = Product.objects.create( merchant=cls.merchant, @@ -83,21 +73,14 @@ class StockServicesTestCase(TestCase): def setUp(self): """每个测试方法执行前的设置""" - # 创建采购单 - self.purchase_order = PurchaseOrder.objects.create( - merchant=self.merchant, - supplier=self.supplier, - order_date=timezone.now().date(), - total_amount=Decimal('1000.00'), - remarks="测试采购单" - ) + self.purchase_source_id = 1001 # 创建库存变动记录(入库) self.stock_change_in = models.StockChangeRecord.objects.create( merchant=self.merchant, type=models.StockChangeTypeEnum.ADD, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, warehouse=self.warehouse_main, is_finished=False ) @@ -392,7 +375,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=self.products_payload, ) @@ -418,7 +401,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=self.products_payload, ) @@ -435,7 +418,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=products, ) @@ -451,7 +434,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=[{'product': self.product_fabric_a.id, 'quantity': [Decimal('10.00')]}], ) @@ -468,7 +451,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=[{'product': self.product_fabric_a.id, 'quantity': [Decimal('5.00'), Decimal('6.00')]}], ) @@ -604,7 +587,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=products, ) @@ -629,7 +612,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=products, ) @@ -654,7 +637,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=products, ) @@ -676,7 +659,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=products, ) @@ -700,7 +683,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=products, ) @@ -724,7 +707,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase): type=models.StockChangeTypeEnum.ADD, warehouse_id=self.warehouse_main.id, source_type=models.StockChangeSourceEnum.PURCHASE, - source_id=self.purchase_order.id, + source_id=self.purchase_source_id, products=products, )