Remoção de projeto
This commit is contained in:
parent
988259a53a
commit
09dc7127f0
101 changed files with 0 additions and 5313 deletions
9
AjustaFundos/.gitattributes
vendored
9
AjustaFundos/.gitattributes
vendored
|
|
@ -1,9 +0,0 @@
|
|||
# Normaliza finais de linha
|
||||
* text=auto
|
||||
|
||||
# Força Python e arquivos de configuração a usarem LF
|
||||
*.py text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.env text eol=lf
|
||||
46
AjustaFundos/.gitignore
vendored
46
AjustaFundos/.gitignore
vendored
|
|
@ -1,46 +0,0 @@
|
|||
# Ambiente virtual
|
||||
venv/
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Bytecode compilado
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Arquivos temporários do sistema
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs e databases locais
|
||||
*.log
|
||||
*.sqlite3
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# Arquivos de testes ou builds
|
||||
*.coverage
|
||||
htmlcov/
|
||||
coverage.xml
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
*.egg-info/
|
||||
|
||||
# Cache do pip
|
||||
pip-wheel-metadata/
|
||||
*.egg
|
||||
.cache/
|
||||
.tox/
|
||||
|
||||
# Arquivo s de conexão
|
||||
config/database/firebird.json
|
||||
storage/temp
|
||||
storage/temp.json
|
||||
|
||||
# Ignorar arquivos storage
|
||||
storage/
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "S:/Web/Ferramentas/AjustaFundos"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
// ============================================================
|
||||
// 🔧 GERAL
|
||||
// ============================================================
|
||||
"editor.minimap.enabled": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnType": false,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.autoSave": "onFocusChange",
|
||||
"telemetry.telemetryLevel": "off",
|
||||
"update.mode": "manual",
|
||||
"workbench.startupEditor": "none",
|
||||
"workbench.editor.enablePreview": false,
|
||||
// ============================================================
|
||||
// ⚡ PERFORMANCE — Ignorar lixo
|
||||
// ============================================================
|
||||
"files.watcherExclude": {
|
||||
"**/__pycache__/**": true,
|
||||
"**/.pytest_cache/**": true,
|
||||
"**/.mypy_cache/**": true,
|
||||
"**/.git/objects/**": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/__pycache__": true,
|
||||
"**/.pytest_cache": true,
|
||||
"**/.mypy_cache": true,
|
||||
"**/.git": true
|
||||
},
|
||||
// ============================================================
|
||||
// 🐍 PYTHON
|
||||
// ============================================================
|
||||
"python.defaultInterpreterPath": "S:/Web/RCCasamentoLvAntigo/venv/Scripts/python.exe",
|
||||
"python.languageServer": "Pylance",
|
||||
"python.analysis.autoImportCompletions": true,
|
||||
"python.analysis.indexing": true,
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"python.analysis.useLibraryCodeForTypes": true,
|
||||
// ============================================================
|
||||
// 🧹 FORMATADOR (Black)
|
||||
// ============================================================
|
||||
"python.formatting.provider": "black",
|
||||
"python.formatting.blackArgs": [
|
||||
"--line-length",
|
||||
"100"
|
||||
],
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
// ============================================================
|
||||
// 🔍 LINTING (Flake8)
|
||||
// ============================================================
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.flake8Enabled": true,
|
||||
"python.linting.flake8Args": [
|
||||
"--max-line-length=100"
|
||||
],
|
||||
"python.linting.pylintEnabled": false,
|
||||
// ============================================================
|
||||
// 🧠 GIT
|
||||
// ============================================================
|
||||
"git.enabled": true,
|
||||
"git.autorefresh": true,
|
||||
"git.fetchOnPull": true,
|
||||
"git.confirmSync": false,
|
||||
"git.postCommitCommand": "sync",
|
||||
"git.openDiffOnClick": true,
|
||||
// ============================================================
|
||||
// 🔍 GITLENS
|
||||
// ============================================================
|
||||
"gitlens.codeLens.enabled": false,
|
||||
"gitlens.currentLine.enabled": false,
|
||||
"gitlens.hovers.enabled": true,
|
||||
"gitlens.defaultDateFormat": "DD/MM/YYYY HH:mm",
|
||||
"gitlens.views.repositories.autoRefresh": true,
|
||||
"gitlens.views.repositories.location": "scm",
|
||||
// ============================================================
|
||||
// 💻 TERMINAL — Perfis úteis
|
||||
// ============================================================
|
||||
"terminal.integrated.profiles.windows": {
|
||||
"Python Shell": {
|
||||
"path": "cmd.exe",
|
||||
"args": [
|
||||
"/k",
|
||||
"cd S:\\Web\\RCCasamentoLvAntigo && venv\\Scripts\\activate"
|
||||
]
|
||||
},
|
||||
"Run Script": {
|
||||
"path": "cmd.exe",
|
||||
"args": [
|
||||
"/k",
|
||||
"cd S:\\Web\\RCCasamentoLvAntigo && venv\\Scripts\\activate && python main.py"
|
||||
]
|
||||
},
|
||||
"Git Bash": {
|
||||
"path": "C:\\Program Files\\Git\\bin\\bash.exe"
|
||||
}
|
||||
},
|
||||
"terminal.integrated.defaultProfile.windows": "Python Shell",
|
||||
"terminal.integrated.scrollback": 10000,
|
||||
"terminal.integrated.enablePersistentSessions": false,
|
||||
// ============================================================
|
||||
// 🗂️ FILTROS DE ARQUIVOS
|
||||
// ============================================================
|
||||
"files.exclude": {
|
||||
"**/.DS_Store": true,
|
||||
"**/*.log": true
|
||||
},
|
||||
// ============================================================
|
||||
// 🚫 DESATIVAR COPILOT (opcional)
|
||||
// ============================================================
|
||||
"github.copilot.enable": {
|
||||
"*": false
|
||||
},
|
||||
"github.copilot.inlineSuggest.enable": false,
|
||||
"editor.inlineSuggest.enabled": false
|
||||
},
|
||||
// ==============================================================
|
||||
// 🎯 DEBUG CONFIG — Python Standalone (não FastAPI)
|
||||
// ==============================================================
|
||||
"launch": {
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Python — main.py",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/main.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// ==============================================================
|
||||
// 📦 Extensões recomendadas
|
||||
// ==============================================================
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"ms-python.python",
|
||||
"ms-python.pylance",
|
||||
"ms-python.black-formatter",
|
||||
"ms-python.flake8",
|
||||
"eamodio.gitlens",
|
||||
"mhutchie.git-graph",
|
||||
"donjayamanne.githistory",
|
||||
"formulahendry.code-runner",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"tamasfe.even-better-toml"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
;TipoBanco=Firebird ou Oracle
|
||||
[Geral]
|
||||
|
||||
;Palmenlo
|
||||
;BaseDados=202A294B585F62033E343434D1DE15140E1F362B406F
|
||||
;São Miguel
|
||||
;BaseDados=202A294B242E780832343734D2DE15140E1F362B406F
|
||||
|
||||
;Santa Rita
|
||||
BaseDados=202A294B293F641D3A2D3634D2DE15140E1F362B406F64BABDB7B7BFA05BB0A2BDADB1BB7A
|
||||
|
||||
Usuario=232C2B363138
|
||||
Senha=015D0D575858
|
||||
Key=265C425D5F461C0E564F4E58B5BB4B41394C31413F0E18B2BB
|
||||
Versao='C:\OriusDekstop\Sistemas'
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
|
||||
# VinculaPartes – Ajuste de Vínculo de Noivos no SIRC
|
||||
|
||||
Aplicação em Python (empacotada em `.exe`) criada para **corrigir e vincular os dados dos noivos** entre as tabelas:
|
||||
|
||||
- `V_CASAMENTO`
|
||||
- `V_PESSOA_VINCULO` (que referencia `V_PESSOA`)
|
||||
|
||||
Essa correção é necessária porque, **na tela do SIRC**, os dados são **validados com base nos registros de `V_PESSOA_VINCULO`**.
|
||||
Sem esse vínculo correto, os dados dos noivos podem não aparecer ou não ser validados adequadamente.
|
||||
|
||||
---
|
||||
|
||||
## 1. Como a aplicação funciona
|
||||
|
||||
1. **Lê os dados dos noivos em `V_CASAMENTO`**
|
||||
Para cada casamento, a aplicação busca as informações dos noivos já cadastradas nessa view/tabela.
|
||||
|
||||
2. **Verifica se a pessoa já existe em `V_PESSOA`**
|
||||
- Se **já existir** cadastro da pessoa:
|
||||
- A aplicação **apenas cria o vínculo** correto em `V_PESSOA_VINCULO`.
|
||||
- Se **não existir**:
|
||||
- A aplicação **cria um novo registro em `V_PESSOA`** com base nos dados de `V_CASAMENTO`;
|
||||
- Em seguida, **cria o vínculo** desse registro recém-criado com o casamento em `V_PESSOA_VINCULO`.
|
||||
|
||||
3. **Resultado esperado**
|
||||
- Todos os noivos presentes em `V_CASAMENTO` passam a ter:
|
||||
- Um registro correspondente em `V_PESSOA` (quando necessário);
|
||||
- Um vínculo correto em `V_PESSOA_VINCULO`, permitindo a **validação correta no SIRC**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Dependência do `config.ini`
|
||||
|
||||
A aplicação **deve ser executada na mesma pasta em que está o arquivo `config.ini`**.
|
||||
|
||||
- O `config.ini` contém os **dados de acesso ao banco de dados**.
|
||||
- A aplicação **reutiliza essas mesmas credenciais** para conectar no banco (host, porta, usuário, senha, caminho da base, etc.).
|
||||
- Sem o `config.ini` correto na mesma pasta, a aplicação **não conseguirá conectar** ao banco.
|
||||
|
||||
> 🔹 Em resumo:
|
||||
> **`VinculaPartes.exe` (ou `main.py`) e `config.ini` precisam estar lado a lado na mesma pasta.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Execução
|
||||
|
||||
### 3.1. Pré-requisitos (modo desenvolvimento – Python)
|
||||
|
||||
- Python instalado
|
||||
- Biblioteca `fdb` instalada (`pip install fdb`)
|
||||
- `fbclient.dll` acessível (na pasta do projeto ou em local conhecido)
|
||||
- Arquivo `config.ini` configurado com os dados do banco
|
||||
|
||||
Para executar via Python:
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
Certifique-se de que:
|
||||
|
||||
- `main.py`
|
||||
- `config.ini`
|
||||
- `fbclient.dll`
|
||||
estão na mesma pasta (ou com caminhos corretamente configurados).
|
||||
|
||||
### 3.2. Execução do executável (`.exe`)
|
||||
|
||||
Após gerado o executável:
|
||||
|
||||
- Copie para a mesma pasta:
|
||||
- `VinculaPartes.exe`
|
||||
- `config.ini`
|
||||
- `fbclient.dll` (se necessário)
|
||||
- Dê duplo clique no `VinculaPartes.exe` **ou** execute pelo Prompt de Comando:
|
||||
|
||||
```bash
|
||||
VinculaPartes.exe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Build do executável com PyInstaller
|
||||
|
||||
Para gerar o executável em modo one-file com ícone e dependências embutidas, utilize o comando:
|
||||
|
||||
```bash
|
||||
pyinstaller --onefile --name "VinculaPartes" --icon="images/icon.ico" --hidden-import=fdb --add-binary "fbclient.dll;." main.py
|
||||
```
|
||||
|
||||
**Descrição dos parâmetros principais:**
|
||||
|
||||
- `--onefile`
|
||||
Gera um único arquivo executável.
|
||||
- `--name "VinculaPartes"`
|
||||
Define o nome do executável gerado.
|
||||
- `--icon="images/icon.ico"`
|
||||
Define o ícone do executável.
|
||||
- `--hidden-import=fdb`
|
||||
Garante que o PyInstaller inclua a biblioteca `fdb` no build.
|
||||
- `--add-binary "fbclient.dll;."`
|
||||
Inclui o `fbclient.dll` e o disponibiliza na mesma pasta do executável.
|
||||
- `main.py`
|
||||
Arquivo principal da aplicação.
|
||||
|
||||
Após a compilação, o executável será gerado na pasta `dist/`:
|
||||
|
||||
```text
|
||||
dist/
|
||||
└── VinculaPartes.exe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Resumo
|
||||
|
||||
- A aplicação **corrige o vínculo dos noivos** entre `V_CASAMENTO`, `V_PESSOA` e `V_PESSOA_VINCULO`.
|
||||
- É **fundamental** para que os dados sejam **validados corretamente no SIRC**.
|
||||
- **Depende do `config.ini`** para conectar ao banco de dados.
|
||||
- Pode ser executada via Python (`main.py`) ou via executável (`VinculaPartes.exe`) gerado com PyInstaller.
|
||||
|
||||
> Qualquer alteração futura na estrutura das tabelas ou nas regras de negócio
|
||||
> deve ser refletida na lógica da aplicação antes de nova execução em produção.
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=[],
|
||||
binaries=[('fbclient.dll', '.')],
|
||||
datas=[],
|
||||
hiddenimports=['fdb'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='VinculaPartes',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=['images\\icon.ico'],
|
||||
)
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
from typing import Any, List, Optional, Literal, Union, overload
|
||||
from types import SimpleNamespace
|
||||
from datetime import datetime, date
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.engine import CursorResult
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from actions.ui.ui import warn
|
||||
from database.firebird import Firebird
|
||||
|
||||
|
||||
class BaseRepositoryFirebird:
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Sobrecargas
|
||||
# ------------------------------------------------------------------
|
||||
@overload
|
||||
def _execute(
|
||||
self, sql: str, params: Optional[dict[str, Any]], fetch: Literal["all"]
|
||||
) -> List[Any]: ...
|
||||
|
||||
@overload
|
||||
def _execute(
|
||||
self, sql: str, params: Optional[dict[str, Any]], fetch: Literal["one"]
|
||||
) -> Optional[Any]: ...
|
||||
|
||||
@overload
|
||||
def _execute(
|
||||
self, sql: str, params: Optional[dict[str, Any]], fetch: Literal["none"]
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def _execute(
|
||||
self, sql: str, params: Optional[dict[str, Any]], fetch: Literal["result"]
|
||||
) -> CursorResult[Any]: ...
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Sanitizador seguro de parâmetros (CORREÇÃO CRÍTICA)
|
||||
# ------------------------------------------------------------------
|
||||
def _sanitize_params(self, params: Optional[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""
|
||||
Sanitiza parâmetros antes de enviar ao Firebird.
|
||||
Trava datas inválidas (< 1900) e remove timezone para evitar overflow.
|
||||
"""
|
||||
if params is None:
|
||||
return {}
|
||||
|
||||
safe: dict[str, Any] = {}
|
||||
|
||||
for key, value in params.items():
|
||||
|
||||
# Permite None normalmente
|
||||
if value is None:
|
||||
safe[key] = None
|
||||
continue
|
||||
|
||||
# ---------------------------
|
||||
# Tratamento de datetime
|
||||
# ---------------------------
|
||||
if isinstance(value, datetime):
|
||||
|
||||
# Firebird explode com datas muito antigas
|
||||
if value.year < 1900:
|
||||
warn(
|
||||
f"⚠️ Data inválida detectada em '{key}': {value} "
|
||||
f"(ano < 1900). Definido como NULL para evitar overflow."
|
||||
)
|
||||
safe[key] = None
|
||||
continue
|
||||
|
||||
# Remove timezone se existir (evita timestamp negativo!)
|
||||
if value.tzinfo is not None:
|
||||
safe[key] = value.replace(tzinfo=None)
|
||||
else:
|
||||
safe[key] = value
|
||||
continue
|
||||
|
||||
# ---------------------------
|
||||
# Tratamento de date
|
||||
# ---------------------------
|
||||
if isinstance(value, date):
|
||||
if value.year < 1900:
|
||||
warn(
|
||||
f"⚠️ Data de calendário inválida em '{key}': {value}. "
|
||||
f"Convertido para NULL."
|
||||
)
|
||||
safe[key] = None
|
||||
else:
|
||||
safe[key] = value
|
||||
continue
|
||||
|
||||
# Outros valores seguem direto
|
||||
safe[key] = value
|
||||
|
||||
return safe
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Execução de SQL
|
||||
# ------------------------------------------------------------------
|
||||
def _execute(
|
||||
self,
|
||||
sql: str,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
fetch: Literal["all", "one", "none", "result"] = "result",
|
||||
) -> Union[List[Any], Optional[Any], None, CursorResult[Any]]:
|
||||
|
||||
engine = Firebird.get_engine()
|
||||
|
||||
# 🔥 Sanitiza todos os parâmetros antes de enviar ao driver
|
||||
safe_params = self._sanitize_params(params)
|
||||
|
||||
try:
|
||||
with engine.begin() as conn:
|
||||
result = conn.execute(text(sql), safe_params)
|
||||
|
||||
# Lê BLOBs com segurança
|
||||
def _read_blob(value):
|
||||
if hasattr(value, "read"):
|
||||
try:
|
||||
return value.read()
|
||||
except Exception:
|
||||
return b""
|
||||
return value
|
||||
|
||||
# all
|
||||
if fetch == "all":
|
||||
rows = []
|
||||
for row in result.mappings().all():
|
||||
row_dict = {k.lower(): _read_blob(v) for k, v in row.items()}
|
||||
rows.append(SimpleNamespace(**row_dict))
|
||||
return rows
|
||||
|
||||
# one
|
||||
elif fetch == "one":
|
||||
row = result.mappings().first()
|
||||
if row:
|
||||
row_dict = {k.lower(): _read_blob(v) for k, v in row.items()}
|
||||
return SimpleNamespace(**row_dict)
|
||||
return None
|
||||
|
||||
# none
|
||||
elif fetch == "none":
|
||||
return None
|
||||
|
||||
# result
|
||||
return result
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
warn("⚠️ [ERRO SQL]: execução falhou")
|
||||
warn(f"SQL:\n{sql}")
|
||||
warn(f"Parâmetros SANITIZADOS enviados ao banco:\n{safe_params}")
|
||||
raise
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Métodos utilitários públicos
|
||||
# ------------------------------------------------------------------
|
||||
def query(
|
||||
self, sql: str, params: Optional[dict[str, Any]] = None
|
||||
) -> CursorResult[Any]:
|
||||
return self._execute(sql, params, fetch="result")
|
||||
|
||||
def fetch_all(self, sql: str, params: Optional[dict[str, Any]] = None) -> List[Any]:
|
||||
return self._execute(sql, params, fetch="all")
|
||||
|
||||
def fetch_one(
|
||||
self, sql: str, params: Optional[dict[str, Any]] = None
|
||||
) -> Optional[Any]:
|
||||
return self._execute(sql, params, fetch="one")
|
||||
|
||||
def run(self, sql: str, params: Optional[dict[str, Any]] = None) -> None:
|
||||
self._execute(sql, params, fetch="none")
|
||||
|
||||
def run_and_return(
|
||||
self, sql: str, params: Optional[dict[str, Any]] = None
|
||||
) -> Optional[Any]:
|
||||
return self._execute(sql, params, fetch="one")
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
# Importa o módulo padrão do Python para ler arquivos INI (.ini)
|
||||
import configparser
|
||||
|
||||
# Importa Path, que facilita o trabalho com caminhos de arquivos (mais seguro que strings)
|
||||
from pathlib import Path
|
||||
|
||||
# Importa tipos para anotações — Dict (dicionário) e Any (qualquer tipo)
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
# Define uma classe chamada ConfigIni
|
||||
class ConfigIni:
|
||||
|
||||
@staticmethod
|
||||
def read(path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Lê um arquivo INI (ignorando comentários iniciados por ';')
|
||||
e retorna um dicionário com suas seções e pares chave=valor.
|
||||
|
||||
Tenta múltiplas codificações comuns no Windows:
|
||||
- utf-8
|
||||
- utf-8-sig
|
||||
- latin-1
|
||||
- cp1252
|
||||
"""
|
||||
|
||||
# Converte o caminho recebido (string) em um objeto Path (mais seguro e portável)
|
||||
config_path = Path(path)
|
||||
|
||||
# Verifica se o arquivo realmente existe no caminho informado
|
||||
if not config_path.exists():
|
||||
# Caso não exista, lança uma exceção com uma mensagem explicativa
|
||||
raise FileNotFoundError(f"Arquivo não encontrado: {path}")
|
||||
|
||||
# Lista de codificações a tentar, em ordem de preferência
|
||||
encodings_to_try = ["utf-8", "utf-8-sig", "latin-1", "cp1252"]
|
||||
|
||||
last_error: Exception | None = None
|
||||
config: configparser.ConfigParser | None = None
|
||||
|
||||
# Tenta ler o arquivo usando diferentes encodings
|
||||
for enc in encodings_to_try:
|
||||
try:
|
||||
tmp_config = configparser.ConfigParser()
|
||||
# Garante que as chaves mantenham o mesmo formato de maiúsculas/minúsculas
|
||||
# Por padrão, o configparser converte tudo para minúsculas.
|
||||
tmp_config.optionxform = str
|
||||
|
||||
# Tenta ler o arquivo com a codificação atual
|
||||
tmp_config.read(config_path, encoding=enc)
|
||||
|
||||
# Se chegou aqui sem UnicodeDecodeError, consideramos que deu certo
|
||||
config = tmp_config
|
||||
break
|
||||
except UnicodeDecodeError as e:
|
||||
# Guarda o último erro para caso nenhuma codificação funcione
|
||||
last_error = e
|
||||
continue
|
||||
|
||||
# Se nenhuma codificação funcionou, levanta um erro mais amigável
|
||||
if config is None:
|
||||
msg = (
|
||||
f"Não foi possível decodificar o arquivo INI '{path}' "
|
||||
f"usando as codificações: {', '.join(encodings_to_try)}"
|
||||
)
|
||||
if last_error:
|
||||
raise UnicodeDecodeError(
|
||||
last_error.encoding or "utf-8",
|
||||
last_error.object,
|
||||
last_error.start,
|
||||
last_error.end,
|
||||
msg,
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# Cria um dicionário vazio que armazenará os dados lidos do INI
|
||||
data: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# Percorre cada seção do arquivo INI (exemplo: [Geral], [Banco], etc.)
|
||||
for section in config.sections():
|
||||
# Cria um dicionário interno para armazenar as chaves e valores dessa seção
|
||||
data[section] = {}
|
||||
|
||||
# Percorre todas as chaves e valores da seção atual
|
||||
for key, value in config.items(section):
|
||||
# .strip() remove espaços no início/fim
|
||||
# .strip("'\"") remove aspas simples ou duplas em volta do valor (se existirem)
|
||||
data[section][key] = value.strip().strip("'\"")
|
||||
|
||||
# Retorna o dicionário completo contendo todas as seções e seus pares chave=valor
|
||||
return data
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
||||
class DictToObj:
|
||||
def __new__(cls, x):
|
||||
# dict (ou Mapping): vira SimpleNamespace com conversão recursiva
|
||||
if isinstance(x, Mapping):
|
||||
return SimpleNamespace(**{k: cls(v) for k, v in x.items()})
|
||||
# listas: converte cada item
|
||||
if isinstance(x, list):
|
||||
return [cls(i) for i in x]
|
||||
# (opcional) outras coleções comuns
|
||||
if isinstance(x, tuple):
|
||||
return tuple(cls(i) for i in x)
|
||||
if isinstance(x, set):
|
||||
return {cls(i) for i in x}
|
||||
# primitivos: retornam como estão
|
||||
return x
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
from actions.data.json_size import JsonSize
|
||||
|
||||
|
||||
class EnsureBatchLimit:
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Função reutilizável: recebe o lote (array) e o item, respeita o limite
|
||||
# Retorna (to_send, new_batch):
|
||||
# - se couber: (None, batch_com_item)
|
||||
# - se não couber: (batch_atual_para_envio, novo_batch_com_item)
|
||||
# ------------------------------------------------------------------
|
||||
def execute(self, batch: list, item, max_bytes: int):
|
||||
|
||||
batch_size = JsonSize(batch)
|
||||
item_size = JsonSize(item)
|
||||
|
||||
if batch_size + item_size <= max_bytes:
|
||||
batch.append(item)
|
||||
return None, batch # nada a enviar agora
|
||||
else:
|
||||
# não coube: envia o lote atual e começa um novo com o item
|
||||
to_send = batch[:] # cópia para envio
|
||||
new_batch = [item]
|
||||
return to_send, new_batch
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
from pydantic import BaseModel
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
def generate_insert_sql(table_name: str, data: BaseModel | dict) -> str:
|
||||
"""
|
||||
Gera um comando SQL INSERT seguro para Firebird.
|
||||
- Aceita BaseModel (Pydantic) ou dict.
|
||||
- Ignora *_ID None.
|
||||
- Mantém colunas em MAIÚSCULAS e parâmetros em minúsculas (para bind funcionar).
|
||||
"""
|
||||
|
||||
# Converte o model em dict limpo
|
||||
if isinstance(data, BaseModel):
|
||||
data_dict = data.model_dump(exclude_unset=True)
|
||||
elif isinstance(data, dict):
|
||||
data_dict = data
|
||||
else:
|
||||
raise TypeError("O parâmetro 'data' deve ser um BaseModel ou dict.")
|
||||
|
||||
columns = []
|
||||
params = []
|
||||
returning_fields = []
|
||||
|
||||
for key, value in data_dict.items():
|
||||
column_name = key.upper()
|
||||
|
||||
# Converte Decimal → float
|
||||
if isinstance(value, Decimal):
|
||||
data_dict[key] = float(value)
|
||||
|
||||
# Campos válidos para retorno
|
||||
if value is not None:
|
||||
returning_fields.append(column_name)
|
||||
|
||||
# Coluna em maiúsculo, parâmetro em minúsculo
|
||||
columns.append(column_name)
|
||||
params.append(f":{key}")
|
||||
|
||||
columns_str = ", ".join(columns)
|
||||
params_str = ", ".join(params)
|
||||
returning_str = ", ".join(returning_fields) if returning_fields else "*"
|
||||
|
||||
sql = (
|
||||
f"INSERT INTO {table_name} (\n"
|
||||
f" {columns_str}\n"
|
||||
f") VALUES (\n"
|
||||
f" {params_str}\n"
|
||||
f") RETURNING {returning_str};"
|
||||
)
|
||||
|
||||
return sql
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
class IsBlank:
|
||||
|
||||
@staticmethod
|
||||
def __new__(self, val) -> bool:
|
||||
|
||||
if val is None:
|
||||
return True
|
||||
|
||||
if isinstance(val, (str, bytes)):
|
||||
return len(val.strip()) == 0
|
||||
|
||||
return False
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
class JsonSize:
|
||||
@staticmethod
|
||||
def _safe_default(o):
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
|
||||
if isinstance(o, (datetime, date)):
|
||||
return o.isoformat()
|
||||
if isinstance(o, Decimal):
|
||||
return float(o)
|
||||
if isinstance(o, SimpleNamespace):
|
||||
return vars(o)
|
||||
if hasattr(o, "__dict__"):
|
||||
return vars(o)
|
||||
return str(o)
|
||||
|
||||
def __new__(cls, obj):
|
||||
try:
|
||||
# 💡 ensure_ascii=False mantém acentos; surrogatepass evita falha com caracteres inválidos
|
||||
json_str = json.dumps(
|
||||
obj,
|
||||
ensure_ascii=False,
|
||||
default=cls._safe_default,
|
||||
)
|
||||
# 💡 errors='ignore' ou 'replace' evita que .encode() quebre
|
||||
return len(json_str.encode("utf-8", errors="ignore"))
|
||||
except Exception:
|
||||
# fallback de segurança (nunca deve quebrar)
|
||||
return len(str(obj).encode("utf-8", errors="ignore"))
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
from collections.abc import Mapping
|
||||
from decimal import Decimal
|
||||
from datetime import datetime, date
|
||||
|
||||
|
||||
class ObjToDict:
|
||||
def __new__(cls, x):
|
||||
# SimpleNamespace -> dict (recursivo)
|
||||
if isinstance(x, SimpleNamespace):
|
||||
return {k: cls(v) for k, v in vars(x).items()}
|
||||
|
||||
# dict / Mapping -> dict (recursivo)
|
||||
if isinstance(x, Mapping):
|
||||
return {k: cls(v) for k, v in x.items()}
|
||||
|
||||
# listas/tuplas/conjuntos -> preserva tipo
|
||||
if isinstance(x, list):
|
||||
return [cls(i) for i in x]
|
||||
if isinstance(x, tuple):
|
||||
return tuple(cls(i) for i in x)
|
||||
if isinstance(x, set):
|
||||
return {cls(i) for i in x}
|
||||
|
||||
# Decimal -> float
|
||||
if isinstance(x, Decimal):
|
||||
return float(x)
|
||||
|
||||
# datetime / date -> string ISO (compatível com JSON)
|
||||
if isinstance(x, (datetime, date)):
|
||||
return x.isoformat()
|
||||
|
||||
# Outros tipos -> mantém
|
||||
return x
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
from datetime import datetime
|
||||
|
||||
|
||||
class FileNameGenerator:
|
||||
"""
|
||||
Gera nomes de arquivos únicos com base na data e hora atuais.
|
||||
Exemplo: relatorio_2025-11-11_17-43-22.txt
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prefix: str = "arquivo",
|
||||
):
|
||||
self.prefix = prefix
|
||||
|
||||
def generate(self, prefix="arquivo") -> str:
|
||||
"""Gera o nome completo do arquivo com base na data e hora."""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
filename = f"{prefix}_{timestamp}"
|
||||
return str(filename)
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import platform
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from actions.env.mirror_sync_env import MirrorSyncEnv
|
||||
from actions.ui.ui import warn, fail
|
||||
from packages.v1.parametros.repositories.g_config.g_config_show_by_nome_repository import (
|
||||
GConfigShowByNomeRepository,
|
||||
)
|
||||
from packages.v1.parametros.schemas.g_config_schema import GConfigNomeSchema
|
||||
|
||||
|
||||
class InputBaseResolver:
|
||||
"""
|
||||
Resolve dinamicamente caminhos base do GED sem precisar passar nada externo.
|
||||
Apenas:
|
||||
get_input_base(nome_variavel, sistema_id)
|
||||
|
||||
Tudo o resto — carregar MirrorSyncEnv, decrypt, buscar LOCAL_IMAGEM —
|
||||
é feito internamente.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# Carrega ENV automaticamente
|
||||
self.env = MirrorSyncEnv().as_object()
|
||||
|
||||
# GConfig carregado internamente
|
||||
self.g_config_repo = GConfigShowByNomeRepository()
|
||||
|
||||
# Cache por chave (env_var_name, sistema_id)
|
||||
self._cache: Dict[Tuple[str, int], str] = {}
|
||||
|
||||
# -------------------------------------------------------
|
||||
def get_input_base(self, env_var_name: str, sistema_id: int) -> str:
|
||||
"""
|
||||
Obtém dinamicamente o caminho do GED:
|
||||
- Se Windows + ged_local=True → retorna variável do .env
|
||||
- Se Windows + ged_local=False → busca LOCAL_IMAGEM no GConfig
|
||||
- Se Linux → sempre retorna variável do .env
|
||||
|
||||
Parâmetros:
|
||||
env_var_name: nome da variável de ambiente (ex.: "ged_tabelionato")
|
||||
sistema_id: ID do sistema (ex.: 2)
|
||||
"""
|
||||
|
||||
cache_key = (env_var_name, sistema_id)
|
||||
|
||||
if cache_key in self._cache:
|
||||
return self._cache[cache_key]
|
||||
|
||||
resolved = self._resolve_path(env_var_name, sistema_id)
|
||||
self._cache[cache_key] = resolved or ""
|
||||
return resolved
|
||||
|
||||
# -------------------------------------------------------
|
||||
def _resolve_path(self, env_var_name: str, sistema_id: int) -> str:
|
||||
system = platform.system()
|
||||
|
||||
ged_local = str(getattr(self.env, "ged_local", "false")).lower() == "true"
|
||||
dynamic_path = getattr(self.env, env_var_name, "")
|
||||
|
||||
# ---------------- WINDOWS ----------------
|
||||
if system == "Windows":
|
||||
if ged_local:
|
||||
# Usa o caminho vindo do .env (dinâmico)
|
||||
return dynamic_path
|
||||
|
||||
# Busca LOCAL_IMAGEM no GConfig
|
||||
return self._resolve_from_gconfig(sistema_id)
|
||||
|
||||
# ---------------- LINUX ------------------
|
||||
if system == "Linux":
|
||||
return dynamic_path
|
||||
|
||||
# ---------------- OUTROS -----------------
|
||||
warn(f"Sistema operacional não suportado: {system}")
|
||||
return ""
|
||||
|
||||
# -------------------------------------------------------
|
||||
def _resolve_from_gconfig(self, sistema_id: int) -> str:
|
||||
"""Busca LOCAL_IMAGEM no GConfig automaticamente."""
|
||||
try:
|
||||
result = self.g_config_repo.execute(
|
||||
GConfigNomeSchema(nome="LOCAL_IMAGEM", sistema_id=sistema_id)
|
||||
)
|
||||
return result.valor
|
||||
except Exception as e:
|
||||
fail(f"Erro no GConfig (sistema_id={sistema_id}): {e}")
|
||||
return ""
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import json
|
||||
from json import JSONDecodeError
|
||||
from types import SimpleNamespace
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
|
||||
class JsonFileLoader:
|
||||
"""
|
||||
Carrega arquivos JSON e retorna como objeto (SimpleNamespace) ou dict.
|
||||
Retorna None quando não houver arquivo ou dados.
|
||||
"""
|
||||
|
||||
def __init__(self, file_path: Union[str, Path], encoding: str = "utf-8"):
|
||||
self.path = Path(file_path)
|
||||
self.encoding = encoding
|
||||
|
||||
def load(
|
||||
self, as_namespace: bool = True, empty_as_none: bool = True
|
||||
) -> Optional[Any]:
|
||||
"""
|
||||
Lê o JSON do disco.
|
||||
- as_namespace=True: retorna SimpleNamespace (com conversão recursiva).
|
||||
- as_namespace=False: retorna dict/list nativos.
|
||||
- empty_as_none=True: {}, [] ou None são tratados como "sem dados" e retornam None.
|
||||
"""
|
||||
# 1) Arquivo inexistente ou não regular
|
||||
if not self.path.exists() or not self.path.is_file():
|
||||
return None
|
||||
|
||||
# 2) Arquivo vazio
|
||||
if self.path.stat().st_size == 0:
|
||||
return None
|
||||
|
||||
# 3) Leitura + limpeza
|
||||
try:
|
||||
with self.path.open("r", encoding=self.encoding) as f:
|
||||
raw = f.read()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
raw = (raw or "").strip()
|
||||
if not raw:
|
||||
return None
|
||||
|
||||
# 4) Parse do JSON
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except JSONDecodeError:
|
||||
return None
|
||||
|
||||
# 5) Tratar dados vazios
|
||||
if empty_as_none and (data is None or data == {} or data == []):
|
||||
return None
|
||||
|
||||
# 6) Converter (opcional) para SimpleNamespace
|
||||
return self._to_namespace(data) if as_namespace else data
|
||||
|
||||
# ---------- helpers ----------
|
||||
@classmethod
|
||||
def _to_namespace(cls, value: Any) -> Any:
|
||||
"""Converte recursivamente dict/list em SimpleNamespace/list."""
|
||||
if isinstance(value, dict):
|
||||
return SimpleNamespace(
|
||||
**{k: cls._to_namespace(v) for k, v in value.items()}
|
||||
)
|
||||
if isinstance(value, list):
|
||||
return [cls._to_namespace(v) for v in value]
|
||||
return value
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
import json
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Optional, Literal, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from actions.file.json_file_saver import JsonFileSaver
|
||||
|
||||
|
||||
class JsonFileMerger:
|
||||
"""
|
||||
Classe utilitária para unir um novo JSON com um existente em disco,
|
||||
aplicando merge profundo e salvando o resultado final.
|
||||
|
||||
Mantém o mesmo padrão seguro de conversão da classe JsonFileSaver.
|
||||
"""
|
||||
|
||||
def __init__(self, saver: Optional[JsonFileSaver] = None):
|
||||
self.saver = saver or JsonFileSaver()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Carrega JSON existente (se houver)
|
||||
# ------------------------------------------------------------
|
||||
def _load_existing(self, file_path: Path) -> Any:
|
||||
if not file_path.exists():
|
||||
return {}
|
||||
try:
|
||||
with open(file_path, "r", encoding=self.saver.encoding) as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
# Em caso de erro, considera como vazio para não quebrar o fluxo
|
||||
return {}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Merge de listas (não duplica itens)
|
||||
# ------------------------------------------------------------
|
||||
def _merge_lists(self, base_list: list, new_list: list) -> list:
|
||||
combined = base_list[:]
|
||||
for item in new_list:
|
||||
if item not in combined:
|
||||
combined.append(item)
|
||||
return combined
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Merge profundo (deep merge) para dicionários
|
||||
# ------------------------------------------------------------
|
||||
def _deep_merge(self, base: dict, new: dict) -> dict:
|
||||
for key, value in new.items():
|
||||
if key in base:
|
||||
# dict + dict → merge recursivo
|
||||
if isinstance(base[key], dict) and isinstance(value, dict):
|
||||
base[key] = self._deep_merge(base[key], value)
|
||||
|
||||
# list + list → merge de listas
|
||||
elif isinstance(base[key], list) and isinstance(value, list):
|
||||
base[key] = self._merge_lists(base[key], value)
|
||||
|
||||
# tipos diferentes ou simples → substitui
|
||||
else:
|
||||
base[key] = value
|
||||
else:
|
||||
base[key] = value
|
||||
|
||||
return base
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Estratégias de merge
|
||||
# ------------------------------------------------------------
|
||||
def _apply_strategy(self, existing: Any, new: Any, strategy: str) -> Any:
|
||||
# Se ambos forem listas, usa merge de listas
|
||||
if isinstance(existing, list) and isinstance(new, list):
|
||||
return self._merge_lists(existing, new)
|
||||
|
||||
# Se tipos forem diferentes, substitui completamente
|
||||
if type(existing) is not type(new):
|
||||
return new
|
||||
|
||||
# replace → ignora o que existia
|
||||
if strategy == "replace":
|
||||
return new
|
||||
|
||||
# update → comportamento similar a dict.update (apenas para dicts)
|
||||
if strategy == "update":
|
||||
if isinstance(existing, dict) and isinstance(new, dict):
|
||||
existing.update(new)
|
||||
return existing
|
||||
# para outros tipos, apenas substitui
|
||||
return new
|
||||
|
||||
# default / "deep" → deep merge para dicts
|
||||
if strategy == "deep":
|
||||
if isinstance(existing, dict) and isinstance(new, dict):
|
||||
return self._deep_merge(existing, new)
|
||||
# se não forem dicts, substitui
|
||||
return new
|
||||
|
||||
# fallback: se for estratégia desconhecida, substitui
|
||||
return new
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# NOVO MÉTODO — merge sem salvar (apenas processa)
|
||||
# ------------------------------------------------------------
|
||||
def merge_data(
|
||||
self,
|
||||
existing_json: Any,
|
||||
new_json: Union[dict, BaseModel, SimpleNamespace],
|
||||
strategy: Literal["replace", "update", "deep"] = "deep",
|
||||
) -> Any:
|
||||
|
||||
new_clean = self.saver._convert(new_json)
|
||||
return self._apply_strategy(existing_json, new_clean, strategy)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# MÉTODO PRINCIPAL — AGORA USANDO merge_data()
|
||||
# ------------------------------------------------------------
|
||||
def merge_and_save(
|
||||
self,
|
||||
new_json: Union[dict, BaseModel, SimpleNamespace],
|
||||
file_path: str,
|
||||
strategy: Literal["replace", "update", "deep"] = "deep",
|
||||
add_timestamp: bool = False,
|
||||
) -> dict:
|
||||
"""
|
||||
Faz merge entre existing.json ← new_json e salva o resultado final.
|
||||
|
||||
Parâmetros:
|
||||
new_json:
|
||||
- Dado novo que será mesclado ao JSON existente.
|
||||
Pode ser dict, BaseModel ou SimpleNamespace.
|
||||
|
||||
file_path:
|
||||
- Caminho do arquivo JSON em disco.
|
||||
|
||||
strategy:
|
||||
- "replace" → sobrescreve tudo
|
||||
- "update" → comportamento semelhante a dict.update
|
||||
- "deep" → merge recursivo (deep merge) para dicts
|
||||
e merge de listas sem duplicação
|
||||
|
||||
add_timestamp:
|
||||
- Se True, adiciona timestamp no nome do arquivo ao salvar.
|
||||
|
||||
Retorno:
|
||||
dict com:
|
||||
{
|
||||
"success": bool,
|
||||
"path": str,
|
||||
"size": int,
|
||||
"error": Optional[str],
|
||||
}
|
||||
"""
|
||||
try:
|
||||
path = Path(file_path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Carrega JSON existente
|
||||
existing = self._load_existing(path)
|
||||
|
||||
# Novo merge utilizando merge_data()
|
||||
merged = self.merge_data(existing, new_json, strategy)
|
||||
|
||||
# Salva em disco
|
||||
return self.saver.save(
|
||||
data=merged,
|
||||
file_path=str(path),
|
||||
as_json=True,
|
||||
add_timestamp=add_timestamp,
|
||||
mode="overwrite",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return {
|
||||
"success": False,
|
||||
"path": file_path,
|
||||
"size": 0,
|
||||
"error": str(e),
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
import json
|
||||
import traceback
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
from types import SimpleNamespace
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Literal, Union
|
||||
from pydantic import BaseModel
|
||||
|
||||
# ✔️ agora usando sua UI
|
||||
from actions.ui.ui import ok, fail
|
||||
|
||||
|
||||
class JsonFileSaver:
|
||||
"""
|
||||
Classe utilitária para salvar qualquer tipo de dado em disco,
|
||||
automaticamente convertido para JSON se aplicável.
|
||||
|
||||
Suporta: dict, list, str, bytes, BaseModel, SimpleNamespace, Decimal, datetime, etc.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
encoding: str = "utf-8",
|
||||
indent: int = 4,
|
||||
default_suffix: str = ".json",
|
||||
):
|
||||
self.encoding = encoding
|
||||
self.indent = indent
|
||||
self.default_suffix = default_suffix
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Método público principal
|
||||
# ------------------------------------------------------------
|
||||
def save(
|
||||
self,
|
||||
data: Union[str, bytes, dict, list, Any],
|
||||
file_path: str,
|
||||
as_json: Optional[bool] = None,
|
||||
add_timestamp: bool = False,
|
||||
mode: Literal["overwrite", "append"] = "overwrite",
|
||||
) -> dict:
|
||||
"""
|
||||
Salva o conteúdo em disco com serialização segura.
|
||||
"""
|
||||
try:
|
||||
path = Path(file_path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Adiciona timestamp no nome, se solicitado
|
||||
if add_timestamp:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
stem = path.stem
|
||||
suffix = path.suffix or self.default_suffix
|
||||
path = path.with_name(f"{stem}_{timestamp}{suffix}")
|
||||
|
||||
# Detecta automaticamente se é JSON
|
||||
if as_json is None:
|
||||
as_json = isinstance(data, (dict, list, SimpleNamespace, BaseModel))
|
||||
|
||||
# Define o modo de escrita
|
||||
write_mode = (
|
||||
"wb" if isinstance(data, bytes) else "a" if mode == "append" else "w"
|
||||
)
|
||||
|
||||
# Salva arquivo
|
||||
if isinstance(data, bytes):
|
||||
with open(path, write_mode) as f:
|
||||
f.write(data)
|
||||
|
||||
elif as_json:
|
||||
safe_data = self._convert(data)
|
||||
with open(path, write_mode, encoding=self.encoding) as f:
|
||||
json.dump(safe_data, f, ensure_ascii=False, indent=self.indent)
|
||||
|
||||
else:
|
||||
if not isinstance(data, str):
|
||||
data = str(data)
|
||||
with open(path, write_mode, encoding=self.encoding) as f:
|
||||
f.write(data)
|
||||
|
||||
file_size = path.stat().st_size if path.exists() else 0
|
||||
|
||||
# ✔️ substitui print() por UI
|
||||
ok(f"Arquivo salvo: {path} ({file_size} bytes)")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"path": str(path.resolve()),
|
||||
"size": file_size,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_message = (
|
||||
f"Falha ao salvar arquivo '{file_path}': {e}\n"
|
||||
f"{traceback.format_exc()}"
|
||||
)
|
||||
|
||||
# ✔️ substitui print de erro por UI
|
||||
fail(error_message)
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"path": str(file_path),
|
||||
"size": 0,
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Conversor recursivo interno
|
||||
# ------------------------------------------------------------
|
||||
def _convert(self, obj: Any) -> Any:
|
||||
"""Converte qualquer tipo para formato serializável JSON."""
|
||||
if obj is None:
|
||||
return None
|
||||
if isinstance(obj, (str, int, float, bool)):
|
||||
return obj
|
||||
if isinstance(obj, bytes):
|
||||
return obj.decode("utf-8", errors="ignore")
|
||||
if isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat()
|
||||
if isinstance(obj, SimpleNamespace):
|
||||
return {k: self._convert(v) for k, v in vars(obj).items()}
|
||||
if isinstance(obj, BaseModel):
|
||||
return obj.model_dump()
|
||||
if isinstance(obj, list):
|
||||
return [self._convert(i) for i in obj]
|
||||
if isinstance(obj, dict):
|
||||
return {k: self._convert(v) for k, v in obj.items()}
|
||||
return str(obj)
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import json
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any
|
||||
|
||||
|
||||
class JsonArrayPersistentStorage:
|
||||
"""
|
||||
Classe para gravar e atualizar um JSON de forma persistente,
|
||||
com MERGE profundo e proteção contra escrita paralela.
|
||||
"""
|
||||
|
||||
def __init__(self, caminho_arquivo: Path):
|
||||
self.caminho = caminho_arquivo
|
||||
self.lock = threading.Lock() # 🔒 proteção de escrita
|
||||
self.caminho.parent.mkdir(parents=True, exist_ok=True)
|
||||
self._cache = self._load_existing()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def _load_existing(self) -> Dict[str, Any]:
|
||||
if not self.caminho.exists():
|
||||
return {}
|
||||
try:
|
||||
with open(self.caminho, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def _deep_merge(self, original: Any, novo: Any) -> Any:
|
||||
if isinstance(original, dict) and isinstance(novo, dict):
|
||||
for k, v in novo.items():
|
||||
if k not in original:
|
||||
original[k] = v
|
||||
else:
|
||||
original[k] = self._deep_merge(original[k], v)
|
||||
return original
|
||||
|
||||
if isinstance(original, list) and isinstance(novo, list):
|
||||
for item in novo:
|
||||
if item not in original:
|
||||
original.append(item)
|
||||
return original
|
||||
|
||||
return novo
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def _save(self):
|
||||
with open(self.caminho, "w", encoding="utf-8") as f:
|
||||
json.dump(self._cache, f, indent=4, ensure_ascii=False)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# MÉTODO QUE RESOLVE O ERRO - AGORA COM LOCK
|
||||
# ------------------------------------------------------------------
|
||||
def add(self, novos_dados: Dict[str, List[dict]]):
|
||||
"""
|
||||
Adiciona novos dados com merge profundo.
|
||||
Protegido por lock para evitar race condition.
|
||||
"""
|
||||
|
||||
with self.lock: # 🔒 trava escrita e merge até finalizar
|
||||
self._cache = self._deep_merge(self._cache, novos_dados)
|
||||
self._save()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def get_all(self) -> Dict[str, Any]:
|
||||
return self._cache
|
||||
|
|
@ -1,300 +0,0 @@
|
|||
from pathlib import Path
|
||||
from typing import Optional, Iterable, Dict, Any, List, TypedDict
|
||||
from actions.string.file_name_generator import FileNameGenerator
|
||||
from actions.ui.ui import ok, warn, fail
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# TRATATIVA DE ERRO SMB — WinError 64 (Rede indisponível)
|
||||
# -------------------------------------------------------------------
|
||||
def _exists_safe(path: Path, verbose: bool = True) -> bool:
|
||||
"""
|
||||
Verifica se o path existe sem explodir WinError 64 (rede indisponível).
|
||||
"""
|
||||
try:
|
||||
return path.exists()
|
||||
except OSError as e:
|
||||
if getattr(e, "winerror", None) == 64:
|
||||
if verbose:
|
||||
fail(f"⚠️ Rede indisponível ao acessar: {path}")
|
||||
return False
|
||||
raise
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# VERIFICAÇÃO REAL DE ACESSO AO ARQUIVO
|
||||
# -------------------------------------------------------------------
|
||||
def can_open_file(path: Path, verbose: bool = True) -> bool:
|
||||
"""
|
||||
Garante que o arquivo pode ser aberto — diferentemente de .exists().
|
||||
Captura erros reais de SMB/Permissão.
|
||||
"""
|
||||
try:
|
||||
with open(path, "rb"):
|
||||
return True
|
||||
|
||||
except PermissionError:
|
||||
if verbose:
|
||||
fail(f"❌ Permissão negada ao tentar abrir: {path}")
|
||||
return False
|
||||
|
||||
except FileNotFoundError:
|
||||
if verbose:
|
||||
fail(f"❌ Arquivo não encontrado no momento da abertura: {path}")
|
||||
return False
|
||||
|
||||
except OSError as e:
|
||||
if verbose:
|
||||
fail(f"⚠️ Erro ao abrir arquivo ({e}) → {path}")
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# TABELAS DE TIPOS
|
||||
# ---------------------------------------------------------------
|
||||
class ArquivoLocalizado(TypedDict):
|
||||
caminho_arquivo: str
|
||||
tipo_documento: str
|
||||
nome_documento: str
|
||||
|
||||
|
||||
TIPOS_PADRAO: Dict[str, Dict[str, str]] = {
|
||||
"Ato": {"base": "Ato", "prefixo": "A_", "tipo_documento": "ATO_LAVRADO"},
|
||||
"Cartao": {
|
||||
"base": "Cartao",
|
||||
"prefixo": "C_",
|
||||
"tipo_documento": "DOCUMENTO_PESSOAL",
|
||||
},
|
||||
"Biometria": {
|
||||
"base": "Biometria",
|
||||
"prefixo": "Biometria_",
|
||||
"tipo_documento": "DOCUMENTO_PESSOAL",
|
||||
},
|
||||
"Procuracao": {
|
||||
"base": "Procuracao",
|
||||
"prefixo": "P_",
|
||||
"tipo_documento": "PROCURACAO",
|
||||
},
|
||||
"Documento": {
|
||||
"base": "Documento",
|
||||
"prefixo": "D_",
|
||||
"tipo_documento": "DOCUMENTO_PESSOAL",
|
||||
},
|
||||
"Registro": {"base": "Registro", "prefixo": "R_", "tipo_documento": "MATRICULA"},
|
||||
"Contrato": {"base": "Contrato", "prefixo": "T_", "tipo_documento": "CONTRATO"},
|
||||
"Dut": {"base": "Dut", "prefixo": "U_", "tipo_documento": "OUTROS"},
|
||||
"RegistroImoveis": {"base": "", "prefixo": "R_", "tipo_documento": "MATRICULA"},
|
||||
}
|
||||
|
||||
PREFIXOS_RC: Dict[str, List[str]] = {
|
||||
"nascimento": ["L_A_", "C_A_"],
|
||||
"casamento": ["L_B_", "C_B_"],
|
||||
"obito": ["L_C_", "C_C_"],
|
||||
"estrangeiro": ["L_E_", "C_E_"],
|
||||
"averbacao": ["R_A_"],
|
||||
}
|
||||
|
||||
EXTS: tuple[str, ...] = (".spd", ".tif", ".tiff")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# FUNÇÕES AUXILIARES
|
||||
# ---------------------------------------------------------------
|
||||
def _nomes_possiveis(
|
||||
prefixos: Iterable[str],
|
||||
id6: str,
|
||||
sufixos_extras: Optional[Iterable[str]] = None,
|
||||
) -> List[str]:
|
||||
simbolos = ("#", "_", "")
|
||||
sufixos = list(sufixos_extras) if sufixos_extras is not None else [""]
|
||||
nomes: List[str] = []
|
||||
|
||||
for prefixo in prefixos:
|
||||
for extra in sufixos:
|
||||
for simbolo in simbolos:
|
||||
for ext in EXTS:
|
||||
nomes.append(f"{prefixo}{id6}{extra}{simbolo}{ext}")
|
||||
|
||||
return nomes
|
||||
|
||||
|
||||
def _calcular_pasta(arquivo_id: int) -> str:
|
||||
if arquivo_id < 1000:
|
||||
return f"{arquivo_id:03d}"
|
||||
return str(arquivo_id)[:3]
|
||||
|
||||
|
||||
def _gerar_nome_documento(caminho: Path, tipo_documento: str) -> str:
|
||||
base_sem_ext = caminho.stem
|
||||
return FileNameGenerator.generate(base_sem_ext)
|
||||
|
||||
|
||||
def _montar_retorno(caminho: Path, tipo_documento: str) -> ArquivoLocalizado:
|
||||
return ArquivoLocalizado(
|
||||
caminho_arquivo=str(caminho),
|
||||
tipo_documento=tipo_documento,
|
||||
nome_documento=_gerar_nome_documento(caminho, tipo_documento),
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# FUNÇÃO PRINCIPAL — agora com verificação REAL de acesso SMB
|
||||
# ---------------------------------------------------------------
|
||||
def localizar_arquivo_por_id(
|
||||
arquivo_id: Optional[int | float],
|
||||
pasta_origem: str,
|
||||
natureza: Optional[str] = None,
|
||||
tipos_permitidos: Optional[Iterable[str]] = None,
|
||||
registro: Optional[str] = None,
|
||||
livro: Optional[str] = None,
|
||||
registro_anterior: Optional[str] = None,
|
||||
verbose: bool = True,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
|
||||
if arquivo_id is None:
|
||||
if verbose:
|
||||
warn("ID inválido (None) — Ignorando busca de arquivo.")
|
||||
return None
|
||||
|
||||
try:
|
||||
arquivo_id_int = int(arquivo_id)
|
||||
except (TypeError, ValueError):
|
||||
if verbose:
|
||||
warn(f"ID inválido ({arquivo_id}) — Ignorando busca de arquivo.")
|
||||
return None
|
||||
|
||||
id6 = f"{arquivo_id_int:06d}"
|
||||
ddd = _calcular_pasta(arquivo_id_int)
|
||||
base_path = Path(pasta_origem)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# REGISTRO CIVIL
|
||||
# -----------------------------------------------------------
|
||||
if natureza in PREFIXOS_RC:
|
||||
prefixos = PREFIXOS_RC[natureza]
|
||||
pasta_ddd = base_path / ddd
|
||||
|
||||
if not _exists_safe(pasta_ddd, verbose):
|
||||
return None
|
||||
|
||||
for nome in _nomes_possiveis(prefixos, id6):
|
||||
caminho_path = pasta_ddd / nome
|
||||
|
||||
if _exists_safe(caminho_path, verbose) and can_open_file(
|
||||
caminho_path, verbose
|
||||
):
|
||||
if verbose:
|
||||
ok(
|
||||
f"[RC/{natureza.upper()}] Arquivo encontrado e acessível: {caminho_path}"
|
||||
)
|
||||
return _montar_retorno(caminho_path, "CERTIDAO")
|
||||
|
||||
if verbose:
|
||||
fail(
|
||||
f"[RC/{natureza.upper()}] Nenhum arquivo acessível para ID {arquivo_id_int}"
|
||||
)
|
||||
return None
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# PROTESTO
|
||||
# -----------------------------------------------------------
|
||||
if natureza == "protesto":
|
||||
sufixos_protesto = ("", "I")
|
||||
prefixos = ("PRO_",)
|
||||
|
||||
for subpasta in ("", "Retirados"):
|
||||
pasta = base_path / ddd if not subpasta else base_path / subpasta / ddd
|
||||
|
||||
if not _exists_safe(pasta, verbose):
|
||||
continue
|
||||
|
||||
for nome in _nomes_possiveis(prefixos, id6, sufixos_protesto):
|
||||
caminho_path = pasta / nome
|
||||
|
||||
if _exists_safe(caminho_path, verbose) and can_open_file(
|
||||
caminho_path, verbose
|
||||
):
|
||||
if verbose:
|
||||
ok(f"[PROTESTO] Arquivo encontrado e acessível: {caminho_path}")
|
||||
return _montar_retorno(caminho_path, "OUTROS")
|
||||
|
||||
if verbose:
|
||||
fail(f"[PROTESTO] Nenhum arquivo acessível para ID {arquivo_id_int}")
|
||||
return None
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# RCPJ / RTD
|
||||
# -----------------------------------------------------------
|
||||
if registro in ("RCPJ", "RTD"):
|
||||
pasta_ddd = base_path / ddd
|
||||
if not _exists_safe(pasta_ddd, verbose):
|
||||
return None
|
||||
|
||||
prefixo_base = f"{registro}_{(livro or 'A')}_"
|
||||
sufixo = (
|
||||
registro_anterior
|
||||
if (registro == "RCPJ" and registro_anterior in ("N", "S", "U"))
|
||||
else ""
|
||||
)
|
||||
|
||||
for nome in _nomes_possiveis((prefixo_base,), id6, (sufixo,)):
|
||||
caminho_path = pasta_ddd / nome
|
||||
|
||||
if _exists_safe(caminho_path, verbose) and can_open_file(
|
||||
caminho_path, verbose
|
||||
):
|
||||
tipo_doc = "ESCRITURA" if registro == "RCPJ" else "OUTROS"
|
||||
if verbose:
|
||||
ok(f"[{registro}] Arquivo encontrado e acessível: {caminho_path}")
|
||||
return _montar_retorno(caminho_path, tipo_doc)
|
||||
|
||||
if verbose:
|
||||
fail(f"[{registro}] Nenhum arquivo acessível para ID {arquivo_id_int}")
|
||||
return None
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# PADRÕES / REGISTRO IMÓVEIS
|
||||
# -----------------------------------------------------------
|
||||
tipos = list(tipos_permitidos) if tipos_permitidos else list(TIPOS_PADRAO.keys())
|
||||
|
||||
for tipo in tipos:
|
||||
info_tipo = TIPOS_PADRAO.get(tipo)
|
||||
if not info_tipo:
|
||||
continue
|
||||
|
||||
tipo_doc = info_tipo["tipo_documento"]
|
||||
|
||||
# REGISTRO DE IMÓVEIS
|
||||
if tipo == "RegistroImoveis":
|
||||
for ri_dir in (p for p in base_path.glob("R_*") if p.is_dir()):
|
||||
for nome in _nomes_possiveis((info_tipo["prefixo"],), id6):
|
||||
for achado in ri_dir.rglob(nome):
|
||||
|
||||
if _exists_safe(achado, verbose) and can_open_file(
|
||||
achado, verbose
|
||||
):
|
||||
if verbose:
|
||||
ok(f"[RI] Arquivo encontrado e acessível: {achado}")
|
||||
return _montar_retorno(achado, tipo_doc)
|
||||
|
||||
# TIPOS NORMAIS
|
||||
base_tipo = info_tipo["base"]
|
||||
pasta_tipo = base_path / base_tipo if base_tipo else base_path
|
||||
pasta_ddd = pasta_tipo / ddd
|
||||
|
||||
if not _exists_safe(pasta_ddd, verbose):
|
||||
continue
|
||||
|
||||
for nome in _nomes_possiveis((info_tipo["prefixo"],), id6):
|
||||
caminho_path = pasta_ddd / nome
|
||||
|
||||
if _exists_safe(caminho_path, verbose) and can_open_file(
|
||||
caminho_path, verbose
|
||||
):
|
||||
if verbose:
|
||||
ok(f"Arquivo encontrado e acessível ({tipo}): {caminho_path}")
|
||||
return _montar_retorno(caminho_path, tipo_doc)
|
||||
|
||||
if verbose:
|
||||
fail(f"Nenhum arquivo acessível para ID {arquivo_id_int} em {pasta_origem}")
|
||||
return None
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import json
|
||||
import traceback
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
from types import SimpleNamespace
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Literal, Union
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def save_to_disk(
|
||||
data: Union[str, bytes, dict, list, Any],
|
||||
file_path: str,
|
||||
as_json: Optional[bool] = None,
|
||||
encoding: str = "utf-8",
|
||||
indent: int = 4,
|
||||
add_timestamp: bool = False,
|
||||
mode: Literal["overwrite", "append"] = "overwrite",
|
||||
) -> dict:
|
||||
"""
|
||||
Salva dados em disco local de forma segura, com tratamento automático
|
||||
para objetos não serializáveis (ex.: SimpleNamespace, Decimal, datetime, BaseModel).
|
||||
"""
|
||||
|
||||
def convert(obj: Any):
|
||||
"""Converte qualquer objeto para formato serializável em JSON."""
|
||||
if obj is None:
|
||||
return None
|
||||
if isinstance(obj, (str, int, float, bool)):
|
||||
return obj
|
||||
if isinstance(obj, bytes):
|
||||
return obj.decode("utf-8", errors="ignore")
|
||||
if isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat()
|
||||
if isinstance(obj, SimpleNamespace):
|
||||
return {k: convert(v) for k, v in vars(obj).items()}
|
||||
if isinstance(obj, BaseModel):
|
||||
return obj.model_dump()
|
||||
if isinstance(obj, list):
|
||||
return [convert(i) for i in obj]
|
||||
if isinstance(obj, dict):
|
||||
return {k: convert(v) for k, v in obj.items()}
|
||||
# fallback final — stringifica o objeto
|
||||
return str(obj)
|
||||
|
||||
try:
|
||||
path = Path(file_path)
|
||||
|
||||
# Garante que o diretório existe
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Adiciona timestamp ao nome se necessário
|
||||
if add_timestamp:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
stem = path.stem
|
||||
suffix = path.suffix or ".json"
|
||||
path = path.with_name(f"{stem}_{timestamp}{suffix}")
|
||||
|
||||
# Detecta automaticamente se deve salvar como JSON
|
||||
if as_json is None:
|
||||
as_json = isinstance(data, (dict, list, SimpleNamespace, BaseModel))
|
||||
|
||||
write_mode = (
|
||||
"wb" if isinstance(data, bytes) else "a" if mode == "append" else "w"
|
||||
)
|
||||
|
||||
if isinstance(data, bytes):
|
||||
with open(path, write_mode) as f:
|
||||
f.write(data)
|
||||
elif as_json:
|
||||
# Converte antes de salvar
|
||||
safe_data = convert(data)
|
||||
with open(path, write_mode, encoding=encoding) as f:
|
||||
json.dump(safe_data, f, ensure_ascii=False, indent=indent)
|
||||
else:
|
||||
if not isinstance(data, str):
|
||||
data = str(data)
|
||||
with open(path, write_mode, encoding=encoding) as f:
|
||||
f.write(data)
|
||||
|
||||
file_size = path.stat().st_size if path.exists() else 0
|
||||
print(f"[OK] Arquivo salvo: {path} ({file_size} bytes)")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"path": str(path.resolve()),
|
||||
"size": file_size,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"[ERRO] Falha ao salvar arquivo '{file_path}': {e}\n{traceback.format_exc()}"
|
||||
print(error_message)
|
||||
return {
|
||||
"success": False,
|
||||
"path": str(file_path),
|
||||
"size": 0,
|
||||
"error": str(e),
|
||||
}
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
from PIL import Image, UnidentifiedImageError
|
||||
from pathlib import Path
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
from reportlab.pdfgen import canvas
|
||||
from io import BytesIO
|
||||
from pypdf.constants import UserAccessPermissions
|
||||
import base64
|
||||
from typing import Optional
|
||||
from actions.ui.ui import info, ok, fail, status, progress
|
||||
|
||||
|
||||
# Alias para manter compatibilidade e usar "error" na interface
|
||||
def error(message: str):
|
||||
fail(message)
|
||||
|
||||
|
||||
class TifToPdfConverter:
|
||||
"""
|
||||
Converte um arquivo TIFF/SPD específico em PDF protegido e confidencial.
|
||||
Totalmente em memória, com marca d'água e feedback visual via UI.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, input_file: str, output_folder: Optional[str] = "storage/output"
|
||||
):
|
||||
self.input_file = Path(input_file)
|
||||
if not self.input_file.exists():
|
||||
raise FileNotFoundError(f"Arquivo não encontrado: {self.input_file}")
|
||||
|
||||
# Pasta de saída padrão
|
||||
self.output_folder = Path(output_folder)
|
||||
self.output_folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.watermark_text = "DISPONÍVEL APENAS PARA ATIVIDADE CORRECIONAL ONLINE"
|
||||
self.confidential_metadata = {
|
||||
"/Title": "Documento Confidencial",
|
||||
"/Author": "Sistema MirrorSYNC",
|
||||
"/Subject": "Documento protegido",
|
||||
"/Keywords": "Confidencial, Corregedoria, MirrorSYNC, Orius",
|
||||
"/Producer": "Orius Tecnologia",
|
||||
"/Creator": "MirrorSYNC Automação",
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Helper 1 — Converte imagem em PDF (BytesIO)
|
||||
# =========================================================
|
||||
def _convert_image_to_pdf(self) -> Optional[BytesIO]:
|
||||
try:
|
||||
with Image.open(self.input_file) as img:
|
||||
frames = []
|
||||
|
||||
# Converte todas as páginas do TIFF para RGB
|
||||
for frame in range(getattr(img, "n_frames", 1)):
|
||||
img.seek(frame)
|
||||
frame_rgb = img.convert("RGB")
|
||||
frames.append(frame_rgb.copy())
|
||||
|
||||
if not frames:
|
||||
info(f"Nenhuma página encontrada em {self.input_file}")
|
||||
return None
|
||||
|
||||
# Buffer em memória
|
||||
buffer = BytesIO()
|
||||
|
||||
# Salva todas as páginas como um único PDF
|
||||
frames[0].save(
|
||||
buffer,
|
||||
format="PDF",
|
||||
resolution=150,
|
||||
save_all=True,
|
||||
append_images=frames[1:],
|
||||
quality=80,
|
||||
optimize=True,
|
||||
)
|
||||
buffer.seek(0)
|
||||
|
||||
ok(
|
||||
f"Imagem convertida para PDF em memória "
|
||||
f"({len(frames)} página(s))."
|
||||
)
|
||||
return buffer
|
||||
|
||||
except UnidentifiedImageError:
|
||||
error(f"Arquivo não reconhecido como imagem: {self.input_file}")
|
||||
except Exception as e:
|
||||
error(f"Erro ao converter {self.input_file}: {e}")
|
||||
return None
|
||||
|
||||
# =========================================================
|
||||
# Helper 2 — Gera PDF de marca d’água
|
||||
# =========================================================
|
||||
def _create_watermark_pdf(self, page_width: float, page_height: float) -> BytesIO:
|
||||
buffer = BytesIO()
|
||||
c = canvas.Canvas(buffer, pagesize=(page_width, page_height))
|
||||
c.saveState()
|
||||
|
||||
# Fonte adaptativa (limites 24..72)
|
||||
base_font = max(24, min(72, max(page_width, page_height) * 0.04))
|
||||
c.setFont("Helvetica-Bold", base_font)
|
||||
|
||||
# Cor vermelha com transparência (~30%)
|
||||
try:
|
||||
c.setFillColorRGB(1, 0, 0) # vermelho
|
||||
c.setFillAlpha(0.3)
|
||||
except Exception:
|
||||
# Fallback sem alpha (ainda vermelho)
|
||||
c.setFillColorRGB(1, 0, 0)
|
||||
|
||||
# Centro e rotação para deixar na diagonal
|
||||
x_center = page_width / 2.0
|
||||
y_center = page_height / 2.0
|
||||
c.translate(x_center, y_center)
|
||||
c.rotate(45) # ângulo da marca d'água (diagonal)
|
||||
|
||||
# Linhas igualmente espaçadas ao longo do eixo Y rotacionado
|
||||
span = max(page_width, page_height) * 0.4
|
||||
y_positions = [-span, -span * 0.5, 0, span * 0.5, span]
|
||||
|
||||
for y in y_positions:
|
||||
c.drawCentredString(0, y, self.watermark_text)
|
||||
|
||||
c.restoreState()
|
||||
c.save()
|
||||
buffer.seek(0)
|
||||
return buffer
|
||||
|
||||
# =========================================================
|
||||
# Helper 3 — Aplica marca d'água e confidencialidade
|
||||
# =========================================================
|
||||
def _apply_watermark_and_security(self, pdf_buffer: BytesIO) -> BytesIO:
|
||||
reader = PdfReader(pdf_buffer)
|
||||
writer = PdfWriter()
|
||||
|
||||
for page in reader.pages:
|
||||
width = float(page.mediabox.width)
|
||||
height = float(page.mediabox.height)
|
||||
watermark_stream = self._create_watermark_pdf(width, height)
|
||||
watermark_pdf = PdfReader(watermark_stream)
|
||||
watermark_page = watermark_pdf.pages[0]
|
||||
page.merge_page(watermark_page)
|
||||
writer.add_page(page)
|
||||
|
||||
# Aplica metadados confidenciais
|
||||
writer.add_metadata(self.confidential_metadata)
|
||||
|
||||
# Bloqueia ações do usuário
|
||||
block_permissions = (
|
||||
UserAccessPermissions.PRINT
|
||||
| UserAccessPermissions.MODIFY
|
||||
| UserAccessPermissions.EXTRACT
|
||||
| UserAccessPermissions.ASSEMBLE_DOC
|
||||
| UserAccessPermissions.PRINT_TO_REPRESENTATION
|
||||
)
|
||||
writer.encrypt(
|
||||
user_password="",
|
||||
owner_password="correcional",
|
||||
permissions_flag=int(block_permissions),
|
||||
)
|
||||
|
||||
output_buffer = BytesIO()
|
||||
writer.write(output_buffer)
|
||||
output_buffer.seek(0)
|
||||
ok("Marca d’água e proteção aplicadas em memória.")
|
||||
return output_buffer
|
||||
|
||||
# =========================================================
|
||||
# Helper 4 — Converte PDF final para Base64
|
||||
# =========================================================
|
||||
def _to_base64(self, pdf_buffer: BytesIO) -> str:
|
||||
b64 = base64.b64encode(pdf_buffer.getvalue()).decode("ascii")
|
||||
ok("Arquivo convertido para Base64.")
|
||||
return b64
|
||||
|
||||
# =========================================================
|
||||
# Método principal — Processamento completo
|
||||
# =========================================================
|
||||
def convert(self, return_base64: bool = False) -> Optional[str]:
|
||||
"""
|
||||
Executa a conversão e retorna:
|
||||
- Base64 (string), se return_base64=True
|
||||
- Caminho do arquivo PDF salvo em disco, se return_base64=False
|
||||
"""
|
||||
|
||||
info(f"📄 Iniciando conversão de: {self.input_file.name}")
|
||||
|
||||
# Barra de progresso única para todo o fluxo
|
||||
with progress(f"Processando {self.input_file.name}...", total=3) as step:
|
||||
|
||||
# 1) Converter imagem em PDF
|
||||
with status("Convertendo imagem para PDF..."):
|
||||
pdf_buffer = self._convert_image_to_pdf()
|
||||
step()
|
||||
|
||||
if not pdf_buffer:
|
||||
error("Falha na conversão da imagem. Processo interrompido.")
|
||||
return None
|
||||
|
||||
# 2) Aplicar marca d’água e segurança
|
||||
with status("Aplicando marca d’água e proteção..."):
|
||||
protected_buffer = self._apply_watermark_and_security(pdf_buffer)
|
||||
step()
|
||||
|
||||
# 3) Gerar Base64 ou salvar em disco
|
||||
if return_base64:
|
||||
with status("Gerando conteúdo Base64..."):
|
||||
result = self._to_base64(protected_buffer)
|
||||
ok("Conversão concluída com sucesso (Base64 gerado).")
|
||||
step()
|
||||
return result
|
||||
|
||||
with status("Salvando arquivo PDF em disco..."):
|
||||
output_path = self.output_folder / f"{self.input_file.stem}.pdf"
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(protected_buffer.getvalue())
|
||||
ok(f"Arquivo PDF salvo em: {output_path}")
|
||||
step()
|
||||
|
||||
return str(output_path)
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import re
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
|
||||
class ParseFirebirdPath:
|
||||
|
||||
DEFAULT_HOST = "localhost"
|
||||
DEFAULT_PORT = 3050
|
||||
|
||||
@classmethod
|
||||
def execute(cls, value: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
|
||||
|
||||
# -------------------------------------------
|
||||
# Se for dicionário, já padroniza
|
||||
# -------------------------------------------
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
"host": value.get("host", cls.DEFAULT_HOST),
|
||||
"port": int(value.get("port", cls.DEFAULT_PORT)),
|
||||
"path": value.get("path"),
|
||||
}
|
||||
|
||||
if not isinstance(value, str):
|
||||
raise TypeError("BaseDados deve ser string ou dict.")
|
||||
|
||||
raw = value.strip().strip('"').strip("'")
|
||||
|
||||
if not raw:
|
||||
raise ValueError("BaseDados está vazio.")
|
||||
|
||||
# -------------------------------------------
|
||||
# 1) host/port:path
|
||||
# -------------------------------------------
|
||||
m = re.match(r"^(?P<host>[^:/]+)\/(?P<port>\d+)\:(?P<path>.+)$", raw)
|
||||
if m:
|
||||
return {
|
||||
"host": m.group("host"),
|
||||
"port": int(m.group("port")),
|
||||
"path": m.group("path").strip(),
|
||||
}
|
||||
|
||||
# -------------------------------------------
|
||||
# 2) host:path (ex: 127.0.0.1:D:\Banco.fdb)
|
||||
# -------------------------------------------
|
||||
m = re.match(r"^(?P<host>[^:\/]+)\:(?P<path>[A-Za-z]:\\.+)$", raw)
|
||||
if m:
|
||||
return {
|
||||
"host": m.group("host"),
|
||||
"port": cls.DEFAULT_PORT,
|
||||
"path": m.group("path"),
|
||||
}
|
||||
|
||||
# -------------------------------------------
|
||||
# 3) Caminho local absoluto (D:\, E:\)
|
||||
# -------------------------------------------
|
||||
if re.match(r"^[A-Za-z]:\\", raw):
|
||||
return {
|
||||
"host": cls.DEFAULT_HOST,
|
||||
"port": cls.DEFAULT_PORT,
|
||||
"path": raw,
|
||||
}
|
||||
|
||||
# -------------------------------------------
|
||||
# 4) UNC path (\\servidor\pasta\arquivo)
|
||||
# -------------------------------------------
|
||||
if raw.startswith("\\\\"):
|
||||
return {
|
||||
"host": cls.DEFAULT_HOST,
|
||||
"port": cls.DEFAULT_PORT,
|
||||
"path": raw,
|
||||
}
|
||||
|
||||
# -------------------------------------------
|
||||
# 5) Alias puro — retorna padrão
|
||||
# -------------------------------------------
|
||||
return {
|
||||
"host": cls.DEFAULT_HOST,
|
||||
"port": cls.DEFAULT_PORT,
|
||||
"path": raw,
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
class HexCipher:
|
||||
"""
|
||||
Um cifrador simples e reversível baseado em:
|
||||
- XOR com chave
|
||||
- Inversão de texto
|
||||
- Representação em hexadecimal
|
||||
|
||||
Inspirado no componente original Delphi TEvCriptografa.
|
||||
"""
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Construtor
|
||||
# -------------------------------------------------------------------------
|
||||
def __init__(self, key: str = "123", ignore_fields: list[str] | None = None):
|
||||
"""Inicializa a classe com a chave fornecida."""
|
||||
self._key = key
|
||||
# nomes de campos que NÃO devem ser descriptografados (sempre minúsculos)
|
||||
self._ignore_fields = (
|
||||
{f.lower() for f in ignore_fields} if ignore_fields else set()
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Métodos públicos principais
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def encrypt(self, text: str) -> str:
|
||||
"""
|
||||
Recebe um texto puro e retorna sua versão criptografada em hexadecimal.
|
||||
"""
|
||||
binary_cipher = self._text_to_binary_cipher(text)
|
||||
hex_cipher = "".join(self._decimal_to_hex(ord(c)) for c in binary_cipher)
|
||||
return hex_cipher
|
||||
|
||||
def decrypt(self, hex_string: str) -> str:
|
||||
"""
|
||||
Recebe um texto criptografado em hexadecimal e retorna o texto puro.
|
||||
"""
|
||||
chars = []
|
||||
for i in range(0, len(hex_string), 2):
|
||||
hex_pair = hex_string[i : i + 2]
|
||||
chars.append(chr(self._hex_to_decimal(hex_pair)))
|
||||
|
||||
binary_str = "".join(chars)
|
||||
return self._binary_to_text(binary_str)
|
||||
|
||||
def decrypt_safe(self, value: str, field: str | None = None) -> str:
|
||||
"""
|
||||
Descriptografa com segurança:
|
||||
- Se o campo estiver em _ignore_fields, retorna o valor original.
|
||||
- Se o valor não parecer HEX válido, retorna o valor original.
|
||||
- Em qualquer erro na descriptografia, retorna o valor original.
|
||||
"""
|
||||
# 1) Campo explicitamente ignorado
|
||||
if field and field.lower() in self._ignore_fields:
|
||||
return value
|
||||
|
||||
# 2) Valor vazio / None
|
||||
if not value:
|
||||
return value
|
||||
|
||||
# 3) Verifica se é HEX puro (somente 0-9 A-F) e tamanho par
|
||||
import re
|
||||
|
||||
if re.fullmatch(r"[0-9A-Fa-f]+", value) is None:
|
||||
return value
|
||||
|
||||
if len(value) % 2 != 0:
|
||||
return value
|
||||
|
||||
# 4) Tenta descriptografar
|
||||
try:
|
||||
return self.decrypt(value)
|
||||
except Exception:
|
||||
return value
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Funções internas de criptografia/descriptografia
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _text_to_binary_cipher(self, text: str) -> str:
|
||||
"""
|
||||
Criptografa um texto aplicando:
|
||||
1. Inversão de caracteres.
|
||||
2. Operação XOR entre cada caractere e um valor derivado da chave.
|
||||
"""
|
||||
text = self._reverse(text)
|
||||
result = []
|
||||
|
||||
for position, char in enumerate(text, start=1):
|
||||
key_char = self._key[position % len(self._key)]
|
||||
key_value = ord(key_char) + position
|
||||
result.append(chr(ord(char) ^ key_value))
|
||||
|
||||
return "".join(result)
|
||||
|
||||
def _binary_to_text(self, cipher_text: str) -> str:
|
||||
"""
|
||||
Descriptografa um texto binário cifrado por XOR.
|
||||
O processo é simétrico: aplica o mesmo XOR e inverte novamente.
|
||||
"""
|
||||
result = []
|
||||
|
||||
for position, char in enumerate(cipher_text, start=1):
|
||||
key_char = self._key[position % len(self._key)]
|
||||
key_value = ord(key_char) + position
|
||||
result.append(chr(ord(char) ^ key_value))
|
||||
|
||||
return self._reverse("".join(result))
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Funções auxiliares
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _reverse(self, text: str) -> str:
|
||||
"""Inverte a ordem dos caracteres de uma string."""
|
||||
return text[::-1]
|
||||
|
||||
def _decimal_to_hex(self, number: int) -> str:
|
||||
"""Converte um número decimal (byte) em uma string hexadecimal de 2 dígitos."""
|
||||
return f"{number:02X}"
|
||||
|
||||
def _hex_to_decimal(self, hex_str: str) -> int:
|
||||
"""Converte uma string hexadecimal de 2 dígitos em seu valor decimal (byte)."""
|
||||
return int(hex_str, 16)
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
from datetime import datetime, timedelta
|
||||
from jose import jwt
|
||||
from pytz import timezone
|
||||
|
||||
from actions.config.config_json import ConfigJson
|
||||
|
||||
|
||||
class CreateToken:
|
||||
def __init__(self):
|
||||
# Busca as configurações da aplicação
|
||||
self.config = ConfigJson.get("app.json")
|
||||
|
||||
# Cria o timedelta com base na config
|
||||
self.access_token_expire = timedelta(
|
||||
minutes=self.config.jwt.expire.minute,
|
||||
hours=self.config.jwt.expire.hours,
|
||||
days=self.config.jwt.expire.days,
|
||||
)
|
||||
|
||||
def execute(self, tipo_token: str, data: str) -> str:
|
||||
|
||||
sp = timezone("America/Sao_Paulo")
|
||||
agora = datetime.now(tz=sp)
|
||||
expira = agora + self.access_token_expire
|
||||
|
||||
# Define os dados do token
|
||||
payload = {"type": tipo_token, "exp": expira, "iat": agora, "data": str(data)}
|
||||
|
||||
# Retorna os dados codificados
|
||||
return jwt.encode(
|
||||
payload, self.config.jwt.token, algorithm=self.config.jwt.algorithm
|
||||
)
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
|
||||
from actions.jwt.verify_token import VerifyToken # A classe que criamos anteriormente
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # Apenas requerido pelo FastAPI
|
||||
|
||||
def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
|
||||
# Ação que válida o tokne
|
||||
verify_token = VerifyToken()
|
||||
# Obtem o resultado da validação
|
||||
result = verify_token.execute(token)
|
||||
|
||||
# Verifica se a resposta é diferente de inválida
|
||||
if result['status'] != 'valid':
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=result.get('message', 'Token inválido ou expirado'),
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Retorna apenas os dados do token
|
||||
return result['payload']
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
from jose import jwt, JWTError, ExpiredSignatureError
|
||||
|
||||
from actions.config.config_json import ConfigJson
|
||||
|
||||
|
||||
class VerifyToken:
|
||||
def __init__(self):
|
||||
# Carrega configurações
|
||||
self.config = ConfigJson.get('app.json')
|
||||
|
||||
def execute(self, token: str, expected_type: str = 'access-token') -> dict:
|
||||
try:
|
||||
# Decodifica o token
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
self.config.jwt.token,
|
||||
algorithms=[self.config.jwt.algorithm]
|
||||
)
|
||||
|
||||
# Valida expiração
|
||||
exp_timestamp = payload.get("exp")
|
||||
if exp_timestamp is None:
|
||||
raise ValueError("O token não possui data de expiração.")
|
||||
|
||||
# Verifica o tipo de token
|
||||
token_type = payload.get("type")
|
||||
if token_type != expected_type:
|
||||
raise ValueError("Tipo de token inválido.")
|
||||
|
||||
# Verificação opcional: validar campo "data"
|
||||
if "data" not in payload:
|
||||
raise ValueError("Token malformado: campo 'data' ausente.")
|
||||
|
||||
return {
|
||||
"status": "valid",
|
||||
"payload": payload
|
||||
}
|
||||
|
||||
except ExpiredSignatureError:
|
||||
return {
|
||||
"status": "expired",
|
||||
"message": "O token expirou."
|
||||
}
|
||||
|
||||
except JWTError as e:
|
||||
return {
|
||||
"status": "invalid",
|
||||
"message": f"Token inválido: {str(e)}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": f"Erro na validação do token: {str(e)}"
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
|
||||
class Log:
|
||||
|
||||
def register(self, data, caminho_arquivo='storage/temp.json'):
|
||||
try:
|
||||
# Garante que a pasta existe
|
||||
os.makedirs(os.path.dirname(caminho_arquivo), exist_ok=True)
|
||||
|
||||
# Lê dados existentes (ou cria nova lista)
|
||||
if os.path.exists(caminho_arquivo):
|
||||
with open(caminho_arquivo, 'r', encoding='utf-8') as arquivo:
|
||||
try:
|
||||
dados_existentes = json.load(arquivo)
|
||||
if not isinstance(dados_existentes, list):
|
||||
dados_existentes = []
|
||||
except json.JSONDecodeError:
|
||||
dados_existentes = []
|
||||
else:
|
||||
dados_existentes = []
|
||||
|
||||
# Adiciona novo dado
|
||||
dados_existentes.append(data)
|
||||
|
||||
# Salva novamente no arquivo com indentação
|
||||
with open(caminho_arquivo, 'w', encoding='utf-8') as arquivo:
|
||||
json.dump(dados_existentes, arquivo, indent=4, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erro ao salvar o dado: {e}")
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
import re
|
||||
import unicodedata
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class FileNameGenerator:
|
||||
"""
|
||||
Gera nomes de arquivos seguros e consistentes a partir de uma string de entrada.
|
||||
- Remove acentos, espaços e caracteres inválidos
|
||||
- Pode adicionar timestamp e extensão
|
||||
- Não grava em disco, apenas retorna a string final
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _sanitize(text: str) -> str:
|
||||
"""Remove acentos, espaços e símbolos inválidos do nome do arquivo."""
|
||||
text = (
|
||||
unicodedata.normalize("NFKD", text)
|
||||
.encode("ASCII", "ignore")
|
||||
.decode("ASCII")
|
||||
)
|
||||
text = re.sub(r"[^a-zA-Z0-9_-]+", "_", text)
|
||||
text = re.sub(r"_+", "_", text).strip("_")
|
||||
return text.lower()
|
||||
|
||||
@classmethod
|
||||
def generate(
|
||||
cls,
|
||||
base_name: str,
|
||||
extension: str = ".txt",
|
||||
include_timestamp: bool = False,
|
||||
counter: Optional[int] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Retorna um nome de arquivo limpo, como string.
|
||||
|
||||
:param base_name: Texto base (ex: título, nome, descrição)
|
||||
:param extension: Extensão desejada (ex: .pdf, .json)
|
||||
:param include_timestamp: Se True, adiciona timestamp no nome
|
||||
:param counter: Número opcional para diferenciar nomes repetidos
|
||||
:return: String do nome final do arquivo
|
||||
"""
|
||||
if not base_name:
|
||||
raise ValueError("O nome base não pode estar vazio.")
|
||||
|
||||
safe_name = cls._sanitize(base_name)
|
||||
|
||||
if include_timestamp:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
safe_name = f"{safe_name}_{timestamp}"
|
||||
|
||||
if counter is not None:
|
||||
safe_name = f"{safe_name}_{counter}"
|
||||
|
||||
if not extension.startswith("."):
|
||||
extension = f".{extension}"
|
||||
|
||||
return f"{safe_name}{extension}"
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
# Importa a biblioteca nativa 'zlib' usada para descompressão de dados binários.
|
||||
import base64
|
||||
import zlib
|
||||
|
||||
# Importa a função 'rtf_to_text' da biblioteca 'striprtf',
|
||||
# responsável por converter documentos RTF em texto plano legível.
|
||||
from striprtf.striprtf import rtf_to_text
|
||||
|
||||
|
||||
# Define uma classe utilitária chamada 'String', contendo apenas métodos estáticos.
|
||||
# Essa abordagem permite o uso direto sem necessidade de instanciar a classe.
|
||||
class String:
|
||||
@staticmethod
|
||||
def decompress(vf_string):
|
||||
"""
|
||||
Descomprime e decodifica texto de origem WPTools/Firebird.
|
||||
|
||||
Finalidade:
|
||||
Converter o conteúdo de campos BLOB ou strings compactadas (como no Delphi)
|
||||
em texto legível, detectando automaticamente se o conteúdo está:
|
||||
- Compactado com zlib
|
||||
- Codificado em ISO-8859-1 (padrão ANSI)
|
||||
- Representado como bytes puros
|
||||
"""
|
||||
# Verifica se o valor recebido é nulo, vazio ou None.
|
||||
# Se for, retorna string vazia para evitar erros de processamento.
|
||||
if not vf_string:
|
||||
return ""
|
||||
|
||||
# Caso seja um objeto tipo stream (ex: campo BLOB do Firebird)
|
||||
# Campos BLOB geralmente possuem o método `.read()` para leitura de bytes.
|
||||
if hasattr(vf_string, "read"):
|
||||
vf_string = vf_string.read() # Lê o conteúdo completo do stream
|
||||
|
||||
# Garante que o valor trabalhado é uma sequência de bytes (não string)
|
||||
# Se o dado já for texto (str), converte para bytes em codificação Latin-1,
|
||||
# que é compatível com ISO-8859-1 usado por sistemas Delphi/Firebird.
|
||||
if isinstance(vf_string, str):
|
||||
vf_bytes = vf_string.encode("latin1", errors="ignore")
|
||||
else:
|
||||
vf_bytes = vf_string # Já está em bytes, então apenas reaproveita
|
||||
|
||||
# Detecta se o conteúdo foi compactado com zlib.
|
||||
# A assinatura padrão do formato zlib começa com bytes: 0x78 0x9C ou 0x78 0xDA.
|
||||
is_zlib = (
|
||||
len(vf_bytes) > 2 and vf_bytes[0] == 0x78 and vf_bytes[1] in (0x9C, 0xDA)
|
||||
)
|
||||
|
||||
# Se a detecção confirmar que o conteúdo é zlib, tenta descompactar.
|
||||
if is_zlib:
|
||||
try:
|
||||
# Descompacta os bytes e decodifica o texto usando ISO-8859-1 (ANSI),
|
||||
# que preserva corretamente acentuação e caracteres especiais.
|
||||
text = zlib.decompress(vf_bytes).decode("iso-8859-1", errors="ignore")
|
||||
return text
|
||||
except Exception:
|
||||
# Caso falhe (por dados corrompidos ou não comprimidos de fato),
|
||||
# o fluxo continua normalmente sem interromper a execução.
|
||||
pass
|
||||
|
||||
# Se não for zlib, tenta tratar o conteúdo como texto puro (não compactado)
|
||||
try:
|
||||
# Decodifica os bytes diretamente de ISO-8859-1 (padrão usado pelo Delphi)
|
||||
return vf_bytes.decode("iso-8859-1", errors="ignore")
|
||||
except Exception:
|
||||
# Como fallback, converte para string bruta para evitar falhas.
|
||||
return str(vf_string)
|
||||
|
||||
# >>> NOVO MÉTODO <<<
|
||||
@staticmethod
|
||||
def compress(text, *, encoding: str = "iso-8859-1", as_base64: bool = True):
|
||||
"""
|
||||
Comprime texto/dados com zlib.
|
||||
|
||||
Parâmetros:
|
||||
text: str | bytes | stream (com .read())
|
||||
encoding: encoding usado quando 'text' for str (padrão: ISO-8859-1)
|
||||
as_base64: se True, retorna string Base64 do conteúdo comprimido;
|
||||
caso False, retorna bytes comprimidos.
|
||||
|
||||
Retorno:
|
||||
- bytes (zlib) quando as_base64=False
|
||||
- str (Base64) quando as_base64=True
|
||||
|
||||
Observações:
|
||||
- Use o mesmo 'encoding' ao descomprimir para simetria.
|
||||
- Ideal para armazenar em BLOB ou trafegar seguro (Base64).
|
||||
"""
|
||||
if text is None or text == "":
|
||||
return "" if as_base64 else b""
|
||||
|
||||
# Se for stream (ex.: BLOB do Firebird)
|
||||
if hasattr(text, "read"):
|
||||
raw = text.read()
|
||||
else:
|
||||
raw = text
|
||||
|
||||
# Garante bytes
|
||||
if isinstance(raw, str):
|
||||
raw_bytes = raw.encode(encoding, errors="ignore")
|
||||
else:
|
||||
raw_bytes = bytes(raw)
|
||||
|
||||
# Comprime com zlib
|
||||
comp = zlib.compress(raw_bytes)
|
||||
|
||||
# Opcional: codifica em Base64 para transporte/JSON
|
||||
if as_base64:
|
||||
return base64.b64encode(comp).decode("ascii")
|
||||
|
||||
return comp
|
||||
|
||||
@staticmethod
|
||||
def to_text(raw_text: str) -> str:
|
||||
"""
|
||||
Converte o conteúdo RTF em texto simples e retorna como string.
|
||||
|
||||
Finalidade:
|
||||
- Detectar automaticamente se o conteúdo está em formato RTF.
|
||||
- Converter para texto plano usando a função 'rtf_to_text' (da striprtf).
|
||||
- Retornar uma string limpa e pronta para ser usada em APIs, logs, etc.
|
||||
"""
|
||||
# Verifica se o texto recebido está vazio ou None — retorna vazio se sim.
|
||||
if not raw_text:
|
||||
return ""
|
||||
|
||||
# Verifica se o texto começa com o cabeçalho padrão de arquivos RTF.
|
||||
# Exemplo: "{\\rtf1\\ansi..." indica conteúdo em formato RTF.
|
||||
if raw_text.strip().startswith("{\\rtf"):
|
||||
try:
|
||||
# Converte o RTF em texto simples, preservando acentuação e quebras de linha.
|
||||
text = rtf_to_text(raw_text)
|
||||
# Remove espaços em branco extras nas extremidades.
|
||||
return text.strip()
|
||||
except Exception:
|
||||
# Se ocorrer erro na conversão (ex: RTF inválido),
|
||||
# retorna o conteúdo original sem alterações.
|
||||
return raw_text
|
||||
|
||||
# Caso o texto não seja RTF, apenas remove espaços em branco extras e retorna.
|
||||
return raw_text.strip()
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# exceptions.py
|
||||
class BusinessRuleException(Exception):
|
||||
def __init__(self, message: str):
|
||||
self.message = message
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
# handlers.py
|
||||
import traceback
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
|
||||
from actions.system.exceptions import BusinessRuleException
|
||||
from actions.log.log import Log
|
||||
|
||||
|
||||
def register_exception_handlers(app):
|
||||
|
||||
def __init__ (self):
|
||||
log = Log()
|
||||
|
||||
@app.exception_handler(BusinessRuleException)
|
||||
async def business_rule_exception_handler(request: Request, exc: BusinessRuleException):
|
||||
|
||||
response = {
|
||||
"status": "422",
|
||||
"error": "Regra de negócio",
|
||||
"detail": exc.message
|
||||
}
|
||||
|
||||
# Salva o log em disco
|
||||
Log.register(response, 'storage/temp/business_rule_exception_handler.json')
|
||||
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content=response
|
||||
)
|
||||
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
|
||||
response = {
|
||||
"status": exc.status_code,
|
||||
"error": "HTTP Error",
|
||||
"detail": exc.detail
|
||||
}
|
||||
|
||||
# Salva o log em disco
|
||||
Log.register(response, 'storage/temp/http_exception_handler.json')
|
||||
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content=response
|
||||
)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
|
||||
response = {
|
||||
"status": 400,
|
||||
"error": "Erro de validação",
|
||||
"detail": exc.errors()
|
||||
}
|
||||
|
||||
# Salva o log em disco
|
||||
Log.register(response, 'storage/temp/validation_exception_handler.json')
|
||||
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content=response
|
||||
)
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def global_exception_handler(request: Request, exc: Exception):
|
||||
|
||||
response = {
|
||||
"status": 500,
|
||||
"error": "Erro Interno do Servidor",
|
||||
"type": type(exc).__name__,
|
||||
"message": str(exc),
|
||||
"trace": traceback.format_exc()
|
||||
}
|
||||
|
||||
# Salva o log em disco
|
||||
Log.register(response, 'storage/temp/validation_exception_handler.json')
|
||||
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content=response
|
||||
)
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
# ui.py
|
||||
from contextlib import asynccontextmanager
|
||||
from contextlib import contextmanager
|
||||
from typing import Iterable, Mapping
|
||||
|
||||
from rich.live import Live
|
||||
from rich.console import Console
|
||||
from rich.theme import Theme
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
from rich.progress import (
|
||||
Progress,
|
||||
SpinnerColumn,
|
||||
BarColumn,
|
||||
TextColumn,
|
||||
TimeElapsedColumn,
|
||||
TimeRemainingColumn,
|
||||
)
|
||||
from rich.markdown import Markdown
|
||||
from rich.traceback import install
|
||||
from rich.logging import RichHandler
|
||||
import logging
|
||||
import json
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Tema global (cores nomeadas para facilitar padronização)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
theme = Theme(
|
||||
{
|
||||
"accent": "bright_magenta",
|
||||
"info": "cyan",
|
||||
"success": "green",
|
||||
"warning": "yellow",
|
||||
"error": "bold red",
|
||||
"muted": "grey62",
|
||||
}
|
||||
)
|
||||
|
||||
# Console único para todo o sistema
|
||||
console = Console(theme=theme)
|
||||
install(show_locals=False) # Tracebacks bonitos
|
||||
|
||||
|
||||
# ─────────────────────────
|
||||
# BÁSICOS
|
||||
# ─────────────────────────
|
||||
def banner(titulo: str, subtitulo: str | None = None):
|
||||
text = Text(titulo, style="bold accent")
|
||||
if subtitulo:
|
||||
text.append(f"\n{subtitulo}", style="muted")
|
||||
console.print(Panel.fit(text, border_style="accent", padding=(1, 2)))
|
||||
|
||||
|
||||
def rule(texto: str = ""):
|
||||
console.rule(Text(texto, style="accent"))
|
||||
|
||||
|
||||
def info(msg: str):
|
||||
console.print(f"ℹ️ [info]{msg}[/]")
|
||||
|
||||
|
||||
def ok(msg: str):
|
||||
console.print(f"✅ [success]{msg}[/]")
|
||||
|
||||
|
||||
def warn(msg: str):
|
||||
console.print(f"⚠️ [warning]{msg}[/]")
|
||||
|
||||
|
||||
def fail(msg: str):
|
||||
console.print(f"❌ [error]{msg}[/]")
|
||||
|
||||
|
||||
def md(markdown: str):
|
||||
console.print(Markdown(markdown))
|
||||
|
||||
|
||||
def json_pretty(data):
|
||||
try:
|
||||
console.print_json(data=json.loads(data) if isinstance(data, str) else data)
|
||||
except Exception:
|
||||
console.print(data)
|
||||
|
||||
|
||||
# ─────────────────────────
|
||||
# TABELA RÁPIDA
|
||||
# ─────────────────────────
|
||||
def table(rows: Iterable[Mapping], title: str | None = None):
|
||||
rows = list(rows)
|
||||
if not rows:
|
||||
warn("Tabela vazia.")
|
||||
return
|
||||
|
||||
t = Table(title=title, title_style="accent")
|
||||
|
||||
for col in rows[0].keys():
|
||||
t.add_column(str(col), style="muted")
|
||||
|
||||
for r in rows:
|
||||
t.add_row(*[str(r[k]) for k in rows[0].keys()])
|
||||
|
||||
console.print(t)
|
||||
|
||||
|
||||
# ─────────────────────────
|
||||
# STATUS / SPINNER
|
||||
# ─────────────────────────
|
||||
@contextmanager
|
||||
def status(msg: str):
|
||||
with console.status(f"[info]{msg}[/]"):
|
||||
yield
|
||||
|
||||
|
||||
# ─────────────────────────
|
||||
# BARRA DE PROGRESSO
|
||||
# ─────────────────────────
|
||||
@contextmanager
|
||||
def progress(task_desc: str, total: int | None = None):
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[accent]{task.description}"),
|
||||
BarColumn(),
|
||||
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
||||
TimeElapsedColumn(),
|
||||
TimeRemainingColumn(),
|
||||
transient=True,
|
||||
console=console,
|
||||
) as p:
|
||||
|
||||
task_id = p.add_task(task_desc, total=total)
|
||||
yield lambda advance=1: p.update(task_id, advance=advance)
|
||||
|
||||
|
||||
# ─────────────────────────
|
||||
# LOGGING RICH
|
||||
# ─────────────────────────
|
||||
def setup_logging(level: int = logging.INFO):
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format="%(message)s",
|
||||
handlers=[RichHandler(rich_tracebacks=True, console=console)],
|
||||
)
|
||||
|
||||
|
||||
# ─────────────────────────
|
||||
# FIREBIRD / DB HELPERS
|
||||
# ─────────────────────────
|
||||
def db_info(msg: str):
|
||||
console.print(f"🗄️ [info][DB INFO][/info] {msg}")
|
||||
|
||||
|
||||
def db_ok(msg: str):
|
||||
console.print(f"🗄️ [success][DB OK][/success] {msg}")
|
||||
|
||||
|
||||
def db_fail(msg: str):
|
||||
console.print(f"🗄️ [error][DB ERROR][/error] {msg}")
|
||||
|
||||
|
||||
def db_warning(msg: str):
|
||||
console.print(f"🗄️ [warning][DB WARNING][/warning] {msg}")
|
||||
|
||||
|
||||
def db_dsn(dsn: str):
|
||||
# Mascara senha com segurança
|
||||
try:
|
||||
before_at = dsn.split("@")[0]
|
||||
user = before_at.split("//")[1].split(":")[0]
|
||||
masked = dsn.replace(user, "******")
|
||||
except Exception:
|
||||
masked = dsn # fallback
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
Text(f"[accent]DSN de conexão[/accent]\n{masked}", style="muted"),
|
||||
border_style="accent",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def progress_manager():
|
||||
|
||||
# Criar progress com o console compartilhado
|
||||
progress = Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(),
|
||||
TextColumn("{task.completed}/{task.total}"),
|
||||
TimeElapsedColumn(),
|
||||
expand=True,
|
||||
console=console, # fundamental!
|
||||
)
|
||||
|
||||
# Live controla a tela inteira
|
||||
live = Live(progress, refresh_per_second=10, console=console, transient=False)
|
||||
live.start()
|
||||
|
||||
try:
|
||||
yield progress
|
||||
finally:
|
||||
live.stop()
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
from typing import Optional
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.exc import DBAPIError, OperationalError
|
||||
from firebird.driver.types import DatabaseError as FbDatabaseError
|
||||
|
||||
from actions.config.config_ini import ConfigIni
|
||||
from actions.firebird.parse_firebird_path import ParseFirebirdPath
|
||||
from actions.hexCipher.hex_chipher import HexCipher
|
||||
from actions.ui.ui import db_fail
|
||||
|
||||
|
||||
class Firebird:
|
||||
|
||||
_engine: Optional[Engine] = None
|
||||
hex_cipher: Optional[HexCipher] = None
|
||||
|
||||
@classmethod
|
||||
def get_engine(cls) -> Engine:
|
||||
|
||||
# Cria a instância de descriptografia apenas uma vez
|
||||
if cls.hex_cipher is None:
|
||||
cls.hex_cipher = HexCipher(key="Wallace&Gromitt")
|
||||
|
||||
# Lê o arquivo INI com os parâmetros
|
||||
database = ConfigIni.read("Config.ini")
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Descriptografa o valor bruto do BaseDados ANTES do parser.
|
||||
# Ex: "127.0.0.1/3050:S:\Bases\SANTARITA.FDB"
|
||||
# ----------------------------------------------------------------------
|
||||
base_raw = cls.hex_cipher.decrypt(database["Geral"]["BaseDados"])
|
||||
|
||||
# Parser converte "host/port:path" → dict {host, port, path}
|
||||
base_info = ParseFirebirdPath.execute(base_raw)
|
||||
|
||||
# Descriptografa usuário e senha
|
||||
user = cls.hex_cipher.decrypt(database["Geral"]["Usuario"])
|
||||
passwd = cls.hex_cipher.decrypt(database["Geral"]["Senha"])
|
||||
|
||||
# Charset da conexão
|
||||
charset = "ISO8859_1"
|
||||
|
||||
# Monta o DSN final no padrão aceito pelo firebird-driver
|
||||
dsn = (
|
||||
f"firebird+firebird://{user}:{passwd}"
|
||||
f"@{base_info['host']}:{base_info['port']}/{base_info['path']}"
|
||||
)
|
||||
|
||||
# Cria a engine apenas uma vez (singleton)
|
||||
if cls._engine is None:
|
||||
try:
|
||||
cls._engine = create_engine(
|
||||
dsn,
|
||||
connect_args={"charset": charset},
|
||||
pool_pre_ping=True,
|
||||
pool_size=5,
|
||||
max_overflow=10,
|
||||
)
|
||||
|
||||
# Testa a conexão imediatamente para evitar erros silenciosos
|
||||
with cls._engine.connect() as conn:
|
||||
conn.execute(text("SELECT 1 FROM RDB$DATABASE"))
|
||||
|
||||
except (OperationalError, DBAPIError, FbDatabaseError) as e:
|
||||
db_fail("Erro ao conectar ao Firebird:")
|
||||
db_fail(str(e))
|
||||
raise
|
||||
|
||||
return cls._engine
|
||||
|
||||
@classmethod
|
||||
def dispose(cls):
|
||||
if cls._engine:
|
||||
cls._engine.dispose()
|
||||
cls._engine = None
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5 KiB |
|
|
@ -1,156 +0,0 @@
|
|||
import asyncio
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from firebird.driver import driver_config
|
||||
from actions.ui.ui import ok, rule, warn
|
||||
from packages.v1.manutencao.controller.manutencao_v_casamento_controller import (
|
||||
ManutencaoVCasamentoController,
|
||||
)
|
||||
|
||||
# ==============================================================
|
||||
# Corrige BASE_DIR (funciona no .exe, --onefile e ambiente normal)
|
||||
# ==============================================================
|
||||
|
||||
if getattr(sys, "frozen", False):
|
||||
BASE_DIR = Path(getattr(sys, "_MEIPASS", Path(sys.executable).parent))
|
||||
else:
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
|
||||
sys.path.insert(0, str(BASE_DIR))
|
||||
|
||||
|
||||
# ==============================================================
|
||||
# Prepara pasta de logs (100% seguro)
|
||||
# ==============================================================
|
||||
|
||||
|
||||
def get_log_path() -> Path:
|
||||
"""Sempre retorna o caminho correto para logs + cria pasta storage."""
|
||||
storage_dir = BASE_DIR / "storage"
|
||||
storage_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# arquivo de log diário
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
return storage_dir / f"errors_{today}.log"
|
||||
|
||||
|
||||
# ==============================================================
|
||||
# Logging robusto para qualquer exceção
|
||||
# ==============================================================
|
||||
|
||||
|
||||
def registrar_erro(e: Exception, contexto: str = ""):
|
||||
"""Grava traceback completo em storage/errors_YYYY-MM-DD.log."""
|
||||
log_path = get_log_path()
|
||||
|
||||
try:
|
||||
with open(log_path, "a", encoding="utf-8") as f:
|
||||
f.write("\n" + "=" * 80 + "\n")
|
||||
f.write(f"DATA: {datetime.now().isoformat()}\n")
|
||||
if contexto:
|
||||
f.write(f"CONTEXTO: {contexto}\n")
|
||||
f.write("ERRO:\n")
|
||||
traceback.print_exc(file=f)
|
||||
f.write("=" * 80 + "\n")
|
||||
|
||||
warn(f"⚠️ Erro registrado em {log_path}")
|
||||
|
||||
except Exception as log_error:
|
||||
print("❌ Falha ao escrever no arquivo de log:", log_error)
|
||||
print("Erro original:", e)
|
||||
input("\nPressione ENTER para sair...")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ==============================================================
|
||||
# Configuração do Firebird
|
||||
# ==============================================================
|
||||
|
||||
|
||||
def configurar_firebird():
|
||||
try:
|
||||
system = platform.system()
|
||||
|
||||
if system == "Windows":
|
||||
fb_client_local = BASE_DIR / "fbclient.dll"
|
||||
if fb_client_local.exists():
|
||||
os.add_dll_directory(str(BASE_DIR))
|
||||
driver_config.fb_client_library.value = str(fb_client_local)
|
||||
ok(f"🔗 Firebird DLL carregada: {fb_client_local}")
|
||||
else:
|
||||
raise FileNotFoundError(
|
||||
f"fbclient.dll não encontrada em {fb_client_local}"
|
||||
)
|
||||
|
||||
elif system == "Linux":
|
||||
possible_paths = [
|
||||
"/usr/lib/x86_64-linux-gnu/firebird/4.0/lib/libfbclient.so.2",
|
||||
"/usr/lib/x86_64-linux-gnu/libfbclient.so.2",
|
||||
"/usr/lib/x86_64-linux-gnu/libfbclient.so.4.0.5",
|
||||
str(BASE_DIR / "libfbclient.so"),
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
if os.path.exists(path):
|
||||
driver_config.fb_client_library.value = path
|
||||
ok(f"🔗 Firebird client carregado: {path}")
|
||||
break
|
||||
else:
|
||||
raise FileNotFoundError(
|
||||
"❌ libfbclient.so não encontrada. "
|
||||
"Instale o firebird-client no Linux."
|
||||
)
|
||||
|
||||
else:
|
||||
warn(f"⚠️ Sistema operacional não suportado: {system}")
|
||||
|
||||
except Exception as e:
|
||||
registrar_erro(e, contexto="CONFIGURAÇÃO FIREBIRD")
|
||||
raise
|
||||
|
||||
|
||||
# ==============================================================
|
||||
# Execução principal
|
||||
# ==============================================================
|
||||
|
||||
|
||||
async def main():
|
||||
manutencao_v_casamento_controller = ManutencaoVCasamentoController()
|
||||
response = manutencao_v_casamento_controller.VincularNoivosAtosAntigos()
|
||||
print(response)
|
||||
|
||||
|
||||
# ==============================================================
|
||||
# Entry point
|
||||
# ==============================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
try:
|
||||
configurar_firebird()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
input("\nErro ao configurar o Firebird. Pressione ENTER para sair...")
|
||||
sys.exit(1)
|
||||
|
||||
# Corrige event loop no Windows
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
input("\nProcesso concluído. Pressione ENTER para sair...")
|
||||
|
||||
except Exception as e:
|
||||
registrar_erro(e, contexto="EXECUÇÃO PRINCIPAL")
|
||||
traceback.print_exc()
|
||||
input("\nOcorreu um erro. Pressione ENTER para sair...")
|
||||
sys.exit(1)
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
import json
|
||||
import re
|
||||
from types import SimpleNamespace
|
||||
from actions.file.file_name_generator import FileNameGenerator
|
||||
from actions.file.json_file_saver import JsonFileSaver
|
||||
import httpx
|
||||
from actions.data.dict_to_obj import DictToObj
|
||||
from actions.ui.ui import fail, ok, rule
|
||||
|
||||
|
||||
class ApiAction:
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
self.file_name_generator = FileNameGenerator()
|
||||
|
||||
# Classe para obter as variaveis de ambiente
|
||||
mirror_sync_env = SimpleNamespace(
|
||||
url="https://mattermost.oriustecnologia.com/api/v4/",
|
||||
timeout=10,
|
||||
token="uzszwh6c7pbc8xyt1d1skope8y",
|
||||
)
|
||||
|
||||
# Obtem as variaveis de ambiente em formato de objeto
|
||||
self.mirror_sync_env = mirror_sync_env
|
||||
|
||||
pass
|
||||
|
||||
def sanitize_unicode(self, data):
|
||||
"""
|
||||
Remove caracteres inválidos (surrogates) de strings dentro de estruturas complexas.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
# Remove caracteres Unicode no intervalo D800–DFFF (não permitidos em UTF-8)
|
||||
return re.sub(r"[\ud800-\udfff]", "", data)
|
||||
elif isinstance(data, list):
|
||||
return [self.sanitize_unicode(i) for i in data]
|
||||
elif isinstance(data, dict):
|
||||
return {k: self.sanitize_unicode(v) for k, v in data.items()}
|
||||
return data
|
||||
|
||||
# Cria os Cabeçalhos de envio
|
||||
def _headers(self, data):
|
||||
|
||||
headers = {
|
||||
"Accept": getattr(data, "accept", "application/json"),
|
||||
"Content-Type": getattr(data, "content_type", "application/json"),
|
||||
}
|
||||
|
||||
token = getattr(data, "token", None)
|
||||
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
return headers
|
||||
|
||||
# Trata os dados da respsota
|
||||
async def _response(self, response: httpx.Response, data):
|
||||
|
||||
# Tenta JSON; se falhar, retorna texto bruto
|
||||
try:
|
||||
|
||||
# Gerador de arquivo JSON
|
||||
json_file_saver = JsonFileSaver()
|
||||
|
||||
# Obtem o código de requisição
|
||||
status = response.status_code
|
||||
|
||||
# Converter o Json para objeto
|
||||
response = DictToObj(response.json())
|
||||
|
||||
# Verifica o tipo de resposta
|
||||
match status:
|
||||
|
||||
case 200:
|
||||
rule("Dados enviados.:" + str(status))
|
||||
ok("Verifique o log...")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/2XX/"
|
||||
+ self.file_name_generator.generate("200")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
case 201:
|
||||
rule("Dados criados.:" + str(status))
|
||||
ok("Verifique o log...")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/2XX/"
|
||||
+ self.file_name_generator.generate("201")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
case 400:
|
||||
rule("Error.:" + str(status))
|
||||
fail("Verifique o log...")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/4XX/"
|
||||
+ self.file_name_generator.generate("400")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
case 404:
|
||||
rule("Error.:" + str(status))
|
||||
fail("Verifique o log...")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/4XX/"
|
||||
+ self.file_name_generator.generate("404")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
case 422:
|
||||
rule("Error.:" + str(status))
|
||||
fail("Verifique o log....")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/4XX/"
|
||||
+ self.file_name_generator.generate("422")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
except ValueError:
|
||||
|
||||
return DictToObj(
|
||||
{
|
||||
"status": status,
|
||||
"message": json.dumps(response, ensure_ascii=False, default=vars),
|
||||
"data": None,
|
||||
}
|
||||
)
|
||||
|
||||
# Contrói a requisição
|
||||
async def _request(self, client: httpx.AsyncClient, data):
|
||||
"""
|
||||
Constrói e envia a requisição com base no método HTTP definido em `data.method`.
|
||||
|
||||
Suporta:
|
||||
- GET: usa params (query string)
|
||||
- POST/PUT/PATCH: usa json ou form (data)
|
||||
- DELETE: suporta params opcionais
|
||||
"""
|
||||
# Obtem o verbo de requisição
|
||||
method = data.method.lower().strip()
|
||||
|
||||
# Obtem o endpoint
|
||||
url = data.endpoint.lstrip("/")
|
||||
|
||||
# Prepara argumentos de envio
|
||||
kwargs = {}
|
||||
|
||||
# Obtem o corpo da requisição
|
||||
body = getattr(data, "body", None)
|
||||
|
||||
# Corpo JSON ou formulário (para POST/PUT/PATCH)
|
||||
if body is not None:
|
||||
|
||||
# Sanitiza caracteres inválidos antes de enviar
|
||||
body = self.sanitize_unicode(body)
|
||||
|
||||
# Guarda o corpo da requisição
|
||||
kwargs["json"] = body
|
||||
|
||||
# Constrói o request (sem enviar ainda)
|
||||
request = client.build_request(method.upper(), url, **kwargs)
|
||||
|
||||
# Envia a requisição (client.send respeita timeout e intercepta tudo)
|
||||
response = await client.send(request)
|
||||
|
||||
# Se sucesso, trata normalmente
|
||||
return await self._response(response, data)
|
||||
|
||||
# Prepara os dados para envio
|
||||
async def send(self, data):
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
base_url=self.mirror_sync_env.url,
|
||||
headers=self._headers(data),
|
||||
timeout=getattr(data, "timeout", int(self.mirror_sync_env.timeout)),
|
||||
follow_redirects=True,
|
||||
) as client:
|
||||
|
||||
rule("Salvando Dados de Envio...")
|
||||
if True:
|
||||
json_file_saver = JsonFileSaver()
|
||||
json_file_saver.save(
|
||||
data,
|
||||
"storage/data/" + self.file_name_generator.generate() + ".json",
|
||||
)
|
||||
|
||||
response = await self._request(client, data)
|
||||
|
||||
# Pydantic v2 valida e ignora campos extras (ex.: userId)
|
||||
return response
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
from actions.data.obj_to_dict import ObjToDict
|
||||
from packages.v1.api.Mattermost.actions.api_action import ApiAction
|
||||
from actions.ui.ui import status
|
||||
|
||||
|
||||
class ApiSendToChannel:
|
||||
|
||||
async def execute(self, data):
|
||||
|
||||
# Converte o SimpleNamespace em um dicionário serializável
|
||||
ato_dict = ObjToDict(data)
|
||||
|
||||
# Classe de requisição
|
||||
api_action = ApiAction()
|
||||
|
||||
# Informa que esta enviandoos dados
|
||||
with status("Mattermost..."):
|
||||
|
||||
# Envia os dados para a API
|
||||
await api_action.send(
|
||||
SimpleNamespace(
|
||||
endpoint="posts",
|
||||
method="post",
|
||||
body=ato_dict,
|
||||
timeout=None,
|
||||
token="uzszwh6c7pbc8xyt1d1skope8y",
|
||||
)
|
||||
)
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
import json
|
||||
import re
|
||||
from actions.env.mirror_sync_env import MirrorSyncEnv
|
||||
from actions.file.file_name_generator import FileNameGenerator
|
||||
from actions.file.json_file_saver import JsonFileSaver
|
||||
import httpx
|
||||
from actions.data.dict_to_obj import DictToObj
|
||||
from actions.ui.ui import fail, ok, rule
|
||||
|
||||
|
||||
class ApiAction:
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
self.file_name_generator = FileNameGenerator()
|
||||
|
||||
# Classe para obter as variaveis de ambiente
|
||||
mirror_sync_env = MirrorSyncEnv()
|
||||
|
||||
# Obtem as variaveis de ambiente em formato de objeto
|
||||
self.mirror_sync_env = mirror_sync_env.as_object()
|
||||
|
||||
pass
|
||||
|
||||
def sanitize_unicode(self, data):
|
||||
"""
|
||||
Remove caracteres inválidos (surrogates) de strings dentro de estruturas complexas.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
# Remove caracteres Unicode no intervalo D800–DFFF (não permitidos em UTF-8)
|
||||
return re.sub(r"[\ud800-\udfff]", "", data)
|
||||
elif isinstance(data, list):
|
||||
return [self.sanitize_unicode(i) for i in data]
|
||||
elif isinstance(data, dict):
|
||||
return {k: self.sanitize_unicode(v) for k, v in data.items()}
|
||||
return data
|
||||
|
||||
# Cria os Cabeçalhos de envio
|
||||
def _headers(self, data):
|
||||
|
||||
headers = {
|
||||
"Accept": getattr(data, "accept", "application/json"),
|
||||
"Content-Type": getattr(data, "content_type", "application/json"),
|
||||
}
|
||||
|
||||
token = getattr(data, "token", None)
|
||||
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
return headers
|
||||
|
||||
# Trata os dados da respsota
|
||||
async def _response(self, response: httpx.Response, data):
|
||||
|
||||
# Tenta JSON; se falhar, retorna texto bruto
|
||||
try:
|
||||
|
||||
# Gerador de arquivo JSON
|
||||
json_file_saver = JsonFileSaver()
|
||||
|
||||
# Obtem o código de requisição
|
||||
status = response.status_code
|
||||
|
||||
# Converter o Json para objeto
|
||||
response = DictToObj(response.json())
|
||||
|
||||
# Verifica o tipo de resposta
|
||||
match status:
|
||||
|
||||
case 200:
|
||||
rule("Dados enviados.:" + str(status))
|
||||
ok("Verifique o log...")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/2XX/"
|
||||
+ self.file_name_generator.generate("200")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
case 201:
|
||||
rule("Dados criados.:" + str(status))
|
||||
ok("Verifique o log...")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/2XX/"
|
||||
+ self.file_name_generator.generate("201")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
case 400:
|
||||
rule("Error.:" + str(status))
|
||||
fail("Verifique o log...")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/4XX/"
|
||||
+ self.file_name_generator.generate("400")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
case 404:
|
||||
rule("Error.:" + str(status))
|
||||
fail("Verifique o log...")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/4XX/"
|
||||
+ self.file_name_generator.generate("404")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
case 422:
|
||||
rule("Error.:" + str(status))
|
||||
fail("Verifique o log....")
|
||||
json_file_saver.save(
|
||||
response,
|
||||
"storage/4XX/"
|
||||
+ self.file_name_generator.generate("422")
|
||||
+ ".json",
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
except ValueError:
|
||||
|
||||
return DictToObj(
|
||||
{
|
||||
"status": status,
|
||||
"message": json.dumps(response, ensure_ascii=False, default=vars),
|
||||
"data": None,
|
||||
}
|
||||
)
|
||||
|
||||
# Contrói a requisição
|
||||
async def _request(self, client: httpx.AsyncClient, data):
|
||||
"""
|
||||
Constrói e envia a requisição com base no método HTTP definido em `data.method`.
|
||||
|
||||
Suporta:
|
||||
- GET: usa params (query string)
|
||||
- POST/PUT/PATCH: usa json ou form (data)
|
||||
- DELETE: suporta params opcionais
|
||||
"""
|
||||
# Obtem o verbo de requisição
|
||||
method = data.method.lower().strip()
|
||||
|
||||
# Obtem o endpoint
|
||||
url = data.endpoint.lstrip("/")
|
||||
|
||||
# Prepara argumentos de envio
|
||||
kwargs = {}
|
||||
|
||||
# Obtem o corpo da requisição
|
||||
body = getattr(data, "body", None)
|
||||
|
||||
# Corpo JSON ou formulário (para POST/PUT/PATCH)
|
||||
if body is not None:
|
||||
|
||||
# Sanitiza caracteres inválidos antes de enviar
|
||||
body = self.sanitize_unicode(body)
|
||||
|
||||
# Guarda o corpo da requisição
|
||||
kwargs["json"] = body
|
||||
|
||||
# Constrói o request (sem enviar ainda)
|
||||
request = client.build_request(method.upper(), url, **kwargs)
|
||||
|
||||
# Envia a requisição (client.send respeita timeout e intercepta tudo)
|
||||
response = await client.send(request)
|
||||
|
||||
# Se sucesso, trata normalmente
|
||||
return await self._response(response, data)
|
||||
|
||||
# Prepara os dados para envio
|
||||
async def send(self, data):
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
base_url=self.mirror_sync_env.api_url,
|
||||
headers=self._headers(data),
|
||||
timeout=getattr(data, "timeout", int(self.mirror_sync_env.timeout)),
|
||||
follow_redirects=True,
|
||||
) as client:
|
||||
|
||||
rule("Salvando Dados de Envio...")
|
||||
if True:
|
||||
json_file_saver = JsonFileSaver()
|
||||
json_file_saver.save(
|
||||
data,
|
||||
"storage/data/" + self.file_name_generator.generate() + ".json",
|
||||
)
|
||||
|
||||
response = await self._request(client, data)
|
||||
|
||||
# Pydantic v2 valida e ignora campos extras (ex.: userId)
|
||||
return response
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
from ast import Dict
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class RequestData:
|
||||
base_url: str
|
||||
endpoint: str
|
||||
method: str = "get"
|
||||
accept: str = "application/json"
|
||||
content_type: str = "application/json"
|
||||
token: Optional[str] = None
|
||||
params: Optional[Dict[str, Any]] = None
|
||||
json: Optional[Dict[str, Any]] = None
|
||||
form: Optional[Dict[str, Any]] = None
|
||||
timeout: float = 10.0
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class UsuarioAuthenticateSchema(BaseModel):
|
||||
|
||||
username: str
|
||||
password: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
|
||||
from actions.env.mirror_sync_env import MirrorSyncEnv
|
||||
from packages.v1.api.Mirror.actions.api_action import ApiAction
|
||||
from actions.ui.ui import ok, rule, status
|
||||
|
||||
|
||||
class ApiAuthenticateService:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# Classe de variaveis de ambiente
|
||||
mirror_sync_env = MirrorSyncEnv()
|
||||
|
||||
# Obtem todas as variaveis que inicia com MIRROR_SYNC
|
||||
self.mirror_sync_env = mirror_sync_env.as_object()
|
||||
|
||||
async def execute(self):
|
||||
|
||||
rule("Autenticação")
|
||||
|
||||
api_action = ApiAction()
|
||||
|
||||
# Informa que esta enviandoos dados
|
||||
with status("Autenticando na plataforma..."):
|
||||
|
||||
# Realiza a autenticação
|
||||
response = await api_action.send(
|
||||
SimpleNamespace(
|
||||
endpoint="usuario/authenticate",
|
||||
method="post",
|
||||
body={
|
||||
"username": self.mirror_sync_env.username,
|
||||
"password": self.mirror_sync_env.password,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
ok("Autenticação realizada!")
|
||||
|
||||
return response
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
from actions.data.obj_to_dict import ObjToDict
|
||||
from packages.v1.api.Mirror.actions.api_action import ApiAction
|
||||
from actions.ui.ui import status
|
||||
|
||||
|
||||
class ApiSendAtoBatchService:
|
||||
|
||||
async def execute(self, data, user_authenticated):
|
||||
|
||||
# Converte o SimpleNamespace em um dicionário serializável
|
||||
ato_dict = ObjToDict(data)
|
||||
|
||||
# Classe de requisição
|
||||
api_action = ApiAction()
|
||||
|
||||
# Informa que esta enviandoos dados
|
||||
with status("Enviando dados..."):
|
||||
|
||||
# Envia os dados para a API
|
||||
await api_action.send(
|
||||
SimpleNamespace(
|
||||
endpoint="ato/batch",
|
||||
method="post",
|
||||
token=user_authenticated.token,
|
||||
body=ato_dict,
|
||||
timeout=None,
|
||||
)
|
||||
)
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
from actions.data.obj_to_dict import ObjToDict
|
||||
from packages.v1.api.Mirror.actions.api_action import ApiAction
|
||||
from actions.ui.ui import status
|
||||
|
||||
|
||||
class ApiSendAtoService:
|
||||
|
||||
async def execute(self, data, user_authenticated):
|
||||
|
||||
# Converte o SimpleNamespace em um dicionário serializável
|
||||
ato_dict = ObjToDict(data)
|
||||
|
||||
# Classe de requisição
|
||||
api_action = ApiAction()
|
||||
|
||||
# Informa que esta enviandoos dados
|
||||
with status("Enviando dados..."):
|
||||
|
||||
# Envia os dados para a API
|
||||
await api_action.send(
|
||||
SimpleNamespace(
|
||||
endpoint="ato/",
|
||||
method="post",
|
||||
token=user_authenticated.token,
|
||||
body=ato_dict,
|
||||
timeout=None,
|
||||
)
|
||||
)
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
from actions.data.obj_to_dict import ObjToDict
|
||||
from packages.v1.api.Mirror.actions.api_action import ApiAction
|
||||
from actions.ui.ui import status
|
||||
|
||||
|
||||
class ApiSendGedBatchService:
|
||||
|
||||
async def execute(self, data, user_authenticated):
|
||||
|
||||
# Converte o SimpleNamespace em um dicionário serializável
|
||||
ato_dict = ObjToDict(data)
|
||||
|
||||
# Classe de requisição
|
||||
api_action = ApiAction()
|
||||
|
||||
# Informa que esta enviandoos dados
|
||||
with status("Enviando dados..."):
|
||||
|
||||
# Envia os dados para a API
|
||||
await api_action.send(
|
||||
SimpleNamespace(
|
||||
endpoint="ato_documento/batch",
|
||||
method="post",
|
||||
token=user_authenticated.token,
|
||||
body=ato_dict,
|
||||
timeout=None,
|
||||
)
|
||||
)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
from packages.v1.manutencao.services.manutencao_vincular_noivos_atos_antigos import (
|
||||
ManutencaoVincularNoivosAtosAntigos,
|
||||
)
|
||||
|
||||
|
||||
class ManutencaoVCasamentoController:
|
||||
|
||||
def VincularNoivosAtosAntigos(self):
|
||||
|
||||
manutencao_vincular_noivos_atos_antigos = ManutencaoVincularNoivosAtosAntigos()
|
||||
|
||||
return manutencao_vincular_noivos_atos_antigos.execute()
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
from actions.ui.ui import info, ok, rule, warn
|
||||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
from packages.v1.sequencia.services.g_sequencia.generate_service import GenerateService
|
||||
from packages.v1.serventias.controllers.v_casamento_controller import (
|
||||
VCasamentoController,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_schema import (
|
||||
VPessoaSaveSchema,
|
||||
VPessoaSearchSchema,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoIndexSchema,
|
||||
VPessoaVinculoSaveSchema,
|
||||
)
|
||||
from packages.v1.serventias.services.v_pessoa.go.v_pessoa_index_service import (
|
||||
VPessoaIndexService,
|
||||
)
|
||||
from packages.v1.serventias.services.v_pessoa.go.v_pessoa_save_service import (
|
||||
VPessoaSaveService,
|
||||
)
|
||||
from packages.v1.serventias.services.v_pessoa_vinculo.go.v_pessoa_vinculo_index_service import (
|
||||
VPessoaVinculoIndexService,
|
||||
)
|
||||
from packages.v1.serventias.services.v_pessoa_vinculo.go.v_pessoa_vinculo_save_service import (
|
||||
VPessoaVInculoSaveService,
|
||||
)
|
||||
|
||||
|
||||
class ManutencaoVincularNoivosAtosAntigos:
|
||||
|
||||
def _gerar_sequencia(self, tabela):
|
||||
|
||||
# Cria o schema de sequência
|
||||
sequencia_schema = GSequenciaSchema()
|
||||
sequencia_schema.tabela = tabela
|
||||
|
||||
# Gera a sequência atualizada
|
||||
generate = GenerateService()
|
||||
|
||||
response = generate.execute(sequencia_schema)
|
||||
|
||||
return response.sequencia
|
||||
|
||||
def _cadastrar_pessoa(self, pessoa):
|
||||
|
||||
info("Cadastro da pessoa.:" + pessoa.nome)
|
||||
|
||||
pessoa.pessoa_id = self._gerar_sequencia("V_PESSOA")
|
||||
|
||||
v_pessoa = VPessoaSaveService()
|
||||
|
||||
info("Criando sequência de pessoa..: " + pessoa.nome)
|
||||
|
||||
response = v_pessoa.execute(
|
||||
VPessoaSaveSchema(
|
||||
pessoa_id=pessoa.pessoa_id,
|
||||
nome=pessoa.nome,
|
||||
cpf_cnpj=pessoa.cpf,
|
||||
nacionalidade=pessoa.nacionalidade,
|
||||
naturalidade=pessoa.naturalidade,
|
||||
data_nascimento=pessoa.data_nascimento,
|
||||
nome_mae=pessoa.nome_mae,
|
||||
nome_pai=pessoa.nome_pai,
|
||||
)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def _vincular_pessoa(self, nome, ato_antigo, sexo):
|
||||
|
||||
info("Buscando noivo(a)..: " + nome)
|
||||
|
||||
v_pessoa_search = VPessoaIndexService()
|
||||
|
||||
response_pessoa = v_pessoa_search.execute(VPessoaSearchSchema(nome=nome))
|
||||
|
||||
if not response_pessoa:
|
||||
|
||||
if sexo == "M":
|
||||
|
||||
response_pessoa = self._cadastrar_pessoa(
|
||||
SimpleNamespace(
|
||||
nome=nome,
|
||||
sexo=sexo,
|
||||
cpf=getattr(ato_antigo, "noivo_cpf", None),
|
||||
nacionalidade=getattr(ato_antigo, "noivo_nacionalidade", None),
|
||||
naturalidade=getattr(
|
||||
ato_antigo, "lv_antigo_noivo_naturalidade", None
|
||||
),
|
||||
data_nascimento=getattr(
|
||||
ato_antigo, "lv_antigo_noivo_nascimento", None
|
||||
),
|
||||
nome_mae=getattr(ato_antigo, "lv_antigo_noivo_mae", None),
|
||||
nome_pai=getattr(ato_antigo, "lv_antigo_noivo_pai", None),
|
||||
)
|
||||
)
|
||||
|
||||
elif sexo == "F":
|
||||
|
||||
response_pessoa = self._cadastrar_pessoa(
|
||||
SimpleNamespace(
|
||||
nome=nome,
|
||||
exo=sexo,
|
||||
cpf=getattr(ato_antigo, "noiva_cpf", None),
|
||||
nacionalidade=getattr(ato_antigo, "noiva_nacionalidade", None),
|
||||
naturalidade=getattr(
|
||||
ato_antigo, "lv_antigo_noiva_naturalidade", None
|
||||
),
|
||||
data_nascimento=getattr(
|
||||
ato_antigo, "lv_antigo_noiva_nascimento", None
|
||||
),
|
||||
nome_mae=getattr(ato_antigo, "lv_antigo_noiva_mae", None),
|
||||
nome_pai=getattr(ato_antigo, "lv_antigo_noiva_pai", None),
|
||||
)
|
||||
)
|
||||
|
||||
ok("Noivo(a) localizado..: " + nome)
|
||||
|
||||
info("Vinculando ao casamento..: " + nome)
|
||||
|
||||
info("Criando sequência de vinculo..: " + nome)
|
||||
|
||||
sequencia = self._gerar_sequencia("V_PESSOA_VINCULO")
|
||||
|
||||
v_pessoa_vinculo_save_service = VPessoaVInculoSaveService()
|
||||
|
||||
response = v_pessoa_vinculo_save_service.execute(
|
||||
VPessoaVinculoSaveSchema(
|
||||
pessoa_vinculo_id=sequencia,
|
||||
registro_id=ato_antigo.casamento_id,
|
||||
livro_natureza_id=4,
|
||||
nome=getattr(response_pessoa, "nome", None),
|
||||
pessoa_id=response_pessoa.pessoa_id,
|
||||
tb_estadocivil_id=getattr(response_pessoa, "tb_estadocivil_id", None),
|
||||
tb_profissao_id=getattr(response_pessoa, "tb_profissao_id", None),
|
||||
tipo_cadastro_geral=7,
|
||||
)
|
||||
)
|
||||
|
||||
if response:
|
||||
ok("Noivo(a) vinculado..: " + ato_antigo.noivo_nome_atual)
|
||||
|
||||
def execute(self):
|
||||
|
||||
v_casamento_controller = VCasamentoController()
|
||||
|
||||
response_atos_antigos = v_casamento_controller.AntigosIndex()
|
||||
|
||||
for ato_antigo in response_atos_antigos:
|
||||
|
||||
rule("Casamento.:" + str(ato_antigo.casamento_id))
|
||||
|
||||
v_pessoa_vinculo_service = VPessoaVinculoIndexService()
|
||||
|
||||
response_pessoas = v_pessoa_vinculo_service.execute(
|
||||
VPessoaVinculoIndexSchema(
|
||||
registro_id=ato_antigo.casamento_id, livro_natureza_id=4
|
||||
)
|
||||
)
|
||||
|
||||
if len(response_pessoas) == 0:
|
||||
|
||||
info("Casamento antigo sem partes vinculadas")
|
||||
|
||||
if ato_antigo.noivo_nome_atual:
|
||||
|
||||
self._vincular_pessoa(ato_antigo.noivo_nome_atual, ato_antigo, "M")
|
||||
|
||||
if ato_antigo.noivo_nome_atual:
|
||||
|
||||
self._vincular_pessoa(ato_antigo.noiva_nome_atual, ato_antigo, "F")
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
from packages.v1.parametros.repositories.g_config.g_config_show_by_nome_repository import (
|
||||
GConfigShowByNomeRepository,
|
||||
)
|
||||
from packages.v1.parametros.schemas.g_config_schema import (
|
||||
GConfigNomeSchema,
|
||||
GConfigResponseSchema,
|
||||
)
|
||||
|
||||
|
||||
class GConfigShowByNomeAction:
|
||||
|
||||
def execute(self, g_config_nome_schema: GConfigNomeSchema) -> GConfigResponseSchema:
|
||||
|
||||
# Instânciamento de repositório
|
||||
g_config_show_by_nome_repository = GConfigShowByNomeRepository()
|
||||
|
||||
# Execução do repositório
|
||||
return g_config_show_by_nome_repository.execute(g_config_nome_schema)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
from packages.v1.parametros.repositories.g_config.g_config_show_by_nome_repository import (
|
||||
GConfigShowByNomeRepository,
|
||||
)
|
||||
from packages.v1.parametros.schemas.g_config_schema import (
|
||||
GConfigNomeSchema,
|
||||
GConfigResponseSchema,
|
||||
)
|
||||
|
||||
|
||||
class GConfigShowByNomeAction:
|
||||
|
||||
def execute(self, g_config_nome_schema: GConfigNomeSchema) -> GConfigResponseSchema:
|
||||
|
||||
# Instânciamento de repositório
|
||||
g_config_show_by_nome_repository = GConfigShowByNomeRepository()
|
||||
|
||||
# Execução do repositório
|
||||
return g_config_show_by_nome_repository.execute(g_config_nome_schema)
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
from packages.v1.parametros.schemas.g_config_schema import (
|
||||
GConfigNomeSchema,
|
||||
GConfigResponseSchema,
|
||||
)
|
||||
|
||||
|
||||
class GConfigShowByNomeRepository(BaseRepositoryFirebird):
|
||||
|
||||
def execute(self, g_config_nome_schema: GConfigNomeSchema) -> GConfigResponseSchema:
|
||||
|
||||
# Montagem da consulta sql
|
||||
sql = """ SELECT
|
||||
FIRST 1 GC.*
|
||||
FROM G_CONFIG GC
|
||||
JOIN G_CONFIG_GRUPO GCG ON GC.CONFIG_GRUPO_ID = GCG.CONFIG_GRUPO_ID
|
||||
WHERE GC.NOME LIKE :nome
|
||||
AND GCG.SISTEMA_ID = :sistema_id
|
||||
"""
|
||||
|
||||
# Preenchimento dos parâmetros
|
||||
params = {
|
||||
"nome": g_config_nome_schema.nome,
|
||||
"sistema_id": g_config_nome_schema.sistema_id,
|
||||
}
|
||||
|
||||
# Execução do sql
|
||||
response = self.fetch_one(sql, params)
|
||||
|
||||
# Transforma em dict associativo
|
||||
return response
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from typing import Optional
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class GConfigSchema(BaseModel):
|
||||
config_id: Optional[float] = None
|
||||
config_grupo_id: Optional[float] = None
|
||||
config_padrao_id: Optional[float] = None
|
||||
secao: Optional[str] = None
|
||||
nome: Optional[str] = None
|
||||
valor: Optional[str] = None
|
||||
descricao: Optional[str] = None
|
||||
texto: Optional[str] = None
|
||||
terminal: Optional[str] = None
|
||||
tipo_valor: Optional[str] = None
|
||||
atualizado: Optional[str] = None
|
||||
|
||||
# substitui a antiga inner class Config
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GConfigResponseSchema(GConfigSchema):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GConfigNomeSchema(BaseModel):
|
||||
nome: str = None
|
||||
sistema_id: float = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
from packages.v1.parametros.actions.g_config.g_config_show_by_nome_action import (
|
||||
GConfigShowByNomeAction,
|
||||
)
|
||||
from packages.v1.parametros.schemas.g_config_schema import (
|
||||
GConfigNomeSchema,
|
||||
GConfigResponseSchema,
|
||||
)
|
||||
|
||||
|
||||
class GConfigShowByNomeService:
|
||||
|
||||
def execute(self, g_config_nome_schema: GConfigNomeSchema) -> GConfigResponseSchema:
|
||||
|
||||
# Instânciamento de Action
|
||||
g_config_show_by_nome_action = GConfigShowByNomeAction()
|
||||
|
||||
# Execução da Ação
|
||||
return g_config_show_by_nome_action.execute(g_config_nome_schema)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from types import SimpleNamespace
|
||||
|
||||
|
||||
class GSeloLivroCreateObjectAction:
|
||||
|
||||
@staticmethod
|
||||
def execute(data):
|
||||
"""Cria um objeto SimpleNamespace para atos vinculados."""
|
||||
return SimpleNamespace(**data)
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
from packages.v1.selos.repositories.g_selo_livro.g_selo_livro_index_repository import (
|
||||
GSeloLivroIndexRepository,
|
||||
)
|
||||
from packages.v1.selos.schemas.g_selo_livro_schema import (
|
||||
GSeloLivroIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class GSeloLivroIndexAction:
|
||||
|
||||
def execute(self, g_selo_livro_index_schema: GSeloLivroIndexSchema):
|
||||
|
||||
# Instânciamento de repositório
|
||||
g_selo_livro_index_repository = GSeloLivroIndexRepository()
|
||||
|
||||
# Retorna todos produtos
|
||||
return g_selo_livro_index_repository.execute(g_selo_livro_index_schema)
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
from packages.v1.serventias.repositories.t_ato_vinculoparte.t_ato_vinculoparte_index_repository import (
|
||||
TAatoVinculoParteIndexRepository,
|
||||
)
|
||||
from packages.v1.serventias.schemas.t_ato_vinculoparte_schema import (
|
||||
ResponseTAtoVinculoParteSchema,
|
||||
TAtoVinculoParteIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class TAtoVinculoParteIndexAction(=):
|
||||
|
||||
def execute(
|
||||
self, ato_vinculoparte_index_schema: TAtoVinculoParteIndexSchema
|
||||
) -> ResponseTAtoVinculoParteSchema:
|
||||
|
||||
# Instânciamento de repositório
|
||||
t_ato_vinculoparte_index_repository = TAatoVinculoParteIndexRepository()
|
||||
|
||||
# Retorna todos produtos
|
||||
return t_ato_vinculoparte_index_repository.execute(
|
||||
ato_vinculoparte_index_schema
|
||||
)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Importação de bibliotecas
|
||||
from packages.v1.selos.services.g_selo_livro.go.g_selo_livro_index_service import (
|
||||
GSeloLivroIndexService,
|
||||
)
|
||||
from packages.v1.selos.schemas.g_selo_livro_schema import (
|
||||
GSeloLivroIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class GSeloLivroController:
|
||||
|
||||
def index(self, g_selo_livro_index_schema: GSeloLivroIndexSchema):
|
||||
|
||||
# Importação da classe desejad
|
||||
g_selo_livro_index_service = GSeloLivroIndexService()
|
||||
|
||||
# Lista todos os produtos
|
||||
return g_selo_livro_index_service.execute(g_selo_livro_index_schema)
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
from packages.v1.serventias.schemas.t_ato_vinculoparte_schema import (
|
||||
ResponseTAtoVinculoParteSchema,
|
||||
TAtoVinculoParteIndexSchema,
|
||||
)
|
||||
from packages.v1.serventias.services.t_ato_vinculoparte.go.t_ato_vinculoparte_index_service import (
|
||||
TAtoVinculoParteIndexService,
|
||||
)
|
||||
|
||||
|
||||
class TAtoVinculoParteController:
|
||||
|
||||
def index(
|
||||
self, ato_vinculoparte_index_schema: TAtoVinculoParteIndexSchema
|
||||
) -> ResponseTAtoVinculoParteSchema:
|
||||
|
||||
# Importação da classe desejada
|
||||
t_ato_vinculoparte_index_service = TAtoVinculoParteIndexService()
|
||||
|
||||
# Executa a classe importada
|
||||
response = t_ato_vinculoparte_index_service.execute(
|
||||
ato_vinculoparte_index_schema
|
||||
)
|
||||
|
||||
return response
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
from packages.v1.selos.schemas.g_selo_livro_schema import (
|
||||
GSeloLivroIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class GSeloLivroIndexRepository(BaseRepositoryFirebird):
|
||||
"""
|
||||
Repositório para a operação de listagem de todos os registros
|
||||
na tabela t_censec_qualidade.
|
||||
"""
|
||||
|
||||
def execute(self, g_selo_livro_index_schema: GSeloLivroIndexSchema):
|
||||
"""
|
||||
Executa a consulta SQL para buscar todos os registros.
|
||||
|
||||
Returns:
|
||||
Uma lista de dicionários contendo os dados dos registros.
|
||||
"""
|
||||
# Montagem do SQL
|
||||
sql = """ SELECT
|
||||
COALESCE(NULLIF(TRIM(GU.NOME_COMPLETO), ''), 'Não informado') AS nome_serventuario_praticou_ato,
|
||||
COALESCE(GSG.NUMERO, 0) AS tipo_ato,
|
||||
COALESCE(NULLIF(TRIM(GST.NOTA_FISCAL), ''), 'Não informado') AS identificacao_pedido_cgj,
|
||||
COALESCE(NULLIF(TRIM(GSL.NUMERO_SELO), ''), 'Não informado') AS codigo_selo,
|
||||
COALESCE(NULLIF(TRIM(GSL.NUMERO_SELO), ''), 'Não informado') AS codigo_ato,
|
||||
COALESCE(NULLIF(TRIM(GSL.APRESENTANTE), ''), 'Não informado') AS nome_civil_ato,
|
||||
COALESCE(GSL.USUARIO_ID, 0) AS USUARIO_ID,
|
||||
GSL.DATA_INFORMACAO as data_solicitacao,
|
||||
COALESCE(NULLIF(TRIM(GSL.IP_MAQUINA), ''), 'Não informado') AS ip_maquina,
|
||||
COALESCE(GSL.VALOR_EMOLUMENTO, 0) AS emolumento,
|
||||
COALESCE(GSL.VALOR_TAXA_JUDICIARIA, 0) AS taxa_judiciaria,
|
||||
COALESCE(GSL.VALOR_FUNDESP, 0) AS fundos_estaduais
|
||||
FROM G_SELO_LIVRO GSL
|
||||
JOIN G_SELO_LOTE GST ON GSL.SELO_LOTE_ID = GST.SELO_LOTE_ID
|
||||
JOIN G_SELO_GRUPO GSG ON GST.SELO_GRUPO_ID = GSG.SELO_GRUPO_ID
|
||||
JOIN G_USUARIO GU ON GSL.USUARIO_ID = GU.USUARIO_ID
|
||||
WHERE GSL.CAMPO_ID = :campo_id
|
||||
AND GSL.TABELA LIKE :tabela
|
||||
ORDER BY GSG.NUMERO ASC; """
|
||||
|
||||
# Preenchimento dos parâmetros
|
||||
params = {
|
||||
"campo_id": g_selo_livro_index_schema.campo_id,
|
||||
"tabela": g_selo_livro_index_schema.tabela,
|
||||
}
|
||||
|
||||
# Execução do sql
|
||||
response = self.fetch_all(sql, params)
|
||||
|
||||
# Retorna os dados localizados
|
||||
return response
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
from packages.v1.serventias.schemas.t_ato_vinculoparte_schema import (
|
||||
ResponseTAtoVinculoParteSchema,
|
||||
TAtoVinculoParteIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class TAatoVinculoParteIndexRepository(BaseRepositoryFirebird):
|
||||
"""
|
||||
Repositório para a operação de listagem de todos os registros
|
||||
na tabela t_censec_qualidade.
|
||||
"""
|
||||
|
||||
def execute(
|
||||
self, ato_vinculoparte_index_schema: TAtoVinculoParteIndexSchema
|
||||
) -> ResponseTAtoVinculoParteSchema:
|
||||
"""
|
||||
Executa a consulta SQL para buscar todos os registros.
|
||||
|
||||
Returns:
|
||||
Uma lista de dicionários contendo os dados dos registros.
|
||||
"""
|
||||
# Montagem do SQL
|
||||
sql = """ SELECT
|
||||
TAV.PESSOA_NOME,
|
||||
TAV.PESSOA_CPF
|
||||
FROM T_ATO_VINCULOPARTE TAV
|
||||
WHERE TAV.ATO_ID = :ato_id """
|
||||
|
||||
# Preenchimento dos parâmetros
|
||||
params = {"ato_id": ato_vinculoparte_index_schema.ato_id}
|
||||
|
||||
# Execução do sql
|
||||
response = self.fetch_all(sql, params)
|
||||
|
||||
# Retorna os dados localizados
|
||||
return response
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class GSeloLivroIndexSchema(BaseModel):
|
||||
campo_id: int
|
||||
tabela: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class GSeloLivro(BaseModel):
|
||||
selo_livro_id: int
|
||||
numero_selo: str
|
||||
apresentante: str
|
||||
usuario_id: str
|
||||
data_informacao: str
|
||||
ip_maquina: str
|
||||
valor_emolumento: str
|
||||
valor_taxa_judiciaria: str
|
||||
valor_fundesp: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ResponseGSeloLivro(BaseModel):
|
||||
data: list[GSeloLivro]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
class TAtoVinculoParteIndexSchema(BaseModel):
|
||||
ato_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class TAtoVinculoParteSchema(BaseModel):
|
||||
pessoa_nome: str
|
||||
pessoa_cpf: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class ResponseTAtoVinculoParteSchema(BaseModel):
|
||||
data: list[TAtoVinculoParteSchema]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
from packages.v1.selos.actions.g_selo_livro.g_selo_livro_index_action import (
|
||||
GSeloLivroIndexAction,
|
||||
)
|
||||
from packages.v1.selos.schemas.g_selo_livro_schema import (
|
||||
GSeloLivroIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class GSeloLivroIndexService:
|
||||
"""
|
||||
Serviço responsável por encapsular a lógica de negócio para a operação
|
||||
de listagem de registros na tabela G_GRAMATICA.
|
||||
"""
|
||||
|
||||
def execute(self, g_selo_livro_index_schema: GSeloLivroIndexSchema):
|
||||
"""
|
||||
Executa a operação de busca de todos os registros no banco de dados.
|
||||
|
||||
Args:
|
||||
g_cartorio_index_schema (GCartorioIndexSchema):
|
||||
Esquema que pode conter filtros ou parâmetros de busca.
|
||||
|
||||
Returns:
|
||||
A lista de registros encontrados.
|
||||
"""
|
||||
# ----------------------------------------------------
|
||||
# Instanciamento da ação
|
||||
# ----------------------------------------------------
|
||||
g_selo_livro_index_action = GSeloLivroIndexAction()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução da ação
|
||||
# ----------------------------------------------------
|
||||
data = g_selo_livro_index_action.execute(g_selo_livro_index_schema)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Retorno da informação
|
||||
# ----------------------------------------------------
|
||||
return data
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
from packages.v1.serventias.actions.t_ato_vinculoparte.t_ato_vinculoparte_index_action import (
|
||||
TAtoVinculoParteIndexAction,
|
||||
)
|
||||
from packages.v1.serventias.schemas.t_ato_vinculoparte_schema import (
|
||||
ResponseTAtoVinculoParteSchema,
|
||||
TAtoVinculoParteIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class TAtoVinculoParteIndexService:
|
||||
"""
|
||||
Serviço responsável por encapsular a lógica de negócio para a operação
|
||||
de listagem de registros na tabela G_GRAMATICA.
|
||||
"""
|
||||
|
||||
def execute(
|
||||
self, ato_vinculoparte_index_schema: TAtoVinculoParteIndexSchema
|
||||
) -> ResponseTAtoVinculoParteSchema:
|
||||
"""
|
||||
Executa a operação de busca de todos os registros no banco de dados.
|
||||
|
||||
Args:
|
||||
g_cartorio_index_schema (GCartorioIndexSchema):
|
||||
Esquema que pode conter filtros ou parâmetros de busca.
|
||||
|
||||
Returns:
|
||||
A lista de registros encontrados.
|
||||
"""
|
||||
# ----------------------------------------------------
|
||||
# Instanciamento da ação
|
||||
# ----------------------------------------------------
|
||||
t_ato_vinculoparte_action = TAtoVinculoParteIndexAction()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução da ação
|
||||
# ----------------------------------------------------
|
||||
data = t_ato_vinculoparte_action.execute(ato_vinculoparte_index_schema)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Retorno da informação
|
||||
# ----------------------------------------------------
|
||||
return data
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
from packages.v1.sequencia.repositories.g_sequencia.checkout import Checkout
|
||||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
|
||||
|
||||
class CheckoutAction:
|
||||
|
||||
def execute(self, sequencia_schema: GSequenciaSchema):
|
||||
|
||||
# Instânciamento de repositório
|
||||
checkout = Checkout()
|
||||
|
||||
# Execução do repositório
|
||||
return checkout.execute(sequencia_schema)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
from packages.v1.sequencia.repositories.g_sequencia.get import Get
|
||||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
|
||||
|
||||
class GetAction:
|
||||
|
||||
def execute(self, sequencia_schema: GSequenciaSchema):
|
||||
|
||||
# Instânciamento de repositório
|
||||
get = Get()
|
||||
|
||||
# Execução do repositório
|
||||
return get.execute(sequencia_schema)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
from packages.v1.sequencia.repositories.g_sequencia.save import Save
|
||||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
|
||||
|
||||
class SaveAction:
|
||||
|
||||
def execute(self, sequencia_schema: GSequenciaSchema):
|
||||
|
||||
# Instânciamento de repositório
|
||||
save = Save()
|
||||
|
||||
# Execução do repositório
|
||||
return save.execute(sequencia_schema)
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
|
||||
class Checkout(BaseRepositoryFirebird):
|
||||
|
||||
def execute(self, sequencia_schema: GSequenciaSchema):
|
||||
|
||||
# 1) Descobre o nome da PK a partir dos metadados
|
||||
sql = """
|
||||
SELECT
|
||||
sg.RDB$FIELD_NAME AS primary_key
|
||||
FROM RDB$RELATION_CONSTRAINTS rc
|
||||
JOIN RDB$INDEX_SEGMENTS sg
|
||||
ON rc.RDB$INDEX_NAME = sg.RDB$INDEX_NAME
|
||||
WHERE rc.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||
AND rc.RDB$RELATION_NAME = UPPER(:tabela)
|
||||
ORDER BY sg.RDB$FIELD_POSITION
|
||||
"""
|
||||
params = {"tabela": sequencia_schema.tabela}
|
||||
pk_result = self.fetch_one(sql, params)
|
||||
|
||||
if not pk_result:
|
||||
raise Exception(
|
||||
f"Tabela {sequencia_schema.tabela} não possui chave primária"
|
||||
)
|
||||
|
||||
# CORREÇÃO AQUI
|
||||
pk_field = pk_result.primary_key.strip()
|
||||
|
||||
# 2) Monta dinamicamente a query para buscar o último ID
|
||||
sql = f"SELECT MAX({pk_field}) AS last_id FROM {sequencia_schema.tabela}"
|
||||
last_id_row = self.fetch_one(sql)
|
||||
|
||||
# CORREÇÃO: SimpleNamespace → acessar como atributo
|
||||
last_id = last_id_row.last_id if last_id_row else None
|
||||
if last_id is None:
|
||||
last_id = 0 # tabela vazia
|
||||
|
||||
# 3) Verifica se a tabela foi cadastrada no G_SEQUENCIA
|
||||
sql = """
|
||||
SELECT *
|
||||
FROM G_SEQUENCIA
|
||||
WHERE TABELA = :tabela
|
||||
"""
|
||||
params = {"tabela": sequencia_schema.tabela}
|
||||
g_seq = self.fetch_one(sql, params)
|
||||
|
||||
# 4) Se não houver registro no g_sequencia, cadastra um novo
|
||||
if not g_seq:
|
||||
sql = """
|
||||
INSERT INTO G_SEQUENCIA (TABELA, SEQUENCIA)
|
||||
VALUES (:tabela, :sequencia)
|
||||
"""
|
||||
params = {"tabela": sequencia_schema.tabela, "sequencia": last_id}
|
||||
self.run(sql, params)
|
||||
return last_id
|
||||
|
||||
# 5) Se a sequência estiver divergente
|
||||
if g_seq.sequencia != last_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=(
|
||||
f"A sequência atual ({g_seq.sequencia}) está divergente "
|
||||
f"do último ID da tabela ({last_id})"
|
||||
),
|
||||
)
|
||||
|
||||
# Tudo ok → retorna o last_id
|
||||
return last_id
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
|
||||
|
||||
class Get(BaseRepositoryFirebird):
|
||||
|
||||
def execute(self, sequencia_schema: GSequenciaSchema):
|
||||
|
||||
# Montagem da consulta sql
|
||||
sql = """ SELECT FIRST 1 * FROM G_SEQUENCIA gs WHERE gs.TABELA LIKE :tabela """
|
||||
|
||||
# Preenchimento dos parâmetros
|
||||
params = {"tabela": sequencia_schema.tabela}
|
||||
|
||||
# Execução do sql
|
||||
response = self.fetch_one(sql, params)
|
||||
|
||||
# Transforma em dict associativo
|
||||
return response
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
|
||||
|
||||
class Save(BaseRepositoryFirebird):
|
||||
|
||||
def execute(self, sequencia_schema: GSequenciaSchema):
|
||||
|
||||
# Construção do sql
|
||||
sql = """ UPDATE G_SEQUENCIA
|
||||
SET SEQUENCIA = :sequencia
|
||||
WHERE TABELA LIKE :tabela
|
||||
RETURNING TABELA, SEQUENCIA """
|
||||
|
||||
# Preenchimento de parâmetros
|
||||
params = {
|
||||
"sequencia": sequencia_schema.sequencia,
|
||||
"tabela": sequencia_schema.tabela,
|
||||
}
|
||||
|
||||
# Execução do sql
|
||||
response = self.run_and_return(sql, params)
|
||||
|
||||
# Retorna como verdadeiro se for salvo com sucesso
|
||||
return response
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class GSequenciaSchema(BaseModel):
|
||||
tabela: Optional[str] = None
|
||||
sequencia: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
from packages.v1.sequencia.actions.g_sequencia.checkout_action import CheckoutAction
|
||||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
|
||||
class GenerateService:
|
||||
|
||||
def execute(self, sequencia_schema: GSequenciaSchema):
|
||||
|
||||
# Instânciamento de Action
|
||||
checkoutAction = CheckoutAction()
|
||||
|
||||
# Atualiza a sequência atual
|
||||
data = checkoutAction.execute(sequencia_schema)
|
||||
|
||||
# Verifica se foi localizado o registro
|
||||
if not data:
|
||||
# Retorna uma exceção
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Não foi possível localizar a tabela para verificação de sequência",
|
||||
)
|
||||
|
||||
# Retorna a informação localizada
|
||||
return data
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
from packages.v1.sequencia.actions.g_sequencia.get_action import GetAction
|
||||
from packages.v1.sequencia.actions.g_sequencia.save_action import SaveAction
|
||||
from packages.v1.sequencia.actions.g_sequencia.checkout_action import CheckoutAction
|
||||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
|
||||
|
||||
class GenerateService:
|
||||
|
||||
def execute(self, sequencia_schema: GSequenciaSchema):
|
||||
|
||||
# Instânciamento de Action
|
||||
getAction = GetAction()
|
||||
saveAction = SaveAction()
|
||||
checkoutAction = CheckoutAction()
|
||||
|
||||
# # Verifico se a tabela existe no G_SEQUENCIA e se a sequência está correta
|
||||
# checkoutAction.execute(sequencia_schema)
|
||||
|
||||
# Busco a sequência atual
|
||||
sequencia_result = getAction.execute(sequencia_schema)
|
||||
|
||||
# Incrementa a sequência atual
|
||||
sequencia_schema.sequencia = sequencia_result.sequencia + 1
|
||||
|
||||
# Atualiza a sequência atual
|
||||
return saveAction.execute(sequencia_schema)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
from packages.v1.sequencia.actions.g_sequencia.save_action import \
|
||||
SaveAction
|
||||
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
|
||||
|
||||
|
||||
class SaveService:
|
||||
|
||||
def execute(self, sequencia_schema : GSequenciaSchema):
|
||||
|
||||
# Instânciamento de Action
|
||||
saveAction = SaveAction()
|
||||
|
||||
# Execução da Ação
|
||||
return saveAction.execute(sequencia_schema)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
from packages.v1.serventias.repositories.v_casamento.v_casamento_antigo_index_repository import (
|
||||
VCasamentoAntigoIndexRepository,
|
||||
)
|
||||
|
||||
|
||||
class VCasamentoAntigosIndexAction:
|
||||
|
||||
def execute(self):
|
||||
|
||||
# Instânciamento de repositório
|
||||
v_casamento_index_repository = VCasamentoAntigoIndexRepository()
|
||||
|
||||
# Retorna todos produtos
|
||||
return v_casamento_index_repository.execute()
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
from packages.v1.serventias.repositories.v_pessoa.v_pessoa_index_repository import (
|
||||
VPessoaIndexRepository,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_schema import (
|
||||
VPessoaSaveSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaIndexAction:
|
||||
|
||||
def execute(self, v_pessoa_search: VPessoaSaveSchema):
|
||||
|
||||
# Instânciamento de repositório
|
||||
v_pessoa_save = VPessoaIndexRepository()
|
||||
|
||||
# Retorna todos produtos
|
||||
return v_pessoa_save.execute(v_pessoa_search)
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
from packages.v1.serventias.repositories.v_pessoa.v_pessoa_save_repository import (
|
||||
VPessoaSaveRepository,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_schema import (
|
||||
VPessoaSaveSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaSaveAction:
|
||||
|
||||
def execute(self, v_pessoa_save_schema: VPessoaSaveSchema):
|
||||
|
||||
# Instânciamento de repositório
|
||||
v_pessoa_save = VPessoaSaveRepository()
|
||||
|
||||
# Retorna todos produtos
|
||||
return v_pessoa_save.execute(v_pessoa_save_schema)
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
from packages.v1.serventias.repositories.v_pessoa_vinculo.v_pessoa_vinculo_index_repository import (
|
||||
VPessoaVinculoIndexRepository,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaVinculoIndexAction:
|
||||
|
||||
def execute(self, v_pessoa_vinculo_index_schema: VPessoaVinculoIndexSchema):
|
||||
|
||||
# Instânciamento de repositório
|
||||
v_pessoa_vinculo_index_repository = VPessoaVinculoIndexRepository()
|
||||
|
||||
# Retorna todos produtos
|
||||
return v_pessoa_vinculo_index_repository.execute(v_pessoa_vinculo_index_schema)
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
from packages.v1.serventias.repositories.v_pessoa_vinculo.v_pessoa_vinculo_save_repository import (
|
||||
VPessoaVinculoSaveRepository,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoSaveSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaVinculoSaveAction:
|
||||
|
||||
def execute(self, v_pessoa_vinculo_save_schema: VPessoaVinculoSaveSchema):
|
||||
|
||||
# Instânciamento de repositório
|
||||
v_pessoa_vinculo_save_repository = VPessoaVinculoSaveRepository()
|
||||
|
||||
# Retorna todos produtos
|
||||
return v_pessoa_vinculo_save_repository.execute(v_pessoa_vinculo_save_schema)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
from packages.v1.serventias.services.v_casamento.go.v_casamento_antigo_index_service import (
|
||||
VCasamentosAntigosIndexService,
|
||||
)
|
||||
|
||||
|
||||
class VCasamentoController:
|
||||
|
||||
def AntigosIndex(self):
|
||||
|
||||
# Importação da classe desejad
|
||||
antigos_index = VCasamentosAntigosIndexService()
|
||||
|
||||
# Lista todos os produtos
|
||||
return antigos_index.execute()
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Importação de bibliotecas
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoIndexSchema,
|
||||
)
|
||||
from packages.v1.serventias.services.v_pessoa_vinculo.go.v_pessoa_vinculo_index_service import (
|
||||
VPessoaVinculoIndexService,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaVinculoController:
|
||||
|
||||
def index(self, v_pessoa_vinculo_index_schema: VPessoaVinculoIndexSchema):
|
||||
|
||||
# Importação da classe desejad
|
||||
v_pessoa_vinculo_index_service = VPessoaVinculoIndexService()
|
||||
|
||||
# Intânciamento da classe service
|
||||
self.v_pessoa_vinculo_index_service = v_pessoa_vinculo_index_service
|
||||
|
||||
# Lista todos os produtos
|
||||
return self.v_pessoa_vinculo_index_service.execute(
|
||||
v_pessoa_vinculo_index_schema
|
||||
)
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import sys
|
||||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
|
||||
|
||||
class VCasamentoAntigoIndexRepository(BaseRepositoryFirebird):
|
||||
"""
|
||||
Repositório para a operação de listagem de todos os registros
|
||||
na tabela t_censec_qualidade.
|
||||
"""
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Executa a consulta SQL para buscar todos os registros.
|
||||
|
||||
Returns:
|
||||
Uma lista de dicionários contendo os dados dos registros.
|
||||
"""
|
||||
# Montagem do SQL
|
||||
sql = """ SELECT
|
||||
VCA.CASAMENTO_ID,
|
||||
VCA.NOIVO_CPF,
|
||||
VCA.NOIVO_NOME_ATUAL,
|
||||
VCA.NOIVO_NACIONALIDADE,
|
||||
VCA.NOIVA_CPF,
|
||||
VCA.NOIVA_NOME_ATUAL,
|
||||
VCA.NOIVA_NACIONALIDADE,
|
||||
VCA.LV_ANTIGO_NOIVO_NASCIMENTO,
|
||||
VCA.LV_ANTIGO_NOIVO_MAE,
|
||||
VCA.LV_ANTIGO_NOIVO_PAI,
|
||||
VCA.LV_ANTIGO_NOIVA_NASCIMENTO,
|
||||
VCA.LV_ANTIGO_NOIVA_PAI,
|
||||
VCA.LV_ANTIGO_NOIVA_MAE,
|
||||
VCA.LV_ANTIGO_NOIVO_NATURALIDADE,
|
||||
VCA.LV_ANTIGO_NOIVA_NATURALIDADE
|
||||
FROM V_CASAMENTO VCA
|
||||
WHERE VCA.LV_ANTIGO_TEXTO_NOIVO IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_TEXTO_NOIVA IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_NOIVO_NASCIMENTO IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_NOIVO_MAE IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_NOIVO_PAI IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_NOIVA_NASCIMENTO IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_NOIVA_PAI IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_NOIVA_MAE IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_NOIVO_NATURALIDADE IS NOT NULL
|
||||
OR VCA.LV_ANTIGO_NOIVA_NATURALIDADE IS NOT NULL
|
||||
ORDER BY CASAMENTO_ID ASC"""
|
||||
|
||||
# Execução do sql
|
||||
response = self.fetch_all(sql)
|
||||
|
||||
# Retorna os dados localizados
|
||||
return response
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
from packages.v1.serventias.schemas.v_pessoa_schema import VPessoaSearchSchema
|
||||
|
||||
|
||||
class VPessoaIndexRepository(BaseRepositoryFirebird):
|
||||
"""
|
||||
Repositório para a operação de listagem de todos os registros
|
||||
na tabela t_censec_qualidade.
|
||||
"""
|
||||
|
||||
def execute(self, v_pessoa_search: VPessoaSearchSchema):
|
||||
"""
|
||||
Executa a consulta SQL para buscar todos os registros.
|
||||
|
||||
Returns:
|
||||
Uma lista de dicionários contendo os dados dos registros.
|
||||
"""
|
||||
# Montagem do SQL
|
||||
sql = """ SELECT FIRST 1 * FROM V_PESSOA WHERE NOME LIKE :nome"""
|
||||
|
||||
params = {
|
||||
"nome": v_pessoa_search.nome,
|
||||
}
|
||||
|
||||
# Execução do sql
|
||||
response = self.fetch_one(sql, params)
|
||||
|
||||
# Retorna os dados localizados
|
||||
return response
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
from actions.data.generate_insert_sql import generate_insert_sql
|
||||
from packages.v1.serventias.schemas.v_pessoa_schema import VPessoaSaveSchema
|
||||
|
||||
|
||||
class VPessoaSaveRepository(BaseRepositoryFirebird):
|
||||
"""
|
||||
Repositório para a operação de listagem de todos os registros
|
||||
na tabela t_censec_qualidade.
|
||||
"""
|
||||
|
||||
def execute(self, v_pessoa_save_schema: VPessoaSaveSchema):
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Preenchimento dos parâmetros
|
||||
# ----------------------------------------------------
|
||||
params = v_pessoa_save_schema.model_dump(exclude_unset=True)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Montagem do SQL dinâmico
|
||||
# ----------------------------------------------------
|
||||
sql = generate_insert_sql("V_PESSOA", params)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução do SQL e retorno do registro
|
||||
# ----------------------------------------------------
|
||||
return self.run_and_return(sql, params)
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaVinculoIndexRepository(BaseRepositoryFirebird):
|
||||
"""
|
||||
Repositório para a operação de listagem de todos os registros
|
||||
na tabela t_censec_qualidade.
|
||||
"""
|
||||
|
||||
def execute(self, v_pessoa_vinculo_index_schema: VPessoaVinculoIndexSchema):
|
||||
"""
|
||||
Executa a consulta SQL para buscar todos os registros.
|
||||
|
||||
Returns:
|
||||
Uma lista de dicionários contendo os dados dos registros.
|
||||
"""
|
||||
# Montagem do SQL
|
||||
sql = """
|
||||
SELECT
|
||||
VPV.PESSOA_VINCULO_ID
|
||||
FROM V_PESSOA_VINCULO VPV
|
||||
WHERE VPV.REGISTRO_ID = :registro_id
|
||||
AND VPV.LIVRO_NATUREZA_ID = :livro_natureza_id
|
||||
"""
|
||||
|
||||
params = {
|
||||
"registro_id": v_pessoa_vinculo_index_schema.registro_id,
|
||||
"livro_natureza_id": v_pessoa_vinculo_index_schema.livro_natureza_id,
|
||||
}
|
||||
|
||||
# Execução do sql
|
||||
response = self.fetch_all(sql, params)
|
||||
|
||||
# Retorna os dados localizados
|
||||
return response
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
from abstracts.repository_firebird import BaseRepositoryFirebird
|
||||
from actions.data.generate_insert_sql import generate_insert_sql
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoIndexSchema,
|
||||
VPessoaVinculoSaveSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaVinculoSaveRepository(BaseRepositoryFirebird):
|
||||
"""
|
||||
Repositório para a operação de listagem de todos os registros
|
||||
na tabela t_censec_qualidade.
|
||||
"""
|
||||
|
||||
def execute(self, v_pessoa_vinculo_index_schema: VPessoaVinculoSaveSchema):
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Preenchimento dos parâmetros
|
||||
# ----------------------------------------------------
|
||||
params = v_pessoa_vinculo_index_schema.model_dump(exclude_unset=True)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Montagem do SQL dinâmico
|
||||
# ----------------------------------------------------
|
||||
sql = generate_insert_sql("V_PESSOA_VINCULO", params)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução do SQL e retorno do registro
|
||||
# ----------------------------------------------------
|
||||
return self.run_and_return(sql, params)
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class VCasamentoIndex(BaseModel):
|
||||
casamento_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class VCasamento(BaseModel):
|
||||
casamento_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ResponseVCasamento(BaseModel):
|
||||
data: list[VCasamento]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
from datetime import date
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class VPessoaSearchSchema(BaseModel):
|
||||
|
||||
nome: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class VPessoaSaveSchema(BaseModel):
|
||||
pessoa_id: int | None = None # PK opcional (create/update)
|
||||
|
||||
nome: str
|
||||
sexo: str | None = None
|
||||
data_nascimento: date | None = None
|
||||
|
||||
nacionalidade: str | None = None
|
||||
naturalidade: str | None = None
|
||||
cpf_cnpj: str | None = None
|
||||
|
||||
tb_profissao_id: int | None = None
|
||||
tb_estadocivil_id: int | None = None
|
||||
tb_documentotipo_id: int | None = None
|
||||
|
||||
documento: str | None = None
|
||||
endereco: str | None = None
|
||||
numero_endereco: str | None = None
|
||||
bairro: str | None = None
|
||||
cidade: str | None = None
|
||||
cep: str | None = None
|
||||
uf: str | None = None
|
||||
|
||||
telefone: str | None = None
|
||||
ddd: str | None = None
|
||||
email: str | None = None
|
||||
|
||||
pessoa_tipo: int | None = None
|
||||
cpf_terceiro: str | None = None
|
||||
|
||||
nome_pai: str | None = None
|
||||
nome_mae: str | None = None
|
||||
nome_especial_pai: str | None = None
|
||||
nome_especial_mae: str | None = None
|
||||
|
||||
tempo_residencia: int | None = None
|
||||
|
||||
naturalidade_cidade_id: int | None = None
|
||||
uf_naturalidade: str | None = None
|
||||
pais_nascimento: str | None = None
|
||||
cidade_nat_id: int | None = None
|
||||
cidade_res_id: int | None = None
|
||||
|
||||
data_falecimento: date | None = None
|
||||
pai_falecido: bool | None = None
|
||||
mae_falecida: bool | None = None
|
||||
|
||||
data_emissao_doc: date | None = None
|
||||
uf_emissor_doc: str | None = None
|
||||
serie_doc: str | None = None
|
||||
orgao_emissor_id: int | None = None
|
||||
|
||||
texto_irmaos_gemeos: str | None = None
|
||||
grupo_sanguineo: str | None = None
|
||||
data_validade: date | None = None
|
||||
|
||||
zona_secao: str | None = None
|
||||
cidade_emissao_id: int | None = None
|
||||
|
||||
cert_cartorio_id: int | None = None
|
||||
cert_cidade_id: int | None = None
|
||||
cert_folha: str | None = None
|
||||
cert_livro: str | None = None
|
||||
cert_termo: str | None = None
|
||||
cert_uf: str | None = None
|
||||
cert_data: date | None = None
|
||||
|
||||
pessoa_conjuge_cpf: str | None = None
|
||||
pessoa_conjuge_id: int | None = None
|
||||
pessoa_conjuge_nome: str | None = None
|
||||
|
||||
tabela_origem: str | None = None
|
||||
chave_pessoa_imp: str | None = None
|
||||
ignorar_documentos: bool | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
from datetime import date
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class VPessoaVinculoIndexSchema(BaseModel):
|
||||
registro_id: int
|
||||
livro_natureza_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class VPessoaVinculo(BaseModel):
|
||||
pessoa_vinculo_id: int
|
||||
livro_natureza_id: int
|
||||
registro_id: str
|
||||
nome: str
|
||||
cpf_cnpj: str
|
||||
telefone: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ResponseVPessoaVinculo(BaseModel):
|
||||
data: list[VPessoaVinculo]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class VPessoaVinculoSaveSchema(BaseModel):
|
||||
pessoa_vinculo_id: int | None = None # PK opcional (create/update)
|
||||
|
||||
nome: str
|
||||
tb_estadocivil_id: int | None = None
|
||||
tb_profissao_id: int | None = None
|
||||
pessoa_id: int | None = None
|
||||
falecido: bool | None = None
|
||||
|
||||
tipo_cadastro_familia: int | None = None
|
||||
livro_natureza_id: int
|
||||
registro_id: int # ajuste para str se o seu modelo/trilha usar string
|
||||
tipo_cadastro_geral: int | None = None
|
||||
|
||||
cidade_residencia: str | None = None
|
||||
idade: int | None = None
|
||||
auxiliar: bool | None = None
|
||||
ordem: int | None = None
|
||||
tb_partelivroe_id: int | None = None
|
||||
|
||||
declarante: bool | None = None
|
||||
nome_especial: str | None = None
|
||||
|
||||
cert_data: date | None = None
|
||||
cert_folha: str | None = None
|
||||
cert_livro: str | None = None
|
||||
cert_termo: str | None = None
|
||||
cert_uf: str | None = None
|
||||
|
||||
chave_importacao: str | None = None
|
||||
tabela_vinculo: str | None = None
|
||||
abandonador_pessoa_id: int | None = None
|
||||
assinatura: bool | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
from packages.v1.serventias.actions.v_casamento.v_casamento_antigo_index_action import (
|
||||
VCasamentoAntigosIndexAction,
|
||||
)
|
||||
|
||||
|
||||
class VCasamentosAntigosIndexService:
|
||||
"""
|
||||
Serviço responsável por encapsular a lógica de negócio para a operação
|
||||
de listagem de registros na tabela G_GRAMATICA.
|
||||
"""
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Executa a operação de busca de todos os registros no banco de dados.
|
||||
|
||||
Args:
|
||||
g_cartorio_index_schema (GCartorioIndexSchema):
|
||||
Esquema que pode conter filtros ou parâmetros de busca.
|
||||
|
||||
Returns:
|
||||
A lista de registros encontrados.
|
||||
"""
|
||||
# ----------------------------------------------------
|
||||
# Instanciamento da ação
|
||||
# ----------------------------------------------------
|
||||
antigos_index = VCasamentoAntigosIndexAction()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Retorno da informação
|
||||
# ----------------------------------------------------
|
||||
return antigos_index.execute()
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
from packages.v1.serventias.actions.v_pessoa.v_pessoa_index_action import (
|
||||
VPessoaIndexAction,
|
||||
)
|
||||
from packages.v1.serventias.actions.v_pessoa_vinculo.v_pessoa_vinculo_index_action import (
|
||||
VPessoaVinculoIndexAction,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_schema import VPessoaSearchSchema
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaIndexService:
|
||||
"""
|
||||
Serviço responsável por encapsular a lógica de negócio para a operação
|
||||
de listagem de registros na tabela G_GRAMATICA.
|
||||
"""
|
||||
|
||||
def execute(self, v_pessoa_search: VPessoaSearchSchema):
|
||||
"""
|
||||
Executa a operação de busca de todos os registros no banco de dados.
|
||||
|
||||
Args:
|
||||
g_cartorio_index_schema (GCartorioIndexSchema):
|
||||
Esquema que pode conter filtros ou parâmetros de busca.
|
||||
|
||||
Returns:
|
||||
A lista de registros encontrados.
|
||||
"""
|
||||
# ----------------------------------------------------
|
||||
# Instanciamento da ação
|
||||
# ----------------------------------------------------
|
||||
v_pessoa_index_action = VPessoaIndexAction()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução da ação
|
||||
# ----------------------------------------------------
|
||||
data = v_pessoa_index_action.execute(v_pessoa_search)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Retorno da informação
|
||||
# ----------------------------------------------------
|
||||
return data
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
from packages.v1.serventias.actions.v_pessoa.v_pessoa_save_action import (
|
||||
VPessoaSaveAction,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_schema import VPessoaSaveSchema
|
||||
|
||||
|
||||
class VPessoaSaveService:
|
||||
"""
|
||||
Serviço responsável por encapsular a lógica de negócio para a operação
|
||||
de listagem de registros na tabela G_GRAMATICA.
|
||||
"""
|
||||
|
||||
def execute(self, v_pessoa_save_schema: VPessoaSaveSchema):
|
||||
"""
|
||||
Executa a operação de busca de todos os registros no banco de dados.
|
||||
|
||||
Args:
|
||||
g_cartorio_index_schema (GCartorioIndexSchema):
|
||||
Esquema que pode conter filtros ou parâmetros de busca.
|
||||
|
||||
Returns:
|
||||
A lista de registros encontrados.
|
||||
"""
|
||||
# ----------------------------------------------------
|
||||
# Instanciamento da ação
|
||||
# ----------------------------------------------------
|
||||
v_pessoa_save = VPessoaSaveAction()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução da ação
|
||||
# ----------------------------------------------------
|
||||
data = v_pessoa_save.execute(v_pessoa_save_schema)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Retorno da informação
|
||||
# ----------------------------------------------------
|
||||
return data
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
from packages.v1.serventias.actions.v_pessoa_vinculo.v_pessoa_vinculo_index_action import (
|
||||
VPessoaVinculoIndexAction,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoIndexSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaVinculoIndexService:
|
||||
"""
|
||||
Serviço responsável por encapsular a lógica de negócio para a operação
|
||||
de listagem de registros na tabela G_GRAMATICA.
|
||||
"""
|
||||
|
||||
def execute(self, v_pessoa_vinculo_index_schema: VPessoaVinculoIndexSchema):
|
||||
"""
|
||||
Executa a operação de busca de todos os registros no banco de dados.
|
||||
|
||||
Args:
|
||||
g_cartorio_index_schema (GCartorioIndexSchema):
|
||||
Esquema que pode conter filtros ou parâmetros de busca.
|
||||
|
||||
Returns:
|
||||
A lista de registros encontrados.
|
||||
"""
|
||||
# ----------------------------------------------------
|
||||
# Instanciamento da ação
|
||||
# ----------------------------------------------------
|
||||
v_pessoa_vinculo_index_action = VPessoaVinculoIndexAction()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução da ação
|
||||
# ----------------------------------------------------
|
||||
data = v_pessoa_vinculo_index_action.execute(v_pessoa_vinculo_index_schema)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Retorno da informação
|
||||
# ----------------------------------------------------
|
||||
return data
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
from packages.v1.serventias.actions.v_pessoa_vinculo.v_pessoa_vinculo_save_action import (
|
||||
VPessoaVinculoSaveAction,
|
||||
)
|
||||
from packages.v1.serventias.schemas.v_pessoa_vinculo_schema import (
|
||||
VPessoaVinculoSaveSchema,
|
||||
)
|
||||
|
||||
|
||||
class VPessoaVInculoSaveService:
|
||||
"""
|
||||
Serviço responsável por encapsular a lógica de negócio para a operação
|
||||
de listagem de registros na tabela G_GRAMATICA.
|
||||
"""
|
||||
|
||||
def execute(self, v_pessoa_vinculo_save_schema: VPessoaVinculoSaveSchema):
|
||||
"""
|
||||
Executa a operação de busca de todos os registros no banco de dados.
|
||||
|
||||
Args:
|
||||
g_cartorio_index_schema (GCartorioIndexSchema):
|
||||
Esquema que pode conter filtros ou parâmetros de busca.
|
||||
|
||||
Returns:
|
||||
A lista de registros encontrados.
|
||||
"""
|
||||
# ----------------------------------------------------
|
||||
# Instanciamento da ação
|
||||
# ----------------------------------------------------
|
||||
v_pessoa_vinculo_index_action = VPessoaVinculoSaveAction()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução da ação
|
||||
# ----------------------------------------------------
|
||||
data = v_pessoa_vinculo_index_action.execute(v_pessoa_vinculo_save_schema)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Retorno da informação
|
||||
# ----------------------------------------------------
|
||||
return data
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
@echo off
|
||||
setlocal EnableExtensions EnableDelayedExpansion
|
||||
|
||||
:: ===== Configuração/ajuda =====
|
||||
if /I "%~1"=="-h" goto :help
|
||||
if /I "%~1"=="/h" goto :help
|
||||
if /I "%~1"=="--help" goto :help
|
||||
|
||||
:: Pasta raiz = 1º argumento ou pasta atual
|
||||
set "ROOT=%~1"
|
||||
if not defined ROOT set "ROOT=%cd%"
|
||||
|
||||
:: Checa flag /dry-run em qualquer argumento
|
||||
set "DRYRUN="
|
||||
for %%A in (%*) do (
|
||||
if /I "%%~A"=="/dry-run" set "DRYRUN=1"
|
||||
)
|
||||
|
||||
:: Normaliza ROOT removendo aspas extras
|
||||
for %%# in ("%ROOT%") do set "ROOT=%%~f#"
|
||||
|
||||
if not exist "%ROOT%" (
|
||||
echo [ERRO] Pasta nao encontrada: "%ROOT%"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: ===== Timestamp e log =====
|
||||
set "TS=%date%_%time%"
|
||||
set "TS=%TS:/=%"
|
||||
set "TS=%TS::=%"
|
||||
set "TS=%TS:.=%"
|
||||
set "TS=%TS:,=%"
|
||||
set "TS=%TS: =0%"
|
||||
set "LOG=%ROOT%\cleanup_pycache_%TS%.log"
|
||||
|
||||
echo ================================================== > "%LOG%"
|
||||
echo Limpeza de __pycache__ >> "%LOG%"
|
||||
echo Pasta raiz: "%ROOT%" >> "%LOG%"
|
||||
if defined DRYRUN (echo Modo: DRY-RUN (apenas listar) >> "%LOG%") else (echo Modo: EXECUTANDO REMOCOES >> "%LOG%")
|
||||
echo Iniciado: %date% %time% >> "%LOG%"
|
||||
echo ================================================== >> "%LOG%"
|
||||
|
||||
set /a FOUND=0, OK=0, ERR=0
|
||||
|
||||
:: ===== Procura e (opcionalmente) remove =====
|
||||
for /d /r "%ROOT%" %%D in (__pycache__) do (
|
||||
set /a FOUND+=1
|
||||
if defined DRYRUN (
|
||||
echo [LISTAR] "%%~fD"
|
||||
>>"%LOG%" echo [LISTAR] "%%~fD"
|
||||
) else (
|
||||
echo [APAGAR] "%%~fD"
|
||||
rd /s /q "%%~fD" 1>nul 2>nul
|
||||
if exist "%%~fD" (
|
||||
set /a ERR+=1
|
||||
>>"%LOG%" echo [FALHA] "%%~fD"
|
||||
) else (
|
||||
set /a OK+=1
|
||||
>>"%LOG%" echo [OK] "%%~fD"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
echo.>>"%LOG%"
|
||||
echo Pastas encontradas: %FOUND% >> "%LOG%"
|
||||
echo Removidas com sucesso: %OK% >> "%LOG%"
|
||||
echo Falhas: %ERR% >> "%LOG%"
|
||||
echo Finalizado: %date% %time% >> "%LOG%"
|
||||
|
||||
:: ===== Resumo no console =====
|
||||
echo.
|
||||
echo ===== RESUMO =====
|
||||
echo Pasta raiz: "%ROOT%"
|
||||
if defined DRYRUN (echo Modo: DRY-RUN ^(nao removeu nada^))
|
||||
echo Pastas encontradas: %FOUND%
|
||||
if not defined DRYRUN (
|
||||
echo Removidas com sucesso: %OK%
|
||||
echo Falhas: %ERR%
|
||||
)
|
||||
echo Log salvo em: "%LOG%"
|
||||
exit /b 0
|
||||
|
||||
:help
|
||||
echo Uso:
|
||||
echo cleanup_pycache.bat [PASTA_RAIZ] [/dry-run]
|
||||
echo.
|
||||
echo Ex.: cleanup_pycache.bat "D:\Projetos\MeuApp"
|
||||
echo Ex.: cleanup_pycache.bat /dry-run
|
||||
echo Ex.: cleanup_pycache.bat "D:\Repos" /dry-run
|
||||
exit /b 0
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue