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