[MVPTN-34] feat(Calculo): Implementa Busca de parametros, pesquisa em faixa de valores e o calculo rápido

This commit is contained in:
Keven Willian Pereira de Souza 2025-10-28 18:10:28 -03:00
parent 0664786243
commit b85557194b
18 changed files with 616 additions and 46 deletions

144
.code-workspace Normal file
View file

@ -0,0 +1,144 @@
{
"folders": [
{ "path": "D:/IIS/Orius/api" }
],
"settings": {
// === GERAL ===
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
"editor.formatOnPaste": false,
"editor.formatOnType": false,
"editor.minimap.enabled": false,
"files.trimTrailingWhitespace": true,
"files.autoSave": "onFocusChange",
"telemetry.telemetryLevel": "off",
"update.mode": "manual",
// === PERFORMANCE ===
"files.watcherExclude": {
"**/__pycache__/**": true,
"**/.mypy_cache/**": true,
"**/.pytest_cache/**": true,
"**/.venv/**": true,
"**/venv/**": true,
"**/.git/**": true
},
"search.exclude": {
"**/__pycache__": true,
"**/.git": true,
"**/.mypy_cache": true,
"**/.pytest_cache": true
},
// === PYTHON ===
"python.defaultInterpreterPath": "D:/IIS/Orius/api/venv/Scripts/python.exe",
"python.analysis.typeCheckingMode": "off",
"python.analysis.useLibraryCodeForTypes": true,
"python.languageServer": "Pylance",
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--line-length", "100"],
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.flake8Args": ["--max-line-length=100"],
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true
},
// === TERMINAIS INTEGRADOS ===
"terminal.integrated.profiles.windows": {
"FastAPI Dev": {
"path": "cmd.exe",
"args": [
"/k",
"cd D:\\IIS\\Orius\\api && venv\\Scripts\\activate && uvicorn main:app --reload --log-level debug"
]
},
"FastAPI Prod": {
"path": "cmd.exe",
"args": [
"/k",
"cd D:\\IIS\\Orius\\api && venv\\Scripts\\activate && uvicorn main:app --host 0.0.0.0 --port 8000"
]
},
"Python Shell": {
"path": "cmd.exe",
"args": ["/k", "cd D:\\IIS\\Orius\\api && venv\\Scripts\\activate"]
},
"Git Bash": {
"path": "C:\\Program Files\\Git\\bin\\bash.exe"
}
},
"terminal.integrated.defaultProfile.windows": "Git Bash",
// === GIT ===
"git.enabled": true,
"git.autorefresh": false,
"git.fetchOnPull": true,
"git.confirmSync": false,
// === VISUAL ===
"workbench.colorTheme": "Default Dark Modern",
"window.zoomLevel": 0,
"breadcrumbs.enabled": true,
"explorer.compactFolders": false,
// === TESTES ===
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false,
"python.testing.autoTestDiscoverOnSaveEnabled": true,
// === MISC ===
"files.exclude": {
"**/.DS_Store": true,
"**/*.log": true
}
},
"launch": {
"version": "0.2.0",
"configurations": [
{
"name": "Debug FastAPI",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": [
"main:app",
"--reload",
"--host",
"127.0.0.1",
"--port",
"8000"
],
"jinja": true,
"justMyCode": true,
"cwd": "${workspaceFolder}",
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
},
{
"name": "Pytest All",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/venv/Scripts/pytest.exe",
"args": ["-v"],
"console": "integratedTerminal"
}
]
},
"extensions": {
"recommendations": [
// === PYTHON ===
"ms-python.python",
"ms-python.black-formatter",
"ms-python.flake8",
"ms-python.pylance",
"littlefoxteam.vscode-python-test-adapter",
// === GIT ===
"eamodio.gitlens",

View file

@ -1,8 +1,7 @@
class CEP:
@staticmethod
def validate(data: str) -> bool:
# Valida e retorna a informação
return len(data) == 8
return len(data) == 8

14
actions/values/values.py Normal file
View file

@ -0,0 +1,14 @@
from decimal import ROUND_HALF_UP, Decimal
class Values:
@staticmethod
def percent(value: Decimal, percent: Decimal) -> Decimal:
return value / percent
@staticmethod
def money(scale: str, value: Decimal) -> Decimal:
return value.quantize(scale, rounding=ROUND_HALF_UP)

View file

@ -0,0 +1,74 @@
from __future__ import annotations
from decimal import Decimal
from typing import Iterable, Optional, Protocol, Any
class EmolumentoItemLike(Protocol):
"""Protocolo mínimo esperado para um item de emolumento."""
valor_inicio: Optional[Decimal]
valor_fim: Optional[Decimal]
class GEmolumentoItemGetFaixaValorAction:
"""
Responsável por escolher, dentre uma coleção de itens de emolumento,
aquele cuja faixa [valor_inicio, valor_fim] contem o valor_documento.
- Se houver mais de um candidato, prioriza o de maior valor_inicio (faixa mais específica).
- Se não houver faixa que contenha o valor_documento, tenta a faixa 'aberta' (valor_fim nulo).
- Persistindo a ausência, retorna o item cujo valor_inicio é o mais próximo abaixo do valor_documento.
"""
@staticmethod
def _para_decimal(valor: Any, padrao: str = "0") -> Decimal:
return valor if isinstance(valor, Decimal) else Decimal(str(valor or padrao))
def execute(
self,
itens: Iterable[EmolumentoItemLike],
valor_documento: Decimal,
) -> EmolumentoItemLike:
lista = list(itens)
if not lista:
raise ValueError("Nenhum item de emolumento foi informado.")
valor_doc = self._para_decimal(valor_documento)
candidatos: list[tuple[Decimal, Decimal, EmolumentoItemLike]] = []
abertos: list[tuple[Decimal, EmolumentoItemLike]] = []
abaixo: list[tuple[Decimal, EmolumentoItemLike]] = []
for item in lista:
ini = self._para_decimal(getattr(item, "valor_inicio", None))
fim_raw = getattr(item, "valor_fim", None)
fim = (
self._para_decimal(fim_raw, padrao="Infinity")
if fim_raw is not None
else Decimal("Infinity")
)
if ini <= valor_doc <= fim:
candidatos.append((ini, fim, item))
elif fim == Decimal("Infinity") and ini <= valor_doc:
abertos.append((ini, item))
elif ini <= valor_doc:
abaixo.append((ini, item))
if candidatos:
candidatos.sort(key=lambda t: (t[0], t[1])) # maior ini e menor fim
return candidatos[-1][2]
if abertos:
abertos.sort(key=lambda t: t[0]) # maior ini
return abertos[-1][1]
if abaixo:
abaixo.sort(key=lambda t: t[0]) # maior ini
return abaixo[-1][1]
# Fallback: não há faixa adequada nem valores abaixo; devolve o de menor valor_inicio
lista_ordenada = sorted(
lista, key=lambda it: self._para_decimal(getattr(it, "valor_inicio", None))
)
return lista_ordenada[0]

View file

@ -1,6 +1,10 @@
from abstracts.action import BaseAction
from packages.v1.administrativo.repositories.g_emolumento_item.g_emolumento_item_index_repository import GEmolumentoItemIndexRepository
from packages.v1.administrativo.schemas.g_emolumento_item_schema import GEmolumentoItemIndexSchema
from packages.v1.administrativo.repositories.g_emolumento_item.g_emolumento_item_index_repository import (
GEmolumentoItemIndexRepository,
)
from packages.v1.administrativo.schemas.g_emolumento_item_schema import (
GEmolumentoItemIndexSchema,
)
class GEmolumentoItemIndexAction(BaseAction):
@ -9,7 +13,9 @@ class GEmolumentoItemIndexAction(BaseAction):
de listagem de todos os registros na tabela G_NATUREZA_TITULO.
"""
def execute(self, g_emolumento_item_emolumento_index_schema: GEmolumentoItemIndexSchema):
def execute(
self, g_emolumento_item_emolumento_index_schema: GEmolumentoItemIndexSchema
):
"""
Executa a operação de listagem no banco de dados.
@ -28,7 +34,9 @@ class GEmolumentoItemIndexAction(BaseAction):
# ----------------------------------------------------
# Execução do repositório
# ----------------------------------------------------
response = g_emolumento_item_index_repository.execute(g_emolumento_item_emolumento_index_schema)
response = g_emolumento_item_index_repository.execute(
g_emolumento_item_emolumento_index_schema
)
# ----------------------------------------------------
# Retorno da informação

View file

@ -1,5 +1,7 @@
from actions.dynamic_import.dynamic_import import DynamicImport
from packages.v1.administrativo.schemas.g_calculo_schema import GCalculoRapidoSchema
from packages.v1.administrativo.schemas.g_calculo_schema import (
GCalculoRapidoSchema,
)
class GCalculoController:
@ -27,6 +29,6 @@ class GCalculoController:
# Execução da listagem
return {
"message": "Registros de G_EMOLUMENTO localizados com sucesso.",
"message": "Cálculo realizado com sucesso",
"data": self.rapido_service.execute(g_calculo_rapido_schema),
}

View file

@ -30,7 +30,7 @@ g_calculo_controller = GCalculoController()
async def index(
g_calculo_rapido_schema: GCalculoRapidoSchema,
current_user: dict = Depends(get_current_user),
) -> ResponseGCalculoRapidoSchema:
):
"""
Retorna todos os registros da tabela G_EMOLUMENTO.
"""

View file

@ -1,5 +1,8 @@
from abstracts.repository import BaseRepository
from packages.v1.administrativo.schemas.g_emolumento_item_schema import GEmolumentoItemIndexSchema
from packages.v1.administrativo.schemas.g_emolumento_item_schema import (
GEmolumentoItemIndexSchema,
)
class GEmolumentoItemIndexRepository(BaseRepository):
"""
@ -7,7 +10,9 @@ class GEmolumentoItemIndexRepository(BaseRepository):
na tabela t_censec_qualidade.
"""
def execute(self, g_emolumento_item_emolumento_index_schema: GEmolumentoItemIndexSchema):
def execute(
self, g_emolumento_item_emolumento_index_schema: GEmolumentoItemIndexSchema
):
"""
Executa a consulta SQL para buscar todos os registros.
@ -32,4 +37,4 @@ class GEmolumentoItemIndexRepository(BaseRepository):
response = self.fetch_all(sql, params)
# Retorna os dados localizados
return response
return response

View file

@ -1,4 +1,4 @@
from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict
from typing import Optional
@ -8,6 +8,7 @@ class GCalculoRapidoSchema(BaseModel):
observacao: Optional[str] = None
quantidade: Optional[int] = None
emolumento_id: Optional[float] = None
valor_documento: Optional[float] = None
class Config:
from_attributes = True
@ -15,12 +16,11 @@ class GCalculoRapidoSchema(BaseModel):
class ResponseGCalculoRapidoSchema(GCalculoRapidoSchema):
valor_documento: Optional[str] = None
valor_emolumento: Optional[str] = None
valor_taxa_judiciaria: Optional[str] = None
valor_fundos: Optional[str] = None
valor_iss: Optional[str] = None
valor_total: Optional[str] = None
valor_emolumento: Optional[float] = None
valor_taxa_judiciaria: Optional[float] = None
valor_fundos: Optional[float] = None
valor_iss: Optional[float] = None
valor_total: Optional[float] = None
class Config:
from_attributes = True
# valida e coerce em atribuições após criar o objeto
model_config = ConfigDict(from_attributes=True, validate_assignment=True)

View file

@ -1,6 +1,7 @@
from pydantic import BaseModel
from typing import Optional
# ----------------------------------------------------
# Schema base - representa a tabela G_EMOLUMENTO_ITEM
# ----------------------------------------------------
@ -48,6 +49,7 @@ class GEmolumentoItemIdSchema(BaseModel):
class Config:
from_attributes = True
# ----------------------------------------------------
# Schema para localizar um registro pelo EmolumentoId (GET /{id})
# ----------------------------------------------------

View file

@ -1,37 +1,146 @@
from packages.v1.administrativo.controllers.g_calculo_controller import (
from __future__ import annotations
from decimal import Decimal
from typing import Optional
from actions.values.values import Values
from packages.v1.administrativo.actions.g_emolumento_item.g_emolumento_item_get_faixa_valor_action import (
GEmolumentoItemGetFaixaValorAction,
)
from packages.v1.administrativo.schemas.g_calculo_schema import (
GCalculoRapidoSchema,
ResponseGCalculoRapidoSchema,
)
from packages.v1.administrativo.schemas.g_emolumento_item_schema import (
GEmolumentoItemIndexSchema,
)
from packages.v1.administrativo.schemas.g_emolumento_schema import GEmolumentoIdSchema
from packages.v1.administrativo.services.g_emolumento.go.g_emolumento_index_service import (
GEmolumentoIndexService,
)
from packages.v1.administrativo.services.g_emolumento.go.g_emolumento_show_service import (
GEmolumentoShowService,
)
from packages.v1.administrativo.services.g_emolumento_item.go.g_emolumento_item_index_service import (
GEmolumentoItemIndexService,
)
from packages.v1.parametros.schemas.g_config_schema import GConfigNomeSchema
from packages.v1.parametros.services.g_config.g_config_show_by_nome_service import (
GConfigShowByNomeService,
)
class GCalculoRapidoService:
"""
Cálculo rápido de emolumentos + taxas.
- Usa DI para serviços (facilita teste/mocks).
- Centraliza leitura de configs/percentuais.
- Normaliza moeda com quantize (por padrão 3 casas, conforme NUMERIC(14,3)).
"""
def execute(self, g_calculo_rapido_schema: GCalculoRapidoSchema):
def __init__(
self,
config_service: Optional[GConfigShowByNomeService] = None,
emolumento_show_service: Optional[GEmolumentoShowService] = None,
emolumento_item_index_service: Optional[GEmolumentoItemIndexService] = None,
emolumento_item_get_faixa_valor_action: Optional[
GEmolumentoItemGetFaixaValorAction
] = None,
scale: str = "0.001", # 3 casas decimais (ex.: Firebird NUMERIC(14,3))
) -> None:
self._config_service = config_service or GConfigShowByNomeService()
self._emolumento_show_service = (
emolumento_show_service or GEmolumentoShowService()
)
self._emolumento_item_index_service = (
emolumento_item_index_service or GEmolumentoItemIndexService()
)
self._emolumento_item_get_faixa_valor_action = (
emolumento_item_get_faixa_valor_action
or GEmolumentoItemGetFaixaValorAction()
)
self._scale = Decimal(scale)
# Instâciamento da classe que busca o emolumento
g_emolumento_show_service = GEmolumentoShowService()
def execute(self, data: GCalculoRapidoSchema) -> ResponseGCalculoRapidoSchema:
# Busca o emolumento de acordo com o informado
response_g_emolumento_show_service = g_emolumento_show_service.execute(
GEmolumentoIdSchema(emolumento_id=g_calculo_rapido_schema.emolumento_id)
# Busca os parâmetros da aplicação
periodo_id = float(
self._config_service.execute(
GConfigNomeSchema(nome="PERIODO_PADRAO", sistema_id=2)
).valor
)
# Instânciamento da classe que busca os itens do emolumento
g_emolumento_show_service = GEmolumentoIndexService()
# Gerar o percentual do iss de acordo com o parâmetro
percentual_iss = Values.percent(
Decimal(
self._config_service.execute(
GConfigNomeSchema(nome="PERCENTUAL_ISS", sistema_id=5)
).valor
),
100,
)
response_g_emolumento_show_service = g_emolumento_show_service.execute(
GEmolumentoItemSchema(
emolumento_id=response_g_emolumento_show_service.emolumento_id
# Gerar o percentual do iss de acordo com o parâmetro
percentual_fundos = Values.percent(
Decimal(
self._config_service.execute(
GConfigNomeSchema(nome="PERCENTUAL_FUNDOS_ESTADUAIS", sistema_id=5)
).valor
),
100,
)
# Busca o emolumento desejado
emolumento = self._emolumento_show_service.execute(
GEmolumentoIdSchema(emolumento_id=data.emolumento_id)
)
# Busca os itens do emolumento
emolumento_itens = self._emolumento_item_index_service.execute(
GEmolumentoItemIndexSchema(
emolumento_id=float(emolumento.emolumento_id),
emolumento_periodo_id=periodo_id,
)
)
# ----------------------------------------------------
# Retorno da informação
# ----------------------------------------------------
return response_g_emolumento_show_service
# Se vier lista, usa o primeiro (ou ajuste a regra aqui, se necessário)
emolumento_item = self._emolumento_item_get_faixa_valor_action.execute(
emolumento_itens, Decimal(data.valor_documento)
)
# Converter o valor para decimal
quantidade = Decimal(data.quantidade)
# Cálculos
emolumento_total = Values.money(
self._scale, (emolumento_item.valor_emolumento * quantidade)
)
taxa_judiciaria_total = Values.money(
self._scale, (emolumento_item.valor_taxa_judiciaria * quantidade)
)
iss_total = Values.money(
self._scale,
(emolumento_item.valor_emolumento * percentual_iss * quantidade),
)
fundos_total = Values.money(
self._scale,
(emolumento_item.valor_emolumento * percentual_fundos * quantidade),
)
total = Values.money(
self._scale,
(emolumento_total + taxa_judiciaria_total + iss_total + fundos_total),
)
# Resposta
return ResponseGCalculoRapidoSchema(
apresentante=data.apresentante,
observacao=data.observacao,
emolumento_id=float(data.emolumento_id),
valor_documento=float(data.valor_documento),
valor_emolumento=emolumento_total,
valor_taxa_judiciaria=taxa_judiciaria_total,
valor_iss=iss_total,
valor_fundos=fundos_total,
valor_total=total,
)

View file

@ -1,7 +1,11 @@
from packages.v1.administrativo.actions.g_emolumento_item.g_emolumento_item_index_action import GEmolumentoItemIndexAction
from packages.v1.administrativo.actions.g_emolumento_item.g_emolumento_item_index_action import (
GEmolumentoItemIndexAction,
)
from fastapi import HTTPException, status
from packages.v1.administrativo.schemas.g_emolumento_item_schema import GEmolumentoItemIndexSchema
from packages.v1.administrativo.schemas.g_emolumento_item_schema import (
GEmolumentoItemIndexSchema,
)
class GEmolumentoItemIndexService:
@ -10,8 +14,9 @@ class GEmolumentoItemIndexService:
de listagem de registros na tabela G_EMOLUMENTO_ITEM.
"""
def execute(self, g_emolumento_item_emolumento_index_schema: GEmolumentoItemIndexSchema):
def execute(
self, g_emolumento_item_emolumento_index_schema: GEmolumentoItemIndexSchema
):
"""
Executa a operação de busca de todos os registros no banco de dados.
@ -30,7 +35,9 @@ class GEmolumentoItemIndexService:
# ----------------------------------------------------
# Execução da ação
# ----------------------------------------------------
data = g_emolumento_item_index_action.execute(g_emolumento_item_emolumento_index_schema)
data = g_emolumento_item_index_action.execute(
g_emolumento_item_emolumento_index_schema
)
# ----------------------------------------------------
# Verificação de retorno
@ -38,7 +45,7 @@ class GEmolumentoItemIndexService:
if not data:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Não foi possível localizar registros de G_EMOLUMENTO_ITEM."
detail="Não foi possível localizar registros de G_EMOLUMENTO_ITEM.",
)
# ----------------------------------------------------

View file

@ -0,0 +1,20 @@
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,
)
from packages.v1.sequencia.repositories.g_sequencia.get import Get
from abstracts.action import BaseAction
class GConfigShowByNomeAction(BaseAction):
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)

View file

@ -0,0 +1,19 @@
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,
)
from abstracts.action import BaseAction
class GConfigShowByNomeAction(BaseAction):
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)

View file

@ -0,0 +1,31 @@
from abstracts.repository import BaseRepository
from packages.v1.parametros.schemas.g_config_schema import (
GConfigNomeSchema,
GConfigResponseSchema,
)
class GConfigShowByNomeRepository(BaseRepository):
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

View file

@ -0,0 +1,29 @@
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)

View file

@ -0,0 +1,18 @@
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)

89
python_limpa_cache.bat Normal file
View file

@ -0,0 +1,89 @@
@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: =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 /f "delims=" %%D in ('dir /ad /b /s "%ROOT%\__pycache__" 2^>nul') do (
set /a FOUND+=1
if defined DRYRUN (
echo [LISTAR] "%%D"
>>"%LOG%" echo [LISTAR] "%%D"
) else (
echo [APAGAR] "%%D"
rd /s /q "%%D" 1>nul 2>nul
if exist "%%D" (
set /a ERR+=1
>>"%LOG%" echo [FALHA] "%%D"
) else (
set /a OK+=1
>>"%LOG%" echo [OK] "%%D"
)
)
)
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