relatorios-sivwin 0.2.0__tar.gz

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.
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: relatorios-sivwin
3
+ Version: 0.2.0
4
+ Summary: Consultas SQL reutilizaveis para o ecossistema SivWin/Otimiza.
5
+ Keywords: sql,django,sivwin,otimiza,relatorios
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3 :: Only
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Database
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ Provides-Extra: dev
16
+ Requires-Dist: build; extra == "dev"
17
+ Requires-Dist: pytest; extra == "dev"
18
+ Requires-Dist: twine; extra == "dev"
19
+
20
+ # Relatorios SivWin
21
+
22
+ Biblioteca Python para centralizar consultas SQL do ecossistema SivWin/Otimiza.
23
+
24
+ ## Instalacao
25
+
26
+ ```bash
27
+ pip install relatorios-sivwin
28
+ ```
29
+
30
+ O nome publicado usa hifen, mas o import Python usa underscore:
31
+
32
+ ```python
33
+ from relatorios_sivwin import RelatoriosSivWin
34
+ ```
35
+
36
+ ## Objetivo
37
+
38
+ - Concentrar consultas SQL em um unico lugar.
39
+ - Reduzir duplicacao de regras SQL entre projetos.
40
+ - Facilitar o uso das mesmas consultas em Django, scripts e outros servicos.
41
+
42
+ ## Estrutura
43
+
44
+ - `relatorios_sivwin.SQLQuery`: contrato padrao para consultas parametrizadas.
45
+ - `relatorios_sivwin.RelatoriosSivWin`: hub principal de acesso aos dominios.
46
+ - `relatorios_sivwin.RelatoriosGeraisQueries`: consultas gerais, como declaracoes e ARTs.
47
+ - `relatorios_sivwin.RelatoriosServicosQueries`: consultas do dominio de servicos e inspecoes.
48
+ - `relatorios_sivwin.dbapi`: helpers opcionais para executar uma `SQLQuery` com um cursor ja aberto.
49
+
50
+ ## Exemplo Rapido
51
+
52
+ ```python
53
+ from relatorios_sivwin import RelatoriosSivWin
54
+
55
+ relatorios = RelatoriosSivWin()
56
+ consulta = relatorios.gerais.relatorio_art(120000, 120999)
57
+
58
+ print(consulta.sql)
59
+ print(consulta.params)
60
+ ```
61
+
62
+ Por padrao, a representacao SQL bruta usa `otimiza` como banco:
63
+
64
+ ```python
65
+ relatorios = RelatoriosSivWin(database="sivwin_homolog")
66
+ consulta = relatorios.servicos.relatorio_completo("2026-02-01", "2026-02-28")
67
+
68
+ print(consulta.to_sql_raw())
69
+ ```
70
+
71
+ ## Uso Com Django
72
+
73
+ ```python
74
+ from django.db import connections
75
+ from relatorios_sivwin import RelatoriosSivWin, fetch_all
76
+
77
+ relatorios = RelatoriosSivWin()
78
+ consulta = relatorios.servicos.relatorio_completo("2026-02-01", "2026-02-28")
79
+
80
+ with connections["otimiza"].cursor() as cursor:
81
+ dados = fetch_all(cursor, consulta)
82
+ ```
83
+
84
+ Nesse formato:
85
+
86
+ - o Django continua responsavel pela conexao;
87
+ - o pacote so define a consulta;
88
+ - o mesmo objeto pode ser reutilizado em outros contextos.
89
+
90
+ ## Uso Fora Do Django
91
+
92
+ ```python
93
+ from relatorios_sivwin import RelatoriosSivWin
94
+
95
+ relatorios = RelatoriosSivWin()
96
+ consulta = relatorios.gerais.relatorio_declaracoes(123456)
97
+
98
+ cursor.execute(consulta.sql, consulta.params)
99
+ linhas = cursor.fetchall()
100
+ ```
101
+
102
+ ## Principios Do Pacote
103
+
104
+ - Sem acoplamento com conexao, `.env` ou credenciais.
105
+ - Sem dependencias pesadas para gerar SQL.
106
+ - Metodos de dominio retornam `SQLQuery`.
107
+ - Queries sempre parametrizadas, sem interpolacao direta de valores.
108
+ - Cada relatorio relevante tem sua propria SQL, escrita de forma explicita.
109
+ - Reaproveitamento existe apenas onde ele realmente simplifica, como constantes SQL e bloco de contato.
@@ -0,0 +1,90 @@
1
+ # Relatorios SivWin
2
+
3
+ Biblioteca Python para centralizar consultas SQL do ecossistema SivWin/Otimiza.
4
+
5
+ ## Instalacao
6
+
7
+ ```bash
8
+ pip install relatorios-sivwin
9
+ ```
10
+
11
+ O nome publicado usa hifen, mas o import Python usa underscore:
12
+
13
+ ```python
14
+ from relatorios_sivwin import RelatoriosSivWin
15
+ ```
16
+
17
+ ## Objetivo
18
+
19
+ - Concentrar consultas SQL em um unico lugar.
20
+ - Reduzir duplicacao de regras SQL entre projetos.
21
+ - Facilitar o uso das mesmas consultas em Django, scripts e outros servicos.
22
+
23
+ ## Estrutura
24
+
25
+ - `relatorios_sivwin.SQLQuery`: contrato padrao para consultas parametrizadas.
26
+ - `relatorios_sivwin.RelatoriosSivWin`: hub principal de acesso aos dominios.
27
+ - `relatorios_sivwin.RelatoriosGeraisQueries`: consultas gerais, como declaracoes e ARTs.
28
+ - `relatorios_sivwin.RelatoriosServicosQueries`: consultas do dominio de servicos e inspecoes.
29
+ - `relatorios_sivwin.dbapi`: helpers opcionais para executar uma `SQLQuery` com um cursor ja aberto.
30
+
31
+ ## Exemplo Rapido
32
+
33
+ ```python
34
+ from relatorios_sivwin import RelatoriosSivWin
35
+
36
+ relatorios = RelatoriosSivWin()
37
+ consulta = relatorios.gerais.relatorio_art(120000, 120999)
38
+
39
+ print(consulta.sql)
40
+ print(consulta.params)
41
+ ```
42
+
43
+ Por padrao, a representacao SQL bruta usa `otimiza` como banco:
44
+
45
+ ```python
46
+ relatorios = RelatoriosSivWin(database="sivwin_homolog")
47
+ consulta = relatorios.servicos.relatorio_completo("2026-02-01", "2026-02-28")
48
+
49
+ print(consulta.to_sql_raw())
50
+ ```
51
+
52
+ ## Uso Com Django
53
+
54
+ ```python
55
+ from django.db import connections
56
+ from relatorios_sivwin import RelatoriosSivWin, fetch_all
57
+
58
+ relatorios = RelatoriosSivWin()
59
+ consulta = relatorios.servicos.relatorio_completo("2026-02-01", "2026-02-28")
60
+
61
+ with connections["otimiza"].cursor() as cursor:
62
+ dados = fetch_all(cursor, consulta)
63
+ ```
64
+
65
+ Nesse formato:
66
+
67
+ - o Django continua responsavel pela conexao;
68
+ - o pacote so define a consulta;
69
+ - o mesmo objeto pode ser reutilizado em outros contextos.
70
+
71
+ ## Uso Fora Do Django
72
+
73
+ ```python
74
+ from relatorios_sivwin import RelatoriosSivWin
75
+
76
+ relatorios = RelatoriosSivWin()
77
+ consulta = relatorios.gerais.relatorio_declaracoes(123456)
78
+
79
+ cursor.execute(consulta.sql, consulta.params)
80
+ linhas = cursor.fetchall()
81
+ ```
82
+
83
+ ## Principios Do Pacote
84
+
85
+ - Sem acoplamento com conexao, `.env` ou credenciais.
86
+ - Sem dependencias pesadas para gerar SQL.
87
+ - Metodos de dominio retornam `SQLQuery`.
88
+ - Queries sempre parametrizadas, sem interpolacao direta de valores.
89
+ - Cada relatorio relevante tem sua propria SQL, escrita de forma explicita.
90
+ - Reaproveitamento existe apenas onde ele realmente simplifica, como constantes SQL e bloco de contato.
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "relatorios-sivwin"
7
+ version = "0.2.0"
8
+ description = "Consultas SQL reutilizaveis para o ecossistema SivWin/Otimiza."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ keywords = ["sql", "django", "sivwin", "otimiza", "relatorios"]
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3 :: Only",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Intended Audience :: Developers",
19
+ "Topic :: Database",
20
+ ]
21
+ dependencies = []
22
+
23
+ [project.optional-dependencies]
24
+ dev = [
25
+ "build",
26
+ "pytest",
27
+ "twine",
28
+ ]
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
32
+ include = ["relatorios_sivwin", "relatorios_sivwin.*"]
33
+
34
+ [tool.pytest.ini_options]
35
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ """API publica do pacote ``relatorios_sivwin``."""
2
+
3
+ from .contracts import SQLQuery
4
+ from .core import RelatoriosSivWin
5
+ from .dbapi import execute_sql_query, fetch_all, fetch_one
6
+ from .queries import RelatoriosGeraisQueries, RelatoriosServicosQueries
7
+
8
+ __all__ = [
9
+ "RelatoriosSivWin",
10
+ "RelatoriosGeraisQueries",
11
+ "RelatoriosServicosQueries",
12
+ "SQLQuery",
13
+ "execute_sql_query",
14
+ "fetch_all",
15
+ "fetch_one",
16
+ ]
@@ -0,0 +1,19 @@
1
+ """Compatibilidade para constantes SQL historicamente publicas."""
2
+
3
+ from .queries._shared import (
4
+ CASE_SITUACAO_DECLARACAO,
5
+ CASE_SITUACAO_INSPECAO,
6
+ CASE_TIPO_INSPECAO,
7
+ CASE_TIPO_SERVICO,
8
+ _campos_contato,
9
+ sql_base,
10
+ )
11
+
12
+ __all__ = [
13
+ "CASE_SITUACAO_DECLARACAO",
14
+ "CASE_SITUACAO_INSPECAO",
15
+ "CASE_TIPO_INSPECAO",
16
+ "CASE_TIPO_SERVICO",
17
+ "_campos_contato",
18
+ "sql_base",
19
+ ]
@@ -0,0 +1,48 @@
1
+ """Tipos compartilhados para representacao de consultas SQL."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from textwrap import dedent
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class SQLQuery:
12
+ """Representacao ordenada de uma consulta SQL pronta para execucao.
13
+
14
+ Args:
15
+ sql: A string da consulta SQL, com placeholders (%s) para os parametros.
16
+ name: Um nome opcional para a consulta, util para log e depuracao.
17
+ params: Uma tupla de parametros a serem interpolados na consulta.
18
+ database: Nome do banco usado ao renderizar a consulta para inspecao.
19
+ """
20
+
21
+ sql: str
22
+ name: str = ""
23
+ params: tuple[Any, ...] = field(default_factory=tuple)
24
+ database: str = "otimiza"
25
+
26
+ def __post_init__(self) -> None:
27
+ """Normaliza o SQL e garante que os parametros sejam uma tupla."""
28
+ object.__setattr__(self, "sql", dedent(self.sql).strip())
29
+ object.__setattr__(self, "params", tuple(self.params))
30
+
31
+ def to_sql_raw(self, database: str | None = None) -> str:
32
+ """Retorna a consulta com os parametros interpolados para inspecao."""
33
+ selected_database = self.database if database is None else database
34
+ sql = self.sql
35
+
36
+ for param in self.params:
37
+ if param is None:
38
+ value = "NULL"
39
+ elif isinstance(param, str):
40
+ value = "'" + param.replace("'", "''") + "'"
41
+ elif isinstance(param, bool):
42
+ value = "1" if param else "0"
43
+ else:
44
+ value = str(param)
45
+
46
+ sql = sql.replace("%s", value, 1)
47
+
48
+ return f"USE [{selected_database}];\n\n{sql}"
@@ -0,0 +1,18 @@
1
+ """Objeto principal de acesso aos relatorios SivWin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .queries import RelatoriosGeraisQueries, RelatoriosServicosQueries
6
+
7
+
8
+ class RelatoriosSivWin:
9
+ """Centraliza o acesso aos dominios de consulta do pacote."""
10
+
11
+ def __init__(self, database: str = "otimiza") -> None:
12
+ """Inicializa os modulos publicos do pacote."""
13
+ self.database = database
14
+ self.gerais = RelatoriosGeraisQueries(database=database)
15
+ self.servicos = RelatoriosServicosQueries(database=database)
16
+
17
+ self.relatorios_gerais = self.gerais
18
+ self.relatorios_servicos = self.servicos
@@ -0,0 +1,54 @@
1
+ """Helpers opcionais para executar consultas com cursores ja abertos."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .contracts import SQLQuery
8
+
9
+
10
+ def execute_sql_query(cursor: Any, query: SQLQuery) -> None:
11
+ """Executa uma ``SQLQuery`` usando um cursor existente.
12
+
13
+ Args:
14
+ cursor: Cursor DB-API compativel com ``execute(sql, params)``.
15
+ query: Consulta parametrizada a ser executada.
16
+ """
17
+ cursor.execute(query.sql, query.params)
18
+
19
+
20
+ def fetch_all(cursor: Any, query: SQLQuery) -> list[dict[str, Any]]:
21
+ """Executa uma consulta e retorna todas as linhas como dicionario.
22
+
23
+ Args:
24
+ cursor: Cursor DB-API ja associado a uma conexao aberta.
25
+ query: Consulta parametrizada a ser executada.
26
+
27
+ Returns:
28
+ Lista de dicionarios no formato ``{nome_coluna: valor}``.
29
+ """
30
+ execute_sql_query(cursor, query)
31
+
32
+ columns = [column[0] for column in cursor.description]
33
+ return [dict(zip(columns, linha)) for linha in cursor.fetchall()]
34
+
35
+
36
+ def fetch_one(cursor: Any, query: SQLQuery) -> dict[str, Any] | None:
37
+ """Executa uma consulta e retorna a primeira linha como dicionario.
38
+
39
+ Args:
40
+ cursor: Cursor DB-API ja associado a uma conexao aberta.
41
+ query: Consulta parametrizada a ser executada.
42
+
43
+ Returns:
44
+ Um dicionario com a primeira linha retornada ou ``None`` quando nao ha
45
+ resultados.
46
+ """
47
+ execute_sql_query(cursor, query)
48
+
49
+ linha = cursor.fetchone()
50
+ if linha is None:
51
+ return None
52
+
53
+ columns = [column[0] for column in cursor.description]
54
+ return dict(zip(columns, linha))
@@ -0,0 +1,9 @@
1
+ """Consultas SQL organizadas por dominio."""
2
+
3
+ from .gerais import RelatoriosGeraisQueries
4
+ from .servicos import RelatoriosServicosQueries
5
+
6
+ __all__ = [
7
+ "RelatoriosGeraisQueries",
8
+ "RelatoriosServicosQueries",
9
+ ]
@@ -0,0 +1,152 @@
1
+ """Constantes e pequenos utilitarios SQL compartilhados pelo pacote."""
2
+
3
+ from __future__ import annotations
4
+ from textwrap import dedent
5
+
6
+ CASE_SITUACAO_INSPECAO = """
7
+ CASE i.SituacaoVeiculo
8
+ WHEN 0 THEN 'Ativo'
9
+ WHEN 1 THEN 'Aprovado'
10
+ WHEN 2 THEN 'Reprovado'
11
+ WHEN 3 THEN 'Cancelada'
12
+ WHEN 4 THEN 'Vencida'
13
+ WHEN 5 THEN 'Em correcao'
14
+ WHEN 6 THEN 'Corrigida'
15
+ WHEN 7 THEN 'Reprovada pelo SISCSV'
16
+ ELSE 'Outros'
17
+ END
18
+ """
19
+
20
+ CASE_TIPO_SERVICO = """
21
+ CASE s.TipoServico
22
+ WHEN 'SR' THEN s.TipoCsvSerproNome
23
+ WHEN 'LG' THEN s.TipoLaudoNome
24
+ WHEN 'LS' THEN s.ProjetoSisLitIdentificacao
25
+ WHEN 'LD' THEN 'Laudo CSV Legado'
26
+ END
27
+ """
28
+
29
+ CASE_TIPO_INSPECAO = """
30
+ CASE s.TipoServico
31
+ WHEN 'SR' THEN 'Laudo CSV'
32
+ WHEN 'LD' THEN 'Laudo CSV Legado'
33
+ WHEN 'LG' THEN 'Laudo Geral'
34
+ WHEN 'LS' THEN 'Laudo SISLIT'
35
+ END
36
+ """
37
+
38
+ CASE_SITUACAO_DECLARACAO = """
39
+ CASE i.SituacaoVeiculo
40
+ WHEN 1 THEN 'APROVADO'
41
+ WHEN 2 THEN 'REPROVADO'
42
+ END
43
+ """
44
+
45
+ def _campos_contato(alias: str, nome: str) -> str:
46
+ """Retorna um bloco padrao de colunas de contato.
47
+
48
+ O bloco atende os cenarios recorrentes de proprietario, contratante e condutor. Os aliases seguem o padrao ``<nome><Campo>``.
49
+ Por exemplo, para o proprietario, ``proprietarioNome`` e ``proprietarioCidade``. Para o condutor, por exemplo
50
+ """
51
+ documento_alias = f"{nome}CPF" if nome == "condutor" else f"{nome}CPFCNPJ"
52
+
53
+ colunas = [
54
+ f"{alias}.Nome_RazaoSocial AS [{nome}Nome]",
55
+ f"{alias}.UF AS [{nome}UF]",
56
+ f"{alias}.Municipio AS [{nome}Cidade]",
57
+ f"{alias}.Bairro AS [{nome}Bairro]",
58
+ f"{alias}.CEP AS [{nome}CEP]",
59
+ f"{alias}.Logradouro AS [{nome}Endereco]",
60
+ f"{alias}.Numero AS [{nome}Numero]",
61
+ f"{alias}.Complemento AS [{nome}Complemento]",
62
+ f"{alias}.CPF_CNPJ AS [{documento_alias}]",
63
+ f"CONCAT({alias}.Telefone1DDD, {alias}.Telefone1) AS [{nome}Telefone]",
64
+ f"CONCAT({alias}.Telefone2DDD, {alias}.Telefone2) AS [{nome}Celular]",
65
+ f"{alias}.Email AS [{nome}Email]",
66
+ ]
67
+
68
+ return dedent(",\n".join(colunas)).strip()
69
+
70
+
71
+ def sql_base() -> str:
72
+ """Retorna o SQL base do relatorio completo de servicos."""
73
+ return f"""
74
+ SELECT
75
+ s.ServicoNumero AS [OS],
76
+ tc.Numero AS [RI],
77
+ i.Id AS [idInspecao],
78
+ CONVERT(CHAR, s.AberturaDataHora, 103) AS [dataAberturaOS],
79
+ i.AberturaDataHora AS [dataAberturaInspecao],
80
+ s.TipoServico AS [codigoServico],
81
+ {CASE_TIPO_SERVICO} AS [tipoServico],
82
+ {CASE_TIPO_INSPECAO} AS [tipoInspecao],
83
+ CONVERT(VARCHAR(20), i.NumeroCsvSerpro) AS [numeroCSV],
84
+ esc.Codigo AS [numeroEscopo],
85
+ esc.Nome AS [nomeEscopo],
86
+ i.InspecaoEmissaoDataHora AS [dataConclusaoInspecao],
87
+ i.ConcluidoDataHora AS [dataFinalizacaoInspecao],
88
+ i.CsvCorrigidoDataHora AS [dataCorrecaoInspecao],
89
+ i.InspecaoVencimentoData AS [dataVencimentoDocumento],
90
+ nf.Numero AS [NF],
91
+ s.ValorBrutoServico AS [valorBruto],
92
+ s.ValorServico AS [valorLiquido],
93
+ fdt.Nome AS [formaPagamento],
94
+ i.CsvPdfArquivoNome AS [csvArquivo],
95
+ i.SisLitNomeArquivoLaudo AS [sislitArquivo],
96
+ tp.Portaria AS [portariaInmetro],
97
+ vw_esc.Descricao AS [descricaoEscopo],
98
+ {CASE_SITUACAO_INSPECAO} AS [situacaoInspecao],
99
+ s.Placa AS [placa],
100
+ s.Chassi AS [chassi],
101
+ charac.EspecieVeiculo AS [especieVeiculo],
102
+ charac.TipoVeiculo AS [tipoVeiculo],
103
+ charac.MarcaModelo AS [marcaModelo],
104
+ charac.AnoFabricacao AS [anoFabricacao],
105
+ charac.AnoModelo AS [anoModelo],
106
+ charac.Carrocaria AS [carrocaria],
107
+ g.SeloNumero AS [numeroSelo],
108
+ p.ConfirmadoCpf AS [cpfInspetor],
109
+ p.ConfirmadoNome AS [nomeInspetor],
110
+ i.ConfirmadoCpf AS [cpfEngenheiro],
111
+ i.ConfirmadoNome AS [nomeEngenheiro],
112
+ ind.Nome_RazaoSocial AS [indicacaoNome],
113
+ {_campos_contato("prop", "proprietario")},
114
+ {_campos_contato("cont", "contratante")},
115
+ {_campos_contato("cond", "condutor")}
116
+ FROM dbo.Servicos AS s
117
+ INNER JOIN dbo.Inspecoes AS i
118
+ ON s.Id = i.ServicoId
119
+ LEFT JOIN dbo.TabelaCertificado AS tc
120
+ ON tc.ServicoId = s.Id
121
+ LEFT JOIN dbo.SivWin_CaracteristicasSerpro AS charac
122
+ ON s.CaracteristicaAtualSerproId = charac.Id
123
+ LEFT JOIN dbo.TabelaPortarias AS tp
124
+ ON s.PortariaId = tp.Id
125
+ LEFT JOIN dbo.NotasFiscais AS nf
126
+ ON s.NotaFiscalId = nf.Id
127
+ LEFT JOIN dbo.Financeiro_Lancamentos AS fl
128
+ ON s.ServicoNumero = fl.NumeroPedido
129
+ AND fl.SistemaRemoto = 'SIVWIN'
130
+ LEFT JOIN dbo.Financeiro_DocumentosTipos AS fdt
131
+ ON fdt.Id = fl.DocumentoTipo
132
+ LEFT JOIN dbo.Principal_Indicacoes AS ind
133
+ ON s.IndicacaoId = ind.Id
134
+ LEFT JOIN dbo.Gnv AS g
135
+ ON g.ServicoId = s.Id
136
+ LEFT JOIN dbo.Processos AS p
137
+ ON p.Id = i.ProcessoId
138
+ INNER JOIN dbo.Principal_Contatos AS prop
139
+ ON s.proprietarioId = prop.Id
140
+ INNER JOIN dbo.Principal_Contatos AS cont
141
+ ON s.ClienteId = cont.Id
142
+ INNER JOIN dbo.Principal_Contatos AS cond
143
+ ON s.CondutorId = cond.Id
144
+ LEFT JOIN dbo.SivWin_ProcedimentosEscopoServicoSerpro AS pess
145
+ ON s.Id = pess.ServicoId
146
+ LEFT JOIN dbo.SivWin_ProcedimentosEscoposSerpro AS pes
147
+ ON pess.ProcedimentoEscopoId = pes.Id
148
+ LEFT JOIN dbo.SivWin_TabelaEscoposSerpro AS esc
149
+ ON pes.EscopoDenatranId = esc.Id
150
+ LEFT JOIN dbo.RelatoriosSiv_SivWin_InspecoesPorEscopoInmetro_View AS vw_esc
151
+ ON s.ServicoNumero = vw_esc.ServicoNumero
152
+ """
@@ -0,0 +1,127 @@
1
+ """Consultas SQL de relatorios gerais do ecossistema SivWin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..contracts import SQLQuery
6
+ from ._shared import (
7
+ CASE_SITUACAO_DECLARACAO,
8
+ CASE_TIPO_INSPECAO,
9
+ CASE_TIPO_SERVICO,
10
+ _campos_contato,
11
+ )
12
+
13
+
14
+ class RelatoriosGeraisQueries:
15
+ """Agrupa relatorios gerais nao concentrados em um unico subdominio."""
16
+
17
+ def __init__(self, database: str = "otimiza") -> None:
18
+ self.database = database
19
+
20
+ def relatorio_declaracoes(self, os_numero: int) -> SQLQuery:
21
+ """Gera a consulta usada na emissao da declaracao por OS."""
22
+ sql = f"""
23
+ SELECT
24
+ s.ServicoNumero AS [OS],
25
+ tc.Numero AS [RI],
26
+ CONVERT(VARCHAR(10), s.AberturaDataHora, 103) AS [dataAberturaOS],
27
+ CONVERT(VARCHAR(10), i.AberturaDataHora, 103) AS [dataAberturaInspecao],
28
+ i.NumeroCsvSerpro AS [numeroCSV],
29
+ esc.Codigo AS [numeroEscopo],
30
+ esc.Nome AS [escopo],
31
+ {CASE_TIPO_INSPECAO} AS [tipoInspecao],
32
+ {CASE_TIPO_SERVICO} AS [tipoLaudo],
33
+ tp.Portaria AS [portariaInmetro],
34
+ {CASE_SITUACAO_DECLARACAO} AS [situacaoInspecao],
35
+ s.ValorBrutoServico AS [valorBruto],
36
+ nf.Numero AS [NF],
37
+ fdt.Nome AS [formaPagamento],
38
+ s.Placa AS [placa],
39
+ s.Chassi AS [chassi],
40
+ charac.Carrocaria AS [carrocaria],
41
+ charac.MarcaModelo AS [marcaModelo],
42
+ charac.EspecieVeiculo AS [especieVeiculo],
43
+ charac.TipoVeiculo AS [tipoVeiculo],
44
+ charac.AnoFabricacao AS [anoFabricacao],
45
+ charac.AnoModelo AS [anoModelo],
46
+ {_campos_contato("prop", "proprietario")},
47
+ {_campos_contato("cont", "contratante")},
48
+ {_campos_contato("cond", "condutor")}
49
+ FROM dbo.Servicos AS s
50
+ INNER JOIN dbo.Inspecoes AS i
51
+ ON s.Id = i.ServicoId
52
+ LEFT JOIN dbo.TabelaCertificado AS tc
53
+ ON tc.ServicoId = s.Id
54
+ LEFT JOIN dbo.SivWin_CaracteristicasSerpro AS charac
55
+ ON s.CaracteristicaAtualSerproId = charac.Id
56
+ LEFT JOIN dbo.TabelaPortarias AS tp
57
+ ON s.PortariaId = tp.Id
58
+ LEFT JOIN dbo.NotasFiscais AS nf
59
+ ON s.NotaFiscalId = nf.Id
60
+ LEFT JOIN dbo.Financeiro_Lancamentos AS fl
61
+ ON s.ServicoNumero = fl.NumeroPedido
62
+ AND fl.SistemaRemoto = 'SIVWIN'
63
+ LEFT JOIN dbo.Financeiro_DocumentosTipos AS fdt
64
+ ON fdt.Id = fl.DocumentoTipo
65
+ INNER JOIN dbo.Principal_Contatos AS prop
66
+ ON s.proprietarioId = prop.Id
67
+ INNER JOIN dbo.Principal_Contatos AS cont
68
+ ON s.ClienteId = cont.Id
69
+ INNER JOIN dbo.Principal_Contatos AS cond
70
+ ON s.CondutorId = cond.Id
71
+ LEFT JOIN dbo.SivWin_ProcedimentosEscopoServicoSerpro AS pess
72
+ ON s.Id = pess.ServicoId
73
+ LEFT JOIN dbo.SivWin_ProcedimentosEscoposSerpro AS pes
74
+ ON pess.ProcedimentoEscopoId = pes.Id
75
+ LEFT JOIN dbo.SivWin_TabelaEscoposSerpro AS esc
76
+ ON pes.EscopoDenatranId = esc.Id
77
+ WHERE s.ServicoNumero = %s
78
+ ORDER BY OS ASC
79
+ """
80
+ return SQLQuery(
81
+ sql=sql,
82
+ params=(os_numero,),
83
+ name="relatorio_declaracoes",
84
+ database=self.database,
85
+ )
86
+
87
+ def relatorio_art(self, os_numero_inicio: int, os_numero_fim: int) -> SQLQuery:
88
+ """Gera a consulta do relatorio para emissao de ART por faixa de OS."""
89
+ sql = f"""
90
+ WITH cte_art AS (
91
+ SELECT
92
+ s.ServicoNumero AS [OS],
93
+ tc.Numero AS [RI],
94
+ CONVERT(CHAR, s.AberturaDataHora, 103) AS [dataAberturaOS],
95
+ {CASE_TIPO_SERVICO} AS [tipoLaudo],
96
+ CONVERT(VARCHAR(20), i.NumeroCsvSerpro) AS [numeroCSV],
97
+ i.InspecaoEmissaoDataHora AS [dataConclusaoInspecao],
98
+ s.Placa AS [placa],
99
+ s.Chassi AS [chassi],
100
+ ROW_NUMBER() OVER (
101
+ PARTITION BY s.ServicoNumero
102
+ ORDER BY i.InspecaoEmissaoDataHora ASC, i.Id ASC
103
+ ) AS rn
104
+ FROM dbo.Servicos AS s
105
+ INNER JOIN dbo.Inspecoes AS i
106
+ ON s.Id = i.ServicoId
107
+ LEFT JOIN dbo.TabelaCertificado AS tc
108
+ ON tc.ServicoId = s.Id
109
+ WHERE
110
+ s.ServicoNumero BETWEEN %s AND %s
111
+ AND s.TipoServico = %s
112
+ AND i.SituacaoVeiculo IN (1, 2, 6)
113
+ )
114
+ SELECT
115
+ OS, RI, dataAberturaOS, tipoLaudo, numeroCSV, dataConclusaoInspecao, placa, chassi
116
+ FROM cte_art
117
+ WHERE rn = 1
118
+ ORDER BY OS ASC
119
+ """
120
+ params = (os_numero_inicio, os_numero_fim, "SR")
121
+ return SQLQuery(
122
+ sql=sql,
123
+ params=params,
124
+ name="relatorio_art",
125
+ database=self.database,
126
+ )
127
+
@@ -0,0 +1,277 @@
1
+ """Consultas SQL do dominio de servicos e inspecoes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..contracts import SQLQuery
6
+ from ._shared import sql_base
7
+
8
+
9
+ class RelatoriosServicosQueries:
10
+ """Agrupa consultas relacionadas ao ciclo de vida dos servicos."""
11
+
12
+ def __init__(self, database: str = "otimiza") -> None:
13
+ self.database = database
14
+ self._sql_base = sql_base
15
+
16
+ def relatorio_completo(self, start: str, end: str) -> SQLQuery:
17
+ """Gera o relatorio completo de servicos abertos no periodo.
18
+
19
+ Args:
20
+ start: Data inicial no formato ``YYYY-MM-DD``.
21
+ end: Data final no formato ``YYYY-MM-DD``.
22
+
23
+ Returns:
24
+ Consulta parametrizada com servicos cuja OS foi aberta entre
25
+ ``startT00:00:00`` e ``endT23:59:59``.
26
+ """
27
+ sql = f"""
28
+ {self._sql_base()}
29
+ WHERE
30
+ s.AberturaDataHora BETWEEN %s AND %s
31
+ ORDER BY OS ASC
32
+ """
33
+ params = (f"{start}T00:00:00", f"{end}T23:59:59")
34
+ return SQLQuery(
35
+ sql=sql,
36
+ params=params,
37
+ name="relatorio_completo",
38
+ database=self.database,
39
+ )
40
+
41
+ def cis_emitidos(self, start: str, end: str) -> SQLQuery:
42
+ """Gera a consulta de CIs/RIs emitidos para servicos CSV abertos no periodo.
43
+
44
+ Args:
45
+ start: Data inicial no formato ``YYYY-MM-DD``.
46
+ end: Data final no formato ``YYYY-MM-DD``.
47
+
48
+ Returns:
49
+ Consulta parametrizada com o ultimo registro por OS que possui RI,
50
+ numero CSV e situacao aprovada ou corrigida.
51
+ """
52
+ sql = f"""
53
+ WITH base AS (
54
+ {self._sql_base()}
55
+ WHERE
56
+ tc.Numero IS NOT NULL
57
+ AND s.TipoServico = %s
58
+ AND i.SituacaoVeiculo IN (1, 6)
59
+ AND i.NumeroCsvSerpro IS NOT NULL
60
+ AND s.AberturaDataHora BETWEEN %s AND %s
61
+ )
62
+ SELECT *
63
+ FROM (
64
+ SELECT TOP 1 WITH TIES *
65
+ FROM base
66
+ ORDER BY ROW_NUMBER() OVER (
67
+ PARTITION BY OS
68
+ ORDER BY
69
+ dataConclusaoInspecao DESC,
70
+ RI DESC
71
+ )
72
+ ) AS consulta
73
+ ORDER BY OS ASC
74
+ """
75
+ params = ("SR", f"{start}T00:00:00", f"{end}T23:59:59")
76
+ return SQLQuery(
77
+ sql=sql,
78
+ params=params,
79
+ name="cis_emitidos",
80
+ database=self.database,
81
+ )
82
+
83
+ def servicos_aprovados(self, start: str, end: str) -> SQLQuery:
84
+ """Gera a consulta de servicos aprovados pela conclusao da inspecao.
85
+
86
+ Args:
87
+ start: Data inicial no formato ``YYYY-MM-DD``.
88
+ end: Data final no formato ``YYYY-MM-DD``.
89
+
90
+ Returns:
91
+ Consulta parametrizada com o ultimo registro por OS cuja inspecao
92
+ foi emitida/concluida entre ``startT00:00:00`` e ``endT23:59:59``
93
+ e esteja aprovada ou corrigida.
94
+ """
95
+ sql = f"""
96
+ WITH base AS (
97
+ {self._sql_base()}
98
+ WHERE
99
+ i.InspecaoEmissaoDataHora BETWEEN %s AND %s
100
+ AND i.SituacaoVeiculo IN (1, 6)
101
+ )
102
+ SELECT *
103
+ FROM (
104
+ SELECT TOP 1 WITH TIES *
105
+ FROM base
106
+ ORDER BY ROW_NUMBER() OVER (
107
+ PARTITION BY OS
108
+ ORDER BY
109
+ dataConclusaoInspecao DESC,
110
+ RI DESC
111
+ )
112
+ ) AS consulta
113
+ ORDER BY OS ASC
114
+ """
115
+ params = (f"{start}T00:00:00", f"{end}T23:59:59")
116
+ return SQLQuery(
117
+ sql=sql,
118
+ params=params,
119
+ name="servicos_aprovados",
120
+ database=self.database,
121
+ )
122
+
123
+ def servicos_reprovados(self, start: str, end: str) -> SQLQuery:
124
+ """Gera a consulta de servicos reprovados abertos no periodo.
125
+
126
+ Args:
127
+ start: Data inicial no formato ``YYYY-MM-DD``.
128
+ end: Data final no formato ``YYYY-MM-DD``.
129
+
130
+ Returns:
131
+ Consulta parametrizada com o ultimo registro reprovado por OS cuja
132
+ abertura ocorreu entre ``startT00:00:00`` e ``endT23:59:59``.
133
+ """
134
+ sql = f"""
135
+ WITH base AS (
136
+ {self._sql_base()}
137
+ WHERE
138
+ s.AberturaDataHora BETWEEN %s AND %s
139
+ AND i.SituacaoVeiculo IN (2)
140
+ )
141
+ SELECT *
142
+ FROM (
143
+ SELECT TOP 1 WITH TIES *
144
+ FROM base
145
+ ORDER BY ROW_NUMBER() OVER (
146
+ PARTITION BY OS
147
+ ORDER BY
148
+ dataConclusaoInspecao DESC,
149
+ RI DESC
150
+ )
151
+ ) AS consulta
152
+ ORDER BY OS ASC
153
+ """
154
+ params = (f"{start}T00:00:00", f"{end}T23:59:59")
155
+ return SQLQuery(
156
+ sql=sql,
157
+ params=params,
158
+ name="servicos_reprovados",
159
+ database=self.database,
160
+ )
161
+
162
+ def servicos_inmetro(self, start: str, end: str) -> SQLQuery:
163
+ """Gera a consulta de servicos INMETRO/CSV abertos no periodo.
164
+
165
+ Args:
166
+ start: Data inicial no formato ``YYYY-MM-DD``.
167
+ end: Data final no formato ``YYYY-MM-DD``.
168
+
169
+ Returns:
170
+ Consulta parametrizada com o ultimo registro por OS de servicos do
171
+ tipo ``SR`` abertos no intervalo informado.
172
+ """
173
+ sql = f"""
174
+ WITH base AS (
175
+ {self._sql_base()}
176
+ WHERE
177
+ s.AberturaDataHora BETWEEN %s AND %s
178
+ AND s.TipoServico IN ('SR')
179
+ )
180
+ SELECT *
181
+ FROM (
182
+ SELECT TOP 1 WITH TIES *
183
+ FROM base
184
+ ORDER BY ROW_NUMBER() OVER (
185
+ PARTITION BY OS
186
+ ORDER BY
187
+ dataConclusaoInspecao DESC,
188
+ RI DESC
189
+ )
190
+ ) AS consulta
191
+ ORDER BY OS ASC
192
+ """
193
+ params = (f"{start}T00:00:00", f"{end}T23:59:59")
194
+ return SQLQuery(
195
+ sql=sql,
196
+ params=params,
197
+ name="servicos_inmetro",
198
+ database=self.database,
199
+ )
200
+
201
+ def servicos_sislit(self, start: str, end: str) -> SQLQuery:
202
+ """Gera a consulta de servicos SISLIT abertos no periodo.
203
+
204
+ Args:
205
+ start: Data inicial no formato ``YYYY-MM-DD``.
206
+ end: Data final no formato ``YYYY-MM-DD``.
207
+
208
+ Returns:
209
+ Consulta parametrizada com o ultimo registro por OS de servicos do
210
+ tipo ``LS`` abertos no intervalo informado.
211
+ """
212
+ sql = f"""
213
+ WITH base AS (
214
+ {self._sql_base()}
215
+ WHERE
216
+ s.AberturaDataHora BETWEEN %s AND %s
217
+ AND s.TipoServico IN ('LS')
218
+ )
219
+ SELECT *
220
+ FROM (
221
+ SELECT TOP 1 WITH TIES *
222
+ FROM base
223
+ ORDER BY ROW_NUMBER() OVER (
224
+ PARTITION BY OS
225
+ ORDER BY
226
+ dataConclusaoInspecao DESC,
227
+ RI DESC
228
+ )
229
+ ) AS consulta
230
+ ORDER BY OS ASC
231
+ """
232
+ params = (f"{start}T00:00:00", f"{end}T23:59:59")
233
+ return SQLQuery(
234
+ sql=sql,
235
+ params=params,
236
+ name="servicos_sislit",
237
+ database=self.database,
238
+ )
239
+
240
+ def servicos_laudo_geral(self, start: str, end: str) -> SQLQuery:
241
+ """Gera a consulta de servicos de Laudo Geral abertos no periodo.
242
+
243
+ Args:
244
+ start: Data inicial no formato ``YYYY-MM-DD``.
245
+ end: Data final no formato ``YYYY-MM-DD``.
246
+
247
+ Returns:
248
+ Consulta parametrizada com o ultimo registro por OS de servicos do
249
+ tipo ``LG`` abertos no intervalo informado.
250
+ """
251
+ sql = f"""
252
+ WITH base AS (
253
+ {self._sql_base()}
254
+ WHERE
255
+ s.AberturaDataHora BETWEEN %s AND %s
256
+ AND s.TipoServico IN ('LG')
257
+ )
258
+ SELECT *
259
+ FROM (
260
+ SELECT TOP 1 WITH TIES *
261
+ FROM base
262
+ ORDER BY ROW_NUMBER() OVER (
263
+ PARTITION BY OS
264
+ ORDER BY
265
+ dataConclusaoInspecao DESC,
266
+ RI DESC
267
+ )
268
+ ) AS consulta
269
+ ORDER BY OS ASC
270
+ """
271
+ params = (f"{start}T00:00:00", f"{end}T23:59:59")
272
+ return SQLQuery(
273
+ sql=sql,
274
+ params=params,
275
+ name="servicos_laudo_geral",
276
+ database=self.database,
277
+ )
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: relatorios-sivwin
3
+ Version: 0.2.0
4
+ Summary: Consultas SQL reutilizaveis para o ecossistema SivWin/Otimiza.
5
+ Keywords: sql,django,sivwin,otimiza,relatorios
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3 :: Only
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Database
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ Provides-Extra: dev
16
+ Requires-Dist: build; extra == "dev"
17
+ Requires-Dist: pytest; extra == "dev"
18
+ Requires-Dist: twine; extra == "dev"
19
+
20
+ # Relatorios SivWin
21
+
22
+ Biblioteca Python para centralizar consultas SQL do ecossistema SivWin/Otimiza.
23
+
24
+ ## Instalacao
25
+
26
+ ```bash
27
+ pip install relatorios-sivwin
28
+ ```
29
+
30
+ O nome publicado usa hifen, mas o import Python usa underscore:
31
+
32
+ ```python
33
+ from relatorios_sivwin import RelatoriosSivWin
34
+ ```
35
+
36
+ ## Objetivo
37
+
38
+ - Concentrar consultas SQL em um unico lugar.
39
+ - Reduzir duplicacao de regras SQL entre projetos.
40
+ - Facilitar o uso das mesmas consultas em Django, scripts e outros servicos.
41
+
42
+ ## Estrutura
43
+
44
+ - `relatorios_sivwin.SQLQuery`: contrato padrao para consultas parametrizadas.
45
+ - `relatorios_sivwin.RelatoriosSivWin`: hub principal de acesso aos dominios.
46
+ - `relatorios_sivwin.RelatoriosGeraisQueries`: consultas gerais, como declaracoes e ARTs.
47
+ - `relatorios_sivwin.RelatoriosServicosQueries`: consultas do dominio de servicos e inspecoes.
48
+ - `relatorios_sivwin.dbapi`: helpers opcionais para executar uma `SQLQuery` com um cursor ja aberto.
49
+
50
+ ## Exemplo Rapido
51
+
52
+ ```python
53
+ from relatorios_sivwin import RelatoriosSivWin
54
+
55
+ relatorios = RelatoriosSivWin()
56
+ consulta = relatorios.gerais.relatorio_art(120000, 120999)
57
+
58
+ print(consulta.sql)
59
+ print(consulta.params)
60
+ ```
61
+
62
+ Por padrao, a representacao SQL bruta usa `otimiza` como banco:
63
+
64
+ ```python
65
+ relatorios = RelatoriosSivWin(database="sivwin_homolog")
66
+ consulta = relatorios.servicos.relatorio_completo("2026-02-01", "2026-02-28")
67
+
68
+ print(consulta.to_sql_raw())
69
+ ```
70
+
71
+ ## Uso Com Django
72
+
73
+ ```python
74
+ from django.db import connections
75
+ from relatorios_sivwin import RelatoriosSivWin, fetch_all
76
+
77
+ relatorios = RelatoriosSivWin()
78
+ consulta = relatorios.servicos.relatorio_completo("2026-02-01", "2026-02-28")
79
+
80
+ with connections["otimiza"].cursor() as cursor:
81
+ dados = fetch_all(cursor, consulta)
82
+ ```
83
+
84
+ Nesse formato:
85
+
86
+ - o Django continua responsavel pela conexao;
87
+ - o pacote so define a consulta;
88
+ - o mesmo objeto pode ser reutilizado em outros contextos.
89
+
90
+ ## Uso Fora Do Django
91
+
92
+ ```python
93
+ from relatorios_sivwin import RelatoriosSivWin
94
+
95
+ relatorios = RelatoriosSivWin()
96
+ consulta = relatorios.gerais.relatorio_declaracoes(123456)
97
+
98
+ cursor.execute(consulta.sql, consulta.params)
99
+ linhas = cursor.fetchall()
100
+ ```
101
+
102
+ ## Principios Do Pacote
103
+
104
+ - Sem acoplamento com conexao, `.env` ou credenciais.
105
+ - Sem dependencias pesadas para gerar SQL.
106
+ - Metodos de dominio retornam `SQLQuery`.
107
+ - Queries sempre parametrizadas, sem interpolacao direta de valores.
108
+ - Cada relatorio relevante tem sua propria SQL, escrita de forma explicita.
109
+ - Reaproveitamento existe apenas onde ele realmente simplifica, como constantes SQL e bloco de contato.
@@ -0,0 +1,19 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/relatorios_sivwin/__init__.py
4
+ src/relatorios_sivwin/constants.py
5
+ src/relatorios_sivwin/contracts.py
6
+ src/relatorios_sivwin/core.py
7
+ src/relatorios_sivwin/dbapi.py
8
+ src/relatorios_sivwin.egg-info/PKG-INFO
9
+ src/relatorios_sivwin.egg-info/SOURCES.txt
10
+ src/relatorios_sivwin.egg-info/dependency_links.txt
11
+ src/relatorios_sivwin.egg-info/requires.txt
12
+ src/relatorios_sivwin.egg-info/top_level.txt
13
+ src/relatorios_sivwin/queries/__init__.py
14
+ src/relatorios_sivwin/queries/_shared.py
15
+ src/relatorios_sivwin/queries/gerais.py
16
+ src/relatorios_sivwin/queries/servicos.py
17
+ tests/test_public_api.py
18
+ tests/test_queries.py
19
+ tests/test_sql_query.py
@@ -0,0 +1,5 @@
1
+
2
+ [dev]
3
+ build
4
+ pytest
5
+ twine
@@ -0,0 +1 @@
1
+ relatorios_sivwin
@@ -0,0 +1,26 @@
1
+ from relatorios_sivwin import (
2
+ RelatoriosGeraisQueries,
3
+ RelatoriosServicosQueries,
4
+ RelatoriosSivWin,
5
+ SQLQuery,
6
+ )
7
+
8
+
9
+ def test_public_api_exports_main_objects() -> None:
10
+ relatorios = RelatoriosSivWin()
11
+
12
+ assert relatorios.database == "otimiza"
13
+ assert isinstance(relatorios.gerais, RelatoriosGeraisQueries)
14
+ assert isinstance(relatorios.servicos, RelatoriosServicosQueries)
15
+
16
+ assert relatorios.relatorios_gerais is relatorios.gerais
17
+ assert relatorios.relatorios_servicos is relatorios.servicos
18
+ assert SQLQuery(sql="SELECT 1").sql == "SELECT 1"
19
+
20
+
21
+ def test_public_api_accepts_custom_database() -> None:
22
+ relatorios = RelatoriosSivWin(database="sivwin_homolog")
23
+
24
+ assert relatorios.database == "sivwin_homolog"
25
+ assert relatorios.gerais.database == "sivwin_homolog"
26
+ assert relatorios.servicos.database == "sivwin_homolog"
@@ -0,0 +1,24 @@
1
+ from relatorios_sivwin import RelatoriosSivWin, SQLQuery
2
+
3
+
4
+ def test_servicos_query_returns_parameterized_sql_query() -> None:
5
+ relatorios = RelatoriosSivWin(database="sivwin_homolog")
6
+
7
+ query = relatorios.servicos.relatorio_completo("2026-03-01", "2026-03-31")
8
+
9
+ assert isinstance(query, SQLQuery)
10
+ assert query.name == "relatorio_completo"
11
+ assert query.database == "sivwin_homolog"
12
+ assert query.params == ("2026-03-01T00:00:00", "2026-03-31T23:59:59")
13
+ assert "s.AberturaDataHora BETWEEN %s AND %s" in query.sql
14
+
15
+
16
+ def test_gerais_query_returns_parameterized_sql_query() -> None:
17
+ relatorios = RelatoriosSivWin()
18
+
19
+ query = relatorios.gerais.relatorio_art(120000, 120999)
20
+
21
+ assert isinstance(query, SQLQuery)
22
+ assert query.name == "relatorio_art"
23
+ assert query.params == (120000, 120999, "SR")
24
+ assert "s.ServicoNumero BETWEEN %s AND %s" in query.sql
@@ -0,0 +1,37 @@
1
+ from relatorios_sivwin import SQLQuery
2
+
3
+
4
+ def test_sql_query_normalizes_sql_and_params() -> None:
5
+ query = SQLQuery(
6
+ sql="""
7
+ SELECT *
8
+ FROM dbo.Servicos
9
+ WHERE ServicoNumero = %s
10
+ """,
11
+ params=[123],
12
+ name="servico_por_os",
13
+ )
14
+
15
+ assert query.name == "servico_por_os"
16
+ assert query.database == "otimiza"
17
+ assert query.sql.startswith("SELECT *")
18
+ assert query.params == (123,)
19
+
20
+
21
+ def test_sql_query_to_sql_raw_quotes_values_for_inspection() -> None:
22
+ query = SQLQuery(
23
+ sql="SELECT * FROM exemplo WHERE nome = %s AND ativo = %s AND valor IS %s",
24
+ params=("D'Agua", True, None),
25
+ )
26
+
27
+ assert query.to_sql_raw("teste") == (
28
+ "USE [teste];\n\n"
29
+ "SELECT * FROM exemplo WHERE nome = 'D''Agua' AND ativo = 1 "
30
+ "AND valor IS NULL"
31
+ )
32
+
33
+
34
+ def test_sql_query_to_sql_raw_uses_query_database_by_default() -> None:
35
+ query = SQLQuery(sql="SELECT 1", database="sivwin_homolog")
36
+
37
+ assert query.to_sql_raw() == "USE [sivwin_homolog];\n\nSELECT 1"