Files

185 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""商品中心模型:Category / Brand / Unit / Product。
预留点:
- Product.ext_data:自定义属性 JSON(行业差异)
- Product.source_channel:manual/api/web/miniprogram/ai_agent
- Product.is_deleted:软删除
"""
from decimal import Decimal
from django.db import models
from apps.core.base_models import TenantScopedModel
class Category(TenantScopedModel):
"""商品分类(树形结构)。"""
code = models.CharField(max_length=64, help_text="分类代码")
name = models.CharField(max_length=128)
parent = models.ForeignKey(
"self",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="children",
)
sort_order = models.IntegerField(default=0)
class Meta:
db_table = "catalog_category"
unique_together = [("tenant", "code")]
verbose_name_plural = "商品分类"
ordering = ["sort_order", "code"]
def __str__(self):
return f"{self.code} {self.name}"
class Brand(TenantScopedModel):
"""品牌。"""
code = models.CharField(max_length=64)
name = models.CharField(max_length=128)
logo_url = models.URLField(blank=True, default="")
class Meta:
db_table = "catalog_brand"
unique_together = [("tenant", "code")]
verbose_name_plural = "品牌"
ordering = ["code"]
def __str__(self):
return self.name
class Unit(TenantScopedModel):
"""计量单位(箱/瓶/件/kg)。"""
code = models.CharField(max_length=16, help_text="单位代码,如 box/bottle/kg")
name = models.CharField(max_length=32)
is_base = models.BooleanField(default=True, help_text="是否基本单位")
class Meta:
db_table = "catalog_unit"
unique_together = [("tenant", "code")]
verbose_name_plural = "计量单位"
def __str__(self):
return self.name
class Product(TenantScopedModel):
"""商品档案。"""
STATUS_ACTIVE = "active"
STATUS_INACTIVE = "inactive"
STATUS_CHOICES = [
(STATUS_ACTIVE, "启用"),
(STATUS_INACTIVE, "停用"),
]
code = models.CharField(max_length=64, help_text="商品编码(租户内唯一)")
name = models.CharField(max_length=255)
spec = models.CharField(max_length=255, blank=True, default="", help_text="规格")
barcode = models.CharField(max_length=64, blank=True, default="")
category = models.ForeignKey(
Category,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="products",
)
brand = models.ForeignKey(
Brand,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="products",
)
base_unit = models.ForeignKey(
Unit,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="products",
)
# 价格字段
cost_price = models.DecimalField(
max_digits=18, decimal_places=4, default=0, help_text="参考成本价"
)
sale_price = models.DecimalField(
max_digits=18, decimal_places=4, default=0, help_text="默认销售价"
)
min_sale_price = models.DecimalField(
max_digits=18,
decimal_places=4,
default=0,
help_text="最低售价(0=不限制;低于需审批放行)",
)
# 批次/效期(食品、医药等批次管理行业)
is_batch_managed = models.BooleanField(
default=False, help_text="是否启用批次管理(出库按近效期优先 FEFO)"
)
shelf_life_days = models.IntegerField(
null=True, blank=True, help_text="保质期天数(用于入库时推算默认到期日)"
)
# 税率(批次 D4):默认 13%(一般纳税人常见档),可按商品覆盖
tax_rate = models.DecimalField(
max_digits=6, decimal_places=4, default=Decimal("0.13"),
help_text="增值税率(0.13 = 13%);0 = 免税/不启用",
)
status = models.CharField(
max_length=16, choices=STATUS_CHOICES, default=STATUS_ACTIVE
)
class Meta:
db_table = "catalog_product"
unique_together = [("tenant", "code")]
verbose_name_plural = "商品档案"
indexes = [
models.Index(fields=["tenant", "code"]),
models.Index(fields=["tenant", "name"]),
models.Index(fields=["tenant", "barcode"]),
models.Index(fields=["tenant", "category", "-created_at"]),
]
ordering = ["code"]
def __str__(self):
return f"{self.code} {self.name}"
class UnitConversion(TenantScopedModel):
"""商品多单位换算:1 个该单位 = rate × 基本单位。
例:商品基本单位"瓶",换算"箱" rate=24 → 1 箱 = 24 瓶。
"""
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name="unit_conversions",
)
unit = models.ForeignKey(
Unit,
on_delete=models.PROTECT,
related_name="conversions",
)
rate = models.DecimalField(
max_digits=18, decimal_places=6, help_text="1 个该单位 = rate × 基本单位"
)
class Meta:
db_table = "catalog_unit_conversion"
unique_together = [("tenant", "product", "unit")]
verbose_name_plural = "商品单位换算"
ordering = ["product", "unit"]
def __str__(self):
return f"{self.product.code}:{self.unit.code}×{self.rate}"