- apps/client (11 models): Client, ClientContact, ClientRequirement, ClientSchoolPreference, ClientFollowLog (partitioned), ClientFollowLogAttachment, ClientViewing, ClientPropertyMatch, ClientStatusLog, ClientFavoriteFolder, ClientFolderItem - apps/client/0002 RunSQL: PARTITION BY RANGE(created_at) for client_follow_logs + monthly partitions + default; triggers update_client_last_follow + update_client_viewing_progress; partial unique index on client_no WHERE deleted_at IS NULL - apps/setting (4 models): LookupGroup, LookupItem, TenantSetting, FieldRequirementRule (tenant schema only per spec) manage.py check green; all 9 Phase 2 apps complete.
148 lines
4.9 KiB
Python
148 lines
4.9 KiB
Python
from django.contrib.postgres.fields import ArrayField
|
|
from django.db import models
|
|
|
|
from core.enums import (
|
|
ClientPropertyMatchGroup,
|
|
ClientPropertyMatchSource,
|
|
ClientPropertyMatchStatus,
|
|
ClientStatusLogChangeType,
|
|
ClientViewingIntent,
|
|
ClientViewingType,
|
|
)
|
|
from core.models.base import UUIDPrimaryKeyModel
|
|
|
|
|
|
class ClientViewing(UUIDPrimaryKeyModel):
|
|
client = models.ForeignKey(
|
|
"fonrey_client.Client", on_delete=models.RESTRICT, related_name="viewings"
|
|
)
|
|
property = models.ForeignKey(
|
|
"fonrey_property.Property",
|
|
on_delete=models.RESTRICT,
|
|
related_name="client_viewings",
|
|
)
|
|
viewing_type = models.CharField(
|
|
max_length=20, choices=ClientViewingType.choices, default=ClientViewingType.VIEWING
|
|
)
|
|
|
|
agent = models.ForeignKey(
|
|
"org.Staff",
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="led_viewings",
|
|
)
|
|
companion_ids = ArrayField(models.UUIDField(), blank=True, default=list)
|
|
cooperator_ids = ArrayField(models.UUIDField(), blank=True, default=list)
|
|
|
|
scheduled_at = models.DateTimeField(null=True, blank=True)
|
|
viewing_start_at = models.DateTimeField(null=True, blank=True)
|
|
viewing_end_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
situation = models.TextField(blank=True, default="")
|
|
client_intent = models.CharField(
|
|
max_length=20, choices=ClientViewingIntent.choices, blank=True, default=""
|
|
)
|
|
viewing_progress = models.SmallIntegerField(null=True, blank=True)
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
created_by = models.ForeignKey(
|
|
"org.Staff",
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="created_client_viewings",
|
|
)
|
|
|
|
class Meta:
|
|
db_table = "client_viewings"
|
|
indexes = [
|
|
models.Index(
|
|
fields=["client", "-viewing_start_at"], name="idx_cv_client_time"
|
|
),
|
|
models.Index(fields=["property"], name="idx_cv_property"),
|
|
models.Index(fields=["agent"], name="idx_cv_agent"),
|
|
]
|
|
|
|
|
|
class ClientPropertyMatch(UUIDPrimaryKeyModel):
|
|
client = models.ForeignKey(
|
|
"fonrey_client.Client", on_delete=models.CASCADE, related_name="property_matches"
|
|
)
|
|
property = models.ForeignKey(
|
|
"fonrey_property.Property",
|
|
on_delete=models.CASCADE,
|
|
related_name="client_matches",
|
|
)
|
|
|
|
match_source = models.CharField(
|
|
max_length=20,
|
|
choices=ClientPropertyMatchSource.choices,
|
|
default=ClientPropertyMatchSource.RECORDED,
|
|
)
|
|
match_group = models.CharField(
|
|
max_length=30, choices=ClientPropertyMatchGroup.choices, blank=True, default=""
|
|
)
|
|
match_score = models.DecimalField(
|
|
max_digits=5, decimal_places=2, null=True, blank=True
|
|
)
|
|
match_reasons = models.JSONField(null=True, blank=True)
|
|
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=ClientPropertyMatchStatus.choices,
|
|
default=ClientPropertyMatchStatus.SUGGESTED,
|
|
)
|
|
shared_at = models.DateTimeField(null=True, blank=True)
|
|
feedback = models.CharField(max_length=50, blank=True, default="")
|
|
calculated_at = models.DateTimeField(auto_now_add=True)
|
|
created_by = models.ForeignKey(
|
|
"org.Staff",
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="created_matches",
|
|
)
|
|
|
|
class Meta:
|
|
db_table = "client_property_matches"
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=["client", "property"], name="uq_client_match_pair"
|
|
),
|
|
]
|
|
indexes = [
|
|
models.Index(
|
|
fields=["client", "match_source", "match_group"],
|
|
name="idx_cpm_client_grp",
|
|
),
|
|
models.Index(fields=["client", "status"], name="idx_cpm_status"),
|
|
]
|
|
|
|
|
|
class ClientStatusLog(models.Model):
|
|
"""Audit log; record-level immutable (no deleted_at)."""
|
|
|
|
id = models.UUIDField(primary_key=True)
|
|
client = models.ForeignKey(
|
|
"fonrey_client.Client", on_delete=models.RESTRICT, related_name="status_logs"
|
|
)
|
|
change_type = models.CharField(
|
|
max_length=30, choices=ClientStatusLogChangeType.choices
|
|
)
|
|
old_value = models.JSONField(null=True, blank=True)
|
|
new_value = models.JSONField(null=True, blank=True)
|
|
reason = models.TextField(blank=True, default="")
|
|
operator = models.ForeignKey(
|
|
"org.Staff", on_delete=models.RESTRICT, related_name="client_status_changes"
|
|
)
|
|
operated_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = "client_status_logs"
|
|
indexes = [
|
|
models.Index(fields=["client", "-operated_at"], name="idx_csl_client"),
|
|
models.Index(fields=["change_type", "-operated_at"], name="idx_csl_type"),
|
|
]
|