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/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/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>/', 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( path(
'set-merchant-auto-complete-stock-change/', 'set-merchant-auto-complete-stock-change/',
stock_change_views.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 .list import ListStockChangesView, ListStockChangeDetailsView
from .detail import GetStockChangeView from .detail import GetStockChangeView
from .finish import FinishStockChangeView
from .settings import SetMerchantAutoCompleteView from .settings import SetMerchantAutoCompleteView
from .offset import OffsetStockChangeView
# 向后兼容:保持原有的函数式接口 # 向后兼容:保持原有的函数式接口
create_full_stock_change = CreateStockChangeView.as_view() 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_changes = ListStockChangesView.as_view()
list_stock_change_details = ListStockChangeDetailsView.as_view() list_stock_change_details = ListStockChangeDetailsView.as_view()
get_stock_change = GetStockChangeView.as_view() get_stock_change = GetStockChangeView.as_view()
finish_stock_change = FinishStockChangeView.as_view()
set_merchant_auto_complete_stock_change = SetMerchantAutoCompleteView.as_view() set_merchant_auto_complete_stock_change = SetMerchantAutoCompleteView.as_view()
offset_stock_change = OffsetStockChangeView.as_view()
__all__ = [ __all__ = [
'StockChangeViewMixin', 'StockChangeViewMixin',
@@ -31,12 +35,16 @@ __all__ = [
'ListStockChangesView', 'ListStockChangesView',
'ListStockChangeDetailsView', 'ListStockChangeDetailsView',
'GetStockChangeView', 'GetStockChangeView',
'FinishStockChangeView',
'SetMerchantAutoCompleteView', 'SetMerchantAutoCompleteView',
'OffsetStockChangeView',
'create_full_stock_change', 'create_full_stock_change',
'create_relaxed_stock_change', 'create_relaxed_stock_change',
'create_restrict_stock_change', 'create_restrict_stock_change',
'list_stock_changes', 'list_stock_changes',
'list_stock_change_details', 'list_stock_change_details',
'get_stock_change', 'get_stock_change',
'finish_stock_change',
'set_merchant_auto_complete_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'], stock_models.StockChangeTypeEnum.ADD)
self.assertEqual(result['record_type_display'], '入库') 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 ## Examples
### Example 1: Creating a Stock-in Record (Full Mode) ### 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 ## Error Handling
### Common Error Responses ### Common Error Responses

View File

@@ -381,6 +381,30 @@ class StockFlowService:
products=products, 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: def _get_and_validate_warehouse(self, warehouse_id: int) -> basic_models.WareHouse:
if not warehouse_id: if not warehouse_id:
raise ValueError('必须提供仓库ID') raise ValueError('必须提供仓库ID')

View File

@@ -1,12 +1,10 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase from django.test import TestCase
from django.utils import timezone
from decimal import Decimal from decimal import Decimal
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
import logging import logging
from basic_info.models import Product, WareHouse, Supplier, ProductUnitEnum, ProductCategory, Merchant, MerchantTypeEnum, WareHouseModeEnum from basic_info.models import Product, WareHouse, ProductUnitEnum, ProductCategory, Merchant, MerchantTypeEnum, WareHouseModeEnum
from business.models import PurchaseOrder
from . import models, services from . import models, services
@@ -49,14 +47,6 @@ class StockServicesTestCase(TestCase):
location="备用仓库地址" location="备用仓库地址"
) )
# 创建供应商
cls.supplier = Supplier.objects.create(
merchant=cls.merchant,
name="测试供应商",
contact="张三",
mobile="13800138000"
)
# 创建产品 # 创建产品
cls.product_fabric_a = Product.objects.create( cls.product_fabric_a = Product.objects.create(
merchant=cls.merchant, merchant=cls.merchant,
@@ -83,21 +73,14 @@ class StockServicesTestCase(TestCase):
def setUp(self): def setUp(self):
"""每个测试方法执行前的设置""" """每个测试方法执行前的设置"""
# 创建采购单 self.purchase_source_id = 1001
self.purchase_order = PurchaseOrder.objects.create(
merchant=self.merchant,
supplier=self.supplier,
order_date=timezone.now().date(),
total_amount=Decimal('1000.00'),
remarks="测试采购单"
)
# 创建库存变动记录(入库) # 创建库存变动记录(入库)
self.stock_change_in = models.StockChangeRecord.objects.create( self.stock_change_in = models.StockChangeRecord.objects.create(
merchant=self.merchant, merchant=self.merchant,
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
warehouse=self.warehouse_main, warehouse=self.warehouse_main,
is_finished=False is_finished=False
) )
@@ -392,7 +375,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=self.products_payload, products=self.products_payload,
) )
@@ -418,7 +401,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=self.products_payload, products=self.products_payload,
) )
@@ -435,7 +418,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=products, products=products,
) )
@@ -451,7 +434,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, 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')]}], products=[{'product': self.product_fabric_a.id, 'quantity': [Decimal('10.00')]}],
) )
@@ -468,7 +451,7 @@ class CreateStockChangeRecordWithDetailsTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, 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')]}], 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, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=products, products=products,
) )
@@ -629,7 +612,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=products, products=products,
) )
@@ -654,7 +637,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=products, products=products,
) )
@@ -676,7 +659,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=products, products=products,
) )
@@ -700,7 +683,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=products, products=products,
) )
@@ -724,7 +707,7 @@ class CreateStockChangeRecordRelaxedTestCase(StockServicesTestCase):
type=models.StockChangeTypeEnum.ADD, type=models.StockChangeTypeEnum.ADD,
warehouse_id=self.warehouse_main.id, warehouse_id=self.warehouse_main.id,
source_type=models.StockChangeSourceEnum.PURCHASE, source_type=models.StockChangeSourceEnum.PURCHASE,
source_id=self.purchase_order.id, source_id=self.purchase_source_id,
products=products, products=products,
) )