12.寻光集后台管理系统-库存信息(后端)

有了产品信息之后,下面来写库存信息部分

每一个库存的入库和出库都需要对应一个产品,所以产品信息是它的外键

库存信息

创建App

python manage.py startapp warehouse

warehouse文件夹移动到backend/apps/warehouse

注册App

backend/LightSeeking/settings.pyINSTALLED_APPS中添加

INSTALLED_APPS = [
    ...
    'users.apps.UsersConfig',
    'product.apps.ProductConfig',
    'warehouse.apps.WarehouseConfig'
]

表结构设计

库存信息包含了

  • 产品信息

  • 单价

  • 生产日期

  • 保质期天数

  • 数量

  • 库存状态

  • 订单号

  • 供应商

  • 备注

产品信息使用外键的方式,并且两个键之间不互相影响

product = models.ForeignKey(Product, verbose_name="产品信息", on_delete=models.DO_NOTHING, help_text="产品信息")

其他都是普通的字段

from django.db import models

from product.models import Product
from utils.models import BaseModel


class Warehouse(BaseModel):
    STOCKSTATUS = (
        (1, "入库"),
        (2, "出库"),
        (3, "报废"),
    )
    product = models.ForeignKey(Product, verbose_name="产品信息", on_delete=models.DO_NOTHING, help_text="产品信息")
    price = models.DecimalField('成本单价', max_digits=10, decimal_places=2, default=0, help_text='成本单价')
    date_of_manufacture = models.DateTimeField("生产日期", help_text="生产日期")
    quality_guarantee = models.IntegerField("保质期天数", help_text="保质期天数")
    num = models.IntegerField("数量", default=0, help_text="数量")
    stock_status = models.CharField("库存状态", max_length=2, choices=STOCKSTATUS, default=1,
                                    help_text='库存状态:1-入库,2-出库,3-报废')
    order_id = models.CharField("订单号", max_length=200, null=True, blank=True, default='', help_text='订单号')
    supplier = models.CharField("供应商", max_length=200, null=True, blank=True, default='', help_text='供应商')
    desc = models.CharField('备注', max_length=1000, null=True, blank=True, default='', help_text='备注')

    class Meta:
        db_table = 'tb_warehouse'
        verbose_name = '库存信息'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.id

数据迁移

python manage.py makemigrations
python manage.py migrate

序列化器

在库存序列化器中需要指定产品来进行新增,查看的时候需要返回全部的产品信息

所以先新建一个包含全部产品的序列化器

backend/apps/product/serializers.py

class ProductAllModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'

添加产品的时候使用的是产品的id,这时候就需要判断一下这个产品是否存在了

编写whether_existed_product_id方法来进行判断

def whether_existed_product_id(value):
    """
    检查产品id是否存在
    :param value:
    :return:
    """
    if value != 0:
        if not isinstance(value, int):
            raise serializers.ValidationError('所选产品信息有误!')
        elif not Product.objects.filter(is_delete=False, id=value).exists():
            raise serializers.ValidationError('所选产品信息不存在!')

所以库存的序列化器基础版本为

class WarehouseModelSerializer(serializers.ModelSerializer):
    product_id = serializers.IntegerField(
        write_only=True,
        help_text='产品信息ID',
        validators=[validates.whether_existed_product_id]
    )
    product = ProductAllModelSerializer(read_only=True, many=False)

    class Meta:
        model = Warehouse
        exclude = ('is_delete',)
        extra_kwargs = {
            'c_time': {
                'read_only': True
            },
        }
重点

下面是序列化器中的一个神奇的操作,也就是它可以逐行处理返回的数据,根据逻辑给每行添加需要的数据

在这个序列化器中需要顺便计算一下产品的保质期相关数据

新加两个字段剩余天数保质期日期

剩余天数 = 保质期天数 - (当前日期 - 生产日期)

保质期日期 = 生产日期+剩余天数

remaining = serializers.SerializerMethodField(label="剩余天数", read_only=True, help_text="剩余天数")
remaining_day = serializers.SerializerMethodField(label="保质期日期", read_only=True, help_text="保质期日期")

使用serializers.SerializerMethodField可以在WarehouseModelSerializer类中编写get_xxx方法来计算这个值

def get_remaining(self, warehouse):
    """
    剩余天数 = 保质期天数 - (当前日期 - 生产日期)
    :param warehouse:
    :return:
    """
    return warehouse.quality_guarantee - (
            warehouse.date_of_manufacture - datetime.datetime.now(tz=pytz.UTC)
    ).days

def get_remaining_day(self, warehouse):
    """
    保质期日期 = 生产日期+剩余天数
    """
    return warehouse.date_of_manufacture + datetime.timedelta(days=warehouse.quality_guarantee)

同理再根据需求添加几个字段

  • 总库存

  • 出库

  • 入库

  • 已经出库时间

  • 已经入库时间

total = serializers.SerializerMethodField(label="总库存", read_only=True, help_text="一个产品的总库存")
warehouse1 = serializers.SerializerMethodField(label="出库", read_only=True, help_text="一个产品的总出库")
warehouse2 = serializers.SerializerMethodField(label="入库", read_only=True, help_text="一个产品的总入库")
warehouse1_time = serializers.SerializerMethodField(label="已经出库时间", read_only=True, help_text="出库距离当前的时间")
warehouse2_time = serializers.SerializerMethodField(label="已经入库时间", read_only=True, help_text="入库距离当前的时间")

实现字段的计算

def get_total(self, warehouse):
    total = self.get_warehouse1(warehouse) - self.get_warehouse2(warehouse)
    return total

def get_warehouse1(self, warehouse):
    product_id = warehouse.product_id
    warehouse_info = Warehouse.objects.filter(stock_status=1).values("product").annotate(total=Sum("num")).all()
    total = warehouse_info.filter(product_id=product_id) and \
            warehouse_info.filter(product_id=product_id).get()["total"] or 0
    return total

def get_warehouse2(self, warehouse):
    product_id = warehouse.product_id
    warehouse_info = Warehouse.objects.filter(stock_status=2).values("product").annotate(total=Sum("num")).all()
    total = warehouse_info.filter(product_id=product_id) and \
            warehouse_info.filter(product_id=product_id).get()["total"] or 0
    return total

def get_warehouse1_time(self, warehouse):
    warehouse2_time = warehouse.c_time - datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=+8)))
    return warehouse2_time.days

def get_warehouse2_time(self, warehouse):
    warehouse2_time = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=+8))) - warehouse.c_time
    return warehouse2_time.days

视图

from rest_framework.viewsets import ModelViewSet

from utils.pagination import TenItemPerPagePagination
from warehouse.models import Warehouse
from warehouse.serializers import WarehouseModelSerializer


class WarehouseViewSet(ModelViewSet):
    queryset = Warehouse.objects.filter(is_delete=False).order_by("-c_time")
    serializer_class = WarehouseModelSerializer
    pagination_class = TenItemPerPagePagination
    ordering_fields = ['c_time']

    def perform_destroy(self, instance):
        instance.is_delete = True
        instance.save()  # 逻辑删除

路由

backend/apps/warehouse/urls.py

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register("warehouse", views.WarehouseViewSet)

urlpatterns = [
    path('', include(router.urls))
]

backend/LightSeeking/urls.py

urlpatterns = [
    ...
    path('', include('warehouse.urls')),
]

代码

本次代码修改见:

https://gitee.com/zx660644/light-seeking/commit/2a5f9e4a26ff56e7cef5e6d4bdaf0adc63bb37b0

测试

先添加一个产品信息

访问http://127.0.0.1:8000/product/

填入产品信息后点击POST

c0146a8681ef9055492161525fb36f44.jpeg

再访问http://127.0.0.1:8000/warehouse/

添加一个库存信息

96f9b0f03211e2c7c686e36bab89e017.jpeg

查看结果

{
    "id": 1,
    "product": {
        "id": 1,
        "c_time": "2022-09-02T12:14:32.356282+08:00",
        "u_time": "2022-09-02T12:14:32.356417+08:00",
        "is_delete": false,
        "product_id": "0001",
        "category": "饮料",
        "brand": "可口可乐",
        "name": "可乐",
        "price": "3.00",
        "sample_png": "",
        "desc": "无"
    },
    "remaining": 367,
    "remaining_day": "2023-09-01T12:15:00+08:00",
    "total": 998,
    "warehouse1": 998,
    "warehouse2": 0,
    "warehouse1_time": -1,
    "warehouse2_time": 0,
    "c_time": "2022-09-02T12:15:46.366239+08:00",
    "u_time": "2022-09-02T12:15:46.366352+08:00",
    "price": "2.50",
    "date_of_manufacture": "2022-09-01T12:15:00+08:00",
    "quality_guarantee": 365,
    "num": 998,
    "stock_status": 1,
    "order_id": "",
    "supplier": "测试供应商",
    "desc": ""
}

其中product字段包含了我们入库的产品信息

另外之前序列化器中添加的几个字段也出现了

bug修复

不过从这里看出来,之前写了个bug,之前把日期计算写反了

def get_remaining(self, warehouse):
    """
    剩余天数 = 保质期天数 - (当前日期 - 生产日期)
    :param warehouse:
    :return:
    """
    return warehouse.quality_guarantee - (
            datetime.datetime.now(tz=pytz.UTC) - warehouse.date_of_manufacture
    ).days

再次刷新后查看 remaining变为了364

{
    "data": {
        "page": 1,
        "pageSize": 20,
        "rows": [
            {
                "id": 1,
                "product": {
                    "id": 1,
                    "c_time": "2022-09-02T12:14:32.356282+08:00",
                    "u_time": "2022-09-02T12:14:32.356417+08:00",
                    "is_delete": false,
                    "product_id": "0001",
                    "category": "饮料",
                    "brand": "可口可乐",
                    "name": "可乐",
                    "price": "3.00",
                    "sample_png": "",
                    "desc": "无"
                },
                "remaining": 364,
                "remaining_day": "2023-09-01T04:15:00Z",
                "total": 998,
                "warehouse1": 998,
                "warehouse2": 0,
                "warehouse1_time": -1,
                "warehouse2_time": 0,
                "c_time": "2022-09-02T12:15:46.366239+08:00",
                "u_time": "2022-09-02T12:15:46.366352+08:00",
                "price": "2.50",
                "date_of_manufacture": "2022-09-01T12:15:00+08:00",
                "quality_guarantee": 365,
                "num": 998,
                "stock_status": 1,
                "order_id": "",
                "supplier": "测试供应商",
                "desc": ""
            }
        ],
        "total": 1
    },
    "message": "",
    "code": 200,
    "next": null,
    "previous": null
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值