feat(client,setting): complete Phase 2 with partitioned client_follow_logs
- 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.
This commit is contained in:
147
apps/client/models/viewing_match.py
Normal file
147
apps/client/models/viewing_match.py
Normal file
@@ -0,0 +1,147 @@
|
||||
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"),
|
||||
]
|
||||
Reference in New Issue
Block a user