1
0
forked from erp-dev/erp

fear: stock reverse placeholder

This commit is contained in:
2025-11-30 13:40:47 +08:00
parent 4b3cd8eb75
commit f6f0ec2ae9
8 changed files with 554 additions and 31 deletions

View File

@@ -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/<int:record_id>/', stock_change_views.get_stock_change, name='get_stock_change'),
path('stock-change/<int:record_id>/finish/', stock_change_views.finish_stock_change, name='finish_stock_change'),
path('stock-change/<int:record_id>/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,

View File

@@ -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',
]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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/<id>/finish/` 手动触发 `make_stock_change_completed`,也可直接在后台脚本里调用服务函数。
- **状态字段**`StockChangeRecord.is_finished` / `finished_at` 会在完成时标记,用于列表和详情接口判断记录是否已经生效。
### 7. Finish Stock Change Record (Manual)
- **URL**: `POST /api/v1/stock-change/<id>/finish/`
- **Description**: 手动触发指定出入库记录的库存扣减与快照写入,适用于未开启自动完成或需要补处理的场景。仅当前商户下且仓库可见的员工可调用。
- **Request**: 无需请求体,只需保证 URL 中的 `<id>` 为目标 `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/<id>/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

View File

@@ -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')

View File

@@ -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,
)