Django种子销售库存模型开发:按入库批次定价销售需求
嘿,你的需求正好是典型的先进先出(FIFO)库存计价场景,针对种子的固定包装规格来设计模型和业务逻辑就完全能搞定。我帮你梳理一套具体的实现方案,包括模型设计、核心业务逻辑和注意事项:
1. 核心数据库模型设计
首先得把种子品种、包装规格、入库记录这几个核心实体拆解开,每个入库记录绑定对应的品种、包装规格,同时追踪剩余可售数量和入库单价:
from django.db import models from decimal import Decimal class SeedVariety(models.Model): name = models.CharField(max_length=100, unique=True) # 可按需添加描述、品类等扩展字段 def __str__(self): return self.name class PackageSize(models.Model): size = models.IntegerField( unique=True, choices=[(3, '3粒装'), (5, '5粒装'), (10, '10粒装')] ) def __str__(self): return f"{self.size}粒装" class InventoryReceipt(models.Model): variety = models.ForeignKey(SeedVariety, on_delete=models.CASCADE, related_name='receipts') package_size = models.ForeignKey(PackageSize, on_delete=models.CASCADE, related_name='receipts') total_quantity = models.PositiveIntegerField() # 该批次入库总数量 remaining_quantity = models.PositiveIntegerField() # 剩余可售数量 unit_price = models.DecimalField(max_digits=10, decimal_places=2) # 该批次入库单价 receipt_date = models.DateTimeField(auto_now_add=True) # 入库时间,用于FIFO排序 class Meta: ordering = ['receipt_date'] # 默认按入库时间升序,保证先进先出逻辑 def __str__(self): return f"{self.variety.name} - {self.package_size} - 剩余{self.remaining_quantity}份 - 单价{self.unit_price}" # 可选:出库记录,用于追踪每笔销售对应的入库批次 class InventoryIssue(models.Model): receipt = models.ForeignKey(InventoryReceipt, on_delete=models.CASCADE, related_name='issues') quantity = models.PositiveIntegerField() issue_date = models.DateTimeField(auto_now_add=True) # 可按需关联订单、客户等外键 def __str__(self): return f"出库{self.quantity}份 - 来自批次{self.receipt.id}"
模型说明
SeedVariety和PackageSize作为基础数据,避免重复存储品种名称和包装规格InventoryReceipt是核心:remaining_quantity用来跟踪批次剩余可售量,receipt_date保证FIFO的顺序- 新增入库记录时,可以重写
save方法自动同步remaining_quantity和total_quantity:
def save(self, *args, **kwargs): if not self.pk: # 仅新建记录时执行 self.remaining_quantity = self.total_quantity super().save(*args, **kwargs)
2. 核心销售业务逻辑实现
重点是实现按入库顺序扣减库存、计算对应批次价格的逻辑,同时用事务保证数据一致性:
from django.db import transaction def process_seed_sale(variety_id, package_size_id, sale_quantity): """处理种子销售,按FIFO规则扣减对应入库批次的剩余数量""" with transaction.atomic(): # 事务保证并发场景下的数据一致性 # 获取对应品种+包装规格、有剩余库存的入库批次,按入库时间升序 eligible_receipts = InventoryReceipt.objects.filter( variety_id=variety_id, package_size_id=package_size_id, remaining_quantity__gt=0 ).order_by('receipt_date') remaining_sale = sale_quantity total_sale_price = Decimal('0.00') issue_records = [] for receipt in eligible_receipts: if remaining_sale <= 0: break # 计算当前批次可扣减的数量 deduct_qty = min(receipt.remaining_quantity, remaining_sale) # 更新批次剩余数量 receipt.remaining_quantity -= deduct_qty receipt.save() # 累加该批次的销售金额 total_sale_price += receipt.unit_price * deduct_qty # 生成出库记录(可选) issue_records.append(InventoryIssue(receipt=receipt, quantity=deduct_qty)) remaining_sale -= deduct_qty if remaining_sale > 0: # 库存不足时抛出异常或返回错误信息 raise ValueError(f"库存不足,还差{remaining_sale}份") # 批量保存出库记录 InventoryIssue.objects.bulk_create(issue_records) return { 'total_price': total_sale_price, 'used_batches': issue_records }
逻辑说明
- 用
transaction.atomic()包裹整个操作,防止并发销售时出现超卖或数据不一致 - 按入库顺序遍历可用批次,优先扣减最早入库的库存
- 如果遍历完所有批次仍未满足销售数量,说明库存不足,抛出异常
3. 辅助查询方法
可以写一个方法,快速查询某个品种+包装规格的可用库存及各批次信息:
def get_available_inventory(variety_id, package_size_id): """获取对应品种和包装规格的可用库存及批次详情""" receipts = InventoryReceipt.objects.filter( variety_id=variety_id, package_size_id=package_size_id, remaining_quantity__gt=0 ).order_by('receipt_date') total_available = sum(r.remaining_quantity for r in receipts) batch_details = [ { 'batch_id': r.id, 'remaining': r.remaining_quantity, 'unit_price': r.unit_price, 'in_stock_since': r.receipt_date.strftime('%Y-%m-%d %H:%M') } for r in receipts ] return { 'total_available': total_available, 'batches': batch_details }
4. 关键注意事项
- 并发控制:务必使用事务,避免多用户同时销售时出现库存数据错误
- 价格精度:始终用
DecimalField处理价格,避免浮点数精度问题 - 包装规格约束:通过
PackageSize的choices限制只能是3/5/10粒装,也可以添加clean方法做额外校验 - 库存预警:可以在模型中添加信号或定期任务,监控剩余库存低于阈值的批次
内容的提问来源于stack exchange,提问作者quasimoto
相关产品推荐
相关产品推荐

