diff --git a/apps/goods/models.py b/apps/goods/models.py index 48ef5bf..d78973b 100644 --- a/apps/goods/models.py +++ b/apps/goods/models.py @@ -82,16 +82,16 @@ class Batch(Model): """批次""" number = CharField(max_length=32, verbose_name='编号') + inventory = ForeignKey('goods.Inventory', on_delete=CASCADE, related_name='batchs', verbose_name='库存') warehouse = ForeignKey('data.Warehouse', on_delete=CASCADE, related_name='batchs', verbose_name='仓库') goods = ForeignKey('goods.Goods', on_delete=CASCADE, related_name='batchs', verbose_name='商品') + initial_quantity = FloatField(default=0, verbose_name='初始库存') total_quantity = FloatField(verbose_name='批次数量') remain_quantity = FloatField(verbose_name='批次剩余数量') production_date = DateField(null=True, verbose_name='生产日期') shelf_life_days = IntegerField(null=True, verbose_name='保质期天数') expiration_date = DateField(null=True, verbose_name='过期日期') - initial_inventory = ForeignKey('goods.Inventory', on_delete=SET_NULL, null=True, - related_name='batchs', verbose_name='初始库存') - has_stock = BooleanField(verbose_name='库存状态') + has_stock = BooleanField(default=True, verbose_name='库存状态') create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间') team = ForeignKey('system.Team', on_delete=CASCADE, related_name='batchs') diff --git a/apps/goods/serializers.py b/apps/goods/serializers.py index ee1ba82..d79fa6f 100644 --- a/apps/goods/serializers.py +++ b/apps/goods/serializers.py @@ -34,19 +34,16 @@ class GoodsSerializer(BaseSerializer): class InventoryItemSerializer(BaseSerializer): class BatchItemSerializer(BaseSerializer): + id = IntegerField(required=False, label='批次ID') class Meta: model = Batch - read_only_fields = ['id'] - fields = ['number', 'total_quantity', 'production_date', *read_only_fields] + read_only_fields = ['total_quantity', 'remain_quantity'] + fields = ['id', 'number', 'initial_quantity', 'production_date', *read_only_fields] - def validate_number(self, value): - self.validate_unique({'number': value}, message=f'编号[{value}]已存在') - return value - - def validate_total_quantity(self, value): - if value <= 0: - raise ValidationError('库存数量小于等于零') + def validate_initial_quantity(self, value): + if value < 0: + raise ValidationError('初始库存数量小于零') return value warehouse_number = CharField(source='warehouse.number', read_only=True, label='仓库编号') @@ -116,32 +113,41 @@ class GoodsSerializer(BaseSerializer): for warehouse in Warehouse.objects.filter(team=self.team): for inventory_item in inventory_items: if warehouse == inventory_item['warehouse']: - # 创建库存 + inventory_initial_quantity = inventory_item.get('initial_quantity', 0) + has_stock = inventory_initial_quantity > 0 inventory = Inventory.objects.create( - warehouse=warehouse, goods=goods, - initial_quantity=inventory_item['initial_quantity'], - total_quantity=inventory_item['initial_quantity'], team=self.team + warehouse=warehouse, goods=goods, initial_quantity=inventory_initial_quantity, + total_quantity=inventory_initial_quantity, has_stock=has_stock, team=self.team ) - total_inventory_quantity = 0 + total_initial_quantity = 0 # 商品开启批次控制, 创建批次 batch_items = inventory_item.get('batchs') if goods.enable_batch_control and batch_items: for batch_item in batch_items: - total_quantity = batch_item['total_quantity'] + batch_initial_quantity = batch_item.get('initial_quantity', 0) production_date = batch_item.get('production_date') if production_date and goods.shelf_life_days: expiration_date = pendulum.parse(str(production_date)).add(days=goods.shelf_life_days) + expiration_date = expiration_date.to_date_string() + has_stock = batch_initial_quantity > 0 batchs.append(Batch( - number=batch_item['number'], warehouse=warehouse, goods=goods, - total_quantity=total_quantity, remain_quantity=total_quantity, + number=batch_item['number'], inventory=inventory, warehouse=warehouse, + goods=goods, initial_quantity=batch_initial_quantity, + total_quantity=batch_initial_quantity, remain_quantity=batch_initial_quantity, production_date=production_date, shelf_life_days=goods.shelf_life_days, - expiration_date=expiration_date, initial_inventory=inventory, team=self.team, + expiration_date=expiration_date, has_stock=has_stock, team=self.team )) - total_inventory_quantity = NP.plus(total_inventory_quantity, total_quantity) + total_initial_quantity = NP.plus(total_initial_quantity, batch_initial_quantity) + else: + if total_initial_quantity != inventory_initial_quantity: + inventory.initial_quantity = total_initial_quantity + inventory.total_quantity = total_initial_quantity + inventory.has_stock = inventory.total_quantity > 0 + inventory.save(update_fields=['initial_quantity', 'total_quantity', 'has_stock']) break else: Inventory.objects.create(warehouse=warehouse, goods=goods, team=self.team) @@ -156,15 +162,17 @@ class GoodsSerializer(BaseSerializer): goods = super().update(instance, validated_data) # 同步批次 - if enable_batch_control := validated_data.get('enable_batch_control'): - if enable_batch_control != instance.enable_batch_control: + enable_batch_control = validated_data.get('enable_batch_control') + if enable_batch_control is not None: + if enable_batch_control != goods.enable_batch_control: if enable_batch_control: batch_number = 'B' + pendulum.today().format('YYYYMMDD') batchs = [] for inventory in Inventory.objects.filter(goods=goods, has_stock=True, team=self.team): batchs.append(Batch( - number=batch_number, warehouse=inventory.warehouse, goods=inventory.goods, - total_quantity=inventory.total_quantity, remain_quantity=inventory.total_quantity, + number=batch_number, inventory=inventory, warehouse=inventory.warehouse, + goods=inventory.goods, total_quantity=inventory.total_quantity, + remain_quantity=inventory.total_quantity, shelf_life_days=goods.shelf_life_days, team=self.team )) else: @@ -173,14 +181,80 @@ class GoodsSerializer(BaseSerializer): instance.batchs.all().delete() # 同步库存 + create_batchs = [] + update_batchs = [] for inventory in Inventory.objects.filter(goods=goods, team=self.team): for inventory_item in inventory_items: - if (inventory.warehouse == inventory_item['warehouse'] and - inventory.initial_quantity != inventory_item['initial_quantity']): - inventory.total_quantity = NP.minus(inventory.total_quantity, inventory.initial_quantity) - inventory.initial_quantity = inventory_item['initial_quantity'] - inventory.total_quantity = NP.plus(inventory.total_quantity, inventory.initial_quantity) - inventory.save(update_fields=['initial_quantity', 'total_quantity']) + warehouse = inventory.warehouse + if warehouse == inventory_item['warehouse']: + inventory_initial_quantity = inventory_item.get('initial_quantity', 0) + + if goods.enable_batch_control: + total_initial_quantity = 0 + batch_items = inventory_item.get('batchs', []) + for batch_item in batch_items: + batch_initial_quantity = batch_item.get('initial_quantity', 0) + production_date = batch_item.get('production_date') + if production_date and goods.shelf_life_days: + expiration_date = pendulum.parse(str(production_date)) \ + .add(days=goods.shelf_life_days) + expiration_date = expiration_date.to_date_string() + + if batch_id := batch_item.get('id'): + batch = Batch.objects.filter(id=batch_id, warehouse=warehouse, + goods=goods, team=self.team).first() + if not batch: + batch_number = batch_item['number'] + raise ValidationError(f'批次[{batch_number}]不存在') + + batch.number = batch_item['number'] + batch.total_quantity = NP.minus(batch.total_quantity, batch.initial_quantity) + batch.remain_quantity = NP.minus(batch.remain_quantity, batch.initial_quantity) + batch.initial_quantity = batch_initial_quantity + batch.total_quantity = NP.plus(batch.total_quantity, batch.initial_quantity) + batch.remain_quantity = NP.plus(batch.remain_quantity, batch.initial_quantity) + batch.production_date = production_date + batch.expiration_date = expiration_date + batch.has_stock = batch.total_quantity > 0 + + update_batchs.append(batch) + else: + if Batch.objects.filter(number=batch_item['number'], warehouse=warehouse, + goods=goods, team=self.team).exists(): + batch_number = batch_item['number'] + raise ValidationError(f'批次[{batch_number}]已存在') + + has_stock = batch_initial_quantity > 0 + create_batchs.append(Batch( + number=batch_item['number'], inventory=inventory, warehouse=warehouse, + goods=goods, initial_quantity=batch_initial_quantity, + total_quantity=batch_initial_quantity, remain_quantity=batch_initial_quantity, + production_date=production_date, shelf_life_days=goods.shelf_life_days, + expiration_date=expiration_date, has_stock=has_stock, team=self.team, + )) + + total_initial_quantity = NP.plus(total_initial_quantity, batch_initial_quantity) + else: + inventory.total_quantity = NP.minus(inventory.total_quantity, inventory.initial_quantity) + inventory.initial_quantity = total_initial_quantity + inventory.total_quantity = NP.plus(inventory.total_quantity, inventory.initial_quantity) + inventory.has_stock = inventory.total_quantity > 0 + inventory.save(update_fields=['initial_quantity', 'total_quantity', 'has_stock']) + else: + inventory.total_quantity = NP.minus(inventory.total_quantity, inventory.initial_quantity) + inventory.initial_quantity = inventory_initial_quantity + inventory.total_quantity = NP.plus(inventory.total_quantity, inventory.initial_quantity) + inventory.has_stock = inventory.total_quantity > 0 + inventory.save(update_fields=['initial_quantity', 'total_quantity', 'has_stock']) + + break + else: + Batch.objects.filter(goods=goods, team=self.team) \ + .exclude(id__in=[batch.id for batch in update_batchs]).delete() + Batch.objects.bulk_create(create_batchs) + Batch.objects.bulk_update(update_batchs, + ['number', 'initial_quantity', 'total_quantity', 'remain_quantity', + 'production_date', 'expiration_date', 'has_stock']) return goods