1
0
forked from erp-dev/erp
This commit is contained in:
2026-07-02 18:26:09 +08:00
parent 5170700234
commit 4538e51ad5
14 changed files with 471 additions and 26 deletions

View File

@@ -147,7 +147,7 @@ class DeviceSerializer(serializers.ModelSerializer):
class ProductionAssignmentWriteSerializer(serializers.Serializer):
device = serializers.IntegerField(required=False, min_value=1)
device = serializers.IntegerField(required=False, allow_null=True, min_value=1)
content_type = serializers.IntegerField(required=False, min_value=1)
object_id = serializers.IntegerField(required=False, min_value=1)
assigner = serializers.IntegerField(required=False, min_value=1)
@@ -160,13 +160,14 @@ class ProductionAssignmentWriteSerializer(serializers.Serializer):
self.is_create = kwargs.pop("is_create", False)
super().__init__(*args, **kwargs)
if self.is_create:
self.fields["device"].required = True
self.fields["content_type"].required = True
self.fields["object_id"].required = True
self.fields["assigner"].required = True
self.fields["production_quantity"].required = True
def validate_device(self, value):
if value is None:
return None
employee = self.context["employee"]
try:
return Device.objects.get(id=value, merchant=employee.merchant)
@@ -197,7 +198,7 @@ class ProductionAssignmentWriteSerializer(serializers.Serializer):
class ProductionAssignmentSerializer(serializers.ModelSerializer):
device_name = serializers.CharField(source='device.name', read_only=True)
device_name = serializers.SerializerMethodField(read_only=True)
assigner = serializers.SerializerMethodField()
assignee = serializers.SerializerMethodField()
created_by = serializers.SerializerMethodField()
@@ -226,6 +227,11 @@ class ProductionAssignmentSerializer(serializers.ModelSerializer):
]
read_only_fields = fields
def get_device_name(self, obj):
if obj.device is None:
return None
return obj.device.name
def get_assigner(self, obj):
return _employee_payload(obj.assigner)

59
business/amounts.py Normal file
View File

@@ -0,0 +1,59 @@
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
from django.conf import settings
AMOUNT_MODE_HAOBUYE_INTEGER = 'haobuye_integer_round_half_up'
AMOUNT_MODE_STANDARD_DECIMAL_2 = 'standard_decimal_2'
AMOUNT_MODES = {
AMOUNT_MODE_HAOBUYE_INTEGER,
AMOUNT_MODE_STANDARD_DECIMAL_2,
}
INTEGER_QUANT = Decimal('1')
DECIMAL_2_QUANT = Decimal('0.01')
def to_decimal(value, *, field_name: str = 'value') -> Decimal:
try:
return Decimal(str(value))
except (InvalidOperation, TypeError, ValueError) as exc:
raise ValueError(f'{field_name} 必须是数字') from exc
def normalize_money_amount(value, *, field_name: str = 'amount') -> Decimal:
decimal_value = to_decimal(value, field_name=field_name)
return decimal_value.quantize(_money_quant(), rounding=ROUND_HALF_UP)
def calculate_line_amount(*, quantity, price, standard_quantity=None) -> Decimal:
raw_quantity = quantity
if _amount_mode() == AMOUNT_MODE_STANDARD_DECIMAL_2 and standard_quantity is not None:
raw_quantity = standard_quantity
quantity_value = to_decimal(
raw_quantity,
field_name='quantity',
)
price_value = to_decimal(price, field_name='price')
return (quantity_value * price_value).quantize(_money_quant(), rounding=ROUND_HALF_UP)
def calculate_settlement_amount(*, amount, discount_amount=Decimal('0')) -> Decimal:
return normalize_money_amount(
normalize_money_amount(amount, field_name='amount')
+ normalize_money_amount(discount_amount or Decimal('0'), field_name='discount_amount'),
field_name='settlement_amount',
)
def _amount_mode() -> str:
mode = getattr(settings, 'BUSINESS_AMOUNT_MODE', AMOUNT_MODE_HAOBUYE_INTEGER)
if mode not in AMOUNT_MODES:
raise ValueError(f'BUSINESS_AMOUNT_MODE 不合法: {mode}')
return mode
def _money_quant() -> Decimal:
if _amount_mode() == AMOUNT_MODE_STANDARD_DECIMAL_2:
return DECIMAL_2_QUANT
return INTEGER_QUANT

View File

@@ -14,6 +14,7 @@ from django.db import transaction
from basic_info import models as basic_models
from . import models as business_models
from . import services as business_services
from .amounts import calculate_settlement_amount, normalize_money_amount
logger = logging.getLogger(__name__)
@@ -524,10 +525,19 @@ def _normalize_external_receipt_record(*, record: dict[str, Any], record_kind: s
markup = str(record.get('JieSunFS') or '').strip() or None
receipt_date = _parse_external_date(record.get('RiQi') or record.get('KdRiQi'))
if record_kind == 'receipt':
amount = _to_decimal(record.get('FkJinE'), field_name='FkJinE')
discount_amount = _to_decimal(record.get('ZkJinE'), field_name='ZkJinE', default=Decimal('0'))
amount = normalize_money_amount(
_to_decimal(record.get('FkJinE'), field_name='FkJinE'),
field_name='FkJinE',
)
discount_amount = normalize_money_amount(
_to_decimal(record.get('ZkJinE'), field_name='ZkJinE', default=Decimal('0')),
field_name='ZkJinE',
)
elif record_kind == 'refund':
amount = _to_decimal(record.get('YfJinE'), field_name='YfJinE')
amount = normalize_money_amount(
_to_decimal(record.get('YfJinE'), field_name='YfJinE'),
field_name='YfJinE',
)
discount_amount = Decimal('0')
else:
raise ExternalFinanceSyncError(f'不支持的 record_kind: {record_kind}')
@@ -537,7 +547,10 @@ def _normalize_external_receipt_record(*, record: dict[str, Any], record_kind: s
'receipt_date': receipt_date,
'amount': amount,
'discount_amount': discount_amount,
'settlement_amount': amount + discount_amount,
'settlement_amount': calculate_settlement_amount(
amount=amount,
discount_amount=discount_amount,
),
'customer_name': customer_name,
'markup': markup,
'remarks': _build_external_remarks(record=record, record_kind=record_kind),

View File

@@ -7,6 +7,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from flower.common import ModelBase
from basic_info import models as basic_info_models
from .amounts import calculate_line_amount, calculate_settlement_amount
def build_business_human_id(prefix: str, created_at, object_id) -> str:
@@ -222,7 +223,11 @@ class PurchaseOrderItem(ModelBase):
return round(self.quantity * (self.empty_diff_percent / 100), 2)
def total_amount(self):
return round(self.price * self.real_quantity(), 2)
return calculate_line_amount(
quantity=self.quantity,
price=self.price,
standard_quantity=self.real_quantity(),
)
def split_quantity_of_rolls(self) -> List[int]:
if self.quantity_of_rolls:
@@ -393,7 +398,11 @@ class SalesOrderItem(ModelBase):
return round(self.quantity * (self.empty_diff_percent / 100), 2)
def total_amount(self):
return round(self.price * self.real_quantity(), 2)
return calculate_line_amount(
quantity=self.quantity,
price=self.price,
standard_quantity=self.real_quantity(),
)
class PreSalesOrder(ModelBase):
@@ -878,7 +887,11 @@ class PurchaseReturnOrderItem(ModelBase):
return round(self.quantity * (self.empty_diff_percent / 100), 2)
def total_amount(self):
return round(self.price * self.real_quantity(), 2)
return calculate_line_amount(
quantity=self.quantity,
price=self.price,
standard_quantity=self.real_quantity(),
)
class SalesReturnStatusEnum(models.IntegerChoices):
@@ -1008,7 +1021,11 @@ class SalesReturnOrderItem(ModelBase):
return round(self.quantity * (self.empty_diff_percent / 100), 2)
def total_amount(self):
return round(self.price * self.real_quantity(), 2)
return calculate_line_amount(
quantity=self.quantity,
price=self.price,
standard_quantity=self.real_quantity(),
)
class ExternalCustomerStatementCategoryEnum(models.TextChoices):
@@ -1151,8 +1168,10 @@ class PaymentOrder(OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
@property
def settlement_amount(self) -> Decimal:
discount = self.discount_amount or Decimal('0')
return self.amount + discount
return calculate_settlement_amount(
amount=self.amount,
discount_amount=self.discount_amount or Decimal('0'),
)
def get_total_amount(self) -> Decimal:
return self.settlement_amount
@@ -1242,8 +1261,10 @@ class ReceiptOrder(OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
@property
def settlement_amount(self) -> Decimal:
discount = self.discount_amount or Decimal('0')
return self.amount + discount
return calculate_settlement_amount(
amount=self.amount,
discount_amount=self.discount_amount or Decimal('0'),
)
def get_total_amount(self) -> Decimal:
return self.settlement_amount

View File

@@ -16,6 +16,7 @@ from stock import models as stock_models
from stock.services import StockFlowService
from basic_info.services import MerchantSettingService
from .amounts import normalize_money_amount
from . import models
from .tasks import (
create_purchase_order_stock_entries,
@@ -2272,7 +2273,7 @@ def _to_decimal(value, field_name: str) -> Decimal:
def _ensure_non_zero_amount(value, field_name: str) -> Decimal:
amount = _to_decimal(value, field_name)
amount = normalize_money_amount(_to_decimal(value, field_name), field_name=field_name)
# 金额类字段要求非零(允许负数用于处理退款场景)
# 付款单负金额 = 供应商退款,收款单负金额 = 退款给客户
if amount == 0:
@@ -2288,7 +2289,7 @@ def _ensure_order_pending(order, pending_status, entity_name: str):
def _ensure_non_negative_amount(value, field_name: str) -> Decimal:
if value in (None, ''):
return Decimal('0')
amount = _to_decimal(value, field_name)
amount = normalize_money_amount(_to_decimal(value, field_name), field_name=field_name)
if amount < 0:
raise ValueError(f'{field_name} 不能小于 0')
return amount

View File

@@ -0,0 +1,101 @@
from decimal import Decimal
from django.test import TestCase, override_settings
from django.utils import timezone
from basic_info import models as basic_models
from business import services
from business.amounts import calculate_line_amount, normalize_money_amount
from .fixtures import create_basic_fixtures
class BusinessAmountRulesTestCase(TestCase):
def setUp(self):
(
self.merchant,
self.supplier,
self.warehouse_strict,
self.warehouse_relaxed,
self.product,
self.operator,
) = create_basic_fixtures()
def test_line_amount_rounds_half_up_to_integer(self):
self.assertEqual(calculate_line_amount(quantity='1', price='1.49'), Decimal('1'))
self.assertEqual(calculate_line_amount(quantity='1', price='1.50'), Decimal('2'))
self.assertEqual(calculate_line_amount(quantity='1.5', price='1.5'), Decimal('2'))
self.assertEqual(calculate_line_amount(quantity='1', price='2.5'), Decimal('3'))
def test_order_total_sums_rounded_line_amounts(self):
order = services.create_purchase_order(
merchant=self.merchant,
supplier=self.supplier,
order_date=timezone.now().date(),
warehouse=self.warehouse_relaxed,
operator=self.operator,
items=[
{'product_id': self.product.id, 'quantity': '1', 'num_of_rolls': 1, 'price': '1.5', 'unit': ''},
{'product_id': self.product.id, 'quantity': '1', 'num_of_rolls': 1, 'price': '1.5', 'unit': ''},
],
)
self.assertEqual([item.total_amount() for item in order.items.order_by('id')], [Decimal('2'), Decimal('2')])
self.assertEqual(order.get_total_amount(), Decimal('4'))
def test_payment_and_receipt_amounts_are_normalized_on_create(self):
payment = services.create_payment_order(
merchant=self.merchant,
supplier=self.supplier,
payment_date=timezone.now().date(),
amount='120.50',
discount_amount='20.49',
operator=self.operator,
)
self.assertEqual(payment.amount, Decimal('121'))
self.assertEqual(payment.discount_amount, Decimal('20'))
self.assertEqual(payment.settlement_amount, Decimal('141'))
customer = basic_models.Customer.objects.create(
merchant=self.merchant,
name='金额测试客户',
created_by=None,
)
receipt = services.create_receipt_order(
merchant=self.merchant,
customer=customer,
receipt_date=timezone.now().date(),
amount='80.49',
discount_amount='5.50',
operator=self.operator,
)
self.assertEqual(receipt.amount, Decimal('80'))
self.assertEqual(receipt.discount_amount, Decimal('6'))
self.assertEqual(receipt.settlement_amount, Decimal('86'))
def test_normalize_money_amount_rounds_negative_half_away_from_zero(self):
self.assertEqual(normalize_money_amount('-25.50'), Decimal('-26'))
@override_settings(BUSINESS_AMOUNT_MODE='standard_decimal_2')
def test_standard_decimal_mode_preserves_original_precision(self):
self.assertEqual(
calculate_line_amount(quantity='10', standard_quantity='9.50', price='12.345'),
Decimal('117.28'),
)
payment = services.create_payment_order(
merchant=self.merchant,
supplier=self.supplier,
payment_date=timezone.now().date(),
amount='120.50',
discount_amount='20.49',
operator=self.operator,
)
self.assertEqual(payment.amount, Decimal('120.50'))
self.assertEqual(payment.discount_amount, Decimal('20.49'))
self.assertEqual(payment.settlement_amount, Decimal('140.99'))
@override_settings(BUSINESS_AMOUNT_MODE='unknown_mode')
def test_invalid_amount_mode_fails_fast(self):
with self.assertRaisesMessage(ValueError, 'BUSINESS_AMOUNT_MODE 不合法'):
calculate_line_amount(quantity='1', price='1')

View File

@@ -48,8 +48,9 @@ class PaymentReceiptServiceTestCase(TestCase):
self.assertEqual(order.status, business_models.PaymentOrderStatusEnum.PENDING)
self.assertEqual(order.bank_account, self.bank_account)
self.assertEqual(order.markup, '服务层附言')
self.assertEqual(order.discount_amount, Decimal('20.50'))
self.assertEqual(order.settlement_amount, Decimal('141.00'))
self.assertEqual(order.amount, Decimal('121'))
self.assertEqual(order.discount_amount, Decimal('21'))
self.assertEqual(order.settlement_amount, Decimal('142'))
reviewed = services.review_payment_order(
payment_order=order,
target_status=business_models.PaymentOrderStatusEnum.APPROVED,
@@ -60,14 +61,14 @@ class PaymentReceiptServiceTestCase(TestCase):
merchant=self.merchant,
supplier=self.supplier,
)
self.assertEqual(balance.balance, Decimal('-141.00'))
self.assertEqual(balance.balance, Decimal('-142'))
record = business_models.BalanceChangeRecord.objects.get(
merchant=self.merchant,
source_type=business_models.BalanceChangeSourceEnum.PAYMENT_ORDER,
source_id=order.id,
)
self.assertEqual(record.direction, business_models.BalanceChangeDirectionEnum.DECREASE)
self.assertEqual(record.delta, Decimal('-141.00'))
self.assertEqual(record.delta, Decimal('-142'))
self.assertEqual(record.balance_after, balance.balance)
with self.assertRaises(ValueError):
services.review_payment_order(
@@ -113,7 +114,7 @@ class PaymentReceiptServiceTestCase(TestCase):
amount='-25.50',
operator=self.operator,
)
self.assertEqual(order.amount, Decimal('-25.50'))
self.assertEqual(order.amount, Decimal('-26'))
def test_payment_discount_can_exceed_amount(self):
order = services.create_payment_order(

View File

@@ -0,0 +1,131 @@
# Business 金额计算口径设计说明
日期2026-07-01
## 背景
Business 模块中存在采购、销售、退货、收款、付款等单据。当前项目原本按较高精度计算;为兼容好布业遗留系统,需要支持其整数元金额口径。
本次实现不保存两套金额,不新增字段,不迁移历史数据。系统在任意时刻只启用一种计算口径,口径由配置项控制。
## 目标
- 将金额规则集中在一个入口,后续如果会议调整规则,修改范围尽量收窄。
- 默认启用好布业整数元口径。
- 保留回退到原两位小数口径的能力。
- 采购、销售、采购退货、销售退货、收款、付款保持一致。
- 覆盖外部财务同步直接创建收款单的路径。
## 配置项
配置项名称:
```env
BUSINESS_AMOUNT_MODE=haobuye_integer_round_half_up
```
可选值:
- `haobuye_integer_round_half_up`:好布业整数元口径,默认值。
- `standard_decimal_2`:原系统两位小数口径。
配置读取位置:
- `flower/settings.py`
- 示例配置:`env.example`
配置变更后需要重启会执行金额计算的进程,至少包括 Web 容器和 worker 容器。
## 好布业整数元口径
单据明细行:
```text
行金额 = ROUND_HALF_UP(quantity * price, 0)
```
单据总额:
```text
单据总额 = SUM(每一行取整后的行金额)
```
这里的“多行”对应 DataGrid 形式的一张单据内有多条明细:先逐行计算并取整,再对取整后的行金额求和。
收款单、付款单没有明细行,按同一口径归一化金额:
```text
amount = ROUND_HALF_UP(amount, 0)
discount_amount = ROUND_HALF_UP(discount_amount, 0)
settlement_amount = ROUND_HALF_UP(amount, 0) + ROUND_HALF_UP(discount_amount, 0)
```
## 原两位小数口径
单据明细行:
```text
行金额 = ROUND_HALF_UP(real_quantity * price, 2)
```
单据总额:
```text
单据总额 = SUM(每一行两位小数行金额)
```
收款单、付款单:
```text
amount = ROUND_HALF_UP(amount, 2)
discount_amount = ROUND_HALF_UP(discount_amount, 2)
settlement_amount = amount + discount_amount
```
## 涉及范围
业务单据:
- `PurchaseOrder`
- `SalesOrder`
- `PurchaseReturnOrder`
- `SalesReturnOrder`
- `PaymentOrder`
- `ReceiptOrder`
创建入口:
- Business 服务层创建收款单、付款单时会归一化金额。
- 好布业外部财务同步创建收款单时会归一化金额。
- 明细类单据通过模型明细行的统一计算入口计算行金额和单据总额。
## 不做的事
- 不新增数据库字段。
- 不新增 migration。
- 不回算历史数据。
- 不自动修复已经生成的资金流水。
- 不同时保存整数元金额和两位小数金额。
## 运行影响
该变更主要影响未来创建或后续计算的业务金额。已经审批并生成的余额流水不会自动变化。
如果存在未审批单据,审批时会按当前配置计算并写入流水。配置切换前后,需要避免同一批未审批单据跨口径处理。
## 回退方式
如需恢复原计算模式:
```env
BUSINESS_AMOUNT_MODE=standard_decimal_2
```
修改配置后重启 Web 和 worker 进程即可。由于本次没有数据库结构变更,也不回算历史数据,回退动作可以较快完成。
## 风险与注意事项
- 口径切换会改变未审批单据后续审批时的金额。
- 对于没有保存快照、运行时重新计算总额的展示接口,切换配置后展示金额可能按新口径变化。
- 收款、付款没有明细,已按同一配置归一化金额,以保证和明细单据一致。
- 如果以后好布业规则再次调整,应优先修改集中计算入口,而不是分散修改各类单据。

View File

@@ -90,6 +90,13 @@ HAOBUYE_API_TIMEOUT_SECONDS=30
# 可选作为外部财务同步默认经办人Employee.id
HAOBUYE_FINANCE_SYNC_OPERATOR_ID=0
############################
# Business 金额计算口径
############################
# haobuye_integer_round_half_up: 好布业整数元口径(默认)
# standard_decimal_2: 原系统两位小数口径
BUSINESS_AMOUNT_MODE=haobuye_integer_round_half_up
# PrintingJob 状态推进通知的“跟进地址”模板;为空则消息里省略“跟进地址”字段
PRINTING_JOB_STATE_ADVANCED_FOLLOWUP_URL_TEMPLATE=https://app.yuwen.cloud/workstation/production/batch-advance?orderId={order_id}
@@ -126,4 +133,3 @@ CELERY_RESULT_DB=1
############################
# 逗号分隔
CORS_ALLOWED_ORIGINS=http://localhost:8000,http://127.0.0.1:5173,http://127.0.0.1:5174,http://127.0.0.1:5179,https://yuwenerp.yuwen.cloud

View File

@@ -106,6 +106,11 @@ HAOBUYE_API_AUTHORIZATION = env('HAOBUYE_API_AUTHORIZATION', default='your-fixed
HAOBUYE_API_TIMEOUT_SECONDS = env.float('HAOBUYE_API_TIMEOUT_SECONDS', default=30.0)
HAOBUYE_FINANCE_SYNC_OPERATOR_ID = env.int('HAOBUYE_FINANCE_SYNC_OPERATOR_ID', default=6)
# Business 金额计算口径
# - haobuye_integer_round_half_up: 行金额=ROUND_HALF_UP(quantity * price, 0),收/付款金额取整数元
# - standard_decimal_2: 原系统口径,行金额=ROUND_HALF_UP(real_quantity * price, 2),收/付款金额保留两位
BUSINESS_AMOUNT_MODE = env('BUSINESS_AMOUNT_MODE', default='haobuye_integer_round_half_up')
# 定时财务同步客户列表(临时需求,直接写死不走 env
FINANCE_SYNC_CUSTOMER_NAMES: list[str] = [
'曾念', '紫琪', '胡肖宇', '歌斯拉-胜利星厂', '胡鼎',

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.8 on 2026-07-01 06:55
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mes', '0003_productionassignment_production_quantity_and_more'),
]
operations = [
migrations.AlterField(
model_name='productionassignment',
name='device',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='production_assignments', to='mes.device', verbose_name='设备'),
),
]

View File

@@ -128,6 +128,8 @@ class ProductionAssignment(ModelBase):
Device,
on_delete=models.PROTECT,
related_name='production_assignments',
null=True,
blank=True,
verbose_name='设备',
)
content_type = models.ForeignKey(

View File

@@ -244,14 +244,15 @@ def get_production_assignment_for_merchant(*, merchant, assignment_id: int) -> P
@transaction.atomic
def create_production_assignment(*, merchant, device: Device, content_type: ContentType, object_id: int,
def create_production_assignment(*, merchant, device: Device | None = None,
content_type: ContentType, object_id: int,
assigner, production_quantity: int, assignee=None, created_by=None, operator=None,
status: int = ProductionAssignmentStatusEnum.DRAFT, extra=None) -> ProductionAssignment:
_assert_employee_belongs_to_merchant(employee=assigner, merchant=merchant, role='指派者')
if assignee is not None:
_assert_employee_belongs_to_merchant(employee=assignee, merchant=merchant, role='被指派人')
_assert_employee_belongs_to_merchant(employee=operator, merchant=merchant, role='操作人')
if device.merchant_id != merchant.id:
if device is not None and device.merchant_id != merchant.id:
raise ValueError('设备不属于当前商户')
_validate_content_object_merchant(content_type=content_type, object_id=object_id, merchant=merchant)
assignment = ProductionAssignment(
@@ -277,7 +278,7 @@ def update_production_assignment(*, assignment: ProductionAssignment, operator,
_assert_employee_belongs_to_merchant(employee=operator, merchant=assignment.merchant, role='操作人')
assignment.operator = operator
if device is not UNSET:
if device.merchant_id != assignment.merchant_id:
if device is not None and device.merchant_id != assignment.merchant_id:
raise ValueError('设备不属于当前商户')
assignment.device = device
if assignee is not UNSET:

View File

@@ -366,3 +366,82 @@ class MesServiceTestCase(TestCase):
operator=self.operator,
status=ProductionAssignmentStatusEnum.CANCELLED,
)
def test_create_production_assignment_without_device(self):
customer = Customer.objects.create(
merchant=self.merchant,
name='无设备客户',
mobile='13800138002',
)
printing_order = PrintingOrder.objects.create(
merchant=self.merchant,
customer=customer,
fabric='无设备面料',
width='150cm',
created_by=self.user,
)
content_type = ContentType.objects.get_for_model(PrintingOrder)
assignment = services.create_production_assignment(
merchant=self.merchant,
device=None,
content_type=content_type,
object_id=printing_order.id,
assigner=self.operator,
production_quantity=100,
created_by=self.user,
operator=self.operator,
)
self.assertIsNone(assignment.device)
self.assertIsNone(assignment.device_id)
self.assertEqual(assignment.status, ProductionAssignmentStatusEnum.DRAFT)
self.assertEqual(assignment.production_quantity, 100)
def test_update_production_assignment_clear_device(self):
customer = Customer.objects.create(
merchant=self.merchant,
name='清空设备客户',
mobile='13800138003',
)
printing_order = PrintingOrder.objects.create(
merchant=self.merchant,
customer=customer,
fabric='清空设备面料',
width='150cm',
created_by=self.user,
)
category = services.create_device_category(
merchant=self.merchant,
name='打印机',
created_by=self.user,
operator=self.operator,
)
device = services.create_device(
merchant=self.merchant,
category=category,
name='设备A',
created_by=self.user,
operator=self.operator,
peak_capacity=120,
)
content_type = ContentType.objects.get_for_model(PrintingOrder)
assignment = services.create_production_assignment(
merchant=self.merchant,
device=device,
content_type=content_type,
object_id=printing_order.id,
assigner=self.operator,
production_quantity=200,
created_by=self.user,
operator=self.operator,
)
self.assertIsNotNone(assignment.device)
updated = services.update_production_assignment(
assignment=assignment,
operator=self.operator,
device=None,
)
self.assertIsNone(updated.device)
self.assertIsNone(updated.device_id)