sienge-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sienge/__init__.py +52 -0
- sienge/auth.py +23 -0
- sienge/client.py +146 -0
- sienge/endpoints/__init__.py +27 -0
- sienge/endpoints/base.py +187 -0
- sienge/endpoints/bulk.py +264 -0
- sienge/endpoints/comercial.py +237 -0
- sienge/endpoints/contabilidade.py +165 -0
- sienge/endpoints/credores.py +75 -0
- sienge/endpoints/engenharia.py +200 -0
- sienge/endpoints/financeiro.py +361 -0
- sienge/endpoints/patrimonio.py +57 -0
- sienge/endpoints/suprimentos.py +280 -0
- sienge/endpoints/tabelas.py +121 -0
- sienge/endpoints/webhooks.py +52 -0
- sienge/exceptions.py +40 -0
- sienge/models/__init__.py +17 -0
- sienge/models/comercial.py +103 -0
- sienge/models/credores.py +50 -0
- sienge/models/financeiro.py +179 -0
- sienge/models/obra.py +76 -0
- sienge/models/suprimentos.py +137 -0
- sienge/rate_limiter.py +84 -0
- sienge/utils.py +118 -0
- sienge_python-0.1.0.dist-info/METADATA +208 -0
- sienge_python-0.1.0.dist-info/RECORD +28 -0
- sienge_python-0.1.0.dist-info/WHEEL +4 -0
- sienge_python-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sienge.endpoints.comercial — Endpoints Comerciais (clientes, contratos, unidades).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Iterator
|
|
8
|
+
|
|
9
|
+
from .base import BaseEndpoints
|
|
10
|
+
from ..models.comercial import Cliente, Unidade
|
|
11
|
+
from ..utils import paginate
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ComercialEndpoints(BaseEndpoints):
|
|
15
|
+
"""Endpoints Comerciais: clientes, contratos de venda, unidades."""
|
|
16
|
+
|
|
17
|
+
# ── Clientes ───────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
def list_clientes(self, limit: int | None = None) -> list[Cliente]:
|
|
20
|
+
"""Lista clientes.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
limit: Limite de resultados (None = todos).
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Lista de Cliente.
|
|
27
|
+
"""
|
|
28
|
+
return list(self.iter_clientes(max_results=limit))
|
|
29
|
+
|
|
30
|
+
def iter_clientes(self, max_results: int | None = None) -> Iterator[Cliente]:
|
|
31
|
+
"""Itera sobre clientes com paginacao.
|
|
32
|
+
|
|
33
|
+
Yields:
|
|
34
|
+
Cliente.
|
|
35
|
+
"""
|
|
36
|
+
def fetch(offset: int, limit: int) -> dict:
|
|
37
|
+
return self._get("/customers", params={"offset": offset, "limit": limit})
|
|
38
|
+
|
|
39
|
+
for item in paginate(fetch, max_results=max_results):
|
|
40
|
+
yield Cliente.from_api(item)
|
|
41
|
+
|
|
42
|
+
def get_cliente(self, client_id: int) -> Cliente:
|
|
43
|
+
"""Busca um cliente pelo ID.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
client_id: ID do cliente.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Cliente.
|
|
50
|
+
"""
|
|
51
|
+
data = self._get(f"/customers/{client_id}")
|
|
52
|
+
return Cliente.from_api(data)
|
|
53
|
+
|
|
54
|
+
def search_clientes(self, nome: str) -> list[Cliente]:
|
|
55
|
+
"""Busca clientes pelo nome.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
nome: Nome ou parte do nome.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Lista de Cliente.
|
|
62
|
+
"""
|
|
63
|
+
data = self._get("/customers", params={"name": nome})
|
|
64
|
+
results = data.get("results", data if isinstance(data, list) else [])
|
|
65
|
+
return [Cliente.from_api(r) for r in results]
|
|
66
|
+
|
|
67
|
+
# ── Contratos de Venda (/sales-contracts) ───────────────────────────
|
|
68
|
+
|
|
69
|
+
def list_contratos_venda(
|
|
70
|
+
self,
|
|
71
|
+
limit: int | None = None,
|
|
72
|
+
) -> list[dict]:
|
|
73
|
+
"""Lista contratos de venda.
|
|
74
|
+
|
|
75
|
+
NOTA: /contracts NAO EXISTE. Usar /sales-contracts.
|
|
76
|
+
Para contratos de suprimentos, usar SuprimentosEndpoints.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
limit: Limite de resultados.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Lista de dicts com contratos.
|
|
83
|
+
"""
|
|
84
|
+
params: dict = {}
|
|
85
|
+
if limit:
|
|
86
|
+
params["limit"] = min(limit, 200)
|
|
87
|
+
|
|
88
|
+
data = self._get("/sales-contracts", params=params)
|
|
89
|
+
results = data.get("results", data if isinstance(data, list) else [])
|
|
90
|
+
return results
|
|
91
|
+
|
|
92
|
+
def get_contrato_venda(self, contract_id: int) -> dict:
|
|
93
|
+
"""Busca um contrato de venda pelo ID.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
contract_id: ID do contrato.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Dict com dados do contrato.
|
|
100
|
+
"""
|
|
101
|
+
return self._get(f"/sales-contracts/{contract_id}")
|
|
102
|
+
|
|
103
|
+
# ── Contas a Receber (/accounts-receivable) ──────────────────────
|
|
104
|
+
|
|
105
|
+
def list_titulos_receber(
|
|
106
|
+
self,
|
|
107
|
+
customer_id: int,
|
|
108
|
+
company_id: int | None = None,
|
|
109
|
+
cost_center_id: int | None = None,
|
|
110
|
+
limit: int | None = None,
|
|
111
|
+
) -> list[dict]:
|
|
112
|
+
"""Lista titulos a receber de um cliente.
|
|
113
|
+
|
|
114
|
+
NOTA: /receivable-bills NAO EXISTE.
|
|
115
|
+
Endpoint correto: /accounts-receivable/receivable-bills
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
customer_id: ID do cliente (OBRIGATORIO).
|
|
119
|
+
company_id: ID da empresa (opcional).
|
|
120
|
+
cost_center_id: ID do centro de custo (opcional).
|
|
121
|
+
limit: Limite de resultados.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Lista de dicts com titulos a receber.
|
|
125
|
+
"""
|
|
126
|
+
params: dict = {"customerId": customer_id}
|
|
127
|
+
if company_id:
|
|
128
|
+
params["companyId"] = company_id
|
|
129
|
+
if cost_center_id:
|
|
130
|
+
params["costCenterId"] = cost_center_id
|
|
131
|
+
if limit:
|
|
132
|
+
params["limit"] = min(limit, 200)
|
|
133
|
+
|
|
134
|
+
data = self._get("/accounts-receivable/receivable-bills", params=params)
|
|
135
|
+
results = data.get("results", data if isinstance(data, list) else [])
|
|
136
|
+
return results
|
|
137
|
+
|
|
138
|
+
# ── Unidades ───────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
def list_unidades(
|
|
141
|
+
self,
|
|
142
|
+
building_id: int | None = None,
|
|
143
|
+
limit: int | None = None,
|
|
144
|
+
) -> list[Unidade]:
|
|
145
|
+
"""Lista unidades imobiliarias.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
building_id: Filtrar por obra.
|
|
149
|
+
limit: Limite de resultados.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Lista de Unidade.
|
|
153
|
+
"""
|
|
154
|
+
params: dict = {}
|
|
155
|
+
if building_id:
|
|
156
|
+
params["buildingId"] = building_id
|
|
157
|
+
if limit:
|
|
158
|
+
params["limit"] = min(limit, 200)
|
|
159
|
+
|
|
160
|
+
data = self._get("/units", params=params)
|
|
161
|
+
results = data.get("results", data if isinstance(data, list) else [])
|
|
162
|
+
return [Unidade.from_api(r) for r in results]
|
|
163
|
+
|
|
164
|
+
def get_unidade(self, unit_id: int) -> Unidade:
|
|
165
|
+
"""Busca uma unidade pelo ID.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
unit_id: ID da unidade.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Unidade.
|
|
172
|
+
"""
|
|
173
|
+
data = self._get(f"/units/{unit_id}")
|
|
174
|
+
return Unidade.from_api(data)
|
|
175
|
+
|
|
176
|
+
# ── Tipos de Cliente (/customer-types) ───────────────────────────
|
|
177
|
+
|
|
178
|
+
def list_tipos_cliente(self) -> list[dict]:
|
|
179
|
+
"""Lista tipos de cliente cadastrados.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Lista de dicts com tipos de cliente.
|
|
183
|
+
"""
|
|
184
|
+
data = self._get("/customer-types")
|
|
185
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
186
|
+
|
|
187
|
+
# ── Unidades — Detalhes Imobiliarios ─────────────────────────────
|
|
188
|
+
|
|
189
|
+
def list_caracteristicas_unidades(self) -> list[dict]:
|
|
190
|
+
"""Lista caracteristicas de unidades imobiliarias.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Lista de dicts com caracteristicas.
|
|
194
|
+
"""
|
|
195
|
+
data = self._get("/units/characteristics")
|
|
196
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
197
|
+
|
|
198
|
+
def list_situacoes_unidades(self) -> list[dict]:
|
|
199
|
+
"""Lista situacoes possiveis de unidades.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Lista de dicts com situacoes.
|
|
203
|
+
"""
|
|
204
|
+
data = self._get("/units/situations")
|
|
205
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
206
|
+
|
|
207
|
+
def list_tipos_imovel(self) -> list[dict]:
|
|
208
|
+
"""Lista tipos de imovel.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Lista de dicts com tipos (4 na CONIN).
|
|
212
|
+
"""
|
|
213
|
+
data = self._get("/property-types")
|
|
214
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
215
|
+
|
|
216
|
+
def get_mapa_imobiliario(
|
|
217
|
+
self,
|
|
218
|
+
cost_center_id: int,
|
|
219
|
+
start_date: str,
|
|
220
|
+
end_date: str,
|
|
221
|
+
) -> list[dict]:
|
|
222
|
+
"""Busca mapa imobiliario (quadro de areas).
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
cost_center_id: ID do centro de custo (OBRIGATORIO).
|
|
226
|
+
start_date: Data inicio (YYYY-MM-DD).
|
|
227
|
+
end_date: Data fim (YYYY-MM-DD).
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Lista de dicts com dados do mapa.
|
|
231
|
+
"""
|
|
232
|
+
data = self._get("/real-estate-map", params={
|
|
233
|
+
"costCentersId": cost_center_id,
|
|
234
|
+
"startDate": start_date,
|
|
235
|
+
"endDate": end_date,
|
|
236
|
+
})
|
|
237
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sienge.endpoints.contabilidade — Endpoints de Contabilidade (lancamentos, plano de contas, lotes, empresas).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from typing import Iterator
|
|
9
|
+
|
|
10
|
+
from .base import BaseEndpoints
|
|
11
|
+
from ..models.financeiro import ContaContabil, LancamentoContabil
|
|
12
|
+
from ..utils import paginate
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ContabilidadeEndpoints(BaseEndpoints):
|
|
16
|
+
"""Endpoints de Contabilidade: lancamentos, plano de contas, lotes, empresas."""
|
|
17
|
+
|
|
18
|
+
# ── Lancamentos (/accountancy/entries) ───────────────────────────
|
|
19
|
+
|
|
20
|
+
def list_lancamentos(
|
|
21
|
+
self,
|
|
22
|
+
company_id: int,
|
|
23
|
+
start_date: str | None = None,
|
|
24
|
+
end_date: str | None = None,
|
|
25
|
+
limit: int | None = None,
|
|
26
|
+
) -> list[LancamentoContabil]:
|
|
27
|
+
"""Lista lancamentos contabeis.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
company_id: ID da empresa (OBRIGATORIO).
|
|
31
|
+
start_date: Data inicio (YYYY-MM-DD). Default: 30 dias atras.
|
|
32
|
+
end_date: Data fim (YYYY-MM-DD). Default: hoje.
|
|
33
|
+
limit: Limite de resultados (None = todos).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Lista de LancamentoContabil.
|
|
37
|
+
"""
|
|
38
|
+
return list(self.iter_lancamentos(company_id, start_date, end_date, max_results=limit))
|
|
39
|
+
|
|
40
|
+
def iter_lancamentos(
|
|
41
|
+
self,
|
|
42
|
+
company_id: int,
|
|
43
|
+
start_date: str | None = None,
|
|
44
|
+
end_date: str | None = None,
|
|
45
|
+
max_results: int | None = None,
|
|
46
|
+
) -> Iterator[LancamentoContabil]:
|
|
47
|
+
"""Itera sobre lancamentos contabeis com paginacao automatica.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
company_id: ID da empresa (OBRIGATORIO).
|
|
51
|
+
start_date: Data inicio (YYYY-MM-DD).
|
|
52
|
+
end_date: Data fim (YYYY-MM-DD).
|
|
53
|
+
max_results: Limite total.
|
|
54
|
+
|
|
55
|
+
Yields:
|
|
56
|
+
LancamentoContabil.
|
|
57
|
+
"""
|
|
58
|
+
if not start_date:
|
|
59
|
+
start_date = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
|
|
60
|
+
if not end_date:
|
|
61
|
+
end_date = datetime.now().strftime("%Y-%m-%d")
|
|
62
|
+
|
|
63
|
+
def fetch(offset: int, limit: int) -> dict:
|
|
64
|
+
return self._get("/accountancy/entries", params={
|
|
65
|
+
"companyId": company_id,
|
|
66
|
+
"startDate": start_date,
|
|
67
|
+
"endDate": end_date,
|
|
68
|
+
"offset": offset,
|
|
69
|
+
"limit": limit,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
for item in paginate(fetch, max_results=max_results):
|
|
73
|
+
yield LancamentoContabil.from_api(item)
|
|
74
|
+
|
|
75
|
+
# ── Plano de Contas (/accountancy/accounts) ──────────────────────
|
|
76
|
+
|
|
77
|
+
def list_plano_contas(self, limit: int | None = None) -> list[ContaContabil]:
|
|
78
|
+
"""Lista contas do plano de contas.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
limit: Limite de resultados (None = todas, 8.235 na CONIN).
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Lista de ContaContabil.
|
|
85
|
+
"""
|
|
86
|
+
return list(self.iter_plano_contas(max_results=limit))
|
|
87
|
+
|
|
88
|
+
def iter_plano_contas(self, max_results: int | None = None) -> Iterator[ContaContabil]:
|
|
89
|
+
"""Itera sobre contas do plano de contas.
|
|
90
|
+
|
|
91
|
+
Yields:
|
|
92
|
+
ContaContabil.
|
|
93
|
+
"""
|
|
94
|
+
def fetch(offset: int, limit: int) -> dict:
|
|
95
|
+
return self._get("/accountancy/accounts", params={"offset": offset, "limit": limit})
|
|
96
|
+
|
|
97
|
+
for item in paginate(fetch, max_results=max_results):
|
|
98
|
+
yield ContaContabil.from_api(item)
|
|
99
|
+
|
|
100
|
+
# ── Lotes Contabeis (/batch) ─────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
def list_lotes(
|
|
103
|
+
self,
|
|
104
|
+
company_id: int,
|
|
105
|
+
start_date: str | None = None,
|
|
106
|
+
end_date: str | None = None,
|
|
107
|
+
limit: int | None = None,
|
|
108
|
+
) -> list[dict]:
|
|
109
|
+
"""Lista lotes contabeis.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
company_id: ID da empresa (OBRIGATORIO).
|
|
113
|
+
start_date: Data inicio (YYYY-MM-DD). Default: 30 dias atras.
|
|
114
|
+
end_date: Data fim (YYYY-MM-DD). Default: hoje.
|
|
115
|
+
limit: Limite de resultados.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Lista de dicts com lotes.
|
|
119
|
+
"""
|
|
120
|
+
if not start_date:
|
|
121
|
+
start_date = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
|
|
122
|
+
if not end_date:
|
|
123
|
+
end_date = datetime.now().strftime("%Y-%m-%d")
|
|
124
|
+
|
|
125
|
+
params: dict = {
|
|
126
|
+
"companyId": company_id,
|
|
127
|
+
"startDate": start_date,
|
|
128
|
+
"endDate": end_date,
|
|
129
|
+
}
|
|
130
|
+
if limit:
|
|
131
|
+
params["limit"] = min(limit, 200)
|
|
132
|
+
data = self._get("/batch", params=params)
|
|
133
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
134
|
+
|
|
135
|
+
# ── Fechamento Contabil (/closingaccountancy) ────────────────────
|
|
136
|
+
|
|
137
|
+
def get_fechamento(self, month_year: str) -> dict:
|
|
138
|
+
"""Busca status de fechamento contabil de um periodo.
|
|
139
|
+
|
|
140
|
+
NOTA: Path correto e /closingaccountancy (NAO /accountancy/closing).
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
month_year: Periodo no formato YYYY-MM (OBRIGATORIO).
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Dict com dados do fechamento.
|
|
147
|
+
"""
|
|
148
|
+
return self._get("/closingaccountancy", params={"monthYear": month_year})
|
|
149
|
+
|
|
150
|
+
# ── Empresas (/companies) ────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
def list_empresas(self, limit: int | None = None) -> list[dict]:
|
|
153
|
+
"""Lista empresas cadastradas no Sienge.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
limit: Limite de resultados.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Lista de dicts com empresas (10 na CONIN).
|
|
160
|
+
"""
|
|
161
|
+
params: dict = {}
|
|
162
|
+
if limit:
|
|
163
|
+
params["limit"] = min(limit, 200)
|
|
164
|
+
data = self._get("/companies", params=params)
|
|
165
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sienge.endpoints.credores — Endpoints de Credores (fornecedores).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Iterator
|
|
8
|
+
|
|
9
|
+
from .base import BaseEndpoints
|
|
10
|
+
from ..models.credores import Credor
|
|
11
|
+
from ..utils import paginate
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CredoresEndpoints(BaseEndpoints):
|
|
15
|
+
"""Endpoints de Credores/Fornecedores."""
|
|
16
|
+
|
|
17
|
+
def list_credores(self, limit: int | None = None) -> list[Credor]:
|
|
18
|
+
"""Lista credores/fornecedores.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
limit: Limite de resultados (None = todos).
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Lista de Credor.
|
|
25
|
+
"""
|
|
26
|
+
return list(self.iter_credores(max_results=limit))
|
|
27
|
+
|
|
28
|
+
def iter_credores(self, max_results: int | None = None) -> Iterator[Credor]:
|
|
29
|
+
"""Itera sobre credores com paginacao.
|
|
30
|
+
|
|
31
|
+
Yields:
|
|
32
|
+
Credor.
|
|
33
|
+
"""
|
|
34
|
+
def fetch(offset: int, limit: int) -> dict:
|
|
35
|
+
return self._get("/creditors", params={"offset": offset, "limit": limit})
|
|
36
|
+
|
|
37
|
+
for item in paginate(fetch, max_results=max_results):
|
|
38
|
+
yield Credor.from_api(item)
|
|
39
|
+
|
|
40
|
+
def get_credor(self, creditor_id: int) -> Credor:
|
|
41
|
+
"""Busca um credor pelo ID.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
creditor_id: ID do credor.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Credor.
|
|
48
|
+
"""
|
|
49
|
+
data = self._get(f"/creditors/{creditor_id}")
|
|
50
|
+
return Credor.from_api(data)
|
|
51
|
+
|
|
52
|
+
def search_credores(self, nome: str) -> list[Credor]:
|
|
53
|
+
"""Busca credores pelo nome.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
nome: Nome ou parte do nome.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Lista de Credor.
|
|
60
|
+
"""
|
|
61
|
+
data = self._get("/creditors", params={"name": nome})
|
|
62
|
+
results = data.get("results", data if isinstance(data, list) else [])
|
|
63
|
+
return [Credor.from_api(r) for r in results]
|
|
64
|
+
|
|
65
|
+
def get_info_bancaria(self, creditor_id: int) -> list[dict]:
|
|
66
|
+
"""Lista informacoes bancarias de um credor.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
creditor_id: ID do credor.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Lista de dicts com dados bancarios.
|
|
73
|
+
"""
|
|
74
|
+
data = self._get(f"/creditors/{creditor_id}/bank-accounts")
|
|
75
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sienge.endpoints.engenharia — Endpoints de Engenharia (empreendimentos, centros de custo como obras).
|
|
3
|
+
|
|
4
|
+
IMPORTANTE: O Sienge NAO tem endpoint /buildings.
|
|
5
|
+
- "Obras" no Sienge sao representadas como /enterprises (empreendimentos)
|
|
6
|
+
- Centros de custo (/cost-centers) tambem representam obras
|
|
7
|
+
- Pedidos de compra (/purchase-orders) referenciam buildingId = centro de custo
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Iterator
|
|
13
|
+
|
|
14
|
+
from .base import BaseEndpoints
|
|
15
|
+
from ..models.obra import Obra
|
|
16
|
+
from ..utils import paginate
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EngenhariaEndpoints(BaseEndpoints):
|
|
20
|
+
"""Endpoints de Engenharia: empreendimentos (obras), centros de custo."""
|
|
21
|
+
|
|
22
|
+
# ── Empreendimentos (/enterprises) ─────────────────────────────────
|
|
23
|
+
# Este e o conceito de "obra" no Sienge
|
|
24
|
+
|
|
25
|
+
def list_obras(self, limit: int | None = None) -> list[Obra]:
|
|
26
|
+
"""Lista empreendimentos (obras/projetos).
|
|
27
|
+
|
|
28
|
+
No Sienge, obras sao "enterprises" (empreendimentos).
|
|
29
|
+
Retornados via GET /enterprises.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
limit: Limite de resultados (None = todos via paginacao).
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Lista de Obra.
|
|
36
|
+
"""
|
|
37
|
+
return list(self.iter_obras(max_results=limit))
|
|
38
|
+
|
|
39
|
+
def iter_obras(self, max_results: int | None = None) -> Iterator[Obra]:
|
|
40
|
+
"""Itera sobre empreendimentos com paginacao automatica.
|
|
41
|
+
|
|
42
|
+
Yields:
|
|
43
|
+
Obra.
|
|
44
|
+
"""
|
|
45
|
+
def fetch(offset: int, limit: int) -> dict:
|
|
46
|
+
return self._get("/enterprises", params={"offset": offset, "limit": limit})
|
|
47
|
+
|
|
48
|
+
for item in paginate(fetch, max_results=max_results):
|
|
49
|
+
yield Obra.from_api(item)
|
|
50
|
+
|
|
51
|
+
def get_obra(self, enterprise_id: int) -> Obra:
|
|
52
|
+
"""Busca um empreendimento pelo ID.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
enterprise_id: ID do empreendimento no Sienge.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Obra.
|
|
59
|
+
"""
|
|
60
|
+
data = self._get(f"/enterprises/{enterprise_id}")
|
|
61
|
+
return Obra.from_api(data)
|
|
62
|
+
|
|
63
|
+
def search_obra(self, nome: str) -> list[Obra]:
|
|
64
|
+
"""Busca empreendimentos pelo nome.
|
|
65
|
+
|
|
66
|
+
Filtra localmente pois /enterprises nao tem parametro de busca por nome.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
nome: Nome ou parte do nome.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Lista de Obra encontradas.
|
|
73
|
+
"""
|
|
74
|
+
nome_lower = nome.lower()
|
|
75
|
+
results = []
|
|
76
|
+
for obra in self.iter_obras():
|
|
77
|
+
if nome_lower in obra.nome.lower():
|
|
78
|
+
results.append(obra)
|
|
79
|
+
if len(results) >= 50: # limite de seguranca
|
|
80
|
+
break
|
|
81
|
+
return results
|
|
82
|
+
|
|
83
|
+
# ── Orcamentos de Obra (/building-cost-estimations) ──────────────
|
|
84
|
+
|
|
85
|
+
def list_orcamento_planilhas(self, building_id: int) -> list[dict]:
|
|
86
|
+
"""Lista planilhas do orcamento de uma obra.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
building_id: ID do empreendimento.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Lista de dicts com planilhas.
|
|
93
|
+
"""
|
|
94
|
+
data = self._get(f"/building-cost-estimations/{building_id}/sheets")
|
|
95
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
96
|
+
|
|
97
|
+
def list_orcamento_insumos(self, building_id: int, limit: int | None = None) -> list[dict]:
|
|
98
|
+
"""Lista insumos do orcamento de uma obra.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
building_id: ID do empreendimento.
|
|
102
|
+
limit: Limite de resultados.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Lista de dicts com insumos (631 para building_id=1 na CONIN).
|
|
106
|
+
"""
|
|
107
|
+
params: dict = {}
|
|
108
|
+
if limit:
|
|
109
|
+
params["limit"] = min(limit, 200)
|
|
110
|
+
data = self._get(f"/building-cost-estimations/{building_id}/resources", params=params)
|
|
111
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
112
|
+
|
|
113
|
+
# ── Diario de Obra (/construction-daily-report) ──────────────────
|
|
114
|
+
|
|
115
|
+
def list_diarios_obra(self, limit: int | None = None) -> list[dict]:
|
|
116
|
+
"""Lista diarios de obra.
|
|
117
|
+
|
|
118
|
+
NOTA: Path correto e /construction-daily-report (singular).
|
|
119
|
+
/construction-daily-reports (plural) retorna 404.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
limit: Limite de resultados.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Lista de dicts com diarios.
|
|
126
|
+
"""
|
|
127
|
+
params: dict = {}
|
|
128
|
+
if limit:
|
|
129
|
+
params["limit"] = min(limit, 200)
|
|
130
|
+
data = self._get("/construction-daily-report", params=params)
|
|
131
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
132
|
+
|
|
133
|
+
# ── Calendario de Obra (/building-projects/{id}/calendar) ────────
|
|
134
|
+
|
|
135
|
+
def get_calendario_obra(self, building_id: int) -> dict:
|
|
136
|
+
"""Busca calendario de uma obra.
|
|
137
|
+
|
|
138
|
+
NOTA: /building-calendar NAO EXISTE.
|
|
139
|
+
Path correto: /building-projects/{buildingId}/calendar
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
building_id: ID do empreendimento.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dict com configuracao do calendario.
|
|
146
|
+
"""
|
|
147
|
+
return self._get(f"/building-projects/{building_id}/calendar")
|
|
148
|
+
|
|
149
|
+
# ── Tipos de Evento de Diario (/construction-daily-report/event-type) ──
|
|
150
|
+
|
|
151
|
+
def list_tipos_evento_diario(self) -> list[dict]:
|
|
152
|
+
"""Lista tipos de evento para diario de obra.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Lista de dicts com tipos de evento.
|
|
156
|
+
"""
|
|
157
|
+
data = self._get("/construction-daily-report/event-type")
|
|
158
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
159
|
+
|
|
160
|
+
# ── Progresso de Obra (/building-projects/progress-logs) ─────────
|
|
161
|
+
|
|
162
|
+
def list_progresso_obra(self, limit: int | None = None) -> list[dict]:
|
|
163
|
+
"""Lista logs de progresso de obras.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
limit: Limite de resultados.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Lista de dicts com logs de progresso.
|
|
170
|
+
"""
|
|
171
|
+
params: dict = {}
|
|
172
|
+
if limit:
|
|
173
|
+
params["limit"] = min(limit, 200)
|
|
174
|
+
data = self._get("/building-projects/progress-logs", params=params)
|
|
175
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
176
|
+
|
|
177
|
+
# ── Canteiros de Obra (/sites) ───────────────────────────────────
|
|
178
|
+
|
|
179
|
+
def list_canteiros(self, building_id: int) -> list[dict]:
|
|
180
|
+
"""Lista canteiros de obra.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
building_id: ID do empreendimento (OBRIGATORIO).
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Lista de dicts com canteiros.
|
|
187
|
+
"""
|
|
188
|
+
data = self._get("/sites", params={"buildingId": building_id})
|
|
189
|
+
return data.get("results", data if isinstance(data, list) else [])
|
|
190
|
+
|
|
191
|
+
# ── Bases de Custo (/cost-databases) ─────────────────────────────
|
|
192
|
+
|
|
193
|
+
def list_bases_custo(self) -> list[dict]:
|
|
194
|
+
"""Lista bases de referencia de custo (SINAPI, SICRO, etc).
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Lista de dicts com bases de custo (2 na CONIN).
|
|
198
|
+
"""
|
|
199
|
+
data = self._get("/cost-databases")
|
|
200
|
+
return data.get("results", data if isinstance(data, list) else [])
|