forked from erp-dev/erp
feat: allocation record for pre sales order
This commit is contained in:
140
business/allocation_services.py
Normal file
140
business/allocation_services.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Iterable, List
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from stock import models as stock_models
|
||||
from . import models
|
||||
|
||||
|
||||
def _normalize_stock_ids(stock_ids: Iterable[int | str]) -> List[int]:
|
||||
if not stock_ids:
|
||||
raise ValueError('stock_ids 不能为空')
|
||||
normalized: List[int] = []
|
||||
for raw in stock_ids:
|
||||
try:
|
||||
value = int(raw)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError('stock_ids 必须为整数数组') from exc
|
||||
if value <= 0:
|
||||
raise ValueError('stock_ids 必须为正整数数组')
|
||||
normalized.append(value)
|
||||
return normalized
|
||||
|
||||
|
||||
def _serialize_stock_ids(stock_ids: Iterable[int]) -> str:
|
||||
return ','.join(str(value) for value in stock_ids)
|
||||
|
||||
|
||||
def validate_stock_details(*, merchant, stock_ids: Iterable[int | str]) -> List[stock_models.StockChangeDetail]:
|
||||
normalized_ids = _normalize_stock_ids(stock_ids)
|
||||
|
||||
details_by_id = stock_models.StockChangeDetail.objects.filter(
|
||||
id__in=normalized_ids,
|
||||
merchant=merchant,
|
||||
).in_bulk(field_name='id')
|
||||
|
||||
missing_ids = [value for value in normalized_ids if value not in details_by_id]
|
||||
if missing_ids:
|
||||
raise ValueError(f'库存明细不存在: {missing_ids}')
|
||||
|
||||
consumed_ids = [
|
||||
detail.id
|
||||
for detail in details_by_id.values()
|
||||
if getattr(detail, 'is_consumed', False)
|
||||
]
|
||||
if consumed_ids:
|
||||
raise ValueError(f'库存明细已被消费: {consumed_ids}')
|
||||
|
||||
frozen_ids = list(
|
||||
stock_models.StockFreeze.objects.filter(
|
||||
stock_detail_id__in=normalized_ids,
|
||||
status=stock_models.StockFreezeStatusEnum.FROZEN,
|
||||
).values_list('stock_detail_id', flat=True)
|
||||
)
|
||||
if frozen_ids:
|
||||
raise ValueError(f'库存明细已被冻结: {list(frozen_ids)}')
|
||||
|
||||
return [details_by_id[value] for value in normalized_ids]
|
||||
|
||||
|
||||
def create_allocation_record(
|
||||
*,
|
||||
item: models.PreSalesOrderItem,
|
||||
stock_ids: Iterable[int | str],
|
||||
quantity: Decimal | int | str,
|
||||
unit: str | None,
|
||||
scanned_by,
|
||||
remarks: str | None = None,
|
||||
) -> models.AllocationRecord:
|
||||
if quantity is None or str(quantity) == '':
|
||||
raise ValueError('quantity 不能为空')
|
||||
try:
|
||||
quantity_decimal = Decimal(str(quantity))
|
||||
except Exception as exc:
|
||||
raise ValueError('quantity 格式不正确') from exc
|
||||
if quantity_decimal <= 0:
|
||||
raise ValueError('quantity 必须大于 0')
|
||||
|
||||
if not unit:
|
||||
unit = getattr(item, 'unit', None)
|
||||
unit = (unit or '').strip()
|
||||
if not unit:
|
||||
raise ValueError('unit 不能为空')
|
||||
|
||||
order = getattr(item, 'pre_sales_order', None)
|
||||
if order is None:
|
||||
raise ValueError('明细缺少所属预销售单')
|
||||
|
||||
normalized_ids = _normalize_stock_ids(stock_ids)
|
||||
details = validate_stock_details(merchant=order.merchant, stock_ids=normalized_ids)
|
||||
|
||||
item_product_id = getattr(item, 'product_id', None)
|
||||
if item_product_id:
|
||||
mismatch_ids = list(
|
||||
stock_models.StockChangeDetail.objects.filter(
|
||||
id__in=normalized_ids,
|
||||
).exclude(product_id=item_product_id).values_list('id', flat=True)
|
||||
)
|
||||
if mismatch_ids:
|
||||
raise ValueError(f'库存明细产品不匹配: {mismatch_ids}')
|
||||
|
||||
with transaction.atomic():
|
||||
record = models.AllocationRecord.objects.create(
|
||||
pre_sales_order_item=item,
|
||||
stock_ids=_serialize_stock_ids(normalized_ids),
|
||||
quantity=quantity_decimal,
|
||||
unit=unit,
|
||||
status=models.AllocationRecordStatusEnum.ACTIVE,
|
||||
scanned_by=scanned_by,
|
||||
remarks=remarks,
|
||||
)
|
||||
|
||||
stock_models.StockFreeze.objects.bulk_create(
|
||||
[
|
||||
stock_models.StockFreeze(
|
||||
merchant=order.merchant,
|
||||
product=detail.product,
|
||||
warehouse=order.warehouse,
|
||||
stock_detail=detail,
|
||||
quantity=detail.quantity,
|
||||
unit=detail.unit,
|
||||
status=stock_models.StockFreezeStatusEnum.FROZEN,
|
||||
frozen_by=getattr(scanned_by, 'sys_user', None),
|
||||
frozen_with=record.id,
|
||||
)
|
||||
for detail in details
|
||||
]
|
||||
)
|
||||
|
||||
return record
|
||||
|
||||
|
||||
def cancel_allocation_record(*, record: models.AllocationRecord) -> models.AllocationRecord:
|
||||
if record.status == models.AllocationRecordStatusEnum.CANCELLED:
|
||||
return record
|
||||
record.status = models.AllocationRecordStatusEnum.CANCELLED
|
||||
record.save(update_fields=['status', 'updated_at'])
|
||||
return record
|
||||
@@ -1,14 +1,24 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def on_pre_sales_order_created(sender, **kwargs):
|
||||
"""Handle PreSalesOrder created domain event.
|
||||
|
||||
For now: log only (no side effects).
|
||||
- 在事务提交后发送企业微信机器人通知(markdown)
|
||||
- 测试环境默认跳过,避免单测出网/刷屏
|
||||
"""
|
||||
|
||||
if not getattr(settings, "PRE_SALES_ORDER_CREATED_WECOM_NOTIFY_ENABLED", True):
|
||||
return
|
||||
if getattr(settings, "TESTING", False):
|
||||
return
|
||||
|
||||
order = kwargs.get('instance')
|
||||
created_by = kwargs.get('created_by')
|
||||
operator = kwargs.get('operator')
|
||||
@@ -18,17 +28,73 @@ def on_pre_sales_order_created(sender, **kwargs):
|
||||
logger.warning('[business.handlers] pre_sales_order_created 缺少 instance,已跳过')
|
||||
return
|
||||
|
||||
created_by_label = getattr(created_by, 'username', None) if created_by else None
|
||||
operator_label = getattr(operator, 'name', None) if operator else None
|
||||
try:
|
||||
from .pre_order_services import render_pre_sales_order_created_markdown
|
||||
|
||||
logger.info(
|
||||
'[business.handlers] pre_sales_order_created: id=%s human_id=%s merchant_id=%s customer_id=%s '
|
||||
'created_by=%s operator=%s items_count=%s',
|
||||
getattr(order, 'id', None),
|
||||
getattr(order, 'human_id', None),
|
||||
getattr(order, 'merchant_id', None),
|
||||
getattr(order, 'customer_id', None),
|
||||
created_by_label or '-',
|
||||
operator_label or '-',
|
||||
items_count if items_count is not None else '-',
|
||||
)
|
||||
created_at_dt = getattr(order, 'created_at', None)
|
||||
created_at = (
|
||||
timezone.localtime(created_at_dt).strftime('%Y-%m-%d %H:%M:%S')
|
||||
if created_at_dt is not None
|
||||
else timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S')
|
||||
)
|
||||
|
||||
operator_label = getattr(operator, 'name', None) if operator else None
|
||||
|
||||
emp = getattr(created_by, 'employee', None) if created_by else None
|
||||
created_by_employee_name = getattr(emp, 'name', None) if emp is not None else None
|
||||
created_by_username = getattr(created_by, 'username', None) if created_by else None
|
||||
sender_label = operator_label or created_by_employee_name or created_by_username or '系统自动发送'
|
||||
|
||||
try:
|
||||
customer_name = getattr(getattr(order, 'customer', None), 'name', None)
|
||||
except Exception:
|
||||
customer_name = None
|
||||
|
||||
try:
|
||||
warehouse_name = getattr(getattr(order, 'warehouse', None), 'name', None)
|
||||
except Exception:
|
||||
warehouse_name = None
|
||||
|
||||
try:
|
||||
kind_label = getattr(order, 'get_kind_display', lambda: None)() or None
|
||||
except Exception:
|
||||
kind_label = None
|
||||
|
||||
resolved_items_count = items_count
|
||||
if resolved_items_count is None:
|
||||
try:
|
||||
resolved_items_count = int(getattr(order, 'items', []).count())
|
||||
except Exception:
|
||||
resolved_items_count = 0
|
||||
|
||||
message = render_pre_sales_order_created_markdown(
|
||||
pre_sales_order_id=str(getattr(order, 'id', '-') or '-'),
|
||||
human_id=str(getattr(order, 'human_id', '-') or '-'),
|
||||
created_at=str(created_at),
|
||||
sender_label=str(sender_label),
|
||||
customer_name=str(customer_name or '-'),
|
||||
warehouse_name=str(warehouse_name or '-'),
|
||||
kind=str(kind_label or '-'),
|
||||
items_count=int(resolved_items_count or 0),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('[business.handlers] 渲染企业微信消息失败,跳过通知')
|
||||
return
|
||||
|
||||
def _send_wecom():
|
||||
try:
|
||||
from api_v1.utils.wecom_webhook import send_wecom_webhook_message
|
||||
|
||||
resp = send_wecom_webhook_message(content=message, msgtype='markdown')
|
||||
if not resp.ok:
|
||||
logger.warning(
|
||||
'[business.handlers] WeCom webhook 返回失败:errcode=%s, errmsg=%s, raw=%s',
|
||||
resp.errcode,
|
||||
resp.errmsg,
|
||||
resp.raw,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('[business.handlers] 发送 WeCom webhook 失败(已忽略,不影响主流程)')
|
||||
|
||||
# 在事务提交后再发送,避免事务回滚但通知已发出
|
||||
transaction.on_commit(_send_wecom)
|
||||
|
||||
33
business/migrations/0025_allocationrecord.py
Normal file
33
business/migrations/0025_allocationrecord.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('business', '0024_prepurchaseorder_operator_presalesorder_operator_and_more'),
|
||||
('basic_info', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AllocationRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('stock_ids', models.TextField(blank=True, null=True, verbose_name='库存明细ID列表')),
|
||||
('quantity', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='配货数量')),
|
||||
('unit', models.CharField(max_length=50, verbose_name='单位')),
|
||||
('status', models.IntegerField(choices=[(1, '有效'), (2, '已撤销')], default=1, verbose_name='状态')),
|
||||
('scanned_at', models.DateTimeField(auto_now_add=True, verbose_name='扫码时间')),
|
||||
('remarks', models.TextField(blank=True, null=True, verbose_name='备注')),
|
||||
('pre_sales_order_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='allocation_records', to='business.presalesorderitem', verbose_name='预销售单明细')),
|
||||
('scanned_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='allocation_records', to='basic_info.employee', verbose_name='扫码人员')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '配货记录',
|
||||
'verbose_name_plural': '配货记录',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -226,6 +226,13 @@ class SalesOrderStatusEnum(models.IntegerChoices):
|
||||
CANCELLED = 3, '作废'
|
||||
|
||||
|
||||
class AllocationRecordStatusEnum(models.IntegerChoices):
|
||||
"""配货记录状态"""
|
||||
|
||||
ACTIVE = 1, '有效'
|
||||
CANCELLED = 2, '已撤销'
|
||||
|
||||
|
||||
class SalesOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
|
||||
"""销售单模型"""
|
||||
|
||||
@@ -516,6 +523,57 @@ class PreSalesOrderItem(ModelBase):
|
||||
verbose_name_plural = '预销售单明细'
|
||||
|
||||
|
||||
class AllocationRecord(ModelBase):
|
||||
"""配货记录(执行记录)"""
|
||||
|
||||
id = models.BigAutoField(primary_key=True)
|
||||
pre_sales_order_item = models.ForeignKey(
|
||||
PreSalesOrderItem,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='allocation_records',
|
||||
verbose_name='预销售单明细',
|
||||
)
|
||||
stock_ids = models.TextField(blank=True, null=True, verbose_name='库存明细ID列表')
|
||||
quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='配货数量')
|
||||
unit = models.CharField(max_length=50, verbose_name='单位')
|
||||
status = models.IntegerField(
|
||||
choices=AllocationRecordStatusEnum.choices,
|
||||
default=AllocationRecordStatusEnum.ACTIVE,
|
||||
verbose_name='状态',
|
||||
)
|
||||
scanned_by = models.ForeignKey(
|
||||
basic_info_models.Employee,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='allocation_records',
|
||||
verbose_name='扫码人员',
|
||||
)
|
||||
scanned_at = models.DateTimeField(auto_now_add=True, verbose_name='扫码时间')
|
||||
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
|
||||
|
||||
def __str__(self):
|
||||
return f'配货记录 {self.id} - 明细 {self.pre_sales_order_item_id}'
|
||||
|
||||
@property
|
||||
def stock_id_list(self) -> List[int]:
|
||||
raw = (self.stock_ids or '').strip()
|
||||
if not raw:
|
||||
return []
|
||||
result: List[int] = []
|
||||
for value in raw.split(','):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
continue
|
||||
try:
|
||||
result.append(int(value))
|
||||
except ValueError:
|
||||
continue
|
||||
return result
|
||||
|
||||
class Meta:
|
||||
verbose_name = '配货记录'
|
||||
verbose_name_plural = '配货记录'
|
||||
|
||||
|
||||
class PrePurchaseOrder(ModelBase):
|
||||
"""预采购单
|
||||
|
||||
|
||||
@@ -3,14 +3,60 @@ from __future__ import annotations
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
|
||||
from basic_info import models as basic_info_models
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
def render_pre_sales_order_created_markdown(
|
||||
*,
|
||||
pre_sales_order_id: str,
|
||||
human_id: str,
|
||||
created_at: str,
|
||||
sender_label: str,
|
||||
customer_name: str,
|
||||
warehouse_name: str,
|
||||
kind: str,
|
||||
items_count: int,
|
||||
) -> str:
|
||||
template = getattr(
|
||||
settings,
|
||||
"PRE_SALES_ORDER_CREATED_WECOM_MARKDOWN_TEMPLATE",
|
||||
(
|
||||
"### 预销售单创建\n"
|
||||
"\n"
|
||||
"- **预销售单ID**:`{pre_sales_order_id}`\n"
|
||||
"- **订单编号**:`{human_id}`\n"
|
||||
"- **创建时间**:`{created_at}`\n"
|
||||
"- **发送者**:{sender_label}\n"
|
||||
"- **客户**:{customer_name}\n"
|
||||
"- **仓库**:{warehouse_name}\n"
|
||||
"- **类型**:{kind}\n"
|
||||
"- **明细条数**:`{items_count}`\n"
|
||||
),
|
||||
)
|
||||
|
||||
created_at_str = (created_at or "").strip()
|
||||
if not created_at_str:
|
||||
created_at_str = timezone.localtime(timezone.now()).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
return template.format(
|
||||
pre_sales_order_id=str(pre_sales_order_id or "-") or "-",
|
||||
human_id=str(human_id or "-") or "-",
|
||||
created_at=str(created_at_str),
|
||||
sender_label=str(sender_label or "系统自动发送"),
|
||||
customer_name=str(customer_name or "-"),
|
||||
warehouse_name=str(warehouse_name or "-"),
|
||||
kind=str(kind or "-"),
|
||||
items_count=int(items_count) if items_count is not None else 0,
|
||||
)
|
||||
|
||||
|
||||
def _to_decimal(value: Any, *, field_name: str) -> Decimal:
|
||||
if value is None or value == '':
|
||||
raise ValueError(f'{field_name} 不能为空')
|
||||
|
||||
185
business/tests/test_allocation_services.py
Normal file
185
business/tests/test_allocation_services.py
Normal file
@@ -0,0 +1,185 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from basic_info.models import (
|
||||
Merchant,
|
||||
MerchantTypeEnum,
|
||||
Customer,
|
||||
WareHouse,
|
||||
WareHouseModeEnum,
|
||||
ProductCategory,
|
||||
Product,
|
||||
ProductUnitEnum,
|
||||
Employee,
|
||||
EmployeeStatusEnum,
|
||||
)
|
||||
from business import models as business_models
|
||||
from business import allocation_services
|
||||
from stock import models as stock_models
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_TASK_ALWAYS_EAGER=True,
|
||||
CELERY_TASK_EAGER_PROPAGATES=True,
|
||||
)
|
||||
class AllocationServicesTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = Merchant.objects.create(name='配货商户', type=MerchantTypeEnum.FACTORY)
|
||||
self.customer = Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='配货客户',
|
||||
created_by=None,
|
||||
)
|
||||
self.warehouse = WareHouse.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='配货仓库',
|
||||
mode=WareHouseModeEnum.UNRESTRICTED,
|
||||
)
|
||||
category = ProductCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='配货品类',
|
||||
product_prefix='ALC',
|
||||
)
|
||||
self.product = Product.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name='配货产品',
|
||||
human_id='ALC-001',
|
||||
unit=ProductUnitEnum.METER,
|
||||
)
|
||||
self.other_product = Product.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name='其他产品',
|
||||
human_id='ALC-002',
|
||||
unit=ProductUnitEnum.METER,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username='allocation_user', password='pass123')
|
||||
self.employee = Employee.objects.create(
|
||||
merchant=self.merchant,
|
||||
sys_user=self.user,
|
||||
name='配货员',
|
||||
status=EmployeeStatusEnum.ACTIVE,
|
||||
)
|
||||
|
||||
self.pre_sales_order = business_models.PreSalesOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=self.customer,
|
||||
warehouse=self.warehouse,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
kind=business_models.SalesOrderKindEnum.WHOLESALE,
|
||||
remarks='预销售单备注',
|
||||
)
|
||||
self.item = business_models.PreSalesOrderItem.objects.create(
|
||||
pre_sales_order=self.pre_sales_order,
|
||||
product_id=self.product.id,
|
||||
product_name=self.product.name,
|
||||
quantity=Decimal('12.5'),
|
||||
unit='米',
|
||||
remarks='明细备注',
|
||||
)
|
||||
|
||||
self.stock_record = stock_models.StockChangeRecord.objects.create(
|
||||
merchant=self.merchant,
|
||||
type=stock_models.StockChangeTypeEnum.ADD,
|
||||
warehouse=self.warehouse,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
created_by=self.user,
|
||||
)
|
||||
self.stock_detail = stock_models.StockChangeDetail.objects.create(
|
||||
merchant=self.merchant,
|
||||
product=self.product,
|
||||
unit=ProductUnitEnum.METER,
|
||||
stock_change_record=self.stock_record,
|
||||
quantity='12.5',
|
||||
)
|
||||
|
||||
def test_validate_stock_details_missing(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
allocation_services.validate_stock_details(
|
||||
merchant=self.merchant,
|
||||
stock_ids=[999999],
|
||||
)
|
||||
self.assertIn('库存明细不存在', str(ctx.exception))
|
||||
|
||||
def test_validate_stock_details_consumed(self):
|
||||
self.stock_detail.is_consumed = True
|
||||
self.stock_detail.save(update_fields=['is_consumed'])
|
||||
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
allocation_services.validate_stock_details(
|
||||
merchant=self.merchant,
|
||||
stock_ids=[self.stock_detail.id],
|
||||
)
|
||||
self.assertIn('库存明细已被消费', str(ctx.exception))
|
||||
|
||||
def test_validate_stock_details_frozen(self):
|
||||
stock_models.StockFreeze.objects.create(
|
||||
merchant=self.merchant,
|
||||
product=self.product,
|
||||
warehouse=self.warehouse,
|
||||
stock_detail=self.stock_detail,
|
||||
quantity=Decimal('1'),
|
||||
unit=ProductUnitEnum.METER,
|
||||
status=stock_models.StockFreezeStatusEnum.FROZEN,
|
||||
frozen_by=self.user,
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
allocation_services.validate_stock_details(
|
||||
merchant=self.merchant,
|
||||
stock_ids=[self.stock_detail.id],
|
||||
)
|
||||
self.assertIn('库存明细已被冻结', str(ctx.exception))
|
||||
|
||||
def test_create_allocation_record_product_mismatch(self):
|
||||
other_detail = stock_models.StockChangeDetail.objects.create(
|
||||
merchant=self.merchant,
|
||||
product=self.other_product,
|
||||
unit=ProductUnitEnum.METER,
|
||||
stock_change_record=self.stock_record,
|
||||
quantity='3.0',
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
allocation_services.create_allocation_record(
|
||||
item=self.item,
|
||||
stock_ids=[other_detail.id],
|
||||
quantity='3.0',
|
||||
unit='米',
|
||||
scanned_by=self.employee,
|
||||
)
|
||||
self.assertIn('库存明细产品不匹配', str(ctx.exception))
|
||||
|
||||
def test_create_allocation_record_success(self):
|
||||
record = allocation_services.create_allocation_record(
|
||||
item=self.item,
|
||||
stock_ids=[self.stock_detail.id],
|
||||
quantity='5.5',
|
||||
unit='米',
|
||||
scanned_by=self.employee,
|
||||
remarks='扫码录入',
|
||||
)
|
||||
self.assertEqual(record.pre_sales_order_item_id, self.item.id)
|
||||
self.assertEqual(record.stock_ids, str(self.stock_detail.id))
|
||||
self.assertEqual(record.stock_id_list, [self.stock_detail.id])
|
||||
self.assertEqual(record.status, business_models.AllocationRecordStatusEnum.ACTIVE)
|
||||
freeze_qs = stock_models.StockFreeze.objects.filter(stock_detail=self.stock_detail)
|
||||
self.assertEqual(freeze_qs.count(), 1)
|
||||
self.assertEqual(freeze_qs.first().status, stock_models.StockFreezeStatusEnum.FROZEN)
|
||||
|
||||
def test_cancel_allocation_record(self):
|
||||
record = allocation_services.create_allocation_record(
|
||||
item=self.item,
|
||||
stock_ids=[self.stock_detail.id],
|
||||
quantity='2.0',
|
||||
unit='米',
|
||||
scanned_by=self.employee,
|
||||
)
|
||||
updated = allocation_services.cancel_allocation_record(record=record)
|
||||
self.assertEqual(updated.status, business_models.AllocationRecordStatusEnum.CANCELLED)
|
||||
Reference in New Issue
Block a user