File size: 6,132 Bytes
50c09c8
 
 
b683f7b
50c09c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8072ab3
50c09c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b683f7b
 
 
 
50c09c8
 
 
 
 
 
b683f7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50c09c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b683f7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50c09c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
"""
Modelos Pydantic usados para validar entrada e formatar saída da API.
"""
import json
from datetime import datetime
from enum import Enum
from typing import Optional

from pydantic import BaseModel, Field, field_validator


class TipoNegocio(str, Enum):
    venda = "Venda"
    locacao = "Locação"


class TipoImovel(str, Enum):
    casa = "Casa"
    apartamento = "Apartamento"
    terreno = "Terreno"
    comercial = "Comercial"
    sala_comercial = "Sala Comercial"
    galpao = "Galpão"
    chacara_sitio = "Chácara/Sítio"
    cobertura = "Cobertura"
    kitnet = "Kitnet/Studio"


class StatusAnuncio(str, Enum):
    disponivel = "Disponível"
    lancamento = "Lançamento"
    vendido = "Vendido"
    alugado = "Alugado"
    reservado = "Reservado"
    inativo = "Inativo"


class AnuncioBase(BaseModel):
    tipo_negocio: TipoNegocio
    tipo_imovel: TipoImovel
    titulo: str = Field(..., min_length=3, max_length=150)
    descricao: str = Field("", max_length=3000)
    endereco: str = Field("", max_length=200)
    bairro: str = Field("", max_length=100)
    cidade: str = Field(..., min_length=2, max_length=100)
    estado: str = Field(..., min_length=2, max_length=2, description="Sigla da UF, ex: PR")
    cep: str = Field("", max_length=10)
    preco: float = Field(..., ge=0)
    condominio: Optional[float] = Field(0, ge=0)
    iptu: Optional[float] = Field(0, ge=0)
    area_util: Optional[float] = Field(0, ge=0)
    area_total: Optional[float] = Field(0, ge=0)
    quartos: Optional[int] = Field(0, ge=0)
    suites: Optional[int] = Field(0, ge=0)
    banheiros: Optional[int] = Field(0, ge=0)
    vagas_garagem: Optional[int] = Field(0, ge=0)
    status: StatusAnuncio = StatusAnuncio.disponivel
    destaque: Optional[bool] = False
    corretor_responsavel: str = Field("", max_length=100)
    telefone_contato: str = Field("", max_length=30)
    midias: Optional[str] = Field(
        "[]",
        description="JSON com a lista de URLs completas de imagens/vídeos, ex: [\"https://.../foto1.jpg\"]",
    )

    @field_validator("estado")
    @classmethod
    def uf_maiuscula(cls, v: str) -> str:
        return v.upper()

    @field_validator("midias", mode="before")
    @classmethod
    def midias_json_valido(cls, v) -> str:
        """Aceita string JSON de lista de URLs, lista Python direta, ou vazio -> normaliza sempre para JSON string."""
        if v in (None, ""):
            return "[]"
        if isinstance(v, list):
            return json.dumps([str(u) for u in v if u])
        try:
            parsed = json.loads(v)
        except (json.JSONDecodeError, TypeError):
            raise ValueError("midias deve ser uma lista JSON válida de URLs, ex: [\"https://.../foto1.jpg\"]")
        if not isinstance(parsed, list):
            raise ValueError("midias deve ser uma lista JSON de URLs")
        return json.dumps([str(u) for u in parsed if u])


class AnuncioCreate(AnuncioBase):
    pass


class AnuncioUpdate(BaseModel):
    """Todos os campos opcionais - usado no PATCH (atualização parcial)."""
    tipo_negocio: Optional[TipoNegocio] = None
    tipo_imovel: Optional[TipoImovel] = None
    titulo: Optional[str] = Field(None, min_length=3, max_length=150)
    descricao: Optional[str] = None
    endereco: Optional[str] = None
    bairro: Optional[str] = None
    cidade: Optional[str] = None
    estado: Optional[str] = Field(None, min_length=2, max_length=2)
    cep: Optional[str] = None
    preco: Optional[float] = Field(None, ge=0)
    condominio: Optional[float] = Field(None, ge=0)
    iptu: Optional[float] = Field(None, ge=0)
    area_util: Optional[float] = Field(None, ge=0)
    area_total: Optional[float] = Field(None, ge=0)
    quartos: Optional[int] = Field(None, ge=0)
    suites: Optional[int] = Field(None, ge=0)
    banheiros: Optional[int] = Field(None, ge=0)
    vagas_garagem: Optional[int] = Field(None, ge=0)
    status: Optional[StatusAnuncio] = None
    destaque: Optional[bool] = None
    corretor_responsavel: Optional[str] = None
    telefone_contato: Optional[str] = None
    midias: Optional[str] = None

    @field_validator("midias", mode="before")
    @classmethod
    def midias_json_valido(cls, v):
        if v in (None, ""):
            return v
        if isinstance(v, list):
            return json.dumps([str(u) for u in v if u])
        try:
            parsed = json.loads(v)
        except (json.JSONDecodeError, TypeError):
            raise ValueError("midias deve ser uma lista JSON válida de URLs")
        if not isinstance(parsed, list):
            raise ValueError("midias deve ser uma lista JSON de URLs")
        return json.dumps([str(u) for u in parsed if u])


class AnuncioResponse(AnuncioBase):
    id: int
    data_cadastro: datetime
    data_atualizacao: datetime


class OrdenacaoAnuncio(str, Enum):
    mais_recentes = "mais_recentes"
    mais_antigos = "mais_antigos"
    preco_asc = "preco_asc"
    preco_desc = "preco_desc"
    area_asc = "area_asc"
    area_desc = "area_desc"


class FacetaItem(BaseModel):
    valor: str
    quantidade: int


class FaixaValores(BaseModel):
    min: float
    max: float


class OpcoesFiltroResponse(BaseModel):
    total_anuncios: int
    tipos_negocio: list[FacetaItem]
    tipos_imovel: list[FacetaItem]
    status: list[FacetaItem]
    cidades: list[FacetaItem]
    bairros: list[FacetaItem]
    faixa_preco: FaixaValores
    faixa_area: FaixaValores
    quartos_disponiveis: list[int]
    vagas_disponiveis: list[int]


class FacetasBusca(BaseModel):
    tipos_imovel: list[FacetaItem]
    cidades: list[FacetaItem]
    bairros: list[FacetaItem]
    status: list[FacetaItem]
    faixa_preco: FaixaValores
    faixa_area: FaixaValores


class BuscaExploratoriaResponse(BaseModel):
    total: int
    skip: int
    limit: int
    ordenar_por: OrdenacaoAnuncio
    resultados: list[AnuncioResponse]
    facetas: FacetasBusca


class ResumoStats(BaseModel):
    total_anuncios: int
    total_venda: int
    total_locacao: int
    total_disponiveis: int
    preco_medio_venda: float
    preco_medio_locacao: float
    por_tipo_imovel: dict
    por_cidade: dict