File size: 5,610 Bytes
b4f2238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""Module for contract."""

from __future__ import annotations

import contextlib
from typing import TYPE_CHECKING, Any, ClassVar

from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from apps.core.models.enums import CaseType

if TYPE_CHECKING:
    from django.db.models.fields.related_descriptors import RelatedManager

    from apps.cases.models import Case

    from .client_payment import ClientPaymentRecord
    from .finalized_material import FinalizedMaterial
    from .finance import ContractFinanceLog
    from .folder_binding import ContractFolderBinding
    from .party import ContractAssignment, ContractParty
    from .payment import ContractPayment
    from .supplementary import SupplementaryAgreement


class ContractStatus(models.TextChoices):
    ACTIVE = "active", _("在办")
    CLOSED = "closed", _("已结案")
    ARCHIVED = "archived", _("已归档")


class FeeMode(models.TextChoices):
    FIXED = "FIXED", _("固定收费")
    SEMI_RISK = "SEMI_RISK", _("半风险收费")
    FULL_RISK = "FULL_RISK", _("全风险收费")
    CUSTOM = "CUSTOM", _("自定义")


class Contract(models.Model):
    id: int
    name: models.CharField = models.CharField(max_length=100, verbose_name=_("合同名称"))
    case_type: models.CharField = models.CharField(max_length=32, choices=CaseType.choices, verbose_name=_("合同类型"))
    status: models.CharField = models.CharField(
        max_length=32, choices=ContractStatus.choices, default=ContractStatus.ACTIVE, verbose_name=_("合同状态")
    )
    specified_date: models.DateField = models.DateField(default=timezone.localdate, verbose_name=_("指定日期"))
    start_date: models.DateField = models.DateField(blank=True, null=True, verbose_name=_("开始日期"))
    end_date: models.DateField = models.DateField(blank=True, null=True, verbose_name=_("结束日期"))
    is_archived: models.BooleanField = models.BooleanField(default=False, verbose_name=_("是否已建档"))
    filing_number: models.CharField = models.CharField(
        max_length=50,
        blank=True,
        null=True,
        unique=True,
        verbose_name=_("建档编号"),
        help_text=_("格式: {年份}_{合同类型}_{HT}_{序号}"),
    )
    fee_mode: models.CharField = models.CharField(
        max_length=16, choices=FeeMode.choices, default=FeeMode.FIXED, verbose_name=_("收费模式")
    )
    fixed_amount: models.DecimalField = models.DecimalField(
        max_digits=14, decimal_places=2, blank=True, null=True, verbose_name=_("固定/前期律师费")
    )
    risk_rate: models.DecimalField = models.DecimalField(
        max_digits=5, decimal_places=2, blank=True, null=True, verbose_name=_("风险比例(%)")
    )
    custom_terms: models.TextField = models.TextField(blank=True, null=True, verbose_name=_("自定义收费条款"))
    law_firm_oa_url: models.URLField = models.URLField(
        max_length=500,
        blank=True,
        null=True,
        verbose_name=_("律所OA链接"),
        help_text=_("跳转至律所OA系统中该合同的页面"),
    )
    law_firm_oa_case_number: models.CharField = models.CharField(
        max_length=100,
        blank=True,
        null=True,
        verbose_name=_("律所OA案件编号"),
        help_text=_("律所OA系统中的案件编号"),
    )
    representation_stages: Any = models.JSONField(default=list, blank=True, verbose_name=_("代理阶段"))

    if TYPE_CHECKING:
        cases: RelatedManager[Case]
        finance_logs: RelatedManager[ContractFinanceLog]
        folder_binding: ContractFolderBinding
        contract_parties: RelatedManager[ContractParty]
        assignments: RelatedManager[ContractAssignment]
        payments: RelatedManager[ContractPayment]
        supplementary_agreements: RelatedManager[SupplementaryAgreement]
        finalized_materials: RelatedManager[FinalizedMaterial]
        client_payment_records: RelatedManager[ClientPaymentRecord]

    class Meta:
        verbose_name = _("合同")
        verbose_name_plural = _("合同")
        indexes: ClassVar = [
            # 单字段索引 - 用于基本过滤
            models.Index(fields=["case_type"]),
            models.Index(fields=["status"]),
            models.Index(fields=["is_archived"]),
            models.Index(fields=["specified_date"]),
            models.Index(fields=["-specified_date"]),
            models.Index(fields=["filing_number"]),
            # 复合索引 - 用于常见的组合查询
            # 按案件类型和状态查询(常用于列表过滤)
            models.Index(fields=["case_type", "status"]),
            # 按状态和指定日期查询(常用于时间范围过滤)
            models.Index(fields=["status", "-specified_date"]),
            # 按是否建档和指定日期查询(常用于归档管理)
            models.Index(fields=["is_archived", "-specified_date"]),
            # 按案件类型、状态和指定日期查询(常用于复杂过滤)
            models.Index(fields=["case_type", "status", "-specified_date"]),
        ]

    def __str__(self) -> str:
        return str(self.name)

    def clean(self) -> None:
        from apps.contracts.validators import normalize_representation_stages
        from apps.core.exceptions import ValidationException

        ctype = getattr(self, "case_type", None)
        rep = getattr(self, "representation_stages", None)
        with contextlib.suppress(ValidationException):
            self.representation_stages = normalize_representation_stages(ctype, rep, strict=False)