baseline: 批次A-D 成果 + membership 半成品(测试红)

This commit is contained in:
agent
2026-09-11 23:11:35 +08:00
commit b3f3095d53
311 changed files with 40540 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
"""商品中心模型: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}"