Files
fonrey/apps/client/models/viewing_match.py
Sisyphus ed40de4050 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.
2026-04-29 17:33:58 +08:00

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"),
]