forked from erp-dev/erp
feat: added print_count api, added merchant setting model, added manual mode setting for purchase order
This commit is contained in:
@@ -1,21 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
|
||||
from basic_info import models as basic_info_models
|
||||
from stock import models as stock_models
|
||||
from stock.services import StockFlowService
|
||||
from basic_info.services import MerchantSettingService
|
||||
|
||||
from . import models
|
||||
from .tasks import create_purchase_order_stock_entries
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _normalize_order_date(value) -> date:
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
@@ -34,8 +38,8 @@ def create_purchase_order(
|
||||
merchant: basic_info_models.Merchant,
|
||||
supplier: basic_info_models.Supplier,
|
||||
order_date,
|
||||
total_amount,
|
||||
warehouse_id: int,
|
||||
warehouse: basic_info_models.WareHouse,
|
||||
operator: basic_info_models.Employee,
|
||||
items: List[Dict[str, Any]],
|
||||
remarks: str | None = '',
|
||||
created_by=None,
|
||||
@@ -47,40 +51,190 @@ def create_purchase_order(
|
||||
merchant: 采购单所属商户
|
||||
supplier: 供应商
|
||||
order_date: 订单日期 (date)
|
||||
total_amount: 总金额
|
||||
warehouse_id: 入库仓库ID
|
||||
items: 产品明细,格式示例:
|
||||
[
|
||||
{'product_id': 1, 'quantities': ['10.00', '5.00']},
|
||||
{'product_id': 2, 'quantities': ['3.50']},
|
||||
]
|
||||
该结构会被传递给 StockFlowService,需满足其模式要求。
|
||||
warehouse: 入库仓库实例
|
||||
operator: 经办人
|
||||
items: 产品明细,字段会根据仓库模式校验
|
||||
remarks: 备注
|
||||
created_by: 创建者用户(可选,用于 stock 记录中的 created_by)
|
||||
"""
|
||||
if not items:
|
||||
raise ValueError('items 不能为空')
|
||||
if not warehouse_id:
|
||||
raise ValueError('warehouse_id 不能为空')
|
||||
|
||||
normalized_date = _normalize_order_date(order_date)
|
||||
total_amount = Decimal(str(total_amount))
|
||||
purchase_items, stock_flow_items = _normalize_purchase_items(
|
||||
merchant=merchant,
|
||||
warehouse=warehouse,
|
||||
items=items,
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
purchase_order = models.PurchaseOrder.objects.create(
|
||||
merchant=merchant,
|
||||
supplier=supplier,
|
||||
order_date=normalized_date,
|
||||
total_amount=total_amount,
|
||||
purchase_date=normalized_date,
|
||||
operator=operator,
|
||||
warehouse=warehouse,
|
||||
remarks=remarks,
|
||||
)
|
||||
bulk_objects = [
|
||||
models.PurchaseOrderItem(
|
||||
purchase_order=purchase_order,
|
||||
product=item_data['product'],
|
||||
price=item_data['price'],
|
||||
color=item_data.get('color'),
|
||||
quantity=item_data['quantity'],
|
||||
unit=item_data['unit'],
|
||||
empty_diff_percent=item_data['empty_diff_percent'],
|
||||
quantity_of_rolls=item_data.get('quantity_of_rolls'),
|
||||
num_of_rolls=item_data['num_of_rolls'],
|
||||
batch_number=item_data.get('batch_number'),
|
||||
remarks=item_data.get('remarks'),
|
||||
)
|
||||
for item_data in purchase_items
|
||||
]
|
||||
models.PurchaseOrderItem.objects.bulk_create(bulk_objects)
|
||||
|
||||
created_by_id = getattr(created_by, 'id', None)
|
||||
create_purchase_order_stock_entries.delay(
|
||||
purchase_order_id=purchase_order.id,
|
||||
warehouse_id=warehouse_id,
|
||||
items=items,
|
||||
created_by_id=created_by_id,
|
||||
)
|
||||
if MerchantSettingService.get_setting(merchant, basic_info_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS).value is True:
|
||||
logger.info('自动创建出入库记录任务已开启,创建入库记录任务')
|
||||
create_purchase_order_stock_entries.delay(
|
||||
purchase_order_id=purchase_order.id,
|
||||
warehouse_id=warehouse.id,
|
||||
items=stock_flow_items,
|
||||
created_by_id=created_by_id,
|
||||
)
|
||||
return purchase_order
|
||||
|
||||
|
||||
def _normalize_purchase_items(
|
||||
*,
|
||||
merchant: basic_info_models.Merchant,
|
||||
warehouse: basic_info_models.WareHouse,
|
||||
items: List[Dict[str, Any]],
|
||||
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
||||
"""
|
||||
根据仓库模式校验采购明细,并返回
|
||||
- purchase_items: 用于创建 PurchaseOrderItem
|
||||
- stock_flow_items: 传递给 StockFlowService 的 items 结构
|
||||
"""
|
||||
purchase_items: List[Dict[str, Any]] = []
|
||||
stock_flow_items: List[Dict[str, Any]] = []
|
||||
|
||||
warehouse_mode = warehouse.mode
|
||||
|
||||
for index, raw_item in enumerate(items):
|
||||
product_id = raw_item.get('product_id')
|
||||
if not product_id:
|
||||
raise ValueError(f'items[{index}].product_id 不能为空')
|
||||
try:
|
||||
product = basic_info_models.Product.objects.get(id=product_id, merchant=merchant)
|
||||
except basic_info_models.Product.DoesNotExist as exc:
|
||||
raise ValueError(f'产品 {product_id} 不存在或不属于当前商户') from exc
|
||||
|
||||
price = _to_decimal(raw_item.get('price', '0'), f'items[{index}].price')
|
||||
empty_diff_percent = _to_decimal(raw_item.get('empty_diff_percent', '0'), f'items[{index}].empty_diff_percent')
|
||||
color = raw_item.get('color')
|
||||
batch_number = raw_item.get('batch_number')
|
||||
remarks = raw_item.get('remarks')
|
||||
unit = raw_item.get('unit') or product.get_unit_display() or '米'
|
||||
|
||||
if warehouse_mode == basic_info_models.WareHouseModeEnum.UNRESTRICTED:
|
||||
if 'numbers' in raw_item and raw_item['numbers']:
|
||||
raise ValueError(f'仓库为宽进模式,items[{index}] 不应提供 numbers')
|
||||
quantity = _to_positive_int(raw_item.get('quantity'), f'items[{index}].quantity')
|
||||
num_of_rolls = _to_positive_int(raw_item.get('num_of_rolls'), f'items[{index}].num_of_rolls')
|
||||
quantity_of_rolls = None
|
||||
stock_flow_items.append({
|
||||
'product_id': product.id,
|
||||
'value': str(quantity),
|
||||
'num_of_rolls': num_of_rolls,
|
||||
})
|
||||
else:
|
||||
numbers = raw_item.get('numbers')
|
||||
if not numbers or not isinstance(numbers, list):
|
||||
raise ValueError(f'仓库为严进模式,items[{index}] 需要提供 numbers 数组')
|
||||
normalized_numbers = [
|
||||
str(_to_positive_int(value, f'items[{index}].numbers[{pos}]'))
|
||||
for pos, value in enumerate(numbers)
|
||||
]
|
||||
num_of_rolls = len(normalized_numbers)
|
||||
quantity = sum(int(val) for val in normalized_numbers)
|
||||
quantity_of_rolls = ','.join(normalized_numbers)
|
||||
stock_flow_items.append({
|
||||
'product_id': product.id,
|
||||
'quantities': normalized_numbers,
|
||||
})
|
||||
|
||||
purchase_items.append({
|
||||
'product': product,
|
||||
'price': price,
|
||||
'color': color,
|
||||
'quantity': quantity,
|
||||
'unit': unit,
|
||||
'empty_diff_percent': empty_diff_percent,
|
||||
'quantity_of_rolls': quantity_of_rolls,
|
||||
'num_of_rolls': num_of_rolls,
|
||||
'batch_number': batch_number,
|
||||
'remarks': remarks,
|
||||
})
|
||||
|
||||
return purchase_items, stock_flow_items
|
||||
|
||||
|
||||
def create_purchase_order_stock_entries_sync(
|
||||
*,
|
||||
purchase_order_id: int,
|
||||
warehouse_id: int,
|
||||
items: List[Dict[str, Any]],
|
||||
created_by_id: int | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
根据采购单生成入库记录。
|
||||
"""
|
||||
try:
|
||||
purchase_order = models.PurchaseOrder.objects.select_related('merchant').get(id=purchase_order_id)
|
||||
except models.PurchaseOrder.DoesNotExist:
|
||||
logger.error('PurchaseOrder %s 不存在,无法创建入库单', purchase_order_id)
|
||||
return {'error': 'purchase_order_not_found', 'purchase_order_id': purchase_order_id}
|
||||
|
||||
merchant = purchase_order.merchant
|
||||
|
||||
created_by = None
|
||||
if created_by_id:
|
||||
UserModel = get_user_model()
|
||||
created_by = UserModel.objects.filter(id=created_by_id).first()
|
||||
|
||||
service = StockFlowService(merchant=merchant, created_by=created_by)
|
||||
record, details, created_count = service.stock_in(
|
||||
warehouse_id=warehouse_id,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=purchase_order.id,
|
||||
items=items,
|
||||
)
|
||||
|
||||
payload = {
|
||||
'purchase_order_id': purchase_order.id,
|
||||
'stock_change_record_id': getattr(record, 'id', None),
|
||||
'created_details_count': created_count,
|
||||
}
|
||||
logger.info('采购单 %s 入库任务完成: %s', purchase_order.id, payload)
|
||||
return payload
|
||||
|
||||
|
||||
def _to_decimal(value, field_name: str) -> Decimal:
|
||||
try:
|
||||
return Decimal(str(value))
|
||||
except (InvalidOperation, TypeError) as exc:
|
||||
raise ValueError(f'{field_name} 必须是合法数值') from exc
|
||||
|
||||
|
||||
def _to_positive_int(value, field_name: str) -> int:
|
||||
if value is None:
|
||||
raise ValueError(f'{field_name} 不能为空')
|
||||
decimal_value = _to_decimal(value, field_name)
|
||||
if decimal_value <= 0:
|
||||
raise ValueError(f'{field_name} 必须大于 0')
|
||||
if decimal_value != decimal_value.to_integral_value():
|
||||
raise ValueError(f'{field_name} 必须为整数')
|
||||
return int(decimal_value)
|
||||
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import logging
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from celery import shared_task
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from business import models as business_models
|
||||
from stock import models as stock_models
|
||||
from stock.services import StockFlowService
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from business import services as business_services
|
||||
|
||||
|
||||
@shared_task(bind=True)
|
||||
@@ -20,37 +13,13 @@ def create_purchase_order_stock_entries(
|
||||
warehouse_id: int,
|
||||
items: List[Dict[str, Any]],
|
||||
created_by_id: int | None = None,
|
||||
):
|
||||
"""
|
||||
为采购单创建入库记录。仅支持入库(StockFlowService.stock_in)。
|
||||
"""
|
||||
try:
|
||||
purchase_order = business_models.PurchaseOrder.objects.select_related('merchant').get(id=purchase_order_id)
|
||||
except business_models.PurchaseOrder.DoesNotExist:
|
||||
logger.error('PurchaseOrder %s 不存在,无法创建入库单', purchase_order_id)
|
||||
return {'error': 'purchase_order_not_found', 'purchase_order_id': purchase_order_id}
|
||||
|
||||
merchant = purchase_order.merchant
|
||||
|
||||
created_by = None
|
||||
if created_by_id:
|
||||
UserModel = get_user_model()
|
||||
created_by = UserModel.objects.filter(id=created_by_id).first()
|
||||
|
||||
service = StockFlowService(merchant=merchant, created_by=created_by)
|
||||
record, details, created_count = service.stock_in(
|
||||
) -> Dict[str, Any]:
|
||||
payload = business_services.create_purchase_order_stock_entries_sync(
|
||||
purchase_order_id=purchase_order_id,
|
||||
warehouse_id=warehouse_id,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=purchase_order.id,
|
||||
items=items,
|
||||
created_by_id=created_by_id,
|
||||
)
|
||||
|
||||
payload = {
|
||||
'task_id': self.request.id,
|
||||
'purchase_order_id': purchase_order.id,
|
||||
'stock_change_record_id': getattr(record, 'id', None),
|
||||
'created_details_count': created_count,
|
||||
}
|
||||
logger.info('采购单 %s 入库任务完成: %s', purchase_order.id, payload)
|
||||
payload['task_id'] = self.request.id
|
||||
return payload
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from decimal import Decimal
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
@@ -20,11 +19,16 @@ def create_basic_fixtures():
|
||||
merchant=merchant,
|
||||
name='测试供应商',
|
||||
)
|
||||
warehouse = basic_models.WareHouse.objects.create(
|
||||
warehouse_strict = basic_models.WareHouse.objects.create(
|
||||
merchant=merchant,
|
||||
name='主仓',
|
||||
name='严进仓',
|
||||
mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
|
||||
)
|
||||
warehouse_relaxed = basic_models.WareHouse.objects.create(
|
||||
merchant=merchant,
|
||||
name='宽进仓',
|
||||
mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
|
||||
)
|
||||
category = basic_models.ProductCategory.objects.create(
|
||||
merchant=merchant,
|
||||
name='面料',
|
||||
@@ -37,36 +41,56 @@ def create_basic_fixtures():
|
||||
human_id='FAB-001',
|
||||
unit=basic_models.ProductUnitEnum.METER,
|
||||
)
|
||||
return merchant, supplier, warehouse, product
|
||||
operator = basic_models.Employee.objects.create(
|
||||
merchant=merchant,
|
||||
name='经办人',
|
||||
status=basic_models.EmployeeStatusEnum.ACTIVE,
|
||||
)
|
||||
return merchant, supplier, warehouse_strict, warehouse_relaxed, product, operator
|
||||
|
||||
|
||||
class PurchaseOrderServiceTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant, self.supplier, self.warehouse, self.product = create_basic_fixtures()
|
||||
(
|
||||
self.merchant,
|
||||
self.supplier,
|
||||
self.warehouse_strict,
|
||||
self.warehouse_relaxed,
|
||||
self.product,
|
||||
self.operator,
|
||||
) = create_basic_fixtures()
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username='creator', password='pass123')
|
||||
self.items: List[Dict[str, Any]] = [
|
||||
{'product_id': self.product.id, 'quantities': ['10.00', '5.00']},
|
||||
self.strict_items: List[Dict[str, Any]] = [
|
||||
{'product_id': self.product.id, 'numbers': [10, 5], 'price': '12.5', 'unit': '米'}
|
||||
]
|
||||
self.relaxed_items: List[Dict[str, Any]] = [
|
||||
{'product_id': self.product.id, 'quantity': 120, 'num_of_rolls': 3, 'price': '10.0', 'unit': '米'}
|
||||
]
|
||||
|
||||
def test_create_purchase_order_triggers_task(self):
|
||||
def test_create_purchase_order_triggers_task_strict(self):
|
||||
with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay:
|
||||
purchase_order = services.create_purchase_order(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
total_amount=Decimal('100.00'),
|
||||
warehouse_id=self.warehouse.id,
|
||||
items=self.items,
|
||||
warehouse=self.warehouse_strict,
|
||||
operator=self.operator,
|
||||
items=self.strict_items,
|
||||
remarks='自动化测试',
|
||||
created_by=self.user,
|
||||
)
|
||||
|
||||
self.assertIsInstance(purchase_order, business_models.PurchaseOrder)
|
||||
self.assertEqual(purchase_order.warehouse, self.warehouse_strict)
|
||||
self.assertEqual(purchase_order.operator, self.operator)
|
||||
self.assertEqual(purchase_order.items.count(), 1)
|
||||
item = purchase_order.items.first()
|
||||
self.assertEqual(item.quantity_of_rolls, '10,5')
|
||||
mock_delay.assert_called_once_with(
|
||||
purchase_order_id=purchase_order.id,
|
||||
warehouse_id=self.warehouse.id,
|
||||
items=self.items,
|
||||
warehouse_id=self.warehouse_strict.id,
|
||||
items=[{'product_id': self.product.id, 'quantities': ['10', '5']}],
|
||||
created_by_id=self.user.id,
|
||||
)
|
||||
|
||||
@@ -76,11 +100,75 @@ class PurchaseOrderServiceTestCase(TestCase):
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
total_amount=Decimal('50.00'),
|
||||
warehouse_id=self.warehouse.id,
|
||||
warehouse=self.warehouse_strict,
|
||||
operator=self.operator,
|
||||
items=[],
|
||||
)
|
||||
|
||||
def test_create_purchase_order_relaxed_items(self):
|
||||
with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay:
|
||||
purchase_order = services.create_purchase_order(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
warehouse=self.warehouse_relaxed,
|
||||
operator=self.operator,
|
||||
items=self.relaxed_items,
|
||||
created_by=self.user,
|
||||
)
|
||||
|
||||
self.assertEqual(purchase_order.items.first().quantity_of_rolls, None)
|
||||
mock_delay.assert_called_once_with(
|
||||
purchase_order_id=purchase_order.id,
|
||||
warehouse_id=self.warehouse_relaxed.id,
|
||||
items=[{'product_id': self.product.id, 'value': '120', 'num_of_rolls': 3}],
|
||||
created_by_id=self.user.id,
|
||||
)
|
||||
|
||||
|
||||
class PurchaseOrderStockServiceTestCase(TestCase):
|
||||
def setUp(self):
|
||||
(
|
||||
self.merchant,
|
||||
self.supplier,
|
||||
self.warehouse_strict,
|
||||
self.warehouse_relaxed,
|
||||
self.product,
|
||||
self.operator,
|
||||
) = create_basic_fixtures()
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username='svc-user', password='pass123')
|
||||
self.purchase_order = business_models.PurchaseOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
purchase_date=timezone.now().date(),
|
||||
operator=self.operator,
|
||||
warehouse=self.warehouse_strict,
|
||||
)
|
||||
self.items = [{'product_id': self.product.id, 'quantities': ['8']}]
|
||||
|
||||
def test_service_calls_stock_flow_service(self):
|
||||
with patch('business.services.StockFlowService') as mock_flow_cls:
|
||||
mock_instance = mock_flow_cls.return_value
|
||||
mock_instance.stock_in.return_value = (MagicMock(id=321), [], 2)
|
||||
|
||||
payload = services.create_purchase_order_stock_entries_sync(
|
||||
purchase_order_id=self.purchase_order.id,
|
||||
warehouse_id=self.warehouse_strict.id,
|
||||
items=self.items,
|
||||
created_by_id=self.user.id,
|
||||
)
|
||||
|
||||
mock_flow_cls.assert_called_once_with(merchant=self.merchant, created_by=self.user)
|
||||
mock_instance.stock_in.assert_called_once_with(
|
||||
warehouse_id=self.warehouse_strict.id,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=self.purchase_order.id,
|
||||
items=[{'product_id': self.product.id, 'quantities': ['8']}],
|
||||
)
|
||||
self.assertEqual(payload['purchase_order_id'], self.purchase_order.id)
|
||||
self.assertEqual(payload['stock_change_record_id'], 321)
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_TASK_ALWAYS_EAGER=True,
|
||||
@@ -88,39 +176,30 @@ class PurchaseOrderServiceTestCase(TestCase):
|
||||
)
|
||||
class PurchaseOrderStockTaskTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant, self.supplier, self.warehouse, self.product = create_basic_fixtures()
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username='task-user', password='pass123')
|
||||
self.purchase_order = business_models.PurchaseOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
total_amount=Decimal('100.00'),
|
||||
)
|
||||
self.items = [{'product_id': self.product.id, 'quantities': ['8.00']}]
|
||||
self.purchase_order_id = 123
|
||||
self.warehouse_id = 456
|
||||
self.items = [{'product_id': 1, 'quantities': ['5']}]
|
||||
|
||||
def test_task_calls_stock_flow_service(self):
|
||||
with patch('business.tasks.StockFlowService') as mock_flow_cls:
|
||||
mock_instance = mock_flow_cls.return_value
|
||||
mock_instance.stock_in.return_value = (MagicMock(id=321), [], 2)
|
||||
def test_task_delegates_to_service(self):
|
||||
with patch('business.tasks.business_services.create_purchase_order_stock_entries_sync') as mock_sync:
|
||||
mock_sync.return_value = {'purchase_order_id': self.purchase_order_id}
|
||||
|
||||
async_result = tasks.create_purchase_order_stock_entries.delay(
|
||||
purchase_order_id=self.purchase_order.id,
|
||||
warehouse_id=self.warehouse.id,
|
||||
purchase_order_id=self.purchase_order_id,
|
||||
warehouse_id=self.warehouse_id,
|
||||
items=self.items,
|
||||
created_by_id=self.user.id,
|
||||
created_by_id=999,
|
||||
)
|
||||
payload = async_result.get(timeout=5)
|
||||
|
||||
mock_flow_cls.assert_called_once_with(merchant=self.merchant, created_by=self.user)
|
||||
mock_instance.stock_in.assert_called_once_with(
|
||||
warehouse_id=self.warehouse.id,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=self.purchase_order.id,
|
||||
mock_sync.assert_called_once_with(
|
||||
purchase_order_id=self.purchase_order_id,
|
||||
warehouse_id=self.warehouse_id,
|
||||
items=self.items,
|
||||
created_by_id=999,
|
||||
)
|
||||
self.assertEqual(payload['purchase_order_id'], self.purchase_order.id)
|
||||
self.assertEqual(payload['stock_change_record_id'], 321)
|
||||
self.assertEqual(payload['purchase_order_id'], self.purchase_order_id)
|
||||
self.assertIn('task_id', payload)
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
Reference in New Issue
Block a user