Sync DATA_MODEL_COMPLEX.md field-level Chinese annotations to Django models across 10 complex tables (Complex, ComplexAlias, ComplexBusinessArea, ComplexSchool, ComplexMetroStation, Building, RoomUnit, ComplexPhoto, ComplexAttachment, ComplexPriceTrend).
842 lines
24 KiB
Python
842 lines
24 KiB
Python
from django.contrib.postgres.fields import ArrayField
|
||
from django.contrib.postgres.indexes import GinIndex
|
||
from django.contrib.postgres.search import SearchVectorField
|
||
from django.db import models
|
||
|
||
from core.enums import (
|
||
ComplexBuildingStructure,
|
||
ComplexBuildingType,
|
||
ComplexElectricityType,
|
||
ComplexPhotoCategory,
|
||
ComplexPropertyUsageType,
|
||
ComplexWaterType,
|
||
SchoolZoneType,
|
||
)
|
||
from core.models.base import SoftDeleteModel, TimeStampedModel, UUIDPrimaryKeyModel
|
||
|
||
|
||
class Complex(SoftDeleteModel):
|
||
name = models.CharField(
|
||
max_length=200,
|
||
verbose_name="楼盘名称",
|
||
help_text="标准楼盘名称,不可在编辑页直接修改(需走合并/申请流程)",
|
||
)
|
||
district = models.ForeignKey(
|
||
"region.District",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="complexes",
|
||
verbose_name="所属城区",
|
||
)
|
||
address = models.CharField(
|
||
max_length=500,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="详细地址",
|
||
help_text="不可在编辑页修改,需走纠错流程",
|
||
)
|
||
address_summary = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="概要地址",
|
||
help_text='如「海波路1000弄」,可编辑',
|
||
)
|
||
latitude = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=7,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="纬度",
|
||
help_text="WGS84,完整度目标 ≥ 90%",
|
||
)
|
||
longitude = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=7,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="经度",
|
||
help_text="WGS84",
|
||
)
|
||
|
||
property_usage_types = ArrayField(
|
||
models.CharField(max_length=30, choices=ComplexPropertyUsageType.choices),
|
||
default=list,
|
||
blank=True,
|
||
verbose_name="物业类型",
|
||
help_text="多选:residential / villa / commercial_residential / commercial / office / other",
|
||
)
|
||
building_structure = models.CharField(
|
||
max_length=30,
|
||
blank=True,
|
||
default="",
|
||
choices=ComplexBuildingStructure.choices,
|
||
verbose_name="楼栋结构",
|
||
help_text="unit_room=单元-房号 / other=其他",
|
||
)
|
||
building_type = models.CharField(
|
||
max_length=20,
|
||
blank=True,
|
||
default="",
|
||
choices=ComplexBuildingType.choices,
|
||
verbose_name="建筑类型",
|
||
help_text="slab=板楼 / tower=塔楼 / slab_tower=板塔结合",
|
||
)
|
||
land_use_years = models.CharField(
|
||
max_length=30,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="土地使用年限",
|
||
help_text='如「70年」',
|
||
)
|
||
built_year = models.SmallIntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="竣工年份",
|
||
help_text="可多选时存最早竣工年",
|
||
)
|
||
built_years = ArrayField(
|
||
models.SmallIntegerField(),
|
||
default=list,
|
||
blank=True,
|
||
verbose_name="竣工年份多值",
|
||
help_text="楼盘分期竣工",
|
||
)
|
||
ownership_category = ArrayField(
|
||
models.CharField(max_length=30),
|
||
default=list,
|
||
blank=True,
|
||
verbose_name="权属类别",
|
||
help_text="多选(运营维护枚举)",
|
||
)
|
||
total_units = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="单元总数",
|
||
)
|
||
total_households = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="总户数",
|
||
)
|
||
|
||
total_floor_area = models.DecimalField(
|
||
max_digits=12,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="小区总建筑面积",
|
||
help_text="单位:m²",
|
||
)
|
||
plot_area = models.DecimalField(
|
||
max_digits=12,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="小区占地面积",
|
||
help_text="单位:m²",
|
||
)
|
||
plot_ratio = models.DecimalField(
|
||
max_digits=5,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="容积率",
|
||
)
|
||
green_rate = models.DecimalField(
|
||
max_digits=5,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="绿化率",
|
||
help_text="单位:%",
|
||
)
|
||
developer = models.CharField(
|
||
max_length=200,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="开发商",
|
||
)
|
||
|
||
property_company = models.CharField(
|
||
max_length=200,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="物业公司",
|
||
)
|
||
property_fee = models.DecimalField(
|
||
max_digits=8,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="物业费",
|
||
help_text="单位:元/m²/月",
|
||
)
|
||
property_phone = models.CharField(
|
||
max_length=30,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="物业电话",
|
||
)
|
||
|
||
parking_total = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="车位总数",
|
||
)
|
||
parking_underground = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="地下车位数",
|
||
)
|
||
parking_ratio = models.CharField(
|
||
max_length=20,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="车位配比",
|
||
help_text='如「100:63」',
|
||
)
|
||
|
||
water_type = models.CharField(
|
||
max_length=10,
|
||
blank=True,
|
||
default="",
|
||
choices=ComplexWaterType.choices,
|
||
verbose_name="水费类型",
|
||
help_text="civil=民水 / commercial=商水",
|
||
)
|
||
electricity_type = models.CharField(
|
||
max_length=10,
|
||
blank=True,
|
||
default="",
|
||
choices=ComplexElectricityType.choices,
|
||
verbose_name="电费类型",
|
||
help_text="civil=民电 / commercial=商电",
|
||
)
|
||
has_central_heating = models.BooleanField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="是否统一供暖",
|
||
)
|
||
has_gas = models.BooleanField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="是否有燃气",
|
||
)
|
||
remarks = models.TextField(
|
||
blank=True,
|
||
default="",
|
||
verbose_name="备注",
|
||
)
|
||
|
||
lock_building = models.BooleanField(
|
||
default=False,
|
||
verbose_name="楼栋锁",
|
||
help_text="锁定后不可增删楼栋",
|
||
)
|
||
lock_room = models.BooleanField(
|
||
default=False,
|
||
verbose_name="房号锁",
|
||
)
|
||
lock_info = models.BooleanField(
|
||
default=False,
|
||
verbose_name="信息锁",
|
||
help_text="锁定后基本信息只读",
|
||
)
|
||
lock_standard_room = models.BooleanField(
|
||
default=False,
|
||
verbose_name="标准房号锁",
|
||
)
|
||
|
||
search_vector = SearchVectorField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="全文检索向量",
|
||
help_text="由触发器自动维护(name + alias + address)",
|
||
)
|
||
|
||
is_active = models.BooleanField(
|
||
default=True,
|
||
verbose_name="是否启用",
|
||
help_text="FALSE=已停用楼盘",
|
||
)
|
||
created_by = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="created_complexes",
|
||
verbose_name="创建人",
|
||
)
|
||
updated_by = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="updated_complexes",
|
||
verbose_name="最后更新人",
|
||
)
|
||
version = models.IntegerField(
|
||
default=1,
|
||
verbose_name="版本号",
|
||
help_text="乐观锁;UPDATE 时 +1;应用层检测 0 行受影响时抛 ConflictError",
|
||
)
|
||
|
||
business_areas = models.ManyToManyField(
|
||
"region.BusinessArea",
|
||
through="fonrey_complex.ComplexBusinessArea",
|
||
related_name="complexes",
|
||
verbose_name="关联商圈",
|
||
)
|
||
schools = models.ManyToManyField(
|
||
"region.School",
|
||
through="fonrey_complex.ComplexSchool",
|
||
related_name="complexes",
|
||
verbose_name="对口学校",
|
||
)
|
||
metro_stations = models.ManyToManyField(
|
||
"region.MetroStation",
|
||
through="fonrey_complex.ComplexMetroStation",
|
||
related_name="complexes",
|
||
verbose_name="周边地铁站",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "complexes"
|
||
verbose_name = "楼盘"
|
||
verbose_name_plural = "楼盘"
|
||
indexes = [
|
||
models.Index(
|
||
fields=["district"],
|
||
name="idx_complexes_district",
|
||
condition=models.Q(deleted_at__isnull=True),
|
||
),
|
||
GinIndex(fields=["search_vector"], name="idx_complexes_search"),
|
||
models.Index(
|
||
fields=["latitude", "longitude"],
|
||
name="idx_complexes_geo",
|
||
condition=models.Q(deleted_at__isnull=True, latitude__isnull=False),
|
||
),
|
||
models.Index(
|
||
fields=["is_active"],
|
||
name="idx_complexes_active",
|
||
condition=models.Q(deleted_at__isnull=True),
|
||
),
|
||
]
|
||
ordering = ["name"]
|
||
|
||
def __str__(self) -> str:
|
||
return self.name
|
||
|
||
|
||
class ComplexAlias(UUIDPrimaryKeyModel):
|
||
complex = models.ForeignKey(
|
||
"fonrey_complex.Complex",
|
||
on_delete=models.CASCADE,
|
||
related_name="aliases",
|
||
verbose_name="所属楼盘",
|
||
help_text="别名随楼盘级联删除",
|
||
)
|
||
alias = models.CharField(
|
||
max_length=200,
|
||
verbose_name="别名",
|
||
help_text="最多20字/条,多别名多行存储",
|
||
)
|
||
is_system = models.BooleanField(
|
||
default=False,
|
||
verbose_name="是否系统别名",
|
||
help_text="TRUE=系统/标准别名(只读),FALSE=用户自定义",
|
||
)
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
)
|
||
created_by = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="created_complex_aliases",
|
||
verbose_name="创建人",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "complex_aliases"
|
||
verbose_name = "楼盘别名"
|
||
verbose_name_plural = "楼盘别名"
|
||
indexes = [
|
||
models.Index(fields=["complex"], name="idx_complex_aliases_complex"),
|
||
]
|
||
ordering = ["complex_id", "alias"]
|
||
|
||
def __str__(self) -> str:
|
||
return self.alias
|
||
|
||
|
||
class ComplexBusinessArea(models.Model):
|
||
complex = models.ForeignKey(
|
||
"fonrey_complex.Complex",
|
||
on_delete=models.CASCADE,
|
||
related_name="complex_business_areas",
|
||
verbose_name="所属楼盘",
|
||
)
|
||
business_area = models.ForeignKey(
|
||
"region.BusinessArea",
|
||
on_delete=models.CASCADE,
|
||
related_name="complex_links",
|
||
verbose_name="关联商圈",
|
||
)
|
||
is_primary = models.BooleanField(
|
||
default=False,
|
||
verbose_name="是否主商圈",
|
||
help_text="主商圈唯一,用于列表显示",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "complex_business_areas"
|
||
verbose_name = "楼盘商圈关联"
|
||
verbose_name_plural = "楼盘商圈关联"
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=["complex", "business_area"],
|
||
name="pk_complex_business_areas",
|
||
),
|
||
models.UniqueConstraint(
|
||
fields=["complex"],
|
||
condition=models.Q(is_primary=True),
|
||
name="uq_complex_biz_area_primary",
|
||
),
|
||
]
|
||
|
||
|
||
class ComplexSchool(models.Model):
|
||
complex = models.ForeignKey(
|
||
"fonrey_complex.Complex",
|
||
on_delete=models.CASCADE,
|
||
related_name="complex_schools",
|
||
verbose_name="所属楼盘",
|
||
)
|
||
school = models.ForeignKey(
|
||
"region.School",
|
||
on_delete=models.CASCADE,
|
||
related_name="complex_links",
|
||
verbose_name="对口学校",
|
||
)
|
||
zone_type = models.CharField(
|
||
max_length=30,
|
||
blank=True,
|
||
default="",
|
||
choices=SchoolZoneType.choices,
|
||
verbose_name="学区类型",
|
||
help_text="guaranteed=对口(直升) / reference=参考(可能入读) / lottery=摇号",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "complex_schools"
|
||
verbose_name = "楼盘学校关联"
|
||
verbose_name_plural = "楼盘学校关联"
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=["complex", "school"],
|
||
name="pk_complex_schools",
|
||
),
|
||
]
|
||
indexes = [
|
||
models.Index(fields=["school"], name="idx_complex_schools_school"),
|
||
]
|
||
|
||
|
||
class ComplexMetroStation(models.Model):
|
||
complex = models.ForeignKey(
|
||
"fonrey_complex.Complex",
|
||
on_delete=models.CASCADE,
|
||
related_name="complex_metro_stations",
|
||
verbose_name="所属楼盘",
|
||
)
|
||
station = models.ForeignKey(
|
||
"region.MetroStation",
|
||
on_delete=models.CASCADE,
|
||
related_name="complex_links",
|
||
verbose_name="关联地铁站",
|
||
)
|
||
distance_meters = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="步行距离",
|
||
help_text="单位:米",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "complex_metro_stations"
|
||
verbose_name = "楼盘地铁站关联"
|
||
verbose_name_plural = "楼盘地铁站关联"
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=["complex", "station"],
|
||
name="pk_complex_metro_stations",
|
||
),
|
||
]
|
||
indexes = [
|
||
models.Index(fields=["complex"], name="idx_complex_metro_complex"),
|
||
models.Index(fields=["station"], name="idx_complex_metro_station"),
|
||
]
|
||
|
||
|
||
class Building(TimeStampedModel):
|
||
complex = models.ForeignKey(
|
||
"fonrey_complex.Complex",
|
||
on_delete=models.CASCADE,
|
||
related_name="buildings",
|
||
verbose_name="所属楼盘",
|
||
)
|
||
name = models.CharField(
|
||
max_length=50,
|
||
verbose_name="楼栋名称",
|
||
help_text='如「1号楼」「A栋2单元」',
|
||
)
|
||
is_standard = models.BooleanField(
|
||
default=False,
|
||
verbose_name="是否标准结构",
|
||
help_text="TRUE=已经运营核准",
|
||
)
|
||
property_usage_type = models.CharField(
|
||
max_length=30,
|
||
blank=True,
|
||
default="",
|
||
choices=ComplexPropertyUsageType.choices,
|
||
verbose_name="物业类型",
|
||
help_text="可与楼盘不同,如商住楼盘内有纯商铺楼栋",
|
||
)
|
||
built_year = models.SmallIntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="竣工年份",
|
||
)
|
||
total_floors = models.SmallIntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="总层数",
|
||
)
|
||
land_use_years = models.CharField(
|
||
max_length=30,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="土地使用年限",
|
||
)
|
||
has_elevator = models.BooleanField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="是否有电梯",
|
||
)
|
||
school = models.ForeignKey(
|
||
"region.School",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="buildings",
|
||
verbose_name="对口学校",
|
||
help_text="楼栋级别的学区差异",
|
||
)
|
||
is_active = models.BooleanField(
|
||
default=True,
|
||
verbose_name="是否启用",
|
||
help_text="FALSE=已停用(楼栋被删除或合并)",
|
||
)
|
||
created_by = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="created_buildings",
|
||
verbose_name="创建人",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "buildings"
|
||
verbose_name = "楼栋"
|
||
verbose_name_plural = "楼栋"
|
||
indexes = [
|
||
models.Index(
|
||
fields=["complex"],
|
||
name="idx_buildings_complex",
|
||
condition=models.Q(is_active=True),
|
||
),
|
||
]
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=["complex", "name"],
|
||
condition=models.Q(is_active=True),
|
||
name="uq_buildings_complex_name",
|
||
),
|
||
]
|
||
ordering = ["complex_id", "name"]
|
||
|
||
def __str__(self) -> str:
|
||
return self.name
|
||
|
||
|
||
class RoomUnit(TimeStampedModel):
|
||
building = models.ForeignKey(
|
||
"fonrey_complex.Building",
|
||
on_delete=models.CASCADE,
|
||
related_name="room_units",
|
||
verbose_name="所属楼栋",
|
||
)
|
||
floor = models.SmallIntegerField(
|
||
verbose_name="楼层",
|
||
help_text="实际层数,地下为负数",
|
||
)
|
||
floor_name = models.CharField(
|
||
max_length=20,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="楼层名称",
|
||
help_text='如「1层」「B1层」',
|
||
)
|
||
room_no = models.CharField(
|
||
max_length=30,
|
||
verbose_name="房号",
|
||
help_text='如「01」「101」',
|
||
)
|
||
display_no = models.CharField(
|
||
max_length=50,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="展示房号",
|
||
help_text='展示用完整房号,如「3-1-101」',
|
||
)
|
||
is_standard = models.BooleanField(
|
||
default=False,
|
||
verbose_name="是否标准化",
|
||
help_text="TRUE=已归一化为标准结构",
|
||
)
|
||
is_active = models.BooleanField(
|
||
default=True,
|
||
verbose_name="是否启用",
|
||
help_text="FALSE=已拆除/不存在",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "room_units"
|
||
verbose_name = "房号单元"
|
||
verbose_name_plural = "房号单元"
|
||
indexes = [
|
||
models.Index(
|
||
fields=["building"],
|
||
name="idx_room_units_building",
|
||
condition=models.Q(is_active=True),
|
||
),
|
||
]
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=["building", "floor", "room_no"],
|
||
condition=models.Q(is_active=True),
|
||
name="uq_room_units_unique",
|
||
),
|
||
]
|
||
ordering = ["building_id", "-floor", "room_no"]
|
||
|
||
def __str__(self) -> str:
|
||
return self.display_no or f"{self.floor}/{self.room_no}"
|
||
|
||
|
||
class ComplexPhoto(UUIDPrimaryKeyModel):
|
||
complex = models.ForeignKey(
|
||
"fonrey_complex.Complex",
|
||
on_delete=models.CASCADE,
|
||
related_name="photos",
|
||
verbose_name="所属楼盘",
|
||
)
|
||
category = models.CharField(
|
||
max_length=20,
|
||
choices=ComplexPhotoCategory.choices,
|
||
verbose_name="照片类别",
|
||
help_text="complex=楼盘图 / layout=户型图 / vr=VR全景 / other=其他",
|
||
)
|
||
file_key = models.TextField(
|
||
verbose_name="文件存储路径",
|
||
help_text="R2/S3 路径",
|
||
)
|
||
thumbnail_key = models.TextField(
|
||
blank=True,
|
||
default="",
|
||
verbose_name="缩略图路径",
|
||
)
|
||
file_name = models.CharField(
|
||
max_length=255,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="原始文件名",
|
||
)
|
||
file_size = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="文件大小",
|
||
help_text="单位:bytes",
|
||
)
|
||
width = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="图片宽度",
|
||
help_text="单位:px",
|
||
)
|
||
height = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="图片高度",
|
||
help_text="单位:px",
|
||
)
|
||
is_cover = models.BooleanField(
|
||
default=False,
|
||
verbose_name="是否封面图",
|
||
help_text="楼盘封面图(每楼盘唯一)",
|
||
)
|
||
sort_order = models.SmallIntegerField(
|
||
default=0,
|
||
verbose_name="排序顺序",
|
||
help_text="同类别内的排序顺序",
|
||
)
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
)
|
||
created_by = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="created_complex_photos",
|
||
verbose_name="上传人",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "complex_photos"
|
||
verbose_name = "楼盘照片"
|
||
verbose_name_plural = "楼盘照片"
|
||
indexes = [
|
||
models.Index(fields=["complex"], name="idx_complex_photos_complex"),
|
||
models.Index(fields=["complex", "category"], name="idx_complex_photos_category"),
|
||
]
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=["complex"],
|
||
condition=models.Q(is_cover=True),
|
||
name="uq_complex_photos_cover",
|
||
),
|
||
]
|
||
ordering = ["complex_id", "sort_order"]
|
||
|
||
|
||
class ComplexAttachment(UUIDPrimaryKeyModel):
|
||
complex = models.ForeignKey(
|
||
"fonrey_complex.Complex",
|
||
on_delete=models.CASCADE,
|
||
related_name="attachments",
|
||
verbose_name="所属楼盘",
|
||
)
|
||
file_key = models.TextField(
|
||
verbose_name="文件存储路径",
|
||
help_text="R2/S3 存储路径",
|
||
)
|
||
file_name = models.CharField(
|
||
max_length=255,
|
||
verbose_name="原始文件名",
|
||
)
|
||
file_size = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="文件大小",
|
||
help_text="单位:bytes",
|
||
)
|
||
file_type = models.CharField(
|
||
max_length=50,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="文件类型",
|
||
help_text="MIME type",
|
||
)
|
||
sort_order = models.SmallIntegerField(
|
||
default=0,
|
||
verbose_name="排序顺序",
|
||
)
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
)
|
||
created_by = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="created_complex_attachments",
|
||
verbose_name="上传人",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "complex_attachments"
|
||
verbose_name = "楼盘附件"
|
||
verbose_name_plural = "楼盘附件"
|
||
ordering = ["complex_id", "sort_order"]
|
||
|
||
|
||
class ComplexPriceTrend(UUIDPrimaryKeyModel):
|
||
complex = models.ForeignKey(
|
||
"fonrey_complex.Complex",
|
||
on_delete=models.CASCADE,
|
||
related_name="price_trends",
|
||
verbose_name="所属楼盘",
|
||
)
|
||
record_month = models.DateField(
|
||
verbose_name="月份",
|
||
help_text="统一存为该月1日,如 2026-04-01",
|
||
)
|
||
avg_sale_price = models.DecimalField(
|
||
max_digits=12,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="月均售价",
|
||
help_text="单位:万元/套",
|
||
)
|
||
avg_unit_price = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="月均单价",
|
||
help_text="单位:元/m²",
|
||
)
|
||
transaction_count = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="成交套数",
|
||
)
|
||
listing_count = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="当月挂牌套数",
|
||
)
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "complex_price_trends"
|
||
verbose_name = "楼盘价格走势"
|
||
verbose_name_plural = "楼盘价格走势"
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=["complex", "record_month"],
|
||
name="uq_complex_price_trend_month",
|
||
),
|
||
]
|
||
indexes = [
|
||
models.Index(
|
||
fields=["complex", "-record_month"],
|
||
name="idx_cpx_price_trend_complex",
|
||
),
|
||
]
|
||
ordering = ["complex_id", "-record_month"]
|