Spaces:
Paused
Paused
Upload folder using huggingface_hub (part 2)
Browse filesThis view is limited to 50 files because it contains too many changes. Β See raw diff
- .gitattributes +1 -0
- Dockerfile +45 -0
- README.md +17 -10
- app.py +33 -0
- app/pyafipws/tests/wscdc.py +88 -0
- app/pyafipws/tests/wsfev1.py +212 -0
- app/pyafipws/tests/wslpg.py +544 -0
- app/pyafipws/tests/wslpg_cert_autorizar_resp.xml +31 -0
- app/pyafipws/tests/wsmtx.py +246 -0
- app/pyafipws/trazafito.py +591 -0
- app/pyafipws/trazamed.py +1041 -0
- app/pyafipws/trazaprodmed.py +555 -0
- app/pyafipws/trazarenpre.py +366 -0
- app/pyafipws/trazavet.py +598 -0
- app/pyafipws/typelib/pyafipws.idl +48 -0
- app/pyafipws/typelib/trazamed.idl +80 -0
- app/pyafipws/typelib/trazamed.tlb +0 -0
- app/pyafipws/typelib/wsaa.idl +106 -0
- app/pyafipws/typelib/wsaa.tlb +0 -0
- app/pyafipws/typelib/wsfev1.idl +225 -0
- app/pyafipws/typelib/wsfev1.tlb +0 -0
- app/pyafipws/utils.py +936 -0
- app/pyafipws/venv.bat +29 -0
- app/pyafipws/wdigdepfiel.py +186 -0
- app/pyafipws/ws_sr_padron.py +380 -0
- app/pyafipws/wsaa.py +525 -0
- app/pyafipws/wsbfev1.py +659 -0
- app/pyafipws/wscdc.py +398 -0
- app/pyafipws/wscoc.py +1032 -0
- app/pyafipws/wsct.py +820 -0
- app/pyafipws/wsctg.py +1198 -0
- app/pyafipws/wsfev1.py +1349 -0
- app/pyafipws/wsfexv1.py +799 -0
- app/pyafipws/wslpg.py +0 -0
- app/pyafipws/wslpg_datos.py +113 -0
- app/pyafipws/wslsp.py +1210 -0
- app/pyafipws/wsltv.py +1069 -0
- app/pyafipws/wslum.py +923 -0
- app/pyafipws/wsmtx.py +1243 -0
- app/pyafipws/wsremcarne.py +691 -0
- app/reports/__init__.py +1 -0
- app/reports/builders.py +682 -0
- app/reports/fallback.py +111 -0
- app/reports/kyc_service.py +156 -0
- app/reports/models.py +42 -0
- app/reports/orchestrator.py +244 -0
- app/reports/registry.py +104 -0
- app/reports/router.py +763 -0
- app/reports/schema_validators.py +203 -0
- app/reports/schemas.py +629 -0
.gitattributes
CHANGED
|
@@ -35,3 +35,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
app/cache/ddjj_pep.csv filter=lfs diff=lfs merge=lfs -text
|
| 37 |
app/pyafipws/ejemplos/wsfe/delphi/Project1.exe filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
app/cache/ddjj_pep.csv filter=lfs diff=lfs merge=lfs -text
|
| 37 |
app/pyafipws/ejemplos/wsfe/delphi/Project1.exe filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
app/utils/logo.png filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Install system dependencies for:
|
| 6 |
+
# - PostgreSQL (libpq-dev)
|
| 7 |
+
# - ddddocr OCR (libglib2.0-0 libsm6 libxext6 libxrender-dev libgl1-mesa-glx)
|
| 8 |
+
# - Playwright Chromium (via --with-deps)
|
| 9 |
+
# - curl_cffi build tools (gcc, curl)
|
| 10 |
+
RUN apt-get update && apt-get install -y \
|
| 11 |
+
gcc \
|
| 12 |
+
g++ \
|
| 13 |
+
libpq-dev \
|
| 14 |
+
curl \
|
| 15 |
+
wget \
|
| 16 |
+
gnupg \
|
| 17 |
+
libglib2.0-0 \
|
| 18 |
+
libsm6 \
|
| 19 |
+
libxext6 \
|
| 20 |
+
libxrender1 \
|
| 21 |
+
libgl1 \
|
| 22 |
+
libgomp1 \
|
| 23 |
+
libssl-dev \
|
| 24 |
+
libsasl2-dev \
|
| 25 |
+
swig \
|
| 26 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 27 |
+
|
| 28 |
+
# Install Python dependencies
|
| 29 |
+
COPY requirements.txt .
|
| 30 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 31 |
+
|
| 32 |
+
# Configure Playwright browser path to be accessible by any user ID
|
| 33 |
+
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
| 34 |
+
RUN mkdir -p /ms-playwright && chmod -R 777 /ms-playwright
|
| 35 |
+
RUN playwright install chromium --with-deps
|
| 36 |
+
|
| 37 |
+
# Copy application code
|
| 38 |
+
COPY . .
|
| 39 |
+
|
| 40 |
+
# Hugging Face Spaces uses port 7860 by default
|
| 41 |
+
EXPOSE 7860
|
| 42 |
+
|
| 43 |
+
# Start FastAPI on port 7860 (or $PORT if overridden)
|
| 44 |
+
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-7860} --workers 2"]
|
| 45 |
+
|
README.md
CHANGED
|
@@ -1,15 +1,22 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk:
|
| 7 |
-
sdk_version: 6.20.0
|
| 8 |
-
python_version: '3.12'
|
| 9 |
-
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
| 12 |
-
|
| 13 |
---
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: CrowData API
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
|
|
|
|
|
|
|
|
|
| 7 |
pinned: false
|
| 8 |
license: mit
|
| 9 |
+
app_port: 7860
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# CrowData API
|
| 13 |
+
|
| 14 |
+
API backend de CrowData β Plataforma de inteligencia de datos personales y empresariales en Argentina.
|
| 15 |
+
|
| 16 |
+
## Endpoints
|
| 17 |
+
|
| 18 |
+
- `GET /api/health` β Health check
|
| 19 |
+
- `POST /api/auth/jwt/login` β AutenticaciΓ³n JWT
|
| 20 |
+
- `GET /api/reports/persona/{cuit}` β Informe persona
|
| 21 |
+
- `GET /api/reports/empresa/{cuit}` β Informe empresa
|
| 22 |
+
- `GET /api/docs` β Swagger UI
|
app.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import subprocess
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
+
logging.basicConfig(level=logging.INFO)
|
| 7 |
+
logger = logging.getLogger("hf_app")
|
| 8 |
+
|
| 9 |
+
# Ensure Playwright browser binaries are installed
|
| 10 |
+
try:
|
| 11 |
+
logger.info("Verificando/instalando Playwright Chromium...")
|
| 12 |
+
subprocess.run([sys.executable, "-m", "playwright", "install", "chromium"], check=False)
|
| 13 |
+
except Exception as e:
|
| 14 |
+
logger.warning(f"Advertencia al instalar Playwright Chromium: {e}")
|
| 15 |
+
|
| 16 |
+
import gradio as gr
|
| 17 |
+
from app.main import app as fastapi_app
|
| 18 |
+
|
| 19 |
+
# Crear interfaz Gradio de aterrizaje para Hugging Face Spaces
|
| 20 |
+
with gr.Blocks(title="CrowData API Backend") as demo:
|
| 21 |
+
gr.Markdown("# π¦
CrowData API Backend")
|
| 22 |
+
gr.Markdown("El backend de CrowData (FastAPI) estΓ‘ ejecutΓ‘ndose correctamente en Hugging Face Spaces con 16 GB RAM.")
|
| 23 |
+
gr.Markdown("### π Accesos Directos:")
|
| 24 |
+
gr.Markdown("- π [DocumentaciΓ³n de la API (Swagger UI)](/api/docs)")
|
| 25 |
+
gr.Markdown("- π©Ί [Health Check](/api/health)")
|
| 26 |
+
|
| 27 |
+
# Montar Gradio en /ui para preservar las rutas principales /api/* en FastAPI
|
| 28 |
+
app = gr.mount_gradio_app(fastapi_app, demo, path="/ui")
|
| 29 |
+
|
| 30 |
+
if __name__ == "__main__":
|
| 31 |
+
import uvicorn
|
| 32 |
+
port = int(os.environ.get("PORT", 7860))
|
| 33 |
+
uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False)
|
app/pyafipws/tests/wscdc.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
import pysimplesoap.client
|
| 14 |
+
from pyafipws.wscdc import WSCDC
|
| 15 |
+
from pyafipws.wsaa import WSAA
|
| 16 |
+
from pyafipws import utils
|
| 17 |
+
"Pruebas para el servicio web ConstataciΓ³n de Comprobantes de AFIP"
|
| 18 |
+
|
| 19 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 20 |
+
__copyright__ = "Copyright (C) 2013 Mariano Reingart"
|
| 21 |
+
__license__ = "GPL 3.0"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
import unittest
|
| 25 |
+
import sys
|
| 26 |
+
from decimal import Decimal
|
| 27 |
+
|
| 28 |
+
sys.path.append("/home/reingart") # TODO: proper packaging
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
print(pysimplesoap.client.__version__)
|
| 32 |
+
#assert pysimplesoap.client.__version__ >= "1.08c"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
WSDL = "https://wswhomo.afip.gov.ar/WSCDC/service.asmx?WSDL"
|
| 36 |
+
CUIT = 20267565393
|
| 37 |
+
CERT = "/home/reingart/pyafipws/reingart.crt"
|
| 38 |
+
PRIVATEKEY = "/home/reingart/pyafipws/reingart.key"
|
| 39 |
+
CACERT = "/home/reingart/pyafipws/afip_root_desa_ca.crt"
|
| 40 |
+
CACHE = "/home/reingart/pyafipws/cache"
|
| 41 |
+
|
| 42 |
+
# AutenticaciΓ³n:
|
| 43 |
+
wsaa = WSAA()
|
| 44 |
+
tra = wsaa.CreateTRA(service="wscdc")
|
| 45 |
+
cms = wsaa.SignTRA(tra, CERT, PRIVATEKEY)
|
| 46 |
+
wsaa.Conectar()
|
| 47 |
+
wsaa.LoginCMS(cms)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class TestWSCDC(unittest.TestCase):
|
| 51 |
+
|
| 52 |
+
def setUp(self):
|
| 53 |
+
sys.argv.append("--trace") # TODO: use logging
|
| 54 |
+
self.wscdc = wslpg = WSCDC()
|
| 55 |
+
wslpg.LanzarExcepciones = True
|
| 56 |
+
wslpg.Conectar(wsdl=WSDL, cacert=None, cache=CACHE)
|
| 57 |
+
wslpg.Cuit = CUIT
|
| 58 |
+
wslpg.Token = wsaa.Token
|
| 59 |
+
wslpg.Sign = wsaa.Sign
|
| 60 |
+
|
| 61 |
+
def test_constatacion_no(self):
|
| 62 |
+
"Prueba de ConstataciΓ³n de Comprobantes (facturas electrΓ³nicas)"
|
| 63 |
+
wscdc = self.wscdc
|
| 64 |
+
cbte_modo = "CAE"
|
| 65 |
+
cuit_emisor = "20267565393"
|
| 66 |
+
pto_vta = 4002
|
| 67 |
+
cbte_tipo = 1
|
| 68 |
+
cbte_nro = 109
|
| 69 |
+
cbte_fch = "20131227"
|
| 70 |
+
imp_total = "121.0"
|
| 71 |
+
cod_autorizacion = "63523178385550"
|
| 72 |
+
doc_tipo_receptor = 80
|
| 73 |
+
doc_nro_receptor = "30628789661"
|
| 74 |
+
ok = wscdc.ConstatarComprobante(cbte_modo, cuit_emisor, pto_vta, cbte_tipo,
|
| 75 |
+
cbte_nro, cbte_fch, imp_total, cod_autorizacion,
|
| 76 |
+
doc_tipo_receptor, doc_nro_receptor)
|
| 77 |
+
self.assertTrue(ok)
|
| 78 |
+
self.assertEqual(wscdc.Resultado, "R") # Rechazado
|
| 79 |
+
self.assertEqual(wscdc.Obs, "100: El NΒ° de CAI/CAE/CAEA consultado no existe en las bases del organismo.")
|
| 80 |
+
self.assertEqual(wscdc.PuntoVenta, pto_vta)
|
| 81 |
+
self.assertEqual(wscdc.CbteNro, cbte_nro)
|
| 82 |
+
self.assertEqual(wscdc.ImpTotal, imp_total)
|
| 83 |
+
self.assertEqual(wscdc.CAE, cod_autorizacion)
|
| 84 |
+
self.assertEqual(wscdc.EmisionTipo, "CAE")
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
if __name__ == '__main__':
|
| 88 |
+
unittest.main()
|
app/pyafipws/tests/wsfev1.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
from pyafipws.wsaa import WSAA
|
| 14 |
+
from pyafipws.wsfev1 import WSFEv1
|
| 15 |
+
"Pruebas para WSFEv1 de AFIP (Factura ElectrοΏ½nica Mercado Interno sin detalle)"
|
| 16 |
+
|
| 17 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 18 |
+
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
|
| 19 |
+
__license__ = "GPL 3.0"
|
| 20 |
+
|
| 21 |
+
import unittest
|
| 22 |
+
import os
|
| 23 |
+
import time
|
| 24 |
+
import sys
|
| 25 |
+
from decimal import Decimal
|
| 26 |
+
import datetime
|
| 27 |
+
|
| 28 |
+
sys.path.append("/home/reingart") # TODO: proper packaging
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
WSDL = "https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL"
|
| 32 |
+
CUIT = 20267565393
|
| 33 |
+
CERT = "/home/reingart/pyafipws/reingart.crt"
|
| 34 |
+
PRIVATEKEY = "/home/reingart/pyafipws/reingart.key"
|
| 35 |
+
CACERT = "/home/reingart/pyafipws/afip_root_desa_ca.crt"
|
| 36 |
+
CACHE = "/home/reingart/pyafipws/cache"
|
| 37 |
+
|
| 38 |
+
# AutenticaciοΏ½n:
|
| 39 |
+
wsaa = WSAA()
|
| 40 |
+
tra = wsaa.CreateTRA(service="wsfe")
|
| 41 |
+
cms = wsaa.SignTRA(tra, CERT, PRIVATEKEY)
|
| 42 |
+
wsaa.Conectar()
|
| 43 |
+
wsaa.LoginCMS(cms)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class TestFE(unittest.TestCase):
|
| 47 |
+
|
| 48 |
+
def setUp(self):
|
| 49 |
+
sys.argv.append("--trace") # TODO: use logging
|
| 50 |
+
self.wsfev1 = wsfev1 = WSFEv1()
|
| 51 |
+
wsfev1.Cuit = CUIT
|
| 52 |
+
wsfev1.Token = wsaa.Token
|
| 53 |
+
wsfev1.Sign = wsaa.Sign
|
| 54 |
+
wsfev1.Conectar(CACHE, WSDL)
|
| 55 |
+
|
| 56 |
+
def atest_dummy(self):
|
| 57 |
+
print(wsfev1.client.help("dummy"))
|
| 58 |
+
wsfev1.Dummy()
|
| 59 |
+
print("AppServerStatus", wsfev1.AppServerStatus)
|
| 60 |
+
print("DbServerStatus", wsfev1.DbServerStatus)
|
| 61 |
+
print("AuthServerStatus", wsfev1.AuthServerStatus)
|
| 62 |
+
|
| 63 |
+
def test_autorizar_comprobante(self, tipo_cbte=1, cbte_nro=None, servicios=True):
|
| 64 |
+
"Prueba de autorizaciοΏ½n de un comprobante (obtenciοΏ½n de CAE)"
|
| 65 |
+
wsfev1 = self.wsfev1
|
| 66 |
+
|
| 67 |
+
# datos generales del comprobante:
|
| 68 |
+
punto_vta = 4000
|
| 69 |
+
if not cbte_nro:
|
| 70 |
+
# si no me especifοΏ½can nro de comprobante, busco el prοΏ½ximo
|
| 71 |
+
cbte_nro = wsfev1.CompUltimoAutorizado(tipo_cbte, punto_vta)
|
| 72 |
+
cbte_nro = int(cbte_nro) + 1
|
| 73 |
+
fecha = datetime.datetime.now().strftime("%Y%m%d")
|
| 74 |
+
tipo_doc = 80
|
| 75 |
+
nro_doc = "30000000007" # "30500010912" # CUIT BNA
|
| 76 |
+
cbt_desde = cbte_nro
|
| 77 |
+
cbt_hasta = cbt_desde
|
| 78 |
+
imp_total = "122.00"
|
| 79 |
+
imp_tot_conc = "0.00"
|
| 80 |
+
imp_neto = "100.00"
|
| 81 |
+
imp_trib = "1.00"
|
| 82 |
+
imp_op_ex = "0.00"
|
| 83 |
+
imp_iva = "21.00"
|
| 84 |
+
fecha_cbte = fecha
|
| 85 |
+
# Fechas del perοΏ½odo del servicio facturado (solo si concepto = 1?)
|
| 86 |
+
if servicios:
|
| 87 |
+
concepto = 3
|
| 88 |
+
fecha_venc_pago = fecha
|
| 89 |
+
fecha_serv_desde = fecha
|
| 90 |
+
fecha_serv_hasta = fecha
|
| 91 |
+
else:
|
| 92 |
+
concepto = 1
|
| 93 |
+
fecha_venc_pago = fecha_serv_desde = fecha_serv_hasta = None
|
| 94 |
+
moneda_id = 'PES'
|
| 95 |
+
moneda_ctz = '1.000'
|
| 96 |
+
obs = "Observaciones Comerciales, libre"
|
| 97 |
+
|
| 98 |
+
wsfev1.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
|
| 99 |
+
cbt_desde, cbt_hasta, imp_total, imp_tot_conc, imp_neto,
|
| 100 |
+
imp_iva, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago,
|
| 101 |
+
fecha_serv_desde, fecha_serv_hasta, # --
|
| 102 |
+
moneda_id, moneda_ctz)
|
| 103 |
+
|
| 104 |
+
# agrego un comprobante asociado (solo notas de crοΏ½dito / dοΏ½bito)
|
| 105 |
+
if tipo_cbte in (2, 3):
|
| 106 |
+
tipo = 1
|
| 107 |
+
pv = 2
|
| 108 |
+
nro = 1234
|
| 109 |
+
wsfev1.AgregarCmpAsoc(tipo, pv, nro)
|
| 110 |
+
|
| 111 |
+
# agrego otros tributos:
|
| 112 |
+
tributo_id = 99
|
| 113 |
+
desc = 'Impuesto Municipal Matanza'
|
| 114 |
+
base_imp = "100.00"
|
| 115 |
+
alic = "1.00"
|
| 116 |
+
importe = "1.00"
|
| 117 |
+
wsfev1.AgregarTributo(tributo_id, desc, base_imp, alic, importe)
|
| 118 |
+
|
| 119 |
+
# agrego el subtotal por tasa de IVA:
|
| 120 |
+
iva_id = 5 # 21%
|
| 121 |
+
base_im = 100
|
| 122 |
+
importe = 21
|
| 123 |
+
wsfev1.AgregarIva(iva_id, base_imp, importe)
|
| 124 |
+
|
| 125 |
+
# llamo al websevice para obtener el CAE:
|
| 126 |
+
wsfev1.CAESolicitar()
|
| 127 |
+
|
| 128 |
+
self.assertEqual(wsfev1.Resultado, "A") # Aprobado!
|
| 129 |
+
self.assertIsInstance(wsfev1.CAE, str)
|
| 130 |
+
self.assertEqual(len(wsfev1.CAE), len("63363178822329"))
|
| 131 |
+
self.assertEqual(len(wsfev1.Vencimiento), len("20130907"))
|
| 132 |
+
wsfev1.AnalizarXml("XmlResponse")
|
| 133 |
+
# observaciοΏ½n "... no se encuentra registrado en los padrones de AFIP.":
|
| 134 |
+
self.assertEqual(wsfev1.ObtenerTagXml('Obs', 0, 'Code'), '10017')
|
| 135 |
+
|
| 136 |
+
def test_consulta(self):
|
| 137 |
+
"Prueba de obtener los datos de un comprobante autorizado"
|
| 138 |
+
wsfev1 = self.wsfev1
|
| 139 |
+
# autorizo un comprobante:
|
| 140 |
+
tipo_cbte = 1
|
| 141 |
+
self.test_autorizar_comprobante(tipo_cbte)
|
| 142 |
+
# obtengo datos para comprobar
|
| 143 |
+
cae = wsfev1.CAE
|
| 144 |
+
wsfev1.AnalizarXml("XmlRequest")
|
| 145 |
+
imp_total = float(wsfev1.ObtenerTagXml('ImpTotal'))
|
| 146 |
+
concepto = int(wsfev1.ObtenerTagXml('Concepto'))
|
| 147 |
+
punto_vta = wsfev1.PuntoVenta
|
| 148 |
+
cbte_nro = wsfev1.CbteNro
|
| 149 |
+
|
| 150 |
+
# llamo al webservice para consultar y validar manualmente el CAE:
|
| 151 |
+
wsfev1.CompConsultar(tipo_cbte, punto_vta, cbte_nro)
|
| 152 |
+
|
| 153 |
+
self.assertEqual(wsfev1.CAE, cae)
|
| 154 |
+
self.assertEqual(wsfev1.CbteNro, cbte_nro)
|
| 155 |
+
self.assertEqual(wsfev1.ImpTotal, imp_total)
|
| 156 |
+
|
| 157 |
+
wsfev1.AnalizarXml("XmlResponse")
|
| 158 |
+
self.assertEqual(wsfev1.ObtenerTagXml('CodAutorizacion'), str(wsfev1.CAE))
|
| 159 |
+
self.assertEqual(wsfev1.ObtenerTagXml('Concepto'), str(concepto))
|
| 160 |
+
|
| 161 |
+
def test_reproceso_servicios(self):
|
| 162 |
+
"Prueba de reproceso de un comprobante (recupero de CAE por consulta)"
|
| 163 |
+
wsfev1 = self.wsfev1
|
| 164 |
+
# obtengo el prοΏ½ximo nοΏ½mero de comprobante
|
| 165 |
+
tipo_cbte = 1
|
| 166 |
+
punto_vta = 4000
|
| 167 |
+
nro = wsfev1.CompUltimoAutorizado(tipo_cbte, punto_vta)
|
| 168 |
+
cbte_nro = int(nro) + 1
|
| 169 |
+
# obtengo CAE
|
| 170 |
+
wsfev1.Reprocesar = True
|
| 171 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro)
|
| 172 |
+
self.assertEqual(wsfev1.Reproceso, "")
|
| 173 |
+
# intento reprocesar:
|
| 174 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro)
|
| 175 |
+
self.assertEqual(wsfev1.Reproceso, "S")
|
| 176 |
+
|
| 177 |
+
def test_reproceso_productos(self):
|
| 178 |
+
"Prueba de reproceso de un comprobante (recupero de CAE por consulta)"
|
| 179 |
+
wsfev1 = self.wsfev1
|
| 180 |
+
# obtengo el prοΏ½ximo nοΏ½mero de comprobante
|
| 181 |
+
tipo_cbte = 1
|
| 182 |
+
punto_vta = 4000
|
| 183 |
+
nro = wsfev1.CompUltimoAutorizado(tipo_cbte, punto_vta)
|
| 184 |
+
cbte_nro = int(nro) + 1
|
| 185 |
+
# obtengo CAE
|
| 186 |
+
wsfev1.Reprocesar = True
|
| 187 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, servicios=False)
|
| 188 |
+
self.assertEqual(wsfev1.Reproceso, "")
|
| 189 |
+
# intento reprocesar:
|
| 190 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, servicios=False)
|
| 191 |
+
self.assertEqual(wsfev1.Reproceso, "S")
|
| 192 |
+
|
| 193 |
+
def test_reproceso_nota_debito(self):
|
| 194 |
+
"Prueba de reproceso de un comprobante (recupero de CAE por consulta)"
|
| 195 |
+
# N/D con comprobantes asociados
|
| 196 |
+
wsfev1 = self.wsfev1
|
| 197 |
+
# obtengo el prοΏ½ximo nοΏ½mero de comprobante
|
| 198 |
+
tipo_cbte = 2
|
| 199 |
+
punto_vta = 4000
|
| 200 |
+
nro = wsfev1.CompUltimoAutorizado(tipo_cbte, punto_vta)
|
| 201 |
+
cbte_nro = int(nro) + 1
|
| 202 |
+
# obtengo CAE
|
| 203 |
+
wsfev1.Reprocesar = True
|
| 204 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, servicios=False)
|
| 205 |
+
self.assertEqual(wsfev1.Reproceso, "")
|
| 206 |
+
# intento reprocesar:
|
| 207 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, servicios=False)
|
| 208 |
+
self.assertEqual(wsfev1.Reproceso, "S")
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
if __name__ == '__main__':
|
| 212 |
+
unittest.main()
|
app/pyafipws/tests/wslpg.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
import pysimplesoap.client
|
| 14 |
+
from pyafipws.wslpg import WSLPG
|
| 15 |
+
from pyafipws.wsaa import WSAA
|
| 16 |
+
from pyafipws import utils
|
| 17 |
+
"Pruebas LiquidaciΓ³n Primaria ElectrΓ³nica de Granos web service WSLPG (AFIP)"
|
| 18 |
+
|
| 19 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 20 |
+
__copyright__ = "Copyright (C) 2013 Mariano Reingart"
|
| 21 |
+
__license__ = "GPL 3.0"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
import unittest
|
| 25 |
+
import sys
|
| 26 |
+
from decimal import Decimal
|
| 27 |
+
|
| 28 |
+
sys.path.append("/home/reingart") # TODO: proper packaging
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
print(pysimplesoap.client.__version__)
|
| 32 |
+
#assert pysimplesoap.client.__version__ >= "1.08c"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
WSDL = "https://fwshomo.afip.gov.ar/wslpg/LpgService?wsdl"
|
| 36 |
+
CUIT = 20267565393
|
| 37 |
+
CERT = "/home/reingart/pyafipws/reingart.crt"
|
| 38 |
+
PRIVATEKEY = "/home/reingart/pyafipws/reingart.key"
|
| 39 |
+
CACERT = "/home/reingart/pyafipws/afip_root_desa_ca.crt"
|
| 40 |
+
CACHE = "/home/reingart/pyafipws/cache"
|
| 41 |
+
|
| 42 |
+
# AutenticaciΓ³n:
|
| 43 |
+
wsaa = WSAA()
|
| 44 |
+
tra = wsaa.CreateTRA(service="wslpg")
|
| 45 |
+
cms = wsaa.SignTRA(tra, CERT, PRIVATEKEY)
|
| 46 |
+
wsaa.Conectar()
|
| 47 |
+
wsaa.LoginCMS(cms)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class TestIssues(unittest.TestCase):
|
| 51 |
+
|
| 52 |
+
def setUp(self):
|
| 53 |
+
sys.argv.append("--trace") # TODO: use logging
|
| 54 |
+
self.wslpg = wslpg = WSLPG()
|
| 55 |
+
wslpg.LanzarExcepciones = True
|
| 56 |
+
wslpg.Conectar(url=WSDL, cacert=None, cache=CACHE)
|
| 57 |
+
wslpg.Cuit = CUIT
|
| 58 |
+
wslpg.Token = wsaa.Token
|
| 59 |
+
wslpg.Sign = wsaa.Sign
|
| 60 |
+
|
| 61 |
+
def test_liquidacion(self):
|
| 62 |
+
"Prueba de autorizaciΓ³n (obtener COE) liquidaciΓ³n electrΓ³nica de granos"
|
| 63 |
+
wslpg = self.wslpg
|
| 64 |
+
pto_emision = 99
|
| 65 |
+
ok = wslpg.ConsultarUltNroOrden(pto_emision)
|
| 66 |
+
self.assertTrue(ok)
|
| 67 |
+
ok = wslpg.CrearLiquidacion(
|
| 68 |
+
pto_emision=pto_emision,
|
| 69 |
+
nro_orden=wslpg.NroOrden + 1,
|
| 70 |
+
cuit_comprador=wslpg.Cuit,
|
| 71 |
+
nro_act_comprador=29, nro_ing_bruto_comprador=wslpg.Cuit,
|
| 72 |
+
cod_tipo_operacion=1,
|
| 73 |
+
es_liquidacion_propia='N', es_canje='N',
|
| 74 |
+
cod_puerto=14, des_puerto_localidad="DETALLE PUERTO",
|
| 75 |
+
cod_grano=31,
|
| 76 |
+
cuit_vendedor=23000000019, nro_ing_bruto_vendedor=23000000019,
|
| 77 |
+
actua_corredor="N", liquida_corredor="N",
|
| 78 |
+
cuit_corredor=0,
|
| 79 |
+
comision_corredor=0, nro_ing_bruto_corredor=0,
|
| 80 |
+
fecha_precio_operacion="2013-02-07",
|
| 81 |
+
precio_ref_tn=2000,
|
| 82 |
+
cod_grado_ref="G1",
|
| 83 |
+
cod_grado_ent="FG",
|
| 84 |
+
factor_ent=98, val_grado_ent=1.02,
|
| 85 |
+
precio_flete_tn=10,
|
| 86 |
+
cont_proteico=20,
|
| 87 |
+
alic_iva_operacion=10.5,
|
| 88 |
+
campania_ppal=1213,
|
| 89 |
+
cod_localidad_procedencia=5544,
|
| 90 |
+
cod_prov_procedencia=12,
|
| 91 |
+
datos_adicionales="DATOS ADICIONALES",
|
| 92 |
+
peso_neto_sin_certificado=10000,
|
| 93 |
+
cod_prov_procedencia_sin_certificado=1,
|
| 94 |
+
cod_localidad_procedencia_sin_certificado=15124,
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
wslpg.AgregarRetencion(
|
| 98 |
+
codigo_concepto="RI",
|
| 99 |
+
detalle_aclaratorio="DETALLE DE IVA",
|
| 100 |
+
base_calculo=1000,
|
| 101 |
+
alicuota=10.5,
|
| 102 |
+
)
|
| 103 |
+
wslpg.AgregarRetencion(
|
| 104 |
+
codigo_concepto="RG",
|
| 105 |
+
detalle_aclaratorio="DETALLE DE GANANCIAS",
|
| 106 |
+
base_calculo=100,
|
| 107 |
+
alicuota=15,
|
| 108 |
+
)
|
| 109 |
+
ok = wslpg.AutorizarLiquidacion()
|
| 110 |
+
self.assertTrue(ok)
|
| 111 |
+
self.assertIsInstance(wslpg.COE, str)
|
| 112 |
+
self.assertEqual(len(wslpg.COE), len("330100013142"))
|
| 113 |
+
|
| 114 |
+
def test_liquidacion_contrato(self, nro_contrato=26):
|
| 115 |
+
"Prueba de obtener COE variante con contrato / corredor (WSLPGv1.4)"
|
| 116 |
+
wslpg = self.wslpg
|
| 117 |
+
pto_emision = 99
|
| 118 |
+
ok = wslpg.ConsultarUltNroOrden(pto_emision)
|
| 119 |
+
self.assertTrue(ok)
|
| 120 |
+
nro_orden = wslpg.NroOrden + 1
|
| 121 |
+
|
| 122 |
+
# probar todas las actividades en caso de que devuelva error AFIP:
|
| 123 |
+
# 1106: La actividad seleccionada no corresponde al comprador
|
| 124 |
+
actividades = (40, 41, 29, 33, 31, 30, 35, 44, 47, 46, 48, 49, 51, 50,
|
| 125 |
+
45, 59, 57, 52, 34, 28, 36, 55, 39, 37)
|
| 126 |
+
|
| 127 |
+
for actid in actividades:
|
| 128 |
+
ok = wslpg.CrearLiquidacion(
|
| 129 |
+
pto_emision=pto_emision,
|
| 130 |
+
nro_orden=nro_orden,
|
| 131 |
+
nro_contrato=nro_contrato,
|
| 132 |
+
cuit_comprador=20400000000,
|
| 133 |
+
nro_act_comprador=actid, nro_ing_bruto_comprador=20400000000,
|
| 134 |
+
cod_tipo_operacion=1,
|
| 135 |
+
es_liquidacion_propia='N', es_canje='N',
|
| 136 |
+
cod_puerto=14, des_puerto_localidad="DETALLE PUERTO",
|
| 137 |
+
cod_grano=31,
|
| 138 |
+
cuit_vendedor=23000000019, nro_ing_bruto_vendedor=23000000019,
|
| 139 |
+
actua_corredor="S", liquida_corredor="S",
|
| 140 |
+
cuit_corredor=20267565393,
|
| 141 |
+
comision_corredor=1, nro_ing_bruto_corredor=20267565393,
|
| 142 |
+
fecha_precio_operacion="2013-02-07",
|
| 143 |
+
precio_ref_tn=2000,
|
| 144 |
+
cod_grado_ref="G1",
|
| 145 |
+
cod_grado_ent="FG",
|
| 146 |
+
factor_ent=98, val_grado_ent=1.02,
|
| 147 |
+
precio_flete_tn=10,
|
| 148 |
+
cont_proteico=20,
|
| 149 |
+
alic_iva_operacion=10.5,
|
| 150 |
+
campania_ppal=1213,
|
| 151 |
+
cod_localidad_procedencia=5544,
|
| 152 |
+
cod_prov_procedencia=12,
|
| 153 |
+
datos_adicionales="DATOS ADICIONALES",
|
| 154 |
+
peso_neto_sin_certificado=10000,
|
| 155 |
+
cod_prov_procedencia_sin_certificado=1,
|
| 156 |
+
cod_localidad_procedencia_sin_certificado=15124,
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
wslpg.AgregarRetencion(
|
| 160 |
+
codigo_concepto="RI",
|
| 161 |
+
detalle_aclaratorio="DETALLE DE IVA",
|
| 162 |
+
base_calculo=1000,
|
| 163 |
+
alicuota=10.5,
|
| 164 |
+
)
|
| 165 |
+
wslpg.AgregarRetencion(
|
| 166 |
+
codigo_concepto="RG",
|
| 167 |
+
detalle_aclaratorio="DETALLE DE GANANCIAS",
|
| 168 |
+
base_calculo=100,
|
| 169 |
+
alicuota=15,
|
| 170 |
+
)
|
| 171 |
+
ok = wslpg.AutorizarLiquidacion()
|
| 172 |
+
if wslpg.COE:
|
| 173 |
+
# print "Actividad OK", actid
|
| 174 |
+
break
|
| 175 |
+
|
| 176 |
+
self.assertTrue(ok)
|
| 177 |
+
self.assertIsInstance(wslpg.COE, str)
|
| 178 |
+
self.assertEqual(len(wslpg.COE), len("330100013142"))
|
| 179 |
+
self.assertEqual(wslpg.NroContrato, nro_contrato)
|
| 180 |
+
|
| 181 |
+
def test_anular(self, coe=None):
|
| 182 |
+
"Prueba de anulaciΓ³n de una liquidaciΓ³n electrΓ³nica de granos"
|
| 183 |
+
wslpg = self.wslpg
|
| 184 |
+
if not coe:
|
| 185 |
+
self.test_liquidacion() # autorizo una nueva liq.
|
| 186 |
+
coe = wslpg.COE
|
| 187 |
+
ok = wslpg.AnularLiquidacion(coe) # la anulo
|
| 188 |
+
self.assertTrue(ok)
|
| 189 |
+
self.assertEqual(wslpg.Resultado, "A")
|
| 190 |
+
|
| 191 |
+
def test_ajuste_unificado(self):
|
| 192 |
+
"Prueba de ajuste unificado de una liquidaciΓ³n de granos (WSLPGv1.4)"
|
| 193 |
+
wslpg = self.wslpg
|
| 194 |
+
# solicito una liquidaciΓ³n para tener el COE autorizado a ajustar:
|
| 195 |
+
self.test_liquidacion()
|
| 196 |
+
coe = wslpg.COE
|
| 197 |
+
# solicito el ΓΊltimo nro de orden para la nueva liquidaciΓ³n de ajuste:
|
| 198 |
+
pto_emision = 55
|
| 199 |
+
ok = wslpg.ConsultarUltNroOrden(pto_emision)
|
| 200 |
+
self.assertTrue(ok)
|
| 201 |
+
nro_orden = wslpg.NroOrden + 1
|
| 202 |
+
# creo el ajuste base y agrego los datos de certificado:
|
| 203 |
+
wslpg.CrearAjusteBase(pto_emision=pto_emision,
|
| 204 |
+
nro_orden=nro_orden,
|
| 205 |
+
coe_ajustado=coe,
|
| 206 |
+
cod_provincia=1,
|
| 207 |
+
cod_localidad=5,
|
| 208 |
+
)
|
| 209 |
+
wslpg.AgregarCertificado(tipo_certificado_deposito=5,
|
| 210 |
+
nro_certificado_deposito=555501200729,
|
| 211 |
+
peso_neto=10000,
|
| 212 |
+
cod_localidad_procedencia=3,
|
| 213 |
+
cod_prov_procedencia=1,
|
| 214 |
+
campania=1213,
|
| 215 |
+
fecha_cierre='2013-01-13',
|
| 216 |
+
peso_neto_total_certificado=10000)
|
| 217 |
+
# creo el ajuste de crΓ©dito (ver documentaciΓ³n AFIP)
|
| 218 |
+
wslpg.CrearAjusteCredito(
|
| 219 |
+
diferencia_peso_neto=1000, diferencia_precio_operacion=100,
|
| 220 |
+
cod_grado="G2", val_grado=1.0, factor=100,
|
| 221 |
+
diferencia_precio_flete_tn=10,
|
| 222 |
+
datos_adicionales='AJUSTE CRED UNIF',
|
| 223 |
+
concepto_importe_iva_0='Alicuota Cero',
|
| 224 |
+
importe_ajustar_Iva_0=900,
|
| 225 |
+
concepto_importe_iva_105='Alicuota Diez',
|
| 226 |
+
importe_ajustar_Iva_105=800,
|
| 227 |
+
concepto_importe_iva_21='Alicuota Veintiuno',
|
| 228 |
+
importe_ajustar_Iva_21=700,
|
| 229 |
+
)
|
| 230 |
+
wslpg.AgregarDeduccion(codigo_concepto="AL",
|
| 231 |
+
detalle_aclaratorio="Deduc Alm",
|
| 232 |
+
dias_almacenaje="1",
|
| 233 |
+
precio_pkg_diario=0.01,
|
| 234 |
+
comision_gastos_adm=1.0,
|
| 235 |
+
base_calculo=1000.0,
|
| 236 |
+
alicuota=10.5, )
|
| 237 |
+
wslpg.AgregarRetencion(codigo_concepto="RI",
|
| 238 |
+
detalle_aclaratorio="Ret IVA",
|
| 239 |
+
base_calculo=1000,
|
| 240 |
+
alicuota=10.5, )
|
| 241 |
+
# creo el ajuste de dΓ©bito (ver documentaciΓ³n AFIP)
|
| 242 |
+
wslpg.CrearAjusteDebito(
|
| 243 |
+
diferencia_peso_neto=500, diferencia_precio_operacion=100,
|
| 244 |
+
cod_grado="G2", val_grado=1.0, factor=100,
|
| 245 |
+
diferencia_precio_flete_tn=0.01,
|
| 246 |
+
datos_adicionales='AJUSTE DEB UNIF',
|
| 247 |
+
concepto_importe_iva_0='Alic 0',
|
| 248 |
+
importe_ajustar_Iva_0=250,
|
| 249 |
+
concepto_importe_iva_105='Alic 10.5',
|
| 250 |
+
importe_ajustar_Iva_105=200,
|
| 251 |
+
concepto_importe_iva_21='Alicuota 21',
|
| 252 |
+
importe_ajustar_Iva_21=50,
|
| 253 |
+
)
|
| 254 |
+
wslpg.AgregarDeduccion(codigo_concepto="AL",
|
| 255 |
+
detalle_aclaratorio="Deduc Alm",
|
| 256 |
+
dias_almacenaje="1",
|
| 257 |
+
precio_pkg_diario=0.01,
|
| 258 |
+
comision_gastos_adm=1.0,
|
| 259 |
+
base_calculo=500.0,
|
| 260 |
+
alicuota=10.5, )
|
| 261 |
+
wslpg.AgregarRetencion(codigo_concepto="RI",
|
| 262 |
+
detalle_aclaratorio="Ret IVA",
|
| 263 |
+
base_calculo=100,
|
| 264 |
+
alicuota=10.5, )
|
| 265 |
+
# autorizo el ajuste:
|
| 266 |
+
ok = wslpg.AjustarLiquidacionUnificado()
|
| 267 |
+
self.assertTrue(ok)
|
| 268 |
+
# verificar respuesta general:
|
| 269 |
+
self.assertIsInstance(wslpg.COE, str)
|
| 270 |
+
self.assertEqual(len(wslpg.COE), len("330100013133"))
|
| 271 |
+
coe_ajustado = coe
|
| 272 |
+
coe = wslpg.COE
|
| 273 |
+
try:
|
| 274 |
+
self.assertEqual(wslpg.Estado, "AC")
|
| 275 |
+
self.assertEqual(wslpg.Subtotal, Decimal("-734.10"))
|
| 276 |
+
self.assertEqual(wslpg.TotalIva105, Decimal("-77.61"))
|
| 277 |
+
self.assertEqual(wslpg.TotalIva21, Decimal("0"))
|
| 278 |
+
self.assertEqual(wslpg.TotalRetencionesGanancias, Decimal("0"))
|
| 279 |
+
self.assertEqual(wslpg.TotalRetencionesIVA, Decimal("-94.50"))
|
| 280 |
+
self.assertEqual(wslpg.TotalNetoAPagar, Decimal("-716.68"))
|
| 281 |
+
self.assertEqual(wslpg.TotalIvaRg2300_07, Decimal("16.89"))
|
| 282 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("-733.57"))
|
| 283 |
+
# verificar ajuste credito
|
| 284 |
+
ok = wslpg.AnalizarAjusteCredito()
|
| 285 |
+
self.assertTrue(ok)
|
| 286 |
+
self.assertEqual(wslpg.GetParametro("precio_operacion"), "1.900")
|
| 287 |
+
self.assertEqual(wslpg.GetParametro("total_peso_neto"), "1000")
|
| 288 |
+
self.assertEqual(wslpg.TotalDeduccion, Decimal("11.05"))
|
| 289 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("2780.95"))
|
| 290 |
+
self.assertEqual(wslpg.GetParametro("importe_iva"), "293.16")
|
| 291 |
+
self.assertEqual(wslpg.GetParametro("operacion_con_iva"), "3085.16")
|
| 292 |
+
self.assertEqual(wslpg.GetParametro("deducciones", 0, "importe_iva"), "1.05")
|
| 293 |
+
# verificar ajuste debito
|
| 294 |
+
ok = wslpg.AnalizarAjusteDebito()
|
| 295 |
+
self.assertTrue(ok)
|
| 296 |
+
self.assertEqual(wslpg.GetParametro("precio_operacion"), "2.090")
|
| 297 |
+
self.assertEqual(wslpg.GetParametro("total_peso_neto"), "500")
|
| 298 |
+
self.assertEqual(wslpg.TotalDeduccion, Decimal("5.52"))
|
| 299 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("2047.38"))
|
| 300 |
+
self.assertEqual(wslpg.GetParametro("importe_iva"), "215.55")
|
| 301 |
+
self.assertEqual(wslpg.GetParametro("operacion_con_iva"), "2268.45")
|
| 302 |
+
self.assertEqual(wslpg.GetParametro("retenciones", 0, "importe_retencion"), "10.50")
|
| 303 |
+
|
| 304 |
+
finally:
|
| 305 |
+
# anulo el ajuste para evitar subsiguiente validaciΓ³n AFIP:
|
| 306 |
+
if coe:
|
| 307 |
+
self.test_anular(coe)
|
| 308 |
+
if coe_ajustado:
|
| 309 |
+
self.test_anular(coe_ajustado) # anulo tambiΓ©n la liq. orig.
|
| 310 |
+
|
| 311 |
+
def test_ajuste_contrato(self, nro_contrato=27):
|
| 312 |
+
"Prueba de ajuste por contrato de una liquidaciΓ³n de granos (WSLPGv1.4)"
|
| 313 |
+
wslpg = self.wslpg
|
| 314 |
+
# solicito una liquidaciΓ³n para tener el COE autorizado a ajustar:
|
| 315 |
+
self.test_liquidacion_contrato(nro_contrato)
|
| 316 |
+
coe_ajustado = wslpg.COE
|
| 317 |
+
# solicito el ΓΊltimo nro de orden para la nueva liquidaciΓ³n de ajuste:
|
| 318 |
+
pto_emision = 55
|
| 319 |
+
ok = wslpg.ConsultarUltNroOrden(pto_emision)
|
| 320 |
+
self.assertTrue(ok)
|
| 321 |
+
nro_orden = wslpg.NroOrden + 1
|
| 322 |
+
wslpg.CrearAjusteBase(pto_emision=55, nro_orden=nro_orden,
|
| 323 |
+
nro_contrato=nro_contrato,
|
| 324 |
+
coe_ajustado=coe_ajustado,
|
| 325 |
+
nro_act_comprador=40,
|
| 326 |
+
cod_grano=31,
|
| 327 |
+
cuit_vendedor=23000000019,
|
| 328 |
+
cuit_comprador=20400000000,
|
| 329 |
+
cuit_corredor=20267565393,
|
| 330 |
+
precio_ref_tn=100,
|
| 331 |
+
cod_grado_ent="G1",
|
| 332 |
+
val_grado_ent=1.01,
|
| 333 |
+
precio_flete_tn=1000,
|
| 334 |
+
cod_puerto=14,
|
| 335 |
+
des_puerto_localidad="Desc Puerto",
|
| 336 |
+
cod_provincia=1,
|
| 337 |
+
cod_localidad=5,
|
| 338 |
+
)
|
| 339 |
+
wslpg.CrearAjusteCredito(
|
| 340 |
+
concepto_importe_iva_0='Ajuste IVA al 0%',
|
| 341 |
+
importe_ajustar_Iva_0=100,
|
| 342 |
+
)
|
| 343 |
+
wslpg.CrearAjusteDebito(
|
| 344 |
+
concepto_importe_iva_105='Ajuste IVA al 10.5%',
|
| 345 |
+
importe_ajustar_Iva_105=100,
|
| 346 |
+
)
|
| 347 |
+
wslpg.AgregarDeduccion(codigo_concepto="OD",
|
| 348 |
+
detalle_aclaratorio="Otras Deduc",
|
| 349 |
+
dias_almacenaje="1",
|
| 350 |
+
base_calculo=100.0,
|
| 351 |
+
alicuota=10.5, )
|
| 352 |
+
|
| 353 |
+
# autorizo el ajuste:
|
| 354 |
+
ok = wslpg.AjustarLiquidacionContrato()
|
| 355 |
+
self.assertTrue(ok)
|
| 356 |
+
# verificar respuesta general:
|
| 357 |
+
coe = wslpg.COE
|
| 358 |
+
self.assertIsInstance(wslpg.COE, str)
|
| 359 |
+
self.assertEqual(len(wslpg.COE), len("330100013133"))
|
| 360 |
+
try:
|
| 361 |
+
self.assertEqual(wslpg.Estado, "AC")
|
| 362 |
+
self.assertEqual(wslpg.Subtotal, Decimal("-100.00"))
|
| 363 |
+
self.assertEqual(wslpg.TotalIva105, Decimal("0"))
|
| 364 |
+
self.assertEqual(wslpg.TotalIva21, Decimal("0"))
|
| 365 |
+
self.assertEqual(wslpg.TotalRetencionesGanancias, Decimal("0"))
|
| 366 |
+
self.assertEqual(wslpg.TotalRetencionesIVA, Decimal("0"))
|
| 367 |
+
self.assertEqual(wslpg.TotalNetoAPagar, Decimal("-110.50"))
|
| 368 |
+
self.assertEqual(wslpg.TotalIvaRg2300_07, Decimal("0"))
|
| 369 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("-110.50"))
|
| 370 |
+
# self.assertEqual(wslpg.NroContrato, nro_contrato) # no devuelto AFIP
|
| 371 |
+
# verificar campos globales no documentados (directamente desde el XML):
|
| 372 |
+
wslpg.AnalizarXml()
|
| 373 |
+
v = wslpg.ObtenerTagXml("totalesUnificados", "subTotalDebCred")
|
| 374 |
+
self.assertEqual(v, "0")
|
| 375 |
+
v = wslpg.ObtenerTagXml("totalesUnificados", "totalBaseDeducciones")
|
| 376 |
+
self.assertEqual(v, "100.0")
|
| 377 |
+
v = wslpg.ObtenerTagXml("totalesUnificados", "ivaDeducciones")
|
| 378 |
+
self.assertEqual(v, "10.50")
|
| 379 |
+
# verificar ajuste credito
|
| 380 |
+
ok = wslpg.AnalizarAjusteCredito()
|
| 381 |
+
self.assertTrue(ok)
|
| 382 |
+
self.assertEqual(wslpg.GetParametro("precio_operacion"), "0.000")
|
| 383 |
+
self.assertEqual(wslpg.GetParametro("total_peso_neto"), "0")
|
| 384 |
+
self.assertEqual(wslpg.TotalDeduccion, Decimal("0.000"))
|
| 385 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("0.000"))
|
| 386 |
+
self.assertEqual(float(wslpg.GetParametro("importe_iva")), 0.00)
|
| 387 |
+
self.assertEqual(float(wslpg.GetParametro("operacion_con_iva")), 0.00)
|
| 388 |
+
# verificar ajuste debito
|
| 389 |
+
ok = wslpg.AnalizarAjusteDebito()
|
| 390 |
+
self.assertTrue(ok)
|
| 391 |
+
self.assertEqual(float(wslpg.GetParametro("precio_operacion")), 0.00)
|
| 392 |
+
self.assertEqual(float(wslpg.GetParametro("total_peso_neto")), 0)
|
| 393 |
+
self.assertEqual(wslpg.TotalDeduccion, Decimal("110.50"))
|
| 394 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("-110.50"))
|
| 395 |
+
self.assertEqual(float(wslpg.GetParametro("importe_iva")), 0.00)
|
| 396 |
+
self.assertEqual(float(wslpg.GetParametro("operacion_con_iva")), 0.00)
|
| 397 |
+
self.assertEqual(float(wslpg.GetParametro("deducciones", 0, "importe_iva")), 10.50)
|
| 398 |
+
self.assertEqual(float(wslpg.GetParametro("deducciones", 0, "importe_deduccion")), 110.50)
|
| 399 |
+
|
| 400 |
+
finally:
|
| 401 |
+
# anulo el ajuste para evitar subsiguiente validaciΓ³n AFIP:
|
| 402 |
+
# 2105: No puede relacionar la liquidacion con el contrato, porque el contrato tiene un Ajuste realizado.
|
| 403 |
+
# 2106: No puede ajustar el contrato, porque tiene liquidaciones relacionadas con ajuste.
|
| 404 |
+
# anular primero el ajuste para evitar la validaciΓ³n de AFIP:
|
| 405 |
+
# 2108: No puede anular la liquidaciΓ³n porque estΓ‘ relacionada a un contrato con ajuste vigente.
|
| 406 |
+
if coe:
|
| 407 |
+
self.test_anular(coe)
|
| 408 |
+
if coe_ajustado:
|
| 409 |
+
self.test_anular(coe_ajustado) # anulo tambiΓ©n el COE ajustado
|
| 410 |
+
|
| 411 |
+
def atest_ajuste_papel(self):
|
| 412 |
+
# deshabilitado ya que el mΓ©todo esta "en estudio" por parte de AFIP
|
| 413 |
+
wslpg = self.wslpg
|
| 414 |
+
wslpg.CrearAjusteBase(pto_emision=50,
|
| 415 |
+
nro_orden=1,
|
| 416 |
+
tipo_formulario=6,
|
| 417 |
+
nro_formulario="000101800999",
|
| 418 |
+
actividad=46,
|
| 419 |
+
cuit_comprador=99999999999,
|
| 420 |
+
nro_ing_bruto_comprador=99999999999,
|
| 421 |
+
tipo_operacion=1,
|
| 422 |
+
cod_grano=31,
|
| 423 |
+
cuit_vendedor=30000000007,
|
| 424 |
+
nro_ing_bruto_vendedor=30000000007,
|
| 425 |
+
cod_provincia=1,
|
| 426 |
+
cod_localidad=5)
|
| 427 |
+
wslpg.AgregarCertificado(tipo_certificado_deposito=5,
|
| 428 |
+
nro_certificado_deposito=555501200802,
|
| 429 |
+
peso_neto=10000,
|
| 430 |
+
cod_localidad_procedencia=5,
|
| 431 |
+
cod_prov_procedencia=1,
|
| 432 |
+
campania=1213,
|
| 433 |
+
fecha_cierre='2013-07-12')
|
| 434 |
+
wslpg.CrearAjusteCredito(
|
| 435 |
+
concepto_importe_iva_21='IVA al 21%',
|
| 436 |
+
importe_ajustar_Iva_21=1500,
|
| 437 |
+
)
|
| 438 |
+
wslpg.AgregarRetencion(codigo_concepto="RI",
|
| 439 |
+
detalle_aclaratorio="Ret IVA",
|
| 440 |
+
base_calculo=1500,
|
| 441 |
+
alicuota=8, )
|
| 442 |
+
wslpg.CrearAjusteDebito(
|
| 443 |
+
concepto_importe_iva_105='IVA al 0%',
|
| 444 |
+
importe_ajustar_Iva_105=100,
|
| 445 |
+
)
|
| 446 |
+
|
| 447 |
+
ret = wslpg.AjustarLiquidacionUnificadoPapel()
|
| 448 |
+
|
| 449 |
+
def test_asociaciar_coe_contrato(self, nro_contrato=27):
|
| 450 |
+
wslpg = self.wslpg
|
| 451 |
+
# solicito una liquidaciΓ³n para tener el COE autorizado a asociar:
|
| 452 |
+
self.test_liquidacion()
|
| 453 |
+
coe = wslpg.COE
|
| 454 |
+
try:
|
| 455 |
+
# Asocio la liquidaciΓ³n con el contrato:
|
| 456 |
+
wslpg.AsociarLiquidacionAContrato(coe=coe,
|
| 457 |
+
nro_contrato=nro_contrato,
|
| 458 |
+
cuit_comprador="20400000000",
|
| 459 |
+
cuit_vendedor="23000000019",
|
| 460 |
+
cuit_corredor="20267565393",
|
| 461 |
+
cod_grano=31)
|
| 462 |
+
self.assertEqual(wslpg.Errores, [])
|
| 463 |
+
self.assertIsInstance(wslpg.COE, str)
|
| 464 |
+
self.assertEqual(len(wslpg.COE), len("330100013133"))
|
| 465 |
+
self.assertEqual(wslpg.Estado, "AC")
|
| 466 |
+
finally:
|
| 467 |
+
# anulo el ajuste para evitar subsiguiente validaciΓ³n AFIP:
|
| 468 |
+
# 2105: No puede relacionar la liquidacion con el contrato, porque el contrato tiene un Ajuste realizado.
|
| 469 |
+
# 2112: La liquidacion ya esta relacionada al contrato.
|
| 470 |
+
try:
|
| 471 |
+
self.test_anular(coe)
|
| 472 |
+
except BaseException:
|
| 473 |
+
# ignorar error de AFIP (aparentemente problema interno):
|
| 474 |
+
self.assertEqual(wslpg.Errores[0], "2100: El contrato ingresado no se encuentra registrado.")
|
| 475 |
+
pass
|
| 476 |
+
|
| 477 |
+
def test_consultar_liquidaciones_por_contrato(self, nro_contrato=26):
|
| 478 |
+
wslpg = self.wslpg
|
| 479 |
+
# obtener las liquidaciones relacionadas al contrato:
|
| 480 |
+
wslpg.ConsultarLiquidacionesPorContrato(
|
| 481 |
+
nro_contrato=nro_contrato,
|
| 482 |
+
cuit_comprador="20400000000",
|
| 483 |
+
cuit_vendedor="23000000019",
|
| 484 |
+
cuit_corredor="20267565393",
|
| 485 |
+
cod_grano=31,
|
| 486 |
+
)
|
| 487 |
+
self.assertEqual(wslpg.Errores, [])
|
| 488 |
+
# verifico COEs previamente relacionados al contrato:
|
| 489 |
+
for coe in sorted([330100014020, 330100014022, 330100014023,
|
| 490 |
+
330100014025, 330100014028, 330100014029,
|
| 491 |
+
330100014040, 330100014043, 330100014057,
|
| 492 |
+
330100014061, 330100014450, 330100014454,
|
| 493 |
+
330100014455, 330100014459, 330100014467,
|
| 494 |
+
330100014472, 330100004664]):
|
| 495 |
+
self.assertIsInstance(wslpg.COE, str)
|
| 496 |
+
self.assertEqual(wslpg.COE, str(coe))
|
| 497 |
+
self.assertEqual(wslpg.Estado, "") # por el momento no lo devuelve
|
| 498 |
+
# leo el prΓ³ximo numero
|
| 499 |
+
wslpg.LeerDatosLiquidacion()
|
| 500 |
+
|
| 501 |
+
def test_consultar_ajuste_unificado(self):
|
| 502 |
+
"Prueba de consulta de un ajuste unificado (WSLPGv1.4)"
|
| 503 |
+
wslpg = self.wslpg
|
| 504 |
+
# uso datos de un ajuste generado con test_ajuste_unificado:
|
| 505 |
+
pto_emision = 55
|
| 506 |
+
nro_orden = 78
|
| 507 |
+
# consulto el ajuste:
|
| 508 |
+
ok = wslpg.ConsultarAjuste(pto_emision, nro_orden)
|
| 509 |
+
self.assertTrue(ok)
|
| 510 |
+
# verificar respuesta general:
|
| 511 |
+
self.assertEqual(wslpg.COE, "330100014501")
|
| 512 |
+
self.assertEqual(wslpg.Estado, "AN") # anulado!
|
| 513 |
+
self.assertEqual(wslpg.Subtotal, Decimal("-734.10"))
|
| 514 |
+
self.assertEqual(wslpg.TotalIva105, Decimal("-77.61"))
|
| 515 |
+
self.assertEqual(wslpg.TotalIva21, Decimal("0"))
|
| 516 |
+
self.assertEqual(wslpg.TotalRetencionesGanancias, Decimal("0"))
|
| 517 |
+
self.assertEqual(wslpg.TotalRetencionesIVA, Decimal("-94.50"))
|
| 518 |
+
self.assertEqual(wslpg.TotalNetoAPagar, Decimal("-716.68"))
|
| 519 |
+
self.assertEqual(wslpg.TotalIvaRg2300_07, Decimal("16.89"))
|
| 520 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("-733.57"))
|
| 521 |
+
# verificar ajuste credito
|
| 522 |
+
ok = wslpg.AnalizarAjusteCredito()
|
| 523 |
+
self.assertTrue(ok)
|
| 524 |
+
self.assertEqual(float(wslpg.GetParametro("precio_operacion")), 1.9)
|
| 525 |
+
self.assertEqual(wslpg.GetParametro("total_peso_neto"), "1000")
|
| 526 |
+
self.assertEqual(wslpg.TotalDeduccion, Decimal("11.05"))
|
| 527 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("2780.95"))
|
| 528 |
+
self.assertEqual(float(wslpg.GetParametro("importe_iva")), 293.16)
|
| 529 |
+
self.assertEqual(float(wslpg.GetParametro("operacion_con_iva")), 3085.16)
|
| 530 |
+
self.assertEqual(float(wslpg.GetParametro("deducciones", 0, "importe_iva")), 1.05)
|
| 531 |
+
# verificar ajuste debito
|
| 532 |
+
ok = wslpg.AnalizarAjusteDebito()
|
| 533 |
+
self.assertTrue(ok)
|
| 534 |
+
self.assertEqual(float(wslpg.GetParametro("precio_operacion")), 2.09)
|
| 535 |
+
self.assertEqual(wslpg.GetParametro("total_peso_neto"), "500")
|
| 536 |
+
self.assertEqual(wslpg.TotalDeduccion, Decimal("5.52"))
|
| 537 |
+
self.assertEqual(wslpg.TotalPagoSegunCondicion, Decimal("2047.38"))
|
| 538 |
+
self.assertEqual(float(wslpg.GetParametro("importe_iva")), 215.55)
|
| 539 |
+
self.assertEqual(float(wslpg.GetParametro("operacion_con_iva")), 2268.45)
|
| 540 |
+
self.assertEqual(float(wslpg.GetParametro("retenciones", 0, "importe_retencion")), 10.50)
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
if __name__ == '__main__':
|
| 544 |
+
unittest.main()
|
app/pyafipws/tests/wslpg_cert_autorizar_resp.xml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version='1.0' encoding='UTF-8'?>
|
| 2 |
+
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
|
| 3 |
+
<S:Body>
|
| 4 |
+
<ns2:cgAutorizarResp xmlns:ns2="http://serviciosjava.afip.gob.ar/wslpg/">
|
| 5 |
+
<oReturn>
|
| 6 |
+
<autorizacion>
|
| 7 |
+
<ptoEmision>21</ptoEmision>
|
| 8 |
+
<nroOrden>2</nroOrden>
|
| 9 |
+
<coe>332000000189</coe>
|
| 10 |
+
<estado>AC</estado>
|
| 11 |
+
<fechaCertificacion>2015-02-24</fechaCertificacion>
|
| 12 |
+
<pesosResumen>
|
| 13 |
+
<pesoBrutoCertificado>1500.00</pesoBrutoCertificado>
|
| 14 |
+
<pesoMermaVolatil>0.00</pesoMermaVolatil>
|
| 15 |
+
<pesoMermaSecado>0.00</pesoMermaSecado>
|
| 16 |
+
<pesoMermaZarandeo>0.00</pesoMermaZarandeo>
|
| 17 |
+
<pesoNetoCertificado>1500.00</pesoNetoCertificado>
|
| 18 |
+
</pesosResumen>
|
| 19 |
+
<serviciosResumen>
|
| 20 |
+
<servicioSecado>0.00</servicioSecado>
|
| 21 |
+
<servicioZarandeo>0.00</servicioZarandeo>
|
| 22 |
+
<servicioOtros>0.00</servicioOtros>
|
| 23 |
+
<servicioGastosGenerales>0.00</servicioGastosGenerales>
|
| 24 |
+
<importeIVA>0.00</importeIVA>
|
| 25 |
+
<servicioTotal>0.00</servicioTotal>
|
| 26 |
+
</serviciosResumen>
|
| 27 |
+
</autorizacion>
|
| 28 |
+
</oReturn>
|
| 29 |
+
</ns2:cgAutorizarResp>
|
| 30 |
+
</S:Body>
|
| 31 |
+
</S:Envelope>
|
app/pyafipws/tests/wsmtx.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
from pyafipws.wsaa import WSAA
|
| 14 |
+
from pyafipws.wsmtx import WSMTXCA
|
| 15 |
+
"Pruebas para WSMTX de AFIP (Factura ElectrοΏ½nica Mercado Interno con detalle)"
|
| 16 |
+
|
| 17 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 18 |
+
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
|
| 19 |
+
__license__ = "GPL 3.0"
|
| 20 |
+
|
| 21 |
+
import unittest
|
| 22 |
+
import os
|
| 23 |
+
import time
|
| 24 |
+
import sys
|
| 25 |
+
from decimal import Decimal
|
| 26 |
+
import datetime
|
| 27 |
+
|
| 28 |
+
sys.path.append("/home/reingart") # TODO: proper packaging
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
WSDL = "https://fwshomo.afip.gov.ar/wsmtxca/services/MTXCAService?wsdl"
|
| 32 |
+
CUIT = 20267565393
|
| 33 |
+
CERT = "/home/reingart/pyafipws/reingart.crt"
|
| 34 |
+
PRIVATEKEY = "/home/reingart/pyafipws/reingart.key"
|
| 35 |
+
CACERT = "/home/reingart/pyafipws/afip_root_desa_ca.crt"
|
| 36 |
+
CACHE = "/home/reingart/pyafipws/cache"
|
| 37 |
+
|
| 38 |
+
# AutenticaciοΏ½n:
|
| 39 |
+
wsaa = WSAA()
|
| 40 |
+
tra = wsaa.CreateTRA(service="wsmtxca")
|
| 41 |
+
cms = wsaa.SignTRA(tra, CERT, PRIVATEKEY)
|
| 42 |
+
wsaa.Conectar()
|
| 43 |
+
wsaa.LoginCMS(cms)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class TestMTX(unittest.TestCase):
|
| 47 |
+
|
| 48 |
+
def setUp(self):
|
| 49 |
+
sys.argv.append("--trace") # TODO: use logging
|
| 50 |
+
self.wsmtxca = wsmtxca = WSMTXCA()
|
| 51 |
+
wsmtxca.Cuit = CUIT
|
| 52 |
+
wsmtxca.Token = wsaa.Token
|
| 53 |
+
wsmtxca.Sign = wsaa.Sign
|
| 54 |
+
wsmtxca.Conectar(CACHE, WSDL)
|
| 55 |
+
|
| 56 |
+
def atest_dummy(self):
|
| 57 |
+
print(wsmtxca.client.help("dummy"))
|
| 58 |
+
wsmtxca.Dummy()
|
| 59 |
+
print("AppServerStatus", wsmtxca.AppServerStatus)
|
| 60 |
+
print("DbServerStatus", wsmtxca.DbServerStatus)
|
| 61 |
+
print("AuthServerStatus", wsmtxca.AuthServerStatus)
|
| 62 |
+
|
| 63 |
+
def test_autorizar_comprobante(self, tipo_cbte=1, cbte_nro=None, servicios=True, tributos=True):
|
| 64 |
+
"Prueba de autorizaciοΏ½n de un comprobante (obtenciοΏ½n de CAE)"
|
| 65 |
+
wsmtxca = self.wsmtxca
|
| 66 |
+
|
| 67 |
+
# datos generales del comprobante:
|
| 68 |
+
punto_vta = 4000
|
| 69 |
+
if not cbte_nro:
|
| 70 |
+
# si no me especifοΏ½can nro de comprobante, busco el prοΏ½ximo
|
| 71 |
+
cbte_nro = wsmtxca.ConsultarUltimoComprobanteAutorizado(tipo_cbte, punto_vta)
|
| 72 |
+
cbte_nro = int(cbte_nro) + 1
|
| 73 |
+
fecha = datetime.datetime.now().strftime("%Y-%m-%d")
|
| 74 |
+
tipo_doc = 80
|
| 75 |
+
nro_doc = "30000000007"
|
| 76 |
+
cbt_desde = cbte_nro
|
| 77 |
+
cbt_hasta = cbt_desde
|
| 78 |
+
imp_tot_conc = "0.00"
|
| 79 |
+
imp_neto = "100.00"
|
| 80 |
+
if tributos:
|
| 81 |
+
imp_total = "123.00"
|
| 82 |
+
imp_trib = "2.00"
|
| 83 |
+
else:
|
| 84 |
+
imp_total = "121.00"
|
| 85 |
+
imp_trib = "0.00"
|
| 86 |
+
imp_op_ex = "0.00"
|
| 87 |
+
imp_subtotal = "100.00"
|
| 88 |
+
fecha_cbte = fecha
|
| 89 |
+
# Fechas del perοΏ½odo del servicio facturado (solo si concepto = 1?)
|
| 90 |
+
if servicios:
|
| 91 |
+
concepto = 3
|
| 92 |
+
fecha_venc_pago = fecha
|
| 93 |
+
fecha_serv_desde = fecha
|
| 94 |
+
fecha_serv_hasta = fecha
|
| 95 |
+
else:
|
| 96 |
+
concepto = 1
|
| 97 |
+
fecha_venc_pago = fecha_serv_desde = fecha_serv_hasta = None
|
| 98 |
+
moneda_id = 'PES'
|
| 99 |
+
moneda_ctz = '1.000'
|
| 100 |
+
obs = "Observaciones Comerciales, libre"
|
| 101 |
+
|
| 102 |
+
wsmtxca.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
|
| 103 |
+
cbt_desde, cbt_hasta, imp_total, imp_tot_conc, imp_neto,
|
| 104 |
+
imp_subtotal, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago,
|
| 105 |
+
fecha_serv_desde, fecha_serv_hasta, # --
|
| 106 |
+
moneda_id, moneda_ctz, obs)
|
| 107 |
+
|
| 108 |
+
# agrego un comprobante asociado (solo notas de crοΏ½dito / dοΏ½bito)
|
| 109 |
+
if tipo_cbte in (2, 3):
|
| 110 |
+
tipo = 1
|
| 111 |
+
pv = 2
|
| 112 |
+
nro = 1234
|
| 113 |
+
wsmtxca.AgregarCmpAsoc(tipo, pv, nro)
|
| 114 |
+
|
| 115 |
+
if tributos:
|
| 116 |
+
# agrego otros tributos:
|
| 117 |
+
tributo_id = 99
|
| 118 |
+
desc = 'Impuesto Municipal Matanza'
|
| 119 |
+
base_imp = "100.00"
|
| 120 |
+
alic = "1.00"
|
| 121 |
+
importe = "1.00"
|
| 122 |
+
wsmtxca.AgregarTributo(tributo_id, desc, base_imp, alic, importe)
|
| 123 |
+
|
| 124 |
+
# agrego otros tributos:
|
| 125 |
+
tributo_id = 1
|
| 126 |
+
desc = 'Impuestos Internos'
|
| 127 |
+
base_imp = "100.00"
|
| 128 |
+
alic = "1.00"
|
| 129 |
+
importe = "1.00"
|
| 130 |
+
wsmtxca.AgregarTributo(tributo_id, desc, base_imp, alic, importe)
|
| 131 |
+
|
| 132 |
+
# agrego el subtotal por tasa de IVA:
|
| 133 |
+
iva_id = 5 # 21%
|
| 134 |
+
base_imp = "100.00"
|
| 135 |
+
importe = 21
|
| 136 |
+
wsmtxca.AgregarIva(iva_id, base_imp, importe)
|
| 137 |
+
|
| 138 |
+
# agrego un artοΏ½culo:
|
| 139 |
+
u_mtx = 123456
|
| 140 |
+
cod_mtx = 1234567890123
|
| 141 |
+
codigo = "P0001"
|
| 142 |
+
ds = "Descripcion del producto P0001"
|
| 143 |
+
qty = 2.00
|
| 144 |
+
umed = 7
|
| 145 |
+
precio = 100.00
|
| 146 |
+
bonif = 0.00
|
| 147 |
+
iva_id = 5
|
| 148 |
+
imp_iva = 42.00
|
| 149 |
+
imp_subtotal = 242.00
|
| 150 |
+
wsmtxca.AgregarItem(u_mtx, cod_mtx, codigo, ds, qty, umed, precio, bonif,
|
| 151 |
+
iva_id, imp_iva, imp_subtotal)
|
| 152 |
+
|
| 153 |
+
# agrego bonificaciοΏ½n general
|
| 154 |
+
wsmtxca.AgregarItem(None, None, None, 'bonificacion', 0, 99, 1, None,
|
| 155 |
+
5, -21, -121)
|
| 156 |
+
|
| 157 |
+
# llamo al websevice para obtener el CAE:
|
| 158 |
+
wsmtxca.AutorizarComprobante()
|
| 159 |
+
|
| 160 |
+
self.assertEqual(wsmtxca.Resultado, "A") # Aprobado!
|
| 161 |
+
self.assertIsInstance(wsmtxca.CAE, str)
|
| 162 |
+
self.assertEqual(len(wsmtxca.CAE), len("63363178822329"))
|
| 163 |
+
self.assertEqual(len(wsmtxca.Vencimiento), len("2013-09-07"))
|
| 164 |
+
|
| 165 |
+
cae = wsmtxca.CAE
|
| 166 |
+
|
| 167 |
+
# llamo al webservice para consultar y validar manualmente el CAE:
|
| 168 |
+
wsmtxca.ConsultarComprobante(tipo_cbte, punto_vta, cbte_nro)
|
| 169 |
+
|
| 170 |
+
self.assertEqual(wsmtxca.CAE, cae)
|
| 171 |
+
self.assertEqual(wsmtxca.CbteNro, cbte_nro)
|
| 172 |
+
self.assertEqual(wsmtxca.ImpTotal, imp_total)
|
| 173 |
+
|
| 174 |
+
wsmtxca.AnalizarXml("XmlResponse")
|
| 175 |
+
self.assertEqual(wsmtxca.ObtenerTagXml('codigoAutorizacion'), str(wsmtxca.CAE))
|
| 176 |
+
self.assertEqual(wsmtxca.ObtenerTagXml('codigoConcepto'), str(concepto))
|
| 177 |
+
self.assertEqual(wsmtxca.ObtenerTagXml('arrayItems', 0, 'item', 'unidadesMtx'), '123456')
|
| 178 |
+
|
| 179 |
+
def test_reproceso_servicios(self):
|
| 180 |
+
"Prueba de reproceso de un comprobante (recupero de CAE por consulta)"
|
| 181 |
+
wsmtxca = self.wsmtxca
|
| 182 |
+
# obtengo el prοΏ½ximo nοΏ½mero de comprobante
|
| 183 |
+
tipo_cbte = 1
|
| 184 |
+
punto_vta = 4000
|
| 185 |
+
nro = wsmtxca.ConsultarUltimoComprobanteAutorizado(tipo_cbte, punto_vta)
|
| 186 |
+
cbte_nro = int(nro) + 1
|
| 187 |
+
# obtengo CAE
|
| 188 |
+
wsmtxca.Reprocesar = True
|
| 189 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro)
|
| 190 |
+
self.assertEqual(wsmtxca.Reproceso, "")
|
| 191 |
+
# intento reprocesar:
|
| 192 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro)
|
| 193 |
+
self.assertEqual(wsmtxca.Reproceso, "S")
|
| 194 |
+
|
| 195 |
+
def test_reproceso_productos(self):
|
| 196 |
+
"Prueba de reproceso de un comprobante (recupero de CAE por consulta)"
|
| 197 |
+
wsmtxca = self.wsmtxca
|
| 198 |
+
# obtengo el prοΏ½ximo nοΏ½mero de comprobante
|
| 199 |
+
tipo_cbte = 1
|
| 200 |
+
punto_vta = 4000
|
| 201 |
+
nro = wsmtxca.ConsultarUltimoComprobanteAutorizado(tipo_cbte, punto_vta)
|
| 202 |
+
cbte_nro = int(nro) + 1
|
| 203 |
+
# obtengo CAE
|
| 204 |
+
wsmtxca.Reprocesar = True
|
| 205 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, servicios=False)
|
| 206 |
+
self.assertEqual(wsmtxca.Reproceso, "")
|
| 207 |
+
# intento reprocesar:
|
| 208 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, servicios=False)
|
| 209 |
+
self.assertEqual(wsmtxca.Reproceso, "S")
|
| 210 |
+
|
| 211 |
+
def test_reproceso_nota_debito(self):
|
| 212 |
+
"Prueba de reproceso de un comprobante (recupero de CAE por consulta)"
|
| 213 |
+
# N/D con comprobantes asociados
|
| 214 |
+
wsmtxca = self.wsmtxca
|
| 215 |
+
# obtengo el prοΏ½ximo nοΏ½mero de comprobante
|
| 216 |
+
tipo_cbte = 2
|
| 217 |
+
punto_vta = 4000
|
| 218 |
+
nro = wsmtxca.ConsultarUltimoComprobanteAutorizado(tipo_cbte, punto_vta)
|
| 219 |
+
cbte_nro = int(nro) + 1
|
| 220 |
+
# obtengo CAE
|
| 221 |
+
wsmtxca.Reprocesar = True
|
| 222 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, servicios=False)
|
| 223 |
+
self.assertEqual(wsmtxca.Reproceso, "")
|
| 224 |
+
# intento reprocesar:
|
| 225 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, servicios=False)
|
| 226 |
+
self.assertEqual(wsmtxca.Reproceso, "S")
|
| 227 |
+
|
| 228 |
+
def test_reproceso_sin_tributos(self):
|
| 229 |
+
"Prueba de reproceso de un comprobante (recupero de CAE por consulta)"
|
| 230 |
+
wsmtxca = self.wsmtxca
|
| 231 |
+
# obtengo el prοΏ½ximo nοΏ½mero de comprobante
|
| 232 |
+
tipo_cbte = 1
|
| 233 |
+
punto_vta = 4000
|
| 234 |
+
nro = wsmtxca.ConsultarUltimoComprobanteAutorizado(tipo_cbte, punto_vta)
|
| 235 |
+
cbte_nro = int(nro) + 1
|
| 236 |
+
# obtengo CAE (sin tributos)
|
| 237 |
+
wsmtxca.Reprocesar = True
|
| 238 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, tributos=False)
|
| 239 |
+
self.assertEqual(wsmtxca.Reproceso, "")
|
| 240 |
+
# intento reprocesar:
|
| 241 |
+
self.test_autorizar_comprobante(tipo_cbte, cbte_nro, tributos=False)
|
| 242 |
+
self.assertEqual(wsmtxca.Reproceso, "S")
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
if __name__ == '__main__':
|
| 246 |
+
unittest.main()
|
app/pyafipws/trazafito.py
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"MοΏ½dulo Trazabilidad de Productos Fitosanitarios SENASA ResoluciοΏ½n 369/2013"
|
| 14 |
+
|
| 15 |
+
# InformaciοΏ½n adicional y documentaciοΏ½n:
|
| 16 |
+
# http://www.sistemasagiles.com.ar/trac/wiki/TrazabilidadProductosFitosanitarios
|
| 17 |
+
|
| 18 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 19 |
+
__copyright__ = "Copyright (C) 2014 Mariano Reingart"
|
| 20 |
+
__license__ = "GPL 3.0+"
|
| 21 |
+
__version__ = "1.11d"
|
| 22 |
+
|
| 23 |
+
# http://senasa.servicios.pami.org.ar/
|
| 24 |
+
|
| 25 |
+
import os
|
| 26 |
+
import socket
|
| 27 |
+
import sys
|
| 28 |
+
import datetime
|
| 29 |
+
import time
|
| 30 |
+
import traceback
|
| 31 |
+
import pysimplesoap.client
|
| 32 |
+
from pysimplesoap.client import SoapClient, SoapFault, parse_proxy, \
|
| 33 |
+
set_http_wrapper
|
| 34 |
+
from pysimplesoap.simplexml import SimpleXMLElement
|
| 35 |
+
from io import StringIO
|
| 36 |
+
|
| 37 |
+
# importo funciones compartidas:
|
| 38 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, \
|
| 39 |
+
dar_nombre_campo_dbf, get_install_dir, BaseWS, \
|
| 40 |
+
inicializar_y_capturar_excepciones
|
| 41 |
+
|
| 42 |
+
HOMO = False
|
| 43 |
+
TYPELIB = False
|
| 44 |
+
|
| 45 |
+
WSDL = "https://servicios.pami.org.ar/trazaenagr.WebService?wsdl"
|
| 46 |
+
LOCATION = "https://servicios.pami.org.ar/trazaenagr.WebService"
|
| 47 |
+
|
| 48 |
+
# Formato de TransaccionSenasaDTO (SaveTransaccion)
|
| 49 |
+
TRANSACCION_DTO = [
|
| 50 |
+
('gln_origen', 13, A),
|
| 51 |
+
('gln_destino', 13, A),
|
| 52 |
+
('f_operacion', 10, A),
|
| 53 |
+
('f_elaboracion', 10, A),
|
| 54 |
+
('f_vto', 10, A),
|
| 55 |
+
('id_evento', 15, N),
|
| 56 |
+
('cod_producto', 14, A),
|
| 57 |
+
('n_cantidad', 30, N),
|
| 58 |
+
('n_serie', 20, A),
|
| 59 |
+
('n_lote', 50, A),
|
| 60 |
+
('n_cai', 15, A),
|
| 61 |
+
('n_cae', 15, A),
|
| 62 |
+
('id_motivo_destruccion', 5, A),
|
| 63 |
+
('n_manifiesto', 15, N),
|
| 64 |
+
('en_transporte', 1, A), # boolean
|
| 65 |
+
('n_remito', 15, A),
|
| 66 |
+
('motivo_devolucion', 100, A),
|
| 67 |
+
('observaciones', 1000, A),
|
| 68 |
+
('n_vale_compra', 15, A),
|
| 69 |
+
('apellidoNombres', 255, A),
|
| 70 |
+
('direccion', 200, A),
|
| 71 |
+
('numero', 6, N),
|
| 72 |
+
('localidad', 15, A),
|
| 73 |
+
('provincia', 15, A),
|
| 74 |
+
('n_postal', 8, A),
|
| 75 |
+
('cuit', 11, A),
|
| 76 |
+
('codigo_transaccion', 14, A),
|
| 77 |
+
]
|
| 78 |
+
|
| 79 |
+
# Formato para TransaccionSenasa (getTransacciones)
|
| 80 |
+
TRANSACCIONES = [
|
| 81 |
+
('id_transaccion_global', 15, N),
|
| 82 |
+
('id_transaccion', 15, N),
|
| 83 |
+
('f_transaccion', 10, A),
|
| 84 |
+
('f_operacion', 10, A),
|
| 85 |
+
('f_vencimiento', 10, A),
|
| 86 |
+
('f_elaboracion', 10, A),
|
| 87 |
+
('d_evento', 100, A),
|
| 88 |
+
('cantidad', 30, N),
|
| 89 |
+
('id_unidad', 15, N),
|
| 90 |
+
('d_unidad', 100, A),
|
| 91 |
+
('cod_producto', 14, A),
|
| 92 |
+
('id_unidad', 15, N),
|
| 93 |
+
('n_serie', 20, A),
|
| 94 |
+
('n_lote', 50, A),
|
| 95 |
+
('n_cai', 15, A),
|
| 96 |
+
('n_cae', 15, A),
|
| 97 |
+
('d_motivo_destruccion', 50, A),
|
| 98 |
+
('d_manifiesto', 15, A),
|
| 99 |
+
('en_transporte', 1, A),
|
| 100 |
+
('n_remito', 30, A),
|
| 101 |
+
('motivo_devolucion', 200, A),
|
| 102 |
+
('observaciones', 1000, A),
|
| 103 |
+
('n_vale_compra', 15, A),
|
| 104 |
+
('apellidoNombres', 255, A),
|
| 105 |
+
('direccion', 200, A),
|
| 106 |
+
('numero', 6, A),
|
| 107 |
+
('localidad', 250, A),
|
| 108 |
+
('provincia', 250, A),
|
| 109 |
+
('n_postal', 8, A),
|
| 110 |
+
('cuit', 11, A),
|
| 111 |
+
('d_agente_informador', 255, A),
|
| 112 |
+
('d_agente_origen', 255, A),
|
| 113 |
+
('d_agente_destino', 255, A),
|
| 114 |
+
('d_producto', 250, A),
|
| 115 |
+
('d_estado_transaccion', 30, A),
|
| 116 |
+
('d_tipo_transaccion', 30, A),
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
# Formato para Errores
|
| 120 |
+
ERRORES = [
|
| 121 |
+
('_c_error', 4, A), # cοΏ½digo
|
| 122 |
+
('_d_error', 250, A), # descripciοΏ½n
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class TrazaFito(BaseWS):
|
| 127 |
+
"Interfaz para el WebService de Trazabilidad de Fitosanitarios SENASA"
|
| 128 |
+
|
| 129 |
+
_public_methods_ = ['SaveTransaccion', 'SendCancelaTransac',
|
| 130 |
+
'SendConfirmaTransacc', 'SendAlertaTransacc',
|
| 131 |
+
'GetTransacciones',
|
| 132 |
+
'Conectar', 'LeerError', 'LeerTransaccion',
|
| 133 |
+
'SetUsername',
|
| 134 |
+
'SetParametro', 'GetParametro',
|
| 135 |
+
'GetCodigoTransaccion', 'GetResultado', 'LoadTestXML']
|
| 136 |
+
|
| 137 |
+
_public_attrs_ = [
|
| 138 |
+
'Username', 'Password',
|
| 139 |
+
'CodigoTransaccion', 'Errores', 'Resultado',
|
| 140 |
+
'XmlRequest', 'XmlResponse',
|
| 141 |
+
'Version', 'InstallDir',
|
| 142 |
+
'Traceback', 'Excepcion', 'LanzarExcepciones',
|
| 143 |
+
'CantPaginas', 'HayError', 'TransaccionSenasa',
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
_reg_progid_ = "TrazaFito"
|
| 147 |
+
_reg_clsid_ = "{39793931-450A-4F66-9324-D4D981FC5319}"
|
| 148 |
+
|
| 149 |
+
# Variables globales para BaseWS:
|
| 150 |
+
HOMO = HOMO
|
| 151 |
+
WSDL = WSDL
|
| 152 |
+
Version = "%s %s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '',
|
| 153 |
+
pysimplesoap.client.__version__)
|
| 154 |
+
|
| 155 |
+
def __init__(self, reintentos=1):
|
| 156 |
+
self.Username = self.Password = None
|
| 157 |
+
self.TransaccionSenasa = []
|
| 158 |
+
BaseWS.__init__(self, reintentos)
|
| 159 |
+
|
| 160 |
+
def inicializar(self):
|
| 161 |
+
BaseWS.inicializar(self)
|
| 162 |
+
self.CodigoTransaccion = self.Errores = self.Resultado = None
|
| 163 |
+
self.Resultado = ''
|
| 164 |
+
self.Errores = [] # lista de strings para la interfaz
|
| 165 |
+
self.errores = [] # lista de diccionarios (uso interno)
|
| 166 |
+
self.CantPaginas = self.HayError = None
|
| 167 |
+
|
| 168 |
+
def __analizar_errores(self, ret):
|
| 169 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 170 |
+
self.errores = ret.get('errores', [])
|
| 171 |
+
self.Errores = ["%s: %s" % (it['c_error'], it['d_error'])
|
| 172 |
+
for it in ret.get('errores', [])]
|
| 173 |
+
self.Resultado = ret.get('resultado')
|
| 174 |
+
|
| 175 |
+
def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, timeout=30):
|
| 176 |
+
# Conecto usando el mοΏ½todo estandard:
|
| 177 |
+
ok = BaseWS.Conectar(self, cache, wsdl, proxy, wrapper, cacert, timeout,
|
| 178 |
+
soap_server="jetty")
|
| 179 |
+
if ok:
|
| 180 |
+
# si el archivo es local, asumo que ya esta corregido:
|
| 181 |
+
if not self.wsdl.startswith("file"):
|
| 182 |
+
# corrijo ubicaciοΏ½n del servidor (localhost:9050 en el WSDL)
|
| 183 |
+
location = self.wsdl[:-5]
|
| 184 |
+
ws = self.client.services['IWebServiceSenasa']
|
| 185 |
+
ws['ports']['IWebServiceSenasaPort']['location'] = location
|
| 186 |
+
|
| 187 |
+
# Establecer credenciales de seguridad:
|
| 188 |
+
self.client['wsse:Security'] = {
|
| 189 |
+
'wsse:UsernameToken': {
|
| 190 |
+
'wsse:Username': self.Username,
|
| 191 |
+
'wsse:Password': self.Password,
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
return ok
|
| 195 |
+
|
| 196 |
+
@inicializar_y_capturar_excepciones
|
| 197 |
+
def SaveTransaccion(self, usuario, password,
|
| 198 |
+
gln_origen=None, gln_destino=None,
|
| 199 |
+
f_operacion=None, f_elaboracion=None, f_vto=None,
|
| 200 |
+
id_evento=None, cod_producto=None, n_cantidad=None,
|
| 201 |
+
n_serie=None, n_lote=None, n_cai=None, n_cae=None,
|
| 202 |
+
id_motivo_destruccion=None, n_manifiesto=None,
|
| 203 |
+
en_transporte=None, n_remito=None,
|
| 204 |
+
motivo_devolucion=None, observaciones=None,
|
| 205 |
+
n_vale_compra=None, apellidoNombres=None,
|
| 206 |
+
direccion=None, numero=None, localidad=None,
|
| 207 |
+
provincia=None, n_postal=None, cuit=None
|
| 208 |
+
):
|
| 209 |
+
"Realiza el registro de una transacciοΏ½n de productos fitosanitarios. "
|
| 210 |
+
# creo los parοΏ½metros para esta llamada
|
| 211 |
+
params = {'gln_origen': gln_origen,
|
| 212 |
+
'gln_destino': gln_destino,
|
| 213 |
+
'f_operacion': f_operacion,
|
| 214 |
+
'f_elaboracion': f_elaboracion,
|
| 215 |
+
'f_vto': f_vto,
|
| 216 |
+
'id_evento': id_evento,
|
| 217 |
+
'cod_producto': cod_producto,
|
| 218 |
+
'n_cantidad': n_cantidad,
|
| 219 |
+
'n_serie': n_serie,
|
| 220 |
+
'n_lote': n_lote,
|
| 221 |
+
'n_cai': n_cai or None,
|
| 222 |
+
'n_cae': n_cae or None,
|
| 223 |
+
'id_motivo_destruccion': id_motivo_destruccion or None,
|
| 224 |
+
'n_manifiesto': n_manifiesto or None,
|
| 225 |
+
'en_transporte': en_transporte or None,
|
| 226 |
+
'n_remito': n_remito or None,
|
| 227 |
+
'motivo_devolucion': motivo_devolucion or None,
|
| 228 |
+
'observaciones': observaciones or None,
|
| 229 |
+
'n_vale_compra': n_vale_compra or None,
|
| 230 |
+
'apellidoNombres': apellidoNombres or None,
|
| 231 |
+
'direccion': direccion or None,
|
| 232 |
+
'numero': numero or None,
|
| 233 |
+
'localidad': localidad or None,
|
| 234 |
+
'provincia': provincia or None,
|
| 235 |
+
'n_postal': n_postal or None,
|
| 236 |
+
'cuit': cuit or None,
|
| 237 |
+
}
|
| 238 |
+
res = self.client.saveTransacciones(
|
| 239 |
+
arg0=params,
|
| 240 |
+
arg1=usuario,
|
| 241 |
+
arg2=password,
|
| 242 |
+
)
|
| 243 |
+
ret = res['return']
|
| 244 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 245 |
+
self.__analizar_errores(ret)
|
| 246 |
+
return True
|
| 247 |
+
|
| 248 |
+
@inicializar_y_capturar_excepciones
|
| 249 |
+
def SendCancelaTransac(self, usuario, password, codigo_transaccion):
|
| 250 |
+
" Realiza la cancelaciοΏ½n de una transacciοΏ½n"
|
| 251 |
+
res = self.client.sendCancelaTransac(
|
| 252 |
+
arg0=codigo_transaccion,
|
| 253 |
+
arg1=usuario,
|
| 254 |
+
arg2=password,
|
| 255 |
+
)
|
| 256 |
+
ret = res['return']
|
| 257 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 258 |
+
self.__analizar_errores(ret)
|
| 259 |
+
return True
|
| 260 |
+
|
| 261 |
+
@inicializar_y_capturar_excepciones
|
| 262 |
+
def SendConfirmaTransacc(self, usuario, password, p_ids_transac, f_operacion, n_cantidad=None):
|
| 263 |
+
"Confirma la recepciοΏ½n de un medicamento"
|
| 264 |
+
res = self.client.sendConfirmaTransacc(
|
| 265 |
+
arg0=usuario,
|
| 266 |
+
arg1=password,
|
| 267 |
+
arg2={'p_ids_transac': p_ids_transac, 'f_operacion': f_operacion,
|
| 268 |
+
'n_cantidad': n_cantidad,
|
| 269 |
+
},
|
| 270 |
+
)
|
| 271 |
+
ret = res['return']
|
| 272 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 273 |
+
self.__analizar_errores(ret)
|
| 274 |
+
return True
|
| 275 |
+
|
| 276 |
+
@inicializar_y_capturar_excepciones
|
| 277 |
+
def SendAlertaTransacc(self, usuario, password, p_ids_transac_ws):
|
| 278 |
+
"Alerta un medicamento, acciοΏ½n contraria a οΏ½confirmar la transacciοΏ½nοΏ½."
|
| 279 |
+
res = self.client.sendAlertaTransacc(
|
| 280 |
+
arg0=usuario,
|
| 281 |
+
arg1=password,
|
| 282 |
+
arg2=p_ids_transac_ws,
|
| 283 |
+
)
|
| 284 |
+
ret = res['return']
|
| 285 |
+
self.CodigoTransaccion = ret.get('id_transac_asociada')
|
| 286 |
+
self.__analizar_errores(ret)
|
| 287 |
+
return True
|
| 288 |
+
|
| 289 |
+
@inicializar_y_capturar_excepciones
|
| 290 |
+
def GetTransacciones(self, usuario, password,
|
| 291 |
+
id_transaccion=None, id_evento=None, gln_origen=None,
|
| 292 |
+
fecha_desde_t=None, fecha_hasta_t=None,
|
| 293 |
+
fecha_desde_v=None, fecha_hasta_v=None,
|
| 294 |
+
gln_informador=None, id_tipo_transaccion=None,
|
| 295 |
+
gtin_elemento=None, n_lote=None, n_serie=None,
|
| 296 |
+
n_remito_factura=None,
|
| 297 |
+
):
|
| 298 |
+
"Trae un listado de las transacciones que no estοΏ½n confirmadas"
|
| 299 |
+
|
| 300 |
+
# preparo los parametros de entrada opcionales:
|
| 301 |
+
kwargs = {}
|
| 302 |
+
if id_transaccion is not None:
|
| 303 |
+
kwargs['arg2'] = id_transaccion
|
| 304 |
+
if id_evento is not None:
|
| 305 |
+
kwargs['arg3'] = id_evento
|
| 306 |
+
if gln_origen is not None:
|
| 307 |
+
kwargs['arg4'] = gln_origen
|
| 308 |
+
if fecha_desde_t is not None:
|
| 309 |
+
kwargs['arg5'] = fecha_desde_t
|
| 310 |
+
if fecha_hasta_t is not None:
|
| 311 |
+
kwargs['arg6'] = fecha_hasta_t
|
| 312 |
+
if fecha_desde_v is not None:
|
| 313 |
+
kwargs['arg7'] = fecha_desde_v
|
| 314 |
+
if fecha_hasta_v is not None:
|
| 315 |
+
kwargs['arg8'] = fecha_hasta_v
|
| 316 |
+
if gln_informador is not None:
|
| 317 |
+
kwargs['arg9'] = gln_informador
|
| 318 |
+
if id_tipo_transaccion is not None:
|
| 319 |
+
kwargs['arg10'] = id_tipo_transaccion
|
| 320 |
+
if gtin_elemento is not None:
|
| 321 |
+
kwargs['arg11'] = gtin_elemento
|
| 322 |
+
if n_lote is not None:
|
| 323 |
+
kwargs['arg12'] = n_lote
|
| 324 |
+
if n_serie is not None:
|
| 325 |
+
kwargs['arg13'] = n_serie
|
| 326 |
+
if n_remito_factura is not None:
|
| 327 |
+
kwargs['arg14'] = n_remito_factura
|
| 328 |
+
|
| 329 |
+
# llamo al webservice
|
| 330 |
+
res = self.client.getTransacciones(
|
| 331 |
+
arg0=usuario,
|
| 332 |
+
arg1=password,
|
| 333 |
+
**kwargs
|
| 334 |
+
)
|
| 335 |
+
ret = res['return']
|
| 336 |
+
if ret:
|
| 337 |
+
self.__analizar_errores(ret)
|
| 338 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 339 |
+
self.HayError = ret.get('hay_error')
|
| 340 |
+
self.TransaccionSenasa = [it for it in ret.get('list', [])]
|
| 341 |
+
return True
|
| 342 |
+
|
| 343 |
+
def LeerTransaccion(self):
|
| 344 |
+
"Recorro TransaccionSenasa devuelto por GetTransacciones"
|
| 345 |
+
# usar GetParametro para consultar el valor retornado por el webservice
|
| 346 |
+
|
| 347 |
+
if self.TransaccionSenasa:
|
| 348 |
+
# extraigo el primer item
|
| 349 |
+
self.params_out = self.TransaccionSenasa.pop(0)
|
| 350 |
+
return True
|
| 351 |
+
else:
|
| 352 |
+
# limpio los parοΏ½metros
|
| 353 |
+
self.params_out = {}
|
| 354 |
+
return False
|
| 355 |
+
|
| 356 |
+
def LeerError(self):
|
| 357 |
+
"Recorro los errores devueltos y devuelvo el primero si existe"
|
| 358 |
+
|
| 359 |
+
if self.Errores:
|
| 360 |
+
# extraigo el primer item
|
| 361 |
+
er = self.Errores.pop(0)
|
| 362 |
+
return er
|
| 363 |
+
else:
|
| 364 |
+
return ""
|
| 365 |
+
|
| 366 |
+
def SetUsername(self, username):
|
| 367 |
+
"Establezco el nombre de usuario"
|
| 368 |
+
self.Username = username
|
| 369 |
+
|
| 370 |
+
def SetPassword(self, password):
|
| 371 |
+
"Establezco la contraseοΏ½a"
|
| 372 |
+
self.Password = password
|
| 373 |
+
|
| 374 |
+
def GetCodigoTransaccion(self):
|
| 375 |
+
"Devuelvo el cοΏ½digo de transacciοΏ½n"
|
| 376 |
+
return self.CodigoTransaccion
|
| 377 |
+
|
| 378 |
+
def GetResultado(self):
|
| 379 |
+
"Devuelvo el resultado"
|
| 380 |
+
return self.Resultado
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def main():
|
| 384 |
+
"FunciοΏ½n principal de pruebas (obtener CAE)"
|
| 385 |
+
import os
|
| 386 |
+
import time
|
| 387 |
+
import sys
|
| 388 |
+
global WSDL, LOCATION
|
| 389 |
+
|
| 390 |
+
DEBUG = '--debug' in sys.argv
|
| 391 |
+
|
| 392 |
+
ws = TrazaFito()
|
| 393 |
+
|
| 394 |
+
ws.Username = 'testwservice'
|
| 395 |
+
ws.Password = 'testwservicepsw'
|
| 396 |
+
|
| 397 |
+
if '--prod' in sys.argv and not HOMO:
|
| 398 |
+
WSDL = "https://servicios.pami.org.ar/trazaagr.WebService?wsdl"
|
| 399 |
+
print("Usando WSDL:", WSDL)
|
| 400 |
+
sys.argv.pop(sys.argv.index("--prod"))
|
| 401 |
+
|
| 402 |
+
# Inicializo las variables y estructuras para el archivo de intercambio:
|
| 403 |
+
transaccion_dto = []
|
| 404 |
+
transacciones = []
|
| 405 |
+
errores = []
|
| 406 |
+
formatos = [('TransaccionDTO', TRANSACCION_DTO, transaccion_dto),
|
| 407 |
+
('Transacciones', TRANSACCIONES, transacciones),
|
| 408 |
+
('Errores', ERRORES, errores),
|
| 409 |
+
]
|
| 410 |
+
|
| 411 |
+
if '--formato' in sys.argv:
|
| 412 |
+
print("Formato:")
|
| 413 |
+
for msg, formato, lista in formatos:
|
| 414 |
+
comienzo = 1
|
| 415 |
+
print("=== %s ===" % msg)
|
| 416 |
+
print("|| %-25s || %-12s || %-5s || %-4s || %-10s ||" % (
|
| 417 |
+
"Nombre", "Tipo", "Long.", "Pos(txt)", "Campo(dbf)"))
|
| 418 |
+
claves = []
|
| 419 |
+
for fmt in formato:
|
| 420 |
+
clave, longitud, tipo = fmt[0:3]
|
| 421 |
+
clave_dbf = dar_nombre_campo_dbf(clave, claves)
|
| 422 |
+
claves.append(clave_dbf)
|
| 423 |
+
print("|| %-25s || %-12s || %5d || %4d || %-10s ||" % (
|
| 424 |
+
clave, tipo, longitud, comienzo, clave_dbf))
|
| 425 |
+
comienzo += longitud
|
| 426 |
+
sys.exit(0)
|
| 427 |
+
|
| 428 |
+
if '--cargar' in sys.argv:
|
| 429 |
+
if '--dbf' in sys.argv:
|
| 430 |
+
leer_dbf(formatos[:1], {})
|
| 431 |
+
elif '--json' in sys.argv:
|
| 432 |
+
for formato in formatos[:1]:
|
| 433 |
+
archivo = open(formato[0].lower() + ".json", "r")
|
| 434 |
+
d = json.load(archivo)
|
| 435 |
+
formato[2].extend(d)
|
| 436 |
+
archivo.close()
|
| 437 |
+
else:
|
| 438 |
+
for formato in formatos[:1]:
|
| 439 |
+
archivo = open(formato[0].lower() + ".txt", "r")
|
| 440 |
+
for linea in archivo:
|
| 441 |
+
d = leer(linea, formato[1])
|
| 442 |
+
formato[2].append(d)
|
| 443 |
+
archivo.close()
|
| 444 |
+
|
| 445 |
+
ws.Conectar("", WSDL)
|
| 446 |
+
|
| 447 |
+
if ws.Excepcion:
|
| 448 |
+
print(ws.Excepcion)
|
| 449 |
+
print(ws.Traceback)
|
| 450 |
+
sys.exit(-1)
|
| 451 |
+
|
| 452 |
+
# Datos de pruebas:
|
| 453 |
+
|
| 454 |
+
if '--test' in sys.argv:
|
| 455 |
+
transaccion_dto.append(dict(
|
| 456 |
+
gln_origen="9876543210982", gln_destino="3692581473693",
|
| 457 |
+
f_operacion=datetime.datetime.now().strftime("%d/%m/%Y"),
|
| 458 |
+
f_elaboracion=datetime.datetime.now().strftime("%d/%m/%Y"),
|
| 459 |
+
f_vto=(datetime.datetime.now() + datetime.timedelta(30)).strftime("%d/%m/%Y"),
|
| 460 |
+
id_evento=11,
|
| 461 |
+
cod_producto="88900000000001",
|
| 462 |
+
n_cantidad=1,
|
| 463 |
+
n_serie=int(time.time() * 10),
|
| 464 |
+
n_lote=datetime.datetime.now().strftime("%Y"),
|
| 465 |
+
n_cai="123456789012345",
|
| 466 |
+
n_cae="",
|
| 467 |
+
id_motivo_destruccion=0,
|
| 468 |
+
n_manifiesto="",
|
| 469 |
+
en_transporte="N",
|
| 470 |
+
n_remito="1234",
|
| 471 |
+
motivo_devolucion="",
|
| 472 |
+
observaciones="prueba",
|
| 473 |
+
n_vale_compra="",
|
| 474 |
+
apellidoNombres="Juan Peres",
|
| 475 |
+
direccion="Saraza", numero="1234",
|
| 476 |
+
localidad="Hurlingham", provincia="Buenos Aires",
|
| 477 |
+
n_postal="1688",
|
| 478 |
+
cuit="20267565393",
|
| 479 |
+
codigo_transaccion=None,
|
| 480 |
+
))
|
| 481 |
+
|
| 482 |
+
# Opciones principales:
|
| 483 |
+
|
| 484 |
+
if '--confirma' in sys.argv:
|
| 485 |
+
if '--loadxml' in sys.argv:
|
| 486 |
+
ws.LoadTestXML("trazamed_confirma.xml") # cargo respuesta
|
| 487 |
+
ok = ws.SendConfirmaTransacc(usuario="pruebasws", password="pruebasws",
|
| 488 |
+
p_ids_transac="1", f_operacion="31-12-2013")
|
| 489 |
+
if not ok:
|
| 490 |
+
raise RuntimeError(ws.Excepcion)
|
| 491 |
+
ws.SendConfirmaTransacc(*sys.argv[sys.argv.index("--confirma") + 1:])
|
| 492 |
+
elif '--alerta' in sys.argv:
|
| 493 |
+
ws.SendAlertaTransacc(*sys.argv[sys.argv.index("--alerta") + 1:])
|
| 494 |
+
elif '--cancela' in sys.argv:
|
| 495 |
+
ws.SendCancelaTransac(*sys.argv[sys.argv.index("--cancela") + 1:])
|
| 496 |
+
elif '--consulta' in sys.argv:
|
| 497 |
+
ws.GetTransacciones(
|
| 498 |
+
*sys.argv[sys.argv.index("--consulta") + 1:]
|
| 499 |
+
)
|
| 500 |
+
print("CantPaginas", ws.CantPaginas)
|
| 501 |
+
print("HayError", ws.HayError)
|
| 502 |
+
# print "TransaccionSenasa", ws.TransaccionSenasa
|
| 503 |
+
# parametros comunes de salida (columnas de la tabla):
|
| 504 |
+
claves = [k for k, v, l in TRANSACCIONES]
|
| 505 |
+
# extiendo la lista de resultado para el archivo de intercambio:
|
| 506 |
+
transacciones.extend(ws.TransaccionSenasa)
|
| 507 |
+
# encabezado de la tabla:
|
| 508 |
+
print("||", "||".join(["%s" % clave for clave in claves]), "||")
|
| 509 |
+
# recorro los datos devueltos (TransaccionSenasa):
|
| 510 |
+
while ws.LeerTransaccion():
|
| 511 |
+
for clave in claves:
|
| 512 |
+
print("||", ws.GetParametro(clave), end=' ') # imprimo cada fila
|
| 513 |
+
print("||")
|
| 514 |
+
else:
|
| 515 |
+
argv = [argv for argv in sys.argv if not argv.startswith("--")]
|
| 516 |
+
if not transaccion_dto:
|
| 517 |
+
if len(argv) > 10:
|
| 518 |
+
ws.SaveTransaccion(*argv[1:])
|
| 519 |
+
else:
|
| 520 |
+
print("ERROR: no se indicaron todos los parοΏ½metros requeridos")
|
| 521 |
+
elif transaccion_dto:
|
| 522 |
+
try:
|
| 523 |
+
usuario, password = argv[-2:]
|
| 524 |
+
except BaseException:
|
| 525 |
+
print("ADVERTENCIA: no se indico parοΏ½metros usuario y passoword")
|
| 526 |
+
usuario, password = "senasaws", "Clave2013"
|
| 527 |
+
for i, dto in enumerate(transaccion_dto):
|
| 528 |
+
print("Procesando registro", i)
|
| 529 |
+
del dto['codigo_transaccion']
|
| 530 |
+
ws.SaveTransaccion(usuario, password, **dto)
|
| 531 |
+
dto['codigo_transaccion'] = ws.CodigoTransaccion
|
| 532 |
+
errores.extend(ws.errores)
|
| 533 |
+
print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
|
| 534 |
+
ws.Resultado,
|
| 535 |
+
ws.CodigoTransaccion,
|
| 536 |
+
'|'.join(ws.Errores or []),
|
| 537 |
+
))
|
| 538 |
+
else:
|
| 539 |
+
print("ERROR: no se especificaron productos a informar")
|
| 540 |
+
|
| 541 |
+
if not transaccion_dto:
|
| 542 |
+
print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
|
| 543 |
+
ws.Resultado,
|
| 544 |
+
ws.CodigoTransaccion,
|
| 545 |
+
'|'.join(ws.Errores or []),
|
| 546 |
+
))
|
| 547 |
+
|
| 548 |
+
if ws.Excepcion:
|
| 549 |
+
print(ws.Traceback)
|
| 550 |
+
|
| 551 |
+
if '--grabar' in sys.argv:
|
| 552 |
+
if '--dbf' in sys.argv:
|
| 553 |
+
guardar_dbf(formatos, True, {})
|
| 554 |
+
elif '--json' in sys.argv:
|
| 555 |
+
for formato in formatos:
|
| 556 |
+
archivo = open(formato[0].lower() + ".json", "w")
|
| 557 |
+
json.dump(formato[2], archivo, sort_keys=True, indent=4)
|
| 558 |
+
archivo.close()
|
| 559 |
+
else:
|
| 560 |
+
for formato in formatos:
|
| 561 |
+
archivo = open(formato[0].lower() + ".txt", "w")
|
| 562 |
+
for it in formato[2]:
|
| 563 |
+
archivo.write(escribir(it, formato[1]))
|
| 564 |
+
archivo.close()
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 568 |
+
INSTALL_DIR = TrazaFito.InstallDir = get_install_dir()
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
if __name__ == '__main__':
|
| 572 |
+
|
| 573 |
+
# ajusto el encoding por defecto (si se redirije la salida)
|
| 574 |
+
if not hasattr(sys.stdout, "encoding") or sys.stdout.encoding is None:
|
| 575 |
+
import codecs
|
| 576 |
+
import locale
|
| 577 |
+
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout, "replace")
|
| 578 |
+
sys.stderr = codecs.getwriter(locale.getpreferredencoding())(sys.stderr, "replace")
|
| 579 |
+
|
| 580 |
+
if '--register' in sys.argv or '--unregister' in sys.argv:
|
| 581 |
+
import pythoncom
|
| 582 |
+
import win32com.server.register
|
| 583 |
+
win32com.server.register.UseCommandLine(TrazaFito)
|
| 584 |
+
elif "/Automate" in sys.argv:
|
| 585 |
+
# MS seems to like /automate to run the class factories.
|
| 586 |
+
import win32com.server.localserver
|
| 587 |
+
# win32com.server.localserver.main()
|
| 588 |
+
# start the server.
|
| 589 |
+
win32com.server.localserver.serve([TrazaFito._reg_clsid_])
|
| 590 |
+
else:
|
| 591 |
+
main()
|
app/pyafipws/trazamed.py
ADDED
|
@@ -0,0 +1,1041 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"""MοΏ½dulo para Trazabilidad de Medicamentos ANMAT - PAMI - INSSJP Disp. 3683/11
|
| 14 |
+
segοΏ½n EspecificaciοΏ½n TοΏ½cnica para Pruebas de Servicios v2 (2013)"""
|
| 15 |
+
|
| 16 |
+
# InformaciοΏ½n adicional y documentaciοΏ½n:
|
| 17 |
+
# http://www.sistemasagiles.com.ar/trac/wiki/TrazabilidadMedicamentos
|
| 18 |
+
|
| 19 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 20 |
+
__copyright__ = "Copyright (C) 2011 Mariano Reingart"
|
| 21 |
+
__license__ = "GPL 3.0"
|
| 22 |
+
__version__ = "1.16c"
|
| 23 |
+
|
| 24 |
+
import os
|
| 25 |
+
import socket
|
| 26 |
+
import sys
|
| 27 |
+
import datetime
|
| 28 |
+
import time
|
| 29 |
+
import traceback
|
| 30 |
+
import pysimplesoap.client
|
| 31 |
+
from pysimplesoap.client import SoapClient, SoapFault, parse_proxy, \
|
| 32 |
+
set_http_wrapper
|
| 33 |
+
from pysimplesoap.simplexml import SimpleXMLElement
|
| 34 |
+
from io import StringIO
|
| 35 |
+
|
| 36 |
+
# importo funciones compartidas:
|
| 37 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, dar_nombre_campo_dbf, get_install_dir, BaseWS, inicializar_y_capturar_excepciones
|
| 38 |
+
|
| 39 |
+
HOMO = False
|
| 40 |
+
TYPELIB = False
|
| 41 |
+
|
| 42 |
+
WSDL = "https://servicios.pami.org.ar/trazamed.WebService?wsdl"
|
| 43 |
+
LOCATION = "https://servicios.pami.org.ar/trazamed.WebService"
|
| 44 |
+
#WSDL = "https://trazabilidad.pami.org.ar:9050/trazamed.WebService?wsdl"
|
| 45 |
+
|
| 46 |
+
# Formato de MedicamentosDTO, MedicamentosDTODHSerie, MedicamentosDTOFraccion
|
| 47 |
+
MEDICAMENTOS = [
|
| 48 |
+
('f_evento', 10, A), # formato DD/MM/AAAA
|
| 49 |
+
('h_evento', 5, A), # formato HH:MM
|
| 50 |
+
('gln_origen', 13, A),
|
| 51 |
+
('gln_destino', 13, A),
|
| 52 |
+
('n_remito', 20, A),
|
| 53 |
+
('n_factura', 20, A),
|
| 54 |
+
('vencimiento', 10, A),
|
| 55 |
+
('gtin', 14, A),
|
| 56 |
+
('lote', 20, A),
|
| 57 |
+
('numero_serial', 20, A),
|
| 58 |
+
('desde_numero_serial', 20, A), # sendMedicamentosDHSerie
|
| 59 |
+
('hasta_numero_serial', 20, A), # sendMedicamentosDHSerie
|
| 60 |
+
('id_obra_social', 9, N),
|
| 61 |
+
('id_evento', 3, N),
|
| 62 |
+
('cuit_origen', 11, A),
|
| 63 |
+
('cuit_destino', 11, A),
|
| 64 |
+
('apellido', 50, A),
|
| 65 |
+
('nombres', 100, A),
|
| 66 |
+
('tipo_documento', 2, N), # 96: DNI,80: CUIT
|
| 67 |
+
('n_documento', 10, A),
|
| 68 |
+
('sexo', 1, A), # M o F
|
| 69 |
+
('direccion', 100, A),
|
| 70 |
+
('numero', 10, A),
|
| 71 |
+
('piso', 5, A),
|
| 72 |
+
('depto', 5, A),
|
| 73 |
+
('localidad', 50, A),
|
| 74 |
+
('provincia', 100, A),
|
| 75 |
+
('n_postal', 8, A),
|
| 76 |
+
('fecha_nacimiento', 100, A),
|
| 77 |
+
('telefono', 30, A),
|
| 78 |
+
('nro_asociado', 30, A),
|
| 79 |
+
('cantidad', 3, N), # sendMedicamentosFraccion
|
| 80 |
+
('codigo_transaccion', 14, A),
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
# Formato para TransaccionPlainWS (getTransaccionesNoConfirmadas)
|
| 84 |
+
TRANSACCIONES = [
|
| 85 |
+
('_id_transaccion', 14, A),
|
| 86 |
+
('_id_transaccion_global', 14, A),
|
| 87 |
+
('_f_evento', 10, A),
|
| 88 |
+
('_f_transaccion', 16, A), # formato DD/MM/AAAA HH:MM
|
| 89 |
+
('_gtin', 14, A),
|
| 90 |
+
('_lote', 20, A),
|
| 91 |
+
('_numero_serial', 20, A),
|
| 92 |
+
('_nombre', 200, A),
|
| 93 |
+
('_d_evento', 100, A),
|
| 94 |
+
('_gln_origen', 13, A),
|
| 95 |
+
('_razon_social_origen', 200, A),
|
| 96 |
+
('_gln_destino', 13, A),
|
| 97 |
+
('_razon_social_destino', 200, A),
|
| 98 |
+
('_n_remito', 20, A),
|
| 99 |
+
('_n_factura', 20, A),
|
| 100 |
+
('_vencimiento', 10, A),
|
| 101 |
+
('_id_evento', 3, N), # agregado el 30/01/2014
|
| 102 |
+
]
|
| 103 |
+
|
| 104 |
+
# Formato para Errores
|
| 105 |
+
ERRORES = [
|
| 106 |
+
('c_error', 4, A), # cοΏ½digo
|
| 107 |
+
('d_error', 250, A), # descripciοΏ½n
|
| 108 |
+
]
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class TrazaMed(BaseWS):
|
| 112 |
+
"Interfaz para el WebService de Trazabilidad de Medicamentos ANMAT - PAMI - INSSJP"
|
| 113 |
+
_public_methods_ = ['SendMedicamentos',
|
| 114 |
+
'SendCancelacTransacc', 'SendCancelacTransaccParcial',
|
| 115 |
+
'SendMedicamentosDHSerie',
|
| 116 |
+
'SendMedicamentosFraccion',
|
| 117 |
+
'SendConfirmaTransacc', 'SendAlertaTransacc',
|
| 118 |
+
'GetTransaccionesNoConfirmadas',
|
| 119 |
+
'GetEnviosPropiosAlertados', 'GetConsultaStock',
|
| 120 |
+
'GetTransaccionesWS', 'GetCatalogoElectronicoByGTIN',
|
| 121 |
+
'Conectar', 'LeerError', 'LeerTransaccion',
|
| 122 |
+
'SetUsername', 'SetPassword',
|
| 123 |
+
'SetParametro', 'GetParametro',
|
| 124 |
+
'GetCodigoTransaccion', 'GetResultado', 'LoadTestXML']
|
| 125 |
+
|
| 126 |
+
_public_attrs_ = [
|
| 127 |
+
'Username', 'Password',
|
| 128 |
+
'CodigoTransaccion', 'Errores', 'Resultado',
|
| 129 |
+
'XmlRequest', 'XmlResponse',
|
| 130 |
+
'Version', 'InstallDir',
|
| 131 |
+
'Traceback', 'Excepcion', 'LanzarExcepciones',
|
| 132 |
+
'CantPaginas', 'HayError', 'TransaccionPlainWS',
|
| 133 |
+
]
|
| 134 |
+
|
| 135 |
+
_reg_progid_ = "TrazaMed"
|
| 136 |
+
_reg_clsid_ = "{8472867A-AE6F-487F-8554-C2C896CFFC3E}"
|
| 137 |
+
|
| 138 |
+
if TYPELIB:
|
| 139 |
+
_typelib_guid_ = '{F992EB7E-AFBD-41BB-B717-5693D3A2BADB}'
|
| 140 |
+
_typelib_version_ = 1, 4
|
| 141 |
+
_com_interfaces_ = ['ITrazaMed']
|
| 142 |
+
|
| 143 |
+
# Variables globales para BaseWS:
|
| 144 |
+
HOMO = HOMO
|
| 145 |
+
WSDL = WSDL
|
| 146 |
+
Version = "%s %s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '',
|
| 147 |
+
pysimplesoap.client.__version__)
|
| 148 |
+
|
| 149 |
+
def __init__(self, reintentos=1):
|
| 150 |
+
self.Username = self.Password = None
|
| 151 |
+
self.TransaccionPlainWS = []
|
| 152 |
+
BaseWS.__init__(self, reintentos)
|
| 153 |
+
|
| 154 |
+
def inicializar(self):
|
| 155 |
+
BaseWS.inicializar(self)
|
| 156 |
+
self.CodigoTransaccion = self.Errores = self.Resultado = None
|
| 157 |
+
self.Resultado = ''
|
| 158 |
+
self.Errores = [] # lista de strings para la interfaz
|
| 159 |
+
self.errores = [] # lista de diccionarios (uso interno)
|
| 160 |
+
self.CantPaginas = self.HayError = None
|
| 161 |
+
|
| 162 |
+
def __analizar_errores(self, ret):
|
| 163 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 164 |
+
self.errores = ret.get('errores', [])
|
| 165 |
+
self.Errores = ["%s: %s" % (it['_c_error'], it['_d_error'])
|
| 166 |
+
for it in ret.get('errores', [])]
|
| 167 |
+
self.Resultado = ret.get('resultado')
|
| 168 |
+
|
| 169 |
+
def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, timeout=30):
|
| 170 |
+
# Conecto usando el mοΏ½todo estandard:
|
| 171 |
+
ok = BaseWS.Conectar(self, cache, wsdl, proxy, wrapper, cacert, timeout,
|
| 172 |
+
soap_server="jetty")
|
| 173 |
+
|
| 174 |
+
if ok:
|
| 175 |
+
# si el archivo es local, asumo que ya esta corregido:
|
| 176 |
+
if not self.wsdl.startswith("file"):
|
| 177 |
+
# corrijo ubicaciοΏ½n del servidor (localhost:9050 en el WSDL)
|
| 178 |
+
location = self.wsdl[:-5]
|
| 179 |
+
if 'IWebServiceService' in self.client.services:
|
| 180 |
+
ws = self.client.services['IWebServiceService'] # version 1
|
| 181 |
+
else:
|
| 182 |
+
ws = self.client.services['IWebService'] # version 2
|
| 183 |
+
ws['ports']['IWebServicePort']['location'] = location
|
| 184 |
+
|
| 185 |
+
# Establecer credenciales de seguridad:
|
| 186 |
+
self.client['wsse:Security'] = {
|
| 187 |
+
'wsse:UsernameToken': {
|
| 188 |
+
'wsse:Username': self.Username,
|
| 189 |
+
'wsse:Password': self.Password,
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
return ok
|
| 193 |
+
|
| 194 |
+
@inicializar_y_capturar_excepciones
|
| 195 |
+
def SendMedicamentos(self, usuario, password,
|
| 196 |
+
f_evento, h_evento, gln_origen, gln_destino,
|
| 197 |
+
n_remito, n_factura, vencimiento, gtin, lote,
|
| 198 |
+
numero_serial, id_obra_social, id_evento,
|
| 199 |
+
cuit_origen='', cuit_destino='', apellido='', nombres='',
|
| 200 |
+
tipo_documento='', n_documento='', sexo='',
|
| 201 |
+
direccion='', numero='', piso='', depto='', localidad='', provincia='',
|
| 202 |
+
n_postal='', fecha_nacimiento='', telefono='',
|
| 203 |
+
nro_asociado=None,
|
| 204 |
+
):
|
| 205 |
+
"Realiza el registro de una transacciοΏ½n de medicamentos. "
|
| 206 |
+
# creo los parοΏ½metros para esta llamada
|
| 207 |
+
params = {'f_evento': f_evento,
|
| 208 |
+
'h_evento': h_evento,
|
| 209 |
+
'gln_origen': gln_origen,
|
| 210 |
+
'gln_destino': gln_destino,
|
| 211 |
+
'n_remito': n_remito,
|
| 212 |
+
'n_factura': n_factura,
|
| 213 |
+
'vencimiento': vencimiento,
|
| 214 |
+
'gtin': gtin,
|
| 215 |
+
'lote': lote,
|
| 216 |
+
'numero_serial': numero_serial,
|
| 217 |
+
'id_obra_social': id_obra_social or None,
|
| 218 |
+
'id_evento': id_evento,
|
| 219 |
+
'cuit_origen': cuit_origen,
|
| 220 |
+
'cuit_destino': cuit_destino,
|
| 221 |
+
'apellido': apellido,
|
| 222 |
+
'nombres': nombres,
|
| 223 |
+
'tipo_documento': tipo_documento,
|
| 224 |
+
'n_documento': n_documento,
|
| 225 |
+
'sexo': sexo,
|
| 226 |
+
'direccion': direccion,
|
| 227 |
+
'numero': numero,
|
| 228 |
+
'piso': piso,
|
| 229 |
+
'depto': depto,
|
| 230 |
+
'localidad': localidad,
|
| 231 |
+
'provincia': provincia,
|
| 232 |
+
'n_postal': n_postal,
|
| 233 |
+
'fecha_nacimiento': fecha_nacimiento,
|
| 234 |
+
'telefono': telefono,
|
| 235 |
+
'nro_asociado': nro_asociado,
|
| 236 |
+
}
|
| 237 |
+
res = self.client.sendMedicamentos(
|
| 238 |
+
arg0=params,
|
| 239 |
+
arg1=usuario,
|
| 240 |
+
arg2=password,
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
ret = res['return']
|
| 244 |
+
|
| 245 |
+
self.CodigoTransaccion = ret['codigoTransaccion']
|
| 246 |
+
self.__analizar_errores(ret)
|
| 247 |
+
|
| 248 |
+
return True
|
| 249 |
+
|
| 250 |
+
@inicializar_y_capturar_excepciones
|
| 251 |
+
def SendMedicamentosFraccion(self, usuario, password,
|
| 252 |
+
f_evento, h_evento, gln_origen, gln_destino,
|
| 253 |
+
n_remito, n_factura, vencimiento, gtin, lote,
|
| 254 |
+
numero_serial, id_obra_social, id_evento,
|
| 255 |
+
cuit_origen='', cuit_destino='', apellido='', nombres='',
|
| 256 |
+
tipo_documento='', n_documento='', sexo='',
|
| 257 |
+
direccion='', numero='', piso='', depto='', localidad='', provincia='',
|
| 258 |
+
n_postal='', fecha_nacimiento='', telefono='',
|
| 259 |
+
nro_asociado=None, cantidad=None,
|
| 260 |
+
):
|
| 261 |
+
"Realiza el registro de una transacciοΏ½n de medicamentos fraccionados"
|
| 262 |
+
# creo los parοΏ½metros para esta llamada
|
| 263 |
+
params = {'f_evento': f_evento,
|
| 264 |
+
'h_evento': h_evento,
|
| 265 |
+
'gln_origen': gln_origen,
|
| 266 |
+
'gln_destino': gln_destino,
|
| 267 |
+
'n_remito': n_remito,
|
| 268 |
+
'n_factura': n_factura,
|
| 269 |
+
'vencimiento': vencimiento,
|
| 270 |
+
'gtin': gtin,
|
| 271 |
+
'lote': lote,
|
| 272 |
+
'numero_serial': numero_serial,
|
| 273 |
+
'id_obra_social': id_obra_social or None,
|
| 274 |
+
'id_evento': id_evento,
|
| 275 |
+
'cuit_origen': cuit_origen,
|
| 276 |
+
'cuit_destino': cuit_destino,
|
| 277 |
+
'apellido': apellido,
|
| 278 |
+
'nombres': nombres,
|
| 279 |
+
'tipo_documento': tipo_documento,
|
| 280 |
+
'n_documento': n_documento,
|
| 281 |
+
'sexo': sexo,
|
| 282 |
+
'direccion': direccion,
|
| 283 |
+
'numero': numero,
|
| 284 |
+
'piso': piso,
|
| 285 |
+
'depto': depto,
|
| 286 |
+
'localidad': localidad,
|
| 287 |
+
'provincia': provincia,
|
| 288 |
+
'n_postal': n_postal,
|
| 289 |
+
'fecha_nacimiento': fecha_nacimiento,
|
| 290 |
+
'telefono': telefono,
|
| 291 |
+
'nro_asociado': nro_asociado,
|
| 292 |
+
'cantidad': cantidad,
|
| 293 |
+
}
|
| 294 |
+
res = self.client.sendMedicamentosFraccion(
|
| 295 |
+
arg0=params,
|
| 296 |
+
arg1=usuario,
|
| 297 |
+
arg2=password,
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
ret = res['return']
|
| 301 |
+
|
| 302 |
+
self.CodigoTransaccion = ret['codigoTransaccion']
|
| 303 |
+
self.__analizar_errores(ret)
|
| 304 |
+
|
| 305 |
+
return True
|
| 306 |
+
|
| 307 |
+
@inicializar_y_capturar_excepciones
|
| 308 |
+
def SendMedicamentosDHSerie(self, usuario, password,
|
| 309 |
+
f_evento, h_evento, gln_origen, gln_destino,
|
| 310 |
+
n_remito, n_factura, vencimiento, gtin, lote,
|
| 311 |
+
desde_numero_serial, hasta_numero_serial,
|
| 312 |
+
id_obra_social, id_evento,
|
| 313 |
+
cuit_origen='', cuit_destino='', apellido='', nombres='',
|
| 314 |
+
tipo_documento='', n_documento='', sexo='',
|
| 315 |
+
direccion='', numero='', piso='', depto='', localidad='', provincia='',
|
| 316 |
+
n_postal='', fecha_nacimiento='', telefono='',
|
| 317 |
+
nro_asociado=None,
|
| 318 |
+
):
|
| 319 |
+
"EnvοΏ½a un lote de medicamentos informando el desde-hasta nοΏ½mero de serie"
|
| 320 |
+
# creo los parοΏ½metros para esta llamada
|
| 321 |
+
params = {'f_evento': f_evento,
|
| 322 |
+
'h_evento': h_evento,
|
| 323 |
+
'gln_origen': gln_origen,
|
| 324 |
+
'gln_destino': gln_destino,
|
| 325 |
+
'n_remito': n_remito,
|
| 326 |
+
'n_factura': n_factura,
|
| 327 |
+
'vencimiento': vencimiento,
|
| 328 |
+
'gtin': gtin,
|
| 329 |
+
'lote': lote,
|
| 330 |
+
'desde_numero_serial': desde_numero_serial,
|
| 331 |
+
'hasta_numero_serial': hasta_numero_serial,
|
| 332 |
+
'id_obra_social': id_obra_social or None,
|
| 333 |
+
'id_evento': id_evento,
|
| 334 |
+
'cuit_origen': cuit_origen,
|
| 335 |
+
'cuit_destino': cuit_destino,
|
| 336 |
+
'apellido': apellido,
|
| 337 |
+
'nombres': nombres,
|
| 338 |
+
'tipo_documento': tipo_documento,
|
| 339 |
+
'n_documento': n_documento,
|
| 340 |
+
'sexo': sexo,
|
| 341 |
+
'direccion': direccion,
|
| 342 |
+
'numero': numero,
|
| 343 |
+
'piso': piso,
|
| 344 |
+
'depto': depto,
|
| 345 |
+
'localidad': localidad,
|
| 346 |
+
'provincia': provincia,
|
| 347 |
+
'n_postal': n_postal,
|
| 348 |
+
'fecha_nacimiento': fecha_nacimiento,
|
| 349 |
+
'telefono': telefono,
|
| 350 |
+
'nro_asociado': nro_asociado,
|
| 351 |
+
}
|
| 352 |
+
res = self.client.sendMedicamentosDHSerie(
|
| 353 |
+
arg0=params,
|
| 354 |
+
arg1=usuario,
|
| 355 |
+
arg2=password,
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
ret = res['return']
|
| 359 |
+
|
| 360 |
+
self.CodigoTransaccion = ret['codigoTransaccion']
|
| 361 |
+
self.__analizar_errores(ret)
|
| 362 |
+
|
| 363 |
+
return True
|
| 364 |
+
|
| 365 |
+
@inicializar_y_capturar_excepciones
|
| 366 |
+
def SendCancelacTransacc(self, usuario, password, codigo_transaccion):
|
| 367 |
+
" Realiza la cancelaciοΏ½n de una transacciοΏ½n"
|
| 368 |
+
res = self.client.sendCancelacTransacc(
|
| 369 |
+
arg0=codigo_transaccion,
|
| 370 |
+
arg1=usuario,
|
| 371 |
+
arg2=password,
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
ret = res['return']
|
| 375 |
+
|
| 376 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 377 |
+
self.__analizar_errores(ret)
|
| 378 |
+
|
| 379 |
+
return True
|
| 380 |
+
|
| 381 |
+
@inicializar_y_capturar_excepciones
|
| 382 |
+
def SendCancelacTransaccParcial(self, usuario, password, codigo_transaccion,
|
| 383 |
+
gtin_medicamento=None, numero_serial=None):
|
| 384 |
+
" Realiza la cancelaciοΏ½n parcial de una transacciοΏ½n"
|
| 385 |
+
res = self.client.sendCancelacTransaccParcial(
|
| 386 |
+
arg0=codigo_transaccion,
|
| 387 |
+
arg1=usuario,
|
| 388 |
+
arg2=password,
|
| 389 |
+
arg3=gtin_medicamento,
|
| 390 |
+
arg4=numero_serial,
|
| 391 |
+
)
|
| 392 |
+
ret = res['return']
|
| 393 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 394 |
+
self.__analizar_errores(ret)
|
| 395 |
+
return True
|
| 396 |
+
|
| 397 |
+
@inicializar_y_capturar_excepciones
|
| 398 |
+
def SendConfirmaTransacc(self, usuario, password, p_ids_transac, f_operacion):
|
| 399 |
+
"Confirma la recepciοΏ½n de un medicamento"
|
| 400 |
+
res = self.client.sendConfirmaTransacc(
|
| 401 |
+
arg0=usuario,
|
| 402 |
+
arg1=password,
|
| 403 |
+
arg2={'p_ids_transac': p_ids_transac, 'f_operacion': f_operacion},
|
| 404 |
+
)
|
| 405 |
+
ret = res['return']
|
| 406 |
+
self.CodigoTransaccion = ret.get('id_transac_asociada')
|
| 407 |
+
self.__analizar_errores(ret)
|
| 408 |
+
return True
|
| 409 |
+
|
| 410 |
+
@inicializar_y_capturar_excepciones
|
| 411 |
+
def SendAlertaTransacc(self, usuario, password, p_ids_transac_ws):
|
| 412 |
+
"Alerta un medicamento, acciοΏ½n contraria a οΏ½confirmar la transacciοΏ½nοΏ½."
|
| 413 |
+
res = self.client.sendAlertaTransacc(
|
| 414 |
+
arg0=usuario,
|
| 415 |
+
arg1=password,
|
| 416 |
+
arg2=p_ids_transac_ws,
|
| 417 |
+
)
|
| 418 |
+
ret = res['return']
|
| 419 |
+
self.CodigoTransaccion = ret.get('id_transac_asociada')
|
| 420 |
+
self.__analizar_errores(ret)
|
| 421 |
+
return True
|
| 422 |
+
|
| 423 |
+
@inicializar_y_capturar_excepciones
|
| 424 |
+
def GetTransaccionesNoConfirmadas(self, usuario, password,
|
| 425 |
+
p_id_transaccion_global=None, id_agente_informador=None,
|
| 426 |
+
id_agente_origen=None, id_agente_destino=None,
|
| 427 |
+
id_medicamento=None, id_evento=None,
|
| 428 |
+
fecha_desde_op=None, fecha_hasta_op=None,
|
| 429 |
+
fecha_desde_t=None, fecha_hasta_t=None,
|
| 430 |
+
fecha_desde_v=None, fecha_hasta_v=None,
|
| 431 |
+
n_remito=None, n_factura=None,
|
| 432 |
+
estado=None, lote=None, numero_serial=None,
|
| 433 |
+
):
|
| 434 |
+
"Trae un listado de las transacciones que no estοΏ½n confirmadas"
|
| 435 |
+
|
| 436 |
+
# preparo los parametros de entrada opcionales:
|
| 437 |
+
kwargs = {}
|
| 438 |
+
if p_id_transaccion_global is not None:
|
| 439 |
+
kwargs['arg2'] = p_id_transaccion_global
|
| 440 |
+
if id_agente_informador is not None:
|
| 441 |
+
kwargs['arg3'] = id_agente_informador
|
| 442 |
+
if id_agente_origen is not None:
|
| 443 |
+
kwargs['arg4'] = id_agente_origen
|
| 444 |
+
if id_agente_destino is not None:
|
| 445 |
+
kwargs['arg5'] = id_agente_destino
|
| 446 |
+
if id_medicamento is not None:
|
| 447 |
+
kwargs['arg6'] = id_medicamento
|
| 448 |
+
if id_evento is not None:
|
| 449 |
+
kwargs['arg7'] = id_evento
|
| 450 |
+
if fecha_desde_op is not None:
|
| 451 |
+
kwargs['arg8'] = fecha_desde_op
|
| 452 |
+
if fecha_hasta_op is not None:
|
| 453 |
+
kwargs['arg9'] = fecha_hasta_op
|
| 454 |
+
if fecha_desde_t is not None:
|
| 455 |
+
kwargs['arg10'] = fecha_desde_t
|
| 456 |
+
if fecha_hasta_t is not None:
|
| 457 |
+
kwargs['arg11'] = fecha_hasta_t
|
| 458 |
+
if fecha_desde_v is not None:
|
| 459 |
+
kwargs['arg12'] = fecha_desde_v
|
| 460 |
+
if fecha_hasta_v is not None:
|
| 461 |
+
kwargs['arg13'] = fecha_hasta_v
|
| 462 |
+
if n_remito is not None:
|
| 463 |
+
kwargs['arg14'] = n_remito
|
| 464 |
+
if n_factura is not None:
|
| 465 |
+
kwargs['arg15'] = n_factura
|
| 466 |
+
if estado is not None:
|
| 467 |
+
kwargs['arg16'] = estado
|
| 468 |
+
if lote is not None:
|
| 469 |
+
kwargs['arg17'] = lote
|
| 470 |
+
if numero_serial is not None:
|
| 471 |
+
kwargs['arg18'] = numero_serial
|
| 472 |
+
|
| 473 |
+
# llamo al webservice
|
| 474 |
+
res = self.client.getTransaccionesNoConfirmadas(
|
| 475 |
+
arg0=usuario,
|
| 476 |
+
arg1=password,
|
| 477 |
+
**kwargs
|
| 478 |
+
)
|
| 479 |
+
ret = res['return']
|
| 480 |
+
if ret:
|
| 481 |
+
self.__analizar_errores(ret)
|
| 482 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 483 |
+
self.HayError = ret.get('hay_error')
|
| 484 |
+
self.TransaccionPlainWS = [it for it in ret.get('list', [])]
|
| 485 |
+
return True
|
| 486 |
+
|
| 487 |
+
def LeerTransaccion(self):
|
| 488 |
+
"Recorro TransaccionPlainWS devuelto por GetTransaccionesNoConfirmadas"
|
| 489 |
+
# usar GetParametro para consultar el valor retornado por el webservice
|
| 490 |
+
|
| 491 |
+
if self.TransaccionPlainWS:
|
| 492 |
+
# extraigo el primer item
|
| 493 |
+
self.params_out = self.TransaccionPlainWS.pop(0)
|
| 494 |
+
return True
|
| 495 |
+
else:
|
| 496 |
+
# limpio los parοΏ½metros
|
| 497 |
+
self.params_out = {}
|
| 498 |
+
return False
|
| 499 |
+
|
| 500 |
+
def LeerError(self):
|
| 501 |
+
"Recorro los errores devueltos y devuelvo el primero si existe"
|
| 502 |
+
|
| 503 |
+
if self.Errores:
|
| 504 |
+
# extraigo el primer item
|
| 505 |
+
er = self.Errores.pop(0)
|
| 506 |
+
return er
|
| 507 |
+
else:
|
| 508 |
+
return ""
|
| 509 |
+
|
| 510 |
+
@inicializar_y_capturar_excepciones
|
| 511 |
+
def GetEnviosPropiosAlertados(self, usuario, password,
|
| 512 |
+
p_id_transaccion_global=None, id_agente_informador=None,
|
| 513 |
+
id_agente_origen=None, id_agente_destino=None,
|
| 514 |
+
id_medicamento=None, id_evento=None,
|
| 515 |
+
fecha_desde_op=None, fecha_hasta_op=None,
|
| 516 |
+
fecha_desde_t=None, fecha_hasta_t=None,
|
| 517 |
+
fecha_desde_v=None, fecha_hasta_v=None,
|
| 518 |
+
n_remito=None, n_factura=None,
|
| 519 |
+
):
|
| 520 |
+
"Obtiene las distribuciones y envοΏ½os propios que han sido alertados"
|
| 521 |
+
|
| 522 |
+
# preparo los parametros de entrada opcionales:
|
| 523 |
+
kwargs = {}
|
| 524 |
+
if p_id_transaccion_global is not None:
|
| 525 |
+
kwargs['arg2'] = p_id_transaccion_global
|
| 526 |
+
if id_agente_informador is not None:
|
| 527 |
+
kwargs['arg3'] = id_agente_informador
|
| 528 |
+
if id_agente_origen is not None:
|
| 529 |
+
kwargs['arg4'] = id_agente_origen
|
| 530 |
+
if id_agente_destino is not None:
|
| 531 |
+
kwargs['arg5'] = id_agente_destino
|
| 532 |
+
if id_medicamento is not None:
|
| 533 |
+
kwargs['arg6'] = id_medicamento
|
| 534 |
+
if id_evento is not None:
|
| 535 |
+
kwargs['arg7'] = id_evento
|
| 536 |
+
if fecha_desde_op is not None:
|
| 537 |
+
kwargs['arg8'] = fecha_desde_op
|
| 538 |
+
if fecha_hasta_op is not None:
|
| 539 |
+
kwargs['arg9'] = fecha_hasta_op
|
| 540 |
+
if fecha_desde_t is not None:
|
| 541 |
+
kwargs['arg10'] = fecha_desde_t
|
| 542 |
+
if fecha_hasta_t is not None:
|
| 543 |
+
kwargs['arg11'] = fecha_hasta_t
|
| 544 |
+
if fecha_desde_v is not None:
|
| 545 |
+
kwargs['arg12'] = fecha_desde_v
|
| 546 |
+
if fecha_hasta_v is not None:
|
| 547 |
+
kwargs['arg13'] = fecha_hasta_v
|
| 548 |
+
if n_remito is not None:
|
| 549 |
+
kwargs['arg14'] = n_remito
|
| 550 |
+
if n_factura is not None:
|
| 551 |
+
kwargs['arg15'] = n_factura
|
| 552 |
+
|
| 553 |
+
# llamo al webservice
|
| 554 |
+
res = self.client.getEnviosPropiosAlertados(
|
| 555 |
+
arg0=usuario,
|
| 556 |
+
arg1=password,
|
| 557 |
+
**kwargs
|
| 558 |
+
)
|
| 559 |
+
ret = res['return']
|
| 560 |
+
if ret:
|
| 561 |
+
self.__analizar_errores(ret)
|
| 562 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 563 |
+
self.HayError = ret.get('hay_error')
|
| 564 |
+
self.TransaccionPlainWS = [it for it in ret.get('list', [])]
|
| 565 |
+
return True
|
| 566 |
+
|
| 567 |
+
@inicializar_y_capturar_excepciones
|
| 568 |
+
def GetTransaccionesWS(self, usuario, password,
|
| 569 |
+
p_id_transaccion_global=None,
|
| 570 |
+
id_agente_origen=None, id_agente_destino=None,
|
| 571 |
+
id_medicamento=None, id_evento=None,
|
| 572 |
+
fecha_desde_op=None, fecha_hasta_op=None,
|
| 573 |
+
fecha_desde_t=None, fecha_hasta_t=None,
|
| 574 |
+
fecha_desde_v=None, fecha_hasta_v=None,
|
| 575 |
+
n_remito=None, n_factura=None,
|
| 576 |
+
id_estado=None, nro_pag=None,
|
| 577 |
+
):
|
| 578 |
+
"Obtiene los movimientos realizados y permite filtros de bοΏ½squeda"
|
| 579 |
+
|
| 580 |
+
# preparo los parametros de entrada opcionales:
|
| 581 |
+
kwargs = {}
|
| 582 |
+
if p_id_transaccion_global is not None:
|
| 583 |
+
kwargs['arg2'] = p_id_transaccion_global
|
| 584 |
+
if id_agente_origen is not None:
|
| 585 |
+
kwargs['arg3'] = id_agente_origen
|
| 586 |
+
if id_agente_destino is not None:
|
| 587 |
+
kwargs['arg4'] = id_agente_destino
|
| 588 |
+
if id_medicamento is not None:
|
| 589 |
+
kwargs['arg5'] = id_medicamento
|
| 590 |
+
if id_evento is not None:
|
| 591 |
+
kwargs['arg6'] = id_evento
|
| 592 |
+
if fecha_desde_op is not None:
|
| 593 |
+
kwargs['arg7'] = fecha_desde_op
|
| 594 |
+
if fecha_hasta_op is not None:
|
| 595 |
+
kwargs['arg8'] = fecha_hasta_op
|
| 596 |
+
if fecha_desde_t is not None:
|
| 597 |
+
kwargs['arg9'] = fecha_desde_t
|
| 598 |
+
if fecha_hasta_t is not None:
|
| 599 |
+
kwargs['arg10'] = fecha_hasta_t
|
| 600 |
+
if fecha_desde_v is not None:
|
| 601 |
+
kwargs['arg11'] = fecha_desde_v
|
| 602 |
+
if fecha_hasta_v is not None:
|
| 603 |
+
kwargs['arg12'] = fecha_hasta_v
|
| 604 |
+
if n_remito is not None:
|
| 605 |
+
kwargs['arg13'] = n_remito
|
| 606 |
+
if n_factura is not None:
|
| 607 |
+
kwargs['arg14'] = n_factura
|
| 608 |
+
if id_estado is not None:
|
| 609 |
+
kwargs['arg15'] = id_estado
|
| 610 |
+
if nro_pag is not None:
|
| 611 |
+
kwargs['arg16'] = nro_pag
|
| 612 |
+
|
| 613 |
+
# llamo al webservice
|
| 614 |
+
res = self.client.getTransaccionesWS(
|
| 615 |
+
arg0=usuario,
|
| 616 |
+
arg1=password,
|
| 617 |
+
**kwargs
|
| 618 |
+
)
|
| 619 |
+
ret = res['return']
|
| 620 |
+
if ret:
|
| 621 |
+
self.__analizar_errores(ret)
|
| 622 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 623 |
+
self.HayError = ret.get('hay_error')
|
| 624 |
+
self.TransaccionPlainWS = [it for it in ret.get('list', [])]
|
| 625 |
+
return True
|
| 626 |
+
|
| 627 |
+
@inicializar_y_capturar_excepciones
|
| 628 |
+
def GetCatalogoElectronicoByGTIN(self, usuario, password,
|
| 629 |
+
cuit_fabricante=None, gtin=None, descripcion=None,
|
| 630 |
+
id_monodroga=None,
|
| 631 |
+
):
|
| 632 |
+
"Obtiene el CatοΏ½logo ElectrοΏ½nico de Medicamentos"
|
| 633 |
+
|
| 634 |
+
# preparo los parametros de entrada opcionales:
|
| 635 |
+
kwargs = {}
|
| 636 |
+
if cuit_fabricante is not None:
|
| 637 |
+
kwargs['arg2'] = cuit_fabricante
|
| 638 |
+
if gtin is not None:
|
| 639 |
+
kwargs['arg3'] = gtin
|
| 640 |
+
if descripcion is not None:
|
| 641 |
+
kwargs['arg4'] = descripcion
|
| 642 |
+
if id_monodroga is not None:
|
| 643 |
+
kwargs['arg5'] = id_monodroga
|
| 644 |
+
|
| 645 |
+
# llamo al webservice
|
| 646 |
+
res = self.client.getCatalogoElectronicoByGTIN(
|
| 647 |
+
arg0=usuario,
|
| 648 |
+
arg1=password,
|
| 649 |
+
**kwargs
|
| 650 |
+
)
|
| 651 |
+
ret = res['return']
|
| 652 |
+
if ret:
|
| 653 |
+
self.__analizar_errores(ret)
|
| 654 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 655 |
+
self.HayError = ret.get('hay_error')
|
| 656 |
+
self.params_out = dict([(i, it) for i, it
|
| 657 |
+
in enumerate(ret.get('list', []))])
|
| 658 |
+
return len(self.params_out)
|
| 659 |
+
else:
|
| 660 |
+
return 0
|
| 661 |
+
|
| 662 |
+
@inicializar_y_capturar_excepciones
|
| 663 |
+
def GetConsultaStock(self, usuario, password,
|
| 664 |
+
id_medicamento=None, id_agente=None, descripcion=None,
|
| 665 |
+
cantidad=None, presentacion=None,
|
| 666 |
+
lote=None, numero_serial=None,
|
| 667 |
+
nro_pag=1, cant_reg=100,
|
| 668 |
+
):
|
| 669 |
+
"Permite consultar el stock actual del agente."
|
| 670 |
+
|
| 671 |
+
# preparo los parametros de entrada opcionales:
|
| 672 |
+
kwargs = {}
|
| 673 |
+
if id_medicamento is not None:
|
| 674 |
+
kwargs['arg2'] = id_medicamento
|
| 675 |
+
if id_agente is not None:
|
| 676 |
+
kwargs['arg3'] = id_agente
|
| 677 |
+
if descripcion is not None:
|
| 678 |
+
kwargs['arg4'] = descripcion
|
| 679 |
+
if cantidad is not None:
|
| 680 |
+
kwargs['arg5'] = cantidad
|
| 681 |
+
if presentacion is not None:
|
| 682 |
+
kwargs['arg6'] = presentacion
|
| 683 |
+
if lote is not None:
|
| 684 |
+
kwargs['arg7'] = lote
|
| 685 |
+
if numero_serial is not None:
|
| 686 |
+
kwargs['arg8'] = numero_serial
|
| 687 |
+
if nro_pag is not None:
|
| 688 |
+
kwargs['arg9'] = nro_pag
|
| 689 |
+
if cant_reg is not None:
|
| 690 |
+
kwargs['arg10'] = cant_reg
|
| 691 |
+
|
| 692 |
+
# llamo al webservice
|
| 693 |
+
res = self.client.getConsultaStock(
|
| 694 |
+
arg0=usuario,
|
| 695 |
+
arg1=password,
|
| 696 |
+
**kwargs
|
| 697 |
+
)
|
| 698 |
+
ret = res['return']
|
| 699 |
+
if ret:
|
| 700 |
+
self.__analizar_errores(ret)
|
| 701 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 702 |
+
self.HayError = ret.get('hay_error')
|
| 703 |
+
self.params_out = dict([(i, it) for i, it
|
| 704 |
+
in enumerate(ret.get('list', []))])
|
| 705 |
+
return len(self.params_out)
|
| 706 |
+
else:
|
| 707 |
+
return 0
|
| 708 |
+
|
| 709 |
+
def SetUsername(self, username):
|
| 710 |
+
"Establezco el nombre de usuario"
|
| 711 |
+
self.Username = username
|
| 712 |
+
|
| 713 |
+
def SetPassword(self, password):
|
| 714 |
+
"Establezco la contraseοΏ½a"
|
| 715 |
+
self.Password = password
|
| 716 |
+
|
| 717 |
+
def GetCodigoTransaccion(self):
|
| 718 |
+
"Devuelvo el cοΏ½digo de transacciοΏ½n"
|
| 719 |
+
return self.CodigoTransaccion
|
| 720 |
+
|
| 721 |
+
def GetResultado(self):
|
| 722 |
+
"Devuelvo el resultado"
|
| 723 |
+
return self.Resultado
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
def main():
|
| 727 |
+
"FunciοΏ½n principal de pruebas (obtener CAE)"
|
| 728 |
+
import os
|
| 729 |
+
import time
|
| 730 |
+
import sys
|
| 731 |
+
global WSDL, LOCATION
|
| 732 |
+
|
| 733 |
+
DEBUG = '--debug' in sys.argv
|
| 734 |
+
|
| 735 |
+
ws = TrazaMed()
|
| 736 |
+
|
| 737 |
+
ws.Username = 'testwservice'
|
| 738 |
+
ws.Password = 'testwservicepsw'
|
| 739 |
+
|
| 740 |
+
if '--prod' in sys.argv and not HOMO:
|
| 741 |
+
WSDL = "https://trazabilidad.pami.org.ar:9050/trazamed.WebService"
|
| 742 |
+
print("Usando WSDL:", WSDL)
|
| 743 |
+
sys.argv.pop(sys.argv.index("--prod"))
|
| 744 |
+
|
| 745 |
+
# Inicializo las variables y estructuras para el archivo de intercambio:
|
| 746 |
+
medicamentos = []
|
| 747 |
+
transacciones = []
|
| 748 |
+
errores = []
|
| 749 |
+
formatos = [('Medicamentos', MEDICAMENTOS, medicamentos),
|
| 750 |
+
('Transacciones', TRANSACCIONES, transacciones),
|
| 751 |
+
('Errores', ERRORES, errores),
|
| 752 |
+
]
|
| 753 |
+
|
| 754 |
+
if '--formato' in sys.argv:
|
| 755 |
+
print("Formato:")
|
| 756 |
+
for msg, formato, lista in formatos:
|
| 757 |
+
comienzo = 1
|
| 758 |
+
print("=== %s ===" % msg)
|
| 759 |
+
print("|| %-25s || %-12s || %-5s || %-4s || %-10s ||" % (
|
| 760 |
+
"Nombre", "Tipo", "Long.", "Pos(txt)", "Campo(dbf)"))
|
| 761 |
+
claves = []
|
| 762 |
+
for fmt in formato:
|
| 763 |
+
clave, longitud, tipo = fmt[0:3]
|
| 764 |
+
clave_dbf = dar_nombre_campo_dbf(clave, claves)
|
| 765 |
+
claves.append(clave_dbf)
|
| 766 |
+
print("|| %-25s || %-12s || %5d || %4d || %-10s ||" % (
|
| 767 |
+
clave, tipo, longitud, comienzo, clave_dbf))
|
| 768 |
+
comienzo += longitud
|
| 769 |
+
sys.exit(0)
|
| 770 |
+
|
| 771 |
+
if '--cargar' in sys.argv:
|
| 772 |
+
if '--dbf' in sys.argv:
|
| 773 |
+
leer_dbf(formatos[:1], {})
|
| 774 |
+
elif '--json' in sys.argv:
|
| 775 |
+
for formato in formatos[:1]:
|
| 776 |
+
archivo = open(formato[0].lower() + ".json", "r")
|
| 777 |
+
d = json.load(archivo)
|
| 778 |
+
formato[2].extend(d)
|
| 779 |
+
archivo.close()
|
| 780 |
+
else:
|
| 781 |
+
for formato, campos, lista in formatos[:1]:
|
| 782 |
+
archivo = open(formato.lower() + ".txt", "r")
|
| 783 |
+
for linea in archivo:
|
| 784 |
+
d = leer(linea, campos)
|
| 785 |
+
lista.append(d)
|
| 786 |
+
archivo.close()
|
| 787 |
+
if DEBUG:
|
| 788 |
+
for campo in campos:
|
| 789 |
+
print(campo[0], "=", lista[0][campo[0]])
|
| 790 |
+
|
| 791 |
+
ws.Conectar("", WSDL)
|
| 792 |
+
|
| 793 |
+
if ws.Excepcion:
|
| 794 |
+
print(ws.Excepcion)
|
| 795 |
+
print(ws.Traceback)
|
| 796 |
+
sys.exit(-1)
|
| 797 |
+
|
| 798 |
+
# Datos de pruebas:
|
| 799 |
+
|
| 800 |
+
if '--test' in sys.argv:
|
| 801 |
+
medicamentos.append(dict(
|
| 802 |
+
f_evento=datetime.datetime.now().strftime("%d/%m/%Y"),
|
| 803 |
+
h_evento=datetime.datetime.now().strftime("%H:%M"),
|
| 804 |
+
gln_origen="9999999999918", gln_destino="glnws",
|
| 805 |
+
n_remito="R000100001234", n_factura="A000100001234",
|
| 806 |
+
vencimiento=(datetime.datetime.now() + datetime.timedelta(30)).strftime("%d/%m/%Y"),
|
| 807 |
+
gtin="GTIN1", lote=datetime.datetime.now().strftime("%Y"),
|
| 808 |
+
numero_serial=int(time.time() * 10),
|
| 809 |
+
id_obra_social=None, id_evento=134,
|
| 810 |
+
cuit_origen="20267565393", cuit_destino="20267565393",
|
| 811 |
+
apellido="Reingart", nombres="Mariano",
|
| 812 |
+
tipo_documento="96", n_documento="26756539", sexo="M",
|
| 813 |
+
direccion="Saraza", numero="1234", piso="", depto="",
|
| 814 |
+
localidad="Hurlingham", provincia="Buenos Aires",
|
| 815 |
+
n_postal="1688", fecha_nacimiento="01/01/2000",
|
| 816 |
+
telefono="5555-5555",
|
| 817 |
+
nro_asociado="9999999999999",
|
| 818 |
+
cantidad=None,
|
| 819 |
+
desde_numero_serial=None, hasta_numero_serial=None,
|
| 820 |
+
codigo_transaccion=None,
|
| 821 |
+
))
|
| 822 |
+
if '--testfraccion' in sys.argv:
|
| 823 |
+
medicamentos.append(dict(
|
| 824 |
+
f_evento=datetime.datetime.now().strftime("%d/%m/%Y"),
|
| 825 |
+
h_evento=datetime.datetime.now().strftime("%H:%M"),
|
| 826 |
+
gln_origen="9999999999918", gln_destino="glnws",
|
| 827 |
+
n_remito="1234", n_factura="1234",
|
| 828 |
+
vencimiento=(datetime.datetime.now() + datetime.timedelta(30)).strftime("%d/%m/%Y"),
|
| 829 |
+
gtin="GTIN1", lote=datetime.datetime.now().strftime("%Y"),
|
| 830 |
+
numero_serial=int(time.time() * 10),
|
| 831 |
+
id_obra_social=None, id_evento=134,
|
| 832 |
+
cuit_origen="20267565393", cuit_destino="20267565393",
|
| 833 |
+
apellido="Reingart", nombres="Mariano",
|
| 834 |
+
tipo_documento="96", n_documento="26756539", sexo="M",
|
| 835 |
+
direccion="Saraza", numero="1234", piso="", depto="",
|
| 836 |
+
localidad="Hurlingham", provincia="Buenos Aires",
|
| 837 |
+
n_postal="1688", fecha_nacimiento="01/01/2000",
|
| 838 |
+
telefono="5555-5555",
|
| 839 |
+
nro_asociado="9999999999999",
|
| 840 |
+
cantidad=5,
|
| 841 |
+
desde_numero_serial=None, hasta_numero_serial=None,
|
| 842 |
+
codigo_transaccion=None,
|
| 843 |
+
))
|
| 844 |
+
if '--testdh' in sys.argv:
|
| 845 |
+
medicamentos.append(dict(
|
| 846 |
+
f_evento=datetime.datetime.now().strftime("%d/%m/%Y"),
|
| 847 |
+
h_evento=datetime.datetime.now().strftime("%H:%M"),
|
| 848 |
+
gln_origen="9999999999918", gln_destino="glnws",
|
| 849 |
+
n_remito="1234", n_factura="1234",
|
| 850 |
+
vencimiento=(datetime.datetime.now() + datetime.timedelta(30)).strftime("%d/%m/%Y"),
|
| 851 |
+
gtin="GTIN1", lote=datetime.datetime.now().strftime("%Y"),
|
| 852 |
+
desde_numero_serial=int(time.time() * 10) - 1,
|
| 853 |
+
hasta_numero_serial=int(time.time() * 10) + 1,
|
| 854 |
+
id_obra_social=None, id_evento=134,
|
| 855 |
+
nro_asociado="1234",
|
| 856 |
+
cantidad=None, numero_serial=None,
|
| 857 |
+
codigo_transaccion=None,
|
| 858 |
+
))
|
| 859 |
+
|
| 860 |
+
# Opciones principales:
|
| 861 |
+
|
| 862 |
+
if '--cancela' in sys.argv:
|
| 863 |
+
if '--loadxml' in sys.argv:
|
| 864 |
+
ws.LoadTestXML("trazamed_cancela_err.xml") # cargo respuesta
|
| 865 |
+
ws.SendCancelacTransacc(*sys.argv[sys.argv.index("--cancela") + 1:])
|
| 866 |
+
elif '--cancela_parcial' in sys.argv:
|
| 867 |
+
ws.SendCancelacTransaccParcial(*sys.argv[sys.argv.index("--cancela_parcial") + 1:])
|
| 868 |
+
elif '--confirma' in sys.argv:
|
| 869 |
+
if '--loadxml' in sys.argv:
|
| 870 |
+
ws.LoadTestXML("trazamed_confirma.xml") # cargo respuesta
|
| 871 |
+
ok = ws.SendConfirmaTransacc(usuario="pruebasws", password="pruebasws",
|
| 872 |
+
p_ids_transac="1", f_operacion="31-12-2013")
|
| 873 |
+
if not ok:
|
| 874 |
+
raise RuntimeError(ws.Excepcion)
|
| 875 |
+
ws.SendConfirmaTransacc(*sys.argv[sys.argv.index("--confirma") + 1:])
|
| 876 |
+
elif '--alerta' in sys.argv:
|
| 877 |
+
ws.SendAlertaTransacc(*sys.argv[sys.argv.index("--alerta") + 1:])
|
| 878 |
+
elif '--consulta' in sys.argv:
|
| 879 |
+
if '--alertados' in sys.argv:
|
| 880 |
+
ws.GetEnviosPropiosAlertados(
|
| 881 |
+
*sys.argv[sys.argv.index("--alertados") + 1:]
|
| 882 |
+
)
|
| 883 |
+
elif '--movimientos' in sys.argv:
|
| 884 |
+
ws.GetTransaccionesWS(
|
| 885 |
+
*sys.argv[sys.argv.index("--movimientos") + 1:]
|
| 886 |
+
)
|
| 887 |
+
else:
|
| 888 |
+
ws.GetTransaccionesNoConfirmadas(
|
| 889 |
+
*sys.argv[sys.argv.index("--consulta") + 1:]
|
| 890 |
+
# usuario="pruebasws", password="pruebasws",
|
| 891 |
+
# p_id_transaccion_global="1234",
|
| 892 |
+
# id_agente_informador="1",
|
| 893 |
+
# id_agente_origen="1",
|
| 894 |
+
# id_agente_destino="1",
|
| 895 |
+
# id_medicamento="1",
|
| 896 |
+
# id_evento="1",
|
| 897 |
+
# fecha_desde_op="01/01/2015",
|
| 898 |
+
# fecha_hasta_op="31/12/2013",
|
| 899 |
+
# fecha_desde_t="01/01/2013",
|
| 900 |
+
# fecha_hasta_t="31/12/2013",
|
| 901 |
+
# fecha_desde_v="01/04/2013",
|
| 902 |
+
# fecha_hasta_v="30/04/2013",
|
| 903 |
+
# n_factura=5, n_remito=6,
|
| 904 |
+
# estado=1,
|
| 905 |
+
# lote=88745,
|
| 906 |
+
# numero_serial=894124788,
|
| 907 |
+
)
|
| 908 |
+
print("CantPaginas", ws.CantPaginas)
|
| 909 |
+
print("HayError", ws.HayError)
|
| 910 |
+
# print "TransaccionPlainWS", ws.TransaccionPlainWS
|
| 911 |
+
# parametros comunes de salida (columnas de la tabla):
|
| 912 |
+
claves = [k for k, v, l in TRANSACCIONES]
|
| 913 |
+
# extiendo la lista de resultado para el archivo de intercambio:
|
| 914 |
+
transacciones.extend(ws.TransaccionPlainWS)
|
| 915 |
+
# encabezado de la tabla:
|
| 916 |
+
print("||", "||".join(["%s" % clave for clave in claves]), "||")
|
| 917 |
+
# recorro los datos devueltos (TransaccionPlainWS):
|
| 918 |
+
while ws.LeerTransaccion():
|
| 919 |
+
for clave in claves:
|
| 920 |
+
print("||", ws.GetParametro(clave), end=' ') # imprimo cada fila
|
| 921 |
+
print("||")
|
| 922 |
+
elif '--catalogo' in sys.argv:
|
| 923 |
+
ret = ws.GetCatalogoElectronicoByGTIN(
|
| 924 |
+
*sys.argv[sys.argv.index("--catalogo") + 1:]
|
| 925 |
+
)
|
| 926 |
+
for catalogo in list(ws.params_out.values()):
|
| 927 |
+
print(catalogo) # imprimo cada fila
|
| 928 |
+
elif '--stock' in sys.argv:
|
| 929 |
+
ret = ws.GetConsultaStock(
|
| 930 |
+
*sys.argv[sys.argv.index("--stock") + 1:]
|
| 931 |
+
)
|
| 932 |
+
print("\n".join([str(s) for s in list(ws.params_out.values())]))
|
| 933 |
+
else:
|
| 934 |
+
argv = [argv for argv in sys.argv if not argv.startswith("--")]
|
| 935 |
+
if not medicamentos:
|
| 936 |
+
if len(argv) > 16:
|
| 937 |
+
if '--dh' in sys.argv:
|
| 938 |
+
ws.SendMedicamentosDHSerie(*argv[1:])
|
| 939 |
+
elif '--fraccion' in sys.argv:
|
| 940 |
+
ws.SendMedicamentosFraccion(*argv[1:])
|
| 941 |
+
else:
|
| 942 |
+
ws.SendMedicamentos(*argv[1:])
|
| 943 |
+
else:
|
| 944 |
+
print("ERROR: no se indicaron todos los parοΏ½metros requeridos")
|
| 945 |
+
elif medicamentos:
|
| 946 |
+
try:
|
| 947 |
+
usuario, password = argv[1:3]
|
| 948 |
+
except BaseException:
|
| 949 |
+
print("ADVERTENCIA: no se indico parοΏ½metros usuario y passoword")
|
| 950 |
+
usuario = password = "pruebasws"
|
| 951 |
+
for i, med in enumerate(medicamentos):
|
| 952 |
+
print("Procesando registro", i)
|
| 953 |
+
del med['codigo_transaccion']
|
| 954 |
+
if med.get("cantidad"):
|
| 955 |
+
del med["desde_numero_serial"]
|
| 956 |
+
del med["hasta_numero_serial"]
|
| 957 |
+
ws.SendMedicamentosFraccion(usuario, password, **med)
|
| 958 |
+
elif med.get("desde_numero_serial"):
|
| 959 |
+
del med["cantidad"]
|
| 960 |
+
del med["numero_serial"]
|
| 961 |
+
ws.SendMedicamentosDHSerie(usuario, password, **med)
|
| 962 |
+
else:
|
| 963 |
+
del med["cantidad"]
|
| 964 |
+
del med["desde_numero_serial"]
|
| 965 |
+
del med["hasta_numero_serial"]
|
| 966 |
+
ws.SendMedicamentos(usuario, password, **med)
|
| 967 |
+
med['codigo_transaccion'] = ws.CodigoTransaccion
|
| 968 |
+
errores.extend(ws.errores)
|
| 969 |
+
print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
|
| 970 |
+
ws.Resultado,
|
| 971 |
+
ws.CodigoTransaccion,
|
| 972 |
+
'|'.join(ws.Errores or []),
|
| 973 |
+
))
|
| 974 |
+
else:
|
| 975 |
+
print("ERROR: no se especificaron medicamentos a informar")
|
| 976 |
+
|
| 977 |
+
if not medicamentos:
|
| 978 |
+
print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
|
| 979 |
+
ws.Resultado,
|
| 980 |
+
ws.CodigoTransaccion,
|
| 981 |
+
'|'.join(ws.Errores or []),
|
| 982 |
+
))
|
| 983 |
+
|
| 984 |
+
if ws.Excepcion:
|
| 985 |
+
print(ws.Traceback)
|
| 986 |
+
|
| 987 |
+
if '--grabar' in sys.argv:
|
| 988 |
+
if '--dbf' in sys.argv:
|
| 989 |
+
guardar_dbf(formatos, True, {})
|
| 990 |
+
elif '--json' in sys.argv:
|
| 991 |
+
for formato in formatos:
|
| 992 |
+
archivo = open(formato[0].lower() + ".json", "w")
|
| 993 |
+
json.dump(formato[2], archivo, sort_keys=True, indent=4)
|
| 994 |
+
archivo.close()
|
| 995 |
+
else:
|
| 996 |
+
for formato, campos, lista in formatos:
|
| 997 |
+
archivo = open(formato.lower() + ".txt", "w")
|
| 998 |
+
for it in lista:
|
| 999 |
+
archivo.write(escribir(it, campos))
|
| 1000 |
+
archivo.close()
|
| 1001 |
+
|
| 1002 |
+
|
| 1003 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 1004 |
+
INSTALL_DIR = TrazaMed.InstallDir = get_install_dir()
|
| 1005 |
+
|
| 1006 |
+
|
| 1007 |
+
if __name__ == '__main__':
|
| 1008 |
+
|
| 1009 |
+
# ajusto el encoding por defecto (si se redirije la salida)
|
| 1010 |
+
if not hasattr(sys.stdout, "encoding") or sys.stdout.encoding is None:
|
| 1011 |
+
import codecs
|
| 1012 |
+
import locale
|
| 1013 |
+
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout, "replace")
|
| 1014 |
+
sys.stderr = codecs.getwriter(locale.getpreferredencoding())(sys.stderr, "replace")
|
| 1015 |
+
|
| 1016 |
+
if '--register' in sys.argv or '--unregister' in sys.argv:
|
| 1017 |
+
import pythoncom
|
| 1018 |
+
if TYPELIB:
|
| 1019 |
+
if '--register' in sys.argv:
|
| 1020 |
+
tlb = os.path.abspath(os.path.join(INSTALL_DIR, "typelib", "trazamed.tlb"))
|
| 1021 |
+
print("Registering %s" % (tlb,))
|
| 1022 |
+
tli = pythoncom.LoadTypeLib(tlb)
|
| 1023 |
+
pythoncom.RegisterTypeLib(tli, tlb)
|
| 1024 |
+
elif '--unregister' in sys.argv:
|
| 1025 |
+
k = TrazaMed
|
| 1026 |
+
pythoncom.UnRegisterTypeLib(k._typelib_guid_,
|
| 1027 |
+
k._typelib_version_[0],
|
| 1028 |
+
k._typelib_version_[1],
|
| 1029 |
+
0,
|
| 1030 |
+
pythoncom.SYS_WIN32)
|
| 1031 |
+
print("Unregistered typelib")
|
| 1032 |
+
import win32com.server.register
|
| 1033 |
+
win32com.server.register.UseCommandLine(TrazaMed)
|
| 1034 |
+
elif "/Automate" in sys.argv:
|
| 1035 |
+
# MS seems to like /automate to run the class factories.
|
| 1036 |
+
import win32com.server.localserver
|
| 1037 |
+
# win32com.server.localserver.main()
|
| 1038 |
+
# start the server.
|
| 1039 |
+
win32com.server.localserver.serve([TrazaMed._reg_clsid_])
|
| 1040 |
+
else:
|
| 1041 |
+
main()
|
app/pyafipws/trazaprodmed.py
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"""MοΏ½dulo para Trazabilidad de Productos MοΏ½dicos ANMAT - Disp. 2303/2014
|
| 14 |
+
segοΏ½n EspecificaciοΏ½n TοΏ½cnica para Pruebas de Servicios (17/09/2015)"""
|
| 15 |
+
|
| 16 |
+
# InformaciοΏ½n adicional y documentaciοΏ½n:
|
| 17 |
+
# http://www.sistemasagiles.com.ar/trac/wiki/TrazabilidadProductosMedicos
|
| 18 |
+
|
| 19 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 20 |
+
__copyright__ = "Copyright (C) 2016 Mariano Reingart"
|
| 21 |
+
__license__ = "GPL 3.0"
|
| 22 |
+
__version__ = "1.01b"
|
| 23 |
+
|
| 24 |
+
import os
|
| 25 |
+
import socket
|
| 26 |
+
import sys
|
| 27 |
+
import datetime
|
| 28 |
+
import time
|
| 29 |
+
import traceback
|
| 30 |
+
import pysimplesoap.client
|
| 31 |
+
from pysimplesoap.client import SoapClient, SoapFault, parse_proxy, \
|
| 32 |
+
set_http_wrapper
|
| 33 |
+
from pysimplesoap.simplexml import SimpleXMLElement
|
| 34 |
+
from io import StringIO
|
| 35 |
+
|
| 36 |
+
# importo funciones compartidas:
|
| 37 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, dar_nombre_campo_dbf, get_install_dir, BaseWS, inicializar_y_capturar_excepciones
|
| 38 |
+
|
| 39 |
+
HOMO = False
|
| 40 |
+
TYPELIB = False
|
| 41 |
+
|
| 42 |
+
WSDL = "https://servicios.pami.org.ar/trazaenprodmed.WebService?wsdl"
|
| 43 |
+
WSDL_PROD = "https://servicios.pami.org.ar/trazaprodmed.WebService?wsdl"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class TrazaProdMed(BaseWS):
|
| 47 |
+
"Interfaz para el WebService de Trazabilidad de Productos MοΏ½dicos ANMAT - PAMI - INSSJP"
|
| 48 |
+
_public_methods_ = ['InformarProducto', 'CrearTransaccion',
|
| 49 |
+
'SendCancelacTransacc', 'SendCancelacTransaccParcial',
|
| 50 |
+
'GetTransaccionesWS', 'GetCatalogoElectronicoByGTIN',
|
| 51 |
+
'GetCatalogoElectronicoByGLN', 'GetMedico',
|
| 52 |
+
'Conectar', 'LeerError', 'LeerTransaccion',
|
| 53 |
+
'SetUsername', 'SetPassword',
|
| 54 |
+
'SetParametro', 'GetParametro',
|
| 55 |
+
'GetCodigoTransaccion', 'GetResultado', 'LoadTestXML']
|
| 56 |
+
|
| 57 |
+
_public_attrs_ = [
|
| 58 |
+
'Username', 'Password',
|
| 59 |
+
'CodigoTransaccion', 'Errores', 'Resultado',
|
| 60 |
+
'XmlRequest', 'XmlResponse',
|
| 61 |
+
'Version', 'InstallDir',
|
| 62 |
+
'Traceback', 'Excepcion', 'LanzarExcepciones',
|
| 63 |
+
'CantPaginas', 'HayError', 'TransaccionesWS',
|
| 64 |
+
]
|
| 65 |
+
|
| 66 |
+
_reg_progid_ = "TrazaProdMed"
|
| 67 |
+
_reg_clsid_ = "{D4112556-EF2E-45D3-A2A2-7A2849A364D9}"
|
| 68 |
+
|
| 69 |
+
# Variables globales para BaseWS:
|
| 70 |
+
HOMO = HOMO
|
| 71 |
+
WSDL = WSDL
|
| 72 |
+
Version = "%s %s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '',
|
| 73 |
+
pysimplesoap.client.__version__)
|
| 74 |
+
|
| 75 |
+
def __init__(self, reintentos=1):
|
| 76 |
+
self.Username = self.Password = None
|
| 77 |
+
self.Transacciones = []
|
| 78 |
+
BaseWS.__init__(self, reintentos)
|
| 79 |
+
|
| 80 |
+
def inicializar(self):
|
| 81 |
+
BaseWS.inicializar(self)
|
| 82 |
+
self.CodigoTransaccion = self.Errores = self.Resultado = None
|
| 83 |
+
self.Resultado = ''
|
| 84 |
+
self.Errores = [] # lista de strings para la interfaz
|
| 85 |
+
self.errores = [] # lista de diccionarios (uso interno)
|
| 86 |
+
self.CantPaginas = self.HayError = None
|
| 87 |
+
|
| 88 |
+
def __analizar_errores(self, ret):
|
| 89 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 90 |
+
self.errores = ret.get('errores', [])
|
| 91 |
+
self.Errores = ["%s: %s" % (it['c_error'], it['d_error'])
|
| 92 |
+
for it in ret.get('errores', [])]
|
| 93 |
+
self.Resultado = ret.get('resultado')
|
| 94 |
+
|
| 95 |
+
def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, timeout=30):
|
| 96 |
+
# Conecto usando el mοΏ½todo estandard:
|
| 97 |
+
print("timeout", timeout)
|
| 98 |
+
ok = BaseWS.Conectar(self, cache, wsdl, proxy, wrapper, cacert, timeout,
|
| 99 |
+
soap_server="jetty")
|
| 100 |
+
|
| 101 |
+
if ok:
|
| 102 |
+
# Establecer credenciales de seguridad:
|
| 103 |
+
self.client['wsse:Security'] = {
|
| 104 |
+
'wsse:UsernameToken': {
|
| 105 |
+
'wsse:Username': self.Username,
|
| 106 |
+
'wsse:Password': self.Password,
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
return ok
|
| 110 |
+
|
| 111 |
+
@inicializar_y_capturar_excepciones
|
| 112 |
+
def CrearTransaccion(self,
|
| 113 |
+
f_evento, h_evento, gln_origen, gln_destino,
|
| 114 |
+
n_remito, n_factura, vencimiento, gtin, lote,
|
| 115 |
+
numero_serial, id_evento,
|
| 116 |
+
cuit_medico=None, id_obra_social=None, apellido='', nombres='',
|
| 117 |
+
tipo_documento='', n_documento='', sexo='',
|
| 118 |
+
calle='', numero='', piso='', depto='', localidad='',
|
| 119 |
+
provincia='', n_postal='', fecha_nacimiento='', telefono='',
|
| 120 |
+
nro_afiliado=None, cod_diagnostico=None, cod_hiv=None,
|
| 121 |
+
id_motivo_devolucion=None, otro_motivo_devolucion=None):
|
| 122 |
+
"Inicializa internamente una estructura TransaccionDTO para informar"
|
| 123 |
+
# creo la transacciοΏ½n con los parοΏ½metros para llamar a InformarProducto
|
| 124 |
+
tx = {
|
| 125 |
+
'fEvento': f_evento, 'hEvento': h_evento,
|
| 126 |
+
'glnOrigen': gln_origen, 'glnDestino': gln_destino,
|
| 127 |
+
'nroFactura': n_factura, 'nroRemito': n_remito,
|
| 128 |
+
'vencimiento': vencimiento,
|
| 129 |
+
'gtin': gtin, 'idEvento': id_evento,
|
| 130 |
+
'nroSerial': numero_serial, 'lote': lote,
|
| 131 |
+
'cuitMedico': cuit_medico,
|
| 132 |
+
'apellidos': apellido, 'nombres': nombres, 'telefono': telefono,
|
| 133 |
+
'calle': calle, 'nroCalle': numero, 'departamento': depto,
|
| 134 |
+
'piso': piso, 'localidad': localidad, 'provincia': provincia,
|
| 135 |
+
'codPostal': n_postal,
|
| 136 |
+
'fechaNacimiento': fecha_nacimiento, 'sexo': sexo,
|
| 137 |
+
'idTipoDocumento': tipo_documento, 'nroDocumento': n_documento,
|
| 138 |
+
'codDiagnostico': cod_diagnostico, 'codHiv': cod_hiv,
|
| 139 |
+
'idObraSocial': id_obra_social, 'nroAfiliado': nro_afiliado,
|
| 140 |
+
'idMotivoDevolucion': id_motivo_devolucion,
|
| 141 |
+
'otroMotivoDevolucion': otro_motivo_devolucion,
|
| 142 |
+
}
|
| 143 |
+
self.Transacciones.append(tx)
|
| 144 |
+
return True
|
| 145 |
+
|
| 146 |
+
@inicializar_y_capturar_excepciones
|
| 147 |
+
def InformarProducto(self, usuario, password):
|
| 148 |
+
"Realiza el registro de una transacciοΏ½n de producto. "
|
| 149 |
+
# El usuario (titular del registro/distribuidor/mοΏ½dico/establecimiento
|
| 150 |
+
# asistencial) informa el evento ocurrido para cada uno de los productos.
|
| 151 |
+
res = self.client.informarProducto(
|
| 152 |
+
transacciones=self.Transacciones,
|
| 153 |
+
usuario=usuario,
|
| 154 |
+
password=password,
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
ret = res['return']
|
| 158 |
+
|
| 159 |
+
self.CodigoTransaccion = ret['codigoTransaccion']
|
| 160 |
+
self.__analizar_errores(ret)
|
| 161 |
+
|
| 162 |
+
return True
|
| 163 |
+
|
| 164 |
+
@inicializar_y_capturar_excepciones
|
| 165 |
+
def SendCancelacTransacc(self, usuario, password, codigo_transaccion):
|
| 166 |
+
" Realiza la cancelaciοΏ½n de una transacciοΏ½n"
|
| 167 |
+
res = self.client.sendCancelacTransacc(
|
| 168 |
+
transaccion=codigo_transaccion,
|
| 169 |
+
usuario=usuario,
|
| 170 |
+
password=password,
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
ret = res['return']
|
| 174 |
+
|
| 175 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 176 |
+
self.__analizar_errores(ret)
|
| 177 |
+
|
| 178 |
+
return True
|
| 179 |
+
|
| 180 |
+
@inicializar_y_capturar_excepciones
|
| 181 |
+
def SendCancelacTransaccParcial(self, usuario, password, codigo_transaccion,
|
| 182 |
+
gtin=None, numero_serial=None):
|
| 183 |
+
" Realiza la cancelaciοΏ½n parcial de una transacciοΏ½n"
|
| 184 |
+
res = self.client.sendCancelacTransaccParcial(
|
| 185 |
+
transaccion=codigo_transaccion,
|
| 186 |
+
usuario=usuario,
|
| 187 |
+
password=password,
|
| 188 |
+
gtin=gtin,
|
| 189 |
+
serie=numero_serial,
|
| 190 |
+
)
|
| 191 |
+
ret = res['return']
|
| 192 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 193 |
+
self.__analizar_errores(ret)
|
| 194 |
+
return True
|
| 195 |
+
|
| 196 |
+
def LeerTransaccion(self):
|
| 197 |
+
"Recorro Transacciones devueltas por GetTransaccionesWS"
|
| 198 |
+
# usar GetParametro para consultar el valor retornado por el webservice
|
| 199 |
+
|
| 200 |
+
if self.Transacciones:
|
| 201 |
+
# extraigo el primer item
|
| 202 |
+
self.params_out = self.Transacciones.pop(0)
|
| 203 |
+
return True
|
| 204 |
+
else:
|
| 205 |
+
# limpio los parοΏ½metros
|
| 206 |
+
self.params_out = {}
|
| 207 |
+
return False
|
| 208 |
+
|
| 209 |
+
def LeerError(self):
|
| 210 |
+
"Recorro los errores devueltos y devuelvo el primero si existe"
|
| 211 |
+
|
| 212 |
+
if self.Errores:
|
| 213 |
+
# extraigo el primer item
|
| 214 |
+
er = self.Errores.pop(0)
|
| 215 |
+
return er
|
| 216 |
+
else:
|
| 217 |
+
return ""
|
| 218 |
+
|
| 219 |
+
@inicializar_y_capturar_excepciones
|
| 220 |
+
def GetTransaccionesWS(self, usuario, password,
|
| 221 |
+
id_transaccion=None,
|
| 222 |
+
gln_agente_origen=None, gln_agente_destino=None,
|
| 223 |
+
gtin=None, lote=None, serie=None, id_evento=None,
|
| 224 |
+
fecha_desde_op=None, fecha_hasta_op=None,
|
| 225 |
+
fecha_desde_t=None, fecha_hasta_t=None,
|
| 226 |
+
fecha_desde_v=None, fecha_hasta_v=None,
|
| 227 |
+
n_remito=None, n_factura=None,
|
| 228 |
+
id_provincia=None, id_estado=None, nro_pag=1, offset=100,
|
| 229 |
+
):
|
| 230 |
+
"Obtiene los movimientos realizados y permite filtros de bοΏ½squeda"
|
| 231 |
+
|
| 232 |
+
# preparo los parametros de entrada opcionales:
|
| 233 |
+
kwargs = {}
|
| 234 |
+
if id_transaccion is not None:
|
| 235 |
+
kwargs['idTransaccion'] = id_transaccion
|
| 236 |
+
if gln_agente_origen is not None:
|
| 237 |
+
kwargs['glnAgenteOrigen'] = gln_agente_origen
|
| 238 |
+
if gln_agente_destino is not None:
|
| 239 |
+
kwargs['glnAgenteDestino'] = gln_agente_destino
|
| 240 |
+
if gtin is not None:
|
| 241 |
+
kwargs['gtin'] = gtin
|
| 242 |
+
if lote is not None:
|
| 243 |
+
kwargs['lote'] = lote
|
| 244 |
+
if serie is not None:
|
| 245 |
+
kwargs['serie'] = serie
|
| 246 |
+
if id_evento is not None:
|
| 247 |
+
kwargs['idEvento'] = id_evento
|
| 248 |
+
if fecha_desde_op is not None:
|
| 249 |
+
kwargs['fechaOperacionDesde'] = fecha_desde_op
|
| 250 |
+
if fecha_hasta_op is not None:
|
| 251 |
+
kwargs['fechaOperacionHasta'] = fecha_hasta_op
|
| 252 |
+
if fecha_desde_t is not None:
|
| 253 |
+
kwargs['fechaTransaccionDesde'] = fecha_desde_t
|
| 254 |
+
if fecha_hasta_t is not None:
|
| 255 |
+
kwargs['fechaTransaccionHasta'] = fecha_hasta_t
|
| 256 |
+
if fecha_desde_v is not None:
|
| 257 |
+
kwargs['fechaVencimientoDesde'] = fecha_desde_v
|
| 258 |
+
if fecha_hasta_v is not None:
|
| 259 |
+
kwargs['fechaVencimientoHasta'] = fecha_hasta_v
|
| 260 |
+
if n_remito is not None:
|
| 261 |
+
kwargs['remito'] = n_remito
|
| 262 |
+
if n_factura is not None:
|
| 263 |
+
kwargs['factura'] = n_factura
|
| 264 |
+
if id_provincia is not None:
|
| 265 |
+
kwargs['idProvincia'] = id_provincia
|
| 266 |
+
if id_estado is not None:
|
| 267 |
+
kwargs['idEstadoTransaccion'] = id_estado
|
| 268 |
+
if nro_pag is not None:
|
| 269 |
+
kwargs['pagina'] = nro_pag
|
| 270 |
+
if offset is not None:
|
| 271 |
+
kwargs['offset'] = offset
|
| 272 |
+
|
| 273 |
+
# llamo al webservice
|
| 274 |
+
res = self.client.getTransaccionesWS(
|
| 275 |
+
usuario=usuario,
|
| 276 |
+
password=password,
|
| 277 |
+
**kwargs
|
| 278 |
+
)
|
| 279 |
+
ret = res['return']
|
| 280 |
+
if ret:
|
| 281 |
+
self.__analizar_errores(ret)
|
| 282 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 283 |
+
self.HayError = ret.get('hay_error')
|
| 284 |
+
self.Transacciones = [it for it in ret.get('list', [])]
|
| 285 |
+
return True
|
| 286 |
+
|
| 287 |
+
@inicializar_y_capturar_excepciones
|
| 288 |
+
def GetCatalogoElectronicoByGTIN(self, usuario, password,
|
| 289 |
+
gtin=None, gln=None, marca=None, modelo=None,
|
| 290 |
+
cuit=None, id_nombre_generico=None, nro_pag=1, offset=100,
|
| 291 |
+
):
|
| 292 |
+
"Obtiene el CatοΏ½logo ElectrοΏ½nico de Medicamentos"
|
| 293 |
+
|
| 294 |
+
# preparo los parametros de entrada opcionales:
|
| 295 |
+
kwargs = {}
|
| 296 |
+
if cuit is not None:
|
| 297 |
+
kwargs['cuit'] = cuit
|
| 298 |
+
if gtin is not None:
|
| 299 |
+
kwargs['gtin'] = gtin
|
| 300 |
+
if gln is not None:
|
| 301 |
+
kwargs['gln'] = gln
|
| 302 |
+
if marca is not None:
|
| 303 |
+
kwargs['marca'] = marca
|
| 304 |
+
if modelo is not None:
|
| 305 |
+
kwargs['modelo'] = modelo
|
| 306 |
+
if id_nombre_generico is not None:
|
| 307 |
+
kwargs['id_nombre_generico'] = id_nombre_generico
|
| 308 |
+
if nro_pag is not None:
|
| 309 |
+
kwargs['pagina'] = nro_pag
|
| 310 |
+
if offset is not None:
|
| 311 |
+
kwargs['offset'] = offset
|
| 312 |
+
|
| 313 |
+
# llamo al webservice
|
| 314 |
+
res = self.client.getCatalogoElectronicoByGTIN(
|
| 315 |
+
usuario=usuario,
|
| 316 |
+
password=password,
|
| 317 |
+
**kwargs
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
ret = res['return']
|
| 321 |
+
if ret:
|
| 322 |
+
self.__analizar_errores(ret)
|
| 323 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 324 |
+
self.HayError = ret.get('hay_error')
|
| 325 |
+
self.params_out = dict([(i, it) for i, it
|
| 326 |
+
in enumerate(ret.get('lstProductos', []))])
|
| 327 |
+
return len(self.params_out)
|
| 328 |
+
else:
|
| 329 |
+
return 0
|
| 330 |
+
|
| 331 |
+
def SetUsername(self, username):
|
| 332 |
+
"Establezco el nombre de usuario"
|
| 333 |
+
self.Username = username
|
| 334 |
+
|
| 335 |
+
def SetPassword(self, password):
|
| 336 |
+
"Establezco la contraseοΏ½a"
|
| 337 |
+
self.Password = password
|
| 338 |
+
|
| 339 |
+
def GetCodigoTransaccion(self):
|
| 340 |
+
"Devuelvo el cοΏ½digo de transacciοΏ½n"
|
| 341 |
+
return self.CodigoTransaccion
|
| 342 |
+
|
| 343 |
+
def GetResultado(self):
|
| 344 |
+
"Devuelvo el resultado"
|
| 345 |
+
return self.Resultado
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def main():
|
| 349 |
+
"FunciοΏ½n principal de pruebas (obtener CAE)"
|
| 350 |
+
import os
|
| 351 |
+
import time
|
| 352 |
+
import sys
|
| 353 |
+
global WSDL, LOCATION
|
| 354 |
+
|
| 355 |
+
DEBUG = '--debug' in sys.argv
|
| 356 |
+
|
| 357 |
+
ws = TrazaProdMed()
|
| 358 |
+
|
| 359 |
+
ws.Username = 'testwservice'
|
| 360 |
+
ws.Password = 'testwservicepsw'
|
| 361 |
+
|
| 362 |
+
if '--prod' in sys.argv and not HOMO:
|
| 363 |
+
WSDL = WSDL_PROD
|
| 364 |
+
print("Usando WSDL:", WSDL)
|
| 365 |
+
sys.argv.pop(sys.argv.index("--prod"))
|
| 366 |
+
|
| 367 |
+
# Inicializo las variables y estructuras para el archivo de intercambio:
|
| 368 |
+
transacciones = []
|
| 369 |
+
errores = []
|
| 370 |
+
formatos = []
|
| 371 |
+
|
| 372 |
+
if '--formato' in sys.argv:
|
| 373 |
+
print("Formato:")
|
| 374 |
+
for msg, formato, lista in formatos:
|
| 375 |
+
comienzo = 1
|
| 376 |
+
print("=== %s ===" % msg)
|
| 377 |
+
print("|| %-25s || %-12s || %-5s || %-4s || %-10s ||" % (
|
| 378 |
+
"Nombre", "Tipo", "Long.", "Pos(txt)", "Campo(dbf)"))
|
| 379 |
+
claves = []
|
| 380 |
+
for fmt in formato:
|
| 381 |
+
clave, longitud, tipo = fmt[0:3]
|
| 382 |
+
clave_dbf = dar_nombre_campo_dbf(clave, claves)
|
| 383 |
+
claves.append(clave_dbf)
|
| 384 |
+
print("|| %-25s || %-12s || %5d || %4d || %-10s ||" % (
|
| 385 |
+
clave, tipo, longitud, comienzo, clave_dbf))
|
| 386 |
+
comienzo += longitud
|
| 387 |
+
sys.exit(0)
|
| 388 |
+
|
| 389 |
+
if '--cargar' in sys.argv:
|
| 390 |
+
if '--dbf' in sys.argv:
|
| 391 |
+
leer_dbf(formatos[:1], {})
|
| 392 |
+
elif '--json' in sys.argv:
|
| 393 |
+
for formato in formatos[:1]:
|
| 394 |
+
archivo = open(formato[0].lower() + ".json", "r")
|
| 395 |
+
d = json.load(archivo)
|
| 396 |
+
formato[2].extend(d)
|
| 397 |
+
archivo.close()
|
| 398 |
+
else:
|
| 399 |
+
for formato in formatos[:1]:
|
| 400 |
+
archivo = open(formato[0].lower() + ".txt", "r")
|
| 401 |
+
for linea in archivo:
|
| 402 |
+
d = leer(linea, formato[1])
|
| 403 |
+
formato[2].append(d)
|
| 404 |
+
archivo.close()
|
| 405 |
+
|
| 406 |
+
ws.Conectar("", WSDL)
|
| 407 |
+
|
| 408 |
+
if ws.Excepcion:
|
| 409 |
+
print(ws.Excepcion)
|
| 410 |
+
print(ws.Traceback)
|
| 411 |
+
sys.exit(-1)
|
| 412 |
+
|
| 413 |
+
# Datos de pruebas:
|
| 414 |
+
|
| 415 |
+
if '--test' in sys.argv:
|
| 416 |
+
ws.CrearTransaccion(
|
| 417 |
+
f_evento=datetime.datetime.now().strftime("%d/%m/%Y"),
|
| 418 |
+
h_evento=datetime.datetime.now().strftime("%H:%M"),
|
| 419 |
+
gln_origen="7791234567801", gln_destino="7791234567801",
|
| 420 |
+
n_remito="R0001-12341234", n_factura="A0001-12341234",
|
| 421 |
+
vencimiento=(datetime.datetime.now() + datetime.timedelta(30)).strftime("%d/%m/%Y"),
|
| 422 |
+
gtin="07791234567810", lote=datetime.datetime.now().strftime("%Y"), # R4556567
|
| 423 |
+
numero_serial=int(time.time() * 10), # A23434
|
| 424 |
+
id_evento=1,
|
| 425 |
+
cuit_medico="30711622507", id_obra_social=465667,
|
| 426 |
+
apellido="Reingart", nombres="Mariano",
|
| 427 |
+
tipo_documento="96", n_documento="28510785", sexo="M",
|
| 428 |
+
calle="San Martin", numero="5656", piso="", depto="1",
|
| 429 |
+
localidad="Berazategui", provincia="Buenos Aires",
|
| 430 |
+
n_postal="1700", fecha_nacimiento="20/12/1972",
|
| 431 |
+
telefono="5555-5555",
|
| 432 |
+
nro_afiliado="9999999999999",
|
| 433 |
+
cod_diagnostico="B30",
|
| 434 |
+
cod_hiv="NOAP31121970",
|
| 435 |
+
id_motivo_devolucion=1,
|
| 436 |
+
otro_motivo_devolucion="producto fallado",
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
# Opciones principales:
|
| 440 |
+
|
| 441 |
+
if '--cancela' in sys.argv:
|
| 442 |
+
if '--loadxml' in sys.argv:
|
| 443 |
+
ws.LoadTestXML("tests/xml/trazaprodmed_cancela_err.xml") # cargo respuesta
|
| 444 |
+
ws.SendCancelacTransacc(*sys.argv[sys.argv.index("--cancela") + 1:])
|
| 445 |
+
elif '--cancela_parcial' in sys.argv:
|
| 446 |
+
ws.SendCancelacTransaccParcial(*sys.argv[sys.argv.index("--cancela_parcial") + 1:])
|
| 447 |
+
elif '--consulta' in sys.argv:
|
| 448 |
+
ws.GetTransaccionesWS(
|
| 449 |
+
*sys.argv[sys.argv.index("--consulta") + 1:]
|
| 450 |
+
)
|
| 451 |
+
print("CantPaginas", ws.CantPaginas)
|
| 452 |
+
print("HayError", ws.HayError)
|
| 453 |
+
# print "TransaccionPlainWS", ws.TransaccionPlainWS
|
| 454 |
+
# parametros comunes de salida (columnas de la tabla):
|
| 455 |
+
TRANSACCIONES = list(ws.Transacciones[0].keys()) if ws.Transacciones else []
|
| 456 |
+
claves = [k for k in TRANSACCIONES]
|
| 457 |
+
# extiendo la lista de resultado para el archivo de intercambio:
|
| 458 |
+
transacciones.extend(ws.Transacciones)
|
| 459 |
+
# encabezado de la tabla:
|
| 460 |
+
print("||", "||".join(["%s" % clave for clave in claves]), "||")
|
| 461 |
+
# recorro los datos devueltos (TransaccionPlainWS):
|
| 462 |
+
while ws.LeerTransaccion():
|
| 463 |
+
for clave in claves:
|
| 464 |
+
print("||", ws.GetParametro(clave), end=' ') # imprimo cada fila
|
| 465 |
+
print("||")
|
| 466 |
+
elif '--catalogo' in sys.argv:
|
| 467 |
+
ret = ws.GetCatalogoElectronicoByGTIN(
|
| 468 |
+
*sys.argv[sys.argv.index("--catalogo") + 1:]
|
| 469 |
+
)
|
| 470 |
+
for catalogo in list(ws.params_out.values()):
|
| 471 |
+
print(catalogo) # imprimo cada fila
|
| 472 |
+
else:
|
| 473 |
+
argv = [argv for argv in sys.argv if not argv.startswith("--")]
|
| 474 |
+
if not transacciones:
|
| 475 |
+
if len(argv) > 16:
|
| 476 |
+
ws.CrearTransaccion(*argv[3:])
|
| 477 |
+
else:
|
| 478 |
+
print("ERROR: no se indicaron todos los parοΏ½metros requeridos")
|
| 479 |
+
if ws.Transacciones:
|
| 480 |
+
try:
|
| 481 |
+
usuario, password = argv[1:3]
|
| 482 |
+
except BaseException:
|
| 483 |
+
print("ADVERTENCIA: no se indico parοΏ½metros usuario y passoword")
|
| 484 |
+
usuario = password = "pruebasws"
|
| 485 |
+
ws.InformarProducto(usuario, password)
|
| 486 |
+
for i, tx in enumerate(transacciones):
|
| 487 |
+
print("Procesando registro", i)
|
| 488 |
+
tx['codigo_transaccion'] = ws.CodigoTransaccion
|
| 489 |
+
errores.extend(ws.errores)
|
| 490 |
+
print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
|
| 491 |
+
ws.Resultado,
|
| 492 |
+
ws.CodigoTransaccion,
|
| 493 |
+
'|'.join(ws.Errores or []),
|
| 494 |
+
))
|
| 495 |
+
else:
|
| 496 |
+
print("ERROR: no se especificaron productos a informar")
|
| 497 |
+
|
| 498 |
+
if ws.Excepcion:
|
| 499 |
+
print(ws.Traceback)
|
| 500 |
+
|
| 501 |
+
if '--grabar' in sys.argv:
|
| 502 |
+
if '--dbf' in sys.argv:
|
| 503 |
+
guardar_dbf(formatos, True, {})
|
| 504 |
+
elif '--json' in sys.argv:
|
| 505 |
+
for formato in formatos:
|
| 506 |
+
archivo = open(formato[0].lower() + ".json", "w")
|
| 507 |
+
json.dump(formato[2], archivo, sort_keys=True, indent=4)
|
| 508 |
+
archivo.close()
|
| 509 |
+
else:
|
| 510 |
+
for formato in formatos:
|
| 511 |
+
archivo = open(formato[0].lower() + ".txt", "w")
|
| 512 |
+
for it in formato[2]:
|
| 513 |
+
archivo.write(escribir(it, formato[1]))
|
| 514 |
+
archivo.close()
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 518 |
+
INSTALL_DIR = TrazaProdMed.InstallDir = get_install_dir()
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
if __name__ == '__main__':
|
| 522 |
+
|
| 523 |
+
# ajusto el encoding por defecto (si se redirije la salida)
|
| 524 |
+
if not hasattr(sys.stdout, "encoding") or sys.stdout.encoding is None:
|
| 525 |
+
import codecs
|
| 526 |
+
import locale
|
| 527 |
+
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout, "replace")
|
| 528 |
+
sys.stderr = codecs.getwriter(locale.getpreferredencoding())(sys.stderr, "replace")
|
| 529 |
+
|
| 530 |
+
if '--register' in sys.argv or '--unregister' in sys.argv:
|
| 531 |
+
import pythoncom
|
| 532 |
+
if TYPELIB:
|
| 533 |
+
if '--register' in sys.argv:
|
| 534 |
+
tlb = os.path.abspath(os.path.join(INSTALL_DIR, "typelib", "trazaprodmed.tlb"))
|
| 535 |
+
print("Registering %s" % (tlb,))
|
| 536 |
+
tli = pythoncom.LoadTypeLib(tlb)
|
| 537 |
+
pythoncom.RegisterTypeLib(tli, tlb)
|
| 538 |
+
elif '--unregister' in sys.argv:
|
| 539 |
+
k = TrazaProdMed
|
| 540 |
+
pythoncom.UnRegisterTypeLib(k._typelib_guid_,
|
| 541 |
+
k._typelib_version_[0],
|
| 542 |
+
k._typelib_version_[1],
|
| 543 |
+
0,
|
| 544 |
+
pythoncom.SYS_WIN32)
|
| 545 |
+
print("Unregistered typelib")
|
| 546 |
+
import win32com.server.register
|
| 547 |
+
win32com.server.register.UseCommandLine(TrazaProdMed)
|
| 548 |
+
elif "/Automate" in sys.argv:
|
| 549 |
+
# MS seems to like /automate to run the class factories.
|
| 550 |
+
import win32com.server.localserver
|
| 551 |
+
# win32com.server.localserver.main()
|
| 552 |
+
# start the server.
|
| 553 |
+
win32com.server.localserver.serve([TrazaProdMed._reg_clsid_])
|
| 554 |
+
else:
|
| 555 |
+
main()
|
app/pyafipws/trazarenpre.py
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"MΓ³dulo para Trazabilidad de Precursores QuΓmicos RENPRE ResoluciΓ³n 900/12"
|
| 14 |
+
|
| 15 |
+
# InformaciΓ³n adicional y documentaciΓ³n:
|
| 16 |
+
# http://www.sistemasagiles.com.ar/trac/wiki/TrazabilidadPrecursoresQuimicos
|
| 17 |
+
|
| 18 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 19 |
+
__copyright__ = "Copyright (C) 2011 Mariano Reingart"
|
| 20 |
+
__license__ = "GPL 3.0+"
|
| 21 |
+
__version__ = "1.12a"
|
| 22 |
+
|
| 23 |
+
# http://renpre.servicios.pami.org.ar/portal_traza_renpre/paso5.html
|
| 24 |
+
|
| 25 |
+
import os
|
| 26 |
+
import socket
|
| 27 |
+
import sys
|
| 28 |
+
import datetime
|
| 29 |
+
import time
|
| 30 |
+
import pysimplesoap.client
|
| 31 |
+
from pysimplesoap.client import SoapFault
|
| 32 |
+
from .utils import BaseWS, inicializar_y_capturar_excepciones, get_install_dir
|
| 33 |
+
|
| 34 |
+
HOMO = False
|
| 35 |
+
TYPELIB = False
|
| 36 |
+
|
| 37 |
+
WSDL = "https://servicios.pami.org.ar/trazamed.WebServiceSDRN?wsdl"
|
| 38 |
+
LOCATION = "https://servicios.pami.org.ar/trazamed.WebServiceSDRN?wsdl"
|
| 39 |
+
# WSDL = "https://trazabilidad.pami.org.ar:59050/trazamed.WebServiceSDRN?wsdl" # prod.
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class TrazaRenpre(BaseWS):
|
| 43 |
+
"Interfaz para el WebService de Trazabilidad de Precursores Quimicos SEDRONAR SNT"
|
| 44 |
+
_public_methods_ = ['SaveTransacciones',
|
| 45 |
+
'SendCancelacTransacc', 'GetTransaccionesWS',
|
| 46 |
+
'Conectar', 'LeerError', 'LeerTransaccion',
|
| 47 |
+
'SetUsername',
|
| 48 |
+
'SetParametro', 'GetParametro',
|
| 49 |
+
'GetCodigoTransaccion', 'GetResultado', 'LoadTestXML']
|
| 50 |
+
|
| 51 |
+
_public_attrs_ = [
|
| 52 |
+
'Username', 'Password',
|
| 53 |
+
'CodigoTransaccion', 'Errores', 'Resultado',
|
| 54 |
+
'XmlRequest', 'XmlResponse',
|
| 55 |
+
'Version', 'InstallDir',
|
| 56 |
+
'Traceback', 'Excepcion', 'LanzarExcepciones',
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
_reg_progid_ = "TrazaRenpre"
|
| 60 |
+
_reg_clsid_ = "{461298DB-0531-47CA-B3D9-B36FE6967209}"
|
| 61 |
+
|
| 62 |
+
# Variables globales para BaseWS:
|
| 63 |
+
HOMO = HOMO
|
| 64 |
+
WSDL = WSDL
|
| 65 |
+
Version = "%s %s %s" % (__version__, HOMO and 'HomologaciΓ³n' or '',
|
| 66 |
+
pysimplesoap.client.__version__)
|
| 67 |
+
|
| 68 |
+
def __init__(self, reintentos=1):
|
| 69 |
+
self.Username = self.Password = None
|
| 70 |
+
BaseWS.__init__(self, reintentos)
|
| 71 |
+
|
| 72 |
+
def inicializar(self):
|
| 73 |
+
BaseWS.inicializar(self)
|
| 74 |
+
self.CodigoTransaccion = self.Errores = self.Resultado = None
|
| 75 |
+
|
| 76 |
+
def __analizar_errores(self, ret):
|
| 77 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 78 |
+
self.Errores = ["%s: %s" % (it.get('_c_error', ""), it.get('_d_error', ""))
|
| 79 |
+
for it in ret.get('errores', [])]
|
| 80 |
+
self.Resultado = ret.get('resultado')
|
| 81 |
+
|
| 82 |
+
def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, timeout=30):
|
| 83 |
+
# Conecto usando el mΓ©todo estandard:
|
| 84 |
+
ok = BaseWS.Conectar(self, cache, wsdl, proxy, wrapper, cacert, timeout,
|
| 85 |
+
soap_server="jetty")
|
| 86 |
+
if ok:
|
| 87 |
+
# si el archivo es local, asumo que ya esta corregido:
|
| 88 |
+
if not self.wsdl.startswith("file"):
|
| 89 |
+
# corrijo ubicaciΓ³n del servidor (localhost:9050 en el WSDL)
|
| 90 |
+
location = self.wsdl[:-5]
|
| 91 |
+
if 'IWebServiceSDRNService' in self.client.services:
|
| 92 |
+
ws = self.client.services['IWebServiceSDRNService']
|
| 93 |
+
else:
|
| 94 |
+
ws = self.client.services['IWebServiceSDRN']
|
| 95 |
+
ws['ports']['IWebServiceSDRNPort']['location'] = location
|
| 96 |
+
# Establecer credenciales de seguridad:
|
| 97 |
+
self.client['wsse:Security'] = {
|
| 98 |
+
'wsse:UsernameToken': {
|
| 99 |
+
'wsse:Username': self.Username,
|
| 100 |
+
'wsse:Password': self.Password,
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
return ok
|
| 104 |
+
|
| 105 |
+
@inicializar_y_capturar_excepciones
|
| 106 |
+
def SaveTransacciones(self, usuario, password,
|
| 107 |
+
gln_origen=None, gln_destino=None, f_operacion=None,
|
| 108 |
+
id_evento=None, cod_producto=None, n_cantidad=None,
|
| 109 |
+
n_documento_operacion=None, n_remito=None,
|
| 110 |
+
id_tipo_transporte=None,
|
| 111 |
+
id_paso_frontera=None,
|
| 112 |
+
id_tipo_documento_operacion=None,
|
| 113 |
+
d_dominio_tractor=None,
|
| 114 |
+
d_dominio_semi=None,
|
| 115 |
+
n_serie=None, n_lote=None, doc_despacho_plaza=None,
|
| 116 |
+
djai=None, n_cert_impo_expo=None,
|
| 117 |
+
id_tipo_documento=None, n_documento=None,
|
| 118 |
+
m_calidad_analitica=None, m_entrega_parcial=None,
|
| 119 |
+
doc_permiso_embarque=None, gln_transportista=None,
|
| 120 |
+
operacion_excento_djai=None, control_duplicidad=None,
|
| 121 |
+
):
|
| 122 |
+
"Permite informe por parte de un agente de una o varias transacciones"
|
| 123 |
+
# creo los parΓ‘metros para esta llamada
|
| 124 |
+
params = {'gln_origen': gln_origen, 'gln_destino': gln_destino,
|
| 125 |
+
'f_operacion': f_operacion, 'id_evento': id_evento,
|
| 126 |
+
'cod_producto': cod_producto, 'n_cantidad': n_cantidad,
|
| 127 |
+
'n_documento_operacion': n_documento_operacion,
|
| 128 |
+
'n_remito': n_remito,
|
| 129 |
+
'id_tipo_transporte': id_tipo_transporte,
|
| 130 |
+
'id_paso_frontera': id_paso_frontera,
|
| 131 |
+
'id_tipo_documento_operacion': id_tipo_documento_operacion,
|
| 132 |
+
'd_dominio_tractor': d_dominio_tractor,
|
| 133 |
+
'd_dominio_semi': d_dominio_semi,
|
| 134 |
+
'n_serie': n_serie, 'n_lote': n_lote,
|
| 135 |
+
'doc_despacho_plaza': doc_despacho_plaza,
|
| 136 |
+
'djai': djai, 'n_cert_impo_expo': n_cert_impo_expo,
|
| 137 |
+
'id_tipo_documento': id_tipo_documento,
|
| 138 |
+
'n_documento': n_documento,
|
| 139 |
+
'm_calidad_analitica': m_calidad_analitica,
|
| 140 |
+
'm_entrega_parcial': m_entrega_parcial,
|
| 141 |
+
'doc_permiso_embarque': doc_permiso_embarque,
|
| 142 |
+
'gln_transportista': gln_transportista,
|
| 143 |
+
'operacion_excento_djai': operacion_excento_djai,
|
| 144 |
+
'control_duplicidad': control_duplicidad,
|
| 145 |
+
}
|
| 146 |
+
# actualizo con parΓ‘metros generales:
|
| 147 |
+
params.update(self.params_in)
|
| 148 |
+
res = self.client.saveTransacciones(
|
| 149 |
+
arg0=params,
|
| 150 |
+
arg1=usuario,
|
| 151 |
+
arg2=password,
|
| 152 |
+
)
|
| 153 |
+
ret = res['return']
|
| 154 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 155 |
+
self.__analizar_errores(ret)
|
| 156 |
+
return True
|
| 157 |
+
|
| 158 |
+
@inicializar_y_capturar_excepciones
|
| 159 |
+
def SendCancelacTransacc(self, usuario, password, codigo_transaccion):
|
| 160 |
+
" Realiza la cancelaciΓ³n de una transacciΓ³n"
|
| 161 |
+
res = self.client.sendCancelaTransac(
|
| 162 |
+
arg0=codigo_transaccion,
|
| 163 |
+
arg1=usuario,
|
| 164 |
+
arg2=password,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
ret = res['return']
|
| 168 |
+
|
| 169 |
+
self.CodigoTransaccion = ret['codigoTransaccion']
|
| 170 |
+
self.__analizar_errores(ret)
|
| 171 |
+
|
| 172 |
+
return True
|
| 173 |
+
|
| 174 |
+
@inicializar_y_capturar_excepciones
|
| 175 |
+
def SendConfirmaTransacc(self, usuario, password, p_ids_transac, f_operacion):
|
| 176 |
+
"Confirma la recepciΓ³n de un medicamento"
|
| 177 |
+
res = self.client.sendConfirmaTransacc(
|
| 178 |
+
arg0=usuario,
|
| 179 |
+
arg1=password,
|
| 180 |
+
arg2={'p_ids_transac': p_ids_transac, 'f_operacion': f_operacion},
|
| 181 |
+
)
|
| 182 |
+
ret = res['return']
|
| 183 |
+
self.CodigoTransaccion = ret.get('id_transac_asociada')
|
| 184 |
+
self.__analizar_errores(ret)
|
| 185 |
+
return True
|
| 186 |
+
|
| 187 |
+
@inicializar_y_capturar_excepciones
|
| 188 |
+
def SendAlertaTransacc(self, usuario, password, p_ids_transac_ws):
|
| 189 |
+
"Alerta un medicamento, acciΓ³n contraria a βconfirmar la transacciΓ³nβ."
|
| 190 |
+
res = self.client.sendAlertaTransacc(
|
| 191 |
+
arg0=usuario,
|
| 192 |
+
arg1=password,
|
| 193 |
+
arg2=p_ids_transac_ws,
|
| 194 |
+
)
|
| 195 |
+
ret = res['return']
|
| 196 |
+
self.CodigoTransaccion = ret.get('id_transac_asociada')
|
| 197 |
+
self.__analizar_errores(ret)
|
| 198 |
+
return True
|
| 199 |
+
|
| 200 |
+
@inicializar_y_capturar_excepciones
|
| 201 |
+
def GetTransaccionesWS(self, usuario, password,
|
| 202 |
+
p_id_transaccion_global=None,
|
| 203 |
+
id_agente_origen=None, id_agente_destino=None,
|
| 204 |
+
id_agente_informador=None,
|
| 205 |
+
gtin=None, id_evento=None, cant_analitica=None,
|
| 206 |
+
fecha_desde_op=None, fecha_hasta_op=None,
|
| 207 |
+
fecha_desde_t=None, fecha_hasta_t=None,
|
| 208 |
+
id_tipo=None,
|
| 209 |
+
id_estado=None, nro_pag=1, cant_reg=100,
|
| 210 |
+
):
|
| 211 |
+
"Obtiene los movimientos realizados y permite filtros de bΓΊsqueda"
|
| 212 |
+
|
| 213 |
+
# preparo los parametros de entrada opcionales:
|
| 214 |
+
kwargs = {}
|
| 215 |
+
if p_id_transaccion_global is not None:
|
| 216 |
+
kwargs['arg2'] = p_id_transaccion_global
|
| 217 |
+
if id_agente_origen is not None:
|
| 218 |
+
kwargs['arg3'] = id_agente_origen
|
| 219 |
+
if id_agente_destino is not None:
|
| 220 |
+
kwargs['arg4'] = id_agente_destino
|
| 221 |
+
if id_agente_destino is not None:
|
| 222 |
+
kwargs['arg5'] = id_agente_informador
|
| 223 |
+
if gtin is not None:
|
| 224 |
+
kwargs['arg6'] = gtin
|
| 225 |
+
if id_evento is not None:
|
| 226 |
+
kwargs['arg7'] = id_evento
|
| 227 |
+
if cant_analitica is not None:
|
| 228 |
+
kwargs['arg8'] = cant_analitica
|
| 229 |
+
if fecha_desde_op is not None:
|
| 230 |
+
kwargs['arg9'] = fecha_desde_op
|
| 231 |
+
if fecha_hasta_op is not None:
|
| 232 |
+
kwargs['arg10'] = fecha_hasta_op
|
| 233 |
+
if fecha_desde_t is not None:
|
| 234 |
+
kwargs['arg11'] = fecha_desde_t
|
| 235 |
+
if fecha_hasta_t is not None:
|
| 236 |
+
kwargs['arg12'] = fecha_hasta_t
|
| 237 |
+
if id_tipo is not None:
|
| 238 |
+
kwargs['arg13'] = id_tipo
|
| 239 |
+
if id_estado is not None:
|
| 240 |
+
kwargs['arg14'] = id_estado
|
| 241 |
+
if nro_pag is not None:
|
| 242 |
+
kwargs['arg15'] = nro_pag
|
| 243 |
+
if cant_reg is not None:
|
| 244 |
+
kwargs['arg16'] = cant_reg
|
| 245 |
+
|
| 246 |
+
# llamo al webservice
|
| 247 |
+
res = self.client.getTransaccionesWs(
|
| 248 |
+
arg0=usuario,
|
| 249 |
+
arg1=password,
|
| 250 |
+
**kwargs
|
| 251 |
+
)
|
| 252 |
+
ret = res['return']
|
| 253 |
+
if ret:
|
| 254 |
+
self.__analizar_errores(ret)
|
| 255 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 256 |
+
self.HayError = ret.get('hay_error')
|
| 257 |
+
self.TransaccionPlainWS = [it for it in ret.get('list', [])]
|
| 258 |
+
return True
|
| 259 |
+
|
| 260 |
+
def SetUsername(self, username):
|
| 261 |
+
"Establezco el nombre de usuario"
|
| 262 |
+
self.Username = username
|
| 263 |
+
|
| 264 |
+
def SetPassword(self, password):
|
| 265 |
+
"Establezco la contraseΓ±a"
|
| 266 |
+
self.Password = password
|
| 267 |
+
|
| 268 |
+
def GetCodigoTransaccion(self):
|
| 269 |
+
"Devuelvo el cΓ³digo de transacciΓ³n"
|
| 270 |
+
return self.CodigoTransaccion
|
| 271 |
+
|
| 272 |
+
def GetResultado(self):
|
| 273 |
+
"Devuelvo el resultado"
|
| 274 |
+
return self.Resultado
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def main():
|
| 278 |
+
"FunciΓ³n principal de pruebas (transaccionar!)"
|
| 279 |
+
import os
|
| 280 |
+
import time
|
| 281 |
+
import sys
|
| 282 |
+
global WSDL, LOCATION
|
| 283 |
+
|
| 284 |
+
DEBUG = '--debug' in sys.argv
|
| 285 |
+
|
| 286 |
+
ws = TrazaRenpre()
|
| 287 |
+
|
| 288 |
+
ws.Username = 'testwservice'
|
| 289 |
+
ws.Password = 'testwservicepsw'
|
| 290 |
+
|
| 291 |
+
if '--prod' in sys.argv and not HOMO:
|
| 292 |
+
WSDL = "https://trazabilidad.pami.org.ar:59050/trazamed.WebServiceSDRN?wsdl"
|
| 293 |
+
print("Usando WSDL:", WSDL)
|
| 294 |
+
sys.argv.pop(0)
|
| 295 |
+
|
| 296 |
+
ws.Conectar("", WSDL)
|
| 297 |
+
|
| 298 |
+
if ws.Excepcion:
|
| 299 |
+
print(ws.Excepcion)
|
| 300 |
+
print(ws.Traceback)
|
| 301 |
+
sys.exit(-1)
|
| 302 |
+
|
| 303 |
+
# print ws.client.services
|
| 304 |
+
#op = ws.client.get_operation("sendMedicamentos")
|
| 305 |
+
#import pdb;pdb.set_trace()
|
| 306 |
+
if '--test' in sys.argv:
|
| 307 |
+
ws.SaveTransacciones(
|
| 308 |
+
usuario='pruebasws', password='pruebasws',
|
| 309 |
+
gln_origen=8888888888888,
|
| 310 |
+
gln_destino=8888888888888,
|
| 311 |
+
f_operacion="20/05/2014",
|
| 312 |
+
id_evento=44,
|
| 313 |
+
cod_producto=88800000000035, # acido sulfΓΊrico
|
| 314 |
+
n_cantidad=1,
|
| 315 |
+
n_documento_operacion=1,
|
| 316 |
+
# m_entrega_parcial="",
|
| 317 |
+
n_remito=123,
|
| 318 |
+
n_serie=112,
|
| 319 |
+
)
|
| 320 |
+
print("Resultado", ws.Resultado)
|
| 321 |
+
print("CodigoTransaccion", ws.CodigoTransaccion)
|
| 322 |
+
print("Excepciones", ws.Excepcion)
|
| 323 |
+
print("Erroes", ws.Errores)
|
| 324 |
+
elif '--cancela' in sys.argv:
|
| 325 |
+
ws.SendCancelacTransacc(*sys.argv[sys.argv.index("--cancela") + 1:])
|
| 326 |
+
elif '--consulta' in sys.argv:
|
| 327 |
+
if '--movimientos' in sys.argv:
|
| 328 |
+
ws.GetTransaccionesWS(
|
| 329 |
+
*sys.argv[sys.argv.index("--movimientos") + 1:]
|
| 330 |
+
)
|
| 331 |
+
else:
|
| 332 |
+
ws.SaveTransacciones(*sys.argv[1:])
|
| 333 |
+
print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
|
| 334 |
+
ws.Resultado,
|
| 335 |
+
ws.CodigoTransaccion,
|
| 336 |
+
'|'.join(ws.Errores),
|
| 337 |
+
))
|
| 338 |
+
if ws.Excepcion:
|
| 339 |
+
print(ws.Traceback)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
# busco el directorio de instalaciΓ³n (global para que no cambie si usan otra dll)
|
| 343 |
+
INSTALL_DIR = TrazaRenpre.InstallDir = get_install_dir()
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
if __name__ == '__main__':
|
| 347 |
+
|
| 348 |
+
# ajusto el encoding por defecto (si se redirije la salida)
|
| 349 |
+
if sys.stdout.encoding is None:
|
| 350 |
+
import codecs
|
| 351 |
+
import locale
|
| 352 |
+
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout, "replace")
|
| 353 |
+
sys.stderr = codecs.getwriter(locale.getpreferredencoding())(sys.stderr, "replace")
|
| 354 |
+
|
| 355 |
+
if '--register' in sys.argv or '--unregister' in sys.argv:
|
| 356 |
+
import pythoncom
|
| 357 |
+
import win32com.server.register
|
| 358 |
+
win32com.server.register.UseCommandLine(TrazaRenpre)
|
| 359 |
+
elif "/Automate" in sys.argv:
|
| 360 |
+
# MS seems to like /automate to run the class factories.
|
| 361 |
+
import win32com.server.localserver
|
| 362 |
+
# win32com.server.localserver.main()
|
| 363 |
+
# start the server.
|
| 364 |
+
win32com.server.localserver.serve([TrazaRenpre._reg_clsid_])
|
| 365 |
+
else:
|
| 366 |
+
main()
|
app/pyafipws/trazavet.py
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"MοΏ½dulo Trazabilidad de Productos Veterinarios SENASA ResoluciοΏ½n 369/2013"
|
| 14 |
+
|
| 15 |
+
# InformaciοΏ½n adicional y documentaciοΏ½n:
|
| 16 |
+
# http://www.sistemasagiles.com.ar/trac/wiki/TrazabilidadProductosVeterinarios
|
| 17 |
+
|
| 18 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 19 |
+
__copyright__ = "Copyright (C) 2014 Mariano Reingart"
|
| 20 |
+
__license__ = "GPL 3.0+"
|
| 21 |
+
__version__ = "1.11d"
|
| 22 |
+
|
| 23 |
+
# http://senasa.servicios.pami.org.ar/
|
| 24 |
+
|
| 25 |
+
import os
|
| 26 |
+
import socket
|
| 27 |
+
import sys
|
| 28 |
+
import datetime
|
| 29 |
+
import time
|
| 30 |
+
import traceback
|
| 31 |
+
import pysimplesoap.client
|
| 32 |
+
from pysimplesoap.client import SoapClient, SoapFault, parse_proxy, \
|
| 33 |
+
set_http_wrapper
|
| 34 |
+
from pysimplesoap.simplexml import SimpleXMLElement
|
| 35 |
+
from io import StringIO
|
| 36 |
+
|
| 37 |
+
# importo funciones compartidas:
|
| 38 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, \
|
| 39 |
+
dar_nombre_campo_dbf, get_install_dir, BaseWS, \
|
| 40 |
+
inicializar_y_capturar_excepciones
|
| 41 |
+
|
| 42 |
+
HOMO = True
|
| 43 |
+
TYPELIB = False
|
| 44 |
+
|
| 45 |
+
WSDL = "https://servicios.pami.org.ar/trazaenvet.WebService?wsdl"
|
| 46 |
+
LOCATION = "https://servicios.pami.org.ar/trazaenvet.WebService?wsdl"
|
| 47 |
+
|
| 48 |
+
# Formato de TransaccionSenasaDTO (SaveTransaccion)
|
| 49 |
+
TRANSACCION_DTO = [
|
| 50 |
+
('gln_origen', 13, A),
|
| 51 |
+
('gln_destino', 13, A),
|
| 52 |
+
('f_operacion', 10, A),
|
| 53 |
+
('f_elaboracion', 10, A),
|
| 54 |
+
('f_vto', 10, A),
|
| 55 |
+
('id_evento', 15, N),
|
| 56 |
+
('cod_producto', 14, A),
|
| 57 |
+
('n_cantidad', 30, N),
|
| 58 |
+
('n_serie', 20, A),
|
| 59 |
+
('n_lote', 50, A),
|
| 60 |
+
('n_cai', 15, A),
|
| 61 |
+
('n_cae', 15, A),
|
| 62 |
+
('id_motivo_destruccion', 5, A),
|
| 63 |
+
('n_manifiesto', 15, N),
|
| 64 |
+
('en_transporte', 1, A), # boolean
|
| 65 |
+
('n_remito', 15, A),
|
| 66 |
+
('motivo_devolucion', 100, A),
|
| 67 |
+
('observaciones', 1000, A),
|
| 68 |
+
('n_vale_compra', 15, A),
|
| 69 |
+
('apellidoNombres', 255, A),
|
| 70 |
+
('direccion', 200, A),
|
| 71 |
+
('numero', 6, N),
|
| 72 |
+
('localidad', 15, A),
|
| 73 |
+
('provincia', 15, A),
|
| 74 |
+
('n_postal', 8, A),
|
| 75 |
+
('cuit', 11, A),
|
| 76 |
+
('codigo_transaccion', 14, A),
|
| 77 |
+
]
|
| 78 |
+
|
| 79 |
+
# Formato para TransaccionSenasa (getTransacciones)
|
| 80 |
+
TRANSACCIONES = [
|
| 81 |
+
('id_transaccion_global', 15, N),
|
| 82 |
+
('id_transaccion', 15, N),
|
| 83 |
+
('f_transaccion', 10, A),
|
| 84 |
+
('f_operacion', 10, A),
|
| 85 |
+
('f_vencimiento', 10, A),
|
| 86 |
+
('f_elaboracion', 10, A),
|
| 87 |
+
('d_evento', 100, A),
|
| 88 |
+
('n_cantidad', 30, N),
|
| 89 |
+
('id_unidad', 15, N),
|
| 90 |
+
('d_unidad', 100, A),
|
| 91 |
+
('cod_producto', 14, A),
|
| 92 |
+
('id_unidad', 15, N),
|
| 93 |
+
('n_serie', 20, A),
|
| 94 |
+
('n_lote', 50, A),
|
| 95 |
+
('n_cai', 15, A),
|
| 96 |
+
('n_cae', 15, A),
|
| 97 |
+
('d_motivo_destruccion', 50, A),
|
| 98 |
+
('d_manifiesto', 15, A),
|
| 99 |
+
('en_transporte', 1, A),
|
| 100 |
+
('n_remito', 30, A),
|
| 101 |
+
('motivo_devolucion', 200, A),
|
| 102 |
+
('observaciones', 1000, A),
|
| 103 |
+
('n_vale_compra', 15, A),
|
| 104 |
+
('apellidoNombres', 255, A),
|
| 105 |
+
('direccion', 200, A),
|
| 106 |
+
('numero', 6, A),
|
| 107 |
+
('localidad', 250, A),
|
| 108 |
+
('provincia', 250, A),
|
| 109 |
+
('n_postal', 8, A),
|
| 110 |
+
('cuit', 11, A),
|
| 111 |
+
('d_agente_informador', 255, A),
|
| 112 |
+
('d_agente_origen', 255, A),
|
| 113 |
+
('d_agente_destino', 255, A),
|
| 114 |
+
('d_producto', 250, A),
|
| 115 |
+
('d_estado_transaccion', 30, A),
|
| 116 |
+
('d_tipo_transaccion', 30, A),
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
# Formato para Errores
|
| 120 |
+
ERRORES = [
|
| 121 |
+
('_c_error', 4, A), # cοΏ½digo
|
| 122 |
+
('_d_error', 250, A), # descripciοΏ½n
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class TrazaVet(BaseWS):
|
| 127 |
+
"Interfaz para el WebService de Trazabilidad de Veterinarios SENASA"
|
| 128 |
+
|
| 129 |
+
_public_methods_ = ['SaveTransaccion', 'SendCancelaTransac',
|
| 130 |
+
'SendConfirmaTransacc', 'SendAlertaTransacc',
|
| 131 |
+
'GetTransacciones',
|
| 132 |
+
'Conectar', 'LeerError', 'LeerTransaccion',
|
| 133 |
+
'SetUsername',
|
| 134 |
+
'SetParametro', 'GetParametro',
|
| 135 |
+
'GetCodigoTransaccion', 'GetResultado', 'LoadTestXML']
|
| 136 |
+
|
| 137 |
+
_public_attrs_ = [
|
| 138 |
+
'Username', 'Password',
|
| 139 |
+
'CodigoTransaccion', 'Errores', 'Resultado',
|
| 140 |
+
'XmlRequest', 'XmlResponse',
|
| 141 |
+
'Version', 'InstallDir',
|
| 142 |
+
'Traceback', 'Excepcion',
|
| 143 |
+
'CantPaginas', 'HayError', 'TransaccionSenasa',
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
_reg_progid_ = "TrazaVet"
|
| 147 |
+
_reg_clsid_ = "{2E28D79D-23B0-4C1E-85B9-64BDC41B8FCF}"
|
| 148 |
+
|
| 149 |
+
# Variables globales para BaseWS:
|
| 150 |
+
HOMO = HOMO
|
| 151 |
+
WSDL = WSDL
|
| 152 |
+
Version = "%s %s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '',
|
| 153 |
+
pysimplesoap.client.__version__)
|
| 154 |
+
|
| 155 |
+
def __init__(self, reintentos=1):
|
| 156 |
+
self.Username = self.Password = None
|
| 157 |
+
self.TransaccionSenasa = []
|
| 158 |
+
BaseWS.__init__(self, reintentos)
|
| 159 |
+
|
| 160 |
+
def inicializar(self):
|
| 161 |
+
BaseWS.inicializar(self)
|
| 162 |
+
self.CodigoTransaccion = self.Errores = self.Resultado = None
|
| 163 |
+
self.Resultado = ''
|
| 164 |
+
self.Errores = [] # lista de strings para la interfaz
|
| 165 |
+
self.errores = [] # lista de diccionarios (uso interno)
|
| 166 |
+
self.CantPaginas = self.HayError = None
|
| 167 |
+
|
| 168 |
+
def __analizar_errores(self, ret):
|
| 169 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 170 |
+
self.errores = ret.get('errores', [])
|
| 171 |
+
self.Errores = ["%s: %s" % (it['c_error'], it['d_error'])
|
| 172 |
+
for it in ret.get('errores', [])]
|
| 173 |
+
self.Resultado = ret.get('resultado')
|
| 174 |
+
|
| 175 |
+
def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, timeout=30):
|
| 176 |
+
# Conecto usando el mοΏ½todo estandard:
|
| 177 |
+
ok = BaseWS.Conectar(self, cache, wsdl, proxy, wrapper, cacert, timeout,
|
| 178 |
+
soap_server="jetty")
|
| 179 |
+
if ok:
|
| 180 |
+
# si el archivo es local, asumo que ya esta corregido:
|
| 181 |
+
if not self.wsdl.startswith("file"):
|
| 182 |
+
# corrijo ubicaciοΏ½n del servidor (localhost:59050 en el WSDL)
|
| 183 |
+
location = self.wsdl[:-5]
|
| 184 |
+
ws = self.client.services['IWebServiceSenasa']
|
| 185 |
+
ws['ports']['IWebServiceSenasaPort']['location'] = location
|
| 186 |
+
|
| 187 |
+
# Establecer credenciales de seguridad:
|
| 188 |
+
self.client['wsse:Security'] = {
|
| 189 |
+
'wsse:UsernameToken': {
|
| 190 |
+
'wsse:Username': self.Username,
|
| 191 |
+
'wsse:Password': self.Password,
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
return ok
|
| 195 |
+
|
| 196 |
+
@inicializar_y_capturar_excepciones
|
| 197 |
+
def SaveTransaccion(self, usuario, password,
|
| 198 |
+
gln_origen=None, gln_destino=None,
|
| 199 |
+
f_operacion=None, f_elaboracion=None, f_vto=None,
|
| 200 |
+
id_evento=None, cod_producto=None, n_cantidad=None,
|
| 201 |
+
n_serie=None, n_lote=None, n_cai=None, n_cae=None,
|
| 202 |
+
id_motivo_destruccion=None, n_manifiesto=None,
|
| 203 |
+
en_transporte=None, n_remito=None,
|
| 204 |
+
motivo_devolucion=None, observaciones=None,
|
| 205 |
+
n_vale_compra=None, apellidoNombres=None,
|
| 206 |
+
direccion=None, numero=None, localidad=None,
|
| 207 |
+
provincia=None, n_postal=None, cuit=None
|
| 208 |
+
):
|
| 209 |
+
"Realiza el registro de una transacciοΏ½n de productos fitosanitarios. "
|
| 210 |
+
# creo los parοΏ½metros para esta llamada
|
| 211 |
+
params = {'gln_origen': gln_origen,
|
| 212 |
+
'gln_destino': gln_destino,
|
| 213 |
+
'f_operacion': f_operacion,
|
| 214 |
+
'f_elaboracion': f_elaboracion,
|
| 215 |
+
'f_vto': f_vto,
|
| 216 |
+
'id_evento': id_evento,
|
| 217 |
+
'cod_producto': cod_producto,
|
| 218 |
+
'n_cantidad': n_cantidad,
|
| 219 |
+
'n_serie': n_serie,
|
| 220 |
+
'n_lote': n_lote,
|
| 221 |
+
'n_cai': n_cai or None,
|
| 222 |
+
'n_cae': n_cae or None,
|
| 223 |
+
'id_motivo_destruccion': id_motivo_destruccion or None,
|
| 224 |
+
'n_manifiesto': n_manifiesto or None,
|
| 225 |
+
'en_transporte': en_transporte or None,
|
| 226 |
+
'n_remito': n_remito or None,
|
| 227 |
+
'motivo_devolucion': motivo_devolucion or None,
|
| 228 |
+
'observaciones': observaciones or None,
|
| 229 |
+
'n_vale_compra': n_vale_compra or None,
|
| 230 |
+
'apellidoNombres': apellidoNombres or None,
|
| 231 |
+
'direccion': direccion or None,
|
| 232 |
+
'numero': numero or None,
|
| 233 |
+
'localidad': localidad or None,
|
| 234 |
+
'provincia': provincia or None,
|
| 235 |
+
'n_postal': n_postal or None,
|
| 236 |
+
'cuit': cuit or None,
|
| 237 |
+
}
|
| 238 |
+
res = self.client.saveTransacciones(
|
| 239 |
+
arg0=params,
|
| 240 |
+
arg1=usuario,
|
| 241 |
+
arg2=password,
|
| 242 |
+
)
|
| 243 |
+
ret = res['return']
|
| 244 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 245 |
+
self.__analizar_errores(ret)
|
| 246 |
+
return True
|
| 247 |
+
|
| 248 |
+
@inicializar_y_capturar_excepciones
|
| 249 |
+
def SendCancelaTransac(self, usuario, password, codigo_transaccion):
|
| 250 |
+
" Realiza la cancelaciοΏ½n de una transacciοΏ½n"
|
| 251 |
+
res = self.client.sendCancelaTransac(
|
| 252 |
+
arg0=codigo_transaccion,
|
| 253 |
+
arg1=usuario,
|
| 254 |
+
arg2=password,
|
| 255 |
+
)
|
| 256 |
+
ret = res['return']
|
| 257 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 258 |
+
self.__analizar_errores(ret)
|
| 259 |
+
return True
|
| 260 |
+
|
| 261 |
+
@inicializar_y_capturar_excepciones
|
| 262 |
+
def SendConfirmaTransacc(self, usuario, password, p_ids_transac, f_operacion, n_cantidad=None):
|
| 263 |
+
"Confirma la recepciοΏ½n de un medicamento"
|
| 264 |
+
res = self.client.sendConfirmaTransacc(
|
| 265 |
+
arg0=usuario,
|
| 266 |
+
arg1=password,
|
| 267 |
+
arg2={'p_ids_transac': p_ids_transac, 'f_operacion': f_operacion,
|
| 268 |
+
'n_cantidad': n_cantidad,
|
| 269 |
+
},
|
| 270 |
+
)
|
| 271 |
+
ret = res['return']
|
| 272 |
+
self.CodigoTransaccion = ret.get('codigoTransaccion')
|
| 273 |
+
self.__analizar_errores(ret)
|
| 274 |
+
return True
|
| 275 |
+
|
| 276 |
+
@inicializar_y_capturar_excepciones
|
| 277 |
+
def SendAlertaTransacc(self, usuario, password, p_ids_transac_ws):
|
| 278 |
+
"Alerta un medicamento, acciοΏ½n contraria a οΏ½confirmar la transacciοΏ½nοΏ½."
|
| 279 |
+
res = self.client.sendAlertaTransacc(
|
| 280 |
+
arg0=usuario,
|
| 281 |
+
arg1=password,
|
| 282 |
+
arg2=p_ids_transac_ws,
|
| 283 |
+
)
|
| 284 |
+
ret = res['return']
|
| 285 |
+
self.CodigoTransaccion = ret.get('id_transac_asociada')
|
| 286 |
+
self.__analizar_errores(ret)
|
| 287 |
+
return True
|
| 288 |
+
|
| 289 |
+
@inicializar_y_capturar_excepciones
|
| 290 |
+
def GetTransacciones(self, usuario, password,
|
| 291 |
+
id_transaccion=None, id_evento=None, gln_origen=None,
|
| 292 |
+
fecha_desde_t=None, fecha_hasta_t=None,
|
| 293 |
+
fecha_desde_v=None, fecha_hasta_v=None,
|
| 294 |
+
gln_informador=None, id_tipo_transaccion=None,
|
| 295 |
+
gtin_elemento=None, n_lote=None, n_serie=None,
|
| 296 |
+
n_remito_factura=None,
|
| 297 |
+
):
|
| 298 |
+
"Trae un listado de las transacciones que no estοΏ½n confirmadas"
|
| 299 |
+
|
| 300 |
+
# preparo los parametros de entrada opcionales:
|
| 301 |
+
kwargs = {}
|
| 302 |
+
if id_transaccion is not None:
|
| 303 |
+
kwargs['arg2'] = id_transaccion
|
| 304 |
+
if id_evento is not None:
|
| 305 |
+
kwargs['arg3'] = id_evento
|
| 306 |
+
if gln_origen is not None:
|
| 307 |
+
kwargs['arg4'] = gln_origen
|
| 308 |
+
if fecha_desde_t is not None:
|
| 309 |
+
kwargs['arg5'] = fecha_desde_t
|
| 310 |
+
if fecha_hasta_t is not None:
|
| 311 |
+
kwargs['arg6'] = fecha_hasta_t
|
| 312 |
+
if fecha_desde_v is not None:
|
| 313 |
+
kwargs['arg7'] = fecha_desde_v
|
| 314 |
+
if fecha_hasta_v is not None:
|
| 315 |
+
kwargs['arg8'] = fecha_hasta_v
|
| 316 |
+
if gln_informador is not None:
|
| 317 |
+
kwargs['arg9'] = gln_informador
|
| 318 |
+
if id_tipo_transaccion is not None:
|
| 319 |
+
kwargs['arg10'] = id_tipo_transaccion
|
| 320 |
+
if gtin_elemento is not None:
|
| 321 |
+
kwargs['arg11'] = gtin_elemento
|
| 322 |
+
if n_lote is not None:
|
| 323 |
+
kwargs['arg12'] = n_lote
|
| 324 |
+
if n_serie is not None:
|
| 325 |
+
kwargs['arg13'] = n_serie
|
| 326 |
+
if n_remito_factura is not None:
|
| 327 |
+
kwargs['arg14'] = n_remito_factura
|
| 328 |
+
|
| 329 |
+
# llamo al webservice
|
| 330 |
+
res = self.client.getTransacciones(
|
| 331 |
+
arg0=usuario,
|
| 332 |
+
arg1=password,
|
| 333 |
+
**kwargs
|
| 334 |
+
)
|
| 335 |
+
ret = res['return']
|
| 336 |
+
if ret:
|
| 337 |
+
self.__analizar_errores(ret)
|
| 338 |
+
self.CantPaginas = ret.get('cantPaginas')
|
| 339 |
+
self.HayError = ret.get('hay_error')
|
| 340 |
+
self.TransaccionSenasa = [it for it in ret.get('list', [])]
|
| 341 |
+
return True
|
| 342 |
+
|
| 343 |
+
def LeerTransaccion(self):
|
| 344 |
+
"Recorro TransaccionSenasa devuelto por GetTransacciones"
|
| 345 |
+
# usar GetParametro para consultar el valor retornado por el webservice
|
| 346 |
+
|
| 347 |
+
if self.TransaccionSenasa:
|
| 348 |
+
# extraigo el primer item
|
| 349 |
+
self.params_out = self.TransaccionSenasa.pop(0)
|
| 350 |
+
return True
|
| 351 |
+
else:
|
| 352 |
+
# limpio los parοΏ½metros
|
| 353 |
+
self.params_out = {}
|
| 354 |
+
return False
|
| 355 |
+
|
| 356 |
+
def LeerError(self):
|
| 357 |
+
"Recorro los errores devueltos y devuelvo el primero si existe"
|
| 358 |
+
|
| 359 |
+
if self.Errores:
|
| 360 |
+
# extraigo el primer item
|
| 361 |
+
er = self.Errores.pop(0)
|
| 362 |
+
return er
|
| 363 |
+
else:
|
| 364 |
+
return ""
|
| 365 |
+
|
| 366 |
+
def SetUsername(self, username):
|
| 367 |
+
"Establezco el nombre de usuario"
|
| 368 |
+
self.Username = username
|
| 369 |
+
|
| 370 |
+
def SetPassword(self, password):
|
| 371 |
+
"Establezco la contraseοΏ½a"
|
| 372 |
+
self.Password = password
|
| 373 |
+
|
| 374 |
+
def GetCodigoTransaccion(self):
|
| 375 |
+
"Devuelvo el cοΏ½digo de transacciοΏ½n"
|
| 376 |
+
return self.CodigoTransaccion
|
| 377 |
+
|
| 378 |
+
def GetResultado(self):
|
| 379 |
+
"Devuelvo el resultado"
|
| 380 |
+
return self.Resultado
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def main():
|
| 384 |
+
"FunciοΏ½n principal de pruebas (obtener CAE)"
|
| 385 |
+
import os
|
| 386 |
+
import time
|
| 387 |
+
import sys
|
| 388 |
+
global WSDL, LOCATION
|
| 389 |
+
|
| 390 |
+
DEBUG = '--debug' in sys.argv
|
| 391 |
+
|
| 392 |
+
ws = TrazaVet()
|
| 393 |
+
|
| 394 |
+
ws.Username = 'testwservice'
|
| 395 |
+
ws.Password = 'testwservicepsw'
|
| 396 |
+
|
| 397 |
+
if '--prod' in sys.argv and not HOMO:
|
| 398 |
+
WSDL = "https://servicios.pami.org.ar/trazavet.WebService?wsdl"
|
| 399 |
+
print("Usando WSDL:", WSDL)
|
| 400 |
+
sys.argv.pop(sys.argv.index("--prod"))
|
| 401 |
+
|
| 402 |
+
if '--proxy' in sys.argv and not HOMO:
|
| 403 |
+
proxy = sys.argv.pop(sys.argv.index("--proxy") + 1)
|
| 404 |
+
print("Usando proxy:", proxy)
|
| 405 |
+
sys.argv.pop(sys.argv.index("--proxy"))
|
| 406 |
+
else:
|
| 407 |
+
proxy = None
|
| 408 |
+
|
| 409 |
+
# Inicializo las variables y estructuras para el archivo de intercambio:
|
| 410 |
+
transaccion_dto = []
|
| 411 |
+
transacciones = []
|
| 412 |
+
errores = []
|
| 413 |
+
formatos = [('TransaccionDTO', TRANSACCION_DTO, transaccion_dto),
|
| 414 |
+
('Transacciones', TRANSACCIONES, transacciones),
|
| 415 |
+
('Errores', ERRORES, errores),
|
| 416 |
+
]
|
| 417 |
+
|
| 418 |
+
if '--formato' in sys.argv:
|
| 419 |
+
print("Formato:")
|
| 420 |
+
for msg, formato, lista in formatos:
|
| 421 |
+
comienzo = 1
|
| 422 |
+
print("=== %s ===" % msg)
|
| 423 |
+
print("|| %-25s || %-12s || %-5s || %-4s || %-10s ||" % (
|
| 424 |
+
"Nombre", "Tipo", "Long.", "Pos(txt)", "Campo(dbf)"))
|
| 425 |
+
claves = []
|
| 426 |
+
for fmt in formato:
|
| 427 |
+
clave, longitud, tipo = fmt[0:3]
|
| 428 |
+
clave_dbf = dar_nombre_campo_dbf(clave, claves)
|
| 429 |
+
claves.append(clave_dbf)
|
| 430 |
+
print("|| %-25s || %-12s || %5d || %4d || %-10s ||" % (
|
| 431 |
+
clave, tipo, longitud, comienzo, clave_dbf))
|
| 432 |
+
comienzo += longitud
|
| 433 |
+
sys.exit(0)
|
| 434 |
+
|
| 435 |
+
if '--cargar' in sys.argv:
|
| 436 |
+
if '--dbf' in sys.argv:
|
| 437 |
+
leer_dbf(formatos[:1], {})
|
| 438 |
+
elif '--json' in sys.argv:
|
| 439 |
+
for formato in formatos[:1]:
|
| 440 |
+
archivo = open(formato[0].lower() + ".json", "r")
|
| 441 |
+
d = json.load(archivo)
|
| 442 |
+
formato[2].extend(d)
|
| 443 |
+
archivo.close()
|
| 444 |
+
else:
|
| 445 |
+
for formato in formatos[:1]:
|
| 446 |
+
archivo = open(formato[0].lower() + ".txt", "r")
|
| 447 |
+
for linea in archivo:
|
| 448 |
+
d = leer(linea, formato[1])
|
| 449 |
+
formato[2].append(d)
|
| 450 |
+
archivo.close()
|
| 451 |
+
|
| 452 |
+
ws.Conectar("", WSDL, proxy)
|
| 453 |
+
|
| 454 |
+
if ws.Excepcion:
|
| 455 |
+
print(ws.Excepcion)
|
| 456 |
+
print(ws.Traceback)
|
| 457 |
+
sys.exit(-1)
|
| 458 |
+
|
| 459 |
+
# Datos de pruebas:
|
| 460 |
+
|
| 461 |
+
if '--test' in sys.argv:
|
| 462 |
+
transaccion_dto.append(dict(
|
| 463 |
+
gln_origen="9876543210982", gln_destino="3692581473693",
|
| 464 |
+
f_operacion=datetime.datetime.now().strftime("%d/%m/%Y"),
|
| 465 |
+
f_elaboracion=datetime.datetime.now().strftime("%d/%m/%Y"),
|
| 466 |
+
f_vto=(datetime.datetime.now() + datetime.timedelta(30)).strftime("%d/%m/%Y"),
|
| 467 |
+
id_evento=11,
|
| 468 |
+
cod_producto="88900000000001",
|
| 469 |
+
n_cantidad=1,
|
| 470 |
+
n_serie=int(time.time() * 10),
|
| 471 |
+
n_lote=datetime.datetime.now().strftime("%Y"),
|
| 472 |
+
n_cai="123456789012345",
|
| 473 |
+
n_cae="",
|
| 474 |
+
id_motivo_destruccion=0,
|
| 475 |
+
n_manifiesto="",
|
| 476 |
+
en_transporte="N",
|
| 477 |
+
n_remito="1234",
|
| 478 |
+
motivo_devolucion="",
|
| 479 |
+
observaciones="prueba",
|
| 480 |
+
n_vale_compra="",
|
| 481 |
+
apellidoNombres="Juan Peres",
|
| 482 |
+
direccion="Saraza", numero="1234",
|
| 483 |
+
localidad="Hurlingham", provincia="Buenos Aires",
|
| 484 |
+
n_postal="1688",
|
| 485 |
+
cuit="20267565393",
|
| 486 |
+
codigo_transaccion=None,
|
| 487 |
+
))
|
| 488 |
+
|
| 489 |
+
# Opciones principales:
|
| 490 |
+
|
| 491 |
+
if '--confirma' in sys.argv:
|
| 492 |
+
if '--loadxml' in sys.argv:
|
| 493 |
+
ws.LoadTestXML("trazamed_confirma.xml") # cargo respuesta
|
| 494 |
+
ok = ws.SendConfirmaTransacc(usuario="pruebasws", password="pruebasws",
|
| 495 |
+
p_ids_transac="1", f_operacion="31-12-2013")
|
| 496 |
+
if not ok:
|
| 497 |
+
raise RuntimeError(ws.Excepcion)
|
| 498 |
+
ws.SendConfirmaTransacc(*sys.argv[sys.argv.index("--confirma") + 1:])
|
| 499 |
+
elif '--alerta' in sys.argv:
|
| 500 |
+
ws.SendAlertaTransacc(*sys.argv[sys.argv.index("--alerta") + 1:])
|
| 501 |
+
elif '--cancela' in sys.argv:
|
| 502 |
+
ws.SendCancelaTransac(*sys.argv[sys.argv.index("--cancela") + 1:])
|
| 503 |
+
elif '--consulta' in sys.argv:
|
| 504 |
+
ws.GetTransacciones(
|
| 505 |
+
*sys.argv[sys.argv.index("--consulta") + 1:]
|
| 506 |
+
)
|
| 507 |
+
print("CantPaginas", ws.CantPaginas)
|
| 508 |
+
print("HayError", ws.HayError)
|
| 509 |
+
# print "TransaccionSenasa", ws.TransaccionSenasa
|
| 510 |
+
# parametros comunes de salida (columnas de la tabla):
|
| 511 |
+
claves = [k for k, v, l in TRANSACCIONES]
|
| 512 |
+
# extiendo la lista de resultado para el archivo de intercambio:
|
| 513 |
+
transacciones.extend(ws.TransaccionSenasa)
|
| 514 |
+
# encabezado de la tabla:
|
| 515 |
+
print("||", "||".join(["%s" % clave for clave in claves]), "||")
|
| 516 |
+
# recorro los datos devueltos (TransaccionSenasa):
|
| 517 |
+
while ws.LeerTransaccion():
|
| 518 |
+
for clave in claves:
|
| 519 |
+
print("||", ws.GetParametro(clave), end=' ') # imprimo cada fila
|
| 520 |
+
print("||")
|
| 521 |
+
else:
|
| 522 |
+
argv = [argv for argv in sys.argv if not argv.startswith("--")]
|
| 523 |
+
if not transaccion_dto:
|
| 524 |
+
if len(argv) > 10:
|
| 525 |
+
ws.SaveTransaccion(*argv[1:])
|
| 526 |
+
else:
|
| 527 |
+
print("ERROR: no se indicaron todos los parοΏ½metros requeridos")
|
| 528 |
+
elif transaccion_dto:
|
| 529 |
+
try:
|
| 530 |
+
usuario, password = argv[-2:]
|
| 531 |
+
except BaseException:
|
| 532 |
+
print("ADVERTENCIA: no se indico parοΏ½metros usuario y passoword")
|
| 533 |
+
usuario, password = "senasaws", "Clave2013"
|
| 534 |
+
for i, dto in enumerate(transaccion_dto):
|
| 535 |
+
print("Procesando registro", i)
|
| 536 |
+
del dto['codigo_transaccion']
|
| 537 |
+
ws.SaveTransaccion(usuario, password, **dto)
|
| 538 |
+
dto['codigo_transaccion'] = ws.CodigoTransaccion
|
| 539 |
+
errores.extend(ws.errores)
|
| 540 |
+
print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
|
| 541 |
+
ws.Resultado,
|
| 542 |
+
ws.CodigoTransaccion,
|
| 543 |
+
'|'.join(ws.Errores or []),
|
| 544 |
+
))
|
| 545 |
+
else:
|
| 546 |
+
print("ERROR: no se especificaron productos a informar")
|
| 547 |
+
|
| 548 |
+
if not transaccion_dto:
|
| 549 |
+
print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
|
| 550 |
+
ws.Resultado,
|
| 551 |
+
ws.CodigoTransaccion,
|
| 552 |
+
'|'.join(ws.Errores or []),
|
| 553 |
+
))
|
| 554 |
+
|
| 555 |
+
if ws.Excepcion:
|
| 556 |
+
print(ws.Traceback)
|
| 557 |
+
|
| 558 |
+
if '--grabar' in sys.argv:
|
| 559 |
+
if '--dbf' in sys.argv:
|
| 560 |
+
guardar_dbf(formatos, True, {})
|
| 561 |
+
elif '--json' in sys.argv:
|
| 562 |
+
for formato in formatos:
|
| 563 |
+
archivo = open(formato[0].lower() + ".json", "w")
|
| 564 |
+
json.dump(formato[2], archivo, sort_keys=True, indent=4)
|
| 565 |
+
archivo.close()
|
| 566 |
+
else:
|
| 567 |
+
for formato in formatos:
|
| 568 |
+
archivo = open(formato[0].lower() + ".txt", "w")
|
| 569 |
+
for it in formato[2]:
|
| 570 |
+
archivo.write(escribir(it, formato[1]))
|
| 571 |
+
archivo.close()
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 575 |
+
INSTALL_DIR = TrazaVet.InstallDir = get_install_dir()
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
if __name__ == '__main__':
|
| 579 |
+
|
| 580 |
+
# ajusto el encoding por defecto (si se redirije la salida)
|
| 581 |
+
if not hasattr(sys.stdout, "encoding") or sys.stdout.encoding is None:
|
| 582 |
+
import codecs
|
| 583 |
+
import locale
|
| 584 |
+
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout, "replace")
|
| 585 |
+
sys.stderr = codecs.getwriter(locale.getpreferredencoding())(sys.stderr, "replace")
|
| 586 |
+
|
| 587 |
+
if '--register' in sys.argv or '--unregister' in sys.argv:
|
| 588 |
+
import pythoncom
|
| 589 |
+
import win32com.server.register
|
| 590 |
+
win32com.server.register.UseCommandLine(TrazaVet)
|
| 591 |
+
elif "/Automate" in sys.argv:
|
| 592 |
+
# MS seems to like /automate to run the class factories.
|
| 593 |
+
import win32com.server.localserver
|
| 594 |
+
# win32com.server.localserver.main()
|
| 595 |
+
# start the server.
|
| 596 |
+
win32com.server.localserver.serve([TrazaVet._reg_clsid_])
|
| 597 |
+
else:
|
| 598 |
+
main()
|
app/pyafipws/typelib/pyafipws.idl
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// pyafipws.idl: source PyAfipWs COM Type Library
|
| 3 |
+
// influenced by msdn example2.idl and pythoncom's pippo.idl
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import "unknwn.idl","oaidl.idl";
|
| 7 |
+
|
| 8 |
+
[
|
| 9 |
+
uuid(65D24842-8691-4FB5-9B52-82393F12F4C6),
|
| 10 |
+
helpstring("PyAfipWs WSAA interface"),
|
| 11 |
+
pointer_default(unique),
|
| 12 |
+
dual,
|
| 13 |
+
object
|
| 14 |
+
//oleautomation
|
| 15 |
+
]
|
| 16 |
+
interface IWSAA : IDispatch
|
| 17 |
+
{
|
| 18 |
+
[id(1)] HRESULT CreateTRA([in] BSTR service, [in] int ttl,
|
| 19 |
+
[out, retval] BSTR * tra);
|
| 20 |
+
[id(2)] HRESULT SignTRA([in] BSTR tra, [in] BSTR cert, [in] BSTR privatekey,
|
| 21 |
+
[out, retval] BSTR * cms);
|
| 22 |
+
[id(3)] HRESULT CallWSAA([in] BSTR cms, [in] BSTR proxy, [in] BSTR url,
|
| 23 |
+
[out, retval] BSTR * ta);
|
| 24 |
+
|
| 25 |
+
[propget, id(4)] HRESULT Token( [out, retval] BSTR * token);
|
| 26 |
+
[propget, id(5)] HRESULT Sign( [out, retval] BSTR * sign);
|
| 27 |
+
[propget, id(6)] HRESULT Version( [out, retval] BSTR * sign);
|
| 28 |
+
[propget, id(7)] HRESULT XmlResponse( [out, retval] BSTR * sign);
|
| 29 |
+
|
| 30 |
+
}; //end interface def
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
[
|
| 34 |
+
uuid(84A3B5EC-D019-4B27-8DB8-469D8D8E97D1),
|
| 35 |
+
version(1.02),
|
| 36 |
+
helpstring("PyAfipWs Type Library"),
|
| 37 |
+
] library PyAfipWs
|
| 38 |
+
{
|
| 39 |
+
importlib("stdole32.tlb");
|
| 40 |
+
|
| 41 |
+
[
|
| 42 |
+
uuid(25949BFC-AE90-43D5-A577-C18F65F86B26),
|
| 43 |
+
helpstring("WSAA Component Class")
|
| 44 |
+
] coclass WSAA
|
| 45 |
+
{
|
| 46 |
+
[default]interface IWSAA;
|
| 47 |
+
}
|
| 48 |
+
}; //end library def
|
app/pyafipws/typelib/trazamed.idl
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// trazamed.idl: source PyAfipWs TrazaMed (ANMAT/PAMI) COM Type Library
|
| 3 |
+
// influenced by msdn example2.idl and pythoncom's pippo.idl
|
| 4 |
+
//
|
| 5 |
+
// Use "print pythoncom.CreateGuid()" to make a new uuid
|
| 6 |
+
// Use MIDL.EXE to compile this file to create TrazaMed.TLB
|
| 7 |
+
|
| 8 |
+
import "unknwn.idl","oaidl.idl";
|
| 9 |
+
|
| 10 |
+
[
|
| 11 |
+
uuid(423FA890-1FE5-4F53-B5F7-71A7737EDB8B),
|
| 12 |
+
helpstring("PyAfipWs TrazaMed ANMAT / PAMI interface"),
|
| 13 |
+
pointer_default(unique),
|
| 14 |
+
dual,
|
| 15 |
+
object
|
| 16 |
+
//oleautomation
|
| 17 |
+
]
|
| 18 |
+
interface ITrazaMed : IDispatch
|
| 19 |
+
{
|
| 20 |
+
[id(1)] HRESULT Conectar([in] BSTR cache, [in] BSTR wsdl,
|
| 21 |
+
[in] BSTR proxy, [in] BSTR wrapper, [in] BSTR cacert,
|
| 22 |
+
[out, retval] BOOL * ok);
|
| 23 |
+
[id(2)] HRESULT SendMedicamentos([in] BSTR usuario, [in] BSTR password,
|
| 24 |
+
[in] BSTR f_evento, [in] BSTR h_evento, [in] BSTR gln_origen, [in] BSTR gln_destino,
|
| 25 |
+
[in] BSTR n_remito, [in] BSTR n_factura, [in] BSTR vencimiento, [in] BSTR gtin, [in] BSTR lote,
|
| 26 |
+
[in] BSTR numero_serial, [in] BSTR id_obra_social, [in] BSTR id_evento,
|
| 27 |
+
[in] BSTR cuit_origen, [in] BSTR cuit_destino, [in] BSTR apellido, [in] BSTR nombres,
|
| 28 |
+
[in] BSTR tipo_docmento, [in] BSTR n_documento, [in] BSTR sexo,
|
| 29 |
+
[in] BSTR direccion, [in] BSTR numero, [in] BSTR piso, [in] BSTR depto, [in] BSTR localidad, [in] BSTR provincia,
|
| 30 |
+
[in] BSTR n_postal, [in] BSTR fecha_nacimiento, [in] BSTR telefono,
|
| 31 |
+
[out, retval] BOOL * ok);
|
| 32 |
+
[id(3)] HRESULT SendMedicamentosDHSerie([in] BSTR usuario, [in] BSTR password,
|
| 33 |
+
[in] BSTR f_evento, [in] BSTR h_evento, [in] BSTR gln_origen, [in] BSTR gln_destino,
|
| 34 |
+
[in] BSTR n_remito, [in] BSTR n_factura, [in] BSTR vencimiento, [in] BSTR gtin, [in] BSTR lote,
|
| 35 |
+
[in] BSTR desde_numero_serial, [in] BSTR hasta_numero_serial, [in] BSTR id_obra_social, [in] BSTR id_evento,
|
| 36 |
+
[in] BSTR cuit_origen, [in] BSTR cuit_destino, [in] BSTR apellido, [in] BSTR nombres,
|
| 37 |
+
[in] BSTR tipo_docmento, [in] BSTR n_documento, [in] BSTR sexo,
|
| 38 |
+
[in] BSTR direccion, [in] BSTR numero, [in] BSTR piso, [in] BSTR depto, [in] BSTR localidad, [in] BSTR provincia,
|
| 39 |
+
[in] BSTR n_postal, [in] BSTR fecha_nacimiento, [in] BSTR telefono,
|
| 40 |
+
[out, retval] BOOL * ok);
|
| 41 |
+
[id(4)] HRESULT SendCancelacTransacc([in] BSTR usuario, [in] BSTR password, [in] BSTR codigo_transaccion,
|
| 42 |
+
[out, retval] BOOL * ok);
|
| 43 |
+
|
| 44 |
+
[id(5)] HRESULT LeerError([out, retval] BSTR * texto);
|
| 45 |
+
|
| 46 |
+
[propget, id(6)] HRESULT InstallDir( [out, retval] BSTR * val);
|
| 47 |
+
[propget, id(7)] HRESULT Traceback( [out, retval] BSTR * val);
|
| 48 |
+
[propget, id(8)] HRESULT Excepcion( [out, retval] BSTR * val);
|
| 49 |
+
[propget, id(9)] HRESULT LanzarExcepciones( [in] BOOL modo, [out, retval] BOOL * val);
|
| 50 |
+
[propget, id(10)] HRESULT Version( [out, retval] BSTR * sign);
|
| 51 |
+
[propget, id(11)] HRESULT XmlRequest( [out, retval] BSTR * val);
|
| 52 |
+
[propget, id(12)] HRESULT XmlResponse( [out, retval] BSTR * val);
|
| 53 |
+
[propput, id(13)] HRESULT Username( [in] BSTR val);
|
| 54 |
+
[propput, id(14)] HRESULT Password( [in] BSTR val);
|
| 55 |
+
[propget, id(15)] HRESULT CodigoTransaccion( [out, retval] BSTR * codigo);
|
| 56 |
+
[propget, id(16)] HRESULT Resultado( [out, retval] BSTR * resultado);
|
| 57 |
+
|
| 58 |
+
[id(17)] HRESULT SetUsername([in] BSTR username);
|
| 59 |
+
[id(18)] HRESULT SetPassword([in] BSTR password);
|
| 60 |
+
[id(19)] HRESULT GetCodigoTransaccion([out, retval] BSTR * texto);
|
| 61 |
+
[id(20)] HRESULT GetResultado([out, retval] BSTR * texto);
|
| 62 |
+
|
| 63 |
+
}; //end interface def
|
| 64 |
+
|
| 65 |
+
[
|
| 66 |
+
uuid(F992EB7E-AFBD-41BB-B717-5693D3A2BADB),
|
| 67 |
+
version(1.5),
|
| 68 |
+
helpstring("PyAfipWs TrazaMed 1.05 Type Library"),
|
| 69 |
+
] library PyAfipWsTrazaMedLib
|
| 70 |
+
{
|
| 71 |
+
importlib("stdole32.tlb");
|
| 72 |
+
|
| 73 |
+
[
|
| 74 |
+
uuid(EC779985-0CED-44A5-B44D-057FCA9E3389),
|
| 75 |
+
helpstring("TrazaMed Component Class")
|
| 76 |
+
] coclass TrazaMed
|
| 77 |
+
{
|
| 78 |
+
[default]interface ITrazaMed;
|
| 79 |
+
}
|
| 80 |
+
}; //end library def
|
app/pyafipws/typelib/trazamed.tlb
ADDED
|
Binary file (5 kB). View file
|
|
|
app/pyafipws/typelib/wsaa.idl
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// wsaa.idl: source PyAfipWs WSAA COM Type Library
|
| 3 |
+
// influenced by msdn example2.idl and pythoncom's pippo.idl
|
| 4 |
+
//
|
| 5 |
+
// Use "print pythoncom.CreateGuid()" to make a new one.
|
| 6 |
+
|
| 7 |
+
import "unknwn.idl","oaidl.idl";
|
| 8 |
+
|
| 9 |
+
[
|
| 10 |
+
uuid(95392BD9-54C3-4334-A045-A069BAF7FB97),
|
| 11 |
+
helpstring("PyAfipWs WSAA interface"),
|
| 12 |
+
pointer_default(unique),
|
| 13 |
+
dual,
|
| 14 |
+
object
|
| 15 |
+
//oleautomation
|
| 16 |
+
]
|
| 17 |
+
interface IWSAA : IDispatch
|
| 18 |
+
{
|
| 19 |
+
[id(1)] HRESULT CreateTRA([in] BSTR service, [in] int ttl,
|
| 20 |
+
[out, retval] BSTR * tra);
|
| 21 |
+
[id(2)] HRESULT SignTRA([in] BSTR tra, [in] BSTR cert, [in] BSTR privatekey,
|
| 22 |
+
[out, retval] BSTR * cms);
|
| 23 |
+
[id(3)] HRESULT CallWSAA([in] BSTR cms, [in] BSTR proxy, [in] BSTR url,
|
| 24 |
+
[out, retval] BSTR * ta);
|
| 25 |
+
|
| 26 |
+
[propget, id(4)] HRESULT Token( [out, retval] BSTR * token);
|
| 27 |
+
[propget, id(5)] HRESULT Sign( [out, retval] BSTR * sign);
|
| 28 |
+
[propget, id(6)] HRESULT Version( [out, retval] BSTR * sign);
|
| 29 |
+
[propget, id(7)] HRESULT XmlResponse( [out, retval] BSTR * sign);
|
| 30 |
+
|
| 31 |
+
[id(8)] HRESULT Conectar([in] BSTR cache, [in] BSTR wsdl,
|
| 32 |
+
[in] BSTR proxy, [in] BSTR wrapper, [in] BSTR cacert,
|
| 33 |
+
[in] int timeout,
|
| 34 |
+
[out, retval] BOOL * ok);
|
| 35 |
+
[id(9)] HRESULT LoginCMS([in] BSTR cms, [out, retval] BSTR * ta);
|
| 36 |
+
[id(10)] HRESULT AnalizarXml([in] BSTR xml, [out, retval] BOOL * ok);
|
| 37 |
+
[id(11)] HRESULT ObtenerTagXml([in] BSTR tags, [out, retval] BSTR * texto);
|
| 38 |
+
[id(12)] HRESULT Expirado([in] BSTR fecha, [out, retval] BOOL * ok);
|
| 39 |
+
|
| 40 |
+
[propget, id(13)] HRESULT InstallDir( [out, retval] BSTR * val);
|
| 41 |
+
[propget, id(14)] HRESULT Traceback( [out, retval] BSTR * val);
|
| 42 |
+
[propget, id(15)] HRESULT Excepcion( [out, retval] BSTR * val);
|
| 43 |
+
[propget, id(16)] HRESULT SoapFault( [out, retval] BSTR * val);
|
| 44 |
+
[propput, id(17)] HRESULT LanzarExcepciones( [in] BOOL * val);
|
| 45 |
+
[propget, id(18)] HRESULT XmlRequest( [out, retval] BSTR * val);
|
| 46 |
+
|
| 47 |
+
[id(19)] HRESULT AnalizarCertificado(
|
| 48 |
+
[in] BSTR crt,
|
| 49 |
+
[in] BOOL binary,
|
| 50 |
+
[out, retval] BOOL * ok);
|
| 51 |
+
|
| 52 |
+
[propget, id(20)] HRESULT Identidad( [out, retval] BSTR * val);
|
| 53 |
+
[propget, id(21)] HRESULT Caducidad( [out, retval] BSTR * val);
|
| 54 |
+
[propget, id(22)] HRESULT Emisor( [out, retval] BSTR * val);
|
| 55 |
+
[propget, id(27)] HRESULT CertX509( [out, retval] BSTR * val);
|
| 56 |
+
|
| 57 |
+
[id(23)] HRESULT CrearClavePrivada(
|
| 58 |
+
[in] BSTR filename,
|
| 59 |
+
[in] int key_length,
|
| 60 |
+
[in] long pub_exponent,
|
| 61 |
+
[in] BSTR passphrase,
|
| 62 |
+
[out, retval] void
|
| 63 |
+
);
|
| 64 |
+
|
| 65 |
+
[id(24)] HRESULT CrearPedidoCertificado(
|
| 66 |
+
[in] BSTR cuit,
|
| 67 |
+
[in] BSTR empresa,
|
| 68 |
+
[in] BSTR nombre,
|
| 69 |
+
[in] BSTR filename,
|
| 70 |
+
[out, retval] void
|
| 71 |
+
);
|
| 72 |
+
|
| 73 |
+
[id(25)] HRESULT SetParametro(
|
| 74 |
+
[in] BSTR clave,
|
| 75 |
+
[in] BSTR valor,
|
| 76 |
+
[out, retval] BSTR * ok);
|
| 77 |
+
|
| 78 |
+
[id(26)] HRESULT Autenticar(
|
| 79 |
+
[in] BSTR service,
|
| 80 |
+
[in] BSTR crt,
|
| 81 |
+
[in] BSTR key,
|
| 82 |
+
[in] BSTR wsdl,
|
| 83 |
+
[in] BSTR proxy,
|
| 84 |
+
[in] BSTR wrapper,
|
| 85 |
+
[in] BSTR cacert,
|
| 86 |
+
[in] BOOL debug,
|
| 87 |
+
[out, retval] BSTR * TA);
|
| 88 |
+
|
| 89 |
+
}; //end interface def
|
| 90 |
+
|
| 91 |
+
[
|
| 92 |
+
uuid(30E9C94B-7385-4534-9A80-DF50FD169253),
|
| 93 |
+
version(2.11),
|
| 94 |
+
helpstring("PyAfipWs WSAA 2.11 Type Library"),
|
| 95 |
+
] library PyAfipWsWSAALib
|
| 96 |
+
{
|
| 97 |
+
importlib("stdole32.tlb");
|
| 98 |
+
|
| 99 |
+
[
|
| 100 |
+
uuid(FC5FDFC3-1F41-4E4D-A346-5459DE1CE973),
|
| 101 |
+
helpstring("WSAA Component Class")
|
| 102 |
+
] coclass WSAA
|
| 103 |
+
{
|
| 104 |
+
[default]interface IWSAA;
|
| 105 |
+
}
|
| 106 |
+
}; //end library def
|
app/pyafipws/typelib/wsaa.tlb
ADDED
|
Binary file (4.7 kB). View file
|
|
|
app/pyafipws/typelib/wsfev1.idl
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// WSFEv1.idl: source PyAfipWs WSFEv1 COM Type Library
|
| 3 |
+
// influenced by msdn example2.idl and pythoncom's pippo.idl
|
| 4 |
+
//
|
| 5 |
+
// Use "print pythoncom.CreateGuid()" to make a new one.
|
| 6 |
+
|
| 7 |
+
import "unknwn.idl","oaidl.idl";
|
| 8 |
+
|
| 9 |
+
[
|
| 10 |
+
uuid(9588B307-67B7-4F27-8968-B78A37D342A2),
|
| 11 |
+
helpstring("PyAfipWs WSFEv1 interface"),
|
| 12 |
+
pointer_default(unique),
|
| 13 |
+
dual,
|
| 14 |
+
object
|
| 15 |
+
//oleautomation
|
| 16 |
+
]
|
| 17 |
+
interface IWSFEv1 : IDispatch
|
| 18 |
+
{
|
| 19 |
+
[id( 1)] HRESULT CrearFactura(
|
| 20 |
+
[in] BSTR concepto,
|
| 21 |
+
[in] BSTR tipo_doc,
|
| 22 |
+
[in] BSTR nro_doc,
|
| 23 |
+
[in] BSTR tipo_cbte,
|
| 24 |
+
[in] BSTR punto_vta,
|
| 25 |
+
[in] BSTR cbt_desde,
|
| 26 |
+
[in] BSTR cbt_hasta,
|
| 27 |
+
[in] BSTR imp_total,
|
| 28 |
+
[in] BSTR imp_tot_conc,
|
| 29 |
+
[in] BSTR imp_neto,
|
| 30 |
+
[in] BSTR imp_iva,
|
| 31 |
+
[in] BSTR imp_trib,
|
| 32 |
+
[in] BSTR imp_op_ex,
|
| 33 |
+
[in] BSTR fecha_cbte,
|
| 34 |
+
[in] BSTR fecha_venc_pago,
|
| 35 |
+
[in] BSTR fecha_serv_desde,
|
| 36 |
+
[in] BSTR fecha_serv_hasta,
|
| 37 |
+
[in] BSTR moneda_id,
|
| 38 |
+
[in] BSTR moneda_ctz,
|
| 39 |
+
[in] BSTR caea,
|
| 40 |
+
[out, retval] BOOL * ok);
|
| 41 |
+
[id( 2)] HRESULT AgregarIva(
|
| 42 |
+
[in] BSTR iva_id,
|
| 43 |
+
[in] BSTR base_imp,
|
| 44 |
+
[in] BSTR importe,
|
| 45 |
+
[out, retval] BOOL * ok);
|
| 46 |
+
[id( 3)] HRESULT CAESolicitar(
|
| 47 |
+
[out, retval] BSTR * cae);
|
| 48 |
+
[id( 4)] HRESULT AgregarTributo(
|
| 49 |
+
[in] BSTR tributo_id,
|
| 50 |
+
[in] BSTR desc,
|
| 51 |
+
[in] BSTR base_imp,
|
| 52 |
+
[in] BSTR alic,
|
| 53 |
+
[in] BSTR importe,
|
| 54 |
+
[out, retval] BOOL * ok);
|
| 55 |
+
[id( 5)] HRESULT AgregarCmpAsoc(
|
| 56 |
+
[in] BSTR tipo,
|
| 57 |
+
[in] BSTR pto_vta,
|
| 58 |
+
[in] BSTR nro,
|
| 59 |
+
[out, retval] BOOL * ok);
|
| 60 |
+
[id( 6)] HRESULT CompUltimoAutorizado(
|
| 61 |
+
[in] BSTR tipo_cbte,
|
| 62 |
+
[in] BSTR punto_vta,
|
| 63 |
+
[out, retval] BSTR * cbte_nro);
|
| 64 |
+
[id( 7)] HRESULT CompConsultar(
|
| 65 |
+
[in] BSTR tipo_cbte,
|
| 66 |
+
[in] BSTR punto_vta,
|
| 67 |
+
[in] BSTR cbte_nro,
|
| 68 |
+
[in] BSTR reproceso,
|
| 69 |
+
[out, retval] BSTR * cae);
|
| 70 |
+
[id( 8)] HRESULT CAEASolicitar(
|
| 71 |
+
[in] BSTR periodo,
|
| 72 |
+
[in] BSTR orden,
|
| 73 |
+
[out, retval] BSTR * caea);
|
| 74 |
+
[id( 9)] HRESULT CAEAConsultar(
|
| 75 |
+
[in] BSTR periodo,
|
| 76 |
+
[in] BSTR orden,
|
| 77 |
+
[out, retval] BSTR * caea);
|
| 78 |
+
[id(10)] HRESULT CAEARegInformativo(
|
| 79 |
+
[out, retval] BSTR * caea);
|
| 80 |
+
[id(11)] HRESULT CAEASinMovimientoInformar(
|
| 81 |
+
[in] BSTR punto_vta,
|
| 82 |
+
[in] BSTR caea,
|
| 83 |
+
[out, retval] BOOL * ok);
|
| 84 |
+
[id(12)] HRESULT ParamGetTiposCbte(
|
| 85 |
+
[in] BSTR sep,
|
| 86 |
+
[out, retval] VARIANT * ret);
|
| 87 |
+
[id(13)] HRESULT ParamGetTiposConcepto(
|
| 88 |
+
[in] BSTR sep,
|
| 89 |
+
[out, retval] VARIANT * ret);
|
| 90 |
+
[id(14)] HRESULT ParamGetTiposDoc(
|
| 91 |
+
[in] BSTR sep,
|
| 92 |
+
[out, retval] VARIANT * ret);
|
| 93 |
+
[id(15)] HRESULT ParamGetTiposIva(
|
| 94 |
+
[in] BSTR sep,
|
| 95 |
+
[out, retval] VARIANT * ret);
|
| 96 |
+
[id(16)] HRESULT ParamGetTiposMonedas(
|
| 97 |
+
[in] BSTR sep,
|
| 98 |
+
[out, retval] VARIANT * ret);
|
| 99 |
+
[id(17)] HRESULT ParamGetTiposOpcional(
|
| 100 |
+
[in] BSTR sep,
|
| 101 |
+
[out, retval] VARIANT * ret);
|
| 102 |
+
[id(18)] HRESULT ParamGetTiposTributos(
|
| 103 |
+
[in] BSTR sep,
|
| 104 |
+
[out, retval] VARIANT * ret);
|
| 105 |
+
[id(19)] HRESULT ParamGetCotizacion(
|
| 106 |
+
[in] BSTR moneda_id,
|
| 107 |
+
[out, retval] BSTR * ctz);
|
| 108 |
+
[id(20)] HRESULT ParamGetPtosVenta(
|
| 109 |
+
[in] BSTR sep,
|
| 110 |
+
[out, retval] VARIANT * ret);
|
| 111 |
+
[id(21)] HRESULT AnalizarXml(
|
| 112 |
+
[in] BSTR xml,
|
| 113 |
+
[out, retval] BOOL * ok);
|
| 114 |
+
[id(22)] HRESULT ObtenerTagXml(
|
| 115 |
+
[out, retval] BOOL * ok);
|
| 116 |
+
[id(23)] HRESULT Dummy(
|
| 117 |
+
[out, retval] BOOL * ok);
|
| 118 |
+
[id(24)] HRESULT Conectar(
|
| 119 |
+
[in] BSTR cache,
|
| 120 |
+
[in] BSTR wsdl,
|
| 121 |
+
[in] BSTR proxy,
|
| 122 |
+
[in] BSTR wrapper,
|
| 123 |
+
[in] BSTR cacert,
|
| 124 |
+
[in] int timeout,
|
| 125 |
+
[out, retval] BOOL * ok);
|
| 126 |
+
[id(25)] HRESULT DebugLog(
|
| 127 |
+
[out, retval] BSTR * msg);
|
| 128 |
+
|
| 129 |
+
[propput, id(27)] HRESULT Token( [in] BSTR val);
|
| 130 |
+
[propput, id(28)] HRESULT Sign( [in] BSTR val);
|
| 131 |
+
[propput, id(29)] HRESULT Cuit( [in] BSTR val);
|
| 132 |
+
[propget, id(30)] HRESULT AppServerStatus( [out, retval] BSTR * val);
|
| 133 |
+
[propget, id(31)] HRESULT DbServerStatus( [out, retval] BSTR * val);
|
| 134 |
+
[propget, id(32)] HRESULT AuthServerStatus( [out, retval] BSTR * val);
|
| 135 |
+
[propget, id(33)] HRESULT XmlRequest( [out, retval] BSTR * val);
|
| 136 |
+
[propget, id(34)] HRESULT XmlResponse( [out, retval] BSTR * val);
|
| 137 |
+
[propget, id(35)] HRESULT Version( [out, retval] BSTR * val);
|
| 138 |
+
[propget, id(36)] HRESULT Excepcion( [out, retval] BSTR * val);
|
| 139 |
+
[propput, id(37)] HRESULT LanzarExcepciones( [in] BOOL * val);
|
| 140 |
+
[propget, id(38)] HRESULT Resultado( [out, retval] BSTR * val);
|
| 141 |
+
[propget, id(39)] HRESULT Obs( [out, retval] BSTR * val);
|
| 142 |
+
[propget, id(40)] HRESULT Observaciones( [out, retval] VARIANT * val);
|
| 143 |
+
[propget, id(41)] HRESULT Traceback( [out, retval] BSTR * val);
|
| 144 |
+
[propget, id(42)] HRESULT InstallDir( [out, retval] BSTR * val);
|
| 145 |
+
[propget, id(43)] HRESULT CAE( [out, retval] BSTR * val);
|
| 146 |
+
[propget, id(44)] HRESULT Vencimiento( [out, retval] BSTR * val);
|
| 147 |
+
[propget, id(45)] HRESULT Eventos( [out, retval] VARIANT * val);
|
| 148 |
+
[propget, id(46)] HRESULT Errores( [out, retval] VARIANT * val);
|
| 149 |
+
[propget, id(47)] HRESULT ErrCode( [out, retval] BSTR * val);
|
| 150 |
+
[propget, id(48)] HRESULT ErrMsg( [out, retval] BSTR * val);
|
| 151 |
+
[propput, id(49)] HRESULT Reprocesar( [in] BSTR val);
|
| 152 |
+
[propget, id(50)] HRESULT Reproceso( [out, retval] BSTR * val);
|
| 153 |
+
[propget, id(51)] HRESULT EmisionTipo( [out, retval] BSTR * val);
|
| 154 |
+
[propget, id(52)] HRESULT CAEA( [out, retval] BSTR * val);
|
| 155 |
+
[propget, id(53)] HRESULT CbteNro( [out, retval] BSTR * val);
|
| 156 |
+
[propget, id(54)] HRESULT CbtDesde( [out, retval] BSTR * val);
|
| 157 |
+
[propget, id(55)] HRESULT CbtHasta( [out, retval] BSTR * val);
|
| 158 |
+
[propget, id(56)] HRESULT FechaCbte( [out, retval] BSTR * val);
|
| 159 |
+
[propget, id(57)] HRESULT ImpTotal( [out, retval] BSTR * val);
|
| 160 |
+
[propget, id(58)] HRESULT ImpNeto( [out, retval] BSTR * val);
|
| 161 |
+
[propget, id(59)] HRESULT ImptoLiq( [out, retval] BSTR * val);
|
| 162 |
+
[propget, id(60)] HRESULT ImpOpExImptIVA( [out, retval] BSTR * val);
|
| 163 |
+
[propget, id(61)] HRESULT ImpOpEx( [out, retval] BSTR * val);
|
| 164 |
+
[propget, id(62)] HRESULT ImpTrib( [out, retval] BSTR * val);
|
| 165 |
+
|
| 166 |
+
[id(63)] HRESULT SetParametros(
|
| 167 |
+
[in] BSTR cuit,
|
| 168 |
+
[in] BSTR token,
|
| 169 |
+
[in] BSTR sign,
|
| 170 |
+
[out, retval] BOOL * ok);
|
| 171 |
+
|
| 172 |
+
[id(64)] HRESULT GetParametro(
|
| 173 |
+
[in] BSTR clave,
|
| 174 |
+
[out, retval] BSTR * ok);
|
| 175 |
+
|
| 176 |
+
[id(65)] HRESULT SetParametro(
|
| 177 |
+
[in] BSTR clave,
|
| 178 |
+
[in] BSTR valor,
|
| 179 |
+
[out, retval] BSTR * ok);
|
| 180 |
+
|
| 181 |
+
[id(66)] HRESULT EstablecerCampoFactura(
|
| 182 |
+
[in] BSTR campo,
|
| 183 |
+
[in] BSTR valor,
|
| 184 |
+
[out, retval] BOOL * ok);
|
| 185 |
+
|
| 186 |
+
[id(67)] HRESULT ObtenerCampoFactura(
|
| 187 |
+
[in] BSTR campo,
|
| 188 |
+
[out, retval] BSTR * valor);
|
| 189 |
+
|
| 190 |
+
[id(68)] HRESULT SetTicketAcceso(
|
| 191 |
+
[in] BSTR ta,
|
| 192 |
+
[out, retval] BOOL * ok);
|
| 193 |
+
|
| 194 |
+
[id(69)] HRESULT AgregarOpcional(
|
| 195 |
+
[in] BSTR opcional_id,
|
| 196 |
+
[in] BSTR valor,
|
| 197 |
+
[out, retval] BOOL * ok);
|
| 198 |
+
|
| 199 |
+
[propget, id(70)] HRESULT ImpIVA( [out, retval] BSTR * val);
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
[id(71)] HRESULT CAESolicitarX([out, retval] int * cant_reg);
|
| 203 |
+
[id(72)] HRESULT IniciarFacturasX([out, retval] BOOL * ok);
|
| 204 |
+
[id(73)] HRESULT AgregarFacturaX([out, retval] BOOL * ok);
|
| 205 |
+
[id(74)] HRESULT LeerFacturaX([in] int indice,
|
| 206 |
+
[out, retval] BOOL * ok);
|
| 207 |
+
|
| 208 |
+
}; //end interface def
|
| 209 |
+
|
| 210 |
+
[
|
| 211 |
+
uuid(B1D7283C-3EC2-463E-89B4-11F5228E2A15),
|
| 212 |
+
version(1.18),
|
| 213 |
+
helpstring("PyAfipWs WSFEv1 1.18 Type Library"),
|
| 214 |
+
] library PyAfipWsWSFEv1Lib
|
| 215 |
+
{
|
| 216 |
+
importlib("stdole32.tlb");
|
| 217 |
+
|
| 218 |
+
[
|
| 219 |
+
uuid(D50B5D14-621E-4473-AC63-8A7B5E9DD57F),
|
| 220 |
+
helpstring("WSFEv1 Component Class")
|
| 221 |
+
] coclass WSFEv1
|
| 222 |
+
{
|
| 223 |
+
[default]interface IWSFEv1;
|
| 224 |
+
}
|
| 225 |
+
}; //end library def
|
app/pyafipws/typelib/wsfev1.tlb
ADDED
|
Binary file (8.99 kB). View file
|
|
|
app/pyafipws/utils.py
ADDED
|
@@ -0,0 +1,936 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
#
|
| 4 |
+
# This program is free software; you can redistribute it and/or modify
|
| 5 |
+
# it under the terms of the GNU General Public License as published by the
|
| 6 |
+
# Free Software Foundation; version 3.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"MΓ³dulo con funciones auxiliares para el manejo de errores y temas comunes"
|
| 14 |
+
|
| 15 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 16 |
+
__copyright__ = "Copyright (C) 2013 Mariano Reingart"
|
| 17 |
+
__license__ = "GPL 3.0"
|
| 18 |
+
|
| 19 |
+
from io import IOBase
|
| 20 |
+
import datetime
|
| 21 |
+
import functools
|
| 22 |
+
import inspect
|
| 23 |
+
import locale
|
| 24 |
+
import socket
|
| 25 |
+
import sys
|
| 26 |
+
import os
|
| 27 |
+
import stat
|
| 28 |
+
import time
|
| 29 |
+
import traceback
|
| 30 |
+
import warnings
|
| 31 |
+
from io import StringIO
|
| 32 |
+
from decimal import Decimal
|
| 33 |
+
from urllib.parse import urlencode
|
| 34 |
+
from urllib.parse import urlparse
|
| 35 |
+
import unicodedata
|
| 36 |
+
import mimetypes
|
| 37 |
+
from email.generator import _make_boundary
|
| 38 |
+
from html.parser import HTMLParser
|
| 39 |
+
from http.cookies import SimpleCookie
|
| 40 |
+
from configparser import ConfigParser as SafeConfigParser
|
| 41 |
+
|
| 42 |
+
from pysimplesoap.client import SimpleXMLElement, SoapClient, SoapFault, parse_proxy, set_http_wrapper
|
| 43 |
+
from pkg_resources import parse_version
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
import json
|
| 47 |
+
except ImportError:
|
| 48 |
+
try:
|
| 49 |
+
import simplejson as json
|
| 50 |
+
except BaseException:
|
| 51 |
+
print("para soporte de JSON debe instalar simplejson")
|
| 52 |
+
json = None
|
| 53 |
+
|
| 54 |
+
import httplib2
|
| 55 |
+
|
| 56 |
+
DEBUG = False
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
# Funciones para manejo de errores:
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def exception_info(current_filename=None, index=-1):
|
| 63 |
+
"Analizar el traceback y armar un dict con la info amigable user-friendly"
|
| 64 |
+
# guardo el traceback original (por si hay una excepciΓ³n):
|
| 65 |
+
info = sys.exc_info() # exc_type, exc_value, exc_traceback
|
| 66 |
+
# importante: no usar unpacking porque puede causar memory leak
|
| 67 |
+
if not current_filename:
|
| 68 |
+
# genero un call stack para ver quien me llamΓ³ y limitar la traza:
|
| 69 |
+
# advertencia: esto es necesario ya que en py2exe no tengo __file__
|
| 70 |
+
try:
|
| 71 |
+
raise ZeroDivisionError
|
| 72 |
+
except ZeroDivisionError:
|
| 73 |
+
f = sys.exc_info()[2].tb_frame.f_back
|
| 74 |
+
current_filename = os.path.normpath(os.path.abspath(f.f_code.co_filename))
|
| 75 |
+
|
| 76 |
+
# extraer la ΓΊltima traza del archivo solicitado:
|
| 77 |
+
# (ΓΊtil para no alargar demasiado la traza con lineas de las librerΓas)
|
| 78 |
+
ret = {'filename': "", 'lineno': 0, 'function_name': "", 'code': ""}
|
| 79 |
+
try:
|
| 80 |
+
for (filename, lineno, fn, text) in traceback.extract_tb(info[2]):
|
| 81 |
+
if os.path.normpath(os.path.abspath(filename)) == current_filename:
|
| 82 |
+
ret = {'filename': filename, 'lineno': lineno,
|
| 83 |
+
'function_name': fn, 'code': text}
|
| 84 |
+
except Exception as e:
|
| 85 |
+
pass
|
| 86 |
+
# obtengo el mensaje de excepcion tal cual lo formatea python:
|
| 87 |
+
# (para evitar errores de encoding)
|
| 88 |
+
try:
|
| 89 |
+
ret['msg'] = traceback.format_exception_only(*info[0:2])[0]
|
| 90 |
+
except BaseException:
|
| 91 |
+
ret['msg'] = '<no disponible>'
|
| 92 |
+
# obtener el nombre de la excepcion (ej. "NameError")
|
| 93 |
+
try:
|
| 94 |
+
ret['name'] = info[0].__name__
|
| 95 |
+
except BaseException:
|
| 96 |
+
ret['name'] = 'Exception'
|
| 97 |
+
# obtener la traza formateada como string:
|
| 98 |
+
try:
|
| 99 |
+
tb = traceback.format_exception(*info)
|
| 100 |
+
ret['tb'] = ''.join(tb)
|
| 101 |
+
except BaseException:
|
| 102 |
+
ret['tb'] = ""
|
| 103 |
+
return ret
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def inicializar_y_capturar_excepciones(func):
|
| 107 |
+
"Decorador para inicializar y capturar errores (version para webservices)"
|
| 108 |
+
@functools.wraps(func)
|
| 109 |
+
def capturar_errores_wrapper(self, *args, **kwargs):
|
| 110 |
+
try:
|
| 111 |
+
# inicializo (limpio variables)
|
| 112 |
+
self.Errores = [] # listas de str para lenguajes legados
|
| 113 |
+
self.Observaciones = []
|
| 114 |
+
self.errores = [] # listas de dict para usar en python
|
| 115 |
+
self.observaciones = []
|
| 116 |
+
self.Eventos = []
|
| 117 |
+
self.Traceback = self.Excepcion = ""
|
| 118 |
+
self.ErrCode = self.ErrMsg = self.Obs = ""
|
| 119 |
+
# limpio variables especificas del webservice:
|
| 120 |
+
self.inicializar()
|
| 121 |
+
# actualizo los parΓ‘metros
|
| 122 |
+
kwargs.update(self.params_in)
|
| 123 |
+
# limpio los parΓ‘metros
|
| 124 |
+
self.params_in = {}
|
| 125 |
+
self.params_out = {}
|
| 126 |
+
# llamo a la funciΓ³n (con reintentos)
|
| 127 |
+
retry = self.reintentos + 1
|
| 128 |
+
while retry:
|
| 129 |
+
try:
|
| 130 |
+
retry -= 1
|
| 131 |
+
return func(self, *args, **kwargs)
|
| 132 |
+
except socket.error as e:
|
| 133 |
+
if e.errno not in (10054, 10053):
|
| 134 |
+
# solo reintentar si el error es de conexiΓ³n
|
| 135 |
+
# (10054, 'Connection reset by peer')
|
| 136 |
+
# (10053, 'Software caused connection abort')
|
| 137 |
+
raise
|
| 138 |
+
else:
|
| 139 |
+
if DEBUG:
|
| 140 |
+
print(e, "Reintentando...")
|
| 141 |
+
self.log(exception_info().get("msg", ""))
|
| 142 |
+
|
| 143 |
+
except SoapFault as e:
|
| 144 |
+
# guardo destalle de la excepciΓ³n SOAP
|
| 145 |
+
self.ErrCode = str(e.faultcode)
|
| 146 |
+
self.ErrMsg = str(e.faultstring)
|
| 147 |
+
self.Excepcion = "%s: %s" % (e.faultcode, e.faultstring, )
|
| 148 |
+
if self.LanzarExcepciones:
|
| 149 |
+
raise
|
| 150 |
+
except Exception as e:
|
| 151 |
+
ex = exception_info()
|
| 152 |
+
self.Traceback = ex.get("tb", "")
|
| 153 |
+
try:
|
| 154 |
+
self.Excepcion = ex.get("msg", "")
|
| 155 |
+
except BaseException:
|
| 156 |
+
self.Excepcion = "<no disponible>"
|
| 157 |
+
if self.LanzarExcepciones:
|
| 158 |
+
raise
|
| 159 |
+
else:
|
| 160 |
+
self.ErrMsg = self.Excepcion
|
| 161 |
+
finally:
|
| 162 |
+
# guardo datos de depuraciΓ³n
|
| 163 |
+
if self.client:
|
| 164 |
+
self.XmlRequest = self.client.xml_request
|
| 165 |
+
self.XmlResponse = self.client.xml_response
|
| 166 |
+
return capturar_errores_wrapper
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def inicializar_y_capturar_excepciones_simple(func):
|
| 170 |
+
"Decorador para inicializar y capturar errores (versiΓ³n bΓ‘sica indep.)"
|
| 171 |
+
@functools.wraps(func)
|
| 172 |
+
def capturar_errores_wrapper(self, *args, **kwargs):
|
| 173 |
+
self.inicializar()
|
| 174 |
+
try:
|
| 175 |
+
return func(self, *args, **kwargs)
|
| 176 |
+
except BaseException:
|
| 177 |
+
ex = exception_info()
|
| 178 |
+
self.Excepcion = ex['name']
|
| 179 |
+
self.Traceback = ex['msg']
|
| 180 |
+
if self.LanzarExcepciones:
|
| 181 |
+
raise
|
| 182 |
+
else:
|
| 183 |
+
return False
|
| 184 |
+
return capturar_errores_wrapper
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class BaseWS:
|
| 188 |
+
"Infraestructura basica para interfaces webservices de AFIP"
|
| 189 |
+
|
| 190 |
+
def __init__(self, reintentos=1):
|
| 191 |
+
self.reintentos = reintentos
|
| 192 |
+
self.xml = self.client = self.Log = None
|
| 193 |
+
self.params_in = {}
|
| 194 |
+
self.inicializar()
|
| 195 |
+
self.Token = self.Sign = ""
|
| 196 |
+
self.LanzarExcepciones = True
|
| 197 |
+
|
| 198 |
+
def inicializar(self):
|
| 199 |
+
self.Excepcion = self.Traceback = ""
|
| 200 |
+
self.XmlRequest = self.XmlResponse = ""
|
| 201 |
+
|
| 202 |
+
def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, timeout=30, soap_server=None):
|
| 203 |
+
"Conectar cliente soap del web service"
|
| 204 |
+
try:
|
| 205 |
+
# analizar transporte y servidor proxy:
|
| 206 |
+
if wrapper:
|
| 207 |
+
Http = set_http_wrapper(wrapper)
|
| 208 |
+
self.Version = self.Version + " " + Http._wrapper_version
|
| 209 |
+
if isinstance(proxy, dict):
|
| 210 |
+
proxy_dict = proxy
|
| 211 |
+
else:
|
| 212 |
+
proxy_dict = parse_proxy(proxy)
|
| 213 |
+
self.log("Proxy Dict: %s" % str(proxy_dict))
|
| 214 |
+
if self.HOMO or not wsdl:
|
| 215 |
+
wsdl = self.WSDL
|
| 216 |
+
# agregar sufijo para descargar descripciΓ³n del servicio ?WSDL o ?wsdl
|
| 217 |
+
if not wsdl.endswith(self.WSDL[-5:]) and wsdl.startswith("http"):
|
| 218 |
+
wsdl += self.WSDL[-5:]
|
| 219 |
+
if not cache or self.HOMO:
|
| 220 |
+
# use 'cache' from installation base directory
|
| 221 |
+
cache = os.path.join(self.InstallDir, 'cache')
|
| 222 |
+
# deshabilitar verificaciΓ³n cert. servidor si es nulo falso vacio
|
| 223 |
+
if not cacert:
|
| 224 |
+
cacert = None
|
| 225 |
+
elif cacert is True or cacert.lower() == 'default':
|
| 226 |
+
# usar certificados predeterminados que vienen en la biblioteca
|
| 227 |
+
try:
|
| 228 |
+
import certifi
|
| 229 |
+
cacert = certifi.where()
|
| 230 |
+
except ImportError:
|
| 231 |
+
cacert = os.path.join(httplib2.__path__[0], 'cacerts.txt')
|
| 232 |
+
elif cacert.startswith("-----BEGIN CERTIFICATE-----"):
|
| 233 |
+
pass
|
| 234 |
+
else:
|
| 235 |
+
if not os.path.exists(cacert):
|
| 236 |
+
self.log("Buscando CACERT en conf...")
|
| 237 |
+
cacert = os.path.join(self.InstallDir, "conf", os.path.basename(cacert))
|
| 238 |
+
if cacert and not os.path.exists(cacert):
|
| 239 |
+
self.log("No se encuentra CACERT: %s" % str(cacert))
|
| 240 |
+
warnings.warn("No se encuentra CACERT: %s" % str(cacert))
|
| 241 |
+
cacert = None # wrong version, certificates not found...
|
| 242 |
+
raise RuntimeError("Error de configuracion CACERT ver DebugLog")
|
| 243 |
+
return False
|
| 244 |
+
|
| 245 |
+
self.log("Conectando a wsdl=%s cache=%s proxy=%s" % (wsdl, cache, proxy_dict))
|
| 246 |
+
# analizar espacio de nombres (axis vs .net):
|
| 247 |
+
ns = 'ser' if self.WSDL[-5:] == "?wsdl" else None
|
| 248 |
+
self.client = SoapClient(
|
| 249 |
+
wsdl=wsdl,
|
| 250 |
+
cache=cache,
|
| 251 |
+
proxy=proxy_dict,
|
| 252 |
+
cacert=cacert,
|
| 253 |
+
timeout=timeout,
|
| 254 |
+
ns=ns, soap_server=soap_server,
|
| 255 |
+
trace="--trace" in sys.argv)
|
| 256 |
+
self.cache = cache # utilizado por WSLPG y WSAA (Ticket de Acceso)
|
| 257 |
+
self.wsdl = wsdl # utilizado por TrazaMed (para corregir el location)
|
| 258 |
+
# corrijo ubicaciΓ³n del servidor (puerto http 80 en el WSDL AFIP)
|
| 259 |
+
for service in list(self.client.services.values()):
|
| 260 |
+
for port in list(service['ports'].values()):
|
| 261 |
+
location = port['location']
|
| 262 |
+
if location and location.startswith("http://"):
|
| 263 |
+
warnings.warn("Corrigiendo WSDL ... %s" % location)
|
| 264 |
+
location = location.replace("http://", "https://").replace(":80", ":443")
|
| 265 |
+
# usar servidor real si en el WSDL figura "localhost"
|
| 266 |
+
localhost = 'https://localhost:'
|
| 267 |
+
if location.startswith(localhost):
|
| 268 |
+
url = urlparse(wsdl)
|
| 269 |
+
location = location.replace("localhost", url.hostname)
|
| 270 |
+
location = location.replace(":9051", ":443")
|
| 271 |
+
port['location'] = location
|
| 272 |
+
return True
|
| 273 |
+
except BaseException:
|
| 274 |
+
ex = traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])
|
| 275 |
+
self.Traceback = ''.join(ex)
|
| 276 |
+
try:
|
| 277 |
+
self.Excepcion = traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[0]
|
| 278 |
+
except BaseException:
|
| 279 |
+
self.Excepcion = "<no disponible>"
|
| 280 |
+
if self.LanzarExcepciones:
|
| 281 |
+
raise
|
| 282 |
+
return False
|
| 283 |
+
|
| 284 |
+
def log(self, msg):
|
| 285 |
+
"Dejar mensaje en bitacora de depuraciΓ³n (mΓ©todo interno)"
|
| 286 |
+
if not isinstance(msg, str):
|
| 287 |
+
msg = str(msg, 'utf8', 'ignore')
|
| 288 |
+
if not self.Log:
|
| 289 |
+
self.Log = StringIO()
|
| 290 |
+
self.Log.write(msg)
|
| 291 |
+
self.Log.write('\n\r')
|
| 292 |
+
if DEBUG:
|
| 293 |
+
warnings.warn(msg)
|
| 294 |
+
|
| 295 |
+
def DebugLog(self):
|
| 296 |
+
"Devolver y limpiar la bitΓ‘cora de depuraciΓ³n"
|
| 297 |
+
if self.Log:
|
| 298 |
+
msg = self.Log.getvalue()
|
| 299 |
+
# limpiar log
|
| 300 |
+
self.Log.close()
|
| 301 |
+
self.Log = None
|
| 302 |
+
else:
|
| 303 |
+
msg = ''
|
| 304 |
+
return msg
|
| 305 |
+
|
| 306 |
+
def LoadTestXML(self, xml):
|
| 307 |
+
"Cargar un archivo de pruebas con la respuesta simulada (depuraciΓ³n)"
|
| 308 |
+
# si el parametro es un nombre de archivo, cargar el contenido:
|
| 309 |
+
if os.path.exists(xml):
|
| 310 |
+
xml = open(xml).read()
|
| 311 |
+
|
| 312 |
+
class DummyHTTP:
|
| 313 |
+
def __init__(self, xml_response):
|
| 314 |
+
self.xml_response = xml_response
|
| 315 |
+
|
| 316 |
+
def request(self, location, method, body, headers):
|
| 317 |
+
return {}, self.xml_response
|
| 318 |
+
self.client.http = DummyHTTP(xml)
|
| 319 |
+
|
| 320 |
+
@property
|
| 321 |
+
def xml_request(self):
|
| 322 |
+
return self.XmlRequest
|
| 323 |
+
|
| 324 |
+
@property
|
| 325 |
+
def xml_response(self):
|
| 326 |
+
return self.XmlResponse
|
| 327 |
+
|
| 328 |
+
def AnalizarXml(self, xml=""):
|
| 329 |
+
"Analiza un mensaje XML (por defecto el ticket de acceso)"
|
| 330 |
+
try:
|
| 331 |
+
if not xml or xml == 'XmlResponse':
|
| 332 |
+
xml = self.XmlResponse
|
| 333 |
+
elif xml == 'XmlRequest':
|
| 334 |
+
xml = self.XmlRequest
|
| 335 |
+
self.xml = SimpleXMLElement(xml)
|
| 336 |
+
return True
|
| 337 |
+
except Exception as e:
|
| 338 |
+
self.Excepcion = traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[0]
|
| 339 |
+
return False
|
| 340 |
+
|
| 341 |
+
def ObtenerTagXml(self, *tags):
|
| 342 |
+
"Busca en el Xml analizado y devuelve el tag solicitado"
|
| 343 |
+
# convierto el xml a un objeto
|
| 344 |
+
try:
|
| 345 |
+
if self.xml:
|
| 346 |
+
xml = self.xml
|
| 347 |
+
# por cada tag, lo busco segun su nombre o posiciΓ³n
|
| 348 |
+
for tag in tags:
|
| 349 |
+
xml = xml(tag) # atajo a getitem y getattr
|
| 350 |
+
# vuelvo a convertir a string el objeto xml encontrado
|
| 351 |
+
return str(xml)
|
| 352 |
+
except Exception as e:
|
| 353 |
+
self.Excepcion = traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[0]
|
| 354 |
+
|
| 355 |
+
def SetParametros(self, cuit, token, sign):
|
| 356 |
+
"Establece un parΓ‘metro general"
|
| 357 |
+
self.Token = token
|
| 358 |
+
self.Sign = sign
|
| 359 |
+
self.Cuit = cuit
|
| 360 |
+
return True
|
| 361 |
+
|
| 362 |
+
@inicializar_y_capturar_excepciones
|
| 363 |
+
def SetTicketAcceso(self, ta_string):
|
| 364 |
+
"Establecer el token y sign desde un ticket de acceso XML"
|
| 365 |
+
if ta_string:
|
| 366 |
+
ta = SimpleXMLElement(ta_string)
|
| 367 |
+
self.Token = str(ta.credentials.token)
|
| 368 |
+
self.Sign = str(ta.credentials.sign)
|
| 369 |
+
return True
|
| 370 |
+
else:
|
| 371 |
+
raise RuntimeError("Ticket de Acceso vacio!")
|
| 372 |
+
|
| 373 |
+
def SetParametro(self, clave, valor):
|
| 374 |
+
"Establece un parΓ‘metro de entrada (a usarse en llamada posterior)"
|
| 375 |
+
# ΓΊtil para parΓ‘metros de entrada (por ej. VFP9 no soporta mΓ‘s de 27)
|
| 376 |
+
self.params_in[str(clave)] = valor
|
| 377 |
+
return True
|
| 378 |
+
|
| 379 |
+
def GetParametro(self, clave, clave1=None, clave2=None, clave3=None, clave4=None):
|
| 380 |
+
"Devuelve un parΓ‘metro de salida (establecido por llamada anterior)"
|
| 381 |
+
# ΓΊtil para parΓ‘metros de salida (por ej. campos de TransaccionPlainWS)
|
| 382 |
+
valor = self.params_out.get(clave)
|
| 383 |
+
# busco datos "anidados" (listas / diccionarios)
|
| 384 |
+
for clave in (clave1, clave2, clave3, clave4):
|
| 385 |
+
if clave is not None and valor is not None:
|
| 386 |
+
if isinstance(clave1, str) and clave.isdigit():
|
| 387 |
+
clave = int(clave)
|
| 388 |
+
try:
|
| 389 |
+
valor = valor[clave]
|
| 390 |
+
except (KeyError, IndexError):
|
| 391 |
+
valor = None
|
| 392 |
+
if valor is not None:
|
| 393 |
+
if isinstance(valor, str):
|
| 394 |
+
return valor
|
| 395 |
+
else:
|
| 396 |
+
return str(valor)
|
| 397 |
+
else:
|
| 398 |
+
return ""
|
| 399 |
+
|
| 400 |
+
def LeerError(self):
|
| 401 |
+
"Recorro los errores devueltos y devuelvo el primero si existe"
|
| 402 |
+
|
| 403 |
+
if self.Errores:
|
| 404 |
+
# extraigo el primer item
|
| 405 |
+
er = self.Errores.pop(0)
|
| 406 |
+
return er
|
| 407 |
+
else:
|
| 408 |
+
return ""
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
class WebClient:
|
| 412 |
+
"Minimal webservice client to do POST request with multipart encoded FORM data"
|
| 413 |
+
|
| 414 |
+
def __init__(self, location, enctype="multipart/form-data", trace=False,
|
| 415 |
+
cacert=None, timeout=30):
|
| 416 |
+
kwargs = {}
|
| 417 |
+
if parse_version(httplib2.__version__) >= parse_version('0.3.0'):
|
| 418 |
+
kwargs['timeout'] = timeout
|
| 419 |
+
if parse_version(httplib2.__version__) >= parse_version('0.7.0'):
|
| 420 |
+
kwargs['disable_ssl_certificate_validation'] = cacert is None
|
| 421 |
+
kwargs['ca_certs'] = cacert
|
| 422 |
+
self.http = httplib2.Http(**kwargs)
|
| 423 |
+
self.trace = trace
|
| 424 |
+
self.location = location
|
| 425 |
+
self.enctype = enctype
|
| 426 |
+
self.cookies = None
|
| 427 |
+
self.method = "POST"
|
| 428 |
+
self.referer = None
|
| 429 |
+
|
| 430 |
+
def multipart_encode(self, vars):
|
| 431 |
+
"Enconde form data (vars dict)"
|
| 432 |
+
boundary = _make_boundary()
|
| 433 |
+
buf = StringIO()
|
| 434 |
+
for key, value in list(vars.items()):
|
| 435 |
+
if not isinstance(value, IOBase):
|
| 436 |
+
buf.write('--%s\r\n' % boundary)
|
| 437 |
+
buf.write('Content-Disposition: form-data; name="%s"' % key)
|
| 438 |
+
buf.write('\r\n\r\n' + value + '\r\n')
|
| 439 |
+
else:
|
| 440 |
+
fd = value
|
| 441 |
+
file_size = os.fstat(fd.fileno())[stat.ST_SIZE]
|
| 442 |
+
filename = os.path.basename(fd.name)
|
| 443 |
+
contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
| 444 |
+
buf.write('--%s\r\n' % boundary)
|
| 445 |
+
buf.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename))
|
| 446 |
+
buf.write('Content-Type: %s\r\n' % contenttype)
|
| 447 |
+
# buffer += 'Content-Length: %s\r\n' % file_size
|
| 448 |
+
fd.seek(0)
|
| 449 |
+
buf.write('\r\n' + fd.read() + '\r\n')
|
| 450 |
+
buf.write('--' + boundary + '--\r\n\r\n')
|
| 451 |
+
buf = buf.getvalue()
|
| 452 |
+
return boundary, buf
|
| 453 |
+
|
| 454 |
+
def __call__(self, *args, **vars):
|
| 455 |
+
"Perform a GET/POST request and return the response"
|
| 456 |
+
|
| 457 |
+
location = self.location
|
| 458 |
+
# if isinstance(location, str):
|
| 459 |
+
# location = location.encode("utf8")
|
| 460 |
+
# extend the base URI with additional components
|
| 461 |
+
if args:
|
| 462 |
+
location += "/".join(args)
|
| 463 |
+
if self.method == "GET":
|
| 464 |
+
location += "?%s" % urlencode(vars)
|
| 465 |
+
|
| 466 |
+
# prepare the request content suitable to be sent to the server:
|
| 467 |
+
if self.enctype == "multipart/form-data":
|
| 468 |
+
boundary, body = self.multipart_encode(vars)
|
| 469 |
+
content_type = '%s; boundary=%s' % (self.enctype, boundary)
|
| 470 |
+
elif self.enctype == "application/x-www-form-urlencoded":
|
| 471 |
+
body = urlencode(vars)
|
| 472 |
+
content_type = self.enctype
|
| 473 |
+
else:
|
| 474 |
+
body = None
|
| 475 |
+
|
| 476 |
+
# add headers according method, cookies, etc.:
|
| 477 |
+
headers = {}
|
| 478 |
+
if self.method == "POST":
|
| 479 |
+
headers.update({
|
| 480 |
+
'Content-type': content_type,
|
| 481 |
+
'Content-length': str(len(body)),
|
| 482 |
+
})
|
| 483 |
+
if self.cookies:
|
| 484 |
+
headers['Cookie'] = self.cookies.output(attrs=(), header="", sep=";")
|
| 485 |
+
if self.referer:
|
| 486 |
+
headers['Referer'] = self.referer
|
| 487 |
+
|
| 488 |
+
if self.trace:
|
| 489 |
+
print("-" * 80)
|
| 490 |
+
print("%s %s" % (self.method, location))
|
| 491 |
+
print('\n'.join(["%s: %s" % (k, v) for k, v in list(headers.items())]))
|
| 492 |
+
print("\n%s" % body)
|
| 493 |
+
|
| 494 |
+
# send the request to the server and store the result:
|
| 495 |
+
response, content = self.http.request(
|
| 496 |
+
location, self.method, body=body, headers=headers)
|
| 497 |
+
self.response = response
|
| 498 |
+
self.content = content
|
| 499 |
+
|
| 500 |
+
if self.trace:
|
| 501 |
+
print()
|
| 502 |
+
print('\n'.join(["%s: %s" % (k, v) for k, v in list(response.items())]))
|
| 503 |
+
print(content)
|
| 504 |
+
print("=" * 80)
|
| 505 |
+
|
| 506 |
+
# Parse and store the cookies (if any)
|
| 507 |
+
if "set-cookie" in self.response:
|
| 508 |
+
if not self.cookies:
|
| 509 |
+
self.cookies = SimpleCookie()
|
| 510 |
+
self.cookies.load(self.response["set-cookie"])
|
| 511 |
+
|
| 512 |
+
return content
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
class AttrDict(dict):
|
| 516 |
+
"Custom Dict to hold attributes and items"
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
class HTMLFormParser(HTMLParser):
|
| 520 |
+
"Convert HTML form into custom named-tuple dicts"
|
| 521 |
+
|
| 522 |
+
def __init__(self, *args, **kwargs):
|
| 523 |
+
HTMLParser.__init__(self, *args, **kwargs)
|
| 524 |
+
self.forms = {}
|
| 525 |
+
|
| 526 |
+
def handle_starttag(self, tag, attrs):
|
| 527 |
+
attrs = dict(attrs)
|
| 528 |
+
if 'name' in attrs:
|
| 529 |
+
name = attrs['name']
|
| 530 |
+
elif 'id' in attrs:
|
| 531 |
+
name = attrs['id']
|
| 532 |
+
else:
|
| 533 |
+
name = None
|
| 534 |
+
if tag == 'form':
|
| 535 |
+
form = AttrDict()
|
| 536 |
+
for k, v in list(attrs.items()):
|
| 537 |
+
setattr(form, "_%s" % k, v)
|
| 538 |
+
self.form = self.forms[name or len(self.forms)] = form
|
| 539 |
+
elif tag == 'input':
|
| 540 |
+
self.form[name or len(self.form)] = attrs.get('value')
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
# Funciones para manejo de archivos de texto de campos de ancho fijo:
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def leer(linea, formato, expandir_fechas=False):
|
| 547 |
+
"Analiza una linea de texto dado un formato, devuelve un diccionario"
|
| 548 |
+
dic = {}
|
| 549 |
+
comienzo = 1
|
| 550 |
+
for fmt in formato:
|
| 551 |
+
clave, longitud, tipo = fmt[0:3]
|
| 552 |
+
dec = (len(fmt) > 3 and isinstance(fmt[3], int)) and fmt[3] or 2
|
| 553 |
+
valor = linea[comienzo - 1:comienzo - 1 + longitud].strip()
|
| 554 |
+
try:
|
| 555 |
+
if chr(8) in valor or chr(127) in valor or chr(255) in valor:
|
| 556 |
+
valor = None # nulo
|
| 557 |
+
elif tipo == N:
|
| 558 |
+
if valor:
|
| 559 |
+
valor = int(valor)
|
| 560 |
+
else:
|
| 561 |
+
valor = 0
|
| 562 |
+
elif tipo == I:
|
| 563 |
+
if valor:
|
| 564 |
+
try:
|
| 565 |
+
if '.' in valor:
|
| 566 |
+
valor = float(valor)
|
| 567 |
+
else:
|
| 568 |
+
valor = valor.strip(" ")
|
| 569 |
+
if valor[0] == "-":
|
| 570 |
+
sign = -1
|
| 571 |
+
valor = valor[1:]
|
| 572 |
+
else:
|
| 573 |
+
sign = +1
|
| 574 |
+
valor = sign * float(("%%s.%%0%sd" % dec) % (int(valor[:-dec] or '0'), int(valor[-dec:] or '0')))
|
| 575 |
+
except ValueError:
|
| 576 |
+
raise ValueError("Campo invalido: %s = '%s'" % (clave, valor))
|
| 577 |
+
else:
|
| 578 |
+
valor = 0.00
|
| 579 |
+
elif expandir_fechas and clave.lower().startswith("fec") and longitud <= 8:
|
| 580 |
+
if valor:
|
| 581 |
+
valor = "%s-%s-%s" % (valor[0:4], valor[4:6], valor[6:8])
|
| 582 |
+
else:
|
| 583 |
+
valor = None
|
| 584 |
+
else:
|
| 585 |
+
valor = valor.decode("ascii", "ignore")
|
| 586 |
+
if not valor and clave in dic and len(linea) <= comienzo:
|
| 587 |
+
pass # ignorar - compatibilidad hacia atrΓ‘s (cambios tamaΓ±o)
|
| 588 |
+
else:
|
| 589 |
+
dic[clave] = valor
|
| 590 |
+
comienzo += longitud
|
| 591 |
+
except Exception as e:
|
| 592 |
+
raise ValueError("Error al leer campo %s pos %s val '%s': %s" % (
|
| 593 |
+
clave, comienzo, valor, str(e)))
|
| 594 |
+
return dic
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
def escribir(dic, formato, contraer_fechas=False):
|
| 598 |
+
"Genera una cadena dado un formato y un diccionario de claves/valores"
|
| 599 |
+
linea = " " * sum([fmt[1] for fmt in formato])
|
| 600 |
+
comienzo = 1
|
| 601 |
+
for fmt in formato:
|
| 602 |
+
clave, longitud, tipo = fmt[0:3]
|
| 603 |
+
try:
|
| 604 |
+
dec = (len(fmt) > 3 and isinstance(fmt[3], int)) and fmt[3] or 2
|
| 605 |
+
if clave.capitalize() in dic:
|
| 606 |
+
clave = clave.capitalize()
|
| 607 |
+
s = dic.get(clave, "")
|
| 608 |
+
if isinstance(s, str):
|
| 609 |
+
s = s.encode("latin1")
|
| 610 |
+
if s is None:
|
| 611 |
+
valor = ""
|
| 612 |
+
else:
|
| 613 |
+
valor = str(s)
|
| 614 |
+
# reemplazo saltos de linea por tabulaci{on vertical
|
| 615 |
+
valor = valor.replace("\n\r", "\v").replace("\n", "\v").replace("\r", "\v")
|
| 616 |
+
if tipo == N and valor and valor != "NULL":
|
| 617 |
+
valor = ("%%0%dd" % longitud) % int(valor)
|
| 618 |
+
elif tipo == I and valor:
|
| 619 |
+
valor = ("%%0%d.%df" % (longitud + 1, dec) % float(valor)).replace(".", "")
|
| 620 |
+
elif contraer_fechas and clave.lower().startswith("fec") and longitud <= 8 and valor:
|
| 621 |
+
valor = valor.replace("-", "")
|
| 622 |
+
else:
|
| 623 |
+
valor = ("%%-0%ds" % longitud) % valor
|
| 624 |
+
linea = linea[:comienzo - 1] + valor + linea[comienzo - 1 + longitud:]
|
| 625 |
+
comienzo += longitud
|
| 626 |
+
except Exception as e:
|
| 627 |
+
warnings.warn("Error al escribir campo %s pos %s val '%s': %s" % (
|
| 628 |
+
clave, comienzo, valor, str(e)))
|
| 629 |
+
return linea + "\n"
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
# Tipos de datos (cΓ³digo RG1361)
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
N = 'Numerico' # 2
|
| 636 |
+
A = 'Alfanumerico' # 3
|
| 637 |
+
I = 'Importe' # 4
|
| 638 |
+
C = A # 1 (caracter alfabetico)
|
| 639 |
+
B = A # 9 (blanco)
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
# Funciones para manejo de tablas en DBF
|
| 643 |
+
|
| 644 |
+
|
| 645 |
+
def guardar_dbf(formatos, agrega=False, conf_dbf=None):
|
| 646 |
+
import dbf
|
| 647 |
+
if DEBUG:
|
| 648 |
+
print("Creando DBF...")
|
| 649 |
+
|
| 650 |
+
tablas = {}
|
| 651 |
+
for nombre, formato, l in formatos:
|
| 652 |
+
campos = []
|
| 653 |
+
claves = []
|
| 654 |
+
filename = conf_dbf.get(nombre.lower(), "%s.dbf" % nombre[:8])
|
| 655 |
+
if DEBUG:
|
| 656 |
+
print("=== tabla %s (%s) ===" % (nombre, filename))
|
| 657 |
+
for fmt in formato:
|
| 658 |
+
clave, longitud, tipo = fmt[0:3]
|
| 659 |
+
dec = len(fmt) > 3 and fmt[3] or (tipo == 'I' and '2' or '')
|
| 660 |
+
if longitud > 250:
|
| 661 |
+
tipo = "M" # memo!
|
| 662 |
+
elif tipo == A:
|
| 663 |
+
tipo = "C(%s)" % longitud
|
| 664 |
+
elif tipo == N:
|
| 665 |
+
if longitud >= 18:
|
| 666 |
+
longitud = 17
|
| 667 |
+
tipo = "N(%s,0)" % longitud
|
| 668 |
+
elif tipo == I:
|
| 669 |
+
if not dec:
|
| 670 |
+
dec = 0
|
| 671 |
+
else:
|
| 672 |
+
dec = int(dec)
|
| 673 |
+
if longitud >= 18:
|
| 674 |
+
longitud = 17
|
| 675 |
+
if longitud - 2 <= dec:
|
| 676 |
+
longitud += longitud - dec + 1 # ajusto long. decimales
|
| 677 |
+
tipo = "N(%s,%s)" % (longitud, dec)
|
| 678 |
+
clave_dbf = dar_nombre_campo_dbf(clave, claves)
|
| 679 |
+
campo = "%s %s" % (clave_dbf, tipo)
|
| 680 |
+
if DEBUG:
|
| 681 |
+
print(" * %s : %s" % (campo, clave))
|
| 682 |
+
campos.append(campo)
|
| 683 |
+
claves.append(clave_dbf)
|
| 684 |
+
if DEBUG:
|
| 685 |
+
print("leyendo tabla", nombre, filename)
|
| 686 |
+
if agrega:
|
| 687 |
+
tabla = dbf.Table(filename, campos)
|
| 688 |
+
else:
|
| 689 |
+
tabla = dbf.Table(filename)
|
| 690 |
+
|
| 691 |
+
for d in l:
|
| 692 |
+
# si no es un diccionario, ignorar ya que seguramente va en otra
|
| 693 |
+
# tabla (por ej. retenciones tiene su propio formato)
|
| 694 |
+
if isinstance(d, str):
|
| 695 |
+
continue
|
| 696 |
+
r = {}
|
| 697 |
+
claves = []
|
| 698 |
+
for fmt in formato:
|
| 699 |
+
clave, longitud, tipo = fmt[0:3]
|
| 700 |
+
if agrega or clave in d:
|
| 701 |
+
v = d.get(clave, None)
|
| 702 |
+
if DEBUG:
|
| 703 |
+
print(clave, v, tipo)
|
| 704 |
+
if v is None and tipo == A:
|
| 705 |
+
v = ''
|
| 706 |
+
if (v is None or v == '') and tipo in (I, N):
|
| 707 |
+
v = 0
|
| 708 |
+
if tipo == A:
|
| 709 |
+
if isinstance(v, str):
|
| 710 |
+
v = v.encode("ascii", "replace")
|
| 711 |
+
if isinstance(v, str):
|
| 712 |
+
v = v.decode("ascii", "replace").encode("ascii", "replace")
|
| 713 |
+
if not isinstance(v, str):
|
| 714 |
+
v = str(v)
|
| 715 |
+
if len(v) > longitud:
|
| 716 |
+
v = v[:longitud] # recorto el string para que quepa
|
| 717 |
+
clave_dbf = dar_nombre_campo_dbf(clave, claves)
|
| 718 |
+
claves.append(clave_dbf)
|
| 719 |
+
r[clave_dbf] = v
|
| 720 |
+
# agregar si lo solicitaron o si la tabla no tiene registros:
|
| 721 |
+
if agrega or not tabla:
|
| 722 |
+
if DEBUG:
|
| 723 |
+
print("Agregando !!!", r)
|
| 724 |
+
registro = tabla.append(r)
|
| 725 |
+
else:
|
| 726 |
+
if DEBUG:
|
| 727 |
+
print("Actualizando ", r)
|
| 728 |
+
reg = tabla.current()
|
| 729 |
+
for k, v in list(reg.scatter_fields().items()):
|
| 730 |
+
if k not in r:
|
| 731 |
+
r[k] = v
|
| 732 |
+
if DEBUG:
|
| 733 |
+
print("Actualizando ", r)
|
| 734 |
+
reg.write_record(**r)
|
| 735 |
+
# mover de registro para no actualizar siempre el primero:
|
| 736 |
+
if not tabla.eof() and len(l) > 1:
|
| 737 |
+
if DEBUG:
|
| 738 |
+
print("Moviendo al prΓ³ximo registro ", tabla.record_number)
|
| 739 |
+
next(tabla)
|
| 740 |
+
tabla.close()
|
| 741 |
+
|
| 742 |
+
|
| 743 |
+
def leer_dbf(formatos, conf_dbf):
|
| 744 |
+
import dbf
|
| 745 |
+
if DEBUG:
|
| 746 |
+
print("Leyendo DBF...")
|
| 747 |
+
|
| 748 |
+
for nombre, formato, ld in formatos:
|
| 749 |
+
filename = conf_dbf.get(nombre.lower(), "%s.dbf" % nombre[:8])
|
| 750 |
+
if DEBUG:
|
| 751 |
+
print("leyendo tabla", nombre, filename)
|
| 752 |
+
if not os.path.exists(filename):
|
| 753 |
+
continue
|
| 754 |
+
tabla = dbf.Table(filename)
|
| 755 |
+
for reg in tabla:
|
| 756 |
+
r = {}
|
| 757 |
+
d = reg.scatter_fields()
|
| 758 |
+
claves = []
|
| 759 |
+
for fmt in formato:
|
| 760 |
+
clave, longitud, tipo = fmt[0:3]
|
| 761 |
+
#import pdb; pdb.set_trace()
|
| 762 |
+
clave_dbf = dar_nombre_campo_dbf(clave, claves)
|
| 763 |
+
claves.append(clave_dbf)
|
| 764 |
+
v = d.get(clave_dbf)
|
| 765 |
+
r[clave] = v
|
| 766 |
+
if isinstance(ld, dict):
|
| 767 |
+
ld.update(r)
|
| 768 |
+
else:
|
| 769 |
+
ld.append(r)
|
| 770 |
+
|
| 771 |
+
|
| 772 |
+
def dar_nombre_campo_dbf(clave, claves):
|
| 773 |
+
"Reducir nombre de campo a 10 caracteres, sin espacios ni _, sin repetir"
|
| 774 |
+
# achico el nombre del campo para que quepa en la tabla:
|
| 775 |
+
nombre = clave.replace("_", "")[:10]
|
| 776 |
+
# si el campo esta repetido, le agrego un nΓΊmero
|
| 777 |
+
i = 0
|
| 778 |
+
while nombre in claves:
|
| 779 |
+
i += 1
|
| 780 |
+
nombre = nombre[:9] + str(i)
|
| 781 |
+
return nombre.lower()
|
| 782 |
+
|
| 783 |
+
|
| 784 |
+
def verifica(ver_list, res_dict, difs):
|
| 785 |
+
"Verificar que dos diccionarios sean iguales, actualiza lista diferencias"
|
| 786 |
+
for k, v in list(ver_list.items()):
|
| 787 |
+
# normalizo a float para poder comparar numericamente:
|
| 788 |
+
if isinstance(v, (Decimal, int)):
|
| 789 |
+
v = float(v)
|
| 790 |
+
if isinstance(res_dict.get(k), (Decimal, int)):
|
| 791 |
+
res_dict[k] = float(res_dict[k])
|
| 792 |
+
if isinstance(v, list):
|
| 793 |
+
# verifico que ambas listas tengan la misma cantidad de elementos:
|
| 794 |
+
if v and not k in res_dict and v:
|
| 795 |
+
difs.append("falta tag %s: %s %s" % (k, repr(v), repr(res_dict.get(k))))
|
| 796 |
+
elif len(res_dict.get(k, [])) != len(v or []):
|
| 797 |
+
difs.append("tag %s len !=: %s %s" % (k, repr(v), repr(res_dict.get(k))))
|
| 798 |
+
else:
|
| 799 |
+
# ordeno las listas para poder compararlas si vienen mezcladas
|
| 800 |
+
rl = sorted(res_dict.get(k, []))
|
| 801 |
+
# comparo los elementos uno a uno:
|
| 802 |
+
for i, vl in enumerate(sorted(v)):
|
| 803 |
+
verifica(vl, rl[i], difs)
|
| 804 |
+
elif isinstance(v, dict):
|
| 805 |
+
# comparo recursivamente los elementos:
|
| 806 |
+
verifica(v, res_dict.get(k, {}), difs)
|
| 807 |
+
elif res_dict.get(k) is None or v is None:
|
| 808 |
+
# alguno de los dos es nulo, verifico si ambos lo son o faltan
|
| 809 |
+
if v == "":
|
| 810 |
+
v = None
|
| 811 |
+
r = res_dict.get(k)
|
| 812 |
+
if r == "":
|
| 813 |
+
r = None
|
| 814 |
+
if not (r is None and v is None):
|
| 815 |
+
difs.append("%s: nil %s!=%s" % (k, repr(v), repr(r)))
|
| 816 |
+
elif isinstance(res_dict.get(k), type(v)):
|
| 817 |
+
# tipos iguales, los comparo directamente
|
| 818 |
+
if res_dict.get(k) != v:
|
| 819 |
+
difs.append("%s: %s!=%s" % (k, repr(v), repr(res_dict.get(k))))
|
| 820 |
+
elif isinstance(v, float) or isinstance(res_dict.get(k), float):
|
| 821 |
+
# comparar numericamente
|
| 822 |
+
if float(res_dict.get(k)) != float(v):
|
| 823 |
+
difs.append("%s: %s!=%s" % (k, repr(v), repr(res_dict.get(k))))
|
| 824 |
+
elif str(res_dict.get(k)) != str(v):
|
| 825 |
+
# tipos diferentes, comparo la representaciΓ³n
|
| 826 |
+
difs.append("%s: str %s!=%s" % (k, repr(v), repr(res_dict.get(k))))
|
| 827 |
+
else:
|
| 828 |
+
pass
|
| 829 |
+
# print "%s: %s==%s" % (k, repr(v), repr(res_dict[k]))
|
| 830 |
+
|
| 831 |
+
|
| 832 |
+
def safe_console():
|
| 833 |
+
if False and sys.stdout.encoding is None:
|
| 834 |
+
class SafeWriter:
|
| 835 |
+
def __init__(self, target):
|
| 836 |
+
self.target = target
|
| 837 |
+
self.encoding = 'utf-8'
|
| 838 |
+
self.errors = 'replace'
|
| 839 |
+
self.encode_to = 'latin-1'
|
| 840 |
+
|
| 841 |
+
def write(self, s):
|
| 842 |
+
self.target.write(self.intercept(s))
|
| 843 |
+
|
| 844 |
+
def flush(self):
|
| 845 |
+
self.target.flush()
|
| 846 |
+
|
| 847 |
+
def intercept(self, s):
|
| 848 |
+
if not isinstance(s, str):
|
| 849 |
+
s = s.decode(self.encode_to, self.errors)
|
| 850 |
+
return s.encode(self.encoding, self.errors)
|
| 851 |
+
|
| 852 |
+
sys.stdout = SafeWriter(sys.stdout)
|
| 853 |
+
#sys.stderr = SafeWriter(sys.stderr)
|
| 854 |
+
print("Encodign in %s" % locale.getpreferredencoding())
|
| 855 |
+
|
| 856 |
+
|
| 857 |
+
def norm(x, encoding="latin1"):
|
| 858 |
+
"Convertir acentos codificados en ISO 8859-1 u otro, a ASCII regular"
|
| 859 |
+
if not isinstance(x, str):
|
| 860 |
+
x = str(x)
|
| 861 |
+
elif isinstance(x, str):
|
| 862 |
+
x = x.decode(encoding, 'ignore')
|
| 863 |
+
return unicodedata.normalize('NFKD', x).encode('ASCII', 'ignore')
|
| 864 |
+
|
| 865 |
+
|
| 866 |
+
def date(fmt=None, timestamp=None):
|
| 867 |
+
"Manejo de fechas (simil PHP)"
|
| 868 |
+
if fmt == 'U': # return timestamp
|
| 869 |
+
t = datetime.datetime.now()
|
| 870 |
+
return int(time.mktime(t.timetuple()))
|
| 871 |
+
if fmt == 'c': # return isoformat
|
| 872 |
+
d = datetime.datetime.fromtimestamp(timestamp)
|
| 873 |
+
return d.isoformat()
|
| 874 |
+
if fmt == 'Ymd':
|
| 875 |
+
d = datetime.datetime.now()
|
| 876 |
+
return d.strftime("%Y%m%d")
|
| 877 |
+
|
| 878 |
+
|
| 879 |
+
def get_install_dir():
|
| 880 |
+
if not hasattr(sys, "frozen"):
|
| 881 |
+
basepath = __file__
|
| 882 |
+
elif sys.frozen == 'dll':
|
| 883 |
+
import win32api
|
| 884 |
+
basepath = win32api.GetModuleFileName(sys.frozendllhandle)
|
| 885 |
+
else:
|
| 886 |
+
basepath = sys.executable
|
| 887 |
+
|
| 888 |
+
if hasattr(sys, "frozen"):
|
| 889 |
+
# we are running as py2exe-packed executable
|
| 890 |
+
try:
|
| 891 |
+
import pythoncom
|
| 892 |
+
pythoncom.frozen = 1
|
| 893 |
+
except ModuleNotFoundError:
|
| 894 |
+
pass
|
| 895 |
+
sys.argv[0] = sys.executable
|
| 896 |
+
|
| 897 |
+
return os.path.dirname(os.path.abspath(basepath))
|
| 898 |
+
|
| 899 |
+
|
| 900 |
+
def abrir_conf(config_file, debug=False):
|
| 901 |
+
"Abrir el archivo de configuraciΓ³n (usar primer parΓ‘metro como ruta)"
|
| 902 |
+
# en principio, usar el nombre de archivo predeterminado
|
| 903 |
+
# si se pasa el archivo de configuraciΓ³n por parΓ‘metro, confirmar que exista
|
| 904 |
+
# y descartar que sea una opciΓ³n
|
| 905 |
+
if len(sys.argv) > 1:
|
| 906 |
+
if os.path.splitext(sys.argv[1])[1].lower() == ".ini":
|
| 907 |
+
config_file = sys.argv.pop(1)
|
| 908 |
+
if not os.path.exists(config_file) or not os.path.isfile(config_file):
|
| 909 |
+
warnings.warn("Archivo de configuracion %s invalido" % config_file)
|
| 910 |
+
|
| 911 |
+
if debug:
|
| 912 |
+
print("CONFIG_FILE:", config_file)
|
| 913 |
+
|
| 914 |
+
config = SafeConfigParser()
|
| 915 |
+
config.read(config_file, encoding="latin1")
|
| 916 |
+
|
| 917 |
+
return config
|
| 918 |
+
|
| 919 |
+
|
| 920 |
+
def json_serializer(obj):
|
| 921 |
+
if isinstance(obj, (datetime.datetime, datetime.date)):
|
| 922 |
+
return obj.isoformat()
|
| 923 |
+
else:
|
| 924 |
+
return str(obj)
|
| 925 |
+
|
| 926 |
+
|
| 927 |
+
if __name__ == "__main__":
|
| 928 |
+
print(get_install_dir())
|
| 929 |
+
try:
|
| 930 |
+
1 / 0
|
| 931 |
+
except BaseException:
|
| 932 |
+
ex = exception_info()
|
| 933 |
+
print(ex)
|
| 934 |
+
assert ex['name'] == "ZeroDivisionError"
|
| 935 |
+
assert ex['lineno'] == 73
|
| 936 |
+
assert ex['tb']
|
app/pyafipws/venv.bat
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
|
| 3 |
+
rem CreaciοΏ½n del entorno virtual (opcional) para el proyecto PyAfipWs
|
| 4 |
+
rem 2015 (c) Mariano Reingart <reingart@gmail.com> - Licencia: GPLv3+
|
| 5 |
+
|
| 6 |
+
rem Nota: Es recomendable ejecutar este programa como Administrador
|
| 7 |
+
rem Ver https://code.google.com/p/pyafipws/wiki/InstalacionCodigoFuente
|
| 8 |
+
|
| 9 |
+
pip 1> NUL 2> NUL
|
| 10 |
+
if %ERRORLEVEL%==9009 (
|
| 11 |
+
echo Python 2.7.9 / PIP no puede ser ejecutado
|
| 12 |
+
echo Por favor instale: https://www.python.org/ftp/python/2.7.9/python-2.7.9.msi
|
| 13 |
+
echo Asegurese que el PATH contenga a C:\Python27 y C:\Python27\scripts
|
| 14 |
+
pause
|
| 15 |
+
start https://www.python.org/ftp/python/2.7.9/python-2.7.9.msi
|
| 16 |
+
exit 1
|
| 17 |
+
)
|
| 18 |
+
echo *** Instalar utilidades de instalaciοΏ½n / entorno virtual:
|
| 19 |
+
|
| 20 |
+
rem pip install --upgrade pip
|
| 21 |
+
pip install --upgrade wheel
|
| 22 |
+
pip install --upgrade virtualenv
|
| 23 |
+
|
| 24 |
+
echo *** Crear y activar el entorno virtual venv (en el directorio actual):
|
| 25 |
+
|
| 26 |
+
virtualenv venv
|
| 27 |
+
venv\Scripts\activate
|
| 28 |
+
|
| 29 |
+
echo *** Listo!, para salir del entorno ejecute deactivate
|
app/pyafipws/wdigdepfiel.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
import time
|
| 14 |
+
import sys
|
| 15 |
+
import os
|
| 16 |
+
from .utils import date, SimpleXMLElement, SoapClient, SoapFault
|
| 17 |
+
"""MοΏ½dulo para interfaz Depositario Fiel web service wDigDepFiel de AFIP
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 21 |
+
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
|
| 22 |
+
__license__ = "LGPL 3.0"
|
| 23 |
+
__version__ = "1.01"
|
| 24 |
+
|
| 25 |
+
LICENCIA = """
|
| 26 |
+
wdigdepfiel.py: Interfaz para Digitalizacion Depositario Fiel AFIP
|
| 27 |
+
Copyright (C) 2010 Mariano Reingart reingart@gmail.com
|
| 28 |
+
|
| 29 |
+
Este progarma es software libre, se entrega ABSOLUTAMENTE SIN GARANTIA
|
| 30 |
+
y es bienvenido a redistribuirlo bajo la licencia GPLv3.
|
| 31 |
+
|
| 32 |
+
Para informaciοΏ½n adicional sobre garantοΏ½a, soporte tοΏ½cnico comercial
|
| 33 |
+
e incorporaciοΏ½n/distribuciοΏ½n en programas propietarios ver PyAfipWs:
|
| 34 |
+
http://www.sistemasagiles.com.ar/trac/wiki/PyAfipWs
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
WSDDFURL = "https://testdia.afip.gov.ar/Dia/Ws/wDigDepFiel/wDigDepFiel.asmx"
|
| 39 |
+
SOAP_ACTION = 'ar.gov.afip.dia.serviciosWeb.wDigDepFiel/'
|
| 40 |
+
SOAP_NS = 'ar.gov.afip.dia.serviciosWeb.wDigDepFiel'
|
| 41 |
+
|
| 42 |
+
DEBUG = True
|
| 43 |
+
XML = False
|
| 44 |
+
HOMO = True
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def dummy(client):
|
| 48 |
+
"Metodo dummy para verificacion de funcionamiento"
|
| 49 |
+
response = client.Dummy()
|
| 50 |
+
result = response.DummyResult
|
| 51 |
+
appserver = dbserver = authserver = None
|
| 52 |
+
try:
|
| 53 |
+
appserver = str(result.appserver)
|
| 54 |
+
dbserver = str(result.dbserver)
|
| 55 |
+
authserver = str(result.authserver)
|
| 56 |
+
except (RuntimeError, IndexError, AttributeError) as e:
|
| 57 |
+
pass
|
| 58 |
+
return {'appserver': appserver,
|
| 59 |
+
'dbserver': dbserver,
|
| 60 |
+
'authserver': authserver}
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def aviso_recep_acept(client, token, sign, cuit, tipo_agente, rol,
|
| 64 |
+
nro_legajo, cuit_declarante, cuit_psad, cuit_ie,
|
| 65 |
+
codigo, fecha_hora_acept, ticket):
|
| 66 |
+
"Aviso de recepcion y aceptacion."
|
| 67 |
+
response = client.AvisoRecepAcept(
|
| 68 |
+
autentica=dict(Cuit=cuit, Token=token, Sign=sign, TipoAgente=tipo_agente, Rol=rol),
|
| 69 |
+
nroLegajo=nro_legajo,
|
| 70 |
+
cuitDeclarante=cuit_declarante,
|
| 71 |
+
cuitPSAD=cuit_psad,
|
| 72 |
+
cuitIE=cuit_ie,
|
| 73 |
+
codigo=codigo,
|
| 74 |
+
fechaHoraAcept=fecha_hora_acept,
|
| 75 |
+
ticket=ticket,
|
| 76 |
+
)
|
| 77 |
+
result = response.AvisoRecepAceptResult
|
| 78 |
+
return str(result.codError), str(result.descError)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def aviso_digit(client, token, sign, cuit, tipo_agente, rol,
|
| 82 |
+
nro_legajo, cuit_declarante, cuit_psad, cuit_ie, cuit_ata,
|
| 83 |
+
codigo, url, familias, ticket, hashing, cantidad_total):
|
| 84 |
+
"Aviso de digitalizacion."
|
| 85 |
+
response = client.AvisoDigit(
|
| 86 |
+
autentica=dict(Cuit=cuit, Token=token, Sign=sign, TipoAgente=tipo_agente, Rol=rol),
|
| 87 |
+
nroLegajo=nro_legajo,
|
| 88 |
+
cuitDeclarante=cuit_declarante,
|
| 89 |
+
cuitPSAD=cuit_psad,
|
| 90 |
+
cuitIE=cuit_ie,
|
| 91 |
+
cuitATA=cuit_ata,
|
| 92 |
+
codigo=codigo,
|
| 93 |
+
url=url,
|
| 94 |
+
familias=familias,
|
| 95 |
+
ticket=ticket,
|
| 96 |
+
hashing=hashing,
|
| 97 |
+
cantidadTotal=cantidad_total,
|
| 98 |
+
)
|
| 99 |
+
result = response.AvisoDigitResult
|
| 100 |
+
return str(result.codError), str(result.descError)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
if __name__ == '__main__':
|
| 104 |
+
if '--ayuda' in sys.argv:
|
| 105 |
+
print(LICENCIA)
|
| 106 |
+
print(AYUDA)
|
| 107 |
+
sys.exit(0)
|
| 108 |
+
|
| 109 |
+
import csv
|
| 110 |
+
import traceback
|
| 111 |
+
import datetime
|
| 112 |
+
|
| 113 |
+
from . import wsaa
|
| 114 |
+
|
| 115 |
+
try:
|
| 116 |
+
|
| 117 |
+
if "--version" in sys.argv:
|
| 118 |
+
print("VersiοΏ½n: ", __version__)
|
| 119 |
+
|
| 120 |
+
CERT = 'reingart.crt'
|
| 121 |
+
PRIVATEKEY = 'reingart.key'
|
| 122 |
+
# obteniendo el TA
|
| 123 |
+
TA = "wsddf-ta.xml"
|
| 124 |
+
if not os.path.exists(TA) or os.path.getmtime(TA) + (60 * 60 * 5) < time.time():
|
| 125 |
+
tra = wsaa.create_tra(service="wDigDepFiel")
|
| 126 |
+
cms = wsaa.sign_tra(tra, CERT, PRIVATEKEY)
|
| 127 |
+
ta_string = wsaa.call_wsaa(cms)
|
| 128 |
+
open(TA, "w").write(ta_string)
|
| 129 |
+
ta_string = open(TA).read()
|
| 130 |
+
ta = SimpleXMLElement(ta_string)
|
| 131 |
+
token = str(ta.credentials.token)
|
| 132 |
+
sign = str(ta.credentials.sign)
|
| 133 |
+
# fin TA
|
| 134 |
+
|
| 135 |
+
# cliente soap del web service
|
| 136 |
+
client = SoapClient(WSDDFURL,
|
| 137 |
+
action=SOAP_ACTION,
|
| 138 |
+
namespace=SOAP_NS, exceptions=True,
|
| 139 |
+
trace=True, ns='ar', soap_ns='soap')
|
| 140 |
+
|
| 141 |
+
if '--dummy' in sys.argv:
|
| 142 |
+
ret = dummy(client)
|
| 143 |
+
print('\n'.join(["%s: %s" % it for it in list(ret.items())]))
|
| 144 |
+
|
| 145 |
+
# ejemplos aviso recep acept (prueba):
|
| 146 |
+
|
| 147 |
+
cuit = 20267565393
|
| 148 |
+
tipo_agente = 'DESP' # 'DESP'
|
| 149 |
+
rol = 'EXTE'
|
| 150 |
+
nro_legajo = '0' * 16 # '1234567890123456'
|
| 151 |
+
cuit_declarante = cuit_psad = cuit_ie = cuit
|
| 152 |
+
codigo = '000' # carpeta completa, '0001' carpeta adicional
|
| 153 |
+
fecha_hora_acept = datetime.datetime.now().isoformat()
|
| 154 |
+
ticket = '1234'
|
| 155 |
+
r = aviso_recep_acept(client, token, sign, cuit, tipo_agente, rol,
|
| 156 |
+
nro_legajo, cuit_declarante, cuit_psad, cuit_ie,
|
| 157 |
+
codigo, fecha_hora_acept, ticket)
|
| 158 |
+
print(r)
|
| 159 |
+
|
| 160 |
+
# ejemplos aviso digit (prueba):
|
| 161 |
+
|
| 162 |
+
cuit = 20267565393
|
| 163 |
+
tipo_agente = 'DESP' # 'DESP'
|
| 164 |
+
rol = 'EXTE'
|
| 165 |
+
nro_legajo = '0' * 16 # '1234567890123456'
|
| 166 |
+
cuit_declarante = cuit_psad = cuit_ie = cuit_ata = cuit
|
| 167 |
+
codigo = '000' # carpeta completa, '0001' carpeta adicional
|
| 168 |
+
ticket = '1234'
|
| 169 |
+
url = 'http://www.example.com'
|
| 170 |
+
hashing = 'db1491eda47d78532cdfca19c62875aade941dc2'
|
| 171 |
+
familias = [{'Familia': {'codigo': '02', 'cantidad': 1}}, {'Familia': {'codigo': '03', 'cantidad': 3}}, ]
|
| 172 |
+
cantidad_total = 4
|
| 173 |
+
r = aviso_digit(client, token, sign, cuit, tipo_agente, rol,
|
| 174 |
+
nro_legajo, cuit_declarante, cuit_psad, cuit_ie, cuit_ata,
|
| 175 |
+
codigo, url, familias, ticket, hashing, cantidad_total)
|
| 176 |
+
print(r)
|
| 177 |
+
print("hecho.")
|
| 178 |
+
|
| 179 |
+
except SoapFault as e:
|
| 180 |
+
print("Falla SOAP:", e.faultcode, e.faultstring.encode("ascii", "ignore"))
|
| 181 |
+
sys.exit(3)
|
| 182 |
+
except Exception as e:
|
| 183 |
+
print(str(e).encode("ascii", "ignore"))
|
| 184 |
+
if DEBUG:
|
| 185 |
+
raise
|
| 186 |
+
sys.exit(5)
|
app/pyafipws/ws_sr_padron.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"""MΓ³dulo para acceder a los datos de un contribuyente registrado en el PadrΓ³n
|
| 14 |
+
de AFIP (WS-SR-PADRON de AFIP). Consulta a PadrΓ³n Alcance 4 version 1.1
|
| 15 |
+
Consulta de PadrΓ³n Constancia InscripciΓ³n Alcance 5 version 2.0
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 19 |
+
__copyright__ = "Copyright (C) 2017 Mariano Reingart"
|
| 20 |
+
__license__ = "GPL 3.0"
|
| 21 |
+
__version__ = "1.03e"
|
| 22 |
+
|
| 23 |
+
import csv
|
| 24 |
+
import datetime
|
| 25 |
+
import decimal
|
| 26 |
+
import json
|
| 27 |
+
import os
|
| 28 |
+
import sys
|
| 29 |
+
|
| 30 |
+
from .utils import inicializar_y_capturar_excepciones, BaseWS, get_install_dir, json_serializer, abrir_conf, norm, SoapFault
|
| 31 |
+
from configparser import ConfigParser as SafeConfigParser
|
| 32 |
+
from .padron import TIPO_CLAVE, PROVINCIAS
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
HOMO = False
|
| 36 |
+
LANZAR_EXCEPCIONES = True
|
| 37 |
+
WSDL = "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA4?wsdl"
|
| 38 |
+
CONFIG_FILE = "rece.ini"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class WSSrPadronA4(BaseWS):
|
| 42 |
+
"Interfaz para el WebService de Consulta PadrΓ³n Contribuyentes Alcance 4"
|
| 43 |
+
_public_methods_ = ['Consultar',
|
| 44 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 45 |
+
'SetParametros', 'SetTicketAcceso', 'GetParametro',
|
| 46 |
+
'Dummy', 'Conectar', 'DebugLog', 'SetTicketAcceso']
|
| 47 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 48 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 49 |
+
'XmlRequest', 'XmlResponse', 'Version', 'InstallDir',
|
| 50 |
+
'LanzarExcepciones', 'Excepcion', 'Traceback',
|
| 51 |
+
'Persona', 'data',
|
| 52 |
+
'denominacion', 'imp_ganancias', 'imp_iva',
|
| 53 |
+
'monotributo', 'integrante_soc', 'empleador',
|
| 54 |
+
'actividad_monotributo', 'cat_iva', 'domicilios',
|
| 55 |
+
'tipo_doc', 'nro_doc',
|
| 56 |
+
'tipo_persona', 'estado', 'impuestos', 'actividades',
|
| 57 |
+
'direccion', 'localidad', 'provincia', 'cod_postal',
|
| 58 |
+
]
|
| 59 |
+
|
| 60 |
+
_reg_progid_ = "WSSrPadronA4"
|
| 61 |
+
_reg_clsid_ = "{C2270008-4324-46F6-A2D3-60836EE63BD7}"
|
| 62 |
+
|
| 63 |
+
# Variables globales para BaseWS:
|
| 64 |
+
HOMO = HOMO
|
| 65 |
+
WSDL = WSDL
|
| 66 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciΓ³n' or '')
|
| 67 |
+
Reprocesar = True # recuperar automaticamente CAE emitidos
|
| 68 |
+
LanzarExcepciones = LANZAR_EXCEPCIONES
|
| 69 |
+
factura = None
|
| 70 |
+
|
| 71 |
+
def inicializar(self):
|
| 72 |
+
BaseWS.inicializar(self)
|
| 73 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 74 |
+
self.Persona = ''
|
| 75 |
+
self.Reproceso = '' # no implementado
|
| 76 |
+
self.cuit = self.dni = 0
|
| 77 |
+
self.tipo_persona = "" # FISICA o JURIDICA
|
| 78 |
+
self.tipo_doc = self.nro_doc = 0
|
| 79 |
+
self.estado = "" # ACTIVO
|
| 80 |
+
self.denominacion = ""
|
| 81 |
+
self.direccion = self.localidad = self.provincia = self.cod_postal = ""
|
| 82 |
+
self.domicilios = []
|
| 83 |
+
self.impuestos = []
|
| 84 |
+
self.actividades = []
|
| 85 |
+
self.imp_iva = self.empleador = self.integrante_soc = self.cat_iva = ""
|
| 86 |
+
self.monotributo = self.actividad_monotributo = ""
|
| 87 |
+
self.data = {}
|
| 88 |
+
self.errores = []
|
| 89 |
+
|
| 90 |
+
def Dummy(self):
|
| 91 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 92 |
+
ret = self.client.dummy()
|
| 93 |
+
result = ret['dummyReturn']
|
| 94 |
+
self.AppServerStatus = result['appserver']
|
| 95 |
+
self.DbServerStatus = result['dbserver']
|
| 96 |
+
self.AuthServerStatus = result['authserver']
|
| 97 |
+
return True
|
| 98 |
+
|
| 99 |
+
@inicializar_y_capturar_excepciones
|
| 100 |
+
def Consultar(self, id_persona):
|
| 101 |
+
"Devuelve el detalle de todos los datos del contribuyente solicitado"
|
| 102 |
+
# llamar al webservice:
|
| 103 |
+
res = self.client.getPersona(
|
| 104 |
+
sign=self.Sign,
|
| 105 |
+
token=self.Token,
|
| 106 |
+
cuitRepresentada=self.Cuit,
|
| 107 |
+
idPersona=id_persona,
|
| 108 |
+
)
|
| 109 |
+
ret = res.get('personaReturn', {})
|
| 110 |
+
# obtengo el resultado de AFIP (dict):
|
| 111 |
+
data = ret.get('persona', None)
|
| 112 |
+
if isinstance(data, list):
|
| 113 |
+
data = data[0]
|
| 114 |
+
self.data = data
|
| 115 |
+
# lo serializo
|
| 116 |
+
self.Persona = json.dumps(self.data,
|
| 117 |
+
default=json_serializer)
|
| 118 |
+
# extraigo los campos principales:
|
| 119 |
+
self.cuit = data["idPersona"]
|
| 120 |
+
self.tipo_persona = data["tipoPersona"]
|
| 121 |
+
self.tipo_doc = TIPO_CLAVE.get(data["tipoClave"])
|
| 122 |
+
self.nro_doc = data.get("numeroDocumento")
|
| 123 |
+
self.estado = data.get("estadoClave")
|
| 124 |
+
if not "razonSocial" in data:
|
| 125 |
+
self.denominacion = ", ".join([data.get("apellido", ""),
|
| 126 |
+
data.get("nombre", "")])
|
| 127 |
+
else:
|
| 128 |
+
self.denominacion = data.get("razonSocial", "")
|
| 129 |
+
# analizo el domicilio, dando prioridad al FISCAL, luego LEGAL/REAL
|
| 130 |
+
domicilios = data.get("domicilio", [])
|
| 131 |
+
domicilios.sort(key=lambda item: item["tipoDomicilio"] != "FISCAL")
|
| 132 |
+
if domicilios:
|
| 133 |
+
domicilio = domicilios[0]
|
| 134 |
+
self.direccion = domicilio.get("direccion", "")
|
| 135 |
+
self.localidad = domicilio.get("localidad", "") # no usado en CABA
|
| 136 |
+
self.provincia = PROVINCIAS.get(domicilio.get("idProvincia"), "")
|
| 137 |
+
self.cod_postal = domicilio.get("codPostal")
|
| 138 |
+
else:
|
| 139 |
+
self.direccion = self.localidad = self.provincia = ""
|
| 140 |
+
self.cod_postal = ""
|
| 141 |
+
# retrocompatibilidad:
|
| 142 |
+
self.domicilios = domicilios
|
| 143 |
+
self.domicilio = "%s - %s (%s) - %s" % (
|
| 144 |
+
self.direccion, self.localidad,
|
| 145 |
+
self.cod_postal, self.provincia,)
|
| 146 |
+
# analizo impuestos:
|
| 147 |
+
self.impuestos = [imp["idImpuesto"] for imp in data.get("impuesto", [])
|
| 148 |
+
if imp['estado'] == 'ACTIVO']
|
| 149 |
+
self.actividades = [act["idActividad"] for act in data.get("actividad", [])]
|
| 150 |
+
mt = [cat for cat in data.get("categoria", [])
|
| 151 |
+
if cat["idImpuesto"] in (20, 21) and cat["estado"] == "ACTIVO"]
|
| 152 |
+
mt.sort(key=lambda cat: cat["idImpuesto"])
|
| 153 |
+
self.analizar_datos(mt[0] if mt else {})
|
| 154 |
+
return True
|
| 155 |
+
|
| 156 |
+
def analizar_datos(self, cat_mt):
|
| 157 |
+
# intenta determinar situaciΓ³n de IVA:
|
| 158 |
+
if 32 in self.impuestos:
|
| 159 |
+
self.imp_iva = "EX"
|
| 160 |
+
elif 33 in self.impuestos:
|
| 161 |
+
self.imp_iva = "NI"
|
| 162 |
+
elif 34 in self.impuestos:
|
| 163 |
+
self.imp_iva = "NA"
|
| 164 |
+
else:
|
| 165 |
+
self.imp_iva = "S" if 30 in self.impuestos else "N"
|
| 166 |
+
self.monotributo = "S" if cat_mt else "N"
|
| 167 |
+
self.actividad_monotributo = cat_mt.get("descripcionCategoria") if cat_mt else ""
|
| 168 |
+
self.integrante_soc = ""
|
| 169 |
+
self.empleador = "S" if 301 in self.impuestos else "N"
|
| 170 |
+
# intenta determinar categorΓa de IVA (confirmar)
|
| 171 |
+
if self.imp_iva in ('AC', 'S'):
|
| 172 |
+
self.cat_iva = 1 # RI
|
| 173 |
+
elif self.imp_iva == 'EX':
|
| 174 |
+
self.cat_iva = 4 # EX
|
| 175 |
+
elif self.monotributo == 'S':
|
| 176 |
+
self.cat_iva = 6 # MT
|
| 177 |
+
else:
|
| 178 |
+
self.cat_iva = 5 # CF
|
| 179 |
+
return True
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
class WSSrPadronA5(WSSrPadronA4):
|
| 183 |
+
"Interfaz para el WebService de Consulta PadrΓ³n Constancia de InscripciΓ³n Alcance 5"
|
| 184 |
+
|
| 185 |
+
_reg_progid_ = "WSSrPadronA5"
|
| 186 |
+
_reg_clsid_ = "{DF7447DD-EEF3-4E6B-A93B-F969B5075EC8}"
|
| 187 |
+
|
| 188 |
+
WSDL = WSDL.replace("personaServiceA4", "personaServiceA5")
|
| 189 |
+
|
| 190 |
+
@inicializar_y_capturar_excepciones
|
| 191 |
+
def Consultar(self, id_persona):
|
| 192 |
+
"Devuelve el detalle de todos los datos del contribuyente solicitado"
|
| 193 |
+
# llamar al webservice:
|
| 194 |
+
res = self.client.getPersona(
|
| 195 |
+
sign=self.Sign,
|
| 196 |
+
token=self.Token,
|
| 197 |
+
cuitRepresentada=self.Cuit,
|
| 198 |
+
idPersona=id_persona,
|
| 199 |
+
)
|
| 200 |
+
ret = res.get('personaReturn', {})
|
| 201 |
+
# obtengo el resultado de AFIP (dict):
|
| 202 |
+
data = ret.get('datosGenerales', {})
|
| 203 |
+
if isinstance(data, list):
|
| 204 |
+
data = data[0]
|
| 205 |
+
self.data = data
|
| 206 |
+
# lo serializo
|
| 207 |
+
self.Persona = json.dumps(ret,
|
| 208 |
+
default=json_serializer)
|
| 209 |
+
for er in 'errorConstancia', 'errorMonotributo', 'errorRegimenGeneral':
|
| 210 |
+
if er in ret:
|
| 211 |
+
self.errores.extend(ret[er])
|
| 212 |
+
self.Excepcion = '\n\r'.join([er["error"] for er in self.errores])
|
| 213 |
+
# extraigo los campos principales:
|
| 214 |
+
self.tipo_persona = data.get("tipoPersona")
|
| 215 |
+
self.tipo_doc = TIPO_CLAVE.get(data.get("tipoClave"))
|
| 216 |
+
self.nro_doc = data.get("idPersona")
|
| 217 |
+
self.cuit = self.nro_doc
|
| 218 |
+
self.estado = data.get("estadoClave")
|
| 219 |
+
if not "razonSocial" in data:
|
| 220 |
+
self.denominacion = ", ".join([data.get("apellido", ""),
|
| 221 |
+
data.get("nombre", "")])
|
| 222 |
+
else:
|
| 223 |
+
self.denominacion = data.get("razonSocial", "")
|
| 224 |
+
# analizo el domicilio, dando prioridad al FISCAL, luego LEGAL/REAL
|
| 225 |
+
domicilio = data.get("domicilioFiscal", [])
|
| 226 |
+
if domicilio:
|
| 227 |
+
self.direccion = domicilio.get("direccion", "")
|
| 228 |
+
self.localidad = domicilio.get("localidad", "") # no usado en CABA
|
| 229 |
+
self.provincia = PROVINCIAS.get(domicilio.get("idProvincia"), "")
|
| 230 |
+
self.cod_postal = domicilio.get("codPostal")
|
| 231 |
+
else:
|
| 232 |
+
self.direccion = self.localidad = self.provincia = ""
|
| 233 |
+
self.cod_postal = ""
|
| 234 |
+
# retrocompatibilidad:
|
| 235 |
+
self.domicilios = [domicilio]
|
| 236 |
+
self.domicilio = "%s - %s (%s) - %s" % (
|
| 237 |
+
self.direccion, self.localidad,
|
| 238 |
+
self.cod_postal, self.provincia,)
|
| 239 |
+
# extraer datos impositivos (inscripciΓ³n / opciΓ³n) para unificarlos:
|
| 240 |
+
data_mt = ret.get("datosMonotributo", {})
|
| 241 |
+
data_rg = ret.get("datosRegimenGeneral", {})
|
| 242 |
+
# analizo impuestos:
|
| 243 |
+
impuestos = data_mt.get("impuesto", []) + data_rg.get("impuesto", [])
|
| 244 |
+
self.impuestos = [imp["idImpuesto"] for imp in impuestos]
|
| 245 |
+
actividades = data_rg.get("actividad", []) + data_mt.get("actividadMonotributista", [])
|
| 246 |
+
self.actividades = [act["idActividad"] for act in actividades]
|
| 247 |
+
cat_mt = data_mt.get("categoriaMonotributo", {})
|
| 248 |
+
self.analizar_datos(cat_mt)
|
| 249 |
+
return not self.errores
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def main():
|
| 253 |
+
"FunciΓ³n principal de pruebas (obtener CAE)"
|
| 254 |
+
import os
|
| 255 |
+
import time
|
| 256 |
+
global CONFIG_FILE
|
| 257 |
+
|
| 258 |
+
DEBUG = '--debug' in sys.argv
|
| 259 |
+
|
| 260 |
+
if '--constancia' in sys.argv:
|
| 261 |
+
padron = WSSrPadronA5()
|
| 262 |
+
SECTION = 'WS-SR-PADRON-A5'
|
| 263 |
+
service = "ws_sr_constancia_inscripcion"
|
| 264 |
+
else:
|
| 265 |
+
padron = WSSrPadronA4()
|
| 266 |
+
SECTION = 'WS-SR-PADRON-A4'
|
| 267 |
+
service = "ws_sr_padron_a4"
|
| 268 |
+
|
| 269 |
+
config = abrir_conf(CONFIG_FILE, DEBUG)
|
| 270 |
+
if config.has_section('WSAA'):
|
| 271 |
+
crt = config.get('WSAA', 'CERT')
|
| 272 |
+
key = config.get('WSAA', 'PRIVATEKEY')
|
| 273 |
+
cuit = config.get(SECTION, 'CUIT')
|
| 274 |
+
else:
|
| 275 |
+
crt, key = "reingart.crt", "reingart.key"
|
| 276 |
+
cuit = "20267565393"
|
| 277 |
+
url_wsaa = url_ws = None
|
| 278 |
+
if config.has_option('WSAA', 'URL'):
|
| 279 |
+
url_wsaa = config.get('WSAA', 'URL')
|
| 280 |
+
if config.has_option(SECTION, 'URL') and not HOMO:
|
| 281 |
+
url_ws = config.get(SECTION, 'URL')
|
| 282 |
+
|
| 283 |
+
# obteniendo el TA para pruebas
|
| 284 |
+
from .wsaa import WSAA
|
| 285 |
+
|
| 286 |
+
cache = ""
|
| 287 |
+
ta = WSAA().Autenticar(service, crt, key, url_wsaa)
|
| 288 |
+
|
| 289 |
+
padron.SetTicketAcceso(ta)
|
| 290 |
+
padron.Cuit = cuit
|
| 291 |
+
padron.Conectar(cache, url_ws, cacert="conf/afip_ca_info.crt")
|
| 292 |
+
|
| 293 |
+
if "--dummy" in sys.argv:
|
| 294 |
+
print(padron.client.help("dummy"))
|
| 295 |
+
wssrpadron4.Dummy()
|
| 296 |
+
print("AppServerStatus", wssrpadron4.AppServerStatus)
|
| 297 |
+
print("DbServerStatus", wssrpadron4.DbServerStatus)
|
| 298 |
+
print("AuthServerStatus", wssrpadron4.AuthServerStatus)
|
| 299 |
+
|
| 300 |
+
if '--csv' in sys.argv:
|
| 301 |
+
csv_reader = csv.reader(open("entrada.csv", "rU"),
|
| 302 |
+
dialect='excel', delimiter=",")
|
| 303 |
+
csv_writer = csv.writer(open("salida.csv", "w"),
|
| 304 |
+
dialect='excel', delimiter=",")
|
| 305 |
+
encabezado = next(csv_reader)
|
| 306 |
+
columnas = ["cuit", "denominacion", "estado", "direccion",
|
| 307 |
+
"localidad", "provincia", "cod_postal",
|
| 308 |
+
"impuestos", "actividades", "imp_iva",
|
| 309 |
+
"monotributo", "actividad_monotributo",
|
| 310 |
+
"empleador", "imp_ganancias", "integrante_soc"]
|
| 311 |
+
csv_writer.writerow(columnas)
|
| 312 |
+
|
| 313 |
+
for fila in csv_reader:
|
| 314 |
+
cuit = (fila[0] if fila else "").replace("-", "")
|
| 315 |
+
if cuit.isdigit():
|
| 316 |
+
print("Consultando AFIP online...", cuit, end=' ')
|
| 317 |
+
try:
|
| 318 |
+
ok = padron.Consultar(cuit)
|
| 319 |
+
except SoapFault as e:
|
| 320 |
+
ok = None
|
| 321 |
+
if e.faultstring != "No existe persona con ese Id":
|
| 322 |
+
raise
|
| 323 |
+
print('ok' if ok else "error", padron.Excepcion)
|
| 324 |
+
# domicilio posiblemente estΓ© en Latin1, normalizar
|
| 325 |
+
csv_writer.writerow([norm(getattr(padron, campo, ""))
|
| 326 |
+
for campo in columnas])
|
| 327 |
+
sys.exit(0)
|
| 328 |
+
|
| 329 |
+
try:
|
| 330 |
+
|
| 331 |
+
if "--prueba" in sys.argv:
|
| 332 |
+
id_persona = "20000000516"
|
| 333 |
+
else:
|
| 334 |
+
id_persona = len(sys.argv) > 1 and sys.argv[1] or "20267565393"
|
| 335 |
+
|
| 336 |
+
if "--testing" in sys.argv:
|
| 337 |
+
padron.LoadTestXML("tests/xml/%s_resp.xml" % service)
|
| 338 |
+
print("Consultando AFIP online via webservice...", end=' ')
|
| 339 |
+
ok = padron.Consultar(id_persona)
|
| 340 |
+
|
| 341 |
+
if DEBUG:
|
| 342 |
+
print("Persona", padron.Persona)
|
| 343 |
+
print(padron.Excepcion)
|
| 344 |
+
|
| 345 |
+
print('ok' if ok else "error", padron.Excepcion)
|
| 346 |
+
print("Denominacion:", padron.denominacion)
|
| 347 |
+
print("Tipo:", padron.tipo_persona, padron.tipo_doc, padron.nro_doc)
|
| 348 |
+
print("Estado:", padron.estado)
|
| 349 |
+
print("Direccion:", padron.direccion)
|
| 350 |
+
print("Localidad:", padron.localidad)
|
| 351 |
+
print("Provincia:", padron.provincia)
|
| 352 |
+
print("Codigo Postal:", padron.cod_postal)
|
| 353 |
+
print("Impuestos:", padron.impuestos)
|
| 354 |
+
print("Actividades:", padron.actividades)
|
| 355 |
+
print("IVA", padron.imp_iva)
|
| 356 |
+
print("MT", padron.monotributo, padron.actividad_monotributo)
|
| 357 |
+
print("Empleador", padron.empleador)
|
| 358 |
+
|
| 359 |
+
if padron.Excepcion:
|
| 360 |
+
print("Excepcion:", padron.Excepcion)
|
| 361 |
+
# ver padron.errores para el detalle
|
| 362 |
+
|
| 363 |
+
except BaseException:
|
| 364 |
+
raise
|
| 365 |
+
print(padron.XmlRequest)
|
| 366 |
+
print(padron.XmlResponse)
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
# busco el directorio de instalaciΓ³n (global para que no cambie si usan otra dll)
|
| 370 |
+
INSTALL_DIR = WSSrPadronA4.InstallDir = WSSrPadronA5.InstallDir = get_install_dir()
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
if __name__ == '__main__':
|
| 374 |
+
|
| 375 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 376 |
+
import win32com.server.register
|
| 377 |
+
win32com.server.register.UseCommandLine(WSSrPadronA4)
|
| 378 |
+
win32com.server.register.UseCommandLine(WSSrPadronA5)
|
| 379 |
+
else:
|
| 380 |
+
main()
|
app/pyafipws/wsaa.py
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"MΓ³dulo para obtener un ticket de autorizaciΓ³n del web service WSAA de AFIP"
|
| 14 |
+
|
| 15 |
+
# Basado en wsaa-client.php de Gerardo Fisanotti - DvSHyS/DiOPIN/AFIP - 13-apr-07
|
| 16 |
+
# Definir WSDL, CERT, PRIVATEKEY, PASSPHRASE, SERVICE, WSAAURL
|
| 17 |
+
# Devuelve TA.xml (ticket de autorizaciΓ³n de WSAA)
|
| 18 |
+
|
| 19 |
+
__author__ = "Mariano Reingart (reingart@gmail.com)"
|
| 20 |
+
__copyright__ = "Copyright (C) 2008-2011 Mariano Reingart"
|
| 21 |
+
__license__ = "GPL 3.0"
|
| 22 |
+
__version__ = "2.11c"
|
| 23 |
+
|
| 24 |
+
import hashlib
|
| 25 |
+
import datetime
|
| 26 |
+
import email
|
| 27 |
+
import os
|
| 28 |
+
import sys
|
| 29 |
+
import time
|
| 30 |
+
import traceback
|
| 31 |
+
import warnings
|
| 32 |
+
import unicodedata
|
| 33 |
+
from pysimplesoap.client import SimpleXMLElement
|
| 34 |
+
from .utils import inicializar_y_capturar_excepciones, BaseWS, get_install_dir, \
|
| 35 |
+
exception_info, safe_console, date
|
| 36 |
+
try:
|
| 37 |
+
from M2Crypto import BIO, Rand, SMIME, SSL
|
| 38 |
+
except ImportError:
|
| 39 |
+
ex = exception_info()
|
| 40 |
+
warnings.warn("No es posible importar M2Crypto (OpenSSL)")
|
| 41 |
+
warnings.warn(ex['msg']) # revisar instalaciΓ³n y DLLs de OpenSSL
|
| 42 |
+
BIO = Rand = SMIME = SSL = None
|
| 43 |
+
# utilizar alternativa (ejecutar proceso por separado)
|
| 44 |
+
from subprocess import Popen, PIPE
|
| 45 |
+
from base64 import b64encode
|
| 46 |
+
from tempfile import NamedTemporaryFile
|
| 47 |
+
|
| 48 |
+
# Constantes (si se usa el script de linea de comandos)
|
| 49 |
+
WSDL = "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl" # El WSDL correspondiente al WSAA
|
| 50 |
+
CERT = "reingart.crt" # El certificado X.509 obtenido de Seg. Inf.
|
| 51 |
+
PRIVATEKEY = "reingart.key" # La clave privada del certificado CERT
|
| 52 |
+
PASSPHRASE = "xxxxxxx" # La contraseΓ±a para firmar (si hay)
|
| 53 |
+
SERVICE = "wsfe" # El nombre del web service al que se le pide el TA
|
| 54 |
+
|
| 55 |
+
# WSAAURL: la URL para acceder al WSAA, verificar http/https y wsaa/wsaahomo
|
| 56 |
+
# WSAAURL = "https://wsaa.afip.gov.ar/ws/services/LoginCms" # PRODUCCION!!!
|
| 57 |
+
WSAAURL = "https://wsaahomo.afip.gov.ar/ws/services/LoginCms" # homologacion (pruebas)
|
| 58 |
+
SOAP_ACTION = 'http://ar.gov.afip.dif.facturaelectronica/' # Revisar WSDL
|
| 59 |
+
SOAP_NS = "http://wsaa.view.sua.dvadac.desein.afip.gov" # Revisar WSDL
|
| 60 |
+
|
| 61 |
+
# VerificaciΓ³n del web server remoto, necesario para verificar canal seguro
|
| 62 |
+
CACERT = "conf/afip_ca_info.crt" # WSAA CA Cert (Autoridades de Confiaza)
|
| 63 |
+
|
| 64 |
+
HOMO = False
|
| 65 |
+
TYPELIB = False
|
| 66 |
+
DEFAULT_TTL = 60 * 60 * 5 # five hours
|
| 67 |
+
DEBUG = False
|
| 68 |
+
|
| 69 |
+
# No deberΓa ser necesario modificar nada despues de esta linea
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def create_tra(service=SERVICE, ttl=2400):
|
| 73 |
+
"Crear un Ticket de Requerimiento de Acceso (TRA)"
|
| 74 |
+
tra = SimpleXMLElement(
|
| 75 |
+
'<?xml version="1.0" encoding="UTF-8"?>'
|
| 76 |
+
'<loginTicketRequest version="1.0">'
|
| 77 |
+
'</loginTicketRequest>')
|
| 78 |
+
tra.add_child('header')
|
| 79 |
+
# El source es opcional. Si falta, toma la firma (recomendado).
|
| 80 |
+
# tra.header.addChild('source','subject=...')
|
| 81 |
+
#tra.header.addChild('destination','cn=wsaahomo,o=afip,c=ar,serialNumber=CUIT 33693450239')
|
| 82 |
+
tra.header.add_child('uniqueId', str(date('U')))
|
| 83 |
+
tra.header.add_child('generationTime', str(date('c', date('U') - ttl)))
|
| 84 |
+
tra.header.add_child('expirationTime', str(date('c', date('U') + ttl)))
|
| 85 |
+
tra.add_child('service', service)
|
| 86 |
+
xml = tra.as_xml()
|
| 87 |
+
if isinstance(xml, bytes):
|
| 88 |
+
return xml
|
| 89 |
+
return xml.encode('utf-8')
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""):
|
| 93 |
+
"Firmar PKCS#7 el TRA y devolver CMS (recortando los headers SMIME)"
|
| 94 |
+
|
| 95 |
+
if BIO:
|
| 96 |
+
# Firmar el texto (tra) usando m2crypto (openssl bindings para python)
|
| 97 |
+
buf = BIO.MemoryBuffer(tra) # Crear un buffer desde el texto
|
| 98 |
+
#Rand.load_file('randpool.dat', -1) # Alimentar el PRNG
|
| 99 |
+
s = SMIME.SMIME() # Instanciar un SMIME
|
| 100 |
+
# soporte de contraseΓ±a de encriptaciΓ³n (clave privada, opcional)
|
| 101 |
+
callback = lambda *args, **kwarg: passphrase
|
| 102 |
+
# Cargar clave privada y certificado
|
| 103 |
+
if not privatekey.startswith("-----BEGIN RSA PRIVATE KEY-----"):
|
| 104 |
+
# leer contenido desde archivo (evitar problemas Applink / MSVCRT)
|
| 105 |
+
if os.path.exists(privatekey) and os.path.exists(cert):
|
| 106 |
+
privatekey = open(privatekey).read()
|
| 107 |
+
cert = open(cert).read()
|
| 108 |
+
else:
|
| 109 |
+
raise RuntimeError("Archivos no encontrados: %s, %s" % (privatekey, cert))
|
| 110 |
+
# crear buffers en memoria de la clave privada y certificado:
|
| 111 |
+
key_bio = BIO.MemoryBuffer(privatekey.encode('utf8'))
|
| 112 |
+
crt_bio = BIO.MemoryBuffer(cert.encode('utf8'))
|
| 113 |
+
s.load_key_bio(key_bio, crt_bio, callback) # (desde buffer)
|
| 114 |
+
p7 = s.sign(buf, 0) # Firmar el buffer
|
| 115 |
+
out = BIO.MemoryBuffer() # Crear un buffer para la salida
|
| 116 |
+
s.write(out, p7) # Generar p7 en formato mail
|
| 117 |
+
# Rand.save_file('randpool.dat') # Guardar el estado del PRNG's
|
| 118 |
+
|
| 119 |
+
# extraer el cuerpo del mensaje (parte firmada)
|
| 120 |
+
msg = email.message_from_string(out.read().decode('utf8'))
|
| 121 |
+
for part in msg.walk():
|
| 122 |
+
filename = part.get_filename()
|
| 123 |
+
if filename == "smime.p7m": # es la parte firmada?
|
| 124 |
+
return part.get_payload(decode=False) # devolver CMS
|
| 125 |
+
else:
|
| 126 |
+
# Firmar el texto (tra) usando OPENSSL directamente
|
| 127 |
+
try:
|
| 128 |
+
if sys.platform.startswith("linux"):
|
| 129 |
+
openssl = "openssl"
|
| 130 |
+
else:
|
| 131 |
+
if sys.maxsize <= 2**32:
|
| 132 |
+
openssl = r"c:\OpenSSL-Win32\bin\openssl.exe"
|
| 133 |
+
else:
|
| 134 |
+
openssl = r"c:\OpenSSL-Win64\bin\openssl.exe"
|
| 135 |
+
# NOTE: workaround if certificate is not already stored in a file
|
| 136 |
+
# SECURITY WARNING: the private key will be exposed a bit in /tmp
|
| 137 |
+
# (in theory only for the current user)
|
| 138 |
+
if cert.startswith("-----BEGIN CERTIFICATE-----"):
|
| 139 |
+
cert_f = NamedTemporaryFile()
|
| 140 |
+
cert_f.write(cert.encode('utf-8'))
|
| 141 |
+
cert_f.flush()
|
| 142 |
+
cert = cert_f.name
|
| 143 |
+
else:
|
| 144 |
+
cert_f = None
|
| 145 |
+
if privatekey.startswith("-----BEGIN RSA PRIVATE KEY-----"):
|
| 146 |
+
key_f = NamedTemporaryFile()
|
| 147 |
+
key_f.write(privatekey.encode('utf-8'))
|
| 148 |
+
key_f.flush()
|
| 149 |
+
privatekey = key_f.name
|
| 150 |
+
else:
|
| 151 |
+
key_f = None
|
| 152 |
+
try:
|
| 153 |
+
out = Popen([openssl, "smime", "-sign",
|
| 154 |
+
"-signer", cert, "-inkey", privatekey,
|
| 155 |
+
"-outform","DER", "-nodetach"],
|
| 156 |
+
stdin=PIPE, stdout=PIPE,
|
| 157 |
+
stderr=PIPE).communicate(tra)[0]
|
| 158 |
+
finally:
|
| 159 |
+
# close temp files to delete them (just in case):
|
| 160 |
+
if cert_f:
|
| 161 |
+
cert_f.close()
|
| 162 |
+
if key_f:
|
| 163 |
+
key_f.close()
|
| 164 |
+
return b64encode(out).decode("utf8")
|
| 165 |
+
except OSError as e:
|
| 166 |
+
if e.errno == 2:
|
| 167 |
+
warnings.warn("El ejecutable de OpenSSL no esta disponible en el PATH")
|
| 168 |
+
raise
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def call_wsaa(cms, location=WSAAURL, proxy=None, trace=False, cacert=None):
|
| 172 |
+
"Llamar web service con CMS para obtener ticket de autorizaciΓ³n (TA)"
|
| 173 |
+
|
| 174 |
+
# creo la nueva clase
|
| 175 |
+
wsaa = WSAA()
|
| 176 |
+
try:
|
| 177 |
+
wsaa.Conectar(proxy=proxy, wsdl=location, cache="", cacert=cacert)
|
| 178 |
+
ta = wsaa.LoginCMS(cms)
|
| 179 |
+
if not ta:
|
| 180 |
+
raise RuntimeError(wsaa.Excepcion)
|
| 181 |
+
else:
|
| 182 |
+
return ta
|
| 183 |
+
except BaseException:
|
| 184 |
+
raise
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class WSAA(BaseWS):
|
| 188 |
+
"Interfaz para el WebService de AutenticaciΓ³n y AutorizaciΓ³n"
|
| 189 |
+
_public_methods_ = ['CreateTRA', 'SignTRA', 'CallWSAA', 'LoginCMS', 'Conectar',
|
| 190 |
+
'AnalizarXml', 'ObtenerTagXml', 'Expirado', 'Autenticar',
|
| 191 |
+
'DebugLog', 'AnalizarCertificado',
|
| 192 |
+
'CrearClavePrivada', 'CrearPedidoCertificado',
|
| 193 |
+
]
|
| 194 |
+
_public_attrs_ = ['Token', 'Sign', 'ExpirationTime', 'Version',
|
| 195 |
+
'XmlRequest', 'XmlResponse',
|
| 196 |
+
'InstallDir', 'Traceback', 'Excepcion',
|
| 197 |
+
'Identidad', 'Caducidad', 'Emisor', 'CertX509',
|
| 198 |
+
'SoapFault', 'LanzarExcepciones',
|
| 199 |
+
]
|
| 200 |
+
_readonly_attrs_ = _public_attrs_[:-1]
|
| 201 |
+
_reg_progid_ = "WSAA"
|
| 202 |
+
_reg_clsid_ = "{6268820C-8900-4AE9-8A2D-F0A1EBD4CAC5}"
|
| 203 |
+
|
| 204 |
+
if TYPELIB:
|
| 205 |
+
_typelib_guid_ = '{30E9C94B-7385-4534-9A80-DF50FD169253}'
|
| 206 |
+
_typelib_version_ = 2, 11
|
| 207 |
+
_com_interfaces_ = ['IWSAA']
|
| 208 |
+
|
| 209 |
+
# Variables globales para BaseWS:
|
| 210 |
+
HOMO = HOMO
|
| 211 |
+
WSDL = WSDL
|
| 212 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciΓ³n' or '')
|
| 213 |
+
|
| 214 |
+
@inicializar_y_capturar_excepciones
|
| 215 |
+
def CreateTRA(self, service="wsfe", ttl=2400):
|
| 216 |
+
"Crear un Ticket de Requerimiento de Acceso (TRA)"
|
| 217 |
+
return create_tra(service, ttl)
|
| 218 |
+
|
| 219 |
+
@inicializar_y_capturar_excepciones
|
| 220 |
+
def AnalizarCertificado(self, crt, binary=False):
|
| 221 |
+
"Carga un certificado digital y extrae los campos mΓ‘s importantes"
|
| 222 |
+
from M2Crypto import BIO, EVP, RSA, X509
|
| 223 |
+
if binary:
|
| 224 |
+
bio = BIO.MemoryBuffer(cert.encode('utf8'))
|
| 225 |
+
x509 = X511.load_cert_bio(bio, X509.FORMAT_DER)
|
| 226 |
+
else:
|
| 227 |
+
if not crt.startswith("-----BEGIN CERTIFICATE-----"):
|
| 228 |
+
crt = open(crt).read()
|
| 229 |
+
if isinstance(crt, str):
|
| 230 |
+
crt = crt.encode('utf-8')
|
| 231 |
+
bio = BIO.MemoryBuffer(crt.encode('utf8'))
|
| 232 |
+
x509 = X509.load_cert_bio(bio, X509.FORMAT_PEM)
|
| 233 |
+
if x509:
|
| 234 |
+
self.Identidad = x509.get_subject().as_text()
|
| 235 |
+
self.Caducidad = x509.get_not_after().get_datetime()
|
| 236 |
+
self.Emisor = x509.get_issuer().as_text()
|
| 237 |
+
self.CertX509 = x509.as_text()
|
| 238 |
+
return True
|
| 239 |
+
|
| 240 |
+
@inicializar_y_capturar_excepciones
|
| 241 |
+
def CrearClavePrivada(self, filename="privada.key", key_length=4096,
|
| 242 |
+
pub_exponent=0x10001, passphrase=""):
|
| 243 |
+
"Crea una clave privada (private key)"
|
| 244 |
+
from M2Crypto import RSA, EVP
|
| 245 |
+
|
| 246 |
+
# only protect if passphrase was given (it will fail otherwise)
|
| 247 |
+
callback = lambda *args, **kwarg: passphrase
|
| 248 |
+
chiper = None if not passphrase else "aes_128_cbc"
|
| 249 |
+
# create the RSA key pair (and save the result to a file):
|
| 250 |
+
rsa_key_pair = RSA.gen_key(key_length, pub_exponent, callback)
|
| 251 |
+
bio = BIO.MemoryBuffer()
|
| 252 |
+
rsa_key_pair.save_key_bio(bio, chiper, callback)
|
| 253 |
+
f = open(filename, "w")
|
| 254 |
+
f.write(bio.read())
|
| 255 |
+
f.close()
|
| 256 |
+
# create a public key to sign the certificate request:
|
| 257 |
+
self.pkey = EVP.PKey(md='sha256')
|
| 258 |
+
self.pkey.assign_rsa(rsa_key_pair)
|
| 259 |
+
return True
|
| 260 |
+
|
| 261 |
+
@inicializar_y_capturar_excepciones
|
| 262 |
+
def CrearPedidoCertificado(self, cuit="", empresa="", nombre="pyafipws",
|
| 263 |
+
filename="empresa.csr"):
|
| 264 |
+
"Crear un certificate signing request (X509 CSR)"
|
| 265 |
+
from M2Crypto import RSA, EVP, X509
|
| 266 |
+
|
| 267 |
+
# create the certificate signing request (CSR):
|
| 268 |
+
self.x509_req = X509.Request()
|
| 269 |
+
|
| 270 |
+
# normalizar encoding (reemplazar acentos, eΓ±e, etc.)
|
| 271 |
+
if isinstance(empresa, str):
|
| 272 |
+
empresa = unicodedata.normalize('NFKD', empresa).encode('ASCII', 'ignore')
|
| 273 |
+
if isinstance(nombre, str):
|
| 274 |
+
nombre = unicodedata.normalize('NFKD', nombre).encode('ASCII', 'ignore')
|
| 275 |
+
|
| 276 |
+
# subjet: C=AR/O=[empresa]/CN=[nombre]/serialNumber=CUIT [nro_cuit]
|
| 277 |
+
x509name = X509.X509_Name()
|
| 278 |
+
# default OpenSSL parameters:
|
| 279 |
+
kwargs = {"type": 0x1000 | 1, "len": -1, "loc": -1, "set": 0}
|
| 280 |
+
x509name.add_entry_by_txt(field='C', entry='AR', **kwargs)
|
| 281 |
+
x509name.add_entry_by_txt(field='O', entry=empresa, **kwargs)
|
| 282 |
+
x509name.add_entry_by_txt(field='CN', entry=nombre, **kwargs)
|
| 283 |
+
x509name.add_entry_by_txt(field='serialNumber', entry="CUIT %s" % str(cuit), **kwargs)
|
| 284 |
+
self.x509_req.set_subject_name(x509name)
|
| 285 |
+
|
| 286 |
+
# sign the request with the previously created key (CrearClavePrivada)
|
| 287 |
+
self.x509_req.set_pubkey(pkey=self.pkey)
|
| 288 |
+
self.x509_req.sign(pkey=self.pkey, md='sha256')
|
| 289 |
+
# save the CSR result to a file:
|
| 290 |
+
f = open(filename, "w")
|
| 291 |
+
f.write(self.x509_req.as_pem())
|
| 292 |
+
f.close()
|
| 293 |
+
return True
|
| 294 |
+
|
| 295 |
+
@inicializar_y_capturar_excepciones
|
| 296 |
+
def SignTRA(self, tra, cert, privatekey, passphrase=""):
|
| 297 |
+
"Firmar el TRA y devolver CMS"
|
| 298 |
+
return sign_tra(tra, cert, privatekey, passphrase)
|
| 299 |
+
|
| 300 |
+
@inicializar_y_capturar_excepciones
|
| 301 |
+
def LoginCMS(self, cms):
|
| 302 |
+
"Obtener ticket de autorizaciΓ³n (TA)"
|
| 303 |
+
results = self.client.loginCms(in0=str(cms))
|
| 304 |
+
ta_xml = results['loginCmsReturn'] # .encode("utf-8")
|
| 305 |
+
self.xml = ta = SimpleXMLElement(ta_xml)
|
| 306 |
+
self.Token = str(ta.credentials.token)
|
| 307 |
+
self.Sign = str(ta.credentials.sign)
|
| 308 |
+
self.ExpirationTime = str(ta.header.expirationTime)
|
| 309 |
+
return ta_xml
|
| 310 |
+
|
| 311 |
+
def CallWSAA(self, cms, url="", proxy=None):
|
| 312 |
+
"Obtener ticket de autorizaciΓ³n (TA) -version retrocompatible-"
|
| 313 |
+
self.Conectar("", url, proxy)
|
| 314 |
+
ta_xml = self.LoginCMS(cms)
|
| 315 |
+
if not ta_xml:
|
| 316 |
+
raise RuntimeError(self.Excepcion)
|
| 317 |
+
return ta_xml
|
| 318 |
+
|
| 319 |
+
@inicializar_y_capturar_excepciones
|
| 320 |
+
def Expirado(self, fecha=None):
|
| 321 |
+
"Comprueba la fecha de expiraciΓ³n, devuelve si ha expirado"
|
| 322 |
+
if not fecha:
|
| 323 |
+
fecha = self.ObtenerTagXml('expirationTime')
|
| 324 |
+
now = datetime.datetime.now()
|
| 325 |
+
d = datetime.datetime.strptime(fecha[:19], '%Y-%m-%dT%H:%M:%S')
|
| 326 |
+
return now > d
|
| 327 |
+
|
| 328 |
+
def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cacert=None, cache=None, debug=False):
|
| 329 |
+
"MΓ©todo unificado para obtener el ticket de acceso (cacheado)"
|
| 330 |
+
|
| 331 |
+
self.LanzarExcepciones = True
|
| 332 |
+
try:
|
| 333 |
+
# sanity check: verificar las credenciales
|
| 334 |
+
for filename in (crt, key):
|
| 335 |
+
if not os.access(filename, os.R_OK):
|
| 336 |
+
raise RuntimeError("Imposible abrir %s\n" % filename)
|
| 337 |
+
# creo el nombre para el archivo del TA (segΓΊn credenciales y ws)
|
| 338 |
+
ta_src = (service + crt + key).encode("utf8")
|
| 339 |
+
fn = "TA-%s.xml" % hashlib.md5(ta_src).hexdigest()
|
| 340 |
+
if cache:
|
| 341 |
+
fn = os.path.join(cache, fn)
|
| 342 |
+
else:
|
| 343 |
+
fn = os.path.join(self.InstallDir, "cache", fn)
|
| 344 |
+
|
| 345 |
+
# leer el ticket de acceso (si fue previamente solicitado)
|
| 346 |
+
if not os.path.exists(fn) or os.path.getsize(fn) == 0 or \
|
| 347 |
+
os.path.getmtime(fn) + (DEFAULT_TTL) < time.time():
|
| 348 |
+
# ticket de acceso (TA) vencido, crear un nuevo req. (TRA)
|
| 349 |
+
if DEBUG:
|
| 350 |
+
print("Creando TRA...")
|
| 351 |
+
tra = self.CreateTRA(service=service, ttl=DEFAULT_TTL)
|
| 352 |
+
# firmarlo criptogrΓ‘ficamente
|
| 353 |
+
if DEBUG:
|
| 354 |
+
print("Frimando TRA...")
|
| 355 |
+
cms = self.SignTRA(tra, crt, key)
|
| 356 |
+
# concectar con el servicio web:
|
| 357 |
+
if DEBUG:
|
| 358 |
+
print("Conectando a WSAA...")
|
| 359 |
+
ok = self.Conectar(cache, wsdl, proxy, wrapper, cacert)
|
| 360 |
+
if not ok or self.Excepcion:
|
| 361 |
+
raise RuntimeError("Fallo la conexiΓ³n: %s" % self.Excepcion)
|
| 362 |
+
# llamar al mΓ©todo remoto para solicitar el TA
|
| 363 |
+
if DEBUG:
|
| 364 |
+
print("Llamando WSAA...")
|
| 365 |
+
ta = self.LoginCMS(cms)
|
| 366 |
+
if not ta:
|
| 367 |
+
raise RuntimeError("Ticket de acceso vacio: %s" % WSAA.Excepcion)
|
| 368 |
+
# grabar el ticket de acceso para poder reutilizarlo luego
|
| 369 |
+
if DEBUG:
|
| 370 |
+
print("Grabando TA en %s..." % fn)
|
| 371 |
+
try:
|
| 372 |
+
open(fn, "w").write(ta)
|
| 373 |
+
except IOError as e:
|
| 374 |
+
self.Excepcion = "Imposible grabar ticket de accesso: %s" % fn
|
| 375 |
+
else:
|
| 376 |
+
# leer el ticket de acceso del archivo en cache
|
| 377 |
+
if DEBUG:
|
| 378 |
+
print("Leyendo TA de %s..." % fn)
|
| 379 |
+
ta = open(fn, "r").read()
|
| 380 |
+
# analizar el ticket de acceso y extraer los datos relevantes
|
| 381 |
+
self.AnalizarXml(xml=ta)
|
| 382 |
+
self.Token = self.ObtenerTagXml("token")
|
| 383 |
+
self.Sign = self.ObtenerTagXml("sign")
|
| 384 |
+
except BaseException:
|
| 385 |
+
ta = ""
|
| 386 |
+
if not self.Excepcion:
|
| 387 |
+
# avoid encoding problem when reporting exceptions to the user:
|
| 388 |
+
self.Excepcion = traceback.format_exception_only(sys.exc_info()[0],
|
| 389 |
+
sys.exc_info()[1])[0]
|
| 390 |
+
self.Traceback = ""
|
| 391 |
+
if DEBUG or debug:
|
| 392 |
+
raise
|
| 393 |
+
return ta
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
# busco el directorio de instalaciΓ³n (global para que no cambie si usan otra dll)
|
| 397 |
+
INSTALL_DIR = WSAA.InstallDir = get_install_dir()
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
if __name__ == "__main__":
|
| 401 |
+
|
| 402 |
+
safe_console()
|
| 403 |
+
|
| 404 |
+
if '--register' in sys.argv or '--unregister' in sys.argv:
|
| 405 |
+
import pythoncom
|
| 406 |
+
if TYPELIB:
|
| 407 |
+
if '--register' in sys.argv:
|
| 408 |
+
tlb = os.path.abspath(os.path.join(INSTALL_DIR, "typelib", "wsaa.tlb"))
|
| 409 |
+
print("Registering %s" % (tlb,))
|
| 410 |
+
tli = pythoncom.LoadTypeLib(tlb)
|
| 411 |
+
pythoncom.RegisterTypeLib(tli, tlb)
|
| 412 |
+
elif '--unregister' in sys.argv:
|
| 413 |
+
k = WSAA
|
| 414 |
+
pythoncom.UnRegisterTypeLib(k._typelib_guid_,
|
| 415 |
+
k._typelib_version_[0],
|
| 416 |
+
k._typelib_version_[1],
|
| 417 |
+
0,
|
| 418 |
+
pythoncom.SYS_WIN32)
|
| 419 |
+
print("Unregistered typelib")
|
| 420 |
+
import win32com.server.register
|
| 421 |
+
win32com.server.register.UseCommandLine(WSAA)
|
| 422 |
+
elif "/Automate" in sys.argv:
|
| 423 |
+
# MS seems to like /automate to run the class factories.
|
| 424 |
+
import win32com.server.localserver
|
| 425 |
+
# win32com.server.localserver.main()
|
| 426 |
+
# start the server.
|
| 427 |
+
win32com.server.localserver.serve([WSAA._reg_clsid_])
|
| 428 |
+
elif "--crear_pedido_cert" in sys.argv:
|
| 429 |
+
# instanciar el helper y revisar los parΓ‘metros
|
| 430 |
+
wsaa = WSAA()
|
| 431 |
+
args = [arg for arg in sys.argv if not arg.startswith("--")]
|
| 432 |
+
# obtengo el CUIT y lo normalizo:
|
| 433 |
+
cuit = len(args) > 1 and args[1] or input("Ingrese un CUIT: ")
|
| 434 |
+
cuit = ''.join([c for c in cuit if c.isdigit()])
|
| 435 |
+
nombre = len(args) > 2 and args[2] or "PyAfipWs"
|
| 436 |
+
# consultar el padrΓ³n online de AFIP si no se especificΓ³ razΓ³n social:
|
| 437 |
+
empresa = len(args) > 3 and args[3] or ""
|
| 438 |
+
if not empresa:
|
| 439 |
+
from .padron import PadronAFIP
|
| 440 |
+
padron = PadronAFIP()
|
| 441 |
+
ok = padron.Consultar(cuit)
|
| 442 |
+
if ok and padron.denominacion:
|
| 443 |
+
print("DenominaciΓ³n segΓΊn AFIP:", padron.denominacion)
|
| 444 |
+
empresa = padron.denominacion
|
| 445 |
+
else:
|
| 446 |
+
print("CUIT %s no encontrado: %s..." % (cuit, padron.Excepcion))
|
| 447 |
+
empresa = input("Empresa: ")
|
| 448 |
+
# longitud de la clave (2048 predeterminada a partir de 8/2016)
|
| 449 |
+
key_length = len(args) > 4 and args[4] or ""
|
| 450 |
+
try:
|
| 451 |
+
key_length = int(key_length)
|
| 452 |
+
except ValueError:
|
| 453 |
+
key_length = 2048
|
| 454 |
+
# generar los archivos (con fecha para no pisarlo)
|
| 455 |
+
ts = datetime.datetime.now().strftime("%Y%m%d%M%S")
|
| 456 |
+
clave_privada = "clave_privada_%s_%s.key" % (cuit, ts)
|
| 457 |
+
pedido_cert = "pedido_cert_%s_%s.csr" % (cuit, ts)
|
| 458 |
+
print("Longitud clave %s (bits)" % key_length)
|
| 459 |
+
wsaa.CrearClavePrivada(clave_privada, key_length)
|
| 460 |
+
wsaa.CrearPedidoCertificado(cuit, empresa, nombre, pedido_cert)
|
| 461 |
+
print("Se crearon los archivos:")
|
| 462 |
+
print(clave_privada)
|
| 463 |
+
print(pedido_cert)
|
| 464 |
+
# convertir a terminaciΓ³n de linea windows y abrir con bloc de notas
|
| 465 |
+
if sys.platform == "win32":
|
| 466 |
+
txt = open(pedido_cert + ".txt", "wb")
|
| 467 |
+
for linea in open(pedido_cert, "r"):
|
| 468 |
+
txt.write("%s\r\n" % linea)
|
| 469 |
+
txt.close()
|
| 470 |
+
os.startfile(pedido_cert + ".txt")
|
| 471 |
+
else:
|
| 472 |
+
|
| 473 |
+
# Leer argumentos desde la linea de comando (si no viene tomar default)
|
| 474 |
+
args = [arg for arg in sys.argv if arg.startswith("--")]
|
| 475 |
+
argv = [arg for arg in sys.argv if not arg.startswith("--")]
|
| 476 |
+
crt = len(argv) > 1 and argv[1] or CERT
|
| 477 |
+
key = len(argv) > 2 and argv[2] or PRIVATEKEY
|
| 478 |
+
service = len(argv) > 3 and argv[3] or "wsfe"
|
| 479 |
+
ttl = len(argv) > 4 and int(argv[4]) or 36000
|
| 480 |
+
url = len(argv) > 5 and argv[5] or WSAAURL
|
| 481 |
+
wrapper = len(argv) > 6 and argv[6] or None
|
| 482 |
+
cacert = len(argv) > 7 and argv[7] or CACERT
|
| 483 |
+
DEBUG = "--debug" in args or DEBUG
|
| 484 |
+
|
| 485 |
+
print("Usando CRT=%s KEY=%s URL=%s SERVICE=%s TTL=%s" % (crt, key, url, service, ttl), file=sys.stderr)
|
| 486 |
+
|
| 487 |
+
# creo el objeto para comunicarme con el ws
|
| 488 |
+
wsaa = WSAA()
|
| 489 |
+
wsaa.LanzarExcepciones = True
|
| 490 |
+
|
| 491 |
+
print("WSAA Version %s %s" % (WSAA.Version, HOMO), file=sys.stderr)
|
| 492 |
+
|
| 493 |
+
if '--proxy' in args:
|
| 494 |
+
proxy = sys.argv[sys.argv.index("--proxy") + 1]
|
| 495 |
+
print("Usando PROXY:", proxy, file=sys.stderr)
|
| 496 |
+
else:
|
| 497 |
+
proxy = None
|
| 498 |
+
|
| 499 |
+
if '--analizar' in sys.argv:
|
| 500 |
+
wsaa.AnalizarCertificado(crt)
|
| 501 |
+
print(wsaa.Identidad)
|
| 502 |
+
print(wsaa.Caducidad)
|
| 503 |
+
print(wsaa.Emisor)
|
| 504 |
+
print(wsaa.CertX509)
|
| 505 |
+
|
| 506 |
+
ta = wsaa.Autenticar(service, crt, key, url, proxy, wrapper, cacert)
|
| 507 |
+
if not ta:
|
| 508 |
+
if DEBUG:
|
| 509 |
+
print(wsaa.Traceback, file=sys.stderr)
|
| 510 |
+
sys.exit("Excepcion: %s" % wsaa.Excepcion)
|
| 511 |
+
|
| 512 |
+
else:
|
| 513 |
+
print(ta)
|
| 514 |
+
|
| 515 |
+
if wsaa.Excepcion:
|
| 516 |
+
print(wsaa.Excepcion, file=sys.stderr)
|
| 517 |
+
|
| 518 |
+
if DEBUG:
|
| 519 |
+
print("Source:", wsaa.ObtenerTagXml('source'))
|
| 520 |
+
print("UniqueID Time:", wsaa.ObtenerTagXml('uniqueId'))
|
| 521 |
+
print("Generation Time:", wsaa.ObtenerTagXml('generationTime'))
|
| 522 |
+
print("Expiration Time:", wsaa.ObtenerTagXml('expirationTime'))
|
| 523 |
+
print("Expiro?", wsaa.Expirado())
|
| 524 |
+
##import time; time.sleep(10)
|
| 525 |
+
# print "Expiro?", wsaa.Expirado()
|
app/pyafipws/wsbfev1.py
ADDED
|
@@ -0,0 +1,659 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"""MοΏ½dulo para obtener cοΏ½digo de autorizaciοΏ½n electrοΏ½nico (CAE) del web service
|
| 14 |
+
WSBFEv1 de AFIP (Bonos Fiscales electronicos v1.1 - Factura ElectrοΏ½nica RG)
|
| 15 |
+
a fin de gestionar los Bonos en la SecretarοΏ½a de Industria segοΏ½n RG 2557
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
__author__ = "Mariano Reingart (reingart@gmail.com)"
|
| 19 |
+
__copyright__ = "Copyright (C) 2013-2016 Mariano Reingart"
|
| 20 |
+
__license__ = "GPL 3.0"
|
| 21 |
+
__version__ = "1.06g"
|
| 22 |
+
|
| 23 |
+
import datetime
|
| 24 |
+
import decimal
|
| 25 |
+
import os
|
| 26 |
+
import sys
|
| 27 |
+
from .utils import inicializar_y_capturar_excepciones, BaseWS, get_install_dir
|
| 28 |
+
|
| 29 |
+
HOMO = False
|
| 30 |
+
LANZAR_EXCEPCIONES = True # valor por defecto: True
|
| 31 |
+
WSDL = "https://wswhomo.afip.gov.ar/wsbfev1/service.asmx?WSDL"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class WSBFEv1(BaseWS):
|
| 35 |
+
"Interfaz para el WebService de Bono Fiscal ElectrοΏ½nico V1 (FE Bs. Capital)"
|
| 36 |
+
_public_methods_ = ['CrearFactura', 'AgregarItem', 'Authorize', 'GetCMP', 'AgregarOpcional', 'AgregarCmpAsoc',
|
| 37 |
+
'GetParamMon', 'GetParamTipoCbte', 'GetParamUMed',
|
| 38 |
+
'GetParamTipoIVA', 'GetParamNCM', 'GetParamZonas',
|
| 39 |
+
'GetParamTipoDoc',
|
| 40 |
+
'Dummy', 'Conectar', 'GetLastCMP', 'GetLastID',
|
| 41 |
+
'GetParamCtz', 'LoadTestXML',
|
| 42 |
+
'AnalizarXml', 'ObtenerTagXml', 'DebugLog',
|
| 43 |
+
'SetParametros', 'SetTicketAcceso', 'GetParametro',
|
| 44 |
+
'Dummy', 'Conectar', 'SetTicketAcceso']
|
| 45 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 46 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 47 |
+
'XmlRequest', 'XmlResponse', 'Version',
|
| 48 |
+
'Resultado', 'Obs', 'Reproceso', 'FechaCAE',
|
| 49 |
+
'CAE', 'Vencimiento', 'Eventos', 'ErrCode', 'ErrMsg', 'FchVencCAE',
|
| 50 |
+
'Excepcion', 'LanzarExcepciones', 'Traceback', "InstallDir",
|
| 51 |
+
'PuntoVenta', 'CbteNro', 'FechaCbte', 'ImpTotal', 'ImpNeto', 'ImptoLiq',
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
_reg_progid_ = "WSBFEv1"
|
| 55 |
+
_reg_clsid_ = "{EE4ABEE2-76DD-450F-880B-66710AE464D6}"
|
| 56 |
+
|
| 57 |
+
# Variables globales para BaseWS:
|
| 58 |
+
HOMO = HOMO
|
| 59 |
+
WSDL = WSDL
|
| 60 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '')
|
| 61 |
+
factura = None
|
| 62 |
+
|
| 63 |
+
def inicializar(self):
|
| 64 |
+
BaseWS.inicializar(self)
|
| 65 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 66 |
+
self.Resultado = self.Motivo = self.Reproceso = ''
|
| 67 |
+
self.LastID = self.LastCMP = self.CAE = self.Vencimiento = ''
|
| 68 |
+
self.CbteNro = self.FechaCbte = self.PuntoVenta = self.ImpTotal = None
|
| 69 |
+
self.ImpNeto = self.ImptoLiq = None
|
| 70 |
+
self.LanzarExcepciones = LANZAR_EXCEPCIONES
|
| 71 |
+
self.InstallDir = INSTALL_DIR
|
| 72 |
+
self.FechaCAE = self.FchVencCAE = "" # retrocompatibilidad
|
| 73 |
+
|
| 74 |
+
def __analizar_errores(self, ret):
|
| 75 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 76 |
+
if 'BFEErr' in ret:
|
| 77 |
+
errores = [ret['BFEErr']]
|
| 78 |
+
for error in errores:
|
| 79 |
+
self.Errores.append("%s: %s" % (
|
| 80 |
+
error['ErrCode'],
|
| 81 |
+
error.get('ErrMsg', ""),
|
| 82 |
+
))
|
| 83 |
+
self.ErrCode = ' '.join([str(error['ErrCode']) for error in errores])
|
| 84 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 85 |
+
if 'BFEEvents' in ret:
|
| 86 |
+
events = [ret['BFEEvents']]
|
| 87 |
+
self.Eventos = ['%s: %s' % (evt['EventCode'], evt.get('EventMsg', "")) for evt in events]
|
| 88 |
+
|
| 89 |
+
def CrearFactura(self, tipo_doc=80, nro_doc=23111111113,
|
| 90 |
+
zona=0, tipo_cbte=1, punto_vta=1, cbte_nro=0, fecha_cbte=None,
|
| 91 |
+
imp_total=0.0, imp_neto=0.0, impto_liq=0.0,
|
| 92 |
+
imp_tot_conc=0.0, impto_liq_rni=0.00, imp_op_ex=0.00,
|
| 93 |
+
imp_perc=0.00, imp_iibb=0.00, imp_perc_mun=0.00, imp_internos=0.00,
|
| 94 |
+
imp_moneda_id=0, imp_moneda_ctz=1.0, fecha_venc_pago=None, **kwargs):
|
| 95 |
+
"Creo un objeto factura (interna)"
|
| 96 |
+
# Creo una factura para bonos fiscales electrοΏ½nicos
|
| 97 |
+
|
| 98 |
+
fact = {'tipo_cbte': tipo_cbte, 'punto_vta': punto_vta,
|
| 99 |
+
'cbte_nro': cbte_nro, 'fecha_cbte': fecha_cbte, 'zona': zona,
|
| 100 |
+
'tipo_doc': tipo_doc, 'nro_doc': nro_doc,
|
| 101 |
+
'imp_total': imp_total, 'imp_neto': imp_neto,
|
| 102 |
+
'impto_liq': impto_liq, 'impto_liq_rni': impto_liq_rni,
|
| 103 |
+
'imp_op_ex': imp_op_ex, 'imp_tot_conc': imp_tot_conc,
|
| 104 |
+
'imp_perc': imp_perc, 'imp_perc_mun': imp_perc_mun,
|
| 105 |
+
'imp_iibb': imp_iibb, 'imp_internos': imp_internos,
|
| 106 |
+
'imp_moneda_id': imp_moneda_id, 'imp_moneda_ctz': imp_moneda_ctz,
|
| 107 |
+
'fecha_venc_pago': fecha_venc_pago,
|
| 108 |
+
'cbtes_asoc': [],
|
| 109 |
+
'opcionales': [],
|
| 110 |
+
'iva': [],
|
| 111 |
+
'detalles': [],
|
| 112 |
+
}
|
| 113 |
+
self.factura = fact
|
| 114 |
+
return True
|
| 115 |
+
|
| 116 |
+
def AgregarItem(self, ncm, sec, ds, qty, umed, precio, bonif, iva_id, imp_total, **kwargs):
|
| 117 |
+
"Agrego un item a una factura (interna)"
|
| 118 |
+
# ds = unicode(ds, "latin1") # convierto a latin1
|
| 119 |
+
# Nota: no se calcula neto, iva, etc (deben venir calculados!)
|
| 120 |
+
self.factura['detalles'].append({
|
| 121 |
+
'ncm': ncm, 'sec': sec,
|
| 122 |
+
'ds': ds,
|
| 123 |
+
'qty': qty,
|
| 124 |
+
'umed': umed,
|
| 125 |
+
'precio': precio,
|
| 126 |
+
'bonif': bonif,
|
| 127 |
+
'iva_id': iva_id,
|
| 128 |
+
'imp_total': imp_total,
|
| 129 |
+
})
|
| 130 |
+
return True
|
| 131 |
+
|
| 132 |
+
def AgregarCmpAsoc(self, tipo=1, pto_vta=0, nro=0, cuit=None, fecha=None, **kwarg):
|
| 133 |
+
"Agrego un comprobante asociado a una factura (interna)"
|
| 134 |
+
cmp_asoc = {'tipo': tipo, 'pto_vta': pto_vta, 'nro': nro}
|
| 135 |
+
if cuit is not None:
|
| 136 |
+
cmp_asoc['cuit'] = cuit
|
| 137 |
+
if fecha is not None:
|
| 138 |
+
cmp_asoc['fecha'] = fecha
|
| 139 |
+
self.factura['cbtes_asoc'].append(cmp_asoc)
|
| 140 |
+
return True
|
| 141 |
+
|
| 142 |
+
def AgregarOpcional(self, opcional_id=0, valor="", **kwarg):
|
| 143 |
+
"Agrego un dato opcional a una factura (interna)"
|
| 144 |
+
op = { 'opcional_id': opcional_id, 'valor': valor }
|
| 145 |
+
self.factura['opcionales'].append(op)
|
| 146 |
+
return True
|
| 147 |
+
|
| 148 |
+
@inicializar_y_capturar_excepciones
|
| 149 |
+
def Authorize(self, id):
|
| 150 |
+
"Autoriza la factura cargada en memoria"
|
| 151 |
+
f = self.factura
|
| 152 |
+
ret = self.client.BFEAuthorize(
|
| 153 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 154 |
+
Cmp={
|
| 155 |
+
'Id': id,
|
| 156 |
+
'Zona': f['zona'],
|
| 157 |
+
'Fecha_cbte': f['fecha_cbte'],
|
| 158 |
+
'Tipo_cbte': f['tipo_cbte'],
|
| 159 |
+
'Punto_vta': f['punto_vta'],
|
| 160 |
+
'Cbte_nro': f['cbte_nro'],
|
| 161 |
+
'Tipo_doc': f['tipo_doc'], 'Nro_doc': f['nro_doc'],
|
| 162 |
+
'Imp_moneda_Id': f['imp_moneda_id'],
|
| 163 |
+
'Imp_moneda_ctz': f['imp_moneda_ctz'],
|
| 164 |
+
'Imp_total': f['imp_total'],
|
| 165 |
+
'Imp_tot_conc': f['imp_tot_conc'], 'Imp_op_ex': f['imp_op_ex'],
|
| 166 |
+
'Imp_neto': f['imp_neto'], 'Impto_liq': f['impto_liq'],
|
| 167 |
+
'Impto_liq_rni': f['impto_liq_rni'],
|
| 168 |
+
'Imp_perc': f['imp_perc'], 'Imp_perc_mun': f['imp_perc_mun'],
|
| 169 |
+
'Imp_iibb': f['imp_iibb'],
|
| 170 |
+
'Imp_internos': f['imp_internos'],
|
| 171 |
+
'Fecha_vto_pago': f['fecha_venc_pago'],
|
| 172 |
+
'Items': [
|
| 173 |
+
{'Item': {
|
| 174 |
+
'Pro_codigo_ncm': d['ncm'],
|
| 175 |
+
'Pro_codigo_sec': d['sec'],
|
| 176 |
+
'Pro_ds': d['ds'],
|
| 177 |
+
'Pro_qty': d['qty'],
|
| 178 |
+
'Pro_umed': d['umed'],
|
| 179 |
+
'Pro_precio_uni': d['precio'],
|
| 180 |
+
'Imp_bonif': d['bonif'],
|
| 181 |
+
'Imp_total': d['imp_total'],
|
| 182 |
+
'Iva_id': d['iva_id'],
|
| 183 |
+
}} for d in f['detalles']],
|
| 184 |
+
'CbtesAsoc': f['cbtes_asoc'] and [
|
| 185 |
+
{'CbteAsoc': {
|
| 186 |
+
'Tipo_cbte': cbte_asoc['tipo'],
|
| 187 |
+
'Punto_vta': cbte_asoc['pto_vta'],
|
| 188 |
+
'Cbte_nro': cbte_asoc['nro'],
|
| 189 |
+
'Cuit': cbte_asoc.get('cuit'),
|
| 190 |
+
'Fecha_cbte': cbte_asoc.get('fecha'),
|
| 191 |
+
}}
|
| 192 |
+
for cbte_asoc in f['cbtes_asoc']] or None,
|
| 193 |
+
'Opcionales': [
|
| 194 |
+
{'Opcional': {
|
| 195 |
+
'Id': opcional['opcional_id'],
|
| 196 |
+
'Valor': opcional['valor'],
|
| 197 |
+
}} for opcional in f['opcionales']] or None,
|
| 198 |
+
})
|
| 199 |
+
|
| 200 |
+
result = ret['BFEAuthorizeResult']
|
| 201 |
+
self.__analizar_errores(result)
|
| 202 |
+
if 'BFEResultAuth' in result:
|
| 203 |
+
auth = result['BFEResultAuth']
|
| 204 |
+
# Resultado: A: Aceptado, R: Rechazado
|
| 205 |
+
self.Resultado = auth.get('Resultado', "")
|
| 206 |
+
# Obs:
|
| 207 |
+
self.Obs = auth.get('Obs', "")
|
| 208 |
+
self.Reproceso = auth.get('Reproceso', "")
|
| 209 |
+
self.CAE = auth.get('Cae', "")
|
| 210 |
+
self.CbteNro = auth.get('Fch_cbte', "")
|
| 211 |
+
self.ImpTotal = str(auth.get('Imp_total', ''))
|
| 212 |
+
self.ImptoLiq = str(auth.get('Impto_liq', ''))
|
| 213 |
+
self.ImpNeto = str(auth.get('Imp_neto', ''))
|
| 214 |
+
vto = str(auth.get('Fch_venc_Cae', ''))
|
| 215 |
+
self.FchVencCAE = vto
|
| 216 |
+
self.Vencimiento = "%s/%s/%s" % (vto[6:8], vto[4:6], vto[0:4])
|
| 217 |
+
return self.CAE
|
| 218 |
+
|
| 219 |
+
@inicializar_y_capturar_excepciones
|
| 220 |
+
def Dummy(self):
|
| 221 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 222 |
+
result = self.client.BFEDummy()['BFEDummyResult']
|
| 223 |
+
self.__analizar_errores(result)
|
| 224 |
+
self.AppServerStatus = str(result.get('AppServer', ""))
|
| 225 |
+
self.DbServerStatus = str(result.get('DbServer', ""))
|
| 226 |
+
self.AuthServerStatus = str(result.get('AuthServer', ""))
|
| 227 |
+
return True
|
| 228 |
+
|
| 229 |
+
@inicializar_y_capturar_excepciones
|
| 230 |
+
def GetCMP(self, tipo_cbte, punto_vta, cbte_nro):
|
| 231 |
+
"Recuperar los datos completos de un comprobante ya autorizado"
|
| 232 |
+
ret = self.client.BFEGetCMP(
|
| 233 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 234 |
+
Cmp={"Tipo_cbte": tipo_cbte,
|
| 235 |
+
"Punto_vta": punto_vta, "Cbte_nro": cbte_nro})
|
| 236 |
+
result = ret['BFEGetCMPResult']
|
| 237 |
+
self.__analizar_errores(result)
|
| 238 |
+
if 'BFEResultGet' in result:
|
| 239 |
+
resultget = result['BFEResultGet']
|
| 240 |
+
# Obs, cae y fecha cae
|
| 241 |
+
if 'Cae' in resultget:
|
| 242 |
+
self.Obs = resultget['Obs'] and resultget['Obs'].strip(" ") or ''
|
| 243 |
+
self.CAE = resultget['Cae']
|
| 244 |
+
vto = str(resultget['Fch_venc_Cae'])
|
| 245 |
+
self.Vencimiento = "%s/%s/%s" % (vto[6:8], vto[4:6], vto[0:4])
|
| 246 |
+
self.FechaCbte = resultget['Fecha_cbte_orig'] # .strftime("%Y/%m/%d")
|
| 247 |
+
self.FechaCAE = resultget['Fecha_cbte_cae'] # .strftime("%Y/%m/%d")
|
| 248 |
+
self.PuntoVenta = resultget['Punto_vta'] # 4000
|
| 249 |
+
self.Resultado = resultget['Resultado']
|
| 250 |
+
self.CbteNro = resultget['Cbte_nro']
|
| 251 |
+
self.ImpTotal = resultget['Imp_total']
|
| 252 |
+
self.ImptoLiq = resultget['Impto_liq']
|
| 253 |
+
self.ImpNeto = resultget['Imp_neto']
|
| 254 |
+
return self.CAE
|
| 255 |
+
else:
|
| 256 |
+
return 0
|
| 257 |
+
|
| 258 |
+
@inicializar_y_capturar_excepciones
|
| 259 |
+
def GetLastCMP(self, tipo_cbte, punto_vta):
|
| 260 |
+
"Recuperar οΏ½ltimo nοΏ½mero de comprobante emitido"
|
| 261 |
+
ret = self.client.BFEGetLast_CMP(
|
| 262 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit,
|
| 263 |
+
"Tipo_cbte": tipo_cbte,
|
| 264 |
+
"Pto_venta": punto_vta,
|
| 265 |
+
})
|
| 266 |
+
result = ret['BFEGetLast_CMPResult']
|
| 267 |
+
self.__analizar_errores(result)
|
| 268 |
+
if 'BFEResult_LastCMP' in result:
|
| 269 |
+
resultget = result['BFEResult_LastCMP']
|
| 270 |
+
self.CbteNro = resultget.get('Cbte_nro')
|
| 271 |
+
self.FechaCbte = resultget.get('Cbte_fecha') # .strftime("%Y/%m/%d")
|
| 272 |
+
return self.CbteNro
|
| 273 |
+
|
| 274 |
+
@inicializar_y_capturar_excepciones
|
| 275 |
+
def GetLastID(self):
|
| 276 |
+
"Recuperar οΏ½ltimo nοΏ½mero de transacciοΏ½n (ID)"
|
| 277 |
+
ret = self.client.BFEGetLast_ID(
|
| 278 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 279 |
+
result = ret['BFEGetLast_IDResult']
|
| 280 |
+
self.__analizar_errores(result)
|
| 281 |
+
if 'BFEResultGet' in result:
|
| 282 |
+
resultget = result['BFEResultGet']
|
| 283 |
+
return resultget.get('Id')
|
| 284 |
+
|
| 285 |
+
@inicializar_y_capturar_excepciones
|
| 286 |
+
def GetParamUMed(self):
|
| 287 |
+
ret = self.client.BFEGetPARAM_UMed(
|
| 288 |
+
auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 289 |
+
result = ret['BFEGetPARAM_UMedResult']
|
| 290 |
+
self.__analizar_errores(result)
|
| 291 |
+
|
| 292 |
+
umeds = [] # unidades de medida
|
| 293 |
+
for u in result['BFEResultGet']:
|
| 294 |
+
u = u['ClsBFEResponse_UMed']
|
| 295 |
+
try:
|
| 296 |
+
umed = {'id': u.get('Umed_Id'), 'ds': u.get('Umed_Ds'),
|
| 297 |
+
'vig_desde': u.get('Umed_vig_desde'),
|
| 298 |
+
'vig_hasta': u.get('Umed_vig_hasta')}
|
| 299 |
+
except Exception as e:
|
| 300 |
+
print(e)
|
| 301 |
+
if u is None:
|
| 302 |
+
# <ClsFEXResponse_UMed xsi:nil="true"/> WTF!
|
| 303 |
+
umed = {'id': '', 'ds': '', 'vig_desde': '', 'vig_hasta': ''}
|
| 304 |
+
#import pdb; pdb.set_trace()
|
| 305 |
+
# print u
|
| 306 |
+
|
| 307 |
+
umeds.append(umed)
|
| 308 |
+
return ['%(id)s: %(ds)s (%(vig_desde)s - %(vig_hasta)s)' % p for p in umeds]
|
| 309 |
+
|
| 310 |
+
@inicializar_y_capturar_excepciones
|
| 311 |
+
def GetParamMon(self):
|
| 312 |
+
ret = self.client.BFEGetPARAM_MON(
|
| 313 |
+
auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 314 |
+
result = ret['BFEGetPARAM_MONResult']
|
| 315 |
+
self.__analizar_errores(result)
|
| 316 |
+
|
| 317 |
+
mons = [] # unidades de medida
|
| 318 |
+
for m in result['BFEResultGet']:
|
| 319 |
+
m = m['ClsBFEResponse_Mon']
|
| 320 |
+
try:
|
| 321 |
+
mon = {'id': m.get('Mon_Id'), 'ds': m.get('Mon_Ds'),
|
| 322 |
+
'vig_desde': m.get('Mon_vig_desde'),
|
| 323 |
+
'vig_hasta': m.get('Mon_vig_hasta')}
|
| 324 |
+
except Exception as e:
|
| 325 |
+
raise
|
| 326 |
+
if m is None:
|
| 327 |
+
# <ClsFEXResponse_UMed xsi:nil="true"/> WTF!
|
| 328 |
+
mon = {'id': '', 'ds': '', 'vig_desde': '', 'vig_hasta': ''}
|
| 329 |
+
#import pdb; pdb.set_trace()
|
| 330 |
+
# print u
|
| 331 |
+
mons.append(mon)
|
| 332 |
+
return ['%(id)s: %(ds)s (%(vig_desde)s - %(vig_hasta)s)' % p for p in mons]
|
| 333 |
+
|
| 334 |
+
@inicializar_y_capturar_excepciones
|
| 335 |
+
def GetParamTipoIVA(self):
|
| 336 |
+
"Recuperar lista de valores referenciales de tipos de IVA (alοΏ½cuotas)"
|
| 337 |
+
ret = self.client.BFEGetPARAM_Tipo_IVA(
|
| 338 |
+
auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 339 |
+
result = ret['BFEGetPARAM_Tipo_IVAResult']
|
| 340 |
+
self.__analizar_errores(result)
|
| 341 |
+
|
| 342 |
+
ivas = [] # tipos de iva
|
| 343 |
+
for i in result['BFEResultGet']:
|
| 344 |
+
i = i['ClsBFEResponse_Tipo_IVA']
|
| 345 |
+
try:
|
| 346 |
+
iva = {'id': i.get('IVA_Id'), 'ds': i.get('IVA_Ds'),
|
| 347 |
+
'vig_desde': i.get('IVA_vig_desde'),
|
| 348 |
+
'vig_hasta': i.get('IVA_vig_hasta')}
|
| 349 |
+
ivas.append(iva)
|
| 350 |
+
except Exception as e:
|
| 351 |
+
pass
|
| 352 |
+
raise
|
| 353 |
+
return ['%(id)s: %(ds)s (%(vig_desde)s - %(vig_hasta)s)' % p for p in ivas]
|
| 354 |
+
|
| 355 |
+
@inicializar_y_capturar_excepciones
|
| 356 |
+
def GetParamTipoDoc(self):
|
| 357 |
+
"Recuperar lista de valores referenciales de tipos de documentos"
|
| 358 |
+
ret = self.client.BFEGetPARAM_Tipo_doc(
|
| 359 |
+
auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 360 |
+
result = ret['BFEGetPARAM_Tipo_docResult']
|
| 361 |
+
self.__analizar_errores(result)
|
| 362 |
+
|
| 363 |
+
docs = [] # tipos de documentos
|
| 364 |
+
for d in result['BFEResultGet']:
|
| 365 |
+
d = d['ClsBFEResponse_Tipo_doc']
|
| 366 |
+
try:
|
| 367 |
+
doc = {'id': d.get('Doc_Id'), 'ds': d.get('Doc_Ds'),
|
| 368 |
+
'vig_desde': d.get('Doc_vig_desde'),
|
| 369 |
+
'vig_hasta': d.get('Doc_vig_hasta')}
|
| 370 |
+
docs.append(doc)
|
| 371 |
+
except Exception as e:
|
| 372 |
+
pass
|
| 373 |
+
raise
|
| 374 |
+
return ['%(id)s: %(ds)s (%(vig_desde)s - %(vig_hasta)s)' % d for d in docs]
|
| 375 |
+
|
| 376 |
+
def GetParamTipoCbte(self):
|
| 377 |
+
"Recuperar lista de valores referenciales de Tipos de Comprobantes"
|
| 378 |
+
ret = self.client.BFEGetPARAM_Tipo_Cbte(
|
| 379 |
+
auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 380 |
+
result = ret['BFEGetPARAM_Tipo_CbteResult']
|
| 381 |
+
self.__analizar_errores(result)
|
| 382 |
+
|
| 383 |
+
tipos = [] # tipos de comprobantes
|
| 384 |
+
for t in result['BFEResultGet']:
|
| 385 |
+
t = t['ClsBFEResponse_Tipo_Cbte']
|
| 386 |
+
try:
|
| 387 |
+
tipo = {'id': t.get('Cbte_Id'), 'ds': t.get('Cbte_Ds'),
|
| 388 |
+
'vig_desde': t.get('Cbte_vig_desde'),
|
| 389 |
+
'vig_hasta': t.get('Cbte_vig_hasta')}
|
| 390 |
+
tipos.append(tipo)
|
| 391 |
+
except Exception as e:
|
| 392 |
+
pass
|
| 393 |
+
return ['%(id)s: %(ds)s (%(vig_desde)s - %(vig_hasta)s)' % p for p in tipos]
|
| 394 |
+
|
| 395 |
+
def GetParamNCM(self):
|
| 396 |
+
"Recuperar lista de valores referenciales de cοΏ½digos del Nomenclador ComοΏ½n del Mercosur"
|
| 397 |
+
ret = self.client.BFEGetPARAM_NCM(
|
| 398 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 399 |
+
result = ret['BFEGetPARAM_NCMResult']
|
| 400 |
+
self.__analizar_errores(result)
|
| 401 |
+
|
| 402 |
+
ncms = [] # nomenclador comun del mercosur
|
| 403 |
+
for n in result['BFEResultGet']:
|
| 404 |
+
n = n['ClsBFEResponse_NCM']
|
| 405 |
+
try:
|
| 406 |
+
ncm = {'id': n.get('NCM_Codigo'), 'ds': n.get('NCM_Ds'),
|
| 407 |
+
'vig_desde': n.get('NCM_vig_desde'),
|
| 408 |
+
'vig_hasta': n.get('NCM_vig_hasta')}
|
| 409 |
+
ncms.append(ncm)
|
| 410 |
+
except Exception as e:
|
| 411 |
+
pass
|
| 412 |
+
return ['%(id)s: %(ds)s (%(vig_desde)s - %(vig_hasta)s)' % p for p in ncms]
|
| 413 |
+
|
| 414 |
+
def GetParamZonas(self):
|
| 415 |
+
"Recuperar lista de valores referenciales de Zonas"
|
| 416 |
+
ret = self.client.BFEGetPARAM_Zonas(
|
| 417 |
+
auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 418 |
+
result = ret['BFEGetPARAM_ZonasResult']
|
| 419 |
+
self.__analizar_errores(result)
|
| 420 |
+
|
| 421 |
+
zonas = [] # zonas
|
| 422 |
+
for z in result['BFEResultGet']:
|
| 423 |
+
z = z['ClsBFEResponse_Zon']
|
| 424 |
+
try:
|
| 425 |
+
zona = {'id': z.get('Zon_Id'), 'ds': z.get('Zon_Ds'),
|
| 426 |
+
'vig_desde': z.get('Zon_vig_desde'),
|
| 427 |
+
'vig_hasta': z.get('Zon_vig_hasta')}
|
| 428 |
+
zonas.append(zona)
|
| 429 |
+
except Exception as e:
|
| 430 |
+
pass
|
| 431 |
+
return ['%(id)s: %(ds)s (%(vig_desde)s - %(vig_hasta)s)' % p for p in zonas]
|
| 432 |
+
|
| 433 |
+
@inicializar_y_capturar_excepciones
|
| 434 |
+
def GetParamCtz(self, moneda_id):
|
| 435 |
+
"Recuperador de cotizaciοΏ½n de moneda"
|
| 436 |
+
ret = self.client.BFEGetPARAM_Ctz(
|
| 437 |
+
auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 438 |
+
MonId=moneda_id,
|
| 439 |
+
)
|
| 440 |
+
self.__analizar_errores(ret['BFEGetPARAM_CtzResult'])
|
| 441 |
+
res = ret['BFEGetPARAM_CtzResult'].get('BFEResultGet')
|
| 442 |
+
if res:
|
| 443 |
+
ctz = str(res.get('Mon_ctz', ""))
|
| 444 |
+
else:
|
| 445 |
+
ctz = ''
|
| 446 |
+
return ctz
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
class WSBFE(WSBFEv1):
|
| 450 |
+
"Wrapper para retrocompatibilidad con WSBFE"
|
| 451 |
+
|
| 452 |
+
_reg_progid_ = "WSBFE"
|
| 453 |
+
_reg_clsid_ = "{02CBC6DA-455D-4EE6-8302-411D13253CBF}"
|
| 454 |
+
|
| 455 |
+
def __init__(self):
|
| 456 |
+
WSBFEv1.__init__(self)
|
| 457 |
+
self.Version = "%s %s WSBFEv1" % (__version__, HOMO and 'HomologaciοΏ½n' or '')
|
| 458 |
+
|
| 459 |
+
def Conectar(self, cache=None, url="", **kwargs):
|
| 460 |
+
# Ajustar URL de V0 a V1:
|
| 461 |
+
if url in ("https://wswhomo.afip.gov.ar/wsfex/service.asmx",
|
| 462 |
+
"http://wswhomo.afip.gov.ar/WSFEX/service.asmx"):
|
| 463 |
+
url = "https://wswhomo.afip.gov.ar/wsbfev1/service.asmx"
|
| 464 |
+
elif url in ("https://servicios1.afip.gov.ar/wsfex/service.asmx",
|
| 465 |
+
"http://servicios1.afip.gov.ar/WSFEX/service.asmx"):
|
| 466 |
+
url = "https://servicios1.afip.gov.ar/wsbfev1/service.asmx"
|
| 467 |
+
return WSBFEv1.Conectar(self, cache=cache, wsdl=url, **kwargs)
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 471 |
+
INSTALL_DIR = WSBFEv1.InstallDir = get_install_dir()
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
def p_assert_eq(a, b):
|
| 475 |
+
print(a, a == b and '==' or '!=', b)
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
if __name__ == "__main__":
|
| 479 |
+
|
| 480 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 481 |
+
import win32com.server.register
|
| 482 |
+
win32com.server.register.UseCommandLine(WSBFEv1)
|
| 483 |
+
if '--wsbfe' in sys.argv:
|
| 484 |
+
win32com.server.register.UseCommandLine(WSBFE)
|
| 485 |
+
else:
|
| 486 |
+
|
| 487 |
+
# Crear objeto interface Web Service de Factura ElectrοΏ½nica de ExportaciοΏ½n
|
| 488 |
+
wsbfev1 = WSBFEv1()
|
| 489 |
+
# Setear token y sing de autorizaciοΏ½n (pasos previos)
|
| 490 |
+
|
| 491 |
+
# obteniendo el TA para pruebas
|
| 492 |
+
from .wsaa import WSAA
|
| 493 |
+
ta = WSAA().Autenticar("wsbfe", "reingart.crt", "reingart.key")
|
| 494 |
+
wsbfev1.SetTicketAcceso(ta)
|
| 495 |
+
|
| 496 |
+
# CUIT del emisor (debe estar registrado en la AFIP)
|
| 497 |
+
wsbfev1.Cuit = "20267565393"
|
| 498 |
+
|
| 499 |
+
# Conectar al Servicio Web de FacturaciοΏ½n (homologaciοΏ½n)
|
| 500 |
+
wsdl = "http://wswhomo.afip.gov.ar/WSBFEv1/service.asmx"
|
| 501 |
+
cache = proxy = ""
|
| 502 |
+
wrapper = "httplib2"
|
| 503 |
+
cacert = open("conf/afip_ca_info.crt").read()
|
| 504 |
+
ok = wsbfev1.Conectar(cache, wsdl, proxy, wrapper, cacert)
|
| 505 |
+
|
| 506 |
+
if '--dummy' in sys.argv:
|
| 507 |
+
#wsbfev1.LanzarExcepciones = False
|
| 508 |
+
print(wsbfev1.Dummy())
|
| 509 |
+
print("AppServerStatus", wsbfev1.AppServerStatus)
|
| 510 |
+
print("DbServerStatus", wsbfev1.DbServerStatus)
|
| 511 |
+
print("AuthServerStatus", wsbfev1.AuthServerStatus)
|
| 512 |
+
|
| 513 |
+
if "--prueba" in sys.argv:
|
| 514 |
+
try:
|
| 515 |
+
# Establezco los valores de la factura a autorizar:
|
| 516 |
+
tipo_cbte = '--nc' in sys.argv and 3 or 201 # FC/NC Expo (ver tabla de parΓ‘metros)
|
| 517 |
+
punto_vta = 5
|
| 518 |
+
tipo_doc = 80
|
| 519 |
+
nro_doc = 23111111113
|
| 520 |
+
zona = 0
|
| 521 |
+
# Obtengo el οΏ½ltimo nοΏ½mero de comprobante y le agrego 1
|
| 522 |
+
cbte_nro = int(wsbfev1.GetLastCMP(tipo_cbte, punto_vta)) + 1
|
| 523 |
+
fecha_cbte = datetime.datetime.now().strftime("%Y%m%d")
|
| 524 |
+
imp_moneda_id = "PES" # (ver tabla de parοΏ½metros)
|
| 525 |
+
imp_moneda_ctz = 1
|
| 526 |
+
imp_neto = "390.00"
|
| 527 |
+
impto_liq = "81.90" # 21% IVA
|
| 528 |
+
impto_liq_rni = imp_tot_conc = imp_op_ex = "0.00"
|
| 529 |
+
imp_perc = imp_iibb = imp_perc_mun = imp_internos = "0.00"
|
| 530 |
+
imp_total = "471.90"
|
| 531 |
+
fecha_venc_pago = fecha_cbte if "--fce" in sys.argv else None
|
| 532 |
+
|
| 533 |
+
# Creo una factura (internamente, no se llama al WebService):
|
| 534 |
+
ok = wsbfev1.CrearFactura(tipo_doc, nro_doc,
|
| 535 |
+
zona, tipo_cbte, punto_vta, cbte_nro, fecha_cbte,
|
| 536 |
+
imp_total, imp_neto, impto_liq,
|
| 537 |
+
imp_tot_conc, impto_liq_rni, imp_op_ex,
|
| 538 |
+
imp_perc, imp_iibb, imp_perc_mun, imp_internos,
|
| 539 |
+
imp_moneda_id, imp_moneda_ctz, fecha_venc_pago)
|
| 540 |
+
|
| 541 |
+
# Agrego un item:
|
| 542 |
+
ncm = '7308.10.00'
|
| 543 |
+
sec = ''
|
| 544 |
+
umed = 7 # unidades
|
| 545 |
+
ds = 'prueba Anafe economico'
|
| 546 |
+
qty = "2.00"
|
| 547 |
+
precio = "100.00"
|
| 548 |
+
bonif = "0.00"
|
| 549 |
+
iva_id = 5
|
| 550 |
+
imp_total = "242.00"
|
| 551 |
+
ok = wsbfev1.AgregarItem(ncm, sec, ds, qty, umed, precio, bonif, iva_id, imp_total)
|
| 552 |
+
|
| 553 |
+
# Agrego otro item:
|
| 554 |
+
ncm = '7308.20.00'
|
| 555 |
+
sec = ''
|
| 556 |
+
umed = 7 # unidades
|
| 557 |
+
ds = 'prueba 2'
|
| 558 |
+
qty = "4.00"
|
| 559 |
+
precio = "50.00"
|
| 560 |
+
bonif = "10.00"
|
| 561 |
+
iva_id = 5
|
| 562 |
+
imp_total = "229.90"
|
| 563 |
+
ok = wsbfev1.AgregarItem(ncm, sec, ds, qty, umed, precio, bonif, iva_id, imp_total)
|
| 564 |
+
|
| 565 |
+
# comprobantes asociados (notas de crΓ©dito / dΓ©bito)
|
| 566 |
+
if True:
|
| 567 |
+
tipo = 91
|
| 568 |
+
pto_vta = 4001
|
| 569 |
+
nro = 1
|
| 570 |
+
cuit = "20267565393"
|
| 571 |
+
# obligatorio en Factura de CrΓ©dito ElectrΓ³nica MiPyMEs (FCE):
|
| 572 |
+
fecha_cbte = fecha_cbte if "--fce" in sys.argv else None
|
| 573 |
+
wsbfev1.AgregarCmpAsoc(tipo, pto_vta, nro, cuit, fecha_cbte)
|
| 574 |
+
|
| 575 |
+
# datos de Factura de CrΓ©dito ElectrΓ³nica MiPyMEs (FCE):
|
| 576 |
+
if '--fce' in sys.argv:
|
| 577 |
+
wsbfev1.AgregarOpcional(2101, "2850590940090418135201") # CBU
|
| 578 |
+
wsbfev1.AgregarOpcional(2102, "pyafipws") # alias
|
| 579 |
+
if tipo_cbte in (203, 208, 213):
|
| 580 |
+
wsbfev1.AgregarOpcional(22, "S") # AnulaciΓ³n
|
| 581 |
+
|
| 582 |
+
# id = "99000000000100" # nοΏ½mero propio de transacciοΏ½n
|
| 583 |
+
# obtengo el οΏ½ltimo ID y le adiciono 1
|
| 584 |
+
# (advertencia: evitar overflow y almacenar!)
|
| 585 |
+
id = int(wsbfev1.GetLastID()) + 1
|
| 586 |
+
|
| 587 |
+
# Llamo al WebService de AutorizaciοΏ½n para obtener el CAE
|
| 588 |
+
cae = wsbfev1.Authorize(id)
|
| 589 |
+
|
| 590 |
+
print("Comprobante", tipo_cbte, wsbfev1.CbteNro)
|
| 591 |
+
print("Resultado", wsbfev1.Resultado)
|
| 592 |
+
print("CAE", wsbfev1.CAE)
|
| 593 |
+
print("Vencimiento", wsbfev1.Vencimiento)
|
| 594 |
+
|
| 595 |
+
if wsbfev1.Resultado and False:
|
| 596 |
+
print(wsbfev1.client.help("FEXGetCMP").encode("latin1"))
|
| 597 |
+
wsbfev1.GetCMP(tipo_cbte, punto_vta, cbte_nro)
|
| 598 |
+
print("CAE consulta", wsbfev1.CAE, wsbfev1.CAE == cae)
|
| 599 |
+
print("NRO consulta", wsbfev1.CbteNro, wsbfev1.CbteNro == cbte_nro)
|
| 600 |
+
print("TOTAL consulta", wsbfev1.ImpTotal, wsbfev1.ImpTotal == imp_total)
|
| 601 |
+
|
| 602 |
+
except Exception as e:
|
| 603 |
+
print(wsbfev1.XmlRequest)
|
| 604 |
+
print(wsbfev1.XmlResponse)
|
| 605 |
+
print(wsbfev1.ErrCode)
|
| 606 |
+
print(wsbfev1.ErrMsg)
|
| 607 |
+
print(wsbfev1.Excepcion)
|
| 608 |
+
print(wsbfev1.Traceback)
|
| 609 |
+
raise
|
| 610 |
+
|
| 611 |
+
if "--get" in sys.argv:
|
| 612 |
+
tipo_cbte = 1
|
| 613 |
+
punto_vta = 5
|
| 614 |
+
cbte_nro = wsbfev1.GetLastCMP(tipo_cbte, punto_vta)
|
| 615 |
+
|
| 616 |
+
wsbfev1.GetCMP(tipo_cbte, punto_vta, cbte_nro)
|
| 617 |
+
|
| 618 |
+
print("FechaCbte = ", wsbfev1.FechaCbte)
|
| 619 |
+
print("CbteNro = ", wsbfev1.CbteNro)
|
| 620 |
+
print("PuntoVenta = ", wsbfev1.PuntoVenta)
|
| 621 |
+
print("ImpTotal =", wsbfev1.ImpTotal)
|
| 622 |
+
print("CAE = ", wsbfev1.CAE)
|
| 623 |
+
print("Vencimiento = ", wsbfev1.Vencimiento)
|
| 624 |
+
|
| 625 |
+
wsbfev1.AnalizarXml("XmlResponse")
|
| 626 |
+
p_assert_eq(wsbfev1.ObtenerTagXml('Cae'), str(wsbfev1.CAE))
|
| 627 |
+
p_assert_eq(wsbfev1.ObtenerTagXml('Fecha_cbte_orig'), wsbfev1.FechaCbte)
|
| 628 |
+
p_assert_eq(wsbfev1.ObtenerTagXml('Imp_moneda_Id'), "PES")
|
| 629 |
+
p_assert_eq(wsbfev1.ObtenerTagXml('Imp_moneda_ctz'), "1")
|
| 630 |
+
p_assert_eq(wsbfev1.ObtenerTagXml('Items', 'Item', 1, 'Pro_ds'), "prueba 2")
|
| 631 |
+
|
| 632 |
+
if "--params" in sys.argv:
|
| 633 |
+
import codecs
|
| 634 |
+
import locale
|
| 635 |
+
sys.stdout = codecs.getwriter('latin1')(sys.stdout)
|
| 636 |
+
|
| 637 |
+
print("=== Tipos de Comprobante ===")
|
| 638 |
+
print('\n'.join(wsbfev1.GetParamTipoCbte()))
|
| 639 |
+
|
| 640 |
+
print("=== Zonas ===")
|
| 641 |
+
print('\n'.join(wsbfev1.GetParamZonas()))
|
| 642 |
+
|
| 643 |
+
print("=== Monedas ===")
|
| 644 |
+
print('\n'.join(wsbfev1.GetParamMon()))
|
| 645 |
+
|
| 646 |
+
print("=== Tipos de Documentos ===")
|
| 647 |
+
print('\n'.join(wsbfev1.GetParamTipoDoc()))
|
| 648 |
+
|
| 649 |
+
print("=== Tipos de IVA ===")
|
| 650 |
+
print('\n'.join(wsbfev1.GetParamTipoIVA()))
|
| 651 |
+
|
| 652 |
+
print("=== Unidades de medida ===")
|
| 653 |
+
print('\n'.join(wsbfev1.GetParamUMed()))
|
| 654 |
+
|
| 655 |
+
print("=== CοΏ½digos NCM ===")
|
| 656 |
+
print('\n'.join(wsbfev1.GetParamNCM()))
|
| 657 |
+
|
| 658 |
+
if "--ctz" in sys.argv:
|
| 659 |
+
print(wsbfev1.GetParamCtz('DOL'))
|
app/pyafipws/wscdc.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"MοΏ½dulo para utilizar el servicio web ConstataciοΏ½n de Comprobantes de AFIP"
|
| 14 |
+
|
| 15 |
+
# InformaciοΏ½n adicional y documentaciοΏ½n:
|
| 16 |
+
# http://www.sistemasagiles.com.ar/trac/wiki/ConstatacionComprobantes
|
| 17 |
+
|
| 18 |
+
__author__ = "Mariano Reingart (reingart@gmail.com)"
|
| 19 |
+
__copyright__ = "Copyright (C) 2013-2015 Mariano Reingart"
|
| 20 |
+
__license__ = "GPL 3.0"
|
| 21 |
+
__version__ = "1.02e"
|
| 22 |
+
|
| 23 |
+
import sys
|
| 24 |
+
import os
|
| 25 |
+
import time
|
| 26 |
+
from configparser import SafeConfigParser
|
| 27 |
+
from .utils import inicializar_y_capturar_excepciones, BaseWS, get_install_dir
|
| 28 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Constantes (si se usa el script de linea de comandos)
|
| 32 |
+
WSDL = "https://wswhomo.afip.gov.ar/WSCDC/service.asmx?WSDL"
|
| 33 |
+
HOMO = False
|
| 34 |
+
CONFIG_FILE = "rece.ini"
|
| 35 |
+
|
| 36 |
+
# No deberοΏ½a ser necesario modificar nada despues de esta linea
|
| 37 |
+
|
| 38 |
+
# definiciοΏ½n del formato del archivo de intercambio (sοΏ½lo para linea de comandos):
|
| 39 |
+
|
| 40 |
+
ENCABEZADO = [
|
| 41 |
+
('tipo_reg', 1, A, "0: encabezado"),
|
| 42 |
+
('cbte_modo', 4, A, "Modalidad de autorizaciοΏ½n (CAI, CAE, CAEA)"),
|
| 43 |
+
('cuit_emisor', 11, A, "CUIT del emisor del comprobante"),
|
| 44 |
+
('pto_vta', 4, N, "Punto de Venta del comprobante"),
|
| 45 |
+
('cbte_tipo', 3, N, "Tipo de comprobante"),
|
| 46 |
+
('cbte_nro', 8, N, "NοΏ½mero de comprobante"),
|
| 47 |
+
('cbte_fch', 8, A, "Fecha en formato AAAAMMDD"),
|
| 48 |
+
('imp_total', 15, I, "Importe total Double (13 + 2)"),
|
| 49 |
+
('cod_autorizacion', 14, A, "NοΏ½mero de CAI, CAE, CAEA"),
|
| 50 |
+
('doc_tipo_receptor', 2, A, "Tipo de documento del receptor"),
|
| 51 |
+
('doc_nro_receptor', 20, A, "NοΏ½ de documento del receptor"),
|
| 52 |
+
# campos devueltos por AFIP (respuesta)
|
| 53 |
+
('resultado', 1, A, "Resultado (A: Aprobado, O: Observado, R: rechazado)"),
|
| 54 |
+
('fch_proceso', 14, A, "Fecha y hora de procesamiento"),
|
| 55 |
+
]
|
| 56 |
+
|
| 57 |
+
OBSERVACION = [
|
| 58 |
+
('tipo_reg', 1, A, "O: observaciones devueltas por AFIP"),
|
| 59 |
+
('code', 5, N, "CοΏ½digo de ObservaciοΏ½n / Error / Evento"),
|
| 60 |
+
('msg', 255, A, "Mensaje"),
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
EVENTO = ERROR = OBSERVACION # misma estructura, cambia tipo de registro
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class WSCDC(BaseWS):
|
| 67 |
+
"Interfaz para el WebService de ConstataciοΏ½n de Comprobantes"
|
| 68 |
+
_public_methods_ = ['Conectar', 'SetTicketAcceso', 'DebugLog',
|
| 69 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 70 |
+
'ConstatarComprobante', 'Dummy',
|
| 71 |
+
'ConsultarModalidadComprobantes',
|
| 72 |
+
'ConsultarTipoComprobantes',
|
| 73 |
+
'ConsultarTipoDocumentos', 'ConsultarTipoOpcionales',
|
| 74 |
+
'SetParametros', 'SetParametro', 'GetParametro',
|
| 75 |
+
]
|
| 76 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit', 'ExpirationTime', 'Version',
|
| 77 |
+
'XmlRequest', 'XmlResponse', 'Observaciones', 'Errores',
|
| 78 |
+
'InstallDir', 'Traceback', 'Excepcion', 'ErrMsg', 'Obs',
|
| 79 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 80 |
+
'Resultado', 'FchProceso', 'Observaciones', 'Obs',
|
| 81 |
+
'FechaCbte', 'CbteNro', 'PuntoVenta', 'ImpTotal',
|
| 82 |
+
'EmisionTipo', 'CAE', 'CAEA', 'CAI',
|
| 83 |
+
'DocTipo', 'DocNro',
|
| 84 |
+
'SoapFault', 'LanzarExcepciones',
|
| 85 |
+
]
|
| 86 |
+
_readonly_attrs_ = _public_attrs_[3:-1]
|
| 87 |
+
_reg_progid_ = "WSCDC"
|
| 88 |
+
_reg_clsid_ = "{D1B97BDD-A78C-4D51-8999-1D9A5034EC10}"
|
| 89 |
+
|
| 90 |
+
# Variables globales para BaseWS:
|
| 91 |
+
HOMO = HOMO
|
| 92 |
+
WSDL = WSDL
|
| 93 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '')
|
| 94 |
+
|
| 95 |
+
def inicializar(self):
|
| 96 |
+
BaseWS.inicializar(self)
|
| 97 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 98 |
+
self.Resultado = self.EmisionTipo = ""
|
| 99 |
+
self.CAI = self.CAE = self.CAEA = self.Vencimiento = ''
|
| 100 |
+
self.CbteNro = self.PuntoVenta = self.ImpTotal = None
|
| 101 |
+
|
| 102 |
+
def __analizar_errores(self, ret):
|
| 103 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 104 |
+
if 'Errors' in ret:
|
| 105 |
+
errores = ret['Errors']
|
| 106 |
+
for error in errores:
|
| 107 |
+
self.Errores.append("%s: %s" % (
|
| 108 |
+
error['Err']['Code'],
|
| 109 |
+
error['Err']['Msg'],
|
| 110 |
+
))
|
| 111 |
+
self.errores = [
|
| 112 |
+
{'code': err['Err']['Code'],
|
| 113 |
+
'msg': err['Err']['Msg'].replace("\n", "")
|
| 114 |
+
.replace("\r", "")}
|
| 115 |
+
for err in errores]
|
| 116 |
+
self.ErrCode = ' '.join([str(error['Err']['Code']) for error in errores])
|
| 117 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 118 |
+
|
| 119 |
+
@inicializar_y_capturar_excepciones
|
| 120 |
+
def Dummy(self):
|
| 121 |
+
"MοΏ½todo Dummy para verificaciοΏ½n de funcionamiento de infraestructura"
|
| 122 |
+
result = self.client.ComprobanteDummy()['ComprobanteDummyResult']
|
| 123 |
+
self.AppServerStatus = result['AppServer']
|
| 124 |
+
self.DbServerStatus = result['DbServer']
|
| 125 |
+
self.AuthServerStatus = result['AuthServer']
|
| 126 |
+
self.__analizar_errores(result)
|
| 127 |
+
return True
|
| 128 |
+
|
| 129 |
+
@inicializar_y_capturar_excepciones
|
| 130 |
+
def ConstatarComprobante(self, cbte_modo, cuit_emisor, pto_vta, cbte_tipo,
|
| 131 |
+
cbte_nro, cbte_fch, imp_total, cod_autorizacion,
|
| 132 |
+
doc_tipo_receptor=None, doc_nro_receptor=None,
|
| 133 |
+
**kwargs):
|
| 134 |
+
"MοΏ½todo de ConstataciοΏ½n de Comprobantes"
|
| 135 |
+
response = self.client.ComprobanteConstatar(
|
| 136 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 137 |
+
CmpReq={
|
| 138 |
+
'CbteModo': cbte_modo,
|
| 139 |
+
'CuitEmisor': cuit_emisor,
|
| 140 |
+
'PtoVta': pto_vta,
|
| 141 |
+
'CbteTipo': cbte_tipo,
|
| 142 |
+
'CbteNro': cbte_nro,
|
| 143 |
+
'CbteFch': cbte_fch,
|
| 144 |
+
'ImpTotal': imp_total,
|
| 145 |
+
'CodAutorizacion': cod_autorizacion,
|
| 146 |
+
'DocTipoReceptor': doc_tipo_receptor,
|
| 147 |
+
'DocNroReceptor': doc_nro_receptor,
|
| 148 |
+
}
|
| 149 |
+
)
|
| 150 |
+
result = response['ComprobanteConstatarResult']
|
| 151 |
+
self.__analizar_errores(result)
|
| 152 |
+
if 'CmpResp' in result:
|
| 153 |
+
resp = result['CmpResp']
|
| 154 |
+
self.Resultado = result['Resultado']
|
| 155 |
+
self.FchProceso = result.get('FchProceso', "")
|
| 156 |
+
self.observaciones = []
|
| 157 |
+
for obs in result.get('Observaciones', []):
|
| 158 |
+
self.Observaciones.append("%(Code)s: %(Msg)s" % (obs['Obs']))
|
| 159 |
+
self.observaciones.append({
|
| 160 |
+
'code': obs['Obs']['Code'],
|
| 161 |
+
'msg': obs['Obs']['Msg'].replace("\n", "")
|
| 162 |
+
.replace("\r", "")})
|
| 163 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 164 |
+
self.FechaCbte = resp.get('CbteFch', "") # .strftime("%Y/%m/%d")
|
| 165 |
+
self.CbteNro = resp.get('CbteNro', 0) # 1L
|
| 166 |
+
self.PuntoVenta = resp.get('PtoVta', 0) # 4000
|
| 167 |
+
self.ImpTotal = str(resp['ImpTotal'])
|
| 168 |
+
self.EmisionTipo = resp['CbteModo']
|
| 169 |
+
self.DocTipo = resp.get('DocTipoReceptor', '')
|
| 170 |
+
self.DocNro = resp.get('DocNroReceptor', '')
|
| 171 |
+
cod_aut = str(resp.get('CodAutorizacion', "")) # 60423794871430L
|
| 172 |
+
if self.EmisionTipo == 'CAE':
|
| 173 |
+
self.CAE = cod_aut
|
| 174 |
+
elif self.EmisionTipo == 'CAEA':
|
| 175 |
+
self.CAEA = cod_aut
|
| 176 |
+
elif self.EmisionTipo == 'CAI':
|
| 177 |
+
self.CAI = cod_aut
|
| 178 |
+
return True
|
| 179 |
+
|
| 180 |
+
@inicializar_y_capturar_excepciones
|
| 181 |
+
def ConsultarModalidadComprobantes(self, sep="|"):
|
| 182 |
+
"Recuperador de modalidades de autorizaciοΏ½n de comprobantes"
|
| 183 |
+
response = self.client.ComprobantesModalidadConsultar(
|
| 184 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 185 |
+
)
|
| 186 |
+
result = response['ComprobantesModalidadConsultarResult']
|
| 187 |
+
self.__analizar_errores(result)
|
| 188 |
+
return [("\t%(Cod)s\t%(Desc)s\t" % p['FacModTipo']).replace("\t", sep)
|
| 189 |
+
for p in result['ResultGet']]
|
| 190 |
+
|
| 191 |
+
@inicializar_y_capturar_excepciones
|
| 192 |
+
def ConsultarTipoComprobantes(self, sep="|"):
|
| 193 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de comprobante"
|
| 194 |
+
response = self.client.ComprobantesTipoConsultar(
|
| 195 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 196 |
+
)
|
| 197 |
+
result = response['ComprobantesTipoConsultarResult']
|
| 198 |
+
self.__analizar_errores(result)
|
| 199 |
+
return [("\t%(Id)s\t%(Desc)s\t" % p['CbteTipo']).replace("\t", sep)
|
| 200 |
+
for p in result['ResultGet']]
|
| 201 |
+
|
| 202 |
+
@inicializar_y_capturar_excepciones
|
| 203 |
+
def ConsultarTipoDocumentos(self, sep="|"):
|
| 204 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de Documentos"
|
| 205 |
+
response = self.client.DocumentosTipoConsultar(
|
| 206 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 207 |
+
)
|
| 208 |
+
result = response['DocumentosTipoConsultarResult']
|
| 209 |
+
self.__analizar_errores(result)
|
| 210 |
+
return [("\t%(Id)s\t%(Desc)s\t" % p['DocTipo']).replace("\t", sep)
|
| 211 |
+
for p in result['ResultGet']]
|
| 212 |
+
|
| 213 |
+
@inicializar_y_capturar_excepciones
|
| 214 |
+
def ConsultarTipoOpcionales(self, sep="|"):
|
| 215 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de datos Opcionales"
|
| 216 |
+
response = self.client.OpcionalesTipoConsultar(
|
| 217 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 218 |
+
)
|
| 219 |
+
result = response['OpcionalesTipoConsultarResult']
|
| 220 |
+
res = result['ResultGet'] if 'ResultGet' in result else []
|
| 221 |
+
self.__analizar_errores(result)
|
| 222 |
+
return [("\t%(Id)s\t%(Desc)s\t" % p['OpcionalTipo']).replace("\t", sep)
|
| 223 |
+
for p in res]
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 227 |
+
INSTALL_DIR = WSCDC.InstallDir = get_install_dir()
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def escribir_archivo(dic, nombre_archivo, agrega=True):
|
| 231 |
+
archivo = open(nombre_archivo, agrega and "a" or "w")
|
| 232 |
+
formatos = [('Encabezado', ENCABEZADO, [dic], 0),
|
| 233 |
+
('Observacion', OBSERVACION, dic.get('observaciones', []), 'O'),
|
| 234 |
+
('Eventos', ERROR, dic.get('eventos', []), 'V'),
|
| 235 |
+
('Error', ERROR, dic.get('errores', []), 'E'),
|
| 236 |
+
]
|
| 237 |
+
if '--json' in sys.argv:
|
| 238 |
+
json.dump(dic, archivo, sort_keys=True, indent=4)
|
| 239 |
+
elif '--dbf' in sys.argv:
|
| 240 |
+
guardar_dbf(formatos, agrega, conf_dbf)
|
| 241 |
+
else:
|
| 242 |
+
for nombre, formato, registros, tipo_reg in formatos:
|
| 243 |
+
for it in registros:
|
| 244 |
+
it['tipo_reg'] = tipo_reg
|
| 245 |
+
archivo.write(escribir(it, formato))
|
| 246 |
+
archivo.close()
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def leer_archivo(nombre_archivo):
|
| 250 |
+
archivo = open(nombre_archivo, "r")
|
| 251 |
+
if '--json' in sys.argv:
|
| 252 |
+
dic = json.load(archivo)
|
| 253 |
+
elif '--dbf' in sys.argv:
|
| 254 |
+
dic = {}
|
| 255 |
+
formatos = [('Encabezado', ENCABEZADO, dic),
|
| 256 |
+
]
|
| 257 |
+
leer_dbf(formatos, conf_dbf)
|
| 258 |
+
else:
|
| 259 |
+
dic = {}
|
| 260 |
+
for linea in archivo:
|
| 261 |
+
if str(linea[0]) == '0':
|
| 262 |
+
d = leer(linea, ENCABEZADO)
|
| 263 |
+
dic.update(d)
|
| 264 |
+
else:
|
| 265 |
+
print("Tipo de registro incorrecto:", linea[0])
|
| 266 |
+
archivo.close()
|
| 267 |
+
|
| 268 |
+
if not 'cod_autorizacion' in dic:
|
| 269 |
+
raise RuntimeError("Archivo de entrada invalido, revise campos y lineas en blanco")
|
| 270 |
+
|
| 271 |
+
return dic
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def main():
|
| 275 |
+
"Funcion principal para utilizar la interfaz por linea de comando"
|
| 276 |
+
|
| 277 |
+
if '--formato' in sys.argv:
|
| 278 |
+
print("Formato:")
|
| 279 |
+
for msg, formato in [('Encabezado', ENCABEZADO),
|
| 280 |
+
('Observacion', OBSERVACION),
|
| 281 |
+
('Evento', EVENTO), ('Error', ERROR),
|
| 282 |
+
]:
|
| 283 |
+
comienzo = 1
|
| 284 |
+
print("=== %s ===" % msg)
|
| 285 |
+
print("|| %-20s || %8s || %9s || %-12s || %-20s ||" % (
|
| 286 |
+
"Campo", "PosiciοΏ½n", "Longitud", "Tipo", "DescripciοΏ½n"))
|
| 287 |
+
for fmt in formato:
|
| 288 |
+
clave, longitud, tipo, desc = fmt
|
| 289 |
+
print("|| %-20s || %8d || %9d || %-12s || %-20s ||" % (
|
| 290 |
+
clave, comienzo, longitud, tipo, desc.encode("latin1")))
|
| 291 |
+
comienzo += longitud
|
| 292 |
+
sys.exit(0)
|
| 293 |
+
|
| 294 |
+
# leer configuracion
|
| 295 |
+
global CONFIG_FILE
|
| 296 |
+
if len(sys.argv) > 1 and sys.argv[1][0] not in "-/":
|
| 297 |
+
CONFIG_FILE = sys.argv.pop(1)
|
| 298 |
+
config = SafeConfigParser()
|
| 299 |
+
config.read(CONFIG_FILE)
|
| 300 |
+
crt = config.get('WSAA', 'CERT')
|
| 301 |
+
key = config.get('WSAA', 'PRIVATEKEY')
|
| 302 |
+
cuit = config.get('WSCDC', 'CUIT')
|
| 303 |
+
url_wsaa = config.get('WSAA', 'URL') if config.has_option('WSAA', 'URL') else ""
|
| 304 |
+
url_wscdc = config.get('WSCDC', 'URL') if config.has_option('WSCDC', 'URL') else ""
|
| 305 |
+
|
| 306 |
+
# leo configuraciοΏ½n de archivos de intercambio
|
| 307 |
+
ENTRADA = config.get('WSCDC', 'ENTRADA')
|
| 308 |
+
SALIDA = config.get('WSCDC', 'SALIDA')
|
| 309 |
+
if config.has_section('DBF'):
|
| 310 |
+
conf_dbf = dict(config.items('DBF'))
|
| 311 |
+
else:
|
| 312 |
+
conf_dbf = {}
|
| 313 |
+
|
| 314 |
+
# instanciar la interfaz con el webservice
|
| 315 |
+
wscdc = WSCDC()
|
| 316 |
+
ok = wscdc.Conectar("", url_wscdc)
|
| 317 |
+
|
| 318 |
+
if "--dummy" in sys.argv:
|
| 319 |
+
# print wscdc.client.help("ComprobanteDummy")
|
| 320 |
+
wscdc.Dummy()
|
| 321 |
+
print("AppServerStatus", wscdc.AppServerStatus)
|
| 322 |
+
print("DbServerStatus", wscdc.DbServerStatus)
|
| 323 |
+
print("AuthServerStatus", wscdc.AuthServerStatus)
|
| 324 |
+
sys.exit(0)
|
| 325 |
+
|
| 326 |
+
# Gestionar credenciales de acceso con AFIP:
|
| 327 |
+
from .wsaa import WSAA
|
| 328 |
+
wsaa = WSAA()
|
| 329 |
+
ta = wsaa.Autenticar("wscdc", crt, key, url_wsaa)
|
| 330 |
+
if not ta:
|
| 331 |
+
sys.exit("Imposible autenticar con WSAA: %s" % wsaa.Excepcion)
|
| 332 |
+
wscdc.SetTicketAcceso(ta)
|
| 333 |
+
wscdc.Cuit = cuit
|
| 334 |
+
|
| 335 |
+
if "--constatar" in sys.argv:
|
| 336 |
+
if len(sys.argv) < 8:
|
| 337 |
+
if "--prueba" in sys.argv:
|
| 338 |
+
dic = dict(
|
| 339 |
+
cbte_modo="CAE",
|
| 340 |
+
cuit_emisor="20267565393",
|
| 341 |
+
pto_vta=3,
|
| 342 |
+
cbte_tipo=6,
|
| 343 |
+
cbte_nro=1,
|
| 344 |
+
cbte_fch="20131231",
|
| 345 |
+
imp_total="1.21",
|
| 346 |
+
cod_autorizacion="63533727749637",
|
| 347 |
+
doc_tipo_receptor=99,
|
| 348 |
+
doc_nro_receptor=0,
|
| 349 |
+
)
|
| 350 |
+
# escribir archivo de intercambio con datos de prueba:
|
| 351 |
+
escribir_archivo(dic, ENTRADA)
|
| 352 |
+
else:
|
| 353 |
+
# leer archivo de intercambio:
|
| 354 |
+
dic = leer_archivo(ENTRADA)
|
| 355 |
+
# constatar el comprobante
|
| 356 |
+
wscdc.ConstatarComprobante(**dic)
|
| 357 |
+
# actualizar el diccionario con los datos de devueltos por AFIP
|
| 358 |
+
dic.update({'resultado': wscdc.Resultado,
|
| 359 |
+
'fch_proceso': wscdc.FchProceso,
|
| 360 |
+
})
|
| 361 |
+
dic['observaciones'] = wscdc.observaciones
|
| 362 |
+
dic['errores'] = wscdc.errores
|
| 363 |
+
escribir_archivo(dic, SALIDA)
|
| 364 |
+
else:
|
| 365 |
+
# usar los datos pasados por linea de comandos:
|
| 366 |
+
wscdc.ConstatarComprobante(*sys.argv[sys.argv.index("--constatar") + 1:])
|
| 367 |
+
|
| 368 |
+
print("Resultado:", wscdc.Resultado)
|
| 369 |
+
print("Mensaje de Error:", wscdc.ErrMsg)
|
| 370 |
+
print("Observaciones:", wscdc.Obs)
|
| 371 |
+
|
| 372 |
+
if "--params" in sys.argv:
|
| 373 |
+
|
| 374 |
+
print("=== Modalidad Comprobantes ===")
|
| 375 |
+
print('\n'.join(wscdc.ConsultarModalidadComprobantes("||")))
|
| 376 |
+
print("=== Tipo Comprobantes ===")
|
| 377 |
+
print('\n'.join(wscdc.ConsultarTipoComprobantes("||")))
|
| 378 |
+
print("=== Tipo Documentos ===")
|
| 379 |
+
print('\n'.join(wscdc.ConsultarTipoDocumentos("||")))
|
| 380 |
+
print("=== Tipo Opcionales ===")
|
| 381 |
+
print('\n'.join(wscdc.ConsultarTipoOpcionales("||")))
|
| 382 |
+
print("Mensaje de Error:", wscdc.ErrMsg)
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
if __name__ == "__main__":
|
| 386 |
+
|
| 387 |
+
if '--register' in sys.argv or '--unregister' in sys.argv:
|
| 388 |
+
import pythoncom
|
| 389 |
+
import win32com.server.register
|
| 390 |
+
win32com.server.register.UseCommandLine(WSCDC)
|
| 391 |
+
elif "/Automate" in sys.argv:
|
| 392 |
+
# MS seems to like /automate to run the class factories.
|
| 393 |
+
import win32com.server.localserver
|
| 394 |
+
# win32com.server.localserver.main()
|
| 395 |
+
# start the server.
|
| 396 |
+
win32com.server.localserver.serve([WSCDC._reg_clsid_])
|
| 397 |
+
else:
|
| 398 |
+
main()
|
app/pyafipws/wscoc.py
ADDED
|
@@ -0,0 +1,1032 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"MοΏ½dulo para Consulta de Operaciones Cambiarias"
|
| 14 |
+
|
| 15 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 16 |
+
__copyright__ = "Copyright (C) 2011 Mariano Reingart"
|
| 17 |
+
__license__ = "GPL 3.0"
|
| 18 |
+
__version__ = "1.09a"
|
| 19 |
+
|
| 20 |
+
import os
|
| 21 |
+
import socket
|
| 22 |
+
import sys
|
| 23 |
+
import traceback
|
| 24 |
+
import pysimplesoap.client
|
| 25 |
+
from pysimplesoap.client import SoapClient, SoapFault, parse_proxy, \
|
| 26 |
+
set_http_wrapper
|
| 27 |
+
from pysimplesoap.simplexml import SimpleXMLElement
|
| 28 |
+
from io import StringIO
|
| 29 |
+
|
| 30 |
+
HOMO = True
|
| 31 |
+
|
| 32 |
+
if HOMO:
|
| 33 |
+
WSDL = "https://fwshomo.afip.gov.ar/wscoc/COCService"
|
| 34 |
+
LOCATION = "https://fwshomo.afip.gob.ar:443/wscoc2/COCService"
|
| 35 |
+
else:
|
| 36 |
+
WSDL = "https://serviciosjava.afip.gob.ar/wscoc2/COCService"
|
| 37 |
+
LOCATION = "https://serviciosjava.afip.gob.ar:443/wscoc2/COCService"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def inicializar_y_capturar_excepciones(func):
|
| 41 |
+
"Decorador para inicializar y capturar errores"
|
| 42 |
+
|
| 43 |
+
def capturar_errores_wrapper(self, *args, **kwargs):
|
| 44 |
+
try:
|
| 45 |
+
# inicializo (limpio variables)
|
| 46 |
+
self.Errores = []
|
| 47 |
+
self.ErroresFormato = []
|
| 48 |
+
self.Traceback = self.Excepcion = ""
|
| 49 |
+
self.ErrCode = ""
|
| 50 |
+
self.ErrMsg = ""
|
| 51 |
+
self.CodigoSolicitud = self.FechaSolicitud = None
|
| 52 |
+
self.COC = self.FechaEmisionCOC = self.CodigoDestino = None
|
| 53 |
+
self.EstadoSolicitud = self.FechaEstado = None
|
| 54 |
+
self.CUITComprador = self.DenominacionComprador = None
|
| 55 |
+
self.CodigoMoneda = self.CotizacionMoneda = self.MontoPesos = None
|
| 56 |
+
self.CUITRepresentante = self.DenominacionRepresentante = None
|
| 57 |
+
self.DJAI = self.CodigoExcepcionDJAI = self.EstadoDJAI = None
|
| 58 |
+
self.DJAS = self.CodigoExcepcionDJAS = self.EstadoDJAS = None
|
| 59 |
+
self.MontoFOB = self.CodigoMoneda = None
|
| 60 |
+
# iniciaalizo estructuras internas persistentes
|
| 61 |
+
self.__detalles_solicitudes = []
|
| 62 |
+
self.__detalles_cuit = []
|
| 63 |
+
|
| 64 |
+
# llamo a la funciοΏ½n (sin reintentos)
|
| 65 |
+
return func(self, *args, **kwargs)
|
| 66 |
+
|
| 67 |
+
except SoapFault as e:
|
| 68 |
+
# guardo destalle de la excepciοΏ½n SOAP
|
| 69 |
+
self.ErrCode = str(e.faultcode)
|
| 70 |
+
self.ErrMsg = str(e.faultstring)
|
| 71 |
+
self.Excepcion = "%s: %s" % (e.faultcode, e.faultstring, )
|
| 72 |
+
if self.LanzarExcepciones:
|
| 73 |
+
raise
|
| 74 |
+
except Exception as e:
|
| 75 |
+
ex = traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1],
|
| 76 |
+
sys.exc_info()[2])
|
| 77 |
+
self.Traceback = ''.join(ex)
|
| 78 |
+
self.Excepcion = "%s" % (e)
|
| 79 |
+
if self.LanzarExcepciones:
|
| 80 |
+
raise
|
| 81 |
+
finally:
|
| 82 |
+
# guardo datos de depuraciοΏ½n
|
| 83 |
+
if self.client:
|
| 84 |
+
self.XmlRequest = self.client.xml_request
|
| 85 |
+
self.XmlResponse = self.client.xml_response
|
| 86 |
+
return capturar_errores_wrapper
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class WSCOC:
|
| 90 |
+
"Interfaz para el WebService de Consulta de Operaciones Cambiarias"
|
| 91 |
+
_public_methods_ = ['GenerarSolicitudCompraDivisa',
|
| 92 |
+
'GenerarSolicitudCompraDivisaTurExt',
|
| 93 |
+
'InformarSolicitudCompraDivisa',
|
| 94 |
+
'ConsultarCUIT',
|
| 95 |
+
'ConsultarCOC',
|
| 96 |
+
'AnularCOC',
|
| 97 |
+
'ConsultarSolicitudCompraDivisa',
|
| 98 |
+
'ConsultarSolicitudesCompraDivisas',
|
| 99 |
+
'ConsultarDestinosCompra',
|
| 100 |
+
'ConsultarTiposReferencia',
|
| 101 |
+
'ConsultarMonedas',
|
| 102 |
+
'ConsultarTiposDocumento',
|
| 103 |
+
'ConsultarTiposEstadoSolicitud',
|
| 104 |
+
'ConsultarMotivosExcepcionDJAI',
|
| 105 |
+
'ConsultarDestinosCompraDJAI',
|
| 106 |
+
'ConsultarMotivosExcepcionDJAS',
|
| 107 |
+
'ConsultarDestinosCompraDJAS',
|
| 108 |
+
'ConsultarDestinosCompraTipoReferencia',
|
| 109 |
+
'LeerSolicitudConsultada', 'LeerCUITConsultado',
|
| 110 |
+
'ConsultarDJAI', 'ConsultarDJAS', 'ConsultarReferencia',
|
| 111 |
+
'LeerError', 'LeerErrorFormato', 'LeerInconsistencia',
|
| 112 |
+
'LoadTestXML',
|
| 113 |
+
'AnalizarXml', 'ObtenerTagXml',
|
| 114 |
+
'Dummy', 'Conectar', 'DebugLog']
|
| 115 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 116 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 117 |
+
'XmlRequest', 'XmlResponse', 'Version', 'InstallDir',
|
| 118 |
+
'Resultado', 'Inconsistencias', 'ErrCode', 'ErrMsg',
|
| 119 |
+
'CodigoSolicitud', 'FechaSolicitud', 'EstadoSolicitud', 'FechaEstado',
|
| 120 |
+
'COC', 'FechaEmisionCOC', 'CodigoDestino',
|
| 121 |
+
'CUITComprador', 'DenominacionComprador',
|
| 122 |
+
'CodigoMoneda', 'CotizacionMoneda', 'MontoPesos',
|
| 123 |
+
'CUITRepresentante', 'DenominacionRepresentante',
|
| 124 |
+
'TipoDoc', 'NumeroDoc', 'CUITConsultada', 'DenominacionConsultada',
|
| 125 |
+
'DJAI', 'CodigoExcepcionDJAI', 'DJAS', 'CodigoExcepcionDJAS',
|
| 126 |
+
'MontoFOB', 'EstadoDJAI', 'EstadoDJAS', 'Estado', 'Tipo', 'Codigo',
|
| 127 |
+
'ErroresFormato', 'Errores', 'Traceback', 'Excepcion', 'LanzarExcepciones',
|
| 128 |
+
]
|
| 129 |
+
|
| 130 |
+
_reg_progid_ = "WSCOC"
|
| 131 |
+
_reg_clsid_ = "{B30406CE-326A-46D9-B807-B7916E3F1B96}"
|
| 132 |
+
|
| 133 |
+
Version = "%s %s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '', pysimplesoap.client.__file__)
|
| 134 |
+
LanzarExcepciones = False
|
| 135 |
+
|
| 136 |
+
def __init__(self):
|
| 137 |
+
self.Token = self.Sign = self.Cuit = None
|
| 138 |
+
self.AppServerStatus = None
|
| 139 |
+
self.DbServerStatus = None
|
| 140 |
+
self.AuthServerStatus = None
|
| 141 |
+
self.XmlRequest = ''
|
| 142 |
+
self.XmlResponse = ''
|
| 143 |
+
self.Resultado = self.Motivo = self.Reproceso = ''
|
| 144 |
+
self.__analizar_solicitud({})
|
| 145 |
+
self.__analizar_inconsistencias({})
|
| 146 |
+
self.__analizar_errores({})
|
| 147 |
+
self.__detalles_solicitudes = None
|
| 148 |
+
self.__detalles_cuit = None
|
| 149 |
+
self.client = None
|
| 150 |
+
self.ErrCode = self.ErrMsg = self.Traceback = self.Excepcion = ""
|
| 151 |
+
self.EmisionTipo = ''
|
| 152 |
+
self.Reprocesar = self.Reproceso = '' # no implementado
|
| 153 |
+
self.Log = None
|
| 154 |
+
self.InstallDir = INSTALL_DIR
|
| 155 |
+
|
| 156 |
+
def __analizar_errores(self, ret):
|
| 157 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 158 |
+
self.Errores = []
|
| 159 |
+
self.ErroresFormato = []
|
| 160 |
+
if 'arrayErrores' in ret:
|
| 161 |
+
errores = ret['arrayErrores']
|
| 162 |
+
for error in errores:
|
| 163 |
+
self.Errores.append("%s: %s" % (
|
| 164 |
+
error['codigoDescripcion']['codigo'],
|
| 165 |
+
error['codigoDescripcion']['descripcion'],
|
| 166 |
+
))
|
| 167 |
+
if 'arrayErroresFormato' in ret:
|
| 168 |
+
errores = ret['arrayErroresFormato']
|
| 169 |
+
for error in errores:
|
| 170 |
+
self.ErroresFormato.append("%s: %s" % (
|
| 171 |
+
error['codigoDescripcionString']['codigo'],
|
| 172 |
+
error['codigoDescripcionString']['descripcion'],
|
| 173 |
+
))
|
| 174 |
+
|
| 175 |
+
def __analizar_solicitud(self, det):
|
| 176 |
+
"Analiza y extrae los datos de una solicitud"
|
| 177 |
+
self.CodigoSolicitud = det.get("codigoSolicitud")
|
| 178 |
+
self.FechaSolicitud = det.get("fechaSolicitud")
|
| 179 |
+
self.COC = str(det.get("coc"))
|
| 180 |
+
self.FechaEmisionCOC = det.get("fechaEmisionCOC")
|
| 181 |
+
self.EstadoSolicitud = det.get("estadoSolicitud")
|
| 182 |
+
self.FechaEstado = det.get("fechaEstado")
|
| 183 |
+
self.CUITComprador = str(det.get("detalleCUITComprador",
|
| 184 |
+
{}).get("cuit", ""))
|
| 185 |
+
self.DenominacionComprador = det.get("detalleCUITComprador",
|
| 186 |
+
{}).get("denominacion")
|
| 187 |
+
self.CodigoMoneda = det.get("codigoMoneda")
|
| 188 |
+
self.CotizacionMoneda = det.get("cotizacionMoneda")
|
| 189 |
+
self.MontoPesos = det.get("montoPesos")
|
| 190 |
+
self.CUITRepresentante = str(det.get("DetalleCUITRepresentante",
|
| 191 |
+
{}).get("cuit", ""))
|
| 192 |
+
self.DenominacionRepresentante = det.get("DetalleCUITRepresentante",
|
| 193 |
+
{}).get("denominacion")
|
| 194 |
+
self.CodigoDestino = det.get("codigoDestino")
|
| 195 |
+
self.DJAI = det.get("djai")
|
| 196 |
+
self.CodigoExcepcionDJAI = det.get("codigoExcepcionDJAI")
|
| 197 |
+
self.DJAS = det.get("djas")
|
| 198 |
+
self.CodigoExcepcionDJAS = det.get("codigoExcepcionDJAS")
|
| 199 |
+
ref = det.get("referencia")
|
| 200 |
+
if ref:
|
| 201 |
+
self.Tipo = ref['tipo']
|
| 202 |
+
self.Codigo = ref['codigo']
|
| 203 |
+
|
| 204 |
+
def __analizar_inconsistencias(self, ret):
|
| 205 |
+
"Comprueba y extrae (formatea) las inconsistencias"
|
| 206 |
+
self.Inconsistencias = []
|
| 207 |
+
if 'arrayInconsistencias' in ret:
|
| 208 |
+
inconsistencias = ret['arrayInconsistencias']
|
| 209 |
+
for inconsistencia in inconsistencias:
|
| 210 |
+
self.Inconsistencias.append("%s: %s" % (
|
| 211 |
+
inconsistencia['codigoDescripcion']['codigo'],
|
| 212 |
+
inconsistencia['codigoDescripcion']['descripcion'],
|
| 213 |
+
))
|
| 214 |
+
|
| 215 |
+
def __log(self, msg):
|
| 216 |
+
if not isinstance(msg, str):
|
| 217 |
+
msg = str(msg, 'utf8', 'ignore')
|
| 218 |
+
if not self.Log:
|
| 219 |
+
self.Log = StringIO()
|
| 220 |
+
self.Log.write(msg)
|
| 221 |
+
self.Log.write('\n\r')
|
| 222 |
+
|
| 223 |
+
def DebugLog(self):
|
| 224 |
+
"Devolver y limpiar la bitοΏ½cora de depuraciοΏ½n"
|
| 225 |
+
if self.Log:
|
| 226 |
+
msg = self.Log.getvalue()
|
| 227 |
+
# limpiar log
|
| 228 |
+
self.Log.close()
|
| 229 |
+
self.Log = None
|
| 230 |
+
else:
|
| 231 |
+
msg = ''
|
| 232 |
+
return msg
|
| 233 |
+
|
| 234 |
+
@inicializar_y_capturar_excepciones
|
| 235 |
+
def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, timeout=30):
|
| 236 |
+
# cliente soap del web service
|
| 237 |
+
if timeout:
|
| 238 |
+
self.__log("Estableciendo timeout=%s" % (timeout, ))
|
| 239 |
+
socket.setdefaulttimeout(timeout)
|
| 240 |
+
if wrapper:
|
| 241 |
+
Http = set_http_wrapper(wrapper)
|
| 242 |
+
self.Version = WSCOC.Version + " " + Http._wrapper_version
|
| 243 |
+
proxy_dict = parse_proxy(proxy)
|
| 244 |
+
location = LOCATION
|
| 245 |
+
if HOMO or not wsdl:
|
| 246 |
+
wsdl = WSDL
|
| 247 |
+
elif not wsdl.endswith("?wsdl") and wsdl.startswith("http"):
|
| 248 |
+
location = wsdl
|
| 249 |
+
wsdl += "?wsdl"
|
| 250 |
+
elif wsdl.endswith("?wsdl"):
|
| 251 |
+
location = wsdl[:-5]
|
| 252 |
+
if not cache or HOMO:
|
| 253 |
+
# use 'cache' from installation base directory
|
| 254 |
+
cache = os.path.join(self.InstallDir, 'cache')
|
| 255 |
+
self.__log("Conectando a wsdl=%s cache=%s proxy=%s" % (wsdl, cache, proxy_dict))
|
| 256 |
+
self.client = SoapClient(
|
| 257 |
+
wsdl=wsdl,
|
| 258 |
+
cache=cache,
|
| 259 |
+
proxy=proxy_dict,
|
| 260 |
+
ns="coc",
|
| 261 |
+
cacert=cacert,
|
| 262 |
+
soap_ns="soapenv",
|
| 263 |
+
soap_server="jbossas6",
|
| 264 |
+
trace="--trace" in sys.argv)
|
| 265 |
+
# corrijo ubicaciοΏ½n del servidor (http en el WSDL)
|
| 266 |
+
self.client.services['COCService']['ports']['COCServiceHttpSoap11Endpoint']['location'] = location
|
| 267 |
+
self.__log("Corrigiendo location=%s" % (location, ))
|
| 268 |
+
return True
|
| 269 |
+
|
| 270 |
+
@inicializar_y_capturar_excepciones
|
| 271 |
+
def Dummy(self):
|
| 272 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 273 |
+
result = self.client.dummy()
|
| 274 |
+
ret = result['dummyReturn']
|
| 275 |
+
self.AppServerStatus = ret['appserver']
|
| 276 |
+
self.DbServerStatus = ret['dbserver']
|
| 277 |
+
self.AuthServerStatus = ret['authserver']
|
| 278 |
+
return True
|
| 279 |
+
|
| 280 |
+
@inicializar_y_capturar_excepciones
|
| 281 |
+
def GenerarSolicitudCompraDivisa(self, cuit_comprador, codigo_moneda,
|
| 282 |
+
cotizacion_moneda, monto_pesos,
|
| 283 |
+
cuit_representante, codigo_destino,
|
| 284 |
+
djai=None, codigo_excepcion_djai=None,
|
| 285 |
+
djas=None, codigo_excepcion_djas=None,
|
| 286 |
+
tipo=None, codigo=None,
|
| 287 |
+
):
|
| 288 |
+
"Generar una Solicitud de operaciοΏ½n cambiaria"
|
| 289 |
+
if tipo and codigo:
|
| 290 |
+
referencia = {'tipo': tipo, 'codigo': codigo}
|
| 291 |
+
else:
|
| 292 |
+
referencia = None
|
| 293 |
+
res = self.client.generarSolicitudCompraDivisa(
|
| 294 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 295 |
+
cuitComprador=cuit_comprador,
|
| 296 |
+
codigoMoneda=codigo_moneda,
|
| 297 |
+
cotizacionMoneda=cotizacion_moneda,
|
| 298 |
+
montoPesos=monto_pesos,
|
| 299 |
+
cuitRepresentante=cuit_representante,
|
| 300 |
+
codigoDestino=codigo_destino,
|
| 301 |
+
djai=djai, codigoExcepcionDJAI=codigo_excepcion_djai,
|
| 302 |
+
djas=djas, codigoExcepcionDJAS=codigo_excepcion_djas,
|
| 303 |
+
referencia=referencia,
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
self.Resultado = ""
|
| 307 |
+
ret = res.get('generarSolicitudCompraDivisaReturn', {})
|
| 308 |
+
self.Resultado = ret.get('resultado')
|
| 309 |
+
det = ret.get('detalleSolicitud', {})
|
| 310 |
+
self.__analizar_solicitud(det)
|
| 311 |
+
self.__analizar_inconsistencias(det)
|
| 312 |
+
self.__analizar_errores(ret)
|
| 313 |
+
|
| 314 |
+
return True
|
| 315 |
+
|
| 316 |
+
@inicializar_y_capturar_excepciones
|
| 317 |
+
def GenerarSolicitudCompraDivisaTurExt(self, tipo_doc, numero_doc, apellido_nombre,
|
| 318 |
+
codigo_moneda, cotizacion_moneda, monto_pesos,
|
| 319 |
+
cuit_representante, codigo_destino,
|
| 320 |
+
):
|
| 321 |
+
"Generar una Solicitud de operaciοΏ½n cambiaria"
|
| 322 |
+
res = self.client.generarSolicitudCompraDivisaTurExt(
|
| 323 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 324 |
+
detalleTurExtComprador={
|
| 325 |
+
'tipoNumeroDoc': {'tipoDoc': tipo_doc, 'numeroDoc': numero_doc},
|
| 326 |
+
'apellidoNombre': apellido_nombre},
|
| 327 |
+
codigoMoneda=codigo_moneda,
|
| 328 |
+
cotizacionMoneda=cotizacion_moneda,
|
| 329 |
+
montoPesos=monto_pesos,
|
| 330 |
+
cuitRepresentante=cuit_representante,
|
| 331 |
+
codigoDestino=codigo_destino,
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
self.Resultado = ""
|
| 335 |
+
ret = res.get('generarSolicitudCompraDivisaTurExtReturn', {})
|
| 336 |
+
self.Resultado = ret.get('resultado')
|
| 337 |
+
det = ret.get('detalleSolicitud', {})
|
| 338 |
+
self.__analizar_solicitud(det)
|
| 339 |
+
self.__analizar_inconsistencias(det)
|
| 340 |
+
self.__analizar_errores(ret)
|
| 341 |
+
|
| 342 |
+
return True
|
| 343 |
+
|
| 344 |
+
@inicializar_y_capturar_excepciones
|
| 345 |
+
def InformarSolicitudCompraDivisa(self, codigo_solicitud, nuevo_estado):
|
| 346 |
+
"Informar la aceptaciοΏ½n o desistir una solicitud generada con anterioridad"
|
| 347 |
+
|
| 348 |
+
res = self.client.informarSolicitudCompraDivisa(
|
| 349 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 350 |
+
codigoSolicitud=codigo_solicitud,
|
| 351 |
+
nuevoEstado=nuevo_estado,
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
self.Resultado = ""
|
| 355 |
+
ret = res.get('informarSolicitudCompraDivisaReturn', {})
|
| 356 |
+
self.Resultado = ret.get('resultado')
|
| 357 |
+
self.__analizar_solicitud(ret)
|
| 358 |
+
self.__analizar_errores(ret)
|
| 359 |
+
return True
|
| 360 |
+
|
| 361 |
+
@inicializar_y_capturar_excepciones
|
| 362 |
+
def ConsultarCUIT(self, numero_doc, tipo_doc=80, sep="|"):
|
| 363 |
+
"Consultar la CUIT, CDI οΏ½ CUIL, segοΏ½n corresponda, para un determinado tipo y nοΏ½mero de documento."
|
| 364 |
+
|
| 365 |
+
res = self.client.consultarCUIT(
|
| 366 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 367 |
+
tipoNumeroDoc={'tipoDoc': tipo_doc, 'numeroDoc': numero_doc}
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
self.__detalles_cuit = []
|
| 371 |
+
|
| 372 |
+
if 'consultarCUITReturn' in res:
|
| 373 |
+
ret = res['consultarCUITReturn']
|
| 374 |
+
self.__analizar_errores(ret)
|
| 375 |
+
if 'tipoNumeroDoc' in ret:
|
| 376 |
+
self.TipoDoc = ret['tipoNumeroDoc']['tipoDoc']
|
| 377 |
+
self.NumeroDoc = ret['tipoNumeroDoc']['numeroDoc']
|
| 378 |
+
for detalle in ret.get('arrayDetallesCUIT', []):
|
| 379 |
+
# agrego el detalle para consultarlo luego (LeerCUITConsultado)
|
| 380 |
+
det = detalle['detalleCUIT']
|
| 381 |
+
self.__detalles_cuit.append(det)
|
| 382 |
+
# devuelvo una lista de cuit/denominaciοΏ½n
|
| 383 |
+
return [("%(cuit)s\t%(denominacion)s" %
|
| 384 |
+
d['detalleCUIT']).replace("\t", sep)
|
| 385 |
+
for d in ret.get('arrayDetallesCUIT', [])]
|
| 386 |
+
else:
|
| 387 |
+
return []
|
| 388 |
+
else:
|
| 389 |
+
self.TipoDoc = None
|
| 390 |
+
self.NumeroDoc = None
|
| 391 |
+
return [""]
|
| 392 |
+
|
| 393 |
+
def LeerCUITConsultado(self):
|
| 394 |
+
"Recorro los CUIT devueltos por ConsultarCUIT"
|
| 395 |
+
|
| 396 |
+
if self.__detalles_cuit:
|
| 397 |
+
# extraigo el primer item
|
| 398 |
+
det = self.__detalles_cuit.pop(0)
|
| 399 |
+
self.CUITConsultada = str(det['cuit'])
|
| 400 |
+
self.DenominacionConsultada = str(det['denominacion'])
|
| 401 |
+
return True
|
| 402 |
+
else:
|
| 403 |
+
return False
|
| 404 |
+
|
| 405 |
+
@inicializar_y_capturar_excepciones
|
| 406 |
+
def ConsultarCOC(self, coc):
|
| 407 |
+
"Obtener los datos de un COC existente"
|
| 408 |
+
|
| 409 |
+
res = self.client.consultarCOC(
|
| 410 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 411 |
+
coc=coc,
|
| 412 |
+
)
|
| 413 |
+
ret = res.get('consultarCOCReturn', {})
|
| 414 |
+
det = ret.get('detalleSolicitud', {})
|
| 415 |
+
self.__analizar_solicitud(det)
|
| 416 |
+
self.__analizar_inconsistencias(det)
|
| 417 |
+
self.__analizar_errores(ret)
|
| 418 |
+
return True
|
| 419 |
+
|
| 420 |
+
@inicializar_y_capturar_excepciones
|
| 421 |
+
def AnularCOC(self, coc, cuit_comprador):
|
| 422 |
+
"Anular COC existente (estado CO 24hs)"
|
| 423 |
+
res = self.client.anularCOC(
|
| 424 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 425 |
+
coc=coc,
|
| 426 |
+
cuitComprador=cuit_comprador,
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
ret = res.get('anularCOCReturn', {})
|
| 430 |
+
self.__analizar_solicitud(ret)
|
| 431 |
+
self.__analizar_inconsistencias(ret)
|
| 432 |
+
self.__analizar_errores(ret)
|
| 433 |
+
|
| 434 |
+
return True
|
| 435 |
+
|
| 436 |
+
@inicializar_y_capturar_excepciones
|
| 437 |
+
def ConsultarSolicitudCompraDivisa(self, codigo_solicitud):
|
| 438 |
+
"Consultar una Solicitud de OperaciοΏ½n Cambiaria"
|
| 439 |
+
res = self.client.consultarSolicitudCompraDivisa(
|
| 440 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 441 |
+
codigoSolicitud=codigo_solicitud,
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
ret = res.get('consultarSolicitudCompraDivisaReturn', {})
|
| 445 |
+
det = ret.get('detalleSolicitud', {})
|
| 446 |
+
self.__analizar_solicitud(det)
|
| 447 |
+
self.__analizar_errores(ret)
|
| 448 |
+
|
| 449 |
+
return True
|
| 450 |
+
|
| 451 |
+
@inicializar_y_capturar_excepciones
|
| 452 |
+
def ConsultarSolicitudesCompraDivisas(self, cuit_comprador,
|
| 453 |
+
estado_solicitud,
|
| 454 |
+
fecha_emision_desde,
|
| 455 |
+
fecha_emision_hasta,
|
| 456 |
+
):
|
| 457 |
+
"Consultar Solicitudes de operaciones cambiarias"
|
| 458 |
+
res = self.client.consultarSolicitudesCompraDivisas(
|
| 459 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 460 |
+
cuitComprador=cuit_comprador,
|
| 461 |
+
estadoSolicitud=estado_solicitud,
|
| 462 |
+
fechaEmisionDesde=fecha_emision_desde,
|
| 463 |
+
fechaEmisionHasta=fecha_emision_hasta,
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
self.__analizar_errores(res)
|
| 467 |
+
|
| 468 |
+
ret = res.get('consultarSolicitudesCompraDivisasReturn', {})
|
| 469 |
+
solicitudes = [] # cοΏ½digos a devolver
|
| 470 |
+
self.__detalles_solicitudes = [] # diccionario para recorrerlo luego
|
| 471 |
+
for array in ret.get('arrayDetallesSolicitudes', []):
|
| 472 |
+
det = array['detalleSolicitudes']
|
| 473 |
+
# guardo el detalle para procesarlo luego (LeerSolicitudConsultada)
|
| 474 |
+
self.__detalles_solicitudes.append(det)
|
| 475 |
+
# devuelvo solo el cοΏ½digo de solicitud
|
| 476 |
+
solicitudes.append(det.get("codigoSolicitud"))
|
| 477 |
+
return solicitudes
|
| 478 |
+
|
| 479 |
+
def LeerSolicitudConsultada(self):
|
| 480 |
+
"Proceso de a una solicitud los detalles devueltos por ConsultarSolicitudesCompraDivisas"
|
| 481 |
+
if self.__detalles_solicitudes:
|
| 482 |
+
# extraigo el primer item
|
| 483 |
+
det = self.__detalles_solicitudes.pop(0)
|
| 484 |
+
self.__analizar_solicitud(det)
|
| 485 |
+
self.__analizar_errores(det)
|
| 486 |
+
self.__analizar_inconsistencias(det)
|
| 487 |
+
return True
|
| 488 |
+
else:
|
| 489 |
+
return False
|
| 490 |
+
|
| 491 |
+
@inicializar_y_capturar_excepciones
|
| 492 |
+
def ConsultarDJAI(self, djai, cuit):
|
| 493 |
+
"Consultar DeclaraciοΏ½n Jurada Anticipada de ImportaciοΏ½n"
|
| 494 |
+
res = self.client.consultarDJAI(
|
| 495 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 496 |
+
djai=djai, cuit=cuit,
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
ret = res.get('consultarDJAIReturn', {})
|
| 500 |
+
self.__analizar_errores(ret)
|
| 501 |
+
self.DJAI = ret.get('djai')
|
| 502 |
+
self.MontoFOB = ret.get('montoFOB')
|
| 503 |
+
self.CodigoMoneda = ret.get('codigoMoneda')
|
| 504 |
+
self.EstadoDJAI = ret.get('estadoDJAI')
|
| 505 |
+
return True
|
| 506 |
+
|
| 507 |
+
@inicializar_y_capturar_excepciones
|
| 508 |
+
def ConsultarDJAS(self, djas, cuit):
|
| 509 |
+
"Consultar DeclaraciοΏ½n Jurada Anticipada de Servicios"
|
| 510 |
+
res = self.client.consultarDJAS(
|
| 511 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 512 |
+
djas=djas, cuit=cuit,
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
ret = res.get('consultarDJASReturn', {})
|
| 516 |
+
self.__analizar_errores(ret)
|
| 517 |
+
self.DJAS = ret.get('djas')
|
| 518 |
+
self.MontoFOB = ret.get('montoFOB')
|
| 519 |
+
self.CodigoMoneda = ret.get('codigoMoneda')
|
| 520 |
+
self.EstadoDJAS = ret.get('estadoDJAS')
|
| 521 |
+
return True
|
| 522 |
+
|
| 523 |
+
@inicializar_y_capturar_excepciones
|
| 524 |
+
def ConsultarReferencia(self, tipo, codigo):
|
| 525 |
+
"Consultar una determinada referencia segοΏ½n su tipo (1: DJAI, 2: DJAS, 3: DJAT)"
|
| 526 |
+
res = self.client.consultarReferencia(
|
| 527 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 528 |
+
referencia={'tipo': tipo, 'codigo': codigo},
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
ret = res.get('consultarReferenciaReturn', {})
|
| 532 |
+
self.__analizar_errores(ret)
|
| 533 |
+
#self.Codigo = ret.get('codigo')
|
| 534 |
+
self.MontoFOB = ret.get('monto')
|
| 535 |
+
self.CodigoMoneda = ret.get('codigoMoneda')
|
| 536 |
+
self.Estado = ret.get('estado')
|
| 537 |
+
return True
|
| 538 |
+
|
| 539 |
+
@inicializar_y_capturar_excepciones
|
| 540 |
+
def ConsultarMonedas(self, sep="|"):
|
| 541 |
+
"Este mοΏ½todo retorna el universo de Monedas disponibles en el presente WS, indicando cοΏ½digo y descripciοΏ½n de cada una"
|
| 542 |
+
res = self.client.consultarMonedas(
|
| 543 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 544 |
+
)
|
| 545 |
+
ret = res['consultarMonedasReturn']
|
| 546 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 547 |
+
% p['codigoDescripcion']).replace("\t", sep)
|
| 548 |
+
for p in ret['arrayMonedas']]
|
| 549 |
+
|
| 550 |
+
@inicializar_y_capturar_excepciones
|
| 551 |
+
def ConsultarDestinosCompra(self, sep="|"):
|
| 552 |
+
"Consultar Tipos de Destinos de compra de divisas"
|
| 553 |
+
res = self.client.consultarDestinosCompra(
|
| 554 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 555 |
+
)
|
| 556 |
+
# <consultarDestinosCompraReturn>22.02 m
|
| 557 |
+
# <arrayDestinos>
|
| 558 |
+
# <destinos>
|
| 559 |
+
# <tipoDestino>TipoDestinoSimpleType</tipoDestino>
|
| 560 |
+
# <arrayCodigosDescripciones>
|
| 561 |
+
# <codigoDescripcion>
|
| 562 |
+
# <codigo>short</codigo>
|
| 563 |
+
# <descripcion>string</descripcion>
|
| 564 |
+
# </codigoDescripcion>
|
| 565 |
+
# </arrayCodigosDescripciones>
|
| 566 |
+
# </destinos>
|
| 567 |
+
# </arrayDestinos>
|
| 568 |
+
ret = res['consultarDestinosCompraReturn']
|
| 569 |
+
dest = []
|
| 570 |
+
for array in ret['arrayDestinos']:
|
| 571 |
+
destino = array['destinos']
|
| 572 |
+
codigos = [("%s\t%s\t%s"
|
| 573 |
+
% (destino['tipoDestino'],
|
| 574 |
+
p['codigoDescripcion']['codigo'],
|
| 575 |
+
p['codigoDescripcion']['descripcion'],
|
| 576 |
+
)).replace("\t", sep)
|
| 577 |
+
for p in destino['arrayCodigosDescripciones']]
|
| 578 |
+
dest.extend(codigos)
|
| 579 |
+
return dest
|
| 580 |
+
|
| 581 |
+
@inicializar_y_capturar_excepciones
|
| 582 |
+
def ConsultarTiposDocumento(self, sep="|"):
|
| 583 |
+
"Consultar Tipos de Documentos"
|
| 584 |
+
res = self.client.consultarTiposDocumento(
|
| 585 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 586 |
+
)
|
| 587 |
+
ret = res['consultarTiposDocumentoReturn']
|
| 588 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 589 |
+
% p['codigoDescripcion']).replace("\t", sep)
|
| 590 |
+
for p in ret['arrayTiposDocumento']]
|
| 591 |
+
|
| 592 |
+
@inicializar_y_capturar_excepciones
|
| 593 |
+
def ConsultarTiposEstadoSolicitud(self, sep="|"):
|
| 594 |
+
"Este mοΏ½todo devuelve los diferentes tipos de estado que puede tener una solicitud."
|
| 595 |
+
res = self.client.consultarTiposEstadoSolicitud(
|
| 596 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 597 |
+
)
|
| 598 |
+
ret = res['consultarTiposEstadoSolicitudReturn']
|
| 599 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 600 |
+
% p['codigoDescripcionString']).replace("\t", sep)
|
| 601 |
+
for p in ret['arrayTiposEstadoSolicitud']]
|
| 602 |
+
|
| 603 |
+
@inicializar_y_capturar_excepciones
|
| 604 |
+
def ConsultarMotivosExcepcionDJAI(self, sep='|'):
|
| 605 |
+
"Este mοΏ½todo retorna el universo de motivos de excepciones a la DeclaraciοΏ½n Jurada Anticipada de ImportaciοΏ½n"
|
| 606 |
+
res = self.client.consultarMotivosExcepcionDJAI(
|
| 607 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 608 |
+
)
|
| 609 |
+
ret = res['consultarMotivosExcepcionDJAIReturn']
|
| 610 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 611 |
+
% p['codigoDescripcion']).replace("\t", sep)
|
| 612 |
+
for p in ret['arrayMotivosExcepcion']]
|
| 613 |
+
|
| 614 |
+
@inicializar_y_capturar_excepciones
|
| 615 |
+
def ConsultarDestinosCompraDJAI(self, sep='|'):
|
| 616 |
+
"Este mοΏ½todo retorna el subconjunto de los destinos de compra de divisas alcanzados por las normativas de la DeclaraciοΏ½n Jurada Anticipada de ImportaciοΏ½n."
|
| 617 |
+
res = self.client.consultarDestinosCompraDJAI(
|
| 618 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 619 |
+
)
|
| 620 |
+
ret = res['consultarDestinosCompraDJAIReturn']
|
| 621 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 622 |
+
% p['codigoDescripcion']).replace("\t", sep)
|
| 623 |
+
for p in ret['arrayCodigosDescripciones']]
|
| 624 |
+
|
| 625 |
+
@inicializar_y_capturar_excepciones
|
| 626 |
+
def ConsultarMotivosExcepcionDJAS(self, sep='|'):
|
| 627 |
+
"Este mοΏ½todo retorna el universo de motivos de excepciones a la DeclaraciοΏ½n Jurada Anticipada de Servicios"
|
| 628 |
+
res = self.client.consultarMotivosExcepcionDJAS(
|
| 629 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 630 |
+
)
|
| 631 |
+
ret = res['consultarMotivosExcepcionDJASReturn']
|
| 632 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 633 |
+
% p['codigoDescripcion']).replace("\t", sep)
|
| 634 |
+
for p in ret['arrayMotivosExcepcion']]
|
| 635 |
+
|
| 636 |
+
@inicializar_y_capturar_excepciones
|
| 637 |
+
def ConsultarDestinosCompraDJAS(self, sep='|'):
|
| 638 |
+
"Este mοΏ½todo retorna el subconjunto de los destinos de compra de divisas alcanzados por las normativas de la DeclaraciοΏ½n Jurada Anticipada de Servicios"
|
| 639 |
+
res = self.client.consultarDestinosCompraDJAS(
|
| 640 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 641 |
+
)
|
| 642 |
+
ret = res['consultarDestinosCompraDJASReturn']
|
| 643 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 644 |
+
% p['codigoDescripcion']).replace("\t", sep)
|
| 645 |
+
for p in ret['arrayCodigosDescripciones']]
|
| 646 |
+
|
| 647 |
+
@inicializar_y_capturar_excepciones
|
| 648 |
+
def ConsultarTiposReferencia(self, sep='|'):
|
| 649 |
+
"Este mοΏ½todo retorna el conjunto de los tipos de referencia que pueden ser utilizados en la generaciοΏ½n de una solicitud de compra de divisas segοΏ½n corresponda."
|
| 650 |
+
res = self.client.consultarTiposReferencia(
|
| 651 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 652 |
+
)
|
| 653 |
+
ret = res['consultarTiposReferenciaReturn']
|
| 654 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 655 |
+
% p['codigoDescripcion']).replace("\t", sep)
|
| 656 |
+
for p in ret['arrayCodigosDescripciones']]
|
| 657 |
+
|
| 658 |
+
@inicializar_y_capturar_excepciones
|
| 659 |
+
def ConsultarDestinosCompraTipoReferencia(self, tipo, sep='|'):
|
| 660 |
+
"Este mοΏ½todo retorna el subconjunto de los destinos de compra de divisas alcanzados por algunas de las normativas vigentes segοΏ½n el tipo de referencia requerido"
|
| 661 |
+
res = self.client.consultarDestinosCompraTipoReferencia(
|
| 662 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 663 |
+
tipo=tipo,
|
| 664 |
+
)
|
| 665 |
+
ret = res['consultarDestinosCompraTipoReferenciaReturn']
|
| 666 |
+
return [("%(codigo)s\t%(descripcion)s"
|
| 667 |
+
% p['codigoDescripcion']).replace("\t", sep)
|
| 668 |
+
for p in ret['arrayCodigosDescripciones']]
|
| 669 |
+
|
| 670 |
+
def LeerError(self):
|
| 671 |
+
"Recorro los errores devueltos y devuelvo el primero si existe"
|
| 672 |
+
|
| 673 |
+
if self.Errores:
|
| 674 |
+
# extraigo el primer item
|
| 675 |
+
er = self.Errores.pop(0)
|
| 676 |
+
return er
|
| 677 |
+
else:
|
| 678 |
+
return ""
|
| 679 |
+
|
| 680 |
+
def LeerErrorFormato(self):
|
| 681 |
+
"Recorro los errores de formatos devueltos y devuelvo el primero si existe"
|
| 682 |
+
|
| 683 |
+
if self.ErroresFormato:
|
| 684 |
+
# extraigo el primer item
|
| 685 |
+
er = self.ErroresFormato.pop(0)
|
| 686 |
+
return er
|
| 687 |
+
else:
|
| 688 |
+
return ""
|
| 689 |
+
|
| 690 |
+
def LeerInconsistencia(self):
|
| 691 |
+
"Recorro las inconsistencias devueltas y devuelvo la primera si existe"
|
| 692 |
+
|
| 693 |
+
if self.Inconsistencias:
|
| 694 |
+
# extraigo el primer item
|
| 695 |
+
er = self.Inconsistencias.pop(0)
|
| 696 |
+
return er
|
| 697 |
+
else:
|
| 698 |
+
return ""
|
| 699 |
+
|
| 700 |
+
def LoadTestXML(self, xml_file):
|
| 701 |
+
class DummyHTTP:
|
| 702 |
+
def __init__(self, xml_response):
|
| 703 |
+
self.xml_response = xml_response
|
| 704 |
+
|
| 705 |
+
def request(self, location, method, body, headers):
|
| 706 |
+
return {}, self.xml_response
|
| 707 |
+
self.client.http = DummyHTTP(open(xml_file).read())
|
| 708 |
+
|
| 709 |
+
def AnalizarXml(self, xml=""):
|
| 710 |
+
"Analiza un mensaje XML (por defecto la respuesta)"
|
| 711 |
+
try:
|
| 712 |
+
if not xml or xml == 'XmlResponse':
|
| 713 |
+
xml = self.XmlResponse
|
| 714 |
+
elif xml == 'XmlRequest':
|
| 715 |
+
xml = self.XmlRequest
|
| 716 |
+
self.xml = SimpleXMLElement(xml)
|
| 717 |
+
return True
|
| 718 |
+
except Exception as e:
|
| 719 |
+
self.Excepcion = "%s" % (e)
|
| 720 |
+
return False
|
| 721 |
+
|
| 722 |
+
def ObtenerTagXml(self, *tags):
|
| 723 |
+
"Busca en el Xml analizado y devuelve el tag solicitado"
|
| 724 |
+
# convierto el xml a un objeto
|
| 725 |
+
try:
|
| 726 |
+
if self.xml:
|
| 727 |
+
xml = self.xml
|
| 728 |
+
# por cada tag, lo busco segun su nombre o posiciοΏ½n
|
| 729 |
+
for tag in tags:
|
| 730 |
+
xml = xml(tag) # atajo a getitem y getattr
|
| 731 |
+
# vuelvo a convertir a string el objeto xml encontrado
|
| 732 |
+
return str(xml)
|
| 733 |
+
except Exception as e:
|
| 734 |
+
self.Excepcion = "%s" % (e)
|
| 735 |
+
|
| 736 |
+
|
| 737 |
+
def p_assert_eq(a, b):
|
| 738 |
+
print(a, a == b and '==' or '!=', b)
|
| 739 |
+
|
| 740 |
+
|
| 741 |
+
def main():
|
| 742 |
+
"FunciοΏ½n principal de pruebas (obtener CAE)"
|
| 743 |
+
import os
|
| 744 |
+
import time
|
| 745 |
+
|
| 746 |
+
DEBUG = '--debug' in sys.argv
|
| 747 |
+
|
| 748 |
+
# obteniendo el TA
|
| 749 |
+
TA = "TA-wscoc.xml"
|
| 750 |
+
if not os.path.exists(TA) or os.path.getmtime(TA) + (60 * 60 * 5) < time.time():
|
| 751 |
+
from . import wsaa
|
| 752 |
+
tra = wsaa.create_tra(service="wscoc")
|
| 753 |
+
cms = wsaa.sign_tra(tra, "olano.crt", "olanoycia.key")
|
| 754 |
+
ta_string = wsaa.call_wsaa(cms, trace='--trace' in sys.argv)
|
| 755 |
+
open(TA, "w").write(ta_string)
|
| 756 |
+
|
| 757 |
+
# fin TA
|
| 758 |
+
|
| 759 |
+
ws = WSCOC()
|
| 760 |
+
ws.Cuit = "20267565393"
|
| 761 |
+
|
| 762 |
+
ta_string = open(TA).read()
|
| 763 |
+
ta = SimpleXMLElement(ta_string)
|
| 764 |
+
ws.Token = str(ta.credentials.token)
|
| 765 |
+
ws.Sign = str(ta.credentials.sign)
|
| 766 |
+
|
| 767 |
+
ws.LanzarExcepciones = True
|
| 768 |
+
ws.Conectar(wsdl=WSDL)
|
| 769 |
+
|
| 770 |
+
if "--dummy" in sys.argv:
|
| 771 |
+
# print ws.client.help("dummy")
|
| 772 |
+
try:
|
| 773 |
+
ws.Dummy()
|
| 774 |
+
print("AppServerStatus", ws.AppServerStatus)
|
| 775 |
+
print("DbServerStatus", ws.DbServerStatus)
|
| 776 |
+
print("AuthServerStatus", ws.AuthServerStatus)
|
| 777 |
+
except Exception as e:
|
| 778 |
+
raise
|
| 779 |
+
print("Exception", e)
|
| 780 |
+
print(ws.XmlRequest)
|
| 781 |
+
print(ws.XmlResponse)
|
| 782 |
+
|
| 783 |
+
if "--monedas" in sys.argv:
|
| 784 |
+
print(ws.client.help("consultarMonedas"))
|
| 785 |
+
try:
|
| 786 |
+
for moneda in ws.ConsultarMonedas():
|
| 787 |
+
print(moneda)
|
| 788 |
+
except Exception as e:
|
| 789 |
+
raise
|
| 790 |
+
print("Exception", e)
|
| 791 |
+
print(ws.XmlRequest)
|
| 792 |
+
print(ws.XmlResponse)
|
| 793 |
+
|
| 794 |
+
if "--motivos_ex_djai" in sys.argv:
|
| 795 |
+
print(ws.ConsultarMotivosExcepcionDJAI())
|
| 796 |
+
if "--destinos_djai" in sys.argv:
|
| 797 |
+
print(ws.ConsultarDestinosCompraDJAI())
|
| 798 |
+
|
| 799 |
+
if "--consultar_cuit" in sys.argv:
|
| 800 |
+
print(ws.client.help("consultarCUIT"))
|
| 801 |
+
try:
|
| 802 |
+
print("Consultado CUITs....")
|
| 803 |
+
nro_doc = 26756539
|
| 804 |
+
tipo_doc = 96
|
| 805 |
+
print(ws.ConsultarCUIT(nro_doc, tipo_doc))
|
| 806 |
+
# recorro el detalle de los cuit devueltos:
|
| 807 |
+
while ws.LeerCUITConsultado():
|
| 808 |
+
print("CUIT", ws.CUITConsultada)
|
| 809 |
+
print("DenominaciοΏ½n", ws.DenominacionConsultada)
|
| 810 |
+
except Exception as e:
|
| 811 |
+
raise
|
| 812 |
+
print("Exception", e)
|
| 813 |
+
print(ws.XmlRequest)
|
| 814 |
+
print(ws.XmlResponse)
|
| 815 |
+
|
| 816 |
+
if "--prueba" in sys.argv:
|
| 817 |
+
print(ws.client.help("generarSolicitudCompraDivisa").encode("latin1"))
|
| 818 |
+
try:
|
| 819 |
+
cuit_comprador = 20267565393
|
| 820 |
+
codigo_moneda = 1
|
| 821 |
+
cotizacion_moneda = 4.26
|
| 822 |
+
monto_pesos = 100
|
| 823 |
+
cuit_representante = None
|
| 824 |
+
codigo_destino = 625
|
| 825 |
+
|
| 826 |
+
if "--loadxml" in sys.argv:
|
| 827 |
+
ws.LoadTestXML("wscoc_response.xml")
|
| 828 |
+
|
| 829 |
+
if not "--tur" in sys.argv:
|
| 830 |
+
djai = "--djai" in sys.argv and "12345DJAI000001N" or None
|
| 831 |
+
djas = "--djas" in sys.argv and "12001DJAS000901N" or None
|
| 832 |
+
cod_ex_djai = "--no-djai" and 3 or None
|
| 833 |
+
cod_ex_djas = "--no-djas" and 1 or None
|
| 834 |
+
if '--ref' in sys.argv:
|
| 835 |
+
tipo, codigo = 1, '12345DJAI000067C'
|
| 836 |
+
else:
|
| 837 |
+
tipo, codigo = None, None
|
| 838 |
+
print("djai", djai)
|
| 839 |
+
ok = ws.GenerarSolicitudCompraDivisa(cuit_comprador, codigo_moneda,
|
| 840 |
+
cotizacion_moneda, monto_pesos,
|
| 841 |
+
cuit_representante, codigo_destino,
|
| 842 |
+
djai=djai, codigo_excepcion_djai=cod_ex_djai,
|
| 843 |
+
djas=djas, codigo_excepcion_djas=cod_ex_djas,
|
| 844 |
+
tipo=tipo, codigo=codigo,)
|
| 845 |
+
else:
|
| 846 |
+
print("Turista!")
|
| 847 |
+
tipo_doc = 91
|
| 848 |
+
numero_doc = 1234567
|
| 849 |
+
apellido_nombre = "Nombre y Apellido del turista extranjero"
|
| 850 |
+
codigo_destino = 985
|
| 851 |
+
ok = ws.GenerarSolicitudCompraDivisaTurExt(
|
| 852 |
+
tipo_doc, numero_doc, apellido_nombre,
|
| 853 |
+
codigo_moneda, cotizacion_moneda, monto_pesos,
|
| 854 |
+
cuit_representante, codigo_destino,
|
| 855 |
+
)
|
| 856 |
+
while True:
|
| 857 |
+
i = ws.LeerInconsistencia()
|
| 858 |
+
if not i:
|
| 859 |
+
break
|
| 860 |
+
print("Inconsistencia...", i)
|
| 861 |
+
|
| 862 |
+
assert ok
|
| 863 |
+
print('Resultado', ws.Resultado)
|
| 864 |
+
assert ws.Resultado == 'A'
|
| 865 |
+
print('COC', ws.COC)
|
| 866 |
+
assert len(str(ws.COC)) == 12
|
| 867 |
+
print("FechaEmisionCOC", ws.FechaEmisionCOC)
|
| 868 |
+
print('CodigoSolicitud', ws.CodigoSolicitud)
|
| 869 |
+
assert ws.CodigoSolicitud is not None
|
| 870 |
+
print("EstadoSolicitud", ws.EstadoSolicitud)
|
| 871 |
+
assert ws.EstadoSolicitud == 'OT'
|
| 872 |
+
print("FechaEstado", ws.FechaEstado)
|
| 873 |
+
print("DetalleCUITComprador", ws.CUITComprador, ws.DenominacionComprador)
|
| 874 |
+
print("CodigoMoneda", ws.CodigoMoneda)
|
| 875 |
+
assert ws.CodigoMoneda == 1
|
| 876 |
+
print("CotizacionMoneda", ws.CotizacionMoneda)
|
| 877 |
+
assert round(ws.CotizacionMoneda, 2) == 4.26
|
| 878 |
+
print("MontoPesos", ws.MontoPesos)
|
| 879 |
+
assert ws.MontoPesos - monto_pesos <= 0.01
|
| 880 |
+
print("CodigoDestino", ws.CodigoDestino)
|
| 881 |
+
assert ws.CodigoDestino == codigo_destino
|
| 882 |
+
|
| 883 |
+
coc = ws.COC
|
| 884 |
+
codigo_solicitud = ws.CodigoSolicitud
|
| 885 |
+
# CO: confirmar, o 'DC' (desistio cliente) 'DB' (desistio banco)
|
| 886 |
+
nuevo_estado = 'CO'
|
| 887 |
+
ok = ws.InformarSolicitudCompraDivisa(codigo_solicitud, nuevo_estado)
|
| 888 |
+
assert ok
|
| 889 |
+
print('Resultado', ws.Resultado)
|
| 890 |
+
assert ws.Resultado == 'A'
|
| 891 |
+
print('COC', ws.COC)
|
| 892 |
+
assert ws.COC == coc
|
| 893 |
+
print("EstadoSolicitud", ws.EstadoSolicitud)
|
| 894 |
+
assert ws.EstadoSolicitud == nuevo_estado
|
| 895 |
+
|
| 896 |
+
ok = ws.AnularCOC(coc, cuit_comprador)
|
| 897 |
+
assert ok
|
| 898 |
+
print('Resultado', ws.Resultado)
|
| 899 |
+
assert ws.Resultado == 'A'
|
| 900 |
+
print('COC', ws.COC)
|
| 901 |
+
assert ws.COC == coc
|
| 902 |
+
print("EstadoSolicitud", ws.EstadoSolicitud)
|
| 903 |
+
assert ws.EstadoSolicitud == 'AN'
|
| 904 |
+
|
| 905 |
+
ok = ws.ConsultarSolicitudCompraDivisa(codigo_solicitud)
|
| 906 |
+
assert ok
|
| 907 |
+
print('CodigoSolicitud', ws.CodigoSolicitud)
|
| 908 |
+
assert ws.CodigoSolicitud == codigo_solicitud
|
| 909 |
+
print("EstadoSolicitud", ws.EstadoSolicitud)
|
| 910 |
+
assert ws.EstadoSolicitud == 'AN'
|
| 911 |
+
|
| 912 |
+
except BaseException:
|
| 913 |
+
print(ws.XmlRequest)
|
| 914 |
+
print(ws.XmlResponse)
|
| 915 |
+
print(ws.ErrCode)
|
| 916 |
+
print(ws.ErrMsg)
|
| 917 |
+
raise
|
| 918 |
+
|
| 919 |
+
if "--consultar_solicitudes" in sys.argv:
|
| 920 |
+
cuit_comprador = None
|
| 921 |
+
estado_solicitud = None
|
| 922 |
+
fecha_emision_desde = '2011-11-01'
|
| 923 |
+
fecha_emision_hasta = '2011-11-30'
|
| 924 |
+
sols = ws.ConsultarSolicitudesCompraDivisas(cuit_comprador,
|
| 925 |
+
estado_solicitud,
|
| 926 |
+
fecha_emision_desde,
|
| 927 |
+
fecha_emision_hasta,)
|
| 928 |
+
# muestro los resultados de la bοΏ½squeda
|
| 929 |
+
print("Solicitudes consultadas:")
|
| 930 |
+
for sol in sols:
|
| 931 |
+
print("CοΏ½digo de Solicitud:", sol)
|
| 932 |
+
# podrοΏ½a llamar a ws.ConsultarSolicitudCompraDivisa
|
| 933 |
+
print("hecho.")
|
| 934 |
+
|
| 935 |
+
ws.AnalizarXml("XmlResponse")
|
| 936 |
+
|
| 937 |
+
# recorro las solicitudes devueltas
|
| 938 |
+
i = 0
|
| 939 |
+
while ws.LeerSolicitudConsultada():
|
| 940 |
+
print("-" * 80)
|
| 941 |
+
coc = ws.ObtenerTagXml('arrayDetallesSolicitudes', 'detalleSolicitudes', i, 'coc')
|
| 942 |
+
cuit = ws.ObtenerTagXml('arrayDetallesSolicitudes', 'detalleSolicitudes', i, 'cuit')
|
| 943 |
+
p_assert_eq(coc, ws.COC)
|
| 944 |
+
print('CUIT', cuit)
|
| 945 |
+
print("FechaEmisionCOC", ws.FechaEmisionCOC)
|
| 946 |
+
print('CodigoSolicitud', ws.CodigoSolicitud)
|
| 947 |
+
print("EstadoSolicitud", ws.EstadoSolicitud)
|
| 948 |
+
print("FechaEstado", ws.FechaEstado)
|
| 949 |
+
print("DetalleCUITComprador", ws.CUITComprador, ws.DenominacionComprador)
|
| 950 |
+
print("CodigoMoneda", ws.CodigoMoneda)
|
| 951 |
+
print("CotizacionMoneda", ws.CotizacionMoneda)
|
| 952 |
+
print("MontoPesos", ws.MontoPesos)
|
| 953 |
+
print("CodigoDestino", ws.CodigoDestino)
|
| 954 |
+
print("=" * 80)
|
| 955 |
+
i = i + 1
|
| 956 |
+
|
| 957 |
+
if "--parametros" in sys.argv:
|
| 958 |
+
print("=== Tipos de Estado Solicitud ===")
|
| 959 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarTiposEstadoSolicitud(sep="||")]))
|
| 960 |
+
print("=== Monedas ===")
|
| 961 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarMonedas(sep="||")]))
|
| 962 |
+
print("=== Destinos de Compra ===")
|
| 963 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarDestinosCompra(sep="||")]))
|
| 964 |
+
print("=== Tipos de Documento ===")
|
| 965 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarTiposDocumento(sep="||")]))
|
| 966 |
+
print("=== Tipos Estado Solicitud ===")
|
| 967 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarTiposEstadoSolicitud(sep="||")]))
|
| 968 |
+
print("=== Motivos Excepcion DJAI ===")
|
| 969 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarMotivosExcepcionDJAI(sep="||")]))
|
| 970 |
+
print("=== Destinos Compra DJAI ===")
|
| 971 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarDestinosCompraDJAI(sep="||")]))
|
| 972 |
+
print("=== Motivos Excepcion DJAS ===")
|
| 973 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarMotivosExcepcionDJAS(sep="||")]))
|
| 974 |
+
print("=== Destinos Compra DJAS ===")
|
| 975 |
+
print('\n'.join(["||%s||" % s for s in ws.ConsultarDestinosCompraDJAS(sep="||")]))
|
| 976 |
+
|
| 977 |
+
if "--consultar_djai" in sys.argv:
|
| 978 |
+
djai = "12345DJAI000001N"
|
| 979 |
+
cuit = 20267565393
|
| 980 |
+
ws.ConsultarDJAI(djai, cuit)
|
| 981 |
+
print("DJAI", ws.DJAI)
|
| 982 |
+
print("Monto FOB", ws.MontoFOB)
|
| 983 |
+
print("Codigo Moneda", ws.CodigoMoneda)
|
| 984 |
+
print("Estado DJAI", ws.EstadoDJAI)
|
| 985 |
+
print("Errores", ws.Errores)
|
| 986 |
+
print("ErroresFormato", ws.ErroresFormato)
|
| 987 |
+
|
| 988 |
+
if "--consultar_djas" in sys.argv:
|
| 989 |
+
djas = "12001DJAS000901N"
|
| 990 |
+
cuit = 20267565393
|
| 991 |
+
ws.ConsultarDJAS(djas, cuit)
|
| 992 |
+
print("DJAS", ws.DJAS)
|
| 993 |
+
print("Monto FOB", ws.MontoFOB)
|
| 994 |
+
print("Codigo Moneda", ws.CodigoMoneda)
|
| 995 |
+
print("Estado DJAI", ws.EstadoDJAI)
|
| 996 |
+
print("Errores", ws.Errores)
|
| 997 |
+
print("ErroresFormato", ws.ErroresFormato)
|
| 998 |
+
|
| 999 |
+
if "--consultar_ref" in sys.argv:
|
| 1000 |
+
codigo = "12345DJAI000067C"
|
| 1001 |
+
cuit = 20267565393
|
| 1002 |
+
ws.ConsultarReferencia(1, codigo)
|
| 1003 |
+
print("Monto FOB", ws.MontoFOB)
|
| 1004 |
+
print("Codigo Moneda", ws.CodigoMoneda)
|
| 1005 |
+
print("Estado", ws.Estado)
|
| 1006 |
+
print("Errores", ws.Errores)
|
| 1007 |
+
print("ErroresFormato", ws.ErroresFormato)
|
| 1008 |
+
|
| 1009 |
+
if "--consultar_dest_ref" in sys.argv:
|
| 1010 |
+
print(ws.ConsultarDestinosCompraTipoReferencia(1))
|
| 1011 |
+
|
| 1012 |
+
if "--consultar_tipos_ref" in sys.argv:
|
| 1013 |
+
print(ws.ConsultarTiposReferencia())
|
| 1014 |
+
|
| 1015 |
+
|
| 1016 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 1017 |
+
if not hasattr(sys, "frozen"):
|
| 1018 |
+
basepath = __file__
|
| 1019 |
+
elif sys.frozen == 'dll':
|
| 1020 |
+
import win32api
|
| 1021 |
+
basepath = win32api.GetModuleFileName(sys.frozendllhandle)
|
| 1022 |
+
else:
|
| 1023 |
+
basepath = sys.executable
|
| 1024 |
+
INSTALL_DIR = os.path.dirname(os.path.abspath(basepath))
|
| 1025 |
+
|
| 1026 |
+
if __name__ == '__main__':
|
| 1027 |
+
|
| 1028 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 1029 |
+
import win32com.server.register
|
| 1030 |
+
win32com.server.register.UseCommandLine(WSCOC)
|
| 1031 |
+
else:
|
| 1032 |
+
main()
|
app/pyafipws/wsct.py
ADDED
|
@@ -0,0 +1,820 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"""MοΏ½dulo para obtener cοΏ½digo de autorizaciοΏ½n electrοΏ½nico CAE webservice
|
| 14 |
+
WSCT de AFIP (Factura ElectrοΏ½nica Comprobantes de Turismo)
|
| 15 |
+
ResoluciοΏ½n Conjunta General 3971 y ResoluciοΏ½n 566/2016.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 19 |
+
__copyright__ = "Copyright (C) 2017 Mariano Reingart"
|
| 20 |
+
__license__ = "GPL 3.0"
|
| 21 |
+
__version__ = "1.02c"
|
| 22 |
+
|
| 23 |
+
import datetime
|
| 24 |
+
import decimal
|
| 25 |
+
import os
|
| 26 |
+
import sys
|
| 27 |
+
from .utils import verifica, inicializar_y_capturar_excepciones, BaseWS, get_install_dir
|
| 28 |
+
|
| 29 |
+
HOMO = False
|
| 30 |
+
LANZAR_EXCEPCIONES = True
|
| 31 |
+
WSDL = "https://fwshomo.afip.gov.ar/wsct/CTService?wsdl"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class WSCT(BaseWS):
|
| 35 |
+
"Interfaz para el WebService de Factura ElectrοΏ½nica Comprobantes Turismo"
|
| 36 |
+
_public_methods_ = ['CrearFactura', 'EstablecerCampoFactura', 'AgregarIva', 'AgregarItem',
|
| 37 |
+
'AgregarTributo', 'AgregarCmpAsoc', 'EstablecerCampoItem',
|
| 38 |
+
'AgregarDatoAdicional', 'AgregarFormaPago',
|
| 39 |
+
'AutorizarComprobante', 'CAESolicitar',
|
| 40 |
+
'InformarCAEANoUtilizado', 'InformarCAEANoUtilizadoPtoVta',
|
| 41 |
+
'ConsultarUltimoComprobanteAutorizado', 'CompUltimoAutorizado',
|
| 42 |
+
'ConsultarPtosVtaCAEANoInformados',
|
| 43 |
+
'ConsultarComprobante',
|
| 44 |
+
'ConsultarTiposComprobante', 'ConsultarTiposDocumento',
|
| 45 |
+
'consultarTiposIVA', 'ConsultarCondicionesIVA',
|
| 46 |
+
'ConsultarMonedas', 'ConsultarCotizacion',
|
| 47 |
+
'ConsultarTiposItem', 'ConsultarCodigosItemTurismo',
|
| 48 |
+
'ConsultarTiposTributo',
|
| 49 |
+
'ConsultarCUITsPaises', 'ConsultarPaises',
|
| 50 |
+
'ConsultarTiposDatosAdicionales', 'ConsultarFomasPago',
|
| 51 |
+
'ConsultarTiposTarjeta', 'ConsultarTiposCuenta',
|
| 52 |
+
'ConsultarPuntosVenta',
|
| 53 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 54 |
+
'SetParametros', 'SetTicketAcceso', 'GetParametro',
|
| 55 |
+
'Dummy', 'Conectar', 'DebugLog', 'SetTicketAcceso']
|
| 56 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 57 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 58 |
+
'XmlRequest', 'XmlResponse', 'Version', 'InstallDir', 'LanzarExcepciones',
|
| 59 |
+
'Resultado', 'Obs', 'Observaciones', 'ErrCode', 'ErrMsg',
|
| 60 |
+
'EmisionTipo', 'Reproceso', 'Reprocesar', 'Evento',
|
| 61 |
+
'CAE', 'Vencimiento', 'Evento', 'Errores', 'Traceback', 'Excepcion',
|
| 62 |
+
'CAEA', 'Periodo', 'Orden', 'FchVigDesde', 'FchVigHasta', 'FchTopeInf', 'FchProceso',
|
| 63 |
+
'CbteNro', 'FechaCbte', 'PuntoVenta', 'ImpTotal']
|
| 64 |
+
|
| 65 |
+
_reg_progid_ = "WSCT"
|
| 66 |
+
_reg_clsid_ = "{5DE7917D-CE97-4C88-B6C7-DAF8CEB54E93}"
|
| 67 |
+
|
| 68 |
+
# Variables globales para BaseWS:
|
| 69 |
+
HOMO = HOMO
|
| 70 |
+
WSDL = WSDL
|
| 71 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '')
|
| 72 |
+
Reprocesar = True # recuperar automaticamente CAE emitidos
|
| 73 |
+
LanzarExcepciones = LANZAR_EXCEPCIONES
|
| 74 |
+
factura = None
|
| 75 |
+
|
| 76 |
+
def inicializar(self):
|
| 77 |
+
BaseWS.inicializar(self)
|
| 78 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 79 |
+
self.Resultado = self.Motivo = self.Reproceso = ''
|
| 80 |
+
self.LastID = self.LastCMP = self.CAE = self.Vencimiento = ''
|
| 81 |
+
self.CAEA = None
|
| 82 |
+
self.Periodo = self.Orden = ""
|
| 83 |
+
self.FchVigDesde = self.FchVigHasta = ""
|
| 84 |
+
self.FchTopeInf = self.FchProceso = ""
|
| 85 |
+
self.CbteNro = self.FechaCbte = self.ImpTotal = None
|
| 86 |
+
self.PuntoVenta = self.EmisionTipo = self.Evento = ''
|
| 87 |
+
self.Reproceso = '' # no implementado
|
| 88 |
+
|
| 89 |
+
def __analizar_errores(self, ret):
|
| 90 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 91 |
+
for key in ('arrayErrores', 'arrayErroresFormato'):
|
| 92 |
+
errores = ret.get(key, [])
|
| 93 |
+
for error in errores:
|
| 94 |
+
for k in ('codigoDescripcion', 'codigoDescripcionString'):
|
| 95 |
+
err = error.get(k)
|
| 96 |
+
if err:
|
| 97 |
+
break
|
| 98 |
+
self.Errores.append("%s: %s" % (
|
| 99 |
+
err['codigo'],
|
| 100 |
+
err['descripcion'],
|
| 101 |
+
))
|
| 102 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 103 |
+
|
| 104 |
+
def Dummy(self):
|
| 105 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 106 |
+
ret = self.client.dummy()
|
| 107 |
+
result = ret['dummyReturn']
|
| 108 |
+
self.AppServerStatus = result['appserver']
|
| 109 |
+
self.DbServerStatus = result['dbserver']
|
| 110 |
+
self.AuthServerStatus = result['authserver']
|
| 111 |
+
return True
|
| 112 |
+
|
| 113 |
+
def CrearFactura(self, tipo_doc=None, nro_doc=None, tipo_cbte=None, punto_vta=None,
|
| 114 |
+
cbte_nro=None, imp_total=None, imp_tot_conc=None, imp_neto=None,
|
| 115 |
+
imp_subtotal=None, imp_trib=None, imp_op_ex=None, imp_reintegro=None,
|
| 116 |
+
fecha_cbte=None, id_impositivo="", cod_pais=None, domicilio="", cod_relacion="",
|
| 117 |
+
moneda_id=None, moneda_ctz=None, observaciones=None,
|
| 118 |
+
**kwargs
|
| 119 |
+
):
|
| 120 |
+
"Creo un objeto factura (interna)"
|
| 121 |
+
# Creo una factura electronica de exportaciοΏ½n
|
| 122 |
+
fact = {'tipo_doc': tipo_doc, 'nro_doc': nro_doc,
|
| 123 |
+
'tipo_cbte': tipo_cbte, 'punto_vta': punto_vta,
|
| 124 |
+
'cbte_nro': cbte_nro,
|
| 125 |
+
'id_impositivo': id_impositivo,
|
| 126 |
+
'cod_pais': cod_pais,
|
| 127 |
+
'domicilio': domicilio,
|
| 128 |
+
'cod_relacion': cod_relacion,
|
| 129 |
+
'imp_total': imp_total, 'imp_tot_conc': imp_tot_conc,
|
| 130 |
+
'imp_neto': imp_neto,
|
| 131 |
+
'imp_subtotal': imp_subtotal, # 'imp_iva': imp_iva,
|
| 132 |
+
'imp_trib': imp_trib, 'imp_op_ex': imp_op_ex,
|
| 133 |
+
'imp_reintegro': imp_reintegro,
|
| 134 |
+
'fecha_cbte': fecha_cbte,
|
| 135 |
+
'moneda_id': moneda_id, 'moneda_ctz': moneda_ctz,
|
| 136 |
+
'observaciones': observaciones,
|
| 137 |
+
'cbtes_asoc': [],
|
| 138 |
+
'tributos': [],
|
| 139 |
+
'iva': [],
|
| 140 |
+
'detalles': [],
|
| 141 |
+
'adicionales': [],
|
| 142 |
+
'formas_pago': [],
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
self.factura = fact
|
| 146 |
+
return True
|
| 147 |
+
|
| 148 |
+
def EstablecerCampoFactura(self, campo, valor):
|
| 149 |
+
if campo in self.factura or campo in ('fecha_serv_desde', 'fecha_serv_hasta', 'caea', 'fch_venc_cae'):
|
| 150 |
+
self.factura[campo] = valor
|
| 151 |
+
return True
|
| 152 |
+
else:
|
| 153 |
+
return False
|
| 154 |
+
|
| 155 |
+
def AgregarCmpAsoc(self, tipo=1, pto_vta=0, nro=0, cuit=None, **kwargs):
|
| 156 |
+
"Agrego un comprobante asociado a una factura (interna)"
|
| 157 |
+
cmp_asoc = {
|
| 158 |
+
'tipo': tipo,
|
| 159 |
+
'pto_vta': pto_vta,
|
| 160 |
+
'nro': nro}
|
| 161 |
+
if cuit is not None:
|
| 162 |
+
cmp_asoc['cuit'] = cuit
|
| 163 |
+
self.factura['cbtes_asoc'].append(cmp_asoc)
|
| 164 |
+
return True
|
| 165 |
+
|
| 166 |
+
def AgregarTributo(self, tributo_id, desc, base_imp, alic, importe, **kwargs):
|
| 167 |
+
"Agrego un tributo a una factura (interna)"
|
| 168 |
+
tributo = {
|
| 169 |
+
'tributo_id': tributo_id,
|
| 170 |
+
'desc': desc,
|
| 171 |
+
'base_imp': base_imp,
|
| 172 |
+
'importe': importe,
|
| 173 |
+
}
|
| 174 |
+
self.factura['tributos'].append(tributo)
|
| 175 |
+
return True
|
| 176 |
+
|
| 177 |
+
def AgregarIva(self, iva_id, base_imp, importe, **kwargs):
|
| 178 |
+
"Agrego un tributo a una factura (interna)"
|
| 179 |
+
iva = {
|
| 180 |
+
'iva_id': iva_id,
|
| 181 |
+
'importe': importe,
|
| 182 |
+
}
|
| 183 |
+
self.factura['iva'].append(iva)
|
| 184 |
+
return True
|
| 185 |
+
|
| 186 |
+
def AgregarItem(self, tipo=None, cod_tur=None,
|
| 187 |
+
codigo=None, ds=None,
|
| 188 |
+
iva_id=None, imp_iva=None, imp_subtotal=None, **kwargs):
|
| 189 |
+
"Agrego un item a una factura (interna)"
|
| 190 |
+
# ds = unicode(ds, "latin1") # convierto a latin1
|
| 191 |
+
# Nota: no se calcula neto, iva, etc (deben venir calculados!)
|
| 192 |
+
tipo = int(tipo)
|
| 193 |
+
if tipo == 99:
|
| 194 |
+
imp_subtotal = -abs(float(imp_subtotal))
|
| 195 |
+
imp_iva = -abs(float(imp_iva))
|
| 196 |
+
item = {
|
| 197 |
+
'tipo': tipo,
|
| 198 |
+
'cod_tur': cod_tur,
|
| 199 |
+
'codigo': codigo,
|
| 200 |
+
'ds': ds,
|
| 201 |
+
'iva_id': iva_id,
|
| 202 |
+
'imp_iva': imp_iva,
|
| 203 |
+
'imp_subtotal': imp_subtotal,
|
| 204 |
+
}
|
| 205 |
+
self.factura['detalles'].append(item)
|
| 206 |
+
return True
|
| 207 |
+
|
| 208 |
+
def AgregarDatoAdicional(self, t, c1, c2, c3, c4, c5, c6, **kwarg):
|
| 209 |
+
"Agrego un tipo de dato adicional a una factura (interna)"
|
| 210 |
+
op = {'t': t,
|
| 211 |
+
'c1': c1, 'c2': c2, 'c3': c3, 'c4': c4, 'c5': c5, 'c6': c6, }
|
| 212 |
+
self.factura['adicionales'].append(op)
|
| 213 |
+
return True
|
| 214 |
+
|
| 215 |
+
def AgregarFormaPago(self, codigo, tipo_tarjeta=None, numero_tarjeta=None,
|
| 216 |
+
swift_code=None, tipo_cuenta=None, numero_cuenta=None,
|
| 217 |
+
**kwarg):
|
| 218 |
+
"Agrego una forma de pago a una factura (interna)"
|
| 219 |
+
fp = {'codigo': codigo, 'tipo_tarjeta': tipo_tarjeta,
|
| 220 |
+
'numero_tarjeta': numero_tarjeta, 'swift_code': swift_code,
|
| 221 |
+
'tipo_cuenta': tipo_cuenta, 'numero_cuenta': numero_cuenta}
|
| 222 |
+
self.factura['formas_pago'].append(fp)
|
| 223 |
+
return True
|
| 224 |
+
|
| 225 |
+
def EstablecerCampoItem(self, campo, valor):
|
| 226 |
+
if self.factura['detalles'] and campo in self.factura['detalles'][-1]:
|
| 227 |
+
self.factura['detalles'][-1][campo] = valor
|
| 228 |
+
return True
|
| 229 |
+
else:
|
| 230 |
+
return False
|
| 231 |
+
|
| 232 |
+
@inicializar_y_capturar_excepciones
|
| 233 |
+
def AutorizarComprobante(self):
|
| 234 |
+
f = self.factura
|
| 235 |
+
# contruyo la estructura a convertir en XML:
|
| 236 |
+
fact = {
|
| 237 |
+
'codigoTipoDocumento': f['tipo_doc'], 'numeroDocumento': f['nro_doc'],
|
| 238 |
+
'codigoTipoComprobante': f['tipo_cbte'], 'numeroPuntoVenta': f['punto_vta'],
|
| 239 |
+
'codigoTipoAutorizacion': 'E',
|
| 240 |
+
'numeroComprobante': f['cbte_nro'],
|
| 241 |
+
'importeTotal': f['imp_total'], 'importeNoGravado': f['imp_tot_conc'],
|
| 242 |
+
'idImpositivo': f['id_impositivo'],
|
| 243 |
+
'codigoPais': f['cod_pais'], 'domicilioReceptor': f['domicilio'],
|
| 244 |
+
'codigoRelacionEmisorReceptor': f['cod_relacion'],
|
| 245 |
+
'importeGravado': f['imp_neto'],
|
| 246 |
+
'importeSubtotal': f['imp_subtotal'], # 'imp_iva': imp_iva,
|
| 247 |
+
'importeOtrosTributos': f['tributos'] and f['imp_trib'] or None,
|
| 248 |
+
'importeExento': f['imp_op_ex'], 'importeReintegro': f['imp_reintegro'],
|
| 249 |
+
'fechaEmision': f['fecha_cbte'],
|
| 250 |
+
'codigoMoneda': f['moneda_id'], 'cotizacionMoneda': f['moneda_ctz'],
|
| 251 |
+
'observaciones': f['observaciones'],
|
| 252 |
+
'fechaVencimientoPago': f.get('fecha_venc_pago'),
|
| 253 |
+
'fechaServicioDesde': f.get('fecha_serv_desde'),
|
| 254 |
+
'fechaServicioHasta': f.get('fecha_serv_hasta'),
|
| 255 |
+
'arrayComprobantesAsociados': f['cbtes_asoc'] and [{'comprobanteAsociado': {
|
| 256 |
+
'codigoTipoComprobante': cbte_asoc['tipo'],
|
| 257 |
+
'numeroPuntoVenta': cbte_asoc['pto_vta'],
|
| 258 |
+
'numeroComprobante': cbte_asoc['nro'],
|
| 259 |
+
}} for cbte_asoc in f['cbtes_asoc']] or None,
|
| 260 |
+
'arrayOtrosTributos': f['tributos'] and [{'otroTributo': {
|
| 261 |
+
'codigo': tributo['tributo_id'],
|
| 262 |
+
'descripcion': tributo['desc'],
|
| 263 |
+
'baseImponible': tributo['base_imp'],
|
| 264 |
+
'importe': tributo['importe'],
|
| 265 |
+
}} for tributo in f['tributos']] or None,
|
| 266 |
+
'arraySubtotalesIVA': f['iva'] and [{'subtotalIVA': {
|
| 267 |
+
'codigo': iva['iva_id'],
|
| 268 |
+
'importe': iva['importe'],
|
| 269 |
+
}} for iva in f['iva']] or None,
|
| 270 |
+
'arrayItems': f['detalles'] and [{'item': {
|
| 271 |
+
'tipo': it['tipo'],
|
| 272 |
+
'codigoTurismo': it['cod_tur'],
|
| 273 |
+
'codigo': it['codigo'],
|
| 274 |
+
'descripcion': it['ds'],
|
| 275 |
+
'codigoAlicuotaIVA': it['iva_id'],
|
| 276 |
+
'importeIVA': it['imp_iva'] if int(f['tipo_cbte']) not in (6, 7, 8) and it['imp_iva'] is not None else None,
|
| 277 |
+
'importeItem': it['imp_subtotal'],
|
| 278 |
+
}} for it in f['detalles']] or None,
|
| 279 |
+
'arrayDatosAdicionales': [
|
| 280 |
+
{'tipoDatoAdicional': ta} for ta in f['adicionales']] or None,
|
| 281 |
+
'arrayFormasPago': [
|
| 282 |
+
{'formaPago': {
|
| 283 |
+
'codigo': fp['codigo'],
|
| 284 |
+
'tipoTarjeta': fp['tipo_tarjeta'],
|
| 285 |
+
'numeroTarjeta': fp['numero_tarjeta'],
|
| 286 |
+
'swiftCode': fp['swift_code'],
|
| 287 |
+
'tipoCuenta': fp['tipo_cuenta'],
|
| 288 |
+
'numeroCuenta': fp['numero_cuenta'],
|
| 289 |
+
}} for fp in f['formas_pago']] or None,
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
res = self.client.autorizarComprobante(
|
| 293 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 294 |
+
comprobanteRequest=fact,
|
| 295 |
+
)
|
| 296 |
+
ret = res.get('autorizarComprobanteReturn', {})
|
| 297 |
+
|
| 298 |
+
# Reprocesar en caso de error (recuperar CAE emitido anteriormente)
|
| 299 |
+
if self.Reprocesar and ('arrayErrores' in ret):
|
| 300 |
+
for error in ret['arrayErrores']:
|
| 301 |
+
err_code = error['codigoDescripcion']['codigo']
|
| 302 |
+
if ret['resultado'] == 'R' and err_code == 102:
|
| 303 |
+
# guardo los mensajes xml originales
|
| 304 |
+
xml_request = self.client.xml_request
|
| 305 |
+
xml_response = self.client.xml_response
|
| 306 |
+
cae = self.ConsultarComprobante(f['tipo_cbte'], f['punto_vta'], f['cbt_desde'], reproceso=True)
|
| 307 |
+
if cae and self.EmisionTipo == 'CAE':
|
| 308 |
+
self.Reproceso = 'S'
|
| 309 |
+
self.Resultado = 'A' # verificar O
|
| 310 |
+
return cae
|
| 311 |
+
self.Reproceso = 'N'
|
| 312 |
+
# reestablesco los mensajes xml originales
|
| 313 |
+
self.client.xml_request = xml_request
|
| 314 |
+
self.client.xml_response = xml_response
|
| 315 |
+
|
| 316 |
+
self.Resultado = ret.get('resultado', "") # u'A'
|
| 317 |
+
if self.Resultado in ("A", "O"):
|
| 318 |
+
cbteresp = ret['comprobanteResponse']
|
| 319 |
+
self.FechaCbte = cbteresp['fechaEmision'].strftime("%Y/%m/%d")
|
| 320 |
+
self.CbteNro = cbteresp['numeroComprobante'] # 1L
|
| 321 |
+
self.PuntoVenta = cbteresp['numeroPuntoVenta'] # 4000
|
| 322 |
+
# self. = cbteresp['cuit'] # 20267565393L
|
| 323 |
+
# self. = cbteresp['codigoTipoComprobante']
|
| 324 |
+
self.Vencimiento = cbteresp['fechaVencimientoCAE'].strftime("%Y/%m/%d")
|
| 325 |
+
self.CAE = str(cbteresp['CAE']) # 60423794871430L
|
| 326 |
+
self.__analizar_errores(ret)
|
| 327 |
+
|
| 328 |
+
for error in ret.get('arrayObservaciones', []):
|
| 329 |
+
self.Observaciones.append("%(codigo)s: %(descripcion)s" % (
|
| 330 |
+
error['codigoDescripcion']))
|
| 331 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 332 |
+
self.EmisionTipo = 'CAE'
|
| 333 |
+
|
| 334 |
+
if 'evento' in ret:
|
| 335 |
+
self.Evento = '%(codigo)s: %(descripcion)s' % ret['evento']
|
| 336 |
+
return self.CAE
|
| 337 |
+
|
| 338 |
+
@inicializar_y_capturar_excepciones
|
| 339 |
+
def CAESolicitar(self):
|
| 340 |
+
try:
|
| 341 |
+
cae = self.AutorizarComprobante() or ''
|
| 342 |
+
self.Excepcion = "OK!"
|
| 343 |
+
except BaseException:
|
| 344 |
+
cae = "ERR"
|
| 345 |
+
finally:
|
| 346 |
+
return cae
|
| 347 |
+
|
| 348 |
+
@inicializar_y_capturar_excepciones
|
| 349 |
+
def ConsultarUltimoComprobanteAutorizado(self, tipo_cbte, punto_vta):
|
| 350 |
+
res = self.client.consultarUltimoComprobanteAutorizado(
|
| 351 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 352 |
+
codigoTipoComprobante=tipo_cbte,
|
| 353 |
+
numeroPuntoVenta=punto_vta,
|
| 354 |
+
)
|
| 355 |
+
ret = res.get('consultarUltimoComprobanteAutorizadoReturn', {})
|
| 356 |
+
nro = ret.get('numeroComprobante')
|
| 357 |
+
self.__analizar_errores(ret)
|
| 358 |
+
self.CbteNro = nro
|
| 359 |
+
return nro is not None and str(nro) or 0
|
| 360 |
+
|
| 361 |
+
CompUltimoAutorizado = ConsultarUltimoComprobanteAutorizado
|
| 362 |
+
|
| 363 |
+
@inicializar_y_capturar_excepciones
|
| 364 |
+
def ConsultarComprobante(self, tipo_cbte, punto_vta, cbte_nro, reproceso=False):
|
| 365 |
+
"Recuperar los datos completos de un comprobante ya autorizado"
|
| 366 |
+
res = self.client.consultarComprobanteTipoPVentaNro(
|
| 367 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 368 |
+
codigoTipoComprobante=tipo_cbte,
|
| 369 |
+
numeroPuntoVenta=punto_vta,
|
| 370 |
+
numeroComprobante=cbte_nro,
|
| 371 |
+
)
|
| 372 |
+
ret = res.get('consultarComprobanteReturn', {})
|
| 373 |
+
# diferencias si hay reproceso:
|
| 374 |
+
difs = []
|
| 375 |
+
# analizo el resultado:
|
| 376 |
+
if 'comprobante' in ret:
|
| 377 |
+
cbteresp = ret['comprobante']
|
| 378 |
+
if reproceso:
|
| 379 |
+
# verifico los campos registrados coincidan con los enviados:
|
| 380 |
+
f = self.factura
|
| 381 |
+
verificaciones = {
|
| 382 |
+
'codigoTipoComprobante': f['tipo_cbte'],
|
| 383 |
+
'numeroPuntoVenta': f['punto_vta'],
|
| 384 |
+
'codigoTipoDocumento': f['tipo_doc'],
|
| 385 |
+
'numeroDocumento': f['nro_doc'],
|
| 386 |
+
'numeroComprobante': f['cbt_desde'],
|
| 387 |
+
'numeroComprobante': f['cbt_hasta'],
|
| 388 |
+
'fechaEmision': f['fecha_cbte'],
|
| 389 |
+
'idImpositivo': f['id_impositivo'],
|
| 390 |
+
'codigoPais': f['cod_pais'], 'domicilioReceptor': f['domicilio'],
|
| 391 |
+
'codigoRelacionEmisorReceptor': f['cod_relacion'],
|
| 392 |
+
'importeTotal': decimal.Decimal(str(f['imp_total'])),
|
| 393 |
+
'importeNoGravado': decimal.Decimal(str(f['imp_tot_conc'])),
|
| 394 |
+
'importeGravado': decimal.Decimal(str(f['imp_neto'])),
|
| 395 |
+
'importeExento': decimal.Decimal(str(f['imp_op_ex'])),
|
| 396 |
+
'importeOtrosTributos': f['tributos'] and decimal.Decimal(str(f['imp_trib'])) or None,
|
| 397 |
+
'importeSubtotal': f['imp_subtotal'],
|
| 398 |
+
'importeReintegro': f['imp_reintegro'],
|
| 399 |
+
'codigoMoneda': f['moneda_id'],
|
| 400 |
+
'cotizacionMoneda': str(decimal.Decimal(str(f['moneda_ctz']))),
|
| 401 |
+
'arrayItems': [
|
| 402 |
+
{'item': {
|
| 403 |
+
'tipo': it['tipo'],
|
| 404 |
+
'codigoTurismo': it['cod_tur'],
|
| 405 |
+
'codigo': it['codigo'],
|
| 406 |
+
'descripcion': it['ds'],
|
| 407 |
+
'codigoAlicuotaIVA': decimal.Decimal(str(it['iva_id'])),
|
| 408 |
+
'importeIVA': decimal.Decimal(str(it['imp_iva'])) if int(f['tipo_cbte']) not in (6, 7, 8) and it['imp_iva'] is not None else None,
|
| 409 |
+
'importeItem': decimal.Decimal(str(it['imp_subtotal'])),
|
| 410 |
+
}}
|
| 411 |
+
for it in f['detalles']],
|
| 412 |
+
'arrayComprobantesAsociados': [
|
| 413 |
+
{'comprobanteAsociado': {
|
| 414 |
+
'codigoTipoComprobante': cbte_asoc['tipo'],
|
| 415 |
+
'numeroPuntoVenta': cbte_asoc['pto_vta'],
|
| 416 |
+
'numeroComprobante': cbte_asoc['nro']}}
|
| 417 |
+
for cbte_asoc in f['cbtes_asoc']],
|
| 418 |
+
'arrayOtrosTributos': [
|
| 419 |
+
{'otroTributo': {
|
| 420 |
+
'codigo': tributo['tributo_id'],
|
| 421 |
+
'descripcion': tributo['desc'],
|
| 422 |
+
'baseImponible': decimal.Decimal(str(tributo['base_imp'])),
|
| 423 |
+
'importe': decimal.Decimal(str(tributo['importe'])),
|
| 424 |
+
}}
|
| 425 |
+
for tributo in f['tributos']],
|
| 426 |
+
'arraySubtotalesIVA': [
|
| 427 |
+
{'subtotalIVA': {
|
| 428 |
+
'codigo': iva['iva_id'],
|
| 429 |
+
'importe': decimal.Decimal(str(iva['importe'])),
|
| 430 |
+
}}
|
| 431 |
+
for iva in f['iva']],
|
| 432 |
+
}
|
| 433 |
+
verifica(verificaciones, cbteresp, difs)
|
| 434 |
+
if difs:
|
| 435 |
+
print("Diferencias:", difs)
|
| 436 |
+
self.log("Diferencias: %s" % difs)
|
| 437 |
+
self.FechaCbte = cbteresp['fechaEmision'].strftime("%Y/%m/%d")
|
| 438 |
+
self.CbteNro = cbteresp['numeroComprobante'] # 1L
|
| 439 |
+
self.PuntoVenta = cbteresp['numeroPuntoVenta'] # 4000
|
| 440 |
+
self.Vencimiento = cbteresp['fechaVencimiento'].strftime("%Y/%m/%d")
|
| 441 |
+
self.ImpTotal = str(cbteresp['importeTotal'])
|
| 442 |
+
self.CAE = str(cbteresp['codigoAutorizacion']) # 60423794871430L
|
| 443 |
+
self.EmisionTipo = cbteresp['codigoTipoAutorizacion'] == 'A' and 'CAEA' or 'CAE'
|
| 444 |
+
self.__analizar_errores(ret)
|
| 445 |
+
if not difs:
|
| 446 |
+
return self.CAE
|
| 447 |
+
|
| 448 |
+
@inicializar_y_capturar_excepciones
|
| 449 |
+
def ConsultarTiposComprobante(self):
|
| 450 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 451 |
+
res = self.client.consultarTiposComprobantes(
|
| 452 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 453 |
+
)
|
| 454 |
+
ret = res['consultarTiposComprobantesReturn']
|
| 455 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 456 |
+
for p in ret['arrayTiposComprobantes']]
|
| 457 |
+
|
| 458 |
+
@inicializar_y_capturar_excepciones
|
| 459 |
+
def ConsultarTiposDocumento(self):
|
| 460 |
+
res = self.client.consultarTiposDocumento(
|
| 461 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 462 |
+
)
|
| 463 |
+
ret = res['consultarTiposDocumentoReturn']
|
| 464 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 465 |
+
for p in ret['arrayTiposDocumento']]
|
| 466 |
+
|
| 467 |
+
@inicializar_y_capturar_excepciones
|
| 468 |
+
def ConsultarTiposIVA(self):
|
| 469 |
+
"Este mοΏ½todo permite consultar los tipos de IVA habilitados en este ws"
|
| 470 |
+
res = self.client.consultarTiposIVA(
|
| 471 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 472 |
+
)
|
| 473 |
+
ret = res['consultarTiposIVAReturn']
|
| 474 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcionString']
|
| 475 |
+
for p in ret['arrayTiposIVA']]
|
| 476 |
+
|
| 477 |
+
@inicializar_y_capturar_excepciones
|
| 478 |
+
def ConsultarCondicionesIVA(self):
|
| 479 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 480 |
+
res = self.client.consultarCondicionesIVA(
|
| 481 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 482 |
+
)
|
| 483 |
+
ret = res['consultarCondicionesIVAReturn']
|
| 484 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcionString']
|
| 485 |
+
for p in ret['arrayCondicionesIVA']]
|
| 486 |
+
|
| 487 |
+
@inicializar_y_capturar_excepciones
|
| 488 |
+
def ConsultarMonedas(self):
|
| 489 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 490 |
+
res = self.client.consultarMonedas(
|
| 491 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 492 |
+
)
|
| 493 |
+
ret = res['consultarMonedasReturn']
|
| 494 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcionString']
|
| 495 |
+
for p in ret['arrayTiposMoneda']]
|
| 496 |
+
|
| 497 |
+
@inicializar_y_capturar_excepciones
|
| 498 |
+
def ConsultarTiposItem(self):
|
| 499 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 500 |
+
res = self.client.consultarTiposItem(
|
| 501 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 502 |
+
)
|
| 503 |
+
ret = res['consultarTiposItemReturn']
|
| 504 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 505 |
+
for p in ret['arrayTiposItem']]
|
| 506 |
+
|
| 507 |
+
@inicializar_y_capturar_excepciones
|
| 508 |
+
def ConsultarCodigosItemTurismo(self):
|
| 509 |
+
"Este mοΏ½todo permite consultar los cοΏ½digos de los οΏ½tems de Turismo"
|
| 510 |
+
res = self.client.consultarCodigosItemTurismo(
|
| 511 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 512 |
+
)
|
| 513 |
+
ret = res['consultarCodigosItemTurismoReturn']
|
| 514 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 515 |
+
for p in ret['arrayCodigosItem']]
|
| 516 |
+
|
| 517 |
+
@inicializar_y_capturar_excepciones
|
| 518 |
+
def ConsultarTiposTributo(self):
|
| 519 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 520 |
+
res = self.client.consultarTiposTributo(
|
| 521 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 522 |
+
)
|
| 523 |
+
ret = res['consultarTiposTributoReturn']
|
| 524 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcionString']
|
| 525 |
+
for p in ret['arrayTiposTributo']]
|
| 526 |
+
|
| 527 |
+
@inicializar_y_capturar_excepciones
|
| 528 |
+
def ConsultarCotizacion(self, moneda_id):
|
| 529 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 530 |
+
ret = self.client.consultarCotizacion(
|
| 531 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 532 |
+
codigoMoneda=moneda_id,
|
| 533 |
+
)
|
| 534 |
+
self.__analizar_errores(ret)
|
| 535 |
+
if 'cotizacionMoneda' in ret:
|
| 536 |
+
return str(ret['cotizacionMoneda'])
|
| 537 |
+
|
| 538 |
+
@inicializar_y_capturar_excepciones
|
| 539 |
+
def ConsultarPuntosVenta(self, fmt="%(numeroPuntoVenta)s: bloqueado=%(bloqueado)s baja=%(fechaBaja)s"):
|
| 540 |
+
"Este mοΏ½todo permite consultar los puntos de venta habilitados para CAE en este WS"
|
| 541 |
+
res = self.client.consultarPuntosVenta(
|
| 542 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 543 |
+
)
|
| 544 |
+
ret = []
|
| 545 |
+
self.__analizar_errores(ret)
|
| 546 |
+
for p in res['consultarPuntosVentaReturn'].get("arrayPuntosVenta", {}):
|
| 547 |
+
p = p['puntoVenta']
|
| 548 |
+
if 'fechaBaja' not in p:
|
| 549 |
+
p['fechaBaja'] = ""
|
| 550 |
+
ret.append(fmt % p if fmt else p)
|
| 551 |
+
return ret
|
| 552 |
+
|
| 553 |
+
@inicializar_y_capturar_excepciones
|
| 554 |
+
def ConsultarPaises(self, sep="|"):
|
| 555 |
+
"Recuperador de valores referenciales de cοΏ½digos de PaοΏ½ses"
|
| 556 |
+
ret = self.client.consultarPaises(
|
| 557 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit, })
|
| 558 |
+
result = ret['consultarPaisesReturn']
|
| 559 |
+
self.__analizar_errores(result)
|
| 560 |
+
|
| 561 |
+
ret = []
|
| 562 |
+
for u in result['arrayPaises']:
|
| 563 |
+
u = u['codigoDescripcionString']
|
| 564 |
+
try:
|
| 565 |
+
r = {'codigo': u.get('codigo'), 'ds': u.get('descripcion'), }
|
| 566 |
+
except Exception as e:
|
| 567 |
+
print(e)
|
| 568 |
+
|
| 569 |
+
ret.append(r)
|
| 570 |
+
if sep:
|
| 571 |
+
return [("\t%(codigo)s\t%(ds)s\t"
|
| 572 |
+
% it).replace("\t", sep) for it in ret]
|
| 573 |
+
else:
|
| 574 |
+
return ret
|
| 575 |
+
|
| 576 |
+
@inicializar_y_capturar_excepciones
|
| 577 |
+
def ConsultarCUITsPaises(self, sep="|"):
|
| 578 |
+
"Recuperar lista de valores referenciales de CUIT de PaοΏ½ses"
|
| 579 |
+
ret = self.client.consultarCUITsPaises(
|
| 580 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit, })
|
| 581 |
+
result = ret['consultarCUITsPaisesReturn']
|
| 582 |
+
self.__analizar_errores(result)
|
| 583 |
+
|
| 584 |
+
ret = []
|
| 585 |
+
for u in result['arrayCuitPaises']:
|
| 586 |
+
u = u['codigoDescripcionString']
|
| 587 |
+
try:
|
| 588 |
+
r = {'codigo': u.get('codigo'), 'ds': u.get('descripcion'), }
|
| 589 |
+
except Exception as e:
|
| 590 |
+
print(e)
|
| 591 |
+
|
| 592 |
+
ret.append(r)
|
| 593 |
+
if sep:
|
| 594 |
+
return [("\t%(codigo)s\t%(ds)s\t"
|
| 595 |
+
% it).replace("\t", sep) for it in ret]
|
| 596 |
+
else:
|
| 597 |
+
return ret
|
| 598 |
+
|
| 599 |
+
@inicializar_y_capturar_excepciones
|
| 600 |
+
def ConsultarTiposDatosAdicionales(self, sep="|"):
|
| 601 |
+
"Recuperar lista de los datos adicionales a informar segοΏ½n RG."
|
| 602 |
+
ret = self.client.consultarTiposDatosAdicionales(
|
| 603 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit, })
|
| 604 |
+
result = ret['consultarTiposDatosAdicionalesReturn']
|
| 605 |
+
self.__analizar_errores(result)
|
| 606 |
+
ret = []
|
| 607 |
+
for u in result['arrayTiposDatosAdicionales']:
|
| 608 |
+
u = u['codigoDescripcionString']
|
| 609 |
+
r = {'codigo': u.get('codigo'), 'ds': u.get('descripcion'), }
|
| 610 |
+
ret.append(r)
|
| 611 |
+
return [("\t%(codigo)s\t%(ds)s\t"
|
| 612 |
+
% it).replace("\t", sep) for it in ret] if sep else ret
|
| 613 |
+
|
| 614 |
+
@inicializar_y_capturar_excepciones
|
| 615 |
+
def ConsultarFomasPago(self, sep="|"):
|
| 616 |
+
"Recuperar lista de las formas de pago"
|
| 617 |
+
ret = self.client.consultarFormasPago(
|
| 618 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit, })
|
| 619 |
+
result = ret['consultarFormasPagoReturn']
|
| 620 |
+
self.__analizar_errores(result)
|
| 621 |
+
ret = []
|
| 622 |
+
for u in result['arrayFormasPago']:
|
| 623 |
+
u = u['codigoDescripcion']
|
| 624 |
+
r = {'codigo': u.get('codigo'), 'ds': u.get('descripcion'), }
|
| 625 |
+
ret.append(r)
|
| 626 |
+
return [("\t%(codigo)s\t%(ds)s\t"
|
| 627 |
+
% it).replace("\t", sep) for it in ret] if sep else ret
|
| 628 |
+
|
| 629 |
+
@inicializar_y_capturar_excepciones
|
| 630 |
+
def ConsultarTiposTarjeta(self, forma_pago=None, sep="|"):
|
| 631 |
+
"Recuperar lista de los tipos de tarjeta habilitados"
|
| 632 |
+
ret = self.client.consultarTiposTarjeta(
|
| 633 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit, },
|
| 634 |
+
formaPago=forma_pago)
|
| 635 |
+
result = ret['consultarTiposTarjetaReturn']
|
| 636 |
+
self.__analizar_errores(result)
|
| 637 |
+
ret = []
|
| 638 |
+
for u in result['arrayTiposTarjeta']:
|
| 639 |
+
u = u['codigoDescripcion']
|
| 640 |
+
r = {'codigo': u.get('codigo'), 'ds': u.get('descripcion'), }
|
| 641 |
+
ret.append(r)
|
| 642 |
+
return [("\t%(codigo)s\t%(ds)s\t"
|
| 643 |
+
% it).replace("\t", sep) for it in ret] if sep else ret
|
| 644 |
+
|
| 645 |
+
@inicializar_y_capturar_excepciones
|
| 646 |
+
def ConsultarTiposCuenta(self, sep="|"):
|
| 647 |
+
"Recuperar lista de los tipos de tarjeta habilitados"
|
| 648 |
+
ret = self.client.consultarTiposCuenta(
|
| 649 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit, })
|
| 650 |
+
result = ret['consultarTiposCuentaReturn']
|
| 651 |
+
self.__analizar_errores(result)
|
| 652 |
+
ret = []
|
| 653 |
+
for u in result['arrayTiposCuenta']:
|
| 654 |
+
u = u['codigoDescripcion']
|
| 655 |
+
r = {'codigo': u.get('codigo'), 'ds': u.get('descripcion'), }
|
| 656 |
+
ret.append(r)
|
| 657 |
+
return [("\t%(codigo)s\t%(ds)s\t"
|
| 658 |
+
% it).replace("\t", sep) for it in ret] if sep else ret
|
| 659 |
+
|
| 660 |
+
|
| 661 |
+
def main():
|
| 662 |
+
"FunciοΏ½n principal de pruebas (obtener CAE)"
|
| 663 |
+
import os
|
| 664 |
+
import time
|
| 665 |
+
|
| 666 |
+
DEBUG = '--debug' in sys.argv
|
| 667 |
+
|
| 668 |
+
# obteniendo el TA para pruebas
|
| 669 |
+
from .wsaa import WSAA
|
| 670 |
+
ta = WSAA().Autenticar("wsct", "reingart.crt", "reingart.key")
|
| 671 |
+
|
| 672 |
+
wsct = WSCT()
|
| 673 |
+
wsct.SetTicketAcceso(ta)
|
| 674 |
+
wsct.Cuit = "20267565393"
|
| 675 |
+
|
| 676 |
+
cache = ""
|
| 677 |
+
if "--prod" in sys.argv:
|
| 678 |
+
wsdl = "https://serviciosjava.afip.gob.ar/wsct/CTService?wsdl"
|
| 679 |
+
else:
|
| 680 |
+
wsdl = WSDL
|
| 681 |
+
wsct.Conectar(cache, wsdl, cacert="conf/afip_ca_info.crt")
|
| 682 |
+
|
| 683 |
+
if "--dummy" in sys.argv:
|
| 684 |
+
print(wsct.client.help("dummy"))
|
| 685 |
+
wsct.Dummy()
|
| 686 |
+
print("AppServerStatus", wsct.AppServerStatus)
|
| 687 |
+
print("DbServerStatus", wsct.DbServerStatus)
|
| 688 |
+
print("AuthServerStatus", wsct.AuthServerStatus)
|
| 689 |
+
|
| 690 |
+
if "--prueba" in sys.argv:
|
| 691 |
+
# print wsct.client.help("autorizarComprobante").encode("latin1")
|
| 692 |
+
try:
|
| 693 |
+
tipo_cbte = 195
|
| 694 |
+
punto_vta = 4000
|
| 695 |
+
cbte_nro = wsct.ConsultarUltimoComprobanteAutorizado(tipo_cbte, punto_vta)
|
| 696 |
+
fecha = datetime.datetime.now().strftime("%Y-%m-%d")
|
| 697 |
+
tipo_doc = 80
|
| 698 |
+
nro_doc = "50000000059"
|
| 699 |
+
cbte_nro = int(cbte_nro) + 1
|
| 700 |
+
id_impositivo = 9 # "Cliente del Exterior"
|
| 701 |
+
cod_relacion = 3 # Alojamiento Directo a Turista No Residente
|
| 702 |
+
imp_total = "101.00"
|
| 703 |
+
imp_tot_conc = "0.00"
|
| 704 |
+
imp_neto = "100.00"
|
| 705 |
+
imp_trib = "1.00"
|
| 706 |
+
imp_op_ex = "0.00"
|
| 707 |
+
imp_subtotal = "100.00"
|
| 708 |
+
imp_reintegro = -21.00 # validaciοΏ½n AFIP 346
|
| 709 |
+
cod_pais = 203
|
| 710 |
+
domicilio = "Rua N.76 km 34.5 Alagoas"
|
| 711 |
+
fecha_cbte = fecha
|
| 712 |
+
moneda_id = 'PES'
|
| 713 |
+
moneda_ctz = '1.000'
|
| 714 |
+
obs = "Observaciones Comerciales, libre"
|
| 715 |
+
|
| 716 |
+
wsct.CrearFactura(tipo_doc, nro_doc, tipo_cbte, punto_vta,
|
| 717 |
+
cbte_nro, imp_total, imp_tot_conc, imp_neto,
|
| 718 |
+
imp_subtotal, imp_trib, imp_op_ex, imp_reintegro,
|
| 719 |
+
fecha_cbte, id_impositivo, cod_pais, domicilio,
|
| 720 |
+
cod_relacion, moneda_id, moneda_ctz, obs)
|
| 721 |
+
|
| 722 |
+
tributo_id = 99
|
| 723 |
+
desc = 'Impuesto Municipal Matanza'
|
| 724 |
+
base_imp = "100.00"
|
| 725 |
+
alic = "1.00"
|
| 726 |
+
importe = "1.00"
|
| 727 |
+
wsct.AgregarTributo(tributo_id, desc, base_imp, alic, importe)
|
| 728 |
+
|
| 729 |
+
iva_id = 5 # 21%
|
| 730 |
+
base_imp = 100
|
| 731 |
+
importe = 21
|
| 732 |
+
wsct.AgregarIva(iva_id, base_imp, importe)
|
| 733 |
+
|
| 734 |
+
tipo = 0 # Item General
|
| 735 |
+
cod_tur = 1 # Servicio de hotelerοΏ½a - alojamiento sin desayuno
|
| 736 |
+
codigo = "T0001"
|
| 737 |
+
ds = "Descripcion del producto P0001"
|
| 738 |
+
iva_id = 5
|
| 739 |
+
imp_iva = 21.00
|
| 740 |
+
imp_subtotal = 121.00
|
| 741 |
+
wsct.AgregarItem(tipo, cod_tur, codigo, ds,
|
| 742 |
+
iva_id, imp_iva, imp_subtotal)
|
| 743 |
+
|
| 744 |
+
codigo = 68 # tarjeta de crοΏ½dito
|
| 745 |
+
tipo_tarjeta = 99 # otra (ver tabla de parοΏ½metros)
|
| 746 |
+
numero_tarjeta = "999999"
|
| 747 |
+
swift_code = None
|
| 748 |
+
tipo_cuenta = None
|
| 749 |
+
numero_cuenta = None
|
| 750 |
+
wsct.AgregarFormaPago(codigo, tipo_tarjeta, numero_tarjeta,
|
| 751 |
+
swift_code, tipo_cuenta, numero_cuenta)
|
| 752 |
+
|
| 753 |
+
print(wsct.factura)
|
| 754 |
+
|
| 755 |
+
wsct.AutorizarComprobante()
|
| 756 |
+
|
| 757 |
+
print("Resultado", wsct.Resultado)
|
| 758 |
+
print("CAE", wsct.CAE)
|
| 759 |
+
print("Vencimiento", wsct.Vencimiento)
|
| 760 |
+
print("Reproceso", wsct.Reproceso)
|
| 761 |
+
print("Errores", wsct.ErrMsg)
|
| 762 |
+
|
| 763 |
+
print(wsct.Excepcion)
|
| 764 |
+
print(wsct.ErrMsg)
|
| 765 |
+
|
| 766 |
+
cae = wsct.CAE
|
| 767 |
+
|
| 768 |
+
if cae:
|
| 769 |
+
|
| 770 |
+
wsct.ConsultarComprobante(tipo_cbte, punto_vta, cbte_nro)
|
| 771 |
+
print("CAE consulta", wsct.CAE, wsct.CAE == cae)
|
| 772 |
+
print("NRO consulta", wsct.CbteNro, wsct.CbteNro == cbte_nro)
|
| 773 |
+
print("TOTAL consulta", wsct.ImpTotal, wsct.ImpTotal == imp_total)
|
| 774 |
+
|
| 775 |
+
wsct.AnalizarXml("XmlResponse")
|
| 776 |
+
assert wsct.ObtenerTagXml('codigoAutorizacion') == str(wsct.CAE)
|
| 777 |
+
assert wsct.ObtenerTagXml('codigoConcepto') == str(concepto)
|
| 778 |
+
assert wsct.ObtenerTagXml('arrayItems', 0, 'item', 'unidadesMtx') == '123456'
|
| 779 |
+
|
| 780 |
+
except BaseException:
|
| 781 |
+
print(wsct.XmlRequest)
|
| 782 |
+
print(wsct.XmlResponse)
|
| 783 |
+
print(wsct.ErrCode)
|
| 784 |
+
print(wsct.ErrMsg)
|
| 785 |
+
|
| 786 |
+
if "--ptosventa" in sys.argv:
|
| 787 |
+
print(wsct.ConsultarPuntosVenta())
|
| 788 |
+
|
| 789 |
+
if "--parametros" in sys.argv:
|
| 790 |
+
print(wsct.ConsultarTiposDatosAdicionales())
|
| 791 |
+
print(wsct.ConsultarTiposComprobante())
|
| 792 |
+
print(wsct.ConsultarTiposDocumento())
|
| 793 |
+
print(wsct.ConsultarTiposIVA())
|
| 794 |
+
print(wsct.ConsultarCondicionesIVA())
|
| 795 |
+
print(wsct.ConsultarMonedas())
|
| 796 |
+
print(wsct.ConsultarTiposItem())
|
| 797 |
+
print(wsct.ConsultarCodigosItemTurismo())
|
| 798 |
+
print(wsct.ConsultarTiposTributo())
|
| 799 |
+
print(wsct.ConsultarFomasPago())
|
| 800 |
+
for forma_pago in wsct.ConsultarFomasPago(sep=None)[:2]:
|
| 801 |
+
print(wsct.ConsultarTiposTarjeta(forma_pago["codigo"]))
|
| 802 |
+
print(wsct.ConsultarTiposCuenta())
|
| 803 |
+
print("\n".join(wsct.ConsultarPaises()))
|
| 804 |
+
print("\n".join(wsct.ConsultarCUITsPaises()))
|
| 805 |
+
|
| 806 |
+
if "--cotizacion" in sys.argv:
|
| 807 |
+
print(wsct.ConsultarCotizacionMoneda('DOL'))
|
| 808 |
+
|
| 809 |
+
|
| 810 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 811 |
+
INSTALL_DIR = WSCT.InstallDir = get_install_dir()
|
| 812 |
+
|
| 813 |
+
|
| 814 |
+
if __name__ == '__main__':
|
| 815 |
+
|
| 816 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 817 |
+
import win32com.server.register
|
| 818 |
+
win32com.server.register.UseCommandLine(WSCT)
|
| 819 |
+
else:
|
| 820 |
+
main()
|
app/pyafipws/wsctg.py
ADDED
|
@@ -0,0 +1,1198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
import base64
|
| 14 |
+
import time
|
| 15 |
+
import sys
|
| 16 |
+
import os
|
| 17 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, BaseWS, inicializar_y_capturar_excepciones, get_install_dir
|
| 18 |
+
from . import utils
|
| 19 |
+
from pysimplesoap.client import SoapFault
|
| 20 |
+
import traceback
|
| 21 |
+
from .utils import date
|
| 22 |
+
"""MΓ³dulo para obtener CΓ³digo de Trazabilidad de Granos
|
| 23 |
+
del web service WSCTG versiΓ³n 4.0 de AFIP (RG3593/14)
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 27 |
+
__copyright__ = "Copyright (C) 2010-2014 Mariano Reingart"
|
| 28 |
+
__license__ = "LGPL 3.0"
|
| 29 |
+
__version__ = "1.14e"
|
| 30 |
+
|
| 31 |
+
LICENCIA = """
|
| 32 |
+
wsctg.py: Interfaz para generar CΓ³digo de Trazabilidad de Granos AFIP v1.1
|
| 33 |
+
Copyright (C) 2014-2015 Mariano Reingart reingart@gmail.com
|
| 34 |
+
http://www.sistemasagiles.com.ar/trac/wiki/CodigoTrazabilidadGranos
|
| 35 |
+
|
| 36 |
+
Este progarma es software libre, se entrega ABSOLUTAMENTE SIN GARANTIA
|
| 37 |
+
y es bienvenido a redistribuirlo bajo la licencia GPLv3.
|
| 38 |
+
|
| 39 |
+
Para informaciΓ³n adicional sobre garantΓa, soporte tΓ©cnico comercial
|
| 40 |
+
e incorporaciΓ³n/distribuciΓ³n en programas propietarios ver PyAfipWs:
|
| 41 |
+
http://www.sistemasagiles.com.ar/trac/wiki/PyAfipWs
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
AYUDA = """
|
| 45 |
+
Opciones:
|
| 46 |
+
--ayuda: este mensaje
|
| 47 |
+
|
| 48 |
+
--debug: modo depuraciΓ³n (detalla y confirma las operaciones)
|
| 49 |
+
--formato: muestra el formato de los archivos de entrada/salida
|
| 50 |
+
--prueba: genera y autoriza una CTG de prueba (no usar en producciΓ³n!)
|
| 51 |
+
--xml: almacena los requerimientos y respuestas XML (depuraciΓ³n)
|
| 52 |
+
|
| 53 |
+
--dummy: consulta estado de servidores
|
| 54 |
+
--solicitar: obtiene el CTG (segΓΊn archivo de entrada en TXT o CSV)
|
| 55 |
+
--confirmar: confirma el CTG (segΓΊn archivo de entrada en TXT o CSV)
|
| 56 |
+
--anular: anula el CTG
|
| 57 |
+
--rechazar: permite al destino rechazar el CTG
|
| 58 |
+
--confirmar_arribo: confirma el arribo de un CTG
|
| 59 |
+
--confirmar_definitivo: confirma el arribo definitivo de un CTG
|
| 60 |
+
--regresar_a_origen_rechazado: tomar la acciΓ³n de "Regresar a Origen"
|
| 61 |
+
--cambiar_destino_destinatario_rechazado: "Cambio de Destino y Destinatario"
|
| 62 |
+
|
| 63 |
+
--consultar: consulta las CTG generadas
|
| 64 |
+
--consultar_excel: consulta las CTG generadas (genera un excel)
|
| 65 |
+
--consultar_detalle: obtiene el detalle de una CTG
|
| 66 |
+
--consultar_constancia_pdf: descarga el documento PDF de una CTG
|
| 67 |
+
--pendientes: consulta CTGs otorgados, rechazados, confirmados a resolver
|
| 68 |
+
--consultar_rechazados: obtener CTGs rechazados para darles un nuevo curso
|
| 69 |
+
--consultar_activos_por_patente: consulta de CTGs activos por patente
|
| 70 |
+
|
| 71 |
+
--provincias: obtiene el listado de provincias
|
| 72 |
+
--localidades: obtiene el listado de localidades por provincia
|
| 73 |
+
--especies: obtiene el listado de especies
|
| 74 |
+
--cosechas: obtiene el listado de cosechas
|
| 75 |
+
|
| 76 |
+
Ver wsctg.ini para parΓ‘metros de configuraciΓ³n (URL, certificados, etc.)"
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
# importo funciones compartidas:
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# constantes de configuraciΓ³n (homologaciΓ³n):
|
| 84 |
+
|
| 85 |
+
WSDL = "https://fwshomo.afip.gov.ar/wsctg/services/CTGService_v4.0?wsdl"
|
| 86 |
+
|
| 87 |
+
DEBUG = False
|
| 88 |
+
XML = False
|
| 89 |
+
CONFIG_FILE = "wsctg.ini"
|
| 90 |
+
HOMO = False
|
| 91 |
+
|
| 92 |
+
# definiciΓ³n del formato del archivo de intercambio:
|
| 93 |
+
|
| 94 |
+
ENCABEZADO = [
|
| 95 |
+
# datos enviados
|
| 96 |
+
('tipo_reg', 1, A), # 0: encabezado
|
| 97 |
+
('numero_carta_de_porte', 13, N),
|
| 98 |
+
('codigo_especie', 5, N),
|
| 99 |
+
('cuit_canjeador', 11, N),
|
| 100 |
+
('cuit_destino', 11, N),
|
| 101 |
+
('cuit_destinatario', 11, N),
|
| 102 |
+
('codigo_localidad_origen', 6, N),
|
| 103 |
+
('codigo_localidad_destino', 6, N),
|
| 104 |
+
('codigo_cosecha', 4, N),
|
| 105 |
+
('peso_neto_carga', 5, N),
|
| 106 |
+
('cant_horas', 2, N),
|
| 107 |
+
('reservado1', 6, A),
|
| 108 |
+
('cuit_transportista', 11, N),
|
| 109 |
+
('km_a_recorrer', 4, N), # km_recorridos (en consulta WSCTGv2)
|
| 110 |
+
('establecimiento', 6, N), # confirmar arribo
|
| 111 |
+
('remitente_comercial_como_canjeador', 1, A), # S/N solicitar CTG inicial (WSCTGv2)
|
| 112 |
+
('consumo_propio', 1, A), # S/N confirmar arribo (WSCTGv2)
|
| 113 |
+
|
| 114 |
+
# datos devueltos
|
| 115 |
+
('numero_ctg', 8, N),
|
| 116 |
+
('fecha_hora', 19, A),
|
| 117 |
+
('vigencia_desde', 10, A),
|
| 118 |
+
('vigencia_hasta', 10, A),
|
| 119 |
+
('transaccion', 12, N),
|
| 120 |
+
('tarifa_referencia', 6, I, 2), # consultar detalle
|
| 121 |
+
('estado', 20, A),
|
| 122 |
+
('imprime_constancia', 5, A),
|
| 123 |
+
('observaciones', 200, A),
|
| 124 |
+
('errores', 1000, A),
|
| 125 |
+
('controles', 1000, A),
|
| 126 |
+
('detalle', 1000, A), # consultar detalle (WSCTGv2)
|
| 127 |
+
|
| 128 |
+
# nuevos campos agregados:
|
| 129 |
+
('cuit_chofer', 11, N),
|
| 130 |
+
|
| 131 |
+
# nuevos campos agregados WSCTGv3:
|
| 132 |
+
('cuit_corredor', 12, N),
|
| 133 |
+
('remitente_comercial_como_productor', 1, A),
|
| 134 |
+
('patente_vehiculo', 10, A),
|
| 135 |
+
|
| 136 |
+
# nuevos campos agregados WSCTGv4:
|
| 137 |
+
('ctc_codigo', 2, A),
|
| 138 |
+
('turno', 50, A),
|
| 139 |
+
|
| 140 |
+
]
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class WSCTG(BaseWS):
|
| 144 |
+
"Interfaz para el WebService de CΓ³digo de Trazabilidad de Granos (Version 3)"
|
| 145 |
+
_public_methods_ = ['Conectar', 'Dummy', 'SetTicketAcceso', 'DebugLog',
|
| 146 |
+
'SolicitarCTGInicial', 'SolicitarCTGDatoPendiente',
|
| 147 |
+
'ConfirmarArribo', 'ConfirmarDefinitivo',
|
| 148 |
+
'AnularCTG', 'RechazarCTG', 'CTGsPendientesResolucion',
|
| 149 |
+
'ConsultarCTG', 'LeerDatosCTG', 'ConsultarDetalleCTG',
|
| 150 |
+
'ConsultarCTGExcel', 'ConsultarConstanciaCTGPDF',
|
| 151 |
+
'ConsultarCTGRechazados',
|
| 152 |
+
'RegresarAOrigenCTGRechazado',
|
| 153 |
+
'CambiarDestinoDestinatarioCTGRechazado',
|
| 154 |
+
'ConsultarCTGActivosPorPatente',
|
| 155 |
+
'ConsultarProvincias',
|
| 156 |
+
'ConsultarLocalidadesPorProvincia',
|
| 157 |
+
'ConsultarEstablecimientos',
|
| 158 |
+
'ConsultarCosechas',
|
| 159 |
+
'ConsultarEspecies',
|
| 160 |
+
'SetParametros', 'SetParametro', 'GetParametro',
|
| 161 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 162 |
+
]
|
| 163 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 164 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 165 |
+
'Excepcion', 'ErrCode', 'ErrMsg', 'LanzarExcepciones', 'Errores',
|
| 166 |
+
'XmlRequest', 'XmlResponse', 'Version', 'Traceback',
|
| 167 |
+
'NumeroCTG', 'CartaPorte', 'FechaHora', 'CodigoOperacion',
|
| 168 |
+
'CodigoTransaccion', 'Observaciones', 'Controles', 'DatosCTG',
|
| 169 |
+
'VigenciaHasta', 'VigenciaDesde', 'Estado', 'ImprimeConstancia',
|
| 170 |
+
'TarifaReferencia', 'Destino', 'Destinatario', 'Detalle',
|
| 171 |
+
'Patente', 'PesoNeto', 'FechaVencimiento',
|
| 172 |
+
'UsuarioSolicitante', 'UsuarioReal', 'CtcCodigo', 'Turno',
|
| 173 |
+
]
|
| 174 |
+
_reg_progid_ = "WSCTG"
|
| 175 |
+
_reg_clsid_ = "{4383E947-57C4-47C5-8419-85221580CB48}"
|
| 176 |
+
|
| 177 |
+
# Variables globales para BaseWS:
|
| 178 |
+
HOMO = HOMO
|
| 179 |
+
WSDL = WSDL
|
| 180 |
+
LanzarExcepciones = False
|
| 181 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciΓ³n' or '')
|
| 182 |
+
|
| 183 |
+
def Conectar(self, *args, **kwargs):
|
| 184 |
+
ret = BaseWS.Conectar(self, *args, **kwargs)
|
| 185 |
+
# corregir descripciΓ³n de servicio WSDL publicado por AFIP
|
| 186 |
+
# kmARecorrer -> kmRecorridos (ConsultarDetalleCTG)
|
| 187 |
+
port = self.client.services['CTGService_v4.0']['ports']['CTGServiceHttpSoap20Endpoint']
|
| 188 |
+
msg = port['operations']['consultarDetalleCTG']['output']['consultarDetalleCTGResponse']
|
| 189 |
+
msg['response']['consultarDetalleCTGDatos']['kmRecorridos'] = int
|
| 190 |
+
return ret
|
| 191 |
+
|
| 192 |
+
def inicializar(self):
|
| 193 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 194 |
+
self.CodError = self.DescError = ''
|
| 195 |
+
self.NumeroCTG = self.CartaPorte = ""
|
| 196 |
+
self.CodigoTransaccion = self.Observaciones = ''
|
| 197 |
+
self.FechaHora = self.CodigoOperacion = ""
|
| 198 |
+
self.VigenciaDesde = self.VigenciaHasta = ""
|
| 199 |
+
self.Controles = []
|
| 200 |
+
self.DatosCTG = self.TarifaReferencia = None
|
| 201 |
+
self.CodigoTransaccion = self.Observaciones = ''
|
| 202 |
+
self.Detalle = self.Destino = self.Destinatario = ''
|
| 203 |
+
self.Patente = self.PesoNeto = self.FechaVencimiento = ''
|
| 204 |
+
self.UsuarioSolicitante = self.UsuarioReal = ''
|
| 205 |
+
self.CtcCodigo = self.Turno = ""
|
| 206 |
+
|
| 207 |
+
def __analizar_errores(self, ret):
|
| 208 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 209 |
+
if 'arrayErrores' in ret:
|
| 210 |
+
errores = ret['arrayErrores'] or []
|
| 211 |
+
self.Errores = [err['error'] for err in errores]
|
| 212 |
+
self.ErrCode = ' '.join(self.Errores)
|
| 213 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 214 |
+
|
| 215 |
+
def __analizar_controles(self, ret):
|
| 216 |
+
"Comprueba y extrae controles si existen en la respuesta XML"
|
| 217 |
+
if 'arrayControles' in ret:
|
| 218 |
+
controles = ret['arrayControles']
|
| 219 |
+
self.Controles = ["%(tipo)s: %(descripcion)s" % ctl['control']
|
| 220 |
+
for ctl in controles]
|
| 221 |
+
|
| 222 |
+
@inicializar_y_capturar_excepciones
|
| 223 |
+
def Dummy(self):
|
| 224 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 225 |
+
results = self.client.dummy()['response']
|
| 226 |
+
self.AppServerStatus = str(results['appserver'])
|
| 227 |
+
self.DbServerStatus = str(results['dbserver'])
|
| 228 |
+
self.AuthServerStatus = str(results['authserver'])
|
| 229 |
+
|
| 230 |
+
@inicializar_y_capturar_excepciones
|
| 231 |
+
def AnularCTG(self, carta_porte, ctg):
|
| 232 |
+
"Anular el CTG si se creΓ³ el mismo por error"
|
| 233 |
+
response = self.client.anularCTG(request=dict(
|
| 234 |
+
auth={
|
| 235 |
+
'token': self.Token, 'sign': self.Sign,
|
| 236 |
+
'cuitRepresentado': self.Cuit, },
|
| 237 |
+
datosAnularCTG={
|
| 238 |
+
'cartaPorte': carta_porte,
|
| 239 |
+
'ctg': ctg, }))['response']
|
| 240 |
+
datos = response.get('datosResponse')
|
| 241 |
+
self.__analizar_errores(response)
|
| 242 |
+
if datos:
|
| 243 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 244 |
+
self.NumeroCTG = str(datos['ctg'])
|
| 245 |
+
self.FechaHora = str(datos['fechaHora'])
|
| 246 |
+
self.CodigoOperacion = str(datos['codigoOperacion'])
|
| 247 |
+
|
| 248 |
+
@inicializar_y_capturar_excepciones
|
| 249 |
+
def RechazarCTG(self, carta_porte, ctg, motivo):
|
| 250 |
+
"El Destino puede rechazar el CTG a travΓ©s de la siguiente operatoria"
|
| 251 |
+
response = self.client.rechazarCTG(request=dict(
|
| 252 |
+
auth={
|
| 253 |
+
'token': self.Token, 'sign': self.Sign,
|
| 254 |
+
'cuitRepresentado': self.Cuit, },
|
| 255 |
+
datosRechazarCTG={
|
| 256 |
+
'cartaPorte': carta_porte,
|
| 257 |
+
'ctg': ctg, 'motivoRechazo': motivo,
|
| 258 |
+
}))['response']
|
| 259 |
+
datos = response.get('datosResponse')
|
| 260 |
+
self.__analizar_errores(response)
|
| 261 |
+
if datos:
|
| 262 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 263 |
+
self.NumeroCTG = str(datos['CTG'])
|
| 264 |
+
self.FechaHora = str(datos['fechaHora'])
|
| 265 |
+
self.CodigoOperacion = str(datos['codigoOperacion'])
|
| 266 |
+
|
| 267 |
+
@inicializar_y_capturar_excepciones
|
| 268 |
+
def SolicitarCTGInicial(self, numero_carta_de_porte, codigo_especie,
|
| 269 |
+
cuit_canjeador, cuit_destino, cuit_destinatario, codigo_localidad_origen,
|
| 270 |
+
codigo_localidad_destino, codigo_cosecha, peso_neto_carga,
|
| 271 |
+
cant_horas=None, patente_vehiculo=None, cuit_transportista=None,
|
| 272 |
+
km_a_recorrer=None, remitente_comercial_como_canjeador=None,
|
| 273 |
+
cuit_corredor=None, remitente_comercial_como_productor=None,
|
| 274 |
+
turno=None,
|
| 275 |
+
**kwargs):
|
| 276 |
+
"Solicitar CTG Desde el Inicio"
|
| 277 |
+
# ajusto parΓ‘metros segΓΊn validaciones de AFIP:
|
| 278 |
+
if not cuit_canjeador or int(cuit_canjeador) == 0:
|
| 279 |
+
cuit_canjeador = None # nulo
|
| 280 |
+
if not cuit_corredor or int(cuit_corredor) == 0:
|
| 281 |
+
cuit_corredor = None # nulo
|
| 282 |
+
if not remitente_comercial_como_canjeador:
|
| 283 |
+
remitente_comercial_como_canjeador = None
|
| 284 |
+
if not remitente_comercial_como_productor:
|
| 285 |
+
remitente_comercial_como_productor = None
|
| 286 |
+
if turno == '':
|
| 287 |
+
turno = None # nulo
|
| 288 |
+
|
| 289 |
+
ret = self.client.solicitarCTGInicial(request=dict(
|
| 290 |
+
auth={
|
| 291 |
+
'token': self.Token, 'sign': self.Sign,
|
| 292 |
+
'cuitRepresentado': self.Cuit, },
|
| 293 |
+
datosSolicitarCTGInicial=dict(
|
| 294 |
+
cartaPorte=numero_carta_de_porte,
|
| 295 |
+
codigoEspecie=codigo_especie,
|
| 296 |
+
cuitCanjeador=cuit_canjeador or None,
|
| 297 |
+
remitenteComercialComoCanjeador=remitente_comercial_como_canjeador,
|
| 298 |
+
cuitDestino=cuit_destino,
|
| 299 |
+
cuitDestinatario=cuit_destinatario,
|
| 300 |
+
codigoLocalidadOrigen=codigo_localidad_origen,
|
| 301 |
+
codigoLocalidadDestino=codigo_localidad_destino,
|
| 302 |
+
codigoCosecha=codigo_cosecha,
|
| 303 |
+
pesoNeto=peso_neto_carga,
|
| 304 |
+
cuitTransportista=cuit_transportista,
|
| 305 |
+
cantHoras=cant_horas,
|
| 306 |
+
patente=patente_vehiculo,
|
| 307 |
+
kmARecorrer=km_a_recorrer,
|
| 308 |
+
cuitCorredor=cuit_corredor,
|
| 309 |
+
remitenteComercialcomoProductor=remitente_comercial_como_productor,
|
| 310 |
+
turno=turno,
|
| 311 |
+
)))['response']
|
| 312 |
+
self.__analizar_errores(ret)
|
| 313 |
+
self.Observaciones = ret['observacion']
|
| 314 |
+
datos = ret.get('datosSolicitarCTGResponse')
|
| 315 |
+
if datos:
|
| 316 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 317 |
+
datos_ctg = datos.get('datosSolicitarCTG')
|
| 318 |
+
if datos_ctg:
|
| 319 |
+
self.NumeroCTG = str(datos_ctg['ctg'])
|
| 320 |
+
self.FechaHora = str(datos_ctg['fechaEmision'])
|
| 321 |
+
self.VigenciaDesde = str(datos_ctg['fechaVigenciaDesde'])
|
| 322 |
+
self.VigenciaHasta = str(datos_ctg['fechaVigenciaHasta'])
|
| 323 |
+
self.TarifaReferencia = str(datos_ctg.get('tarifaReferencia'))
|
| 324 |
+
self.__analizar_controles(datos)
|
| 325 |
+
return self.NumeroCTG or 0
|
| 326 |
+
|
| 327 |
+
@inicializar_y_capturar_excepciones
|
| 328 |
+
def SolicitarCTGDatoPendiente(self, numero_carta_de_porte, cant_horas,
|
| 329 |
+
patente_vehiculo, cuit_transportista, patente=None, turno=None):
|
| 330 |
+
"Solicitud que permite completar los datos faltantes de un Pre-CTG "
|
| 331 |
+
"generado anteriormente a travΓ©s de la operaciΓ³n solicitarCTGInicial"
|
| 332 |
+
ret = self.client.solicitarCTGDatoPendiente(request=dict(
|
| 333 |
+
auth={
|
| 334 |
+
'token': self.Token, 'sign': self.Sign,
|
| 335 |
+
'cuitRepresentado': self.Cuit, },
|
| 336 |
+
datosSolicitarCTGDatoPendiente=dict(
|
| 337 |
+
cartaPorte=numero_carta_de_porte,
|
| 338 |
+
cuitTransportista=cuit_transportista,
|
| 339 |
+
cantHoras=cant_horas,
|
| 340 |
+
patente=patente,
|
| 341 |
+
turno=turno,
|
| 342 |
+
)))['response']
|
| 343 |
+
self.__analizar_errores(ret)
|
| 344 |
+
self.Observaciones = ret['observacion']
|
| 345 |
+
datos = ret.get('datosSolicitarCTGResponse')
|
| 346 |
+
if datos:
|
| 347 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 348 |
+
datos_ctg = datos.get('datosSolicitarCTG')
|
| 349 |
+
if datos_ctg:
|
| 350 |
+
self.NumeroCTG = str(datos_ctg['ctg'])
|
| 351 |
+
self.FechaHora = str(datos_ctg['fechaEmision'])
|
| 352 |
+
self.VigenciaDesde = str(datos_ctg['fechaVigenciaDesde'])
|
| 353 |
+
self.VigenciaHasta = str(datos_ctg['fechaVigenciaHasta'])
|
| 354 |
+
self.TarifaReferencia = str(datos_ctg.get('tarifaReferencia'))
|
| 355 |
+
self.__analizar_controles(datos)
|
| 356 |
+
return self.NumeroCTG
|
| 357 |
+
|
| 358 |
+
@inicializar_y_capturar_excepciones
|
| 359 |
+
def ConfirmarArribo(self, numero_carta_de_porte, numero_ctg,
|
| 360 |
+
cuit_transportista, peso_neto_carga,
|
| 361 |
+
consumo_propio, establecimiento=None, cuit_chofer=None,
|
| 362 |
+
**kwargs):
|
| 363 |
+
"Confirma arribo CTG"
|
| 364 |
+
ret = self.client.confirmarArribo(request=dict(
|
| 365 |
+
auth={
|
| 366 |
+
'token': self.Token, 'sign': self.Sign,
|
| 367 |
+
'cuitRepresentado': self.Cuit, },
|
| 368 |
+
datosConfirmarArribo=dict(
|
| 369 |
+
cartaPorte=numero_carta_de_porte,
|
| 370 |
+
ctg=numero_ctg,
|
| 371 |
+
cuitTransportista=cuit_transportista,
|
| 372 |
+
cuitChofer=cuit_chofer,
|
| 373 |
+
cantKilosCartaPorte=peso_neto_carga,
|
| 374 |
+
consumoPropio=consumo_propio,
|
| 375 |
+
establecimiento=establecimiento,
|
| 376 |
+
)))['response']
|
| 377 |
+
self.__analizar_errores(ret)
|
| 378 |
+
datos = ret.get('datosResponse')
|
| 379 |
+
if datos:
|
| 380 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 381 |
+
self.NumeroCTG = str(datos['ctg'])
|
| 382 |
+
self.FechaHora = str(datos['fechaHora'])
|
| 383 |
+
self.CodigoTransaccion = str(datos['codigoOperacion'])
|
| 384 |
+
self.Observaciones = ""
|
| 385 |
+
return self.CodigoTransaccion
|
| 386 |
+
|
| 387 |
+
@inicializar_y_capturar_excepciones
|
| 388 |
+
def ConfirmarDefinitivo(self, numero_carta_de_porte, numero_ctg,
|
| 389 |
+
establecimiento=None, codigo_cosecha=None, peso_neto_carga=None,
|
| 390 |
+
**kwargs):
|
| 391 |
+
"Confirma arribo definitivo CTG"
|
| 392 |
+
ret = self.client.confirmarDefinitivo(request=dict(
|
| 393 |
+
auth={
|
| 394 |
+
'token': self.Token, 'sign': self.Sign,
|
| 395 |
+
'cuitRepresentado': self.Cuit, },
|
| 396 |
+
datosConfirmarDefinitivo=dict(
|
| 397 |
+
cartaPorte=numero_carta_de_porte,
|
| 398 |
+
ctg=numero_ctg,
|
| 399 |
+
establecimiento=establecimiento,
|
| 400 |
+
codigoCosecha=codigo_cosecha,
|
| 401 |
+
pesoNeto=peso_neto_carga,
|
| 402 |
+
)))['response']
|
| 403 |
+
self.__analizar_errores(ret)
|
| 404 |
+
datos = ret.get('datosResponse')
|
| 405 |
+
if datos:
|
| 406 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 407 |
+
self.NumeroCTG = str(datos['ctg'])
|
| 408 |
+
self.FechaHora = str(datos['fechaHora'])
|
| 409 |
+
self.CodigoTransaccion = str(datos.get('codigoOperacion', ""))
|
| 410 |
+
self.Observaciones = ""
|
| 411 |
+
return self.CodigoTransaccion
|
| 412 |
+
|
| 413 |
+
@inicializar_y_capturar_excepciones
|
| 414 |
+
def RegresarAOrigenCTGRechazado(self, numero_carta_de_porte, numero_ctg,
|
| 415 |
+
km_a_recorrer=None,
|
| 416 |
+
**kwargs):
|
| 417 |
+
"Al consultar los CTGs rechazados se puede Regresar a Origen"
|
| 418 |
+
ret = self.client.regresarAOrigenCTGRechazado(request=dict(
|
| 419 |
+
auth={
|
| 420 |
+
'token': self.Token, 'sign': self.Sign,
|
| 421 |
+
'cuitRepresentado': self.Cuit, },
|
| 422 |
+
datosRegresarAOrigenCTGRechazado=dict(
|
| 423 |
+
cartaPorte=numero_carta_de_porte,
|
| 424 |
+
ctg=numero_ctg, kmARecorrer=km_a_recorrer,
|
| 425 |
+
)))['response']
|
| 426 |
+
self.__analizar_errores(ret)
|
| 427 |
+
datos = ret.get('datosResponse')
|
| 428 |
+
if datos:
|
| 429 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 430 |
+
self.NumeroCTG = str(datos['ctg'])
|
| 431 |
+
self.FechaHora = str(datos['fechaHora'])
|
| 432 |
+
self.CodigoTransaccion = str(datos['codigoOperacion'])
|
| 433 |
+
self.Observaciones = ""
|
| 434 |
+
return self.CodigoTransaccion
|
| 435 |
+
|
| 436 |
+
@inicializar_y_capturar_excepciones
|
| 437 |
+
def CambiarDestinoDestinatarioCTGRechazado(self, numero_carta_de_porte,
|
| 438 |
+
numero_ctg, codigo_localidad_destino=None,
|
| 439 |
+
cuit_destino=None, cuit_destinatario=None,
|
| 440 |
+
km_a_recorrer=None, turno=None,
|
| 441 |
+
**kwargs):
|
| 442 |
+
"Tomar acciΓ³n de Cambio de Destino y Destinatario para CTG rechazado"
|
| 443 |
+
ret = self.client.cambiarDestinoDestinatarioCTGRechazado(request=dict(
|
| 444 |
+
auth={
|
| 445 |
+
'token': self.Token, 'sign': self.Sign,
|
| 446 |
+
'cuitRepresentado': self.Cuit, },
|
| 447 |
+
datosCambiarDestinoDestinatarioCTGRechazado=dict(
|
| 448 |
+
cartaPorte=numero_carta_de_porte,
|
| 449 |
+
ctg=numero_ctg,
|
| 450 |
+
codigoLocalidadDestino=codigo_localidad_destino,
|
| 451 |
+
cuitDestino=cuit_destino,
|
| 452 |
+
cuitDestinatario=cuit_destinatario,
|
| 453 |
+
kmARecorrer=km_a_recorrer,
|
| 454 |
+
turno=turno,
|
| 455 |
+
)))['response']
|
| 456 |
+
self.__analizar_errores(ret)
|
| 457 |
+
datos = ret.get('datosResponse')
|
| 458 |
+
if datos:
|
| 459 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 460 |
+
self.NumeroCTG = str(datos['ctg'])
|
| 461 |
+
self.FechaHora = str(datos['fechaHora'])
|
| 462 |
+
self.CodigoTransaccion = str(datos['codigoOperacion'])
|
| 463 |
+
self.Observaciones = ""
|
| 464 |
+
return self.CodigoTransaccion
|
| 465 |
+
|
| 466 |
+
@inicializar_y_capturar_excepciones
|
| 467 |
+
def ConsultarCTG(self, numero_carta_de_porte=None, numero_ctg=None,
|
| 468 |
+
patente=None, cuit_solicitante=None, cuit_destino=None,
|
| 469 |
+
fecha_emision_desde=None, fecha_emision_hasta=None):
|
| 470 |
+
"OperaciΓ³n que realiza consulta de CTGs segΓΊn el criterio ingresado."
|
| 471 |
+
ret = self.client.consultarCTG(request=dict(
|
| 472 |
+
auth={
|
| 473 |
+
'token': self.Token, 'sign': self.Sign,
|
| 474 |
+
'cuitRepresentado': self.Cuit, },
|
| 475 |
+
consultarCTGDatos=dict(
|
| 476 |
+
cartaPorte=numero_carta_de_porte,
|
| 477 |
+
ctg=numero_ctg,
|
| 478 |
+
patente=patente,
|
| 479 |
+
cuitSolicitante=cuit_solicitante,
|
| 480 |
+
cuitDestino=cuit_destino,
|
| 481 |
+
fechaEmisionDesde=fecha_emision_desde,
|
| 482 |
+
fechaEmisionHasta=fecha_emision_hasta,
|
| 483 |
+
)))['response']
|
| 484 |
+
self.__analizar_errores(ret)
|
| 485 |
+
datos = ret.get('arrayDatosConsultarCTG')
|
| 486 |
+
if datos:
|
| 487 |
+
self.DatosCTG = datos
|
| 488 |
+
self.LeerDatosCTG(pop=False)
|
| 489 |
+
return True
|
| 490 |
+
else:
|
| 491 |
+
self.DatosCTG = []
|
| 492 |
+
return ''
|
| 493 |
+
|
| 494 |
+
@inicializar_y_capturar_excepciones
|
| 495 |
+
def ConsultarCTGRechazados(self):
|
| 496 |
+
"Consulta de CTGs Otorgados, CTGs Rechazados y CTGs Confirmados"
|
| 497 |
+
ret = self.client.consultarCTGRechazados(request=dict(
|
| 498 |
+
auth={
|
| 499 |
+
'token': self.Token, 'sign': self.Sign,
|
| 500 |
+
'cuitRepresentado': self.Cuit, },
|
| 501 |
+
))['response']
|
| 502 |
+
self.__analizar_errores(ret)
|
| 503 |
+
datos = ret.get('arrayConsultarCTGRechazados')
|
| 504 |
+
if datos:
|
| 505 |
+
self.DatosCTG = datos
|
| 506 |
+
self.LeerDatosCTG(pop=False)
|
| 507 |
+
return True
|
| 508 |
+
else:
|
| 509 |
+
self.DatosCTG = []
|
| 510 |
+
return False
|
| 511 |
+
|
| 512 |
+
@inicializar_y_capturar_excepciones
|
| 513 |
+
def ConsultarCTGActivosPorPatente(self, patente="ZZZ999"):
|
| 514 |
+
"Consulta de CTGs activos por patente"
|
| 515 |
+
ret = self.client.consultarCTGActivosPorPatente(request=dict(
|
| 516 |
+
auth={
|
| 517 |
+
'token': self.Token, 'sign': self.Sign,
|
| 518 |
+
'cuitRepresentado': self.Cuit, },
|
| 519 |
+
patente=patente,
|
| 520 |
+
))['response']
|
| 521 |
+
self.__analizar_errores(ret)
|
| 522 |
+
datos = ret.get('arrayConsultarCTGActivosPorPatenteResponse')
|
| 523 |
+
if datos:
|
| 524 |
+
self.DatosCTG = datos
|
| 525 |
+
self.LeerDatosCTG(pop=False)
|
| 526 |
+
return True
|
| 527 |
+
else:
|
| 528 |
+
self.DatosCTG = []
|
| 529 |
+
return False
|
| 530 |
+
|
| 531 |
+
@inicializar_y_capturar_excepciones
|
| 532 |
+
def CTGsPendientesResolucion(self):
|
| 533 |
+
"Consulta de CTGs Otorgados, CTGs Rechazados y CTGs Confirmados"
|
| 534 |
+
ret = self.client.CTGsPendientesResolucion(request=dict(
|
| 535 |
+
auth={
|
| 536 |
+
'token': self.Token, 'sign': self.Sign,
|
| 537 |
+
'cuitRepresentado': self.Cuit, },
|
| 538 |
+
))['response']
|
| 539 |
+
self.__analizar_errores(ret)
|
| 540 |
+
if ret:
|
| 541 |
+
self.DatosCTG = ret
|
| 542 |
+
return True
|
| 543 |
+
else:
|
| 544 |
+
self.DatosCTG = {}
|
| 545 |
+
return False
|
| 546 |
+
|
| 547 |
+
def LeerDatosCTG(self, clave='', pop=True):
|
| 548 |
+
"Recorro los datos devueltos y devuelvo el primero si existe"
|
| 549 |
+
|
| 550 |
+
if clave and self.DatosCTG:
|
| 551 |
+
# obtengo la lista por estado pendiente de resoluciΓ³n ("array")
|
| 552 |
+
datos = self.DatosCTG[clave]
|
| 553 |
+
else:
|
| 554 |
+
# uso directamente la lista devuelta por la consulta
|
| 555 |
+
datos = self.DatosCTG
|
| 556 |
+
if datos:
|
| 557 |
+
# extraigo el primer item
|
| 558 |
+
if pop:
|
| 559 |
+
datos = datos.pop(0)
|
| 560 |
+
else:
|
| 561 |
+
datos = datos[0]
|
| 562 |
+
for det in ('datosConsultarCTG', 'detalleConsultaCTGRechazado',
|
| 563 |
+
'detalleConsultaCTGActivo'):
|
| 564 |
+
if det in datos:
|
| 565 |
+
datos_ctg = datos[det]
|
| 566 |
+
break
|
| 567 |
+
else:
|
| 568 |
+
# elemento del array no encontrado:
|
| 569 |
+
return ""
|
| 570 |
+
self.CartaPorte = str(datos_ctg['cartaPorte'])
|
| 571 |
+
self.NumeroCTG = str(datos_ctg['ctg'])
|
| 572 |
+
self.Estado = str(datos_ctg.get('estado', ""))
|
| 573 |
+
self.ImprimeConstancia = str(datos_ctg.get('imprimeConstancia', ""))
|
| 574 |
+
for campo in ("fechaRechazo", "fechaEmision", "fechaSolicitud",
|
| 575 |
+
"fechaConfirmacionArribo"):
|
| 576 |
+
if campo in datos_ctg:
|
| 577 |
+
self.FechaHora = str(datos_ctg.get(campo))
|
| 578 |
+
self.Destino = datos_ctg.get("destino", "")
|
| 579 |
+
self.Destinatario = datos_ctg.get("destinatario", "")
|
| 580 |
+
self.Observaciones = datos_ctg.get("observaciones", "")
|
| 581 |
+
self.Patente = datos_ctg.get("patente")
|
| 582 |
+
self.PesoNeto = datos_ctg.get("pesoNeto")
|
| 583 |
+
self.FechaVencimiento = datos_ctg.get("fechaVencimiento")
|
| 584 |
+
self.UsuarioSolicitante = datos_ctg.get("usuarioSolicitante")
|
| 585 |
+
self.UsuarioReal = datos_ctg.get("usuarioReal")
|
| 586 |
+
self.CtcCodigo = datos_ctg.get("ctcCodigo")
|
| 587 |
+
self.Turno = datos_ctg.get("turno")
|
| 588 |
+
return self.NumeroCTG
|
| 589 |
+
else:
|
| 590 |
+
return ""
|
| 591 |
+
|
| 592 |
+
@inicializar_y_capturar_excepciones
|
| 593 |
+
def ConsultarCTGExcel(self, numero_carta_de_porte=None, numero_ctg=None,
|
| 594 |
+
patente=None, cuit_solicitante=None, cuit_destino=None,
|
| 595 |
+
fecha_emision_desde=None, fecha_emision_hasta=None,
|
| 596 |
+
archivo="planilla.xls"):
|
| 597 |
+
"OperaciΓ³n que realiza consulta de CTGs, graba una planilla xls"
|
| 598 |
+
ret = self.client.consultarCTGExcel(request=dict(
|
| 599 |
+
auth={
|
| 600 |
+
'token': self.Token, 'sign': self.Sign,
|
| 601 |
+
'cuitRepresentado': self.Cuit, },
|
| 602 |
+
consultarCTGDatos=dict(
|
| 603 |
+
cartaPorte=numero_carta_de_porte,
|
| 604 |
+
ctg=numero_ctg,
|
| 605 |
+
patente=patente,
|
| 606 |
+
cuitSolicitante=cuit_solicitante,
|
| 607 |
+
cuitDestino=cuit_destino,
|
| 608 |
+
fechaEmisionDesde=fecha_emision_desde,
|
| 609 |
+
fechaEmisionHasta=fecha_emision_hasta,
|
| 610 |
+
)))['response']
|
| 611 |
+
self.__analizar_errores(ret)
|
| 612 |
+
datos = base64.b64decode(ret.get('archivo') or "")
|
| 613 |
+
f = open(archivo, "wb")
|
| 614 |
+
f.write(datos)
|
| 615 |
+
f.close()
|
| 616 |
+
return True
|
| 617 |
+
|
| 618 |
+
@inicializar_y_capturar_excepciones
|
| 619 |
+
def ConsultarDetalleCTG(self, numero_ctg=None):
|
| 620 |
+
"OperaciΓ³n mostrar este detalle de la solicitud de CTG seleccionada."
|
| 621 |
+
ret = self.client.consultarDetalleCTG(request=dict(
|
| 622 |
+
auth={
|
| 623 |
+
'token': self.Token, 'sign': self.Sign,
|
| 624 |
+
'cuitRepresentado': self.Cuit, },
|
| 625 |
+
ctg=numero_ctg,
|
| 626 |
+
))['response']
|
| 627 |
+
self.__analizar_errores(ret)
|
| 628 |
+
datos = ret.get('consultarDetalleCTGDatos')
|
| 629 |
+
if datos:
|
| 630 |
+
self.NumeroCTG = str(datos['ctg'])
|
| 631 |
+
self.CartaPorte = str(datos['cartaPorte'])
|
| 632 |
+
self.Estado = str(datos['estado'])
|
| 633 |
+
self.FechaHora = str(datos['fechaEmision'])
|
| 634 |
+
self.VigenciaDesde = str(datos['fechaVigenciaDesde'])
|
| 635 |
+
self.VigenciaHasta = str(datos['fechaVigenciaHasta'])
|
| 636 |
+
self.TarifaReferencia = str(datos['tarifaReferencia'])
|
| 637 |
+
self.Detalle = str(datos.get('detalle', ""))
|
| 638 |
+
return True
|
| 639 |
+
|
| 640 |
+
@inicializar_y_capturar_excepciones
|
| 641 |
+
def ConsultarConstanciaCTGPDF(self, numero_ctg=None,
|
| 642 |
+
archivo="constancia.pdf"):
|
| 643 |
+
"OperaciΓ³n Consultar Constancia de CTG en PDF"
|
| 644 |
+
ret = self.client.consultarConstanciaCTGPDF(request=dict(
|
| 645 |
+
auth={
|
| 646 |
+
'token': self.Token, 'sign': self.Sign,
|
| 647 |
+
'cuitRepresentado': self.Cuit, },
|
| 648 |
+
ctg=numero_ctg,
|
| 649 |
+
))['response']
|
| 650 |
+
self.__analizar_errores(ret)
|
| 651 |
+
datos = base64.b64decode(ret.get('archivo', ""))
|
| 652 |
+
f = open(archivo, "wb")
|
| 653 |
+
f.write(datos)
|
| 654 |
+
f.close()
|
| 655 |
+
return True
|
| 656 |
+
|
| 657 |
+
@inicializar_y_capturar_excepciones
|
| 658 |
+
def ConsultarProvincias(self, sep="||"):
|
| 659 |
+
ret = self.client.consultarProvincias(request=dict(
|
| 660 |
+
auth={
|
| 661 |
+
'token': self.Token, 'sign': self.Sign,
|
| 662 |
+
'cuitRepresentado': self.Cuit, },
|
| 663 |
+
))['consultarProvinciasResponse']
|
| 664 |
+
self.__analizar_errores(ret)
|
| 665 |
+
array = ret.get('arrayProvincias', [])
|
| 666 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 667 |
+
(it['provincia']['codigo'],
|
| 668 |
+
it['provincia']['descripcion'])
|
| 669 |
+
for it in array]
|
| 670 |
+
|
| 671 |
+
@inicializar_y_capturar_excepciones
|
| 672 |
+
def ConsultarLocalidadesPorProvincia(self, codigo_provincia, sep="||"):
|
| 673 |
+
ret = self.client.consultarLocalidadesPorProvincia(request=dict(
|
| 674 |
+
auth={
|
| 675 |
+
'token': self.Token, 'sign': self.Sign,
|
| 676 |
+
'cuitRepresentado': self.Cuit, },
|
| 677 |
+
codigoProvincia=codigo_provincia,
|
| 678 |
+
))['response']
|
| 679 |
+
self.__analizar_errores(ret)
|
| 680 |
+
array = ret.get('arrayLocalidades', [])
|
| 681 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 682 |
+
(it['localidad']['codigo'],
|
| 683 |
+
it['localidad']['descripcion'])
|
| 684 |
+
for it in array]
|
| 685 |
+
|
| 686 |
+
@inicializar_y_capturar_excepciones
|
| 687 |
+
def ConsultarEstablecimientos(self, sep="||"):
|
| 688 |
+
ret = self.client.consultarEstablecimientos(request=dict(
|
| 689 |
+
auth={
|
| 690 |
+
'token': self.Token, 'sign': self.Sign,
|
| 691 |
+
'cuitRepresentado': self.Cuit, },
|
| 692 |
+
))['response']
|
| 693 |
+
self.__analizar_errores(ret)
|
| 694 |
+
array = ret.get('arrayEstablecimientos', [])
|
| 695 |
+
return [("%s" %
|
| 696 |
+
(it['establecimiento'],))
|
| 697 |
+
for it in array]
|
| 698 |
+
|
| 699 |
+
@inicializar_y_capturar_excepciones
|
| 700 |
+
def ConsultarEspecies(self, sep="||"):
|
| 701 |
+
ret = self.client.consultarEspecies(request=dict(
|
| 702 |
+
auth={
|
| 703 |
+
'token': self.Token, 'sign': self.Sign,
|
| 704 |
+
'cuitRepresentado': self.Cuit, },
|
| 705 |
+
))['response']
|
| 706 |
+
self.__analizar_errores(ret)
|
| 707 |
+
array = ret.get('arrayEspecies', [])
|
| 708 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 709 |
+
(it['especie']['codigo'],
|
| 710 |
+
it['especie']['descripcion'])
|
| 711 |
+
for it in array]
|
| 712 |
+
|
| 713 |
+
@inicializar_y_capturar_excepciones
|
| 714 |
+
def ConsultarCosechas(self, sep="||"):
|
| 715 |
+
ret = self.client.consultarCosechas(request=dict(
|
| 716 |
+
auth={
|
| 717 |
+
'token': self.Token, 'sign': self.Sign,
|
| 718 |
+
'cuitRepresentado': self.Cuit, },
|
| 719 |
+
))['response']
|
| 720 |
+
self.__analizar_errores(ret)
|
| 721 |
+
array = ret.get('arrayCosechas', [])
|
| 722 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 723 |
+
(it['cosecha']['codigo'],
|
| 724 |
+
it['cosecha']['descripcion'])
|
| 725 |
+
for it in array]
|
| 726 |
+
|
| 727 |
+
|
| 728 |
+
def leer_archivo(nombre_archivo):
|
| 729 |
+
archivo = open(nombre_archivo, "r")
|
| 730 |
+
items = []
|
| 731 |
+
ext = os.path.splitext(nombre_archivo)[1]
|
| 732 |
+
if ext == '.csv':
|
| 733 |
+
csv_reader = csv.reader(open(ENTRADA), dialect='excel', delimiter=";")
|
| 734 |
+
for row in csv_reader:
|
| 735 |
+
items.append(row)
|
| 736 |
+
cols = [str(it).strip() for it in items[0]]
|
| 737 |
+
# armar diccionario por cada linea
|
| 738 |
+
items = [dict([(cols[i], str(v).strip()) for i, v in enumerate(item)]) for item in items[1:]]
|
| 739 |
+
return cols, items
|
| 740 |
+
elif ext == '.json':
|
| 741 |
+
items = json.load(archivo)
|
| 742 |
+
elif ext == '.dbf':
|
| 743 |
+
dic = {}
|
| 744 |
+
formatos = [('Encabezado', ENCABEZADO, dic), ]
|
| 745 |
+
leer_dbf(formatos, conf_dbf)
|
| 746 |
+
items = [dic]
|
| 747 |
+
elif ext == '.txt':
|
| 748 |
+
dic = {}
|
| 749 |
+
for linea in archivo:
|
| 750 |
+
if str(linea[0]) == '0':
|
| 751 |
+
dic.update(leer(linea, ENCABEZADO))
|
| 752 |
+
else:
|
| 753 |
+
print("Tipo de registro incorrecto:", linea[0])
|
| 754 |
+
items.append(dic)
|
| 755 |
+
else:
|
| 756 |
+
raise RuntimeError("Extension de archivo desconocida: %s" % ext)
|
| 757 |
+
archivo.close()
|
| 758 |
+
cols = [k[0] for k in ENCABEZADO]
|
| 759 |
+
return cols, items
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
def escribir_archivo(cols, items, nombre_archivo, agrega=False):
|
| 763 |
+
archivo = open(nombre_archivo, agrega and "a" or "w")
|
| 764 |
+
ext = os.path.splitext(nombre_archivo)[1]
|
| 765 |
+
if ext == '.csv':
|
| 766 |
+
csv_writer = csv.writer(archivo, dialect='excel', delimiter=";")
|
| 767 |
+
csv_writer.writerows([cols])
|
| 768 |
+
csv_writer.writerows([[item[k] for k in cols] for item in items])
|
| 769 |
+
elif ext == '.json':
|
| 770 |
+
json.dump(items, archivo, sort_keys=True, indent=4)
|
| 771 |
+
elif ext == '.dbf':
|
| 772 |
+
formatos = [('Encabezado', ENCABEZADO, items), ]
|
| 773 |
+
guardar_dbf(formatos, True, conf_dbf)
|
| 774 |
+
elif ext == '.txt':
|
| 775 |
+
for dic in items:
|
| 776 |
+
dic['tipo_reg'] = 0
|
| 777 |
+
archivo.write(escribir(dic, ENCABEZADO))
|
| 778 |
+
else:
|
| 779 |
+
raise RuntimeError("Extension de archivo desconocida: %s" % ext)
|
| 780 |
+
archivo.close()
|
| 781 |
+
|
| 782 |
+
|
| 783 |
+
class WSCTGv2(BaseWS):
|
| 784 |
+
_reg_progid_ = "WSCTGv2"
|
| 785 |
+
_reg_clsid_ = "{ACDEFB8A-34E1-48CF-94E8-6AF6ADA0717A}"
|
| 786 |
+
|
| 787 |
+
|
| 788 |
+
# busco el directorio de instalaciΓ³n (global para que no cambie si usan otra dll)
|
| 789 |
+
if not hasattr(sys, "frozen"):
|
| 790 |
+
basepath = __file__
|
| 791 |
+
elif sys.frozen == 'dll':
|
| 792 |
+
import win32api
|
| 793 |
+
basepath = win32api.GetModuleFileName(sys.frozendllhandle)
|
| 794 |
+
else:
|
| 795 |
+
basepath = sys.executable
|
| 796 |
+
INSTALL_DIR = WSCTG.InstallDir = WSCTGv2.InstallDir = get_install_dir()
|
| 797 |
+
|
| 798 |
+
|
| 799 |
+
if __name__ == '__main__':
|
| 800 |
+
if '--ayuda' in sys.argv:
|
| 801 |
+
print(LICENCIA)
|
| 802 |
+
print(AYUDA)
|
| 803 |
+
sys.exit(0)
|
| 804 |
+
if '--formato' in sys.argv:
|
| 805 |
+
print("Formato:")
|
| 806 |
+
for msg, formato in [('Encabezado', ENCABEZADO), ]:
|
| 807 |
+
comienzo = 1
|
| 808 |
+
print("=== %s ===" % msg)
|
| 809 |
+
for fmt in formato:
|
| 810 |
+
clave, longitud, tipo = fmt[0:3]
|
| 811 |
+
dec = len(fmt) > 3 and fmt[3] or (tipo == 'I' and '2' or '')
|
| 812 |
+
print(" * Campo: %-20s PosiciΓ³n: %3d Longitud: %4d Tipo: %s Decimales: %s" % (
|
| 813 |
+
clave, comienzo, longitud, tipo, dec))
|
| 814 |
+
comienzo += longitud
|
| 815 |
+
sys.exit(0)
|
| 816 |
+
|
| 817 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 818 |
+
import win32com.server.register
|
| 819 |
+
win32com.server.register.UseCommandLine(WSCTG)
|
| 820 |
+
# Compatibilidad hacia atrΓ‘s:
|
| 821 |
+
win32com.server.register.UseCommandLine(WSCTGv2)
|
| 822 |
+
sys.exit(0)
|
| 823 |
+
|
| 824 |
+
import csv
|
| 825 |
+
from configparser import SafeConfigParser
|
| 826 |
+
|
| 827 |
+
try:
|
| 828 |
+
|
| 829 |
+
if "--version" in sys.argv:
|
| 830 |
+
print("VersiΓ³n: ", __version__)
|
| 831 |
+
|
| 832 |
+
for arg in sys.argv[1:]:
|
| 833 |
+
if arg.startswith("--"):
|
| 834 |
+
break
|
| 835 |
+
print("Usando configuraciΓ³n:", arg)
|
| 836 |
+
CONFIG_FILE = arg
|
| 837 |
+
|
| 838 |
+
config = SafeConfigParser()
|
| 839 |
+
config.read(CONFIG_FILE)
|
| 840 |
+
CERT = config.get('WSAA', 'CERT')
|
| 841 |
+
PRIVATEKEY = config.get('WSAA', 'PRIVATEKEY')
|
| 842 |
+
CUIT = config.get('WSCTG', 'CUIT')
|
| 843 |
+
ENTRADA = config.get('WSCTG', 'ENTRADA')
|
| 844 |
+
SALIDA = config.get('WSCTG', 'SALIDA')
|
| 845 |
+
|
| 846 |
+
if config.has_option('WSAA', 'URL') and not HOMO:
|
| 847 |
+
wsaa_url = config.get('WSAA', 'URL')
|
| 848 |
+
else:
|
| 849 |
+
wsaa_url = None
|
| 850 |
+
if config.has_option('WSCTG', 'URL') and not HOMO:
|
| 851 |
+
wsctg_url = config.get('WSCTG', 'URL')
|
| 852 |
+
else:
|
| 853 |
+
wsctg_url = WSDL
|
| 854 |
+
|
| 855 |
+
if config.has_section('DBF'):
|
| 856 |
+
conf_dbf = dict(config.items('DBF'))
|
| 857 |
+
if DEBUG:
|
| 858 |
+
print("conf_dbf", conf_dbf)
|
| 859 |
+
else:
|
| 860 |
+
conf_dbf = {}
|
| 861 |
+
|
| 862 |
+
DEBUG = '--debug' in sys.argv
|
| 863 |
+
XML = '--xml' in sys.argv
|
| 864 |
+
|
| 865 |
+
if DEBUG:
|
| 866 |
+
print("Usando ConfiguraciΓ³n:")
|
| 867 |
+
print("wsaa_url:", wsaa_url)
|
| 868 |
+
print("wsctg_url:", wsctg_url)
|
| 869 |
+
|
| 870 |
+
# obteniendo el TA
|
| 871 |
+
from .wsaa import WSAA
|
| 872 |
+
wsaa = WSAA()
|
| 873 |
+
ta = wsaa.Autenticar("wsctg", CERT, PRIVATEKEY, wsaa_url, debug=DEBUG)
|
| 874 |
+
if not ta:
|
| 875 |
+
sys.exit("Imposible autenticar con WSAA: %s" % wsaa.Excepcion)
|
| 876 |
+
|
| 877 |
+
# cliente soap del web service
|
| 878 |
+
wsctg = WSCTG()
|
| 879 |
+
wsctg.Conectar(wsdl=wsctg_url)
|
| 880 |
+
wsctg.SetTicketAcceso(ta)
|
| 881 |
+
wsctg.Cuit = CUIT
|
| 882 |
+
|
| 883 |
+
if '--dummy' in sys.argv:
|
| 884 |
+
ret = wsctg.Dummy()
|
| 885 |
+
print("AppServerStatus", wsctg.AppServerStatus)
|
| 886 |
+
print("DbServerStatus", wsctg.DbServerStatus)
|
| 887 |
+
print("AuthServerStatus", wsctg.AuthServerStatus)
|
| 888 |
+
sys.exit(0)
|
| 889 |
+
|
| 890 |
+
if '--anular' in sys.argv:
|
| 891 |
+
i = sys.argv.index("--anular")
|
| 892 |
+
# print wsctg.client.help("anularCTG")
|
| 893 |
+
if i + 2 > len(sys.argv) or sys.argv[i + 1].startswith("--"):
|
| 894 |
+
carta_porte = input("Ingrese Carta de Porte: ")
|
| 895 |
+
ctg = input("Ingrese CTG: ")
|
| 896 |
+
else:
|
| 897 |
+
carta_porte = sys.argv[i + 1]
|
| 898 |
+
ctg = sys.argv[i + 2]
|
| 899 |
+
ret = wsctg.AnularCTG(carta_porte, ctg)
|
| 900 |
+
print("Carta Porte", wsctg.CartaPorte)
|
| 901 |
+
print("Numero CTG", wsctg.NumeroCTG)
|
| 902 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 903 |
+
print("Codigo Anulacion de CTG", wsctg.CodigoOperacion)
|
| 904 |
+
print("Errores:", wsctg.Errores)
|
| 905 |
+
sys.exit(0)
|
| 906 |
+
|
| 907 |
+
if '--rechazar' in sys.argv:
|
| 908 |
+
i = sys.argv.index("--rechazar")
|
| 909 |
+
# print wsctg.client.help("rechazarCTG")
|
| 910 |
+
if i + 3 > len(sys.argv) or sys.argv[i + 1].startswith("--"):
|
| 911 |
+
carta_porte = input("Ingrese Carta de Porte: ")
|
| 912 |
+
ctg = input("Ingrese CTG: ")
|
| 913 |
+
motivo = input("Motivo: ")
|
| 914 |
+
else:
|
| 915 |
+
carta_porte = sys.argv[i + 1]
|
| 916 |
+
ctg = sys.argv[i + 2]
|
| 917 |
+
motivo = sys.argv[i + 3]
|
| 918 |
+
ret = wsctg.RechazarCTG(carta_porte, ctg, motivo)
|
| 919 |
+
print("Carta Porte", wsctg.CartaPorte)
|
| 920 |
+
print("Numero CTG", wsctg.NumeroCTG)
|
| 921 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 922 |
+
print("Codigo Anulacion de CTG", wsctg.CodigoOperacion)
|
| 923 |
+
print("Errores:", wsctg.Errores)
|
| 924 |
+
sys.exit(0)
|
| 925 |
+
|
| 926 |
+
# Recuperar parΓ‘metros:
|
| 927 |
+
|
| 928 |
+
if '--provincias' in sys.argv:
|
| 929 |
+
ret = wsctg.ConsultarProvincias()
|
| 930 |
+
print("\n".join(ret))
|
| 931 |
+
|
| 932 |
+
if '--localidades' in sys.argv:
|
| 933 |
+
ret = wsctg.ConsultarLocalidadesPorProvincia(16)
|
| 934 |
+
print("\n".join(ret))
|
| 935 |
+
|
| 936 |
+
if '--especies' in sys.argv:
|
| 937 |
+
ret = wsctg.ConsultarEspecies()
|
| 938 |
+
print("\n".join(ret))
|
| 939 |
+
|
| 940 |
+
if '--cosechas' in sys.argv:
|
| 941 |
+
ret = wsctg.ConsultarCosechas()
|
| 942 |
+
print("\n".join(ret))
|
| 943 |
+
|
| 944 |
+
if '--establecimientos' in sys.argv:
|
| 945 |
+
ret = wsctg.ConsultarEstablecimientos()
|
| 946 |
+
print("\n".join(ret))
|
| 947 |
+
|
| 948 |
+
if '--prueba' in sys.argv or '--formato' in sys.argv:
|
| 949 |
+
prueba = dict(numero_carta_de_porte=512345679, codigo_especie=23,
|
| 950 |
+
cuit_canjeador=0, # 30660685908,
|
| 951 |
+
cuit_destino=20111111112, cuit_destinatario=20222222223,
|
| 952 |
+
codigo_localidad_origen=3058, codigo_localidad_destino=3059,
|
| 953 |
+
codigo_cosecha='1314', peso_neto_carga=1000,
|
| 954 |
+
km_a_recorrer=1234,
|
| 955 |
+
observaciones='', establecimiento=1,
|
| 956 |
+
)
|
| 957 |
+
if [argv for argv in sys.argv if argv.startswith(("--confirmar",
|
| 958 |
+
"--regresar", '--cambiar'))]:
|
| 959 |
+
prueba.update(dict(
|
| 960 |
+
numero_ctg="49241727", transaccion='10000001681',
|
| 961 |
+
consumo_propio='S',
|
| 962 |
+
))
|
| 963 |
+
parcial = dict(
|
| 964 |
+
cant_horas=1,
|
| 965 |
+
patente_vehiculo='APE652', cuit_transportista=20333333334,
|
| 966 |
+
)
|
| 967 |
+
if not '--parcial' in sys.argv:
|
| 968 |
+
prueba.update(parcial)
|
| 969 |
+
|
| 970 |
+
escribir_archivo(list(prueba.keys()), [prueba], ENTRADA)
|
| 971 |
+
|
| 972 |
+
cols, items = leer_archivo(ENTRADA)
|
| 973 |
+
ctg = None
|
| 974 |
+
|
| 975 |
+
if '--solicitar' in sys.argv:
|
| 976 |
+
wsctg.LanzarExcepciones = True
|
| 977 |
+
for it in items:
|
| 978 |
+
print("solicitando...", ' '.join(['%s=%s' % (k, v) for k, v in list(it.items())]))
|
| 979 |
+
ctg = wsctg.SolicitarCTGInicial(**it)
|
| 980 |
+
print("numero CTG: ", ctg)
|
| 981 |
+
print("Observiacion: ", wsctg.Observaciones)
|
| 982 |
+
print("Carta Porte", wsctg.CartaPorte)
|
| 983 |
+
print("Numero CTG", wsctg.NumeroCTG)
|
| 984 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 985 |
+
print("Vigencia Desde", wsctg.VigenciaDesde)
|
| 986 |
+
print("Vigencia Hasta", wsctg.VigenciaHasta)
|
| 987 |
+
print("Tarifa Referencia: ", wsctg.TarifaReferencia)
|
| 988 |
+
print("Errores:", wsctg.Errores)
|
| 989 |
+
print("Controles:", wsctg.Controles)
|
| 990 |
+
it['numero_ctg'] = wsctg.NumeroCTG
|
| 991 |
+
it['tarifa_referencia'] = wsctg.TarifaReferencia
|
| 992 |
+
it['observaciones'] = wsctg.Observaciones
|
| 993 |
+
it['fecha_hora'] = wsctg.FechaHora
|
| 994 |
+
it['vigencia_desde'] = wsctg.VigenciaDesde
|
| 995 |
+
it['vigencia_hasta'] = wsctg.VigenciaHasta
|
| 996 |
+
it['errores'] = '|'.join(wsctg.Errores)
|
| 997 |
+
it['controles'] = '|'.join(wsctg.Controles)
|
| 998 |
+
|
| 999 |
+
if '--parcial' in sys.argv:
|
| 1000 |
+
wsctg.LanzarExcepciones = True
|
| 1001 |
+
for it in items:
|
| 1002 |
+
print("solicitando dato pendiente...", ' '.join(['%s=%s' % (k, v) for k, v in list(parcial.items())]))
|
| 1003 |
+
ctg = wsctg.SolicitarCTGDatoPendiente(
|
| 1004 |
+
numero_carta_de_porte=wsctg.CartaPorte,
|
| 1005 |
+
**parcial)
|
| 1006 |
+
print("numero CTG: ", ctg)
|
| 1007 |
+
print("Observiacion: ", wsctg.Observaciones)
|
| 1008 |
+
print("Carta Porte", wsctg.CartaPorte)
|
| 1009 |
+
print("Numero CTG", wsctg.NumeroCTG)
|
| 1010 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 1011 |
+
print("Vigencia Desde", wsctg.VigenciaDesde)
|
| 1012 |
+
print("Vigencia Hasta", wsctg.VigenciaHasta)
|
| 1013 |
+
print("Tarifa Referencia: ", wsctg.TarifaReferencia)
|
| 1014 |
+
print("Errores:", wsctg.Errores)
|
| 1015 |
+
print("Controles:", wsctg.Controles)
|
| 1016 |
+
it['numero_ctg'] = wsctg.NumeroCTG
|
| 1017 |
+
it['tarifa_referencia'] = wsctg.TarifaReferencia
|
| 1018 |
+
it['errores'] = '|'.join(wsctg.Errores)
|
| 1019 |
+
it['controles'] = '|'.join(wsctg.Controles)
|
| 1020 |
+
|
| 1021 |
+
if '--confirmar_arribo' in sys.argv:
|
| 1022 |
+
for it in items:
|
| 1023 |
+
print("confirmando...", ' '.join(['%s=%s' % (k, v) for k, v in list(it.items())]))
|
| 1024 |
+
transaccion = wsctg.ConfirmarArribo(**it)
|
| 1025 |
+
print("transaccion: %s" % (transaccion, ))
|
| 1026 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 1027 |
+
print("Errores:", wsctg.Errores)
|
| 1028 |
+
it['transaccion'] = transaccion
|
| 1029 |
+
it['errores'] = '|'.join(wsctg.Errores)
|
| 1030 |
+
it['controles'] = '|'.join(wsctg.Controles)
|
| 1031 |
+
|
| 1032 |
+
if '--confirmar_definitivo' in sys.argv:
|
| 1033 |
+
if '--testing' in sys.argv:
|
| 1034 |
+
wsctg.LoadTestXML("wsctg_confirmar_def.xml") # cargo respuesta
|
| 1035 |
+
for it in items:
|
| 1036 |
+
print("confirmando...", ' '.join(['%s=%s' % (k, v) for k, v in list(it.items())]))
|
| 1037 |
+
transaccion = wsctg.ConfirmarDefinitivo(**it)
|
| 1038 |
+
print("transaccion: %s" % (transaccion, ))
|
| 1039 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 1040 |
+
print("Errores:", wsctg.Errores)
|
| 1041 |
+
it['transaccion'] = transaccion
|
| 1042 |
+
it['errores'] = '|'.join(wsctg.Errores)
|
| 1043 |
+
it['controles'] = '|'.join(wsctg.Errores)
|
| 1044 |
+
|
| 1045 |
+
if '--regresar_a_origen_rechazado' in sys.argv:
|
| 1046 |
+
for it in items:
|
| 1047 |
+
print("regresando...", ' '.join(['%s=%s' % (k, v) for k, v in list(it.items())]))
|
| 1048 |
+
transaccion = wsctg.RegresarAOrigenCTGRechazado(**it)
|
| 1049 |
+
print("transaccion: %s" % (transaccion, ))
|
| 1050 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 1051 |
+
print("Errores:", wsctg.Errores)
|
| 1052 |
+
it['transaccion'] = transaccion
|
| 1053 |
+
it['errores'] = '|'.join(wsctg.Errores)
|
| 1054 |
+
it['controles'] = '|'.join(wsctg.Errores)
|
| 1055 |
+
|
| 1056 |
+
if '--cambiar_destino_destinatario_rechazado' in sys.argv:
|
| 1057 |
+
for it in items:
|
| 1058 |
+
print("cambiando...", ' '.join(['%s=%s' % (k, v) for k, v in list(it.items())]))
|
| 1059 |
+
transaccion = wsctg.CambiarDestinoDestinatarioCTGRechazado(**it)
|
| 1060 |
+
print("transaccion: %s" % (transaccion, ))
|
| 1061 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 1062 |
+
print("Errores:", wsctg.Errores)
|
| 1063 |
+
it['transaccion'] = transaccion
|
| 1064 |
+
it['errores'] = '|'.join(wsctg.Errores)
|
| 1065 |
+
it['controles'] = '|'.join(wsctg.Errores)
|
| 1066 |
+
|
| 1067 |
+
if '--consultar_detalle' in sys.argv:
|
| 1068 |
+
i = sys.argv.index("--consultar_detalle")
|
| 1069 |
+
if len(sys.argv) > i + 1 and not sys.argv[i + 1].startswith("--"):
|
| 1070 |
+
ctg = int(sys.argv[i + 1])
|
| 1071 |
+
elif not ctg:
|
| 1072 |
+
ctg = int(input("Numero de CTG: ") or '0') or 73714620
|
| 1073 |
+
|
| 1074 |
+
wsctg.LanzarExcepciones = True
|
| 1075 |
+
for i, it in enumerate(items):
|
| 1076 |
+
print("consultando detalle...", ctg)
|
| 1077 |
+
ok = wsctg.ConsultarDetalleCTG(ctg)
|
| 1078 |
+
print("Numero CTG: ", wsctg.NumeroCTG)
|
| 1079 |
+
print("Tarifa Referencia: ", wsctg.TarifaReferencia)
|
| 1080 |
+
print("Observiacion: ", wsctg.Observaciones)
|
| 1081 |
+
print("Carta Porte", wsctg.CartaPorte)
|
| 1082 |
+
print("Numero CTG", wsctg.NumeroCTG)
|
| 1083 |
+
print("Fecha y Hora", wsctg.FechaHora)
|
| 1084 |
+
print("Vigencia Desde", wsctg.VigenciaDesde)
|
| 1085 |
+
print("Vigencia Hasta", wsctg.VigenciaHasta)
|
| 1086 |
+
print("Errores:", wsctg.Errores)
|
| 1087 |
+
print("Controles:", wsctg.Controles)
|
| 1088 |
+
print("Detalle:", wsctg.Detalle)
|
| 1089 |
+
it['numero_ctg'] = wsctg.NumeroCTG
|
| 1090 |
+
it['observaciones'] = wsctg.Observaciones
|
| 1091 |
+
it['fecha_hora'] = wsctg.FechaHora
|
| 1092 |
+
it['vigencia_desde'] = wsctg.VigenciaDesde
|
| 1093 |
+
it['vigencia_hasta'] = wsctg.VigenciaHasta
|
| 1094 |
+
wsctg.AnalizarXml("XmlResponse")
|
| 1095 |
+
for k, ki in list({'ctg': 'numero_ctg', 'solicitante': '',
|
| 1096 |
+
'estado': 'estado',
|
| 1097 |
+
'especie': '', # 'codigo_especie', no devuelve codigo!
|
| 1098 |
+
'cosecha': '', # 'codigo_cosecha', no devuelve codigo!
|
| 1099 |
+
'cuitCanjeador': 'cuit_canjeador',
|
| 1100 |
+
'cuitDestino': 'cuit_destino',
|
| 1101 |
+
'cuitDestinatario': 'cuit_destinatario',
|
| 1102 |
+
'cuitTransportista': 'cuit_transportista',
|
| 1103 |
+
'establecimiento': 'establecimiento',
|
| 1104 |
+
'localidadOrigen': 'localidad_origen',
|
| 1105 |
+
'localidadDestino': 'localidad_destino',
|
| 1106 |
+
'cantidadHoras': 'cantidad_horas',
|
| 1107 |
+
'patenteVehiculo': 'patente_vehiculo',
|
| 1108 |
+
'pesoNetoCarga': 'peso_neto_carga',
|
| 1109 |
+
'kmRecorridos': 'km_recorridos',
|
| 1110 |
+
'tarifaReferencia': 'tarifa_referencia',
|
| 1111 |
+
'ctcCodigo': 'ctc_codigo',
|
| 1112 |
+
'turno': 'turno',
|
| 1113 |
+
}.items()):
|
| 1114 |
+
v = wsctg.ObtenerTagXml('consultarDetalleCTGDatos', k)
|
| 1115 |
+
print(k, v)
|
| 1116 |
+
if ki.startswith("cuit") and v:
|
| 1117 |
+
v = v[:11]
|
| 1118 |
+
it[ki] = v
|
| 1119 |
+
|
| 1120 |
+
escribir_archivo(cols, items, SALIDA)
|
| 1121 |
+
|
| 1122 |
+
if "--consultar" in sys.argv:
|
| 1123 |
+
wsctg.LanzarExcepciones = True
|
| 1124 |
+
wsctg.ConsultarCTG(fecha_emision_desde="01/04/2012")
|
| 1125 |
+
print("Numero CTG - Carta de Porte - Imprime Constancia - Estado")
|
| 1126 |
+
while wsctg.LeerDatosCTG():
|
| 1127 |
+
print(wsctg.NumeroCTG, wsctg.CartaPorte, end=' ')
|
| 1128 |
+
print(wsctg.ImprimeConstancia, wsctg.Estado, wsctg.FechaHora)
|
| 1129 |
+
|
| 1130 |
+
if "--consultar_rechazados" in sys.argv:
|
| 1131 |
+
wsctg.LanzarExcepciones = True
|
| 1132 |
+
wsctg.ConsultarCTGRechazados()
|
| 1133 |
+
print("Numero CTG - Carta de Porte - Fecha - Destino/Dest./Obs.")
|
| 1134 |
+
while wsctg.LeerDatosCTG():
|
| 1135 |
+
print(wsctg.NumeroCTG, wsctg.CartaPorte, wsctg.FechaHora, end=' ')
|
| 1136 |
+
print(wsctg.Destino, wsctg.Destinatario, wstcg.Observaciones)
|
| 1137 |
+
|
| 1138 |
+
if "--consultar_activos_por_patente" in sys.argv:
|
| 1139 |
+
i = sys.argv.index("--consultar_activos_por_patente")
|
| 1140 |
+
if len(sys.argv) > i + 1 and not sys.argv[i + 1].startswith("--"):
|
| 1141 |
+
patente = sys.argv[i + 1]
|
| 1142 |
+
elif not ctg:
|
| 1143 |
+
patente = input("Patente: ") or 'APE652'
|
| 1144 |
+
wsctg.LanzarExcepciones = True
|
| 1145 |
+
if '--testing' in sys.argv:
|
| 1146 |
+
wsctg.LoadTestXML("wsctgv2_activos.xml")
|
| 1147 |
+
wsctg.ConsultarCTGActivosPorPatente(patente=patente)
|
| 1148 |
+
print("Numero CTG - Carta de Porte - Fecha - Peso Neto - Usuario")
|
| 1149 |
+
while wsctg.LeerDatosCTG():
|
| 1150 |
+
print(wsctg.NumeroCTG, wsctg.CartaPorte, wsctg.Patente, end=' ')
|
| 1151 |
+
print(wsctg.FechaHora, wsctg.FechaVencimiento, wsctg.PesoNeto, end=' ')
|
| 1152 |
+
print(wsctg.UsuarioSolicitante, wsctg.UsuarioReal)
|
| 1153 |
+
|
| 1154 |
+
if '--consultar_excel' in sys.argv:
|
| 1155 |
+
archivo = input("Archivo a generar (planilla.xls): ") or \
|
| 1156 |
+
'planilla.xls'
|
| 1157 |
+
wsctg.LanzarExcepciones = True
|
| 1158 |
+
ok = wsctg.ConsultarCTGExcel(fecha_emision_desde="01/04/2012",
|
| 1159 |
+
archivo=archivo)
|
| 1160 |
+
print("Errores:", wsctg.Errores)
|
| 1161 |
+
|
| 1162 |
+
if '--consultar_constancia_pdf' in sys.argv:
|
| 1163 |
+
i = sys.argv.index("--consultar_constancia_pdf")
|
| 1164 |
+
if len(sys.argv) > i + 2 and not sys.argv[i + 1].startswith("--"):
|
| 1165 |
+
ctg = int(sys.argv[i + 1])
|
| 1166 |
+
archivo = sys.argv[i + 2]
|
| 1167 |
+
elif not ctg:
|
| 1168 |
+
ctg = int(input("Numero de CTG: ") or '0') or 83139794
|
| 1169 |
+
archivo = input("Archivo a generar (constancia.pdf): ") or \
|
| 1170 |
+
'constancia.pdf'
|
| 1171 |
+
|
| 1172 |
+
wsctg.LanzarExcepciones = True
|
| 1173 |
+
ok = wsctg.ConsultarConstanciaCTGPDF(ctg, archivo)
|
| 1174 |
+
print("Errores:", wsctg.Errores)
|
| 1175 |
+
|
| 1176 |
+
if "--pendientes" in sys.argv:
|
| 1177 |
+
wsctg.LanzarExcepciones = True
|
| 1178 |
+
wsctg.CTGsPendientesResolucion()
|
| 1179 |
+
for clave in ("arrayCTGsRechazadosAResolver",
|
| 1180 |
+
"arrayCTGsOtorgadosAResolver",
|
| 1181 |
+
"arrayCTGsConfirmadosAResolver", ):
|
| 1182 |
+
print(clave[6:])
|
| 1183 |
+
print("Numero CTG - Carta de Porte - Imprime Constancia - Estado")
|
| 1184 |
+
while wsctg.LeerDatosCTG(clave):
|
| 1185 |
+
print(wsctg.NumeroCTG, wsctg.CartaPorte, wsctg.FechaHora)
|
| 1186 |
+
print(wsctg.Destino, wsctg.Destinatario, wsctg.Observaciones)
|
| 1187 |
+
|
| 1188 |
+
print("hecho.")
|
| 1189 |
+
|
| 1190 |
+
except SoapFault as e:
|
| 1191 |
+
print("Falla SOAP:", e.faultcode, e.faultstring.encode("ascii", "ignore"))
|
| 1192 |
+
sys.exit(3)
|
| 1193 |
+
except Exception as e:
|
| 1194 |
+
ex = utils.exception_info()
|
| 1195 |
+
print(ex)
|
| 1196 |
+
if DEBUG:
|
| 1197 |
+
raise
|
| 1198 |
+
sys.exit(5)
|
app/pyafipws/wsfev1.py
ADDED
|
@@ -0,0 +1,1349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"""MοΏ½dulo para obtener CAE/CAEA, cοΏ½digo de autorizaciοΏ½n electrοΏ½nico webservice
|
| 14 |
+
WSFEv1 de AFIP (Factura ElectrοΏ½nica Nacional - Proyecto Version 1 - 2.10)
|
| 15 |
+
SegοΏ½n RG 2485/08, RG 2757/2010, RG 2904/2010 y RG2926/10 (CAE anticipado),
|
| 16 |
+
RG 3067/2011 (RS - Monotributo), RG 3571/2013 (Responsables inscriptos IVA),
|
| 17 |
+
RG 3668/2014 (Factura A IVA F.8001), RG 3749/2015 (R.I. y exentos)
|
| 18 |
+
RG 4004-E Alquiler de inmuebles con destino casa habitaciοΏ½n).
|
| 19 |
+
RG 4109-E Venta de bienes muebles registrables.
|
| 20 |
+
RG 4291/2018 RοΏ½gimen especial de emisiοΏ½n y almacenamiento electrοΏ½nico
|
| 21 |
+
MοΏ½s info: http://www.sistemasagiles.com.ar/trac/wiki/ProyectoWSFEv1
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 25 |
+
__copyright__ = "Copyright (C) 2010-2017 Mariano Reingart"
|
| 26 |
+
__license__ = "GPL 3.0"
|
| 27 |
+
__version__ = "1.22b"
|
| 28 |
+
|
| 29 |
+
import datetime
|
| 30 |
+
import decimal
|
| 31 |
+
import os
|
| 32 |
+
import sys
|
| 33 |
+
from .utils import verifica, inicializar_y_capturar_excepciones, BaseWS, get_install_dir
|
| 34 |
+
|
| 35 |
+
HOMO = False # solo homologaciοΏ½n
|
| 36 |
+
TYPELIB = False # usar librerοΏ½a de tipos (TLB)
|
| 37 |
+
LANZAR_EXCEPCIONES = False # valor por defecto: True
|
| 38 |
+
|
| 39 |
+
#WSDL = "https://www.sistemasagiles.com.ar/simulador/wsfev1/call/soap?WSDL=None"
|
| 40 |
+
WSDL = "https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL"
|
| 41 |
+
#WSDL = "file:///home/reingart/tmp/service.asmx.xml"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class WSFEv1(BaseWS):
|
| 45 |
+
"Interfaz para el WebService de Factura ElectrοΏ½nica Version 1 - 2.10"
|
| 46 |
+
_public_methods_ = ['CrearFactura', 'AgregarIva', 'CAESolicitar',
|
| 47 |
+
'AgregarTributo', 'AgregarCmpAsoc', 'AgregarOpcional',
|
| 48 |
+
'AgregarComprador',
|
| 49 |
+
'CompUltimoAutorizado', 'CompConsultar',
|
| 50 |
+
'CAEASolicitar', 'CAEAConsultar', 'CAEARegInformativo',
|
| 51 |
+
'CAEASinMovimientoInformar',
|
| 52 |
+
'CAESolicitarX', 'CompTotXRequest',
|
| 53 |
+
'IniciarFacturasX', 'AgregarFacturaX', 'LeerFacturaX',
|
| 54 |
+
'ParamGetTiposCbte',
|
| 55 |
+
'ParamGetTiposConcepto',
|
| 56 |
+
'ParamGetTiposDoc',
|
| 57 |
+
'ParamGetTiposIva',
|
| 58 |
+
'ParamGetTiposMonedas',
|
| 59 |
+
'ParamGetTiposOpcional',
|
| 60 |
+
'ParamGetTiposTributos',
|
| 61 |
+
'ParamGetTiposPaises',
|
| 62 |
+
'ParamGetCotizacion',
|
| 63 |
+
'ParamGetPtosVenta',
|
| 64 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 65 |
+
'SetParametros', 'SetTicketAcceso', 'GetParametro',
|
| 66 |
+
'EstablecerCampoFactura', 'ObtenerCampoFactura',
|
| 67 |
+
'Dummy', 'Conectar', 'DebugLog', 'SetTicketAcceso']
|
| 68 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 69 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 70 |
+
'XmlRequest', 'XmlResponse', 'Version', 'Excepcion', 'LanzarExcepciones',
|
| 71 |
+
'Resultado', 'Obs', 'Observaciones', 'Traceback', 'InstallDir',
|
| 72 |
+
'CAE', 'Vencimiento', 'Eventos', 'Errores', 'ErrCode', 'ErrMsg',
|
| 73 |
+
'Reprocesar', 'Reproceso', 'EmisionTipo', 'CAEA',
|
| 74 |
+
'CbteNro', 'CbtDesde', 'CbtHasta', 'FechaCbte',
|
| 75 |
+
'ImpTotal', 'ImpNeto', 'ImptoLiq',
|
| 76 |
+
'ImpIVA', 'ImpOpEx', 'ImpTrib', 'FchCotiz',]
|
| 77 |
+
|
| 78 |
+
_reg_progid_ = "WSFEv1"
|
| 79 |
+
_reg_clsid_ = "{CA0E604D-E3D7-493A-8880-F6CDD604185E}"
|
| 80 |
+
|
| 81 |
+
if TYPELIB:
|
| 82 |
+
_typelib_guid_ = '{B1D7283C-3EC2-463E-89B4-11F5228E2A15}'
|
| 83 |
+
_typelib_version_ = 1, 18
|
| 84 |
+
_com_interfaces_ = ['IWSFEv1']
|
| 85 |
+
##_reg_class_spec_ = "wsfev1.WSFEv1"
|
| 86 |
+
|
| 87 |
+
# Variables globales para BaseWS:
|
| 88 |
+
HOMO = HOMO
|
| 89 |
+
WSDL = WSDL
|
| 90 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '')
|
| 91 |
+
Reprocesar = True # recuperar automaticamente CAE emitidos
|
| 92 |
+
LanzarExcepciones = LANZAR_EXCEPCIONES
|
| 93 |
+
factura = None
|
| 94 |
+
facturas = None
|
| 95 |
+
|
| 96 |
+
def inicializar(self):
|
| 97 |
+
BaseWS.inicializar(self)
|
| 98 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 99 |
+
self.Resultado = self.Motivo = self.Reproceso = ''
|
| 100 |
+
self.LastID = self.LastCMP = self.CAE = self.CAEA = self.Vencimiento = ''
|
| 101 |
+
self.CbteNro = self.CbtDesde = self.CbtHasta = self.PuntoVenta = None
|
| 102 |
+
self.ImpTotal = self.ImpIVA = self.ImpOpEx = self.ImpNeto = self.ImptoLiq = self.ImpTrib = None
|
| 103 |
+
self.EmisionTipo = self.Periodo = self.Orden = ""
|
| 104 |
+
self.FechaCbte = self.FchVigDesde = self.FchVigHasta = self.FchTopeInf = self.FchProceso = ""
|
| 105 |
+
self.FchCotiz = None
|
| 106 |
+
|
| 107 |
+
def __analizar_errores(self, ret):
|
| 108 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 109 |
+
if 'Errors' in ret:
|
| 110 |
+
errores = ret['Errors']
|
| 111 |
+
for error in errores:
|
| 112 |
+
self.Errores.append("%s: %s" % (
|
| 113 |
+
error['Err']['Code'],
|
| 114 |
+
error['Err']['Msg'],
|
| 115 |
+
))
|
| 116 |
+
self.ErrCode = ' '.join([str(error['Err']['Code']) for error in errores])
|
| 117 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 118 |
+
if 'Events' in ret:
|
| 119 |
+
events = ret['Events']
|
| 120 |
+
self.Eventos = ['%s: %s' % (evt['Evt']['Code'], evt['Evt']['Msg']) for evt in events]
|
| 121 |
+
|
| 122 |
+
@inicializar_y_capturar_excepciones
|
| 123 |
+
def Dummy(self):
|
| 124 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 125 |
+
result = self.client.FEDummy()['FEDummyResult']
|
| 126 |
+
self.AppServerStatus = result.get('AppServer')
|
| 127 |
+
self.DbServerStatus = result.get('DbServer')
|
| 128 |
+
self.AuthServerStatus = result.get('AuthServer')
|
| 129 |
+
return True
|
| 130 |
+
|
| 131 |
+
# los siguientes mοΏ½todos no estοΏ½n decorados para no limpiar propiedades
|
| 132 |
+
|
| 133 |
+
def CrearFactura(self, concepto=1, tipo_doc=80, nro_doc="", tipo_cbte=1, punto_vta=0,
|
| 134 |
+
cbt_desde=0, cbt_hasta=0, imp_total=0.00, imp_tot_conc=0.00, imp_neto=0.00,
|
| 135 |
+
imp_iva=0.00, imp_trib=0.00, imp_op_ex=0.00, fecha_cbte="", fecha_venc_pago=None,
|
| 136 |
+
fecha_serv_desde=None, fecha_serv_hasta=None, #--
|
| 137 |
+
moneda_id="PES", moneda_ctz="1.0000", caea=None, fecha_hs_gen=None, **kwargs
|
| 138 |
+
):
|
| 139 |
+
|
| 140 |
+
"Creo un objeto factura (interna)"
|
| 141 |
+
# Creo una factura electronica de exportaciοΏ½n
|
| 142 |
+
fact = {'tipo_doc': tipo_doc, 'nro_doc': nro_doc,
|
| 143 |
+
'tipo_cbte': tipo_cbte, 'punto_vta': punto_vta,
|
| 144 |
+
'cbt_desde': cbt_desde, 'cbt_hasta': cbt_hasta,
|
| 145 |
+
'imp_total': imp_total, 'imp_tot_conc': imp_tot_conc,
|
| 146 |
+
'imp_neto': imp_neto, 'imp_iva': imp_iva,
|
| 147 |
+
'imp_trib': imp_trib, 'imp_op_ex': imp_op_ex,
|
| 148 |
+
'fecha_cbte': fecha_cbte,
|
| 149 |
+
'fecha_venc_pago': fecha_venc_pago,
|
| 150 |
+
'moneda_id': moneda_id, 'moneda_ctz': moneda_ctz,
|
| 151 |
+
'concepto': concepto, 'fecha_hs_gen': fecha_hs_gen,
|
| 152 |
+
'cbtes_asoc': [],
|
| 153 |
+
'tributos': [],
|
| 154 |
+
'iva': [],
|
| 155 |
+
'opcionales': [],
|
| 156 |
+
'compradores': [],
|
| 157 |
+
}
|
| 158 |
+
if fecha_serv_desde:
|
| 159 |
+
fact['fecha_serv_desde'] = fecha_serv_desde
|
| 160 |
+
if fecha_serv_hasta:
|
| 161 |
+
fact['fecha_serv_hasta'] = fecha_serv_hasta
|
| 162 |
+
if caea:
|
| 163 |
+
fact['caea'] = caea
|
| 164 |
+
|
| 165 |
+
self.factura = fact
|
| 166 |
+
return True
|
| 167 |
+
|
| 168 |
+
def EstablecerCampoFactura(self, campo, valor):
|
| 169 |
+
if campo in self.factura or campo in ('fecha_serv_desde', 'fecha_serv_hasta', 'caea', 'fch_venc_cae', 'fecha_hs_gen'):
|
| 170 |
+
self.factura[campo] = valor
|
| 171 |
+
return True
|
| 172 |
+
else:
|
| 173 |
+
return False
|
| 174 |
+
|
| 175 |
+
def AgregarCmpAsoc(self, tipo=1, pto_vta=0, nro=0, cuit=None, fecha=None, **kwarg):
|
| 176 |
+
"Agrego un comprobante asociado a una factura (interna)"
|
| 177 |
+
cmp_asoc = {'tipo': tipo, 'pto_vta': pto_vta, 'nro': nro}
|
| 178 |
+
if cuit is not None:
|
| 179 |
+
cmp_asoc['cuit'] = cuit
|
| 180 |
+
if fecha is not None:
|
| 181 |
+
cmp_asoc['fecha'] = fecha
|
| 182 |
+
self.factura['cbtes_asoc'].append(cmp_asoc)
|
| 183 |
+
return True
|
| 184 |
+
|
| 185 |
+
def AgregarTributo(self, tributo_id=0, desc="", base_imp=0.00, alic=0, importe=0.00, **kwarg):
|
| 186 |
+
"Agrego un tributo a una factura (interna)"
|
| 187 |
+
tributo = {'tributo_id': tributo_id, 'desc': desc, 'base_imp': base_imp,
|
| 188 |
+
'alic': alic, 'importe': importe}
|
| 189 |
+
self.factura['tributos'].append(tributo)
|
| 190 |
+
return True
|
| 191 |
+
|
| 192 |
+
def AgregarIva(self, iva_id=0, base_imp=0.0, importe=0.0, **kwarg):
|
| 193 |
+
"Agrego un tributo a una factura (interna)"
|
| 194 |
+
iva = {'iva_id': iva_id, 'base_imp': base_imp, 'importe': importe}
|
| 195 |
+
self.factura['iva'].append(iva)
|
| 196 |
+
return True
|
| 197 |
+
|
| 198 |
+
def AgregarOpcional(self, opcional_id=0, valor="", **kwarg):
|
| 199 |
+
"Agrego un dato opcional a una factura (interna)"
|
| 200 |
+
op = {'opcional_id': opcional_id, 'valor': valor}
|
| 201 |
+
self.factura['opcionales'].append(op)
|
| 202 |
+
return True
|
| 203 |
+
|
| 204 |
+
def AgregarComprador(self, doc_tipo=80, doc_nro=0, porcentaje=100.00, **kwarg):
|
| 205 |
+
"Agrego un comprador a una factura (interna) RG 4109-E bienes muebles"
|
| 206 |
+
comp = {'doc_tipo': doc_tipo, 'doc_nro': doc_nro,
|
| 207 |
+
'porcentaje': porcentaje}
|
| 208 |
+
self.factura['compradores'].append(comp)
|
| 209 |
+
return True
|
| 210 |
+
|
| 211 |
+
def ObtenerCampoFactura(self, *campos):
|
| 212 |
+
"Obtener el valor devuelto de AFIP para un campo de factura"
|
| 213 |
+
# cada campo puede ser una clave string (dict) o una posiciοΏ½n (list)
|
| 214 |
+
ret = self.factura
|
| 215 |
+
for campo in campos:
|
| 216 |
+
if isinstance(ret, dict) and isinstance(campo, str):
|
| 217 |
+
ret = ret.get(campo)
|
| 218 |
+
elif isinstance(ret, list) and len(ret) > campo:
|
| 219 |
+
ret = ret[campo]
|
| 220 |
+
else:
|
| 221 |
+
self.Excepcion = "El campo %s solicitado no existe" % campo
|
| 222 |
+
ret = None
|
| 223 |
+
if ret is None:
|
| 224 |
+
break
|
| 225 |
+
return str(ret)
|
| 226 |
+
|
| 227 |
+
# metodos principales para llamar remotamente a AFIP:
|
| 228 |
+
|
| 229 |
+
@inicializar_y_capturar_excepciones
|
| 230 |
+
def CAESolicitar(self):
|
| 231 |
+
f = self.factura
|
| 232 |
+
ret = self.client.FECAESolicitar(
|
| 233 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 234 |
+
FeCAEReq={
|
| 235 |
+
'FeCabReq': {'CantReg': 1,
|
| 236 |
+
'PtoVta': f['punto_vta'],
|
| 237 |
+
'CbteTipo': f['tipo_cbte']},
|
| 238 |
+
'FeDetReq': [{'FECAEDetRequest': {
|
| 239 |
+
'Concepto': f['concepto'],
|
| 240 |
+
'DocTipo': f['tipo_doc'],
|
| 241 |
+
'DocNro': f['nro_doc'],
|
| 242 |
+
'CbteDesde': f['cbt_desde'],
|
| 243 |
+
'CbteHasta': f['cbt_hasta'],
|
| 244 |
+
'CbteFch': f['fecha_cbte'],
|
| 245 |
+
'ImpTotal': f['imp_total'],
|
| 246 |
+
'ImpTotConc': f['imp_tot_conc'],
|
| 247 |
+
'ImpNeto': f['imp_neto'],
|
| 248 |
+
'ImpOpEx': f['imp_op_ex'],
|
| 249 |
+
'ImpTrib': f['imp_trib'],
|
| 250 |
+
'ImpIVA': f['imp_iva'],
|
| 251 |
+
# Fechas solo se informan si Concepto in (2,3)
|
| 252 |
+
'FchServDesde': f.get('fecha_serv_desde'),
|
| 253 |
+
'FchServHasta': f.get('fecha_serv_hasta'),
|
| 254 |
+
'FchVtoPago': f.get('fecha_venc_pago'),
|
| 255 |
+
'MonId': f['moneda_id'],
|
| 256 |
+
'MonCotiz': f['moneda_ctz'],
|
| 257 |
+
'CbtesAsoc': f['cbtes_asoc'] and [
|
| 258 |
+
{'CbteAsoc': {
|
| 259 |
+
'Tipo': cbte_asoc['tipo'],
|
| 260 |
+
'PtoVta': cbte_asoc['pto_vta'],
|
| 261 |
+
'Nro': cbte_asoc['nro'],
|
| 262 |
+
'Cuit': cbte_asoc.get('cuit'),
|
| 263 |
+
'CbteFch': cbte_asoc.get('fecha'),
|
| 264 |
+
}}
|
| 265 |
+
for cbte_asoc in f['cbtes_asoc']] or None,
|
| 266 |
+
'Tributos': f['tributos'] and [
|
| 267 |
+
{'Tributo': {
|
| 268 |
+
'Id': tributo['tributo_id'],
|
| 269 |
+
'Desc': tributo['desc'],
|
| 270 |
+
'BaseImp': tributo['base_imp'],
|
| 271 |
+
'Alic': tributo['alic'],
|
| 272 |
+
'Importe': tributo['importe'],
|
| 273 |
+
}}
|
| 274 |
+
for tributo in f['tributos']] or None,
|
| 275 |
+
'Iva': f['iva'] and [
|
| 276 |
+
{'AlicIva': {
|
| 277 |
+
'Id': iva['iva_id'],
|
| 278 |
+
'BaseImp': iva['base_imp'],
|
| 279 |
+
'Importe': iva['importe'],
|
| 280 |
+
}}
|
| 281 |
+
for iva in f['iva']] or None,
|
| 282 |
+
'Opcionales': [
|
| 283 |
+
{'Opcional': {
|
| 284 |
+
'Id': opcional['opcional_id'],
|
| 285 |
+
'Valor': opcional['valor'],
|
| 286 |
+
}} for opcional in f['opcionales']] or None,
|
| 287 |
+
'Compradores': [
|
| 288 |
+
{'Comprador': {
|
| 289 |
+
'DocTipo': comprador['doc_tipo'],
|
| 290 |
+
'DocNro': comprador['doc_nro'],
|
| 291 |
+
'Porcentaje': comprador['porcentaje'],
|
| 292 |
+
}} for comprador in f['compradores']] or None,
|
| 293 |
+
}
|
| 294 |
+
}]
|
| 295 |
+
})
|
| 296 |
+
|
| 297 |
+
result = ret['FECAESolicitarResult']
|
| 298 |
+
if 'FeCabResp' in result:
|
| 299 |
+
fecabresp = result['FeCabResp']
|
| 300 |
+
fedetresp = result['FeDetResp'][0]['FECAEDetResponse']
|
| 301 |
+
|
| 302 |
+
# Reprocesar en caso de error (recuperar CAE emitido anteriormente)
|
| 303 |
+
if self.Reprocesar and ('Errors' in result or 'Observaciones' in fedetresp):
|
| 304 |
+
for error in result.get('Errors', []) + fedetresp.get('Observaciones', []):
|
| 305 |
+
err_code = str(error.get('Err', error.get('Obs'))['Code'])
|
| 306 |
+
if fedetresp['Resultado'] == 'R' and err_code == '10016':
|
| 307 |
+
# guardo los mensajes xml originales
|
| 308 |
+
xml_request = self.client.xml_request
|
| 309 |
+
xml_response = self.client.xml_response
|
| 310 |
+
cae = self.CompConsultar(f['tipo_cbte'], f['punto_vta'], f['cbt_desde'], reproceso=True)
|
| 311 |
+
if cae and self.EmisionTipo == 'CAE':
|
| 312 |
+
self.Reproceso = 'S'
|
| 313 |
+
return cae
|
| 314 |
+
self.Reproceso = 'N'
|
| 315 |
+
# reestablesco los mensajes xml originales
|
| 316 |
+
self.client.xml_request = xml_request
|
| 317 |
+
self.client.xml_response = xml_response
|
| 318 |
+
|
| 319 |
+
self.Resultado = fecabresp['Resultado']
|
| 320 |
+
# Obs:
|
| 321 |
+
for obs in fedetresp.get('Observaciones', []):
|
| 322 |
+
self.Observaciones.append("%(Code)s: %(Msg)s" % (obs['Obs']))
|
| 323 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 324 |
+
self.CAE = fedetresp['CAE'] and str(fedetresp['CAE']) or ""
|
| 325 |
+
self.EmisionTipo = 'CAE'
|
| 326 |
+
self.Vencimiento = fedetresp['CAEFchVto']
|
| 327 |
+
self.FechaCbte = fedetresp.get('CbteFch', "") # .strftime("%Y/%m/%d")
|
| 328 |
+
self.CbteNro = fedetresp.get('CbteHasta', 0) # 1L
|
| 329 |
+
self.PuntoVenta = fecabresp.get('PtoVta', 0) # 4000
|
| 330 |
+
self.CbtDesde = fedetresp.get('CbteDesde', 0)
|
| 331 |
+
self.CbtHasta = fedetresp.get('CbteHasta', 0)
|
| 332 |
+
self.__analizar_errores(result)
|
| 333 |
+
return self.CAE
|
| 334 |
+
|
| 335 |
+
@inicializar_y_capturar_excepciones
|
| 336 |
+
def CompTotXRequest(self):
|
| 337 |
+
ret = self.client.FECompTotXRequest(
|
| 338 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
result = ret['FECompTotXRequestResult']
|
| 342 |
+
return result['RegXReq']
|
| 343 |
+
|
| 344 |
+
@inicializar_y_capturar_excepciones
|
| 345 |
+
def CompUltimoAutorizado(self, tipo_cbte, punto_vta):
|
| 346 |
+
ret = self.client.FECompUltimoAutorizado(
|
| 347 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 348 |
+
PtoVta=punto_vta,
|
| 349 |
+
CbteTipo=tipo_cbte,
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
result = ret['FECompUltimoAutorizadoResult']
|
| 353 |
+
self.CbteNro = result['CbteNro']
|
| 354 |
+
self.__analizar_errores(result)
|
| 355 |
+
return self.CbteNro is not None and str(self.CbteNro) or ''
|
| 356 |
+
|
| 357 |
+
@inicializar_y_capturar_excepciones
|
| 358 |
+
def CompConsultar(self, tipo_cbte, punto_vta, cbte_nro, reproceso=False):
|
| 359 |
+
difs = [] # si hay reproceso, verifico las diferencias con AFIP
|
| 360 |
+
|
| 361 |
+
ret = self.client.FECompConsultar(
|
| 362 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 363 |
+
FeCompConsReq={
|
| 364 |
+
'CbteTipo': tipo_cbte,
|
| 365 |
+
'CbteNro': cbte_nro,
|
| 366 |
+
'PtoVta': punto_vta,
|
| 367 |
+
})
|
| 368 |
+
|
| 369 |
+
result = ret['FECompConsultarResult']
|
| 370 |
+
if 'ResultGet' in result:
|
| 371 |
+
resultget = result['ResultGet']
|
| 372 |
+
|
| 373 |
+
if reproceso:
|
| 374 |
+
# verifico los campos registrados coincidan con los enviados:
|
| 375 |
+
f = self.factura
|
| 376 |
+
verificaciones = {
|
| 377 |
+
'Concepto': f['concepto'],
|
| 378 |
+
'DocTipo': f['tipo_doc'],
|
| 379 |
+
'DocNro': f['nro_doc'],
|
| 380 |
+
'CbteTipo': f['tipo_cbte'],
|
| 381 |
+
'CbteDesde': f['cbt_desde'],
|
| 382 |
+
'CbteHasta': f['cbt_hasta'],
|
| 383 |
+
'CbteFch': f['fecha_cbte'],
|
| 384 |
+
'ImpTotal': f['imp_total'] and float(f['imp_total']) or 0.0,
|
| 385 |
+
'ImpTotConc': f['imp_tot_conc'] and float(f['imp_tot_conc']) or 0.0,
|
| 386 |
+
'ImpNeto': f['imp_neto'] and float(f['imp_neto']) or 0.0,
|
| 387 |
+
'ImpOpEx': f['imp_op_ex'] and float(f['imp_op_ex']) or 0.0,
|
| 388 |
+
'ImpTrib': f['imp_trib'] and float(f['imp_trib']) or 0.0,
|
| 389 |
+
'ImpIVA': f['imp_iva'] and float(f['imp_iva']) or 0.0,
|
| 390 |
+
'FchServDesde': f.get('fecha_serv_desde'),
|
| 391 |
+
'FchServHasta': f.get('fecha_serv_hasta'),
|
| 392 |
+
'FchVtoPago': f.get('fecha_venc_pago'),
|
| 393 |
+
'MonId': f['moneda_id'],
|
| 394 |
+
'MonCotiz': float(f['moneda_ctz']),
|
| 395 |
+
'CbtesAsoc': [
|
| 396 |
+
{'CbteAsoc': {
|
| 397 |
+
'Tipo': cbte_asoc['tipo'],
|
| 398 |
+
'PtoVta': cbte_asoc['pto_vta'],
|
| 399 |
+
'Nro': cbte_asoc['nro'],
|
| 400 |
+
'Cuit': cbte_asoc.get('cuit'),
|
| 401 |
+
'CbteFch': cbte_asoc.get('fecha') or None,
|
| 402 |
+
}}
|
| 403 |
+
for cbte_asoc in f['cbtes_asoc']],
|
| 404 |
+
'Tributos': [
|
| 405 |
+
{'Tributo': {
|
| 406 |
+
'Id': tributo['tributo_id'],
|
| 407 |
+
'Desc': tributo['desc'],
|
| 408 |
+
'BaseImp': float(tributo['base_imp'] or 0),
|
| 409 |
+
'Alic': float(tributo['alic'] or 0),
|
| 410 |
+
'Importe': float(tributo['importe']),
|
| 411 |
+
}}
|
| 412 |
+
for tributo in f['tributos']],
|
| 413 |
+
'Iva': [
|
| 414 |
+
{'AlicIva': {
|
| 415 |
+
'Id': iva['iva_id'],
|
| 416 |
+
'BaseImp': float(iva['base_imp']),
|
| 417 |
+
'Importe': float(iva['importe']),
|
| 418 |
+
}}
|
| 419 |
+
for iva in f['iva']],
|
| 420 |
+
'Opcionales': [
|
| 421 |
+
{'Opcional': {
|
| 422 |
+
'Id': opcional['opcional_id'],
|
| 423 |
+
'Valor': opcional['valor'],
|
| 424 |
+
}} for opcional in f['opcionales']],
|
| 425 |
+
'Compradores': [
|
| 426 |
+
{'Comprador': {
|
| 427 |
+
'DocTipo': comprador['doc_tipo'],
|
| 428 |
+
'DocNro': comprador['doc_nro'],
|
| 429 |
+
'Porcentaje': comprador['porcentaje'],
|
| 430 |
+
}} for comprador in f['compradores']],
|
| 431 |
+
}
|
| 432 |
+
verifica(verificaciones, resultget.copy(), difs)
|
| 433 |
+
if difs:
|
| 434 |
+
print("Diferencias:", difs)
|
| 435 |
+
self.log("Diferencias: %s" % difs)
|
| 436 |
+
else:
|
| 437 |
+
# guardo los datos de AFIP (reconstruyo estructura interna)
|
| 438 |
+
self.factura = {
|
| 439 |
+
'concepto': resultget.get('Concepto'),
|
| 440 |
+
'tipo_doc': resultget.get('DocTipo'),
|
| 441 |
+
'nro_doc': resultget.get('DocNro'),
|
| 442 |
+
'tipo_cbte': resultget.get('CbteTipo'),
|
| 443 |
+
'punto_vta': resultget.get('PtoVta'),
|
| 444 |
+
'cbt_desde': resultget.get('CbteDesde'),
|
| 445 |
+
'cbt_hasta': resultget.get('CbteHasta'),
|
| 446 |
+
'fecha_cbte': resultget.get('CbteFch'),
|
| 447 |
+
'imp_total': resultget.get('ImpTotal'),
|
| 448 |
+
'imp_tot_conc': resultget.get('ImpTotConc'),
|
| 449 |
+
'imp_neto': resultget.get('ImpNeto'),
|
| 450 |
+
'imp_op_ex': resultget.get('ImpOpEx'),
|
| 451 |
+
'imp_trib': resultget.get('ImpTrib'),
|
| 452 |
+
'imp_iva': resultget.get('ImpIVA'),
|
| 453 |
+
'fecha_serv_desde': resultget.get('FchServDesde'),
|
| 454 |
+
'fecha_serv_hasta': resultget.get('FchServHasta'),
|
| 455 |
+
'fecha_venc_pago': resultget.get('FchVtoPago'),
|
| 456 |
+
'moneda_id': resultget.get('MonId'),
|
| 457 |
+
'moneda_ctz': resultget.get('MonCotiz'),
|
| 458 |
+
'cbtes_asoc': [
|
| 459 |
+
{
|
| 460 |
+
'tipo': cbte_asoc['CbteAsoc']['Tipo'],
|
| 461 |
+
'pto_vta': cbte_asoc['CbteAsoc']['PtoVta'],
|
| 462 |
+
'nro': cbte_asoc['CbteAsoc']['Nro'],
|
| 463 |
+
'cuit': cbte_asoc['CbteAsoc'].get('Cuit'),
|
| 464 |
+
'fecha': cbte_asoc['CbteAsoc'].get('CbteFch'),
|
| 465 |
+
}
|
| 466 |
+
for cbte_asoc in resultget.get('CbtesAsoc', [])],
|
| 467 |
+
'tributos': [
|
| 468 |
+
{
|
| 469 |
+
'tributo_id': tributo['Tributo']['Id'],
|
| 470 |
+
'desc': tributo['Tributo']['Desc'],
|
| 471 |
+
'base_imp': tributo['Tributo'].get('BaseImp'),
|
| 472 |
+
'alic': tributo['Tributo'].get('Alic'),
|
| 473 |
+
'importe': tributo['Tributo']['Importe'],
|
| 474 |
+
}
|
| 475 |
+
for tributo in resultget.get('Tributos', [])],
|
| 476 |
+
'iva': [
|
| 477 |
+
{
|
| 478 |
+
'iva_id': iva['AlicIva']['Id'],
|
| 479 |
+
'base_imp': iva['AlicIva']['BaseImp'],
|
| 480 |
+
'importe': iva['AlicIva']['Importe'],
|
| 481 |
+
}
|
| 482 |
+
for iva in resultget.get('Iva', [])],
|
| 483 |
+
'opcionales': [
|
| 484 |
+
{
|
| 485 |
+
'opcional_id': obs['Opcional']['Id'],
|
| 486 |
+
'valor': obs['Opcional']['Valor'],
|
| 487 |
+
}
|
| 488 |
+
for obs in resultget.get('Opcionales', [])],
|
| 489 |
+
'compradores': [
|
| 490 |
+
{
|
| 491 |
+
'doc_tipo': comp['Comprador']['DocTipo'],
|
| 492 |
+
'doc_nro': comp['Comprador']['DocNro'],
|
| 493 |
+
'porcentaje': comp['Comprador']['Porcentaje'],
|
| 494 |
+
}
|
| 495 |
+
for comp in resultget.get('Compradores', [])],
|
| 496 |
+
'cae': resultget.get('CodAutorizacion'),
|
| 497 |
+
'resultado': resultget.get('Resultado'),
|
| 498 |
+
'fch_venc_cae': resultget.get('FchVto'),
|
| 499 |
+
'obs': [
|
| 500 |
+
{
|
| 501 |
+
'code': obs['Obs']['Code'],
|
| 502 |
+
'msg': obs['Obs']['Msg'],
|
| 503 |
+
}
|
| 504 |
+
for obs in resultget.get('Observaciones', [])],
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
self.FechaCbte = resultget['CbteFch'] # .strftime("%Y/%m/%d")
|
| 508 |
+
self.CbteNro = resultget['CbteHasta'] # 1L
|
| 509 |
+
self.PuntoVenta = resultget['PtoVta'] # 4000
|
| 510 |
+
self.Vencimiento = resultget['FchVto'] # .strftime("%Y/%m/%d")
|
| 511 |
+
self.ImpTotal = str(resultget['ImpTotal'])
|
| 512 |
+
cod_aut = resultget['CodAutorizacion'] and str(resultget['CodAutorizacion']) or '' # 60423794871430L
|
| 513 |
+
self.Resultado = resultget['Resultado']
|
| 514 |
+
self.CbtDesde = resultget['CbteDesde']
|
| 515 |
+
self.CbtHasta = resultget['CbteHasta']
|
| 516 |
+
self.ImpTotal = resultget['ImpTotal']
|
| 517 |
+
self.ImpNeto = resultget['ImpNeto']
|
| 518 |
+
self.ImptoLiq = self.ImpIVA = resultget['ImpIVA']
|
| 519 |
+
self.ImpOpEx = resultget['ImpOpEx']
|
| 520 |
+
self.ImpTrib = resultget['ImpTrib']
|
| 521 |
+
self.EmisionTipo = resultget['EmisionTipo']
|
| 522 |
+
if self.EmisionTipo == 'CAE':
|
| 523 |
+
self.CAE = cod_aut
|
| 524 |
+
elif self.EmisionTipo == 'CAEA':
|
| 525 |
+
self.CAEA = cod_aut
|
| 526 |
+
# Obs:
|
| 527 |
+
for obs in resultget.get('Observaciones', []):
|
| 528 |
+
self.Observaciones.append("%(Code)s: %(Msg)s" % (obs['Obs']))
|
| 529 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 530 |
+
|
| 531 |
+
self.__analizar_errores(result)
|
| 532 |
+
if not difs:
|
| 533 |
+
return self.CAE or self.CAEA
|
| 534 |
+
else:
|
| 535 |
+
return ''
|
| 536 |
+
|
| 537 |
+
@inicializar_y_capturar_excepciones
|
| 538 |
+
def CAESolicitarX(self):
|
| 539 |
+
"Autorizar mοΏ½ltiples facturas (CAE) en una οΏ½nica solicitud"
|
| 540 |
+
# Ver CompTotXRequest -> cantidad maxima comprobantes (250)
|
| 541 |
+
# verificar que hay multiples facturas:
|
| 542 |
+
if not self.facturas:
|
| 543 |
+
raise RuntimeError("Llamar a IniciarFacturasX y AgregarFacturaX!")
|
| 544 |
+
# verificar que todas las facturas
|
| 545 |
+
puntos_vta = set([f['punto_vta'] for f in self.facturas])
|
| 546 |
+
tipos_cbte = set([f['tipo_cbte'] for f in self.facturas])
|
| 547 |
+
if len(puntos_vta) > 1:
|
| 548 |
+
raise RuntimeError("Los comprobantes deben ser del mismo pto_vta!")
|
| 549 |
+
if len(tipos_cbte) > 1:
|
| 550 |
+
raise RuntimeError("Los comprobantes deben tener el mismo tipo!")
|
| 551 |
+
# llamar al webservice:
|
| 552 |
+
ret = self.client.FECAESolicitar(
|
| 553 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 554 |
+
FeCAEReq={
|
| 555 |
+
'FeCabReq': {'CantReg': len(self.facturas),
|
| 556 |
+
'PtoVta': puntos_vta.pop(),
|
| 557 |
+
'CbteTipo': tipos_cbte.pop()},
|
| 558 |
+
'FeDetReq': [{'FECAEDetRequest': {
|
| 559 |
+
'Concepto': f['concepto'],
|
| 560 |
+
'DocTipo': f['tipo_doc'],
|
| 561 |
+
'DocNro': f['nro_doc'],
|
| 562 |
+
'CbteDesde': f['cbt_desde'],
|
| 563 |
+
'CbteHasta': f['cbt_hasta'],
|
| 564 |
+
'CbteFch': f['fecha_cbte'],
|
| 565 |
+
'ImpTotal': f['imp_total'],
|
| 566 |
+
'ImpTotConc': f['imp_tot_conc'],
|
| 567 |
+
'ImpNeto': f['imp_neto'],
|
| 568 |
+
'ImpOpEx': f['imp_op_ex'],
|
| 569 |
+
'ImpTrib': f['imp_trib'],
|
| 570 |
+
'ImpIVA': f['imp_iva'],
|
| 571 |
+
# Fechas solo se informan si Concepto in (2,3)
|
| 572 |
+
'FchServDesde': f.get('fecha_serv_desde'),
|
| 573 |
+
'FchServHasta': f.get('fecha_serv_hasta'),
|
| 574 |
+
'FchVtoPago': f.get('fecha_venc_pago'),
|
| 575 |
+
'MonId': f['moneda_id'],
|
| 576 |
+
'MonCotiz': f['moneda_ctz'],
|
| 577 |
+
'CbtesAsoc': [
|
| 578 |
+
{'CbteAsoc': {
|
| 579 |
+
'Tipo': cbte_asoc['tipo'],
|
| 580 |
+
'PtoVta': cbte_asoc['pto_vta'],
|
| 581 |
+
'Nro': cbte_asoc['nro'],
|
| 582 |
+
'Cuit': cbte_asoc.get('cuit'),
|
| 583 |
+
'CbteFch': cbte_asoc.get('fecha'),
|
| 584 |
+
}}
|
| 585 |
+
for cbte_asoc in f['cbtes_asoc']] or None,
|
| 586 |
+
'Tributos': [
|
| 587 |
+
{'Tributo': {
|
| 588 |
+
'Id': tributo['tributo_id'],
|
| 589 |
+
'Desc': tributo['desc'],
|
| 590 |
+
'BaseImp': tributo['base_imp'],
|
| 591 |
+
'Alic': tributo['alic'],
|
| 592 |
+
'Importe': tributo['importe'],
|
| 593 |
+
}}
|
| 594 |
+
for tributo in f['tributos']] or None,
|
| 595 |
+
'Iva': [
|
| 596 |
+
{'AlicIva': {
|
| 597 |
+
'Id': iva['iva_id'],
|
| 598 |
+
'BaseImp': iva['base_imp'],
|
| 599 |
+
'Importe': iva['importe'],
|
| 600 |
+
}}
|
| 601 |
+
for iva in f['iva']] or None,
|
| 602 |
+
'Opcionales': [
|
| 603 |
+
{'Opcional': {
|
| 604 |
+
'Id': opcional['opcional_id'],
|
| 605 |
+
'Valor': opcional['valor'],
|
| 606 |
+
}} for opcional in f['opcionales']] or None,
|
| 607 |
+
}
|
| 608 |
+
} for f in self.facturas]
|
| 609 |
+
})
|
| 610 |
+
|
| 611 |
+
result = ret['FECAESolicitarResult']
|
| 612 |
+
if 'FeCabResp' in result:
|
| 613 |
+
fecabresp = result['FeCabResp']
|
| 614 |
+
for i, fedetresp in enumerate(result['FeDetResp']):
|
| 615 |
+
fedetresp = fedetresp['FECAEDetResponse']
|
| 616 |
+
f = self.facturas[i]
|
| 617 |
+
# actualizar los campos devueltos por AFIP para cada comp.
|
| 618 |
+
f["resultado"] = fedetresp['Resultado']
|
| 619 |
+
f["cae"] = fedetresp['CAE'] and str(fedetresp['CAE']) or ""
|
| 620 |
+
f["emision_tipo"] = 'CAE'
|
| 621 |
+
f["fch_venc_cae"] = fedetresp['CAEFchVto']
|
| 622 |
+
f["obs"] = [
|
| 623 |
+
{'code': obs['Obs']['Code'], 'msg': obs['Obs']['Msg']}
|
| 624 |
+
for obs in fedetresp.get('Observaciones', [])]
|
| 625 |
+
# sanity checks:
|
| 626 |
+
assert str(f["fecha_cbte"]) == str(fedetresp['CbteFch'])
|
| 627 |
+
assert str(f["cbt_desde"]) == str(fedetresp['CbteDesde'])
|
| 628 |
+
assert str(f["cbt_hasta"]) == str(fedetresp['CbteHasta'])
|
| 629 |
+
assert str(f["punto_vta"]) == str(fecabresp['PtoVta'])
|
| 630 |
+
assert str(f["tipo_cbte"]) == str(fecabresp['CbteTipo'])
|
| 631 |
+
assert str(f["tipo_doc"]) == str(fedetresp['DocTipo'])
|
| 632 |
+
assert str(f["nro_doc"]) == str(fedetresp['DocNro'])
|
| 633 |
+
assert str(f["concepto"]) == str(fedetresp['Concepto'])
|
| 634 |
+
|
| 635 |
+
self.__analizar_errores(result)
|
| 636 |
+
assert fecabresp['CantReg'] == len(self.facturas)
|
| 637 |
+
return fecabresp['CantReg']
|
| 638 |
+
|
| 639 |
+
# metodos auxiliares para soporte de multiples comprobantes por solicitud:
|
| 640 |
+
|
| 641 |
+
def IniciarFacturasX(self):
|
| 642 |
+
"Inicializa lista de facturas para Solicitar multiples CAE"
|
| 643 |
+
self.facturas = []
|
| 644 |
+
return True
|
| 645 |
+
|
| 646 |
+
def AgregarFacturaX(self):
|
| 647 |
+
"Agrega una factura a la lista para Solicitar multiples CAE"
|
| 648 |
+
self.facturas.append(self.factura)
|
| 649 |
+
return True
|
| 650 |
+
|
| 651 |
+
def LeerFacturaX(self, i):
|
| 652 |
+
"Activa internamente una factura para usar ObtenerCampoFactura"
|
| 653 |
+
try:
|
| 654 |
+
# obtengo la factura segun el indice en la lista:
|
| 655 |
+
f = self.factura = self.facturas[i]
|
| 656 |
+
# completar propiedades para retro-compatibilidad:
|
| 657 |
+
self.FechaCbte = f['fecha_cbte']
|
| 658 |
+
self.PuntoVenta = f['punto_vta']
|
| 659 |
+
self.Vencimiento = f['fch_venc_cae']
|
| 660 |
+
self.Resultado = f['resultado']
|
| 661 |
+
self.CbtDesde = f['cbt_desde']
|
| 662 |
+
self.CbtHasta = f['cbt_hasta']
|
| 663 |
+
self.ImpTotal = str(f['imp_total'])
|
| 664 |
+
self.ImpNeto = str(f.get('imp_neto'))
|
| 665 |
+
self.ImptoLiq = self.ImpIVA = str(f.get('imp_iva'))
|
| 666 |
+
self.ImpOpEx = str(f.get('imp_op_ex'))
|
| 667 |
+
self.ImpTrib = str(f.get('imp_trib'))
|
| 668 |
+
self.EmisionTipo = f['emision_tipo']
|
| 669 |
+
if self.EmisionTipo == 'CAE':
|
| 670 |
+
self.CAE = f['cae']
|
| 671 |
+
elif self.EmisionTipo == 'CAEA':
|
| 672 |
+
self.CAEA = f['caea']
|
| 673 |
+
# Obs:
|
| 674 |
+
self.Observaciones = []
|
| 675 |
+
for obs in f.get('obs', []):
|
| 676 |
+
self.Observaciones.append("%(code)s: %(msg)s" % (obs))
|
| 677 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 678 |
+
return True
|
| 679 |
+
except BaseException:
|
| 680 |
+
return False
|
| 681 |
+
|
| 682 |
+
# metodos para CAEA:
|
| 683 |
+
|
| 684 |
+
@inicializar_y_capturar_excepciones
|
| 685 |
+
def CAEASolicitar(self, periodo, orden):
|
| 686 |
+
ret = self.client.FECAEASolicitar(
|
| 687 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 688 |
+
Periodo=periodo,
|
| 689 |
+
Orden=orden,
|
| 690 |
+
)
|
| 691 |
+
|
| 692 |
+
result = ret['FECAEASolicitarResult']
|
| 693 |
+
self.__analizar_errores(result)
|
| 694 |
+
|
| 695 |
+
if 'ResultGet' in result:
|
| 696 |
+
result = result['ResultGet']
|
| 697 |
+
if 'CAEA' in result:
|
| 698 |
+
self.CAEA = result['CAEA']
|
| 699 |
+
self.Periodo = result['Periodo']
|
| 700 |
+
self.Orden = result['Orden']
|
| 701 |
+
self.FchVigDesde = result['FchVigDesde']
|
| 702 |
+
self.FchVigHasta = result['FchVigHasta']
|
| 703 |
+
self.FchTopeInf = result['FchTopeInf']
|
| 704 |
+
self.FchProceso = result['FchProceso']
|
| 705 |
+
# Obs (COMPGv28):
|
| 706 |
+
for obs in result.get('Observaciones', []):
|
| 707 |
+
self.Observaciones.append("%(Code)s: %(Msg)s" % (obs['Obs']))
|
| 708 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 709 |
+
|
| 710 |
+
return self.CAEA and str(self.CAEA) or ''
|
| 711 |
+
|
| 712 |
+
@inicializar_y_capturar_excepciones
|
| 713 |
+
def CAEAConsultar(self, periodo, orden):
|
| 714 |
+
"MοΏ½todo de consulta de CAEA"
|
| 715 |
+
ret = self.client.FECAEAConsultar(
|
| 716 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 717 |
+
Periodo=periodo,
|
| 718 |
+
Orden=orden,
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
result = ret['FECAEAConsultarResult']
|
| 722 |
+
self.__analizar_errores(result)
|
| 723 |
+
|
| 724 |
+
if 'ResultGet' in result:
|
| 725 |
+
result = result['ResultGet']
|
| 726 |
+
if 'CAEA' in result:
|
| 727 |
+
self.CAEA = result['CAEA']
|
| 728 |
+
self.Periodo = result['Periodo']
|
| 729 |
+
self.Orden = result['Orden']
|
| 730 |
+
self.FchVigDesde = result['FchVigDesde']
|
| 731 |
+
self.FchVigHasta = result['FchVigHasta']
|
| 732 |
+
self.FchTopeInf = result['FchTopeInf']
|
| 733 |
+
self.FchProceso = result['FchProceso']
|
| 734 |
+
|
| 735 |
+
return self.CAEA and str(self.CAEA) or ''
|
| 736 |
+
|
| 737 |
+
@inicializar_y_capturar_excepciones
|
| 738 |
+
def CAEARegInformativo(self):
|
| 739 |
+
"MοΏ½todo para informar comprobantes emitidos con CAEA"
|
| 740 |
+
f = self.factura
|
| 741 |
+
ret = self.client.FECAEARegInformativo(
|
| 742 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 743 |
+
FeCAEARegInfReq={
|
| 744 |
+
'FeCabReq': {'CantReg': 1,
|
| 745 |
+
'PtoVta': f['punto_vta'],
|
| 746 |
+
'CbteTipo': f['tipo_cbte']},
|
| 747 |
+
'FeDetReq': [{'FECAEADetRequest': {
|
| 748 |
+
'Concepto': f['concepto'],
|
| 749 |
+
'DocTipo': f['tipo_doc'],
|
| 750 |
+
'DocNro': f['nro_doc'],
|
| 751 |
+
'CbteDesde': f['cbt_desde'],
|
| 752 |
+
'CbteHasta': f['cbt_hasta'],
|
| 753 |
+
'CbteFch': f['fecha_cbte'],
|
| 754 |
+
'ImpTotal': f['imp_total'],
|
| 755 |
+
'ImpTotConc': f['imp_tot_conc'],
|
| 756 |
+
'ImpNeto': f['imp_neto'],
|
| 757 |
+
'ImpOpEx': f['imp_op_ex'],
|
| 758 |
+
'ImpTrib': f['imp_trib'],
|
| 759 |
+
'ImpIVA': f['imp_iva'],
|
| 760 |
+
# Fechas solo se informan si Concepto in (2,3)
|
| 761 |
+
'FchServDesde': f.get('fecha_serv_desde'),
|
| 762 |
+
'FchServHasta': f.get('fecha_serv_hasta'),
|
| 763 |
+
'FchVtoPago': f.get('fecha_venc_pago'),
|
| 764 |
+
'MonId': f['moneda_id'],
|
| 765 |
+
'MonCotiz': f['moneda_ctz'],
|
| 766 |
+
'CbtesAsoc': [
|
| 767 |
+
{'CbteAsoc': {
|
| 768 |
+
'Tipo': cbte_asoc['tipo'],
|
| 769 |
+
'PtoVta': cbte_asoc['pto_vta'],
|
| 770 |
+
'Nro': cbte_asoc['nro'],
|
| 771 |
+
'Cuit': cbte_asoc.get('cuit'),
|
| 772 |
+
'CbteFch': cbte_asoc.get('fecha'),
|
| 773 |
+
}}
|
| 774 |
+
for cbte_asoc in f['cbtes_asoc']]
|
| 775 |
+
if f['cbtes_asoc'] else None,
|
| 776 |
+
'Tributos': [
|
| 777 |
+
{'Tributo': {
|
| 778 |
+
'Id': tributo['tributo_id'],
|
| 779 |
+
'Desc': tributo['desc'],
|
| 780 |
+
'BaseImp': tributo['base_imp'],
|
| 781 |
+
'Alic': tributo['alic'],
|
| 782 |
+
'Importe': tributo['importe'],
|
| 783 |
+
}}
|
| 784 |
+
for tributo in f['tributos']]
|
| 785 |
+
if f['tributos'] else None,
|
| 786 |
+
'Iva': [
|
| 787 |
+
{'AlicIva': {
|
| 788 |
+
'Id': iva['iva_id'],
|
| 789 |
+
'BaseImp': iva['base_imp'],
|
| 790 |
+
'Importe': iva['importe'],
|
| 791 |
+
}}
|
| 792 |
+
for iva in f['iva']]
|
| 793 |
+
if f['iva'] else None,
|
| 794 |
+
'Opcionales': [
|
| 795 |
+
{'Opcional': {
|
| 796 |
+
'Id': opcional['opcional_id'],
|
| 797 |
+
'Valor': opcional['valor'],
|
| 798 |
+
}} for opcional in f['opcionales']] or None,
|
| 799 |
+
'CAEA': f['caea'],
|
| 800 |
+
'CbteFchHsGen': f.get('fecha_hs_gen'),
|
| 801 |
+
}
|
| 802 |
+
}]
|
| 803 |
+
})
|
| 804 |
+
|
| 805 |
+
result = ret['FECAEARegInformativoResult']
|
| 806 |
+
if 'FeCabResp' in result:
|
| 807 |
+
fecabresp = result['FeCabResp']
|
| 808 |
+
fedetresp = result['FeDetResp'][0]['FECAEADetResponse']
|
| 809 |
+
|
| 810 |
+
# Reprocesar en caso de error (recuperar CAE emitido anteriormente)
|
| 811 |
+
if self.Reprocesar and 'Errors' in result:
|
| 812 |
+
for error in result['Errors']:
|
| 813 |
+
err_code = str(error['Err']['Code'])
|
| 814 |
+
if fedetresp['Resultado'] == 'R' and err_code == '703':
|
| 815 |
+
caea = self.CompConsultar(f['tipo_cbte'], f['punto_vta'], f['cbt_desde'], reproceso=True)
|
| 816 |
+
if caea and self.EmisionTipo == 'CAE':
|
| 817 |
+
self.Reproceso = 'S'
|
| 818 |
+
return caea
|
| 819 |
+
self.Reproceso = 'N'
|
| 820 |
+
|
| 821 |
+
self.Resultado = fecabresp['Resultado']
|
| 822 |
+
# Obs:
|
| 823 |
+
for obs in fedetresp.get('Observaciones', []):
|
| 824 |
+
self.Observaciones.append("%(Code)s: %(Msg)s" % (obs['Obs']))
|
| 825 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 826 |
+
self.CAEA = fedetresp['CAEA'] and str(fedetresp['CAEA']) or ""
|
| 827 |
+
self.EmisionTipo = 'CAEA'
|
| 828 |
+
self.FechaCbte = fedetresp['CbteFch'] # .strftime("%Y/%m/%d")
|
| 829 |
+
self.CbteNro = fedetresp['CbteHasta'] # 1L
|
| 830 |
+
self.PuntoVenta = fecabresp['PtoVta'] # 4000
|
| 831 |
+
self.CbtDesde = fedetresp['CbteDesde']
|
| 832 |
+
self.CbtHasta = fedetresp['CbteHasta']
|
| 833 |
+
self.__analizar_errores(result)
|
| 834 |
+
return self.CAEA
|
| 835 |
+
|
| 836 |
+
@inicializar_y_capturar_excepciones
|
| 837 |
+
def CAEASinMovimientoInformar(self, punto_vta, caea):
|
| 838 |
+
"MοΏ½todo para informar CAEA sin movimiento"
|
| 839 |
+
ret = self.client.FECAEASinMovimientoInformar(
|
| 840 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 841 |
+
PtoVta=punto_vta,
|
| 842 |
+
CAEA=caea,
|
| 843 |
+
)
|
| 844 |
+
|
| 845 |
+
result = ret['FECAEASinMovimientoInformarResult']
|
| 846 |
+
self.__analizar_errores(result)
|
| 847 |
+
|
| 848 |
+
if 'CAEA' in result:
|
| 849 |
+
self.CAEA = result['CAEA']
|
| 850 |
+
if 'FchProceso' in result:
|
| 851 |
+
self.FchProceso = result['FchProceso']
|
| 852 |
+
if 'Resultado' in result:
|
| 853 |
+
self.Resultado = result['Resultado']
|
| 854 |
+
self.PuntoVenta = result['PtoVta'] # 4000
|
| 855 |
+
|
| 856 |
+
return self.Resultado or ''
|
| 857 |
+
|
| 858 |
+
@inicializar_y_capturar_excepciones
|
| 859 |
+
def ParamGetTiposCbte(self, sep="|"):
|
| 860 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de Comprobantes"
|
| 861 |
+
ret = self.client.FEParamGetTiposCbte(
|
| 862 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 863 |
+
)
|
| 864 |
+
res = ret['FEParamGetTiposCbteResult']
|
| 865 |
+
return [("%(Id)s\t%(Desc)s\t%(FchDesde)s\t%(FchHasta)s" % p['CbteTipo']).replace("\t", sep)
|
| 866 |
+
for p in res['ResultGet']]
|
| 867 |
+
|
| 868 |
+
@inicializar_y_capturar_excepciones
|
| 869 |
+
def ParamGetTiposConcepto(self, sep="|"):
|
| 870 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de Conceptos"
|
| 871 |
+
ret = self.client.FEParamGetTiposConcepto(
|
| 872 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 873 |
+
)
|
| 874 |
+
res = ret['FEParamGetTiposConceptoResult']
|
| 875 |
+
return [("%(Id)s\t%(Desc)s\t%(FchDesde)s\t%(FchHasta)s" % p['ConceptoTipo']).replace("\t", sep)
|
| 876 |
+
for p in res['ResultGet']]
|
| 877 |
+
|
| 878 |
+
@inicializar_y_capturar_excepciones
|
| 879 |
+
def ParamGetTiposDoc(self, sep="|"):
|
| 880 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de Documentos"
|
| 881 |
+
ret = self.client.FEParamGetTiposDoc(
|
| 882 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 883 |
+
)
|
| 884 |
+
res = ret['FEParamGetTiposDocResult']
|
| 885 |
+
return [("%(Id)s\t%(Desc)s\t%(FchDesde)s\t%(FchHasta)s" % p['DocTipo']).replace("\t", sep)
|
| 886 |
+
for p in res['ResultGet']]
|
| 887 |
+
|
| 888 |
+
@inicializar_y_capturar_excepciones
|
| 889 |
+
def ParamGetTiposIva(self, sep="|"):
|
| 890 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de AlοΏ½cuotas"
|
| 891 |
+
ret = self.client.FEParamGetTiposIva(
|
| 892 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 893 |
+
)
|
| 894 |
+
res = ret['FEParamGetTiposIvaResult']
|
| 895 |
+
return [("%(Id)s\t%(Desc)s\t%(FchDesde)s\t%(FchHasta)s" % p['IvaTipo']).replace("\t", sep)
|
| 896 |
+
for p in res['ResultGet']]
|
| 897 |
+
|
| 898 |
+
@inicializar_y_capturar_excepciones
|
| 899 |
+
def ParamGetTiposMonedas(self, sep="|"):
|
| 900 |
+
"Recuperador de valores referenciales de cοΏ½digos de Monedas"
|
| 901 |
+
ret = self.client.FEParamGetTiposMonedas(
|
| 902 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 903 |
+
)
|
| 904 |
+
res = ret['FEParamGetTiposMonedasResult']
|
| 905 |
+
return [("%(Id)s\t%(Desc)s\t%(FchDesde)s\t%(FchHasta)s" % p['Moneda']).replace("\t", sep)
|
| 906 |
+
for p in res['ResultGet']]
|
| 907 |
+
|
| 908 |
+
@inicializar_y_capturar_excepciones
|
| 909 |
+
def ParamGetTiposOpcional(self, sep="|"):
|
| 910 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de datos opcionales"
|
| 911 |
+
ret = self.client.FEParamGetTiposOpcional(
|
| 912 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 913 |
+
)
|
| 914 |
+
res = ret['FEParamGetTiposOpcionalResult']
|
| 915 |
+
return [("%(Id)s\t%(Desc)s\t%(FchDesde)s\t%(FchHasta)s" % p['OpcionalTipo']).replace("\t", sep)
|
| 916 |
+
for p in res.get('ResultGet', [])]
|
| 917 |
+
|
| 918 |
+
@inicializar_y_capturar_excepciones
|
| 919 |
+
def ParamGetTiposTributos(self, sep="|"):
|
| 920 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipos de Tributos"
|
| 921 |
+
"Este mοΏ½todo permite consultar los tipos de tributos habilitados en este WS"
|
| 922 |
+
ret = self.client.FEParamGetTiposTributos(
|
| 923 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 924 |
+
)
|
| 925 |
+
res = ret['FEParamGetTiposTributosResult']
|
| 926 |
+
return [("%(Id)s\t%(Desc)s\t%(FchDesde)s\t%(FchHasta)s" % p['TributoTipo']).replace("\t", sep)
|
| 927 |
+
for p in res['ResultGet']]
|
| 928 |
+
|
| 929 |
+
@inicializar_y_capturar_excepciones
|
| 930 |
+
def ParamGetTiposPaises(self, sep="|"):
|
| 931 |
+
"Recuperador de valores referenciales de cοΏ½digos de Paises"
|
| 932 |
+
"Este mοΏ½todo permite consultar los tipos de tributos habilitados en este WS"
|
| 933 |
+
ret = self.client.FEParamGetTiposPaises(
|
| 934 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 935 |
+
)
|
| 936 |
+
res = ret['FEParamGetTiposPaisesResult']
|
| 937 |
+
return [("%(Id)s\t%(Desc)s" % p['PaisTipo']).replace("\t", sep)
|
| 938 |
+
for p in res['ResultGet']]
|
| 939 |
+
|
| 940 |
+
@inicializar_y_capturar_excepciones
|
| 941 |
+
def ParamGetCotizacion(self, moneda_id):
|
| 942 |
+
"Recuperador de cotizaciοΏ½n de moneda"
|
| 943 |
+
ret = self.client.FEParamGetCotizacion(
|
| 944 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 945 |
+
MonId=moneda_id,
|
| 946 |
+
)
|
| 947 |
+
self.__analizar_errores(ret)
|
| 948 |
+
res = ret['FEParamGetCotizacionResult']['ResultGet']
|
| 949 |
+
self.FchCotiz = res.get("FchCotiz")
|
| 950 |
+
return str(res.get('MonCotiz', ""))
|
| 951 |
+
|
| 952 |
+
@inicializar_y_capturar_excepciones
|
| 953 |
+
def ParamGetPtosVenta(self, sep="|"):
|
| 954 |
+
"Recuperador de valores referenciales Puntos de Venta registrados"
|
| 955 |
+
ret = self.client.FEParamGetPtosVenta(
|
| 956 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 957 |
+
)
|
| 958 |
+
res = ret.get('FEParamGetPtosVentaResult', {})
|
| 959 |
+
return [("%(Nro)s\tEmisionTipo:%(EmisionTipo)s\tBloqueado:%(Bloqueado)s\tFchBaja:%(FchBaja)s" % p['PtoVenta']).replace("\t", sep)
|
| 960 |
+
for p in res.get('ResultGet', [])]
|
| 961 |
+
|
| 962 |
+
|
| 963 |
+
def p_assert_eq(a, b):
|
| 964 |
+
print(a, a == b and '==' or '!=', b)
|
| 965 |
+
|
| 966 |
+
|
| 967 |
+
def main():
|
| 968 |
+
"FunciοΏ½n principal de pruebas (obtener CAE)"
|
| 969 |
+
import os
|
| 970 |
+
import time
|
| 971 |
+
|
| 972 |
+
DEBUG = '--debug' in sys.argv
|
| 973 |
+
|
| 974 |
+
if DEBUG:
|
| 975 |
+
from pysimplesoap.client import __version__ as soapver
|
| 976 |
+
print("pysimplesoap.__version__ = ", soapver)
|
| 977 |
+
|
| 978 |
+
wsfev1 = WSFEv1()
|
| 979 |
+
wsfev1.LanzarExcepciones = True
|
| 980 |
+
|
| 981 |
+
cache = None
|
| 982 |
+
if "--prod" in sys.argv:
|
| 983 |
+
wsdl = "https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL"
|
| 984 |
+
else:
|
| 985 |
+
wsdl = WSDL
|
| 986 |
+
proxy = ""
|
| 987 |
+
wrapper = "" # "pycurl"
|
| 988 |
+
cacert = "conf/afip_ca_info.crt"
|
| 989 |
+
|
| 990 |
+
ok = wsfev1.Conectar(cache, wsdl, proxy, wrapper, cacert)
|
| 991 |
+
|
| 992 |
+
if not ok:
|
| 993 |
+
raise RuntimeError(wsfev1.Excepcion)
|
| 994 |
+
|
| 995 |
+
if DEBUG:
|
| 996 |
+
print("LOG: ", wsfev1.DebugLog())
|
| 997 |
+
|
| 998 |
+
if "--dummy" in sys.argv:
|
| 999 |
+
print(wsfev1.client.help("FEDummy"))
|
| 1000 |
+
wsfev1.Dummy()
|
| 1001 |
+
print("AppServerStatus", wsfev1.AppServerStatus)
|
| 1002 |
+
print("DbServerStatus", wsfev1.DbServerStatus)
|
| 1003 |
+
print("AuthServerStatus", wsfev1.AuthServerStatus)
|
| 1004 |
+
sys.exit(0)
|
| 1005 |
+
|
| 1006 |
+
# obteniendo el TA para pruebas
|
| 1007 |
+
from .wsaa import WSAA
|
| 1008 |
+
ta = WSAA().Autenticar("wsfe", "reingart.crt", "reingart.key", debug=True)
|
| 1009 |
+
wsfev1.SetTicketAcceso(ta)
|
| 1010 |
+
wsfev1.Cuit = "20267565393"
|
| 1011 |
+
|
| 1012 |
+
if "--prueba" in sys.argv:
|
| 1013 |
+
print(wsfev1.client.help("FECAESolicitar").encode("latin1"))
|
| 1014 |
+
|
| 1015 |
+
if '--usados' in sys.argv:
|
| 1016 |
+
tipo_cbte = 49
|
| 1017 |
+
concepto = 1
|
| 1018 |
+
elif '--fce' in sys.argv:
|
| 1019 |
+
tipo_cbte = 203
|
| 1020 |
+
concepto = 1
|
| 1021 |
+
else:
|
| 1022 |
+
tipo_cbte = 3
|
| 1023 |
+
concepto = 3 if ('--rg4109' not in sys.argv) else 1
|
| 1024 |
+
punto_vta = 4001
|
| 1025 |
+
cbte_nro = int(wsfev1.CompUltimoAutorizado(tipo_cbte, punto_vta) or 0)
|
| 1026 |
+
fecha = datetime.datetime.now().strftime("%Y%m%d")
|
| 1027 |
+
tipo_doc = 80 if '--usados' not in sys.argv else 30
|
| 1028 |
+
nro_doc = "30500010912"
|
| 1029 |
+
cbt_desde = cbte_nro + 1
|
| 1030 |
+
cbt_hasta = cbte_nro + 1
|
| 1031 |
+
imp_total = "222.00"
|
| 1032 |
+
imp_tot_conc = "0.00"
|
| 1033 |
+
imp_neto = "200.00"
|
| 1034 |
+
imp_iva = "21.00"
|
| 1035 |
+
imp_trib = "1.00"
|
| 1036 |
+
imp_op_ex = "0.00"
|
| 1037 |
+
fecha_cbte = fecha
|
| 1038 |
+
fecha_venc_pago = fecha_serv_desde = fecha_serv_hasta = None
|
| 1039 |
+
# Fechas del perΓodo del servicio facturado y vencimiento de pago:
|
| 1040 |
+
if concepto > 1:
|
| 1041 |
+
fecha_venc_pago = fecha
|
| 1042 |
+
fecha_serv_desde = fecha; fecha_serv_hasta = fecha
|
| 1043 |
+
elif '--fce' in sys.argv:
|
| 1044 |
+
# obligatorio en Factura de CrΓ©dito ElectrΓ³nica MiPyMEs (FCE):
|
| 1045 |
+
fecha_venc_pago = fecha
|
| 1046 |
+
moneda_id = 'PES'; moneda_ctz = '1.000'
|
| 1047 |
+
|
| 1048 |
+
# inicializar prueba de multiples comprobantes por solicitud
|
| 1049 |
+
if "--multiple" in sys.argv:
|
| 1050 |
+
wsfev1.IniciarFacturasX()
|
| 1051 |
+
reg_x_req = wsfev1.CompTotXRequest() # cant max. comprobantes
|
| 1052 |
+
else:
|
| 1053 |
+
reg_x_req = 1 # un solo comprobante
|
| 1054 |
+
|
| 1055 |
+
for i in range(reg_x_req):
|
| 1056 |
+
|
| 1057 |
+
wsfev1.CrearFactura(concepto, tipo_doc, nro_doc,
|
| 1058 |
+
tipo_cbte, punto_vta, cbt_desde + i, cbt_hasta + i,
|
| 1059 |
+
imp_total, imp_tot_conc, imp_neto,
|
| 1060 |
+
imp_iva, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago,
|
| 1061 |
+
fecha_serv_desde, fecha_serv_hasta, # --
|
| 1062 |
+
moneda_id, moneda_ctz)
|
| 1063 |
+
|
| 1064 |
+
if '--caea' in sys.argv:
|
| 1065 |
+
periodo = datetime.datetime.today().strftime("%Y%M")
|
| 1066 |
+
orden = 1 if datetime.datetime.today().day < 15 else 2
|
| 1067 |
+
caea = wsfev1.CAEAConsultar(periodo, orden)
|
| 1068 |
+
wsfev1.EstablecerCampoFactura("caea", caea)
|
| 1069 |
+
wsfev1.EstablecerCampoFactura("fecha_hs_gen", "yyyymmddhhmiss")
|
| 1070 |
+
|
| 1071 |
+
# comprobantes asociados (notas de crΓ©dito / dΓ©bito)
|
| 1072 |
+
if tipo_cbte in (2, 3, 7, 8, 12, 13, 203, 208, 213):
|
| 1073 |
+
tipo = 201 if tipo_cbte in (203, 208, 213) else 3
|
| 1074 |
+
pto_vta = 4001
|
| 1075 |
+
nro = 1
|
| 1076 |
+
cuit = "20267565393"
|
| 1077 |
+
# obligatorio en Factura de CrΓ©dito ElectrΓ³nica MiPyMEs (FCE):
|
| 1078 |
+
fecha_cbte = fecha if tipo_cbte in (203, 208, 213) else None
|
| 1079 |
+
wsfev1.AgregarCmpAsoc(tipo, pto_vta, nro, cuit, fecha_cbte)
|
| 1080 |
+
|
| 1081 |
+
# otros tributos:
|
| 1082 |
+
tributo_id = 99
|
| 1083 |
+
desc = 'Impuesto Municipal Matanza'
|
| 1084 |
+
base_imp = None
|
| 1085 |
+
alic = None
|
| 1086 |
+
importe = 1
|
| 1087 |
+
wsfev1.AgregarTributo(tributo_id, desc, base_imp, alic, importe)
|
| 1088 |
+
|
| 1089 |
+
# subtotales por alicuota de IVA:
|
| 1090 |
+
iva_id = 3 # 0%
|
| 1091 |
+
base_imp = 100
|
| 1092 |
+
importe = 0
|
| 1093 |
+
wsfev1.AgregarIva(iva_id, base_imp, importe)
|
| 1094 |
+
|
| 1095 |
+
# subtotales por alicuota de IVA:
|
| 1096 |
+
iva_id = 5 # 21%
|
| 1097 |
+
base_imp = 100
|
| 1098 |
+
importe = 21
|
| 1099 |
+
wsfev1.AgregarIva(iva_id, base_imp, importe)
|
| 1100 |
+
|
| 1101 |
+
# datos opcionales para proyectos promovidos:
|
| 1102 |
+
if '--proyectos' in sys.argv:
|
| 1103 |
+
wsfev1.AgregarOpcional(2, "1234") # identificador del proyecto
|
| 1104 |
+
# datos opcionales para RG Bienes Usados 3411 (del vendedor):
|
| 1105 |
+
if '--usados' in sys.argv:
|
| 1106 |
+
wsfev1.AgregarOpcional(91, "Juan Perez") # Nombre y Apellido
|
| 1107 |
+
wsfev1.AgregarOpcional(92, "200") # Nacionalidad
|
| 1108 |
+
wsfev1.AgregarOpcional(93, "Balcarce 50") # Domicilio
|
| 1109 |
+
# datos opcionales para RG 3668 Impuesto al Valor Agregado - Art.12:
|
| 1110 |
+
if '--rg3668' in sys.argv:
|
| 1111 |
+
wsfev1.AgregarOpcional(5, "02") # IVA Excepciones
|
| 1112 |
+
wsfev1.AgregarOpcional(61, "80") # Firmante Doc Tipo
|
| 1113 |
+
wsfev1.AgregarOpcional(62, "20267565393") # Firmante Doc Nro
|
| 1114 |
+
wsfev1.AgregarOpcional(7, "01") # CarοΏ½cter del Firmante
|
| 1115 |
+
# datos opcionales para RG 4004-E Alquiler de inmuebles (Ganancias)
|
| 1116 |
+
if '--rg4004' in sys.argv:
|
| 1117 |
+
wsfev1.AgregarOpcional(17, "1") # Intermediario
|
| 1118 |
+
wsfev1.AgregarOpcional(1801, "30500010912") # CUIT Propietario
|
| 1119 |
+
wsfev1.AgregarOpcional(1802, "BNA") # Nombr e Titular
|
| 1120 |
+
# datos de compradores RG 4109-E bienes muebles registrables (%)
|
| 1121 |
+
if '--rg4109' in sys.argv:
|
| 1122 |
+
wsfev1.AgregarComprador(80, "30500010912", 99.99)
|
| 1123 |
+
wsfev1.AgregarComprador(80, "30999032083", 0.01)
|
| 1124 |
+
|
| 1125 |
+
# datos de Factura de CrΓ©dito ElectrΓ³nica MiPyMEs (FCE):
|
| 1126 |
+
if '--fce' in sys.argv:
|
| 1127 |
+
wsfev1.AgregarOpcional(2101, "2850590940090418135201") # CBU
|
| 1128 |
+
wsfev1.AgregarOpcional(2102, "pyafipws") # alias
|
| 1129 |
+
if tipo_cbte in (203, 208, 213):
|
| 1130 |
+
wsfev1.AgregarOpcional(22, "S") # AnulaciΓ³n
|
| 1131 |
+
|
| 1132 |
+
# agregar la factura creada internamente para solicitud mΓΊltiple:
|
| 1133 |
+
if "--multiple" in sys.argv:
|
| 1134 |
+
wsfev1.AgregarFacturaX()
|
| 1135 |
+
|
| 1136 |
+
import time
|
| 1137 |
+
t0 = time.time()
|
| 1138 |
+
if not '--caea' in sys.argv:
|
| 1139 |
+
if not "--multiple" in sys.argv:
|
| 1140 |
+
wsfev1.CAESolicitar()
|
| 1141 |
+
else:
|
| 1142 |
+
cant = wsfev1.CAESolicitarX()
|
| 1143 |
+
print("Cantidad de comprobantes procesados:", cant)
|
| 1144 |
+
else:
|
| 1145 |
+
wsfev1.CAEARegInformativo()
|
| 1146 |
+
t1 = time.time()
|
| 1147 |
+
|
| 1148 |
+
# revisar los resultados:
|
| 1149 |
+
for i in range(reg_x_req):
|
| 1150 |
+
if "--multiple" in sys.argv:
|
| 1151 |
+
print("Analizando respuesta para factura indice: ", i)
|
| 1152 |
+
ok = wsfev1.LeerFacturaX(i)
|
| 1153 |
+
print("Nro. Cbte. desde-hasta", wsfev1.CbtDesde, wsfev1.CbtHasta)
|
| 1154 |
+
print("Resultado", wsfev1.Resultado)
|
| 1155 |
+
print("Reproceso", wsfev1.Reproceso)
|
| 1156 |
+
print("CAE", wsfev1.CAE)
|
| 1157 |
+
print("Vencimiento", wsfev1.Vencimiento)
|
| 1158 |
+
print("Observaciones", wsfev1.Obs)
|
| 1159 |
+
|
| 1160 |
+
if DEBUG:
|
| 1161 |
+
print("t0", t0)
|
| 1162 |
+
print("t1", t1)
|
| 1163 |
+
print("lapso", t1 - t0)
|
| 1164 |
+
open("xmlrequest.xml", "wb").write(wsfev1.XmlRequest)
|
| 1165 |
+
open("xmlresponse.xml", "wb").write(wsfev1.XmlResponse)
|
| 1166 |
+
|
| 1167 |
+
if not "--multiple" in sys.argv:
|
| 1168 |
+
wsfev1.AnalizarXml("XmlResponse")
|
| 1169 |
+
p_assert_eq(wsfev1.ObtenerTagXml('CAE'), str(wsfev1.CAE))
|
| 1170 |
+
p_assert_eq(wsfev1.ObtenerTagXml('Concepto'), '2')
|
| 1171 |
+
p_assert_eq(wsfev1.ObtenerTagXml('Obs', 0, 'Code'), "10017")
|
| 1172 |
+
print(wsfev1.ObtenerTagXml('Obs', 0, 'Msg'))
|
| 1173 |
+
|
| 1174 |
+
if "--reprocesar" in sys.argv:
|
| 1175 |
+
print("reprocesando....")
|
| 1176 |
+
wsfev1.Reproceso = True
|
| 1177 |
+
cae = wsfev1.CAE
|
| 1178 |
+
wsfev1.CAESolicitar()
|
| 1179 |
+
assert cae == wsfev1.CAE
|
| 1180 |
+
assert wsfev1.Reproceso == "S"
|
| 1181 |
+
|
| 1182 |
+
if "--consultar" in sys.argv:
|
| 1183 |
+
cae = wsfev1.CAE
|
| 1184 |
+
cae2 = wsfev1.CompConsultar(tipo_cbte, punto_vta, cbt_desde)
|
| 1185 |
+
p_assert_eq(cae, cae2)
|
| 1186 |
+
# comparar datos del encabezado
|
| 1187 |
+
p_assert_eq(wsfev1.ObtenerCampoFactura('cae'), str(wsfev1.CAE))
|
| 1188 |
+
p_assert_eq(wsfev1.ObtenerCampoFactura('nro_doc'), int(nro_doc))
|
| 1189 |
+
p_assert_eq(wsfev1.ObtenerCampoFactura('imp_total'), float(imp_total))
|
| 1190 |
+
# comparar primer alicuota de IVA
|
| 1191 |
+
p_assert_eq(wsfev1.ObtenerCampoFactura('iva', 0, 'importe'), 21)
|
| 1192 |
+
# comparar primer tributo
|
| 1193 |
+
p_assert_eq(wsfev1.ObtenerCampoFactura('tributos', 0, 'importe'), 1)
|
| 1194 |
+
# comparar primer opcional
|
| 1195 |
+
if '--rg3668' in sys.argv:
|
| 1196 |
+
p_assert_eq(wsfev1.ObtenerCampoFactura('opcionales', 0, 'valor'), "02")
|
| 1197 |
+
# comparar primer observacion de AFIP
|
| 1198 |
+
p_assert_eq(wsfev1.ObtenerCampoFactura('obs', 0, 'code'), 10017)
|
| 1199 |
+
# pruebo la segunda observacion inexistente
|
| 1200 |
+
p_assert_eq(wsfev1.ObtenerCampoFactura('obs', 1, 'code'), None)
|
| 1201 |
+
p_assert_eq(wsfev1.Excepcion, "El campo 1 solicitado no existe")
|
| 1202 |
+
|
| 1203 |
+
if "--get" in sys.argv:
|
| 1204 |
+
tipo_cbte = 2
|
| 1205 |
+
punto_vta = 4001
|
| 1206 |
+
cbte_nro = wsfev1.CompUltimoAutorizado(tipo_cbte, punto_vta)
|
| 1207 |
+
|
| 1208 |
+
wsfev1.CompConsultar(tipo_cbte, punto_vta, cbte_nro)
|
| 1209 |
+
|
| 1210 |
+
print("FechaCbte = ", wsfev1.FechaCbte)
|
| 1211 |
+
print("CbteNro = ", wsfev1.CbteNro)
|
| 1212 |
+
print("PuntoVenta = ", wsfev1.PuntoVenta)
|
| 1213 |
+
print("ImpTotal =", wsfev1.ImpTotal)
|
| 1214 |
+
print("CAE = ", wsfev1.CAE)
|
| 1215 |
+
print("Vencimiento = ", wsfev1.Vencimiento)
|
| 1216 |
+
print("EmisionTipo = ", wsfev1.EmisionTipo)
|
| 1217 |
+
|
| 1218 |
+
wsfev1.AnalizarXml("XmlResponse")
|
| 1219 |
+
p_assert_eq(wsfev1.ObtenerTagXml('CodAutorizacion'), str(wsfev1.CAE))
|
| 1220 |
+
p_assert_eq(wsfev1.ObtenerTagXml('CbteFch'), wsfev1.FechaCbte)
|
| 1221 |
+
p_assert_eq(wsfev1.ObtenerTagXml('MonId'), "PES")
|
| 1222 |
+
p_assert_eq(wsfev1.ObtenerTagXml('MonCotiz'), "1")
|
| 1223 |
+
p_assert_eq(wsfev1.ObtenerTagXml('DocTipo'), "80")
|
| 1224 |
+
p_assert_eq(wsfev1.ObtenerTagXml('DocNro'), "30500010912")
|
| 1225 |
+
|
| 1226 |
+
if "--parametros" in sys.argv:
|
| 1227 |
+
import codecs
|
| 1228 |
+
import locale
|
| 1229 |
+
import traceback
|
| 1230 |
+
if sys.stdout.encoding is None:
|
| 1231 |
+
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout, "replace")
|
| 1232 |
+
sys.stderr = codecs.getwriter(locale.getpreferredencoding())(sys.stderr, "replace")
|
| 1233 |
+
|
| 1234 |
+
print('\n'.join(wsfev1.ParamGetTiposDoc()))
|
| 1235 |
+
print("=== Tipos de Comprobante ===")
|
| 1236 |
+
print('\n'.join(wsfev1.ParamGetTiposCbte()))
|
| 1237 |
+
print("=== Tipos de Concepto ===")
|
| 1238 |
+
print('\n'.join(wsfev1.ParamGetTiposConcepto()))
|
| 1239 |
+
print("=== Tipos de Documento ===")
|
| 1240 |
+
print('\n'.join(wsfev1.ParamGetTiposDoc()))
|
| 1241 |
+
print("=== Alicuotas de IVA ===")
|
| 1242 |
+
print('\n'.join(wsfev1.ParamGetTiposIva()))
|
| 1243 |
+
print("=== Monedas ===")
|
| 1244 |
+
print('\n'.join(wsfev1.ParamGetTiposMonedas()))
|
| 1245 |
+
print("=== Tipos de datos opcionales ===")
|
| 1246 |
+
print('\n'.join(wsfev1.ParamGetTiposOpcional()))
|
| 1247 |
+
print("=== Tipos de Tributo ===")
|
| 1248 |
+
print('\n'.join(wsfev1.ParamGetTiposTributos()))
|
| 1249 |
+
print("=== Tipos de Paises ===")
|
| 1250 |
+
print('\n'.join(wsfev1.ParamGetTiposPaises()))
|
| 1251 |
+
print("=== Puntos de Venta ===")
|
| 1252 |
+
print('\n'.join(wsfev1.ParamGetPtosVenta()))
|
| 1253 |
+
|
| 1254 |
+
if "--cotizacion" in sys.argv:
|
| 1255 |
+
print(wsfev1.ParamGetCotizacion('DOL'))
|
| 1256 |
+
|
| 1257 |
+
if "--comptox" in sys.argv:
|
| 1258 |
+
print(wsfev1.CompTotXRequest())
|
| 1259 |
+
|
| 1260 |
+
if "--ptosventa" in sys.argv:
|
| 1261 |
+
print(wsfev1.ParamGetPtosVenta())
|
| 1262 |
+
|
| 1263 |
+
if "--solicitar-caea" in sys.argv:
|
| 1264 |
+
periodo = sys.argv[sys.argv.index("--solicitar-caea") + 1]
|
| 1265 |
+
orden = sys.argv[sys.argv.index("--solicitar-caea") + 2]
|
| 1266 |
+
|
| 1267 |
+
if DEBUG:
|
| 1268 |
+
print("Solicitando CAEA para periodo %s orden %s" % (periodo, orden))
|
| 1269 |
+
|
| 1270 |
+
caea = wsfev1.CAEASolicitar(periodo, orden)
|
| 1271 |
+
print("CAEA:", caea)
|
| 1272 |
+
|
| 1273 |
+
if wsfev1.Observaciones:
|
| 1274 |
+
print("Observaciones:")
|
| 1275 |
+
for obs in wsfev1.Observaciones:
|
| 1276 |
+
print(obs)
|
| 1277 |
+
|
| 1278 |
+
if wsfev1.Errores:
|
| 1279 |
+
print("Errores:")
|
| 1280 |
+
for error in wsfev1.Errores:
|
| 1281 |
+
print(error)
|
| 1282 |
+
|
| 1283 |
+
if DEBUG:
|
| 1284 |
+
print("periodo:", wsfev1.Periodo)
|
| 1285 |
+
print("orden:", wsfev1.Orden)
|
| 1286 |
+
print("fch_vig_desde:", wsfev1.FchVigDesde)
|
| 1287 |
+
print("fch_vig_hasta:", wsfev1.FchVigHasta)
|
| 1288 |
+
print("fch_tope_inf:", wsfev1.FchTopeInf)
|
| 1289 |
+
print("fch_proceso:", wsfev1.FchProceso)
|
| 1290 |
+
|
| 1291 |
+
if not caea:
|
| 1292 |
+
print('Consultando CAEA')
|
| 1293 |
+
caea = wsfev1.CAEAConsultar(periodo, orden)
|
| 1294 |
+
print("CAEA:", caea)
|
| 1295 |
+
if wsfev1.Errores:
|
| 1296 |
+
print("Errores:")
|
| 1297 |
+
for error in wsfev1.Errores:
|
| 1298 |
+
print(error)
|
| 1299 |
+
|
| 1300 |
+
if "--sinmovimiento-caea" in sys.argv:
|
| 1301 |
+
punto_vta = sys.argv[sys.argv.index("--sinmovimiento-caea") + 1]
|
| 1302 |
+
caea = sys.argv[sys.argv.index("--sinmovimiento-caea") + 2]
|
| 1303 |
+
|
| 1304 |
+
if DEBUG:
|
| 1305 |
+
print("Informando Punto Venta %s CAEA %s SIN MOVIMIENTO" % (punto_vta, caea))
|
| 1306 |
+
|
| 1307 |
+
resultado = wsfev1.CAEASinMovimientoInformar(punto_vta, caea)
|
| 1308 |
+
print("Resultado:", resultado)
|
| 1309 |
+
print("fch_proceso:", wsfev1.FchProceso)
|
| 1310 |
+
|
| 1311 |
+
if wsfev1.Errores:
|
| 1312 |
+
print("Errores:")
|
| 1313 |
+
for error in wsfev1.Errores:
|
| 1314 |
+
print(error)
|
| 1315 |
+
|
| 1316 |
+
|
| 1317 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 1318 |
+
INSTALL_DIR = WSFEv1.InstallDir = get_install_dir()
|
| 1319 |
+
|
| 1320 |
+
|
| 1321 |
+
if __name__ == '__main__':
|
| 1322 |
+
|
| 1323 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 1324 |
+
import pythoncom
|
| 1325 |
+
if TYPELIB:
|
| 1326 |
+
if '--register' in sys.argv:
|
| 1327 |
+
tlb = os.path.abspath(os.path.join(INSTALL_DIR, "typelib", "wsfev1.tlb"))
|
| 1328 |
+
print("Registering %s" % (tlb,))
|
| 1329 |
+
tli = pythoncom.LoadTypeLib(tlb)
|
| 1330 |
+
pythoncom.RegisterTypeLib(tli, tlb)
|
| 1331 |
+
elif '--unregister' in sys.argv:
|
| 1332 |
+
k = WSFEv1
|
| 1333 |
+
pythoncom.UnRegisterTypeLib(k._typelib_guid_,
|
| 1334 |
+
k._typelib_version_[0],
|
| 1335 |
+
k._typelib_version_[1],
|
| 1336 |
+
0,
|
| 1337 |
+
pythoncom.SYS_WIN32)
|
| 1338 |
+
print("Unregistered typelib")
|
| 1339 |
+
import win32com.server.register
|
| 1340 |
+
# print "_reg_class_spec_", WSFEv1._reg_class_spec_
|
| 1341 |
+
win32com.server.register.UseCommandLine(WSFEv1)
|
| 1342 |
+
elif "/Automate" in sys.argv:
|
| 1343 |
+
# MS seems to like /automate to run the class factories.
|
| 1344 |
+
import win32com.server.localserver
|
| 1345 |
+
# win32com.server.localserver.main()
|
| 1346 |
+
# start the server.
|
| 1347 |
+
win32com.server.localserver.serve([WSFEv1._reg_clsid_])
|
| 1348 |
+
else:
|
| 1349 |
+
main()
|
app/pyafipws/wsfexv1.py
ADDED
|
@@ -0,0 +1,799 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"""MοΏ½dulo para obtener CAE, cοΏ½digo de autorizaciοΏ½n de impresiοΏ½n o electrοΏ½nico,
|
| 14 |
+
del web service WSFEXv1 de AFIP (Factura ElectrοΏ½nica ExportaciοΏ½n VersiοΏ½n 1)
|
| 15 |
+
segοΏ½n RG2758/2010 (Registros Especiales Aduaneros) y RG3689/14 (servicios)
|
| 16 |
+
http://www.sistemasagiles.com.ar/trac/wiki/FacturaElectronicaExportacion
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
__author__ = "Mariano Reingart (reingart@gmail.com)"
|
| 20 |
+
__copyright__ = "Copyright (C) 2011-2015 Mariano Reingart"
|
| 21 |
+
__license__ = "GPL 3.0"
|
| 22 |
+
__version__ = "1.08f"
|
| 23 |
+
|
| 24 |
+
import datetime
|
| 25 |
+
import decimal
|
| 26 |
+
import os
|
| 27 |
+
import sys
|
| 28 |
+
from .utils import inicializar_y_capturar_excepciones, BaseWS, get_install_dir
|
| 29 |
+
|
| 30 |
+
HOMO = False
|
| 31 |
+
WSDL = "https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class WSFEXv1(BaseWS):
|
| 35 |
+
"Interfaz para el WebService de Factura ElectrοΏ½nica ExportaciοΏ½n VersiοΏ½n 1"
|
| 36 |
+
_public_methods_ = ['CrearFactura', 'AgregarItem', 'Authorize', 'GetCMP',
|
| 37 |
+
'AgregarPermiso', 'AgregarCmpAsoc',
|
| 38 |
+
'GetParamMon', 'GetParamTipoCbte', 'GetParamTipoExpo',
|
| 39 |
+
'GetParamIdiomas', 'GetParamUMed', 'GetParamIncoterms',
|
| 40 |
+
'GetParamDstPais', 'GetParamDstCUIT', 'GetParamIdiomas',
|
| 41 |
+
'GetParamIncoterms', 'GetParamDstCUIT',
|
| 42 |
+
'GetParamMonConCotizacion',
|
| 43 |
+
'GetParamPtosVenta', 'GetParamCtz', 'LoadTestXML',
|
| 44 |
+
'AnalizarXml', 'ObtenerTagXml', 'DebugLog',
|
| 45 |
+
'SetParametros', 'SetTicketAcceso', 'GetParametro',
|
| 46 |
+
'GetLastCMP', 'GetLastID',
|
| 47 |
+
'Dummy', 'Conectar', 'SetTicketAcceso']
|
| 48 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 49 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 50 |
+
'XmlRequest', 'XmlResponse', 'Version',
|
| 51 |
+
'Resultado', 'Obs', 'Reproceso',
|
| 52 |
+
'CAE', 'Vencimiento', 'Eventos', 'ErrCode', 'ErrMsg', 'FchVencCAE',
|
| 53 |
+
'Excepcion', 'LanzarExcepciones', 'Traceback', "InstallDir",
|
| 54 |
+
'PuntoVenta', 'CbteNro', 'FechaCbte', 'ImpTotal', 'FchCotiz']
|
| 55 |
+
|
| 56 |
+
_reg_progid_ = "WSFEXv1"
|
| 57 |
+
_reg_clsid_ = "{8106F039-D132-4F87-8AFE-ADE47B5503D4}"
|
| 58 |
+
|
| 59 |
+
# Variables globales para BaseWS:
|
| 60 |
+
HOMO = HOMO
|
| 61 |
+
WSDL = WSDL
|
| 62 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '')
|
| 63 |
+
factura = None
|
| 64 |
+
|
| 65 |
+
def inicializar(self):
|
| 66 |
+
BaseWS.inicializar(self)
|
| 67 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 68 |
+
self.Resultado = self.Motivo = self.Reproceso = ''
|
| 69 |
+
self.LastID = self.LastCMP = self.CAE = self.Vencimiento = ''
|
| 70 |
+
self.CbteNro = self.FechaCbte = self.PuntoVenta = self.ImpTotal = None
|
| 71 |
+
self.InstallDir = INSTALL_DIR
|
| 72 |
+
self.FchVencCAE = "" # retrocompatibilidad
|
| 73 |
+
self.FchCotiz = None
|
| 74 |
+
|
| 75 |
+
def __analizar_errores(self, ret):
|
| 76 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 77 |
+
if 'FEXErr' in ret:
|
| 78 |
+
errores = [ret['FEXErr']]
|
| 79 |
+
for error in errores:
|
| 80 |
+
self.Errores.append("%s: %s" % (
|
| 81 |
+
error['ErrCode'],
|
| 82 |
+
error['ErrMsg'],
|
| 83 |
+
))
|
| 84 |
+
self.ErrCode = ' '.join([str(error['ErrCode']) for error in errores])
|
| 85 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 86 |
+
if 'FEXEvents' in ret:
|
| 87 |
+
events = [ret['FEXEvents']]
|
| 88 |
+
self.Eventos = ['%s: %s' % (evt['EventCode'], evt.get('EventMsg', "")) for evt in events]
|
| 89 |
+
|
| 90 |
+
def CrearFactura(self, tipo_cbte=19, punto_vta=1, cbte_nro=0, fecha_cbte=None,
|
| 91 |
+
imp_total=0.0, tipo_expo=1, permiso_existente="N", pais_dst_cmp=None,
|
| 92 |
+
nombre_cliente="", cuit_pais_cliente="", domicilio_cliente="",
|
| 93 |
+
id_impositivo="", moneda_id="PES", moneda_ctz=1.0,
|
| 94 |
+
obs_comerciales="", obs_generales="", forma_pago="", incoterms="",
|
| 95 |
+
idioma_cbte=7, incoterms_ds=None, fecha_pago=None, **kwargs):
|
| 96 |
+
"Creo un objeto factura (interna)"
|
| 97 |
+
# Creo una factura electronica de exportaciοΏ½n
|
| 98 |
+
|
| 99 |
+
fact = {'tipo_cbte': tipo_cbte, 'punto_vta': punto_vta,
|
| 100 |
+
'cbte_nro': cbte_nro, 'fecha_cbte': fecha_cbte,
|
| 101 |
+
'tipo_doc': 80, 'nro_doc': cuit_pais_cliente,
|
| 102 |
+
'imp_total': imp_total,
|
| 103 |
+
'permiso_existente': permiso_existente,
|
| 104 |
+
'pais_dst_cmp': pais_dst_cmp,
|
| 105 |
+
'nombre_cliente': nombre_cliente,
|
| 106 |
+
'domicilio_cliente': domicilio_cliente,
|
| 107 |
+
'id_impositivo': id_impositivo,
|
| 108 |
+
'moneda_id': moneda_id, 'moneda_ctz': moneda_ctz,
|
| 109 |
+
'obs_comerciales': obs_comerciales,
|
| 110 |
+
'obs_generales': obs_generales,
|
| 111 |
+
'forma_pago': forma_pago,
|
| 112 |
+
'incoterms': incoterms,
|
| 113 |
+
'incoterms_ds': incoterms_ds,
|
| 114 |
+
'tipo_expo': tipo_expo,
|
| 115 |
+
'idioma_cbte': idioma_cbte,
|
| 116 |
+
'cbtes_asoc': [],
|
| 117 |
+
'permisos': [],
|
| 118 |
+
'detalles': [],
|
| 119 |
+
'fecha_pago': fecha_pago,
|
| 120 |
+
}
|
| 121 |
+
self.factura = fact
|
| 122 |
+
|
| 123 |
+
return True
|
| 124 |
+
|
| 125 |
+
def AgregarItem(self, codigo, ds, qty, umed, precio, importe, bonif=None, **kwargs):
|
| 126 |
+
"Agrego un item a una factura (interna)"
|
| 127 |
+
# Nota: no se calcula total (debe venir calculado!)
|
| 128 |
+
self.factura['detalles'].append({
|
| 129 |
+
'codigo': codigo,
|
| 130 |
+
'ds': ds,
|
| 131 |
+
'qty': qty,
|
| 132 |
+
'umed': umed,
|
| 133 |
+
'precio': precio,
|
| 134 |
+
'bonif': bonif,
|
| 135 |
+
'importe': importe,
|
| 136 |
+
})
|
| 137 |
+
return True
|
| 138 |
+
|
| 139 |
+
def AgregarPermiso(self, id_permiso, dst_merc, **kwargs):
|
| 140 |
+
"Agrego un permiso a una factura (interna)"
|
| 141 |
+
self.factura['permisos'].append({
|
| 142 |
+
'id_permiso': id_permiso,
|
| 143 |
+
'dst_merc': dst_merc,
|
| 144 |
+
})
|
| 145 |
+
return True
|
| 146 |
+
|
| 147 |
+
def AgregarCmpAsoc(self, cbte_tipo=19, cbte_punto_vta=0, cbte_nro=0, cbte_cuit=None, **kwargs):
|
| 148 |
+
"Agrego un comprobante asociado a una factura (interna)"
|
| 149 |
+
self.factura['cbtes_asoc'].append({
|
| 150 |
+
'cbte_tipo': cbte_tipo, 'cbte_punto_vta': cbte_punto_vta,
|
| 151 |
+
'cbte_nro': cbte_nro, 'cbte_cuit': cbte_cuit})
|
| 152 |
+
return True
|
| 153 |
+
|
| 154 |
+
@inicializar_y_capturar_excepciones
|
| 155 |
+
def Authorize(self, id):
|
| 156 |
+
"Autoriza la factura cargada en memoria"
|
| 157 |
+
f = self.factura
|
| 158 |
+
ret = self.client.FEXAuthorize(
|
| 159 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 160 |
+
Cmp={
|
| 161 |
+
'Id': id,
|
| 162 |
+
'Fecha_cbte': f['fecha_cbte'],
|
| 163 |
+
'Cbte_Tipo': f['tipo_cbte'],
|
| 164 |
+
'Punto_vta': f['punto_vta'],
|
| 165 |
+
'Cbte_nro': f['cbte_nro'],
|
| 166 |
+
'Tipo_expo': f['tipo_expo'],
|
| 167 |
+
'Permiso_existente': f['permiso_existente'],
|
| 168 |
+
'Permisos': f['permisos'] and [
|
| 169 |
+
{'Permiso': {
|
| 170 |
+
'Id_permiso': p['id_permiso'],
|
| 171 |
+
'Dst_merc': p['dst_merc'],
|
| 172 |
+
}} for p in f['permisos']] or None,
|
| 173 |
+
'Dst_cmp': f['pais_dst_cmp'],
|
| 174 |
+
'Cliente': f['nombre_cliente'],
|
| 175 |
+
'Cuit_pais_cliente': f['nro_doc'],
|
| 176 |
+
'Domicilio_cliente': f['domicilio_cliente'],
|
| 177 |
+
'Id_impositivo': f['id_impositivo'],
|
| 178 |
+
'Moneda_Id': f['moneda_id'],
|
| 179 |
+
'Moneda_ctz': f['moneda_ctz'],
|
| 180 |
+
'Obs_comerciales': f['obs_comerciales'],
|
| 181 |
+
'Imp_total': f['imp_total'],
|
| 182 |
+
'Obs': f['obs_generales'],
|
| 183 |
+
'Cmps_asoc': f['cbtes_asoc'] and [
|
| 184 |
+
{'Cmp_asoc': {
|
| 185 |
+
'Cbte_tipo': c['cbte_tipo'],
|
| 186 |
+
'Cbte_punto_vta': c['cbte_punto_vta'],
|
| 187 |
+
'Cbte_nro': c['cbte_nro'],
|
| 188 |
+
'Cbte_cuit': c['cbte_cuit'],
|
| 189 |
+
}} for c in f['cbtes_asoc']] or None,
|
| 190 |
+
'Forma_pago': f['forma_pago'],
|
| 191 |
+
'Incoterms': f['incoterms'],
|
| 192 |
+
'Incoterms_Ds': f['incoterms_ds'],
|
| 193 |
+
'Idioma_cbte': f['idioma_cbte'],
|
| 194 |
+
'Items': [
|
| 195 |
+
{'Item': {
|
| 196 |
+
'Pro_codigo': d['codigo'],
|
| 197 |
+
'Pro_ds': d['ds'],
|
| 198 |
+
'Pro_qty': d['qty'],
|
| 199 |
+
'Pro_umed': d['umed'],
|
| 200 |
+
'Pro_precio_uni': d['precio'],
|
| 201 |
+
'Pro_bonificacion': d['bonif'],
|
| 202 |
+
'Pro_total_item': d['importe'],
|
| 203 |
+
}} for d in f['detalles']],
|
| 204 |
+
'Fecha_pago': f['fecha_pago'],
|
| 205 |
+
})
|
| 206 |
+
|
| 207 |
+
result = ret['FEXAuthorizeResult']
|
| 208 |
+
self.__analizar_errores(result)
|
| 209 |
+
if 'FEXResultAuth' in result:
|
| 210 |
+
auth = result['FEXResultAuth']
|
| 211 |
+
# Resultado: A: Aceptado, R: Rechazado
|
| 212 |
+
self.Resultado = auth.get('Resultado', "")
|
| 213 |
+
# Obs:
|
| 214 |
+
self.Obs = auth.get('Motivos_Obs', "")
|
| 215 |
+
self.Reproceso = auth.get('Reproceso', "")
|
| 216 |
+
self.CAE = auth.get('Cae', "")
|
| 217 |
+
self.CbteNro = auth.get('Cbte_nro', "")
|
| 218 |
+
vto = str(auth.get('Fch_venc_Cae', ""))
|
| 219 |
+
self.FchVencCAE = vto
|
| 220 |
+
self.Vencimiento = "%s/%s/%s" % (vto[6:8], vto[4:6], vto[0:4])
|
| 221 |
+
return self.CAE
|
| 222 |
+
|
| 223 |
+
@inicializar_y_capturar_excepciones
|
| 224 |
+
def Dummy(self):
|
| 225 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 226 |
+
result = self.client.FEXDummy()['FEXDummyResult']
|
| 227 |
+
self.__analizar_errores(result)
|
| 228 |
+
self.AppServerStatus = str(result.get('AppServer', ''))
|
| 229 |
+
self.DbServerStatus = str(result.get('DbServer', ''))
|
| 230 |
+
self.AuthServerStatus = str(result.get('AuthServer', ''))
|
| 231 |
+
return True
|
| 232 |
+
|
| 233 |
+
@inicializar_y_capturar_excepciones
|
| 234 |
+
def GetCMP(self, tipo_cbte, punto_vta, cbte_nro):
|
| 235 |
+
"Recuperar los datos completos de un comprobante ya autorizado"
|
| 236 |
+
ret = self.client.FEXGetCMP(
|
| 237 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 238 |
+
Cmp={
|
| 239 |
+
'Cbte_tipo': tipo_cbte,
|
| 240 |
+
'Punto_vta': punto_vta,
|
| 241 |
+
'Cbte_nro': cbte_nro,
|
| 242 |
+
})
|
| 243 |
+
result = ret['FEXGetCMPResult']
|
| 244 |
+
self.__analizar_errores(result)
|
| 245 |
+
if 'FEXResultGet' in result:
|
| 246 |
+
resultget = result['FEXResultGet']
|
| 247 |
+
# Obs, cae y fecha cae
|
| 248 |
+
self.Obs = resultget.get('Obs') and resultget['Obs'].strip(" ") or ''
|
| 249 |
+
self.CAE = resultget.get('Cae', '')
|
| 250 |
+
vto = str(resultget.get('Fch_venc_Cae', ''))
|
| 251 |
+
self.Vencimiento = "%s/%s/%s" % (vto[6:8], vto[4:6], vto[0:4])
|
| 252 |
+
self.FechaCbte = resultget.get('Fecha_cbte', '') # .strftime("%Y/%m/%d")
|
| 253 |
+
self.PuntoVenta = resultget['Punto_vta'] # 4000
|
| 254 |
+
self.Resultado = resultget.get('Resultado', '')
|
| 255 |
+
self.CbteNro = resultget['Cbte_nro']
|
| 256 |
+
self.ImpTotal = str(resultget['Imp_total'])
|
| 257 |
+
return self.CAE
|
| 258 |
+
else:
|
| 259 |
+
return 0
|
| 260 |
+
|
| 261 |
+
@inicializar_y_capturar_excepciones
|
| 262 |
+
def GetLastCMP(self, tipo_cbte, punto_vta):
|
| 263 |
+
"Recuperar οΏ½ltimo nοΏ½mero de comprobante emitido"
|
| 264 |
+
ret = self.client.FEXGetLast_CMP(
|
| 265 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit,
|
| 266 |
+
'Cbte_Tipo': tipo_cbte,
|
| 267 |
+
'Pto_venta': punto_vta,
|
| 268 |
+
})
|
| 269 |
+
result = ret['FEXGetLast_CMPResult']
|
| 270 |
+
self.__analizar_errores(result)
|
| 271 |
+
if 'FEXResult_LastCMP' in result:
|
| 272 |
+
resultget = result['FEXResult_LastCMP']
|
| 273 |
+
self.CbteNro = resultget.get('Cbte_nro')
|
| 274 |
+
self.FechaCbte = resultget.get('Cbte_fecha') # .strftime("%Y/%m/%d")
|
| 275 |
+
return self.CbteNro
|
| 276 |
+
|
| 277 |
+
@inicializar_y_capturar_excepciones
|
| 278 |
+
def GetLastID(self):
|
| 279 |
+
"Recuperar οΏ½ltimo nοΏ½mero de transacciοΏ½n (ID)"
|
| 280 |
+
ret = self.client.FEXGetLast_ID(
|
| 281 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 282 |
+
result = ret['FEXGetLast_IDResult']
|
| 283 |
+
self.__analizar_errores(result)
|
| 284 |
+
if 'FEXResultGet' in result:
|
| 285 |
+
resultget = result['FEXResultGet']
|
| 286 |
+
return resultget.get('Id')
|
| 287 |
+
|
| 288 |
+
@inicializar_y_capturar_excepciones
|
| 289 |
+
def GetParamUMed(self, sep="|"):
|
| 290 |
+
ret = self.client.FEXGetPARAM_UMed(
|
| 291 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 292 |
+
result = ret['FEXGetPARAM_UMedResult']
|
| 293 |
+
self.__analizar_errores(result)
|
| 294 |
+
|
| 295 |
+
umeds = [] # unidades de medida
|
| 296 |
+
for u in result['FEXResultGet']:
|
| 297 |
+
u = u['ClsFEXResponse_UMed']
|
| 298 |
+
try:
|
| 299 |
+
umed = {'id': u.get('Umed_Id'), 'ds': u.get('Umed_Ds'),
|
| 300 |
+
'vig_desde': u.get('Umed_vig_desde'),
|
| 301 |
+
'vig_hasta': u.get('Umed_vig_hasta')}
|
| 302 |
+
except Exception as e:
|
| 303 |
+
print(e)
|
| 304 |
+
if u is None:
|
| 305 |
+
# <ClsFEXResponse_UMed xsi:nil="true"/> WTF!
|
| 306 |
+
umed = {'id': '', 'ds': '', 'vig_desde': '', 'vig_hasta': ''}
|
| 307 |
+
#import pdb; pdb.set_trace()
|
| 308 |
+
# print u
|
| 309 |
+
|
| 310 |
+
umeds.append(umed)
|
| 311 |
+
if sep:
|
| 312 |
+
return [("\t%(id)s\t%(ds)s\t%(vig_desde)s\t%(vig_hasta)s\t"
|
| 313 |
+
% it).replace("\t", sep) for it in umeds]
|
| 314 |
+
else:
|
| 315 |
+
return umeds
|
| 316 |
+
|
| 317 |
+
@inicializar_y_capturar_excepciones
|
| 318 |
+
def GetParamMon(self, sep="|"):
|
| 319 |
+
ret = self.client.FEXGetPARAM_MON(
|
| 320 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 321 |
+
result = ret['FEXGetPARAM_MONResult']
|
| 322 |
+
self.__analizar_errores(result)
|
| 323 |
+
|
| 324 |
+
mons = [] # monedas
|
| 325 |
+
for u in result['FEXResultGet']:
|
| 326 |
+
u = u['ClsFEXResponse_Mon']
|
| 327 |
+
try:
|
| 328 |
+
mon = {'id': u.get('Mon_Id'), 'ds': u.get('Mon_Ds'),
|
| 329 |
+
'vig_desde': u.get('Mon_vig_desde'),
|
| 330 |
+
'vig_hasta': u.get('Mon_vig_hasta')}
|
| 331 |
+
except Exception as e:
|
| 332 |
+
print(e)
|
| 333 |
+
if u is None:
|
| 334 |
+
# <ClsFEXResponse_UMed xsi:nil="true"/> WTF!
|
| 335 |
+
mon = {'id': '', 'ds': '', 'vig_desde': '', 'vig_hasta': ''}
|
| 336 |
+
#import pdb; pdb.set_trace()
|
| 337 |
+
# print u
|
| 338 |
+
|
| 339 |
+
mons.append(mon)
|
| 340 |
+
if sep:
|
| 341 |
+
return [("\t%(id)s\t%(ds)s\t%(vig_desde)s\t%(vig_hasta)s\t"
|
| 342 |
+
% it).replace("\t", sep) for it in mons]
|
| 343 |
+
else:
|
| 344 |
+
return mons
|
| 345 |
+
|
| 346 |
+
@inicializar_y_capturar_excepciones
|
| 347 |
+
def GetParamDstPais(self, sep="|"):
|
| 348 |
+
"Recuperador de valores referenciales de cοΏ½digos de PaοΏ½ses"
|
| 349 |
+
ret = self.client.FEXGetPARAM_DST_pais(
|
| 350 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 351 |
+
result = ret['FEXGetPARAM_DST_paisResult']
|
| 352 |
+
self.__analizar_errores(result)
|
| 353 |
+
|
| 354 |
+
ret = []
|
| 355 |
+
for u in result['FEXResultGet']:
|
| 356 |
+
u = u['ClsFEXResponse_DST_pais']
|
| 357 |
+
try:
|
| 358 |
+
r = {'codigo': u.get('DST_Codigo'), 'ds': u.get('DST_Ds'), }
|
| 359 |
+
except Exception as e:
|
| 360 |
+
print(e)
|
| 361 |
+
|
| 362 |
+
ret.append(r)
|
| 363 |
+
if sep:
|
| 364 |
+
return [("\t%(codigo)s\t%(ds)s\t"
|
| 365 |
+
% it).replace("\t", sep) for it in ret]
|
| 366 |
+
else:
|
| 367 |
+
return ret
|
| 368 |
+
|
| 369 |
+
@inicializar_y_capturar_excepciones
|
| 370 |
+
def GetParamDstCUIT(self, sep="|"):
|
| 371 |
+
"Recuperar lista de valores referenciales de CUIT de PaοΏ½ses"
|
| 372 |
+
ret = self.client.FEXGetPARAM_DST_CUIT(
|
| 373 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 374 |
+
result = ret['FEXGetPARAM_DST_CUITResult']
|
| 375 |
+
self.__analizar_errores(result)
|
| 376 |
+
|
| 377 |
+
ret = []
|
| 378 |
+
for u in result['FEXResultGet']:
|
| 379 |
+
u = u['ClsFEXResponse_DST_cuit']
|
| 380 |
+
try:
|
| 381 |
+
r = {'codigo': u.get('DST_CUIT'), 'ds': u.get('DST_Ds'), }
|
| 382 |
+
except Exception as e:
|
| 383 |
+
print(e)
|
| 384 |
+
|
| 385 |
+
ret.append(r)
|
| 386 |
+
if sep:
|
| 387 |
+
return [("\t%(codigo)s\t%(ds)s\t"
|
| 388 |
+
% it).replace("\t", sep) for it in ret]
|
| 389 |
+
else:
|
| 390 |
+
return ret
|
| 391 |
+
|
| 392 |
+
@inicializar_y_capturar_excepciones
|
| 393 |
+
def GetParamTipoCbte(self, sep="|"):
|
| 394 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipo de comprobantes"
|
| 395 |
+
ret = self.client.FEXGetPARAM_Cbte_Tipo(
|
| 396 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 397 |
+
result = ret['FEXGetPARAM_Cbte_TipoResult']
|
| 398 |
+
self.__analizar_errores(result)
|
| 399 |
+
|
| 400 |
+
ret = []
|
| 401 |
+
for u in result['FEXResultGet']:
|
| 402 |
+
u = u['ClsFEXResponse_Cbte_Tipo']
|
| 403 |
+
try:
|
| 404 |
+
r = {'codigo': u.get('Cbte_Id'),
|
| 405 |
+
'ds': u.get('Cbte_Ds').replace('\n', '').replace('\r', ''),
|
| 406 |
+
'vig_desde': u.get('Cbte_vig_desde'),
|
| 407 |
+
'vig_hasta': u.get('Cbte_vig_hasta')}
|
| 408 |
+
except Exception as e:
|
| 409 |
+
print(e)
|
| 410 |
+
|
| 411 |
+
ret.append(r)
|
| 412 |
+
if sep:
|
| 413 |
+
return [("\t%(codigo)s\t%(ds)s\t%(vig_desde)s\t%(vig_hasta)s\t"
|
| 414 |
+
% it).replace("\t", sep) for it in ret]
|
| 415 |
+
else:
|
| 416 |
+
return ret
|
| 417 |
+
|
| 418 |
+
@inicializar_y_capturar_excepciones
|
| 419 |
+
def GetParamTipoExpo(self, sep="|"):
|
| 420 |
+
"Recuperador de valores referenciales de cοΏ½digos de Tipo de exportaciοΏ½n"
|
| 421 |
+
ret = self.client.FEXGetPARAM_Tipo_Expo(
|
| 422 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 423 |
+
result = ret['FEXGetPARAM_Tipo_ExpoResult']
|
| 424 |
+
self.__analizar_errores(result)
|
| 425 |
+
|
| 426 |
+
ret = []
|
| 427 |
+
for u in result['FEXResultGet']:
|
| 428 |
+
u = u['ClsFEXResponse_Tex']
|
| 429 |
+
try:
|
| 430 |
+
r = {'codigo': u.get('Tex_Id'), 'ds': u.get('Tex_Ds'),
|
| 431 |
+
'vig_desde': u.get('Tex_vig_desde'),
|
| 432 |
+
'vig_hasta': u.get('Tex_vig_hasta')}
|
| 433 |
+
except Exception as e:
|
| 434 |
+
print(e)
|
| 435 |
+
|
| 436 |
+
ret.append(r)
|
| 437 |
+
if sep:
|
| 438 |
+
return [("\t%(codigo)s\t%(ds)s\t%(vig_desde)s\t%(vig_hasta)s\t"
|
| 439 |
+
% it).replace("\t", sep) for it in ret]
|
| 440 |
+
else:
|
| 441 |
+
return ret
|
| 442 |
+
|
| 443 |
+
@inicializar_y_capturar_excepciones
|
| 444 |
+
def GetParamIdiomas(self, sep="|"):
|
| 445 |
+
"Recuperar lista de valores referenciales de cοΏ½digos de Idiomas"
|
| 446 |
+
ret = self.client.FEXGetPARAM_Idiomas(
|
| 447 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 448 |
+
result = ret['FEXGetPARAM_IdiomasResult']
|
| 449 |
+
self.__analizar_errores(result)
|
| 450 |
+
|
| 451 |
+
ret = []
|
| 452 |
+
for u in result['FEXResultGet']:
|
| 453 |
+
u = u['ClsFEXResponse_Idi']
|
| 454 |
+
try:
|
| 455 |
+
r = {'codigo': u.get('Idi_Id'), 'ds': u.get('Idi_Ds'),
|
| 456 |
+
'vig_desde': u.get('Idi_vig_hasta'),
|
| 457 |
+
'vig_hasta': u.get('Idi_vig_desde')}
|
| 458 |
+
except Exception as e:
|
| 459 |
+
print(e)
|
| 460 |
+
|
| 461 |
+
ret.append(r)
|
| 462 |
+
if sep:
|
| 463 |
+
return [("\t%(codigo)s\t%(ds)s\t%(vig_desde)s\t%(vig_hasta)s\t"
|
| 464 |
+
% it).replace("\t", sep) for it in ret]
|
| 465 |
+
else:
|
| 466 |
+
return ret
|
| 467 |
+
|
| 468 |
+
def GetParamIncoterms(self, sep="|"):
|
| 469 |
+
"Recuperar lista de valores referenciales de Incoterms"
|
| 470 |
+
ret = self.client.FEXGetPARAM_Incoterms(
|
| 471 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit, })
|
| 472 |
+
result = ret['FEXGetPARAM_IncotermsResult']
|
| 473 |
+
self.__analizar_errores(result)
|
| 474 |
+
|
| 475 |
+
ret = []
|
| 476 |
+
for u in result['FEXResultGet']:
|
| 477 |
+
u = u['ClsFEXResponse_Inc']
|
| 478 |
+
try:
|
| 479 |
+
r = {'codigo': u.get('Inc_Id'), 'ds': u.get('Inc_Ds'),
|
| 480 |
+
'vig_desde': u.get('Inc_vig_hasta'),
|
| 481 |
+
'vig_hasta': u.get('Inc_vig_desde')}
|
| 482 |
+
except Exception as e:
|
| 483 |
+
print(e)
|
| 484 |
+
|
| 485 |
+
ret.append(r)
|
| 486 |
+
if sep:
|
| 487 |
+
return [("\t%(codigo)s\t%(ds)s\t%(vig_desde)s\t%(vig_hasta)s\t"
|
| 488 |
+
% it).replace("\t", sep) for it in ret]
|
| 489 |
+
else:
|
| 490 |
+
return ret
|
| 491 |
+
|
| 492 |
+
@inicializar_y_capturar_excepciones
|
| 493 |
+
def GetParamCtz(self, moneda_id):
|
| 494 |
+
"Recuperador de cotizaciοΏ½n de moneda"
|
| 495 |
+
ret = self.client.FEXGetPARAM_Ctz(
|
| 496 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 497 |
+
Mon_id=moneda_id,
|
| 498 |
+
)
|
| 499 |
+
self.__analizar_errores(ret['FEXGetPARAM_CtzResult'])
|
| 500 |
+
res = ret['FEXGetPARAM_CtzResult'].get('FEXResultGet')
|
| 501 |
+
if res:
|
| 502 |
+
ctz = str(res.get('Mon_ctz', ""))
|
| 503 |
+
self.FchCotiz = res.get("Mon_fecha")
|
| 504 |
+
else:
|
| 505 |
+
ctz = ''
|
| 506 |
+
return ctz
|
| 507 |
+
|
| 508 |
+
@inicializar_y_capturar_excepciones
|
| 509 |
+
def GetParamMonConCotizacion(self, fecha=None, sep="|"):
|
| 510 |
+
"Recupera el listado de monedas que tengan cotizacion de ADUANA"
|
| 511 |
+
if not fecha:
|
| 512 |
+
fecha = datetime.date.today().strftime("%Y%m%d")
|
| 513 |
+
|
| 514 |
+
ret = self.client.FEXGetPARAM_MON_CON_COTIZACION(
|
| 515 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 516 |
+
Fecha_CTZ=fecha)
|
| 517 |
+
result = ret['FEXGetPARAM_MON_CON_COTIZACIONResult']
|
| 518 |
+
self.__analizar_errores(result)
|
| 519 |
+
|
| 520 |
+
mons = [] # monedas
|
| 521 |
+
for u in result['FEXResultGet']:
|
| 522 |
+
u = u['ClsFEXResponse_Mon_CON_Cotizacion']
|
| 523 |
+
try:
|
| 524 |
+
mon = {'id': u.get('Mon_Id'), 'ctz': u.get('Mon_ctz'),
|
| 525 |
+
'fecha': u.get('Fecha_ctz')}
|
| 526 |
+
except Exception as e:
|
| 527 |
+
print(e)
|
| 528 |
+
if u is None:
|
| 529 |
+
# <ClsFEXResponse_UMed xsi:nil="true"/> WTF!
|
| 530 |
+
mon = {'id':'', 'ctz':'','fecha':''}
|
| 531 |
+
mons.append(mon)
|
| 532 |
+
if sep:
|
| 533 |
+
return [("\t%(id)s\t%(ctz)s\t%(fecha)s\t"
|
| 534 |
+
% it).replace("\t", sep) for it in mons]
|
| 535 |
+
else:
|
| 536 |
+
return mons
|
| 537 |
+
|
| 538 |
+
@inicializar_y_capturar_excepciones
|
| 539 |
+
def GetParamPtosVenta(self, sep="|"):
|
| 540 |
+
"Recupera el listado de los puntos de venta para exportacion y estado"
|
| 541 |
+
ret = self.client.FEXGetPARAM_PtoVenta(
|
| 542 |
+
Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit},
|
| 543 |
+
)
|
| 544 |
+
self.__analizar_errores(ret['FEXGetPARAM_PtoVentaResult'])
|
| 545 |
+
res = ret['FEXGetPARAM_PtoVentaResult'].get('FEXResultGet')
|
| 546 |
+
ret = []
|
| 547 |
+
for pu in res:
|
| 548 |
+
p = pu['ClsFEXResponse_PtoVenta']
|
| 549 |
+
try:
|
| 550 |
+
r = {'nro': p.get('Pve_Nro'), 'baja': p.get('Pve_FchBaj'),
|
| 551 |
+
'bloqueado': p.get('Pve_Bloqueado'), }
|
| 552 |
+
except Exception as e:
|
| 553 |
+
print(e)
|
| 554 |
+
ret.append(r)
|
| 555 |
+
return [("%(nro)s\tBloqueado:%(bloqueado)s\tFchBaja:%(baja)s" % r).replace("\t", sep)
|
| 556 |
+
for r in ret]
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
class WSFEX(WSFEXv1):
|
| 560 |
+
"Wrapper para retrocompatibilidad con WSFEX"
|
| 561 |
+
|
| 562 |
+
_reg_progid_ = "WSFEX"
|
| 563 |
+
_reg_clsid_ = "{B3C8D3D3-D5DA-44C9-B003-11845803B2BD}"
|
| 564 |
+
|
| 565 |
+
def __init__(self):
|
| 566 |
+
WSFEXv1.__init__(self)
|
| 567 |
+
self.Version = "%s %s WSFEXv1" % (__version__, HOMO and 'HomologaciοΏ½n' or '')
|
| 568 |
+
|
| 569 |
+
def Conectar(self, url="", proxy=""):
|
| 570 |
+
# Ajustar URL de V0 a V1:
|
| 571 |
+
if url in ("https://wswhomo.afip.gov.ar/wsfex/service.asmx",
|
| 572 |
+
"http://wswhomo.afip.gov.ar/WSFEX/service.asmx"):
|
| 573 |
+
url = "https://wswhomo.afip.gov.ar/wsfexv1/service.asmx"
|
| 574 |
+
elif url in ("https://servicios1.afip.gov.ar/wsfex/service.asmx",
|
| 575 |
+
"http://servicios1.afip.gov.ar/WSFEX/service.asmx"):
|
| 576 |
+
url = "https://servicios1.afip.gov.ar/wsfexv1/service.asmx"
|
| 577 |
+
return WSFEXv1.Conectar(self, cache=None, wsdl=url, proxy=proxy)
|
| 578 |
+
|
| 579 |
+
|
| 580 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 581 |
+
INSTALL_DIR = WSFEXv1.InstallDir = get_install_dir()
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
def p_assert_eq(a, b):
|
| 585 |
+
print(a, a == b and '==' or '!=', b)
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
if __name__ == "__main__":
|
| 589 |
+
|
| 590 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 591 |
+
import win32com.server.register
|
| 592 |
+
win32com.server.register.UseCommandLine(WSFEXv1)
|
| 593 |
+
if '--wsfex' in sys.argv:
|
| 594 |
+
win32com.server.register.UseCommandLine(WSFEX)
|
| 595 |
+
# elif "/Automate" in sys.argv:
|
| 596 |
+
# # MS seems to like /automate to run the class factories.
|
| 597 |
+
# import win32com.server.localserver
|
| 598 |
+
# #win32com.server.localserver.main()
|
| 599 |
+
# # start the server.
|
| 600 |
+
# win32com.server.localserver.serve([WSFEXv1._reg_clsid_])
|
| 601 |
+
else:
|
| 602 |
+
|
| 603 |
+
# Crear objeto interface Web Service de Factura ElectrοΏ½nica de ExportaciοΏ½n
|
| 604 |
+
wsfexv1 = WSFEXv1()
|
| 605 |
+
# Setear token y sing de autorizaciοΏ½n (pasos previos)
|
| 606 |
+
|
| 607 |
+
# obteniendo el TA para pruebas
|
| 608 |
+
from .wsaa import WSAA
|
| 609 |
+
ta = WSAA().Autenticar("wsfex", "reingart.crt", "reingart.key")
|
| 610 |
+
wsfexv1.SetTicketAcceso(ta)
|
| 611 |
+
|
| 612 |
+
# CUIT del emisor (debe estar registrado en la AFIP)
|
| 613 |
+
wsfexv1.Cuit = "20267565393"
|
| 614 |
+
|
| 615 |
+
# Conectar al Servicio Web de FacturaciοΏ½n (producciοΏ½n u homologaciοΏ½n)
|
| 616 |
+
if "--prod" in sys.argv:
|
| 617 |
+
wsdl = "https://servicios1.afip.gov.ar/wsfexv1/service.asmx?WSDL"
|
| 618 |
+
else:
|
| 619 |
+
wsdl = "https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL"
|
| 620 |
+
cache = proxy = ""
|
| 621 |
+
wrapper = "httplib2"
|
| 622 |
+
cacert = open("conf/afip_ca_info.crt").read()
|
| 623 |
+
ok = wsfexv1.Conectar(cache, wsdl, proxy, wrapper, cacert)
|
| 624 |
+
|
| 625 |
+
if '--dummy' in sys.argv:
|
| 626 |
+
#wsfexv1.LanzarExcepciones = False
|
| 627 |
+
print(wsfexv1.Dummy())
|
| 628 |
+
print("AppServerStatus", wsfexv1.AppServerStatus)
|
| 629 |
+
print("DbServerStatus", wsfexv1.DbServerStatus)
|
| 630 |
+
print("AuthServerStatus", wsfexv1.AuthServerStatus)
|
| 631 |
+
|
| 632 |
+
if "--prueba" in sys.argv:
|
| 633 |
+
try:
|
| 634 |
+
# Establezco los valores de la factura a autorizar:
|
| 635 |
+
tipo_cbte = '--nc' in sys.argv and 21 or 19 # FC/NC Expo (ver tabla de parοΏ½metros)
|
| 636 |
+
punto_vta = 7
|
| 637 |
+
# Obtengo el οΏ½ltimo nοΏ½mero de comprobante y le agrego 1
|
| 638 |
+
cbte_nro = int(wsfexv1.GetLastCMP(tipo_cbte, punto_vta)) + 1
|
| 639 |
+
fecha_cbte = datetime.datetime.now().strftime("%Y%m%d")
|
| 640 |
+
tipo_expo = 1 # tipo de exportaciοΏ½n (ver tabla de parοΏ½metros)
|
| 641 |
+
permiso_existente = (tipo_cbte not in (20, 21) or tipo_expo != 1) and "S" or ""
|
| 642 |
+
print("permiso_existente", permiso_existente)
|
| 643 |
+
dst_cmp = 203 # paοΏ½s destino
|
| 644 |
+
cliente = "Joao Da Silva"
|
| 645 |
+
cuit_pais_cliente = "50000000016"
|
| 646 |
+
domicilio_cliente = "RοΏ½a Ρ°76 km 34.5 Alagoas"
|
| 647 |
+
id_impositivo = "PJ54482221-l"
|
| 648 |
+
moneda_id = "DOL" # para reales, "DOL" o "PES" (ver tabla de parοΏ½metros)
|
| 649 |
+
moneda_ctz = "8.00" # wsfexv1.GetParamCtz('DOL') <- no funciona
|
| 650 |
+
obs_comerciales = "Observaciones comerciales"
|
| 651 |
+
obs = "Sin observaciones"
|
| 652 |
+
forma_pago = "30 dias"
|
| 653 |
+
incoterms = "FOB" # (ver tabla de parοΏ½metros)
|
| 654 |
+
incoterms_ds = "Flete a Bordo"
|
| 655 |
+
idioma_cbte = 1 # (ver tabla de parοΏ½metros)
|
| 656 |
+
imp_total = "250.00"
|
| 657 |
+
|
| 658 |
+
# Creo una factura (internamente, no se llama al WebService):
|
| 659 |
+
ok = wsfexv1.CrearFactura(tipo_cbte, punto_vta, cbte_nro, fecha_cbte,
|
| 660 |
+
imp_total, tipo_expo, permiso_existente, dst_cmp,
|
| 661 |
+
cliente, cuit_pais_cliente, domicilio_cliente,
|
| 662 |
+
id_impositivo, moneda_id, moneda_ctz,
|
| 663 |
+
obs_comerciales, obs, forma_pago, incoterms,
|
| 664 |
+
idioma_cbte, incoterms_ds)
|
| 665 |
+
|
| 666 |
+
# Agrego un item:
|
| 667 |
+
codigo = "PRO1"
|
| 668 |
+
ds = "Producto Tipo 1 Exportacion MERCOSUR ISO 9001"
|
| 669 |
+
qty = 2
|
| 670 |
+
precio = "150.00"
|
| 671 |
+
umed = 1 # Ver tabla de parοΏ½metros (unidades de medida)
|
| 672 |
+
bonif = "50.00"
|
| 673 |
+
imp_total = "250.00" # importe total final del artοΏ½culo
|
| 674 |
+
# lo agrego a la factura (internamente, no se llama al WebService):
|
| 675 |
+
ok = wsfexv1.AgregarItem(codigo, ds, qty, umed, precio, imp_total, bonif)
|
| 676 |
+
ok = wsfexv1.AgregarItem(codigo, ds, qty, umed, precio, imp_total, bonif)
|
| 677 |
+
ok = wsfexv1.AgregarItem(codigo, ds, 0, 99, 0, -float(imp_total), 0)
|
| 678 |
+
|
| 679 |
+
# Agrego un permiso (ver manual para el desarrollador)
|
| 680 |
+
if permiso_existente:
|
| 681 |
+
id = "99999AAXX999999A"
|
| 682 |
+
dst = 225 # paοΏ½s destino de la mercaderia
|
| 683 |
+
ok = wsfexv1.AgregarPermiso(id, dst)
|
| 684 |
+
|
| 685 |
+
# Agrego un comprobante asociado (solo para N/C o N/D)
|
| 686 |
+
if tipo_cbte in (20, 21):
|
| 687 |
+
cbteasoc_tipo = 19
|
| 688 |
+
cbteasoc_pto_vta = 2
|
| 689 |
+
cbteasoc_nro = 1234
|
| 690 |
+
cbteasoc_cuit = 20111111111
|
| 691 |
+
wsfexv1.AgregarCmpAsoc(cbteasoc_tipo, cbteasoc_pto_vta, cbteasoc_nro, cbteasoc_cuit)
|
| 692 |
+
|
| 693 |
+
# id = "99000000000100" # nοΏ½mero propio de transacciοΏ½n
|
| 694 |
+
# obtengo el οΏ½ltimo ID y le adiciono 1
|
| 695 |
+
# (advertencia: evitar overflow y almacenar!)
|
| 696 |
+
id = int(wsfexv1.GetLastID()) + 1
|
| 697 |
+
|
| 698 |
+
# Llamo al WebService de AutorizaciοΏ½n para obtener el CAE
|
| 699 |
+
cae = wsfexv1.Authorize(id)
|
| 700 |
+
|
| 701 |
+
print("Comprobante", tipo_cbte, wsfexv1.CbteNro)
|
| 702 |
+
print("Resultado", wsfexv1.Resultado)
|
| 703 |
+
print("CAE", wsfexv1.CAE)
|
| 704 |
+
print("Vencimiento", wsfexv1.Vencimiento)
|
| 705 |
+
|
| 706 |
+
if wsfexv1.Resultado and False:
|
| 707 |
+
print(wsfexv1.client.help("FEXGetCMP").encode("latin1"))
|
| 708 |
+
wsfexv1.GetCMP(tipo_cbte, punto_vta, cbte_nro)
|
| 709 |
+
print("CAE consulta", wsfexv1.CAE, wsfexv1.CAE == cae)
|
| 710 |
+
print("NRO consulta", wsfexv1.CbteNro, wsfexv1.CbteNro == cbte_nro)
|
| 711 |
+
print("TOTAL consulta", wsfexv1.ImpTotal, wsfexv1.ImpTotal == imp_total)
|
| 712 |
+
|
| 713 |
+
except Exception as e:
|
| 714 |
+
print(wsfexv1.XmlRequest)
|
| 715 |
+
print(wsfexv1.XmlResponse)
|
| 716 |
+
print(wsfexv1.ErrCode)
|
| 717 |
+
print(wsfexv1.ErrMsg)
|
| 718 |
+
print(wsfexv1.Excepcion)
|
| 719 |
+
print(wsfexv1.Traceback)
|
| 720 |
+
raise
|
| 721 |
+
|
| 722 |
+
if "--get" in sys.argv:
|
| 723 |
+
wsfexv1.client.help("FEXGetCMP")
|
| 724 |
+
tipo_cbte = 19
|
| 725 |
+
punto_vta = 7
|
| 726 |
+
cbte_nro = wsfexv1.GetLastCMP(tipo_cbte, punto_vta)
|
| 727 |
+
|
| 728 |
+
wsfexv1.GetCMP(tipo_cbte, punto_vta, cbte_nro)
|
| 729 |
+
|
| 730 |
+
print("FechaCbte = ", wsfexv1.FechaCbte)
|
| 731 |
+
print("CbteNro = ", wsfexv1.CbteNro)
|
| 732 |
+
print("PuntoVenta = ", wsfexv1.PuntoVenta)
|
| 733 |
+
print("ImpTotal =", wsfexv1.ImpTotal)
|
| 734 |
+
print("CAE = ", wsfexv1.CAE)
|
| 735 |
+
print("Vencimiento = ", wsfexv1.Vencimiento)
|
| 736 |
+
|
| 737 |
+
wsfexv1.AnalizarXml("XmlResponse")
|
| 738 |
+
p_assert_eq(wsfexv1.ObtenerTagXml('Cae'), str(wsfexv1.CAE))
|
| 739 |
+
p_assert_eq(wsfexv1.ObtenerTagXml('Fecha_cbte'), wsfexv1.FechaCbte)
|
| 740 |
+
p_assert_eq(wsfexv1.ObtenerTagXml('Moneda_Id'), "DOL")
|
| 741 |
+
p_assert_eq(wsfexv1.ObtenerTagXml('Moneda_ctz'), "8")
|
| 742 |
+
p_assert_eq(wsfexv1.ObtenerTagXml('Id_impositivo'), "PJ54482221-l")
|
| 743 |
+
|
| 744 |
+
if "--params" in sys.argv:
|
| 745 |
+
import codecs
|
| 746 |
+
import locale
|
| 747 |
+
sys.stdout = codecs.getwriter('latin1')(sys.stdout)
|
| 748 |
+
|
| 749 |
+
print("=== Incoterms ===")
|
| 750 |
+
idiomas = wsfexv1.GetParamIncoterms(sep="||")
|
| 751 |
+
for idioma in idiomas:
|
| 752 |
+
print(idioma)
|
| 753 |
+
|
| 754 |
+
print("=== Idiomas ===")
|
| 755 |
+
idiomas = wsfexv1.GetParamIdiomas(sep="||")
|
| 756 |
+
for idioma in idiomas:
|
| 757 |
+
print(idioma)
|
| 758 |
+
|
| 759 |
+
print("=== Tipos Comprobantes ===")
|
| 760 |
+
tipos = wsfexv1.GetParamTipoCbte(sep=False)
|
| 761 |
+
for t in tipos:
|
| 762 |
+
print("||%(codigo)s||%(ds)s||" % t)
|
| 763 |
+
|
| 764 |
+
print("=== Tipos Expo ===")
|
| 765 |
+
tipos = wsfexv1.GetParamTipoExpo(sep=False)
|
| 766 |
+
for t in tipos:
|
| 767 |
+
print("||%(codigo)s||%(ds)s||%(vig_desde)s||%(vig_hasta)s||" % t)
|
| 768 |
+
#umeds = dict([(u.get('id', ""),u.get('ds', "")) for u in umedidas])
|
| 769 |
+
|
| 770 |
+
print("=== Monedas ===")
|
| 771 |
+
mons = wsfexv1.GetParamMon(sep=False)
|
| 772 |
+
for m in mons:
|
| 773 |
+
print("||%(id)s||%(ds)s||%(vig_desde)s||%(vig_hasta)s||" % m)
|
| 774 |
+
#umeds = dict([(u.get('id', ""),u.get('ds', "")) for u in umedidas])
|
| 775 |
+
|
| 776 |
+
print("=== Unidades de medida ===")
|
| 777 |
+
umedidas = wsfexv1.GetParamUMed(sep=False)
|
| 778 |
+
for u in umedidas:
|
| 779 |
+
print("||%(id)s||%(ds)s||%(vig_desde)s||%(vig_hasta)s||" % u)
|
| 780 |
+
umeds = dict([(u.get('id', ""), u.get('ds', "")) for u in umedidas])
|
| 781 |
+
|
| 782 |
+
print("=== CοΏ½digo Pais Destino ===")
|
| 783 |
+
ret = wsfexv1.GetParamDstPais(sep=False)
|
| 784 |
+
for r in ret:
|
| 785 |
+
print("||%(codigo)s||%(ds)s||" % r)
|
| 786 |
+
|
| 787 |
+
print("=== CUIT Pais Destino ===")
|
| 788 |
+
ret = wsfexv1.GetParamDstCUIT(sep=False)
|
| 789 |
+
for r in ret:
|
| 790 |
+
print("||%(codigo)s||%(ds)s||" % r)
|
| 791 |
+
|
| 792 |
+
if "--ctz" in sys.argv:
|
| 793 |
+
print(wsfexv1.GetParamCtz('DOL'))
|
| 794 |
+
|
| 795 |
+
if "--monctz" in sys.argv:
|
| 796 |
+
print(wsfexv1.GetParamMonConCotizacion())
|
| 797 |
+
|
| 798 |
+
if "--ptosventa" in sys.argv:
|
| 799 |
+
print(wsfexv1.GetParamPtosVenta())
|
app/pyafipws/wslpg.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
app/pyafipws/wslpg_datos.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
from decimal import Decimal
|
| 4 |
+
|
| 5 |
+
TIPOS_OP = {1: 'Compraventa de granos', 2: 'ConsignaciΓ³n de granos'}
|
| 6 |
+
GRANOS = {
|
| 7 |
+
1: 'LINO', 2: 'GIRASOL', 3: 'MANI EN CAJA',
|
| 8 |
+
4: 'GIRASOL DESCASCARADO', 5: 'MANI PARA INDUSTRIA DE SELECCION',
|
| 9 |
+
6: 'MANI PARA INDUSTRIA ACEITERA', 7: 'MANI TIPO CONFITERIA',
|
| 10 |
+
8: 'COLZA', 9: 'COLZA 00 CANOLA', 10: 'TRIGO FORRAJERO',
|
| 11 |
+
11: 'CEBADA FORRAJERA', 12: 'CEBADA APTA PARA MALTERIA',
|
| 12 |
+
14: 'TRIGO CANDEAL', 15: 'TRIGO PAN',
|
| 13 |
+
16: 'AVENA', 17: 'CEBADA CERVECERA', 18: 'CENTENO',
|
| 14 |
+
19: 'MAIZ', 20: 'MIJO', 21: 'ARROZ CASCARA',
|
| 15 |
+
22: 'SORGO GRANIFERO', 23: 'SOJA', 25: 'TRIGO PLATA',
|
| 16 |
+
26: 'MAIZ FLYNT O PLATA', 27: 'MAIZ PISINGALLO',
|
| 17 |
+
28: 'TRITICALE', 30: 'ALPISTE', 31: 'ALGODON', 32: 'CARTAMO',
|
| 18 |
+
33: 'POROTO BLANCO NATURAL OVAL Y ALUBIA',
|
| 19 |
+
34: 'POROTO DISTINTO DEL BLANCO OVAL Y ALUBIA',
|
| 20 |
+
35: 'ARROZ', 46: 'LENTEJA', 47: 'ARVEJA',
|
| 21 |
+
48: 'POROTO BLANCO SELECCIONADO OVAL Y ALUBIA',
|
| 22 |
+
49: 'OTRAS LEGUMBRES', 50: 'OTROS GRANOS', 59: 'GARBANZO', }
|
| 23 |
+
|
| 24 |
+
PUERTOS = {1: "SAN LORENZO/SAN MARTIN", 2: "ROSARIO",
|
| 25 |
+
3: "BAHIA BLANCA", 4: "NECOCHEA", 5: "RAMALLO", 6: "LIMA",
|
| 26 |
+
7: "DIAMANTE", 8: "BUENOS AIRES", 9: "SAN PEDRO",
|
| 27 |
+
10: "SAN NICOLAS", 11: "TERMINAL DEL GUAZU", 12: "ZARATE",
|
| 28 |
+
13: "VILLA CONSTITUCION"}
|
| 29 |
+
|
| 30 |
+
PROVINCIAS = {1: 'BUENOS AIRES', 0: 'CAPITAL FEDERAL',
|
| 31 |
+
2: 'CATAMARCA', 16: 'CHACO', 17: 'CHUBUT',
|
| 32 |
+
4: 'CORRIENTES', 3: 'CΓRDOBA', 5: 'ENTRE RIOS',
|
| 33 |
+
18: 'FORMOSA', 6: 'JUJUY', 21: 'LA PAMPA',
|
| 34 |
+
8: 'LA RIOJA', 7: 'MENDOZA', 19: 'MISIONES',
|
| 35 |
+
20: 'NEUQUΓN', 22: 'RIO NEGRO', 9: 'SALTA',
|
| 36 |
+
10: 'SAN JUAN', 11: 'SAN LUIS', 23: 'SANTA CRUZ',
|
| 37 |
+
12: 'SANTA FE', 13: 'SANTIAGO DEL ESTERO',
|
| 38 |
+
24: 'TIERRA DEL FUEGO', 14: 'TUCUMΓN'}
|
| 39 |
+
|
| 40 |
+
TIPO_CERT_DEP = {1: "F1116/RT", 5: "F1116/A", 332: "Cert.Elec."}
|
| 41 |
+
|
| 42 |
+
CAMPANIAS = {1213: "2012/2013", 1112: "2011/2012", 1011: "2010/2011",
|
| 43 |
+
910: "2009/2010", 809: "2008/2009", 708: "2007/2008",
|
| 44 |
+
607: "2006/2007", 506: "2005/2006", 405: "2004/2005",
|
| 45 |
+
304: "2003/2004", 1314: "2013/2014", 1415: "2014/2015"}
|
| 46 |
+
|
| 47 |
+
ACTIVIDADES = {41: "FRACCIONADOR DE GRANOS", 29: "ACOPIADOR - CONSIGNATARIO",
|
| 48 |
+
33: "CANJEADOR DE BIENES Y/O SERVICIOS POR GRANO",
|
| 49 |
+
40: "EXPORTADOR", 31: "ACOPIADOR DE MANΓ",
|
| 50 |
+
30: "ACOPIADOR DE LEGUMBRES",
|
| 51 |
+
35: "COMPRADOR DE GRANO PARA CONSUMO PROPIO",
|
| 52 |
+
44: "INDUSTRIAL ACEITERO", 47: "INDUSTRIAL BIOCOMBUSTIBLE",
|
| 53 |
+
46: "INDUSTRIAL BALANCEADOR", 48: "INDUSTRIAL CERVECERO",
|
| 54 |
+
49: "INDUSTRIAL DESTILERIA",
|
| 55 |
+
51: "INDUSTRIAL MOLINO DE HARINA DE TRIGO",
|
| 56 |
+
50: "INDUSTRIAL MOLINERO", 45: "INDUSTRIAL ARROCERO",
|
| 57 |
+
59: "USUARIO DE MOLIENDA DE TRIGO(incluye MAQUILA)",
|
| 58 |
+
57: "USUARIO DE INDUSTRIA (Otros granos MENOS trigo)",
|
| 59 |
+
52: "INDUSTRIAL SELECCIONADOR", 34: "COMPLEJO INDUSTRIAL",
|
| 60 |
+
28: "ACONDICIONADOR", 36: "CORREDOR",
|
| 61 |
+
55: "MERCADO DE FUTUROS Y OPCIONES O MERCADO A TERMINO",
|
| 62 |
+
39: "EXPLOTADOR DE DEPOSITO Y/O ELEVADOR DE GRANOS",
|
| 63 |
+
37: "DESMOTADOR DE ALGODON",
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
# Grados
|
| 67 |
+
GRADOS_REF = {'G3': 'Grado 3', 'G2': 'Grado 2', 'G1': 'Grado 1'}
|
| 68 |
+
|
| 69 |
+
# Datos de grado entregado por tipo de granos:
|
| 70 |
+
GRADO_ENT_VALOR = {
|
| 71 |
+
49: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 72 |
+
25: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 73 |
+
26: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 74 |
+
27: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 75 |
+
20: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 76 |
+
21: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 77 |
+
22: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 78 |
+
23: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 79 |
+
46: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 80 |
+
47: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 81 |
+
48: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 82 |
+
28: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 83 |
+
1: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 84 |
+
3: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 85 |
+
2: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 86 |
+
5: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 87 |
+
4: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 88 |
+
7: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 89 |
+
6: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 90 |
+
9: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 91 |
+
8: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 92 |
+
59: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 93 |
+
11: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 94 |
+
10: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 95 |
+
12: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 96 |
+
15: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.99'), 'G2': Decimal('1.00'), 'G1': Decimal('1.015'), 'FG': Decimal('0')},
|
| 97 |
+
14: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 98 |
+
17: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 99 |
+
16: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 100 |
+
19: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 101 |
+
18: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 102 |
+
31: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 103 |
+
30: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 104 |
+
50: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 105 |
+
35: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 106 |
+
34: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 107 |
+
33: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 108 |
+
32: {'F1': Decimal('0'), 'F2': Decimal('0'), 'F3': Decimal('0'), 'G3': Decimal('0.985'), 'G2': Decimal('1.00'), 'G1': Decimal('1.01'), 'FG': Decimal('0')},
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
# Diccionario de localidades por provincia
|
| 112 |
+
# (wslpg.py lo reemplaza con un shelve si es posible)
|
| 113 |
+
LOCALIDADES = {}
|
app/pyafipws/wslsp.py
ADDED
|
@@ -0,0 +1,1210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
import shelve
|
| 15 |
+
import sys
|
| 16 |
+
import datetime
|
| 17 |
+
import decimal
|
| 18 |
+
import os
|
| 19 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, BaseWS, inicializar_y_capturar_excepciones, get_install_dir
|
| 20 |
+
from . import utils
|
| 21 |
+
from fpdf import Template
|
| 22 |
+
from pysimplesoap.client import SoapFault
|
| 23 |
+
import pprint
|
| 24 |
+
import traceback
|
| 25 |
+
"""MΓ³dulo para obtener cΓ³digo de autorizaciΓ³n electrΓ³nica (CAE) para
|
| 26 |
+
LiquidaciΓ³n Sector Pecuario (hacienda/carne) del web service WSLSP de AFIP
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 30 |
+
__copyright__ = "Copyright (C) 2016 Mariano Reingart"
|
| 31 |
+
__license__ = "GPL 3.0"
|
| 32 |
+
__version__ = "1.07a"
|
| 33 |
+
|
| 34 |
+
LICENCIA = """
|
| 35 |
+
wslsp.py: Interfaz para generar CΓ³digo de AutorizaciΓ³n ElectrΓ³nica (CAE) para
|
| 36 |
+
LiquidaciΓ³n Sector Pecuario (LspService)
|
| 37 |
+
Copyright (C) 2016 Mariano Reingart reingart@gmail.com
|
| 38 |
+
http://www.sistemasagiles.com.ar/trac/wiki/LiquidacionSectorPecuario
|
| 39 |
+
|
| 40 |
+
Este progarma es software libre, se entrega ABSOLUTAMENTE SIN GARANTIA
|
| 41 |
+
y es bienvenido a redistribuirlo respetando la licencia GPLv3.
|
| 42 |
+
|
| 43 |
+
Para informaciΓ³n adicional sobre garantΓa, soporte tΓ©cnico comercial
|
| 44 |
+
e incorporaciΓ³n/distribuciΓ³n en programas propietarios ver PyAfipWs:
|
| 45 |
+
http://www.sistemasagiles.com.ar/trac/wiki/PyAfipWs
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
AYUDA = """
|
| 49 |
+
Opciones:
|
| 50 |
+
--ayuda: este mensaje
|
| 51 |
+
|
| 52 |
+
--debug: modo depuraciΓ³n (detalla y confirma las operaciones)
|
| 53 |
+
--formato: muestra el formato de los archivos de entrada/salida
|
| 54 |
+
--prueba: genera y autoriza una liquidaciΓ³n de prueba (no usar en producciΓ³n!)
|
| 55 |
+
--xml: almacena los requerimientos y respuestas XML (depuraciΓ³n)
|
| 56 |
+
--json: utilizar formato json para el archivo de intercambio
|
| 57 |
+
--dummy: consulta estado de servidores
|
| 58 |
+
|
| 59 |
+
--autorizar: Autorizar LiquidaciΓ³n Γnica (generarLiquidacion)
|
| 60 |
+
--ajustar: Ajuste FΓsico/Monetario/Financiero, Credito/Debito (generarAjuste)
|
| 61 |
+
--ult: Consulta el ΓΊltimo nΓΊmero de orden registrado en AFIP
|
| 62 |
+
(consultarUltimoComprobanteXPuntoVenta)
|
| 63 |
+
--consultar: Consulta una liquidaciΓ³n registrada en AFIP
|
| 64 |
+
(consultarLiquidacionXNroComprobante / consultarLiquidacionXCAE)
|
| 65 |
+
|
| 66 |
+
--provincias: obtiene el listado de provincias (cΓ³digo/descripciΓ³n)
|
| 67 |
+
--localidades: obtiene el listado de localidades para una provincia
|
| 68 |
+
--tributos: obtiene el listado de los tipos de tributos
|
| 69 |
+
--gastos: obtiene el listado de los tipos de gastos
|
| 70 |
+
--puntosventa: obtiene el listado de puntos de venta habilitados
|
| 71 |
+
|
| 72 |
+
Ver wslsp.ini para parΓ‘metros de configuraciΓ³n (URL, certificados, etc.)"
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# importo funciones compartidas:
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
WSDL = "https://fwshomo.afip.gov.ar/wslsp/LspService?wsdl"
|
| 80 |
+
#WSDL = "https://serviciosjava.afip.gov.ar/wslsp/LspService?wsdl"
|
| 81 |
+
|
| 82 |
+
DEBUG = False
|
| 83 |
+
XML = False
|
| 84 |
+
CONFIG_FILE = "wslsp.ini"
|
| 85 |
+
HOMO = False
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class WSLSP(BaseWS):
|
| 89 |
+
"Interfaz para el WebService de LiquidaciΓ³n Γnica Mensual (lecherΓa)"
|
| 90 |
+
_public_methods_ = ['Conectar', 'Dummy', 'SetTicketAcceso', 'DebugLog',
|
| 91 |
+
'AutorizarLiquidacion',
|
| 92 |
+
'CrearLiquidacion', 'AgregarFrigorifico',
|
| 93 |
+
'AgregarEmisor', 'AgregarReceptor', 'AgregarOperador',
|
| 94 |
+
'AgregarDTE',
|
| 95 |
+
'AgregarItemDetalle', 'AgregarCompraAsociada',
|
| 96 |
+
'AgregarGasto', 'AgregarTributo', 'AgregarGuia',
|
| 97 |
+
'ConsultarLiquidacion', 'ConsultarUltimoComprobante',
|
| 98 |
+
'CrearAjuste', 'AgregarComprobanteAAjustar',
|
| 99 |
+
'AgregarItemDetalleAjuste',
|
| 100 |
+
'AgregarAjusteMonetario', 'AgregarAjusteFisico',
|
| 101 |
+
'AgregarAjusteFinanciero',
|
| 102 |
+
'AjustarLiquidacion',
|
| 103 |
+
'LeerDatosLiquidacion',
|
| 104 |
+
'ConsultarOperaciones',
|
| 105 |
+
'ConsultarTiposComprobante',
|
| 106 |
+
'ConsultarTiposLiquidacion',
|
| 107 |
+
'ConsultarCategorias', 'ConsultarMotivos',
|
| 108 |
+
'ConsultarRazas', 'ConsultarCortes',
|
| 109 |
+
'ConsultarCaracteresParticipante',
|
| 110 |
+
'ConsultarGastos', 'ConsultarTributos',
|
| 111 |
+
'ConsultarPuntosVentas',
|
| 112 |
+
'ConsultarProvincias', 'ConsultarLocalidades',
|
| 113 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 114 |
+
'SetParametros', 'SetParametro', 'GetParametro',
|
| 115 |
+
]
|
| 116 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 117 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 118 |
+
'Excepcion', 'ErrCode', 'ErrMsg', 'LanzarExcepciones', 'Errores',
|
| 119 |
+
'XmlRequest', 'XmlResponse', 'Version', 'Traceback', 'InstallDir',
|
| 120 |
+
'CAE', 'NroComprobante', 'FechaComprobante',
|
| 121 |
+
'NroCodigoBarras', 'FechaVencimientoCae', 'FechaProcesoAFIP',
|
| 122 |
+
'ImporteBruto', 'ImporteIVASobreBruto', '',
|
| 123 |
+
'ImporteTotalGastos', 'ImporteIVASobreGastos',
|
| 124 |
+
'ImporteTotalTributos', 'ImporteTotalNeto',
|
| 125 |
+
]
|
| 126 |
+
_reg_progid_ = "WSLSP"
|
| 127 |
+
_reg_clsid_ = "{9750BBD4-FBC3-4FE7-8DE5-E193667D6813}"
|
| 128 |
+
|
| 129 |
+
# Variables globales para BaseWS:
|
| 130 |
+
HOMO = HOMO
|
| 131 |
+
WSDL = WSDL
|
| 132 |
+
LanzarExcepciones = False
|
| 133 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciΓ³n' or '')
|
| 134 |
+
|
| 135 |
+
def inicializar(self):
|
| 136 |
+
BaseWS.inicializar(self)
|
| 137 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 138 |
+
self.errores = []
|
| 139 |
+
self.CAE = ""
|
| 140 |
+
self.NroComprobante = self.FechaComprobante = self.FechaProcesoAFIP = ''
|
| 141 |
+
self.NroCodigoBarras = self.FechaVencimientoCae = None
|
| 142 |
+
self.ImporteBruto = self.ImporteIVASobreBruto = None
|
| 143 |
+
self.ImporteTotalGastos = self.ImporteIVASobreGastos = None
|
| 144 |
+
self.ImporteTotalTributos = self.ImporteTotalNeto = None
|
| 145 |
+
self.datos = {}
|
| 146 |
+
|
| 147 |
+
@inicializar_y_capturar_excepciones
|
| 148 |
+
def Conectar(self, cache=None, url="", proxy="", wrapper="", cacert=None, timeout=60):
|
| 149 |
+
"Establecer la conexiΓ³n a los servidores de la AFIP"
|
| 150 |
+
# llamo al constructor heredado:
|
| 151 |
+
ok = BaseWS.Conectar(self, cache, url, proxy, wrapper, cacert, timeout)
|
| 152 |
+
if False and ok:
|
| 153 |
+
# corrijo ubicaciΓ³n del servidor (puerto htttp 80 en el WSDL)
|
| 154 |
+
location = self.client.services['LspService']['ports']['LumEndPoint']['location']
|
| 155 |
+
if location.startswith("http://"):
|
| 156 |
+
print("Corrigiendo WSDL ...", location, end=' ')
|
| 157 |
+
location = location.replace("http://", "https://").replace(":80", ":443")
|
| 158 |
+
self.client.services['LspService']['ports']['LspEndPoint']['location'] = location
|
| 159 |
+
print(location)
|
| 160 |
+
return ok
|
| 161 |
+
|
| 162 |
+
def __analizar_errores(self, ret):
|
| 163 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 164 |
+
errores = []
|
| 165 |
+
if 'errores' in ret:
|
| 166 |
+
errores.extend(ret['errores'])
|
| 167 |
+
if errores:
|
| 168 |
+
self.Errores = ["%(codigo)s: %(descripcion)s" % err['error'][0]
|
| 169 |
+
for err in errores]
|
| 170 |
+
self.errores = [
|
| 171 |
+
{'codigo': err['error'][0]['codigo'],
|
| 172 |
+
'descripcion': err['error'][0]['descripcion'].replace("\n", "")
|
| 173 |
+
.replace("\r", "")}
|
| 174 |
+
for err in errores]
|
| 175 |
+
self.ErrCode = ' '.join(self.Errores)
|
| 176 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 177 |
+
|
| 178 |
+
@inicializar_y_capturar_excepciones
|
| 179 |
+
def Dummy(self):
|
| 180 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 181 |
+
results = self.client.dummy()['respuesta']
|
| 182 |
+
self.AppServerStatus = str(results['appserver'])
|
| 183 |
+
self.DbServerStatus = str(results['dbserver'])
|
| 184 |
+
self.AuthServerStatus = str(results['authserver'])
|
| 185 |
+
return True
|
| 186 |
+
|
| 187 |
+
@inicializar_y_capturar_excepciones
|
| 188 |
+
def CrearLiquidacion(self, cod_operacion, fecha_cbte, fecha_op, cod_motivo,
|
| 189 |
+
cod_localidad_procedencia, cod_provincia_procedencia,
|
| 190 |
+
cod_localidad_destino, cod_provincia_destino,
|
| 191 |
+
lugar_realizacion=None,
|
| 192 |
+
fecha_recepcion=None, fecha_faena=None,
|
| 193 |
+
datos_adicionales=None, **kwargs):
|
| 194 |
+
"Inicializa internamente los datos de una liquidaciΓ³n para autorizar"
|
| 195 |
+
# creo el diccionario con los campos generales de la liquidaciΓ³n:
|
| 196 |
+
liq = {'fechaComprobante': fecha_cbte, 'fechaOperacion': fecha_op,
|
| 197 |
+
'lugarRealizacion': lugar_realizacion,
|
| 198 |
+
'codMotivo': cod_motivo,
|
| 199 |
+
'codLocalidadProcedencia': cod_localidad_procedencia,
|
| 200 |
+
'codProvinciaProcedencia': cod_provincia_procedencia,
|
| 201 |
+
'codLocalidadDestino': cod_localidad_destino,
|
| 202 |
+
'codProvinciaDestino': cod_provincia_destino,
|
| 203 |
+
'fechaRecepcion': fecha_recepcion,
|
| 204 |
+
'fechaFaena': fecha_faena,
|
| 205 |
+
}
|
| 206 |
+
self.solicitud = dict(codOperacion=cod_operacion,
|
| 207 |
+
emisor={}, receptor={},
|
| 208 |
+
datosLiquidacion=liq,
|
| 209 |
+
itemDetalleLiquidacion=[],
|
| 210 |
+
guia=[], dte=[],
|
| 211 |
+
tributo=[], gasto=[],
|
| 212 |
+
datosAdicionales=datos_adicionales,
|
| 213 |
+
)
|
| 214 |
+
return True
|
| 215 |
+
|
| 216 |
+
@inicializar_y_capturar_excepciones
|
| 217 |
+
def AgregarFrigorifico(self, cuit, nro_planta):
|
| 218 |
+
"Agrego el frigorifico a la liquidacΓon (opcional)."
|
| 219 |
+
frig = {'cuit': cuit, 'nroPlanta': nro_planta}
|
| 220 |
+
self.solicitud['datosLiquidacion']['frigorifico'] = frig
|
| 221 |
+
return True
|
| 222 |
+
|
| 223 |
+
@inicializar_y_capturar_excepciones
|
| 224 |
+
def AgregarEmisor(self, tipo_cbte, pto_vta, nro_cbte, cod_caracter=None,
|
| 225 |
+
fecha_inicio_act=None, iibb=None, nro_ruca=None,
|
| 226 |
+
nro_renspa=None, cuit_autorizado=None, **kwargs):
|
| 227 |
+
"Agrego los datos del emisor a la liq."
|
| 228 |
+
# cod_caracter y fecha_inicio_act no es requerido para ajustes
|
| 229 |
+
d = {'tipoComprobante': tipo_cbte, 'puntoVenta': pto_vta,
|
| 230 |
+
'nroComprobante': nro_cbte,
|
| 231 |
+
'codCaracter': cod_caracter,
|
| 232 |
+
'fechaInicioActividades': fecha_inicio_act,
|
| 233 |
+
'iibb': iibb,
|
| 234 |
+
'nroRUCA': nro_ruca,
|
| 235 |
+
'nroRenspa': nro_renspa,
|
| 236 |
+
'cuitAutorizado': cuit_autorizado}
|
| 237 |
+
self.solicitud['emisor'].update(d)
|
| 238 |
+
return True
|
| 239 |
+
|
| 240 |
+
@inicializar_y_capturar_excepciones
|
| 241 |
+
def AgregarReceptor(self, cod_caracter, **kwargs):
|
| 242 |
+
"Agrego los datos del receptor a la liq."
|
| 243 |
+
d = {'codCaracter': cod_caracter}
|
| 244 |
+
self.solicitud['receptor'].update(d)
|
| 245 |
+
return True
|
| 246 |
+
|
| 247 |
+
@inicializar_y_capturar_excepciones
|
| 248 |
+
def AgregarOperador(self, cuit, iibb=None, nro_ruca=None, nro_renspa=None,
|
| 249 |
+
cuit_autorizado=None, **kwargs):
|
| 250 |
+
"Agrego los datos del operador a la liq."
|
| 251 |
+
d = {'cuit': cuit,
|
| 252 |
+
'iibb': iibb,
|
| 253 |
+
'nroRUCA': nro_ruca,
|
| 254 |
+
'nroRenspa': nro_renspa,
|
| 255 |
+
'cuitAutorizado': cuit_autorizado}
|
| 256 |
+
self.solicitud['receptor']['operador'] = d
|
| 257 |
+
return True
|
| 258 |
+
|
| 259 |
+
@inicializar_y_capturar_excepciones
|
| 260 |
+
def AgregarItemDetalle(self, cuit_cliente, cod_categoria, tipo_liquidacion,
|
| 261 |
+
cantidad, precio_unitario, alicuota_iva, cod_raza,
|
| 262 |
+
cantidad_cabezas=None, nro_tropa=None,
|
| 263 |
+
cod_corte=None, cantidad_kg_vivo=None,
|
| 264 |
+
precio_recupero=None, detalle_raza=None,
|
| 265 |
+
nro_item=None,
|
| 266 |
+
**kwargs):
|
| 267 |
+
"Agrega el detalle de item de la liquidaciΓ³n"
|
| 268 |
+
d = {'cuitCliente': cuit_cliente,
|
| 269 |
+
'codCategoria': cod_categoria,
|
| 270 |
+
'tipoLiquidacion': tipo_liquidacion,
|
| 271 |
+
'cantidad': cantidad,
|
| 272 |
+
'precioUnitario': precio_unitario,
|
| 273 |
+
'alicuotaIVA': alicuota_iva,
|
| 274 |
+
'cantidadCabezas': cantidad_cabezas,
|
| 275 |
+
'raza': {'codRaza': cod_raza, 'detalle': detalle_raza},
|
| 276 |
+
'nroTropa': nro_tropa,
|
| 277 |
+
'codCorte': cod_corte,
|
| 278 |
+
'cantidadKgVivo': cantidad_kg_vivo,
|
| 279 |
+
'precioRecupero': precio_recupero,
|
| 280 |
+
'liquidacionCompraAsociada': [],
|
| 281 |
+
'nroItem': nro_item,
|
| 282 |
+
}
|
| 283 |
+
self.solicitud['itemDetalleLiquidacion'].append(d)
|
| 284 |
+
return True
|
| 285 |
+
|
| 286 |
+
@inicializar_y_capturar_excepciones
|
| 287 |
+
def AgregarCompraAsociada(self, tipo_cbte, pto_vta, nro_cbte, cant_asoc, nro_item):
|
| 288 |
+
"Agrega la informaciΓ³n referente a la liquidaciΓ³n compra asociada"
|
| 289 |
+
d = {'tipoComprobante': tipo_cbte,
|
| 290 |
+
'puntoVenta': pto_vta,
|
| 291 |
+
'nroComprobante': nro_cbte,
|
| 292 |
+
'nroItem': nro_item,
|
| 293 |
+
'cantidadAsociada': cant_asoc}
|
| 294 |
+
if 'itemDetalleLiquidacion' in self.solicitud:
|
| 295 |
+
item_liq = self.solicitud['itemDetalleLiquidacion'][-1]
|
| 296 |
+
item_liq['liquidacionCompraAsociada'].append(d)
|
| 297 |
+
else:
|
| 298 |
+
item_liq = self.solicitud['itemDetalleAjusteLiquidacion'][-1]
|
| 299 |
+
item_liq['ajusteCompraAsociada'].append(d)
|
| 300 |
+
return True
|
| 301 |
+
|
| 302 |
+
@inicializar_y_capturar_excepciones
|
| 303 |
+
def AgregarGasto(self, cod_gasto, descripcion=None, base_imponible=None,
|
| 304 |
+
alicuota=None, importe=None, alicuota_iva=None,
|
| 305 |
+
tipo_iva_nulo=None):
|
| 306 |
+
"Agrega la informaciΓ³n referente a los gastos de la liquidaciΓ³n"
|
| 307 |
+
# WSLSPv1.4.1: tipo_iva_nulo debe ser NG, NA, EX: Exento.
|
| 308 |
+
if alicuota_iva == 0:
|
| 309 |
+
alicuota_iva = None # sΓ³lo acepta [10.5, 21.0]
|
| 310 |
+
elif alicuota_iva:
|
| 311 |
+
tipo_iva_nulo = None
|
| 312 |
+
gasto = {'codGasto': cod_gasto, 'descripcion': descripcion,
|
| 313 |
+
'baseImponible': base_imponible, 'alicuota': alicuota,
|
| 314 |
+
'importe': importe, 'alicuotaIVA': alicuota_iva,
|
| 315 |
+
'tipoIVANulo': tipo_iva_nulo,
|
| 316 |
+
}
|
| 317 |
+
if 'ajusteFinanciero' in self.solicitud:
|
| 318 |
+
self.solicitud['ajusteFinanciero']['gasto'].append(gasto)
|
| 319 |
+
else:
|
| 320 |
+
self.solicitud['gasto'].append(gasto)
|
| 321 |
+
return True
|
| 322 |
+
|
| 323 |
+
@inicializar_y_capturar_excepciones
|
| 324 |
+
def AgregarTributo(self, cod_tributo, descripcion=None,
|
| 325 |
+
base_imponible=None, alicuota=None, importe=None):
|
| 326 |
+
"Agrega la informaciΓ³n referente a los tributos de la liquidaciΓ³n"
|
| 327 |
+
trib = {'codTributo': cod_tributo, 'descripcion': descripcion,
|
| 328 |
+
'baseImponible': base_imponible, 'alicuota': alicuota,
|
| 329 |
+
'importe': importe}
|
| 330 |
+
if 'ajusteFinanciero' in self.solicitud:
|
| 331 |
+
self.solicitud['ajusteFinanciero']['tributo'].append(trib)
|
| 332 |
+
else:
|
| 333 |
+
self.solicitud['tributo'].append(trib)
|
| 334 |
+
return True
|
| 335 |
+
|
| 336 |
+
@inicializar_y_capturar_excepciones
|
| 337 |
+
def AgregarDTE(self, nro_dte, nro_renspa=None):
|
| 338 |
+
"Agrega la informaciΓ³n referente a DTE (multiples)"
|
| 339 |
+
d = {"nroDTE": nro_dte, "nroRenspa": nro_renspa}
|
| 340 |
+
self.solicitud['dte'].append(d)
|
| 341 |
+
return True
|
| 342 |
+
|
| 343 |
+
@inicializar_y_capturar_excepciones
|
| 344 |
+
def AgregarGuia(self, nro_guia):
|
| 345 |
+
"Agrega la informaciΓ³n referente a las guΓas (multiples)"
|
| 346 |
+
self.solicitud['guia'].append({"nroGuia": nro_guia})
|
| 347 |
+
return True
|
| 348 |
+
|
| 349 |
+
@inicializar_y_capturar_excepciones
|
| 350 |
+
def AutorizarLiquidacion(self):
|
| 351 |
+
"Generar o ajustar una liquidaciΓ³n ΓΊnica y obtener del CAE"
|
| 352 |
+
# limpio los elementos que no correspondan por estar vacios:
|
| 353 |
+
for campo in ["guia", "dte", "gasto", "tributo"]:
|
| 354 |
+
if campo in self.solicitud and not self.solicitud[campo]:
|
| 355 |
+
del self.solicitud[campo]
|
| 356 |
+
for item in self.solicitud['itemDetalleLiquidacion']:
|
| 357 |
+
if not item.get("liquidacionCompraAsociada", True):
|
| 358 |
+
del item["liquidacionCompraAsociada"]
|
| 359 |
+
# llamo al webservice:
|
| 360 |
+
ret = self.client.generarLiquidacion(
|
| 361 |
+
auth={
|
| 362 |
+
'token': self.Token, 'sign': self.Sign,
|
| 363 |
+
'cuit': self.Cuit, },
|
| 364 |
+
solicitud=self.solicitud,
|
| 365 |
+
)
|
| 366 |
+
# analizo la respusta
|
| 367 |
+
ret = ret['respuesta']
|
| 368 |
+
self.__analizar_errores(ret)
|
| 369 |
+
self.AnalizarLiquidacion(ret)
|
| 370 |
+
return True
|
| 371 |
+
|
| 372 |
+
def AnalizarLiquidacion(self, liq):
|
| 373 |
+
"MΓ©todo interno para analizar la respuesta de AFIP"
|
| 374 |
+
# proceso los datos bΓ‘sicos de la liquidaciΓ³n (devuelto por consultar):
|
| 375 |
+
cab = liq.get('cabecera')
|
| 376 |
+
if cab:
|
| 377 |
+
self.CAE = str(cab.get('cae', ''))
|
| 378 |
+
self.FechaVencimientoCae = str(cab.get('fechaVencimientoCae', ''))
|
| 379 |
+
self.FechaProcesoAFIP = str(cab.get('fechaProcesoAFIP', ''))
|
| 380 |
+
self.NroCodigoBarras = cab.get('nroCodigoBarras')
|
| 381 |
+
datos = liq.get('datosLiquidacion', {})
|
| 382 |
+
self.FechaComprobante = str(datos.get('fechaComprobante', ''))
|
| 383 |
+
emisor = liq.get('emisor', {})
|
| 384 |
+
self.NroComprobante = emisor.get('nroComprobante')
|
| 385 |
+
tot = liq.get('resumenTotales', {})
|
| 386 |
+
self.ImporteBruto = tot.get('importeBruto')
|
| 387 |
+
self.ImporteTotalGastos = tot.get('importeTotalGastos')
|
| 388 |
+
self.ImporteTotalTributos = tot.get('importeTotalTributos')
|
| 389 |
+
self.ImporteTotalNeto = tot.get('importeTotalNeto')
|
| 390 |
+
self.ImporteIVASobreBruto = tot.get('importeIVASobreBruto')
|
| 391 |
+
self.ImporteIVASobreGastos = tot.get('importeIVASobreGastos')
|
| 392 |
+
receptor = liq.get('receptor', {})
|
| 393 |
+
|
| 394 |
+
# parΓ‘metros de salida:
|
| 395 |
+
self.params_out = dict(
|
| 396 |
+
tipo_cbte=emisor.get('tipoComprobante'),
|
| 397 |
+
pto_vta=emisor.get('puntoVenta'),
|
| 398 |
+
nro_cbte=emisor.get('nroComprobante'),
|
| 399 |
+
fecha=datos.get('fechaComprobante'),
|
| 400 |
+
cae=cab.get('cae'),
|
| 401 |
+
emisor=dict(
|
| 402 |
+
razon_social=emisor.get('razonSocial'),
|
| 403 |
+
domicilio_punto_venta=emisor.get('domicilioPuntoVenta'),
|
| 404 |
+
),
|
| 405 |
+
receptor=dict(
|
| 406 |
+
nombre=receptor.get('nombre'),
|
| 407 |
+
domicilio=receptor.get('domicilio'),
|
| 408 |
+
),
|
| 409 |
+
bruto=tot.get('importeBruto'),
|
| 410 |
+
iva_bruto=tot.get('importeIVASobreBruto'),
|
| 411 |
+
iva_gastos=tot.get('importeIVASobreGastos'),
|
| 412 |
+
total_neto=tot.get('importeTotalNeto'),
|
| 413 |
+
total_tributos=tot.get('importeTotalTributos'),
|
| 414 |
+
total_gastos=tot.get('importeTotalGastos'),
|
| 415 |
+
gasto=[],
|
| 416 |
+
guia=[],
|
| 417 |
+
tributo=[],
|
| 418 |
+
pdf=liq.get('pdf'),
|
| 419 |
+
)
|
| 420 |
+
for ret in liq.get('gasto', []):
|
| 421 |
+
self.params_out['gasto'].append(dict(
|
| 422 |
+
cod_gasto=ret['codGasto'],
|
| 423 |
+
importe=ret['importe'],
|
| 424 |
+
))
|
| 425 |
+
for trib in liq.get('tributo', []):
|
| 426 |
+
self.params_out['tributo'].append(dict(
|
| 427 |
+
descripcion=trib.get('descripcion', ""),
|
| 428 |
+
base_imponible=trib.get('baseImponible'),
|
| 429 |
+
alicuota=trib.get('alicuota'),
|
| 430 |
+
codigo=trib['codTributo'],
|
| 431 |
+
importe=trib['importe'],
|
| 432 |
+
))
|
| 433 |
+
# analizar datos del ajuste generado:
|
| 434 |
+
ajuste = liq.get('ajuste', {})
|
| 435 |
+
if ajuste:
|
| 436 |
+
self.params_out.update(dict(
|
| 437 |
+
tipo_ajuste=ajuste['tipoAjuste'],
|
| 438 |
+
modo_ajuste=ajuste['modoAjuste'],
|
| 439 |
+
))
|
| 440 |
+
ajustado = ajuste.get('comprobanteAjustado')
|
| 441 |
+
if ajustado:
|
| 442 |
+
self.params_out.update(dict(
|
| 443 |
+
cbte_ajuste=dict(
|
| 444 |
+
tipo_cbte=ajustado['tipoComprobante'],
|
| 445 |
+
pto_vta=ajustado['puntoVenta'],
|
| 446 |
+
nro_cbte=ajustado['nroComprobante'],
|
| 447 |
+
)
|
| 448 |
+
))
|
| 449 |
+
if DEBUG:
|
| 450 |
+
import pprint
|
| 451 |
+
pprint.pprint(self.params_out)
|
| 452 |
+
self.params_out['errores'] = self.errores
|
| 453 |
+
|
| 454 |
+
@inicializar_y_capturar_excepciones
|
| 455 |
+
def ConsultarLiquidacion(self, tipo_cbte=None, pto_vta=None, nro_cbte=None,
|
| 456 |
+
cae=None, cuit_comprador=None, pdf="liq.pdf"):
|
| 457 |
+
"Consulta una liquidaciΓ³n por No de Comprobante o CAE"
|
| 458 |
+
if cae:
|
| 459 |
+
ret = self.client.consultarLiquidacionPorCae(
|
| 460 |
+
auth={
|
| 461 |
+
'token': self.Token, 'sign': self.Sign,
|
| 462 |
+
'cuit': self.Cuit, },
|
| 463 |
+
solicitud={
|
| 464 |
+
'cae': cae,
|
| 465 |
+
'pdf': pdf and True or False,
|
| 466 |
+
},
|
| 467 |
+
)
|
| 468 |
+
else:
|
| 469 |
+
ret = self.client.consultarLiquidacionPorNroComprobante(
|
| 470 |
+
auth={
|
| 471 |
+
'token': self.Token, 'sign': self.Sign,
|
| 472 |
+
'cuit': self.Cuit, },
|
| 473 |
+
solicitud={
|
| 474 |
+
'cuitComprador': cuit_comprador,
|
| 475 |
+
'puntoVenta': pto_vta,
|
| 476 |
+
'nroComprobante': nro_cbte,
|
| 477 |
+
'tipoComprobante': tipo_cbte,
|
| 478 |
+
'pdf': pdf and True or False,
|
| 479 |
+
},
|
| 480 |
+
)
|
| 481 |
+
ret = ret['respuesta']
|
| 482 |
+
self.__analizar_errores(ret)
|
| 483 |
+
self.AnalizarLiquidacion(ret)
|
| 484 |
+
# guardo el PDF si se indico archivo y vino en la respuesta:
|
| 485 |
+
if pdf and 'pdf' in ret:
|
| 486 |
+
open(pdf, "wb").write(ret['pdf'])
|
| 487 |
+
return True
|
| 488 |
+
|
| 489 |
+
@inicializar_y_capturar_excepciones
|
| 490 |
+
def ConsultarUltimoComprobante(self, tipo_cbte=151, pto_vta=1):
|
| 491 |
+
"Consulta el ΓΊltimo No de Comprobante registrado"
|
| 492 |
+
ret = self.client.consultarUltimoNroComprobantePorPtoVta(
|
| 493 |
+
auth={
|
| 494 |
+
'token': self.Token, 'sign': self.Sign,
|
| 495 |
+
'cuit': self.Cuit, },
|
| 496 |
+
solicitud={
|
| 497 |
+
'puntoVenta': pto_vta,
|
| 498 |
+
'tipoComprobante': tipo_cbte},
|
| 499 |
+
)
|
| 500 |
+
ret = ret['respuesta']
|
| 501 |
+
self.__analizar_errores(ret)
|
| 502 |
+
self.NroComprobante = ret['nroComprobante']
|
| 503 |
+
return True
|
| 504 |
+
|
| 505 |
+
@inicializar_y_capturar_excepciones
|
| 506 |
+
def CrearAjuste(self, tipo_ajuste, fecha_cbte, datos_adicionales=None, **kwargs):
|
| 507 |
+
"Inicializa internamente los datos de una liquidaciΓ³n para ajustar"
|
| 508 |
+
# creo el diccionario con los campos generales de la liquidaciΓ³n:
|
| 509 |
+
self.solicitud = dict(tipoAjuste=tipo_ajuste,
|
| 510 |
+
fechaComprobante=fecha_cbte,
|
| 511 |
+
emisor={},
|
| 512 |
+
itemDetalleAjusteLiquidacion=[],
|
| 513 |
+
ajusteFinanciero={},
|
| 514 |
+
datosAdicionales=datos_adicionales,
|
| 515 |
+
)
|
| 516 |
+
return True
|
| 517 |
+
|
| 518 |
+
@inicializar_y_capturar_excepciones
|
| 519 |
+
def AgregarComprobanteAAjustar(self, tipo_cbte, pto_vta, nro_cbte):
|
| 520 |
+
"Agrega comprobante a ajustar"
|
| 521 |
+
cbte = dict(tipoComprobante=tipo_cbte, puntoVenta=pto_vta, nroComprobante=nro_cbte)
|
| 522 |
+
self.solicitud['emisor']["comprobanteAAjustar"] = cbte
|
| 523 |
+
return True
|
| 524 |
+
|
| 525 |
+
@inicializar_y_capturar_excepciones
|
| 526 |
+
def AgregarItemDetalleAjuste(self, nro_item_ajustar, **kwargs):
|
| 527 |
+
"Agrega el detalle de item a un ajuste de liquidaciΓ³n"
|
| 528 |
+
d = {'nroItemAjustar': nro_item_ajustar, 'ajusteCompraAsociada': []}
|
| 529 |
+
self.solicitud['itemDetalleAjusteLiquidacion'].append(d)
|
| 530 |
+
return True
|
| 531 |
+
|
| 532 |
+
@inicializar_y_capturar_excepciones
|
| 533 |
+
def AgregarAjusteFisico(self, cantidad, cantidad_cabezas=None,
|
| 534 |
+
cantidad_kg_vivo=None, **kwargs):
|
| 535 |
+
"Agrega campos al detalle de item por un ajuste fisico"
|
| 536 |
+
d = {'cantidad': cantidad,
|
| 537 |
+
'cantidadCabezas': cantidad_cabezas,
|
| 538 |
+
'cantidadKgVivo': cantidad_kg_vivo,
|
| 539 |
+
}
|
| 540 |
+
item_liq = self.solicitud['itemDetalleAjusteLiquidacion'][-1]
|
| 541 |
+
item_liq['ajusteFisico'] = d
|
| 542 |
+
return True
|
| 543 |
+
|
| 544 |
+
@inicializar_y_capturar_excepciones
|
| 545 |
+
def AgregarAjusteMonetario(self, precio_unitario, precio_recupero=None,
|
| 546 |
+
**kwargs):
|
| 547 |
+
"Agrega campos al detalle de item por un ajuste monetario"
|
| 548 |
+
d = {'precioUnitario': precio_unitario,
|
| 549 |
+
'precioRecupero': precio_recupero,
|
| 550 |
+
}
|
| 551 |
+
item_liq = self.solicitud['itemDetalleAjusteLiquidacion'][-1]
|
| 552 |
+
item_liq['ajusteMonetario'] = d
|
| 553 |
+
return True
|
| 554 |
+
|
| 555 |
+
@inicializar_y_capturar_excepciones
|
| 556 |
+
def AgregarAjusteFinanciero(self, **kwargs):
|
| 557 |
+
"Prepara el detalle de item por un ajuste financiero (gastos/tributos)"
|
| 558 |
+
self.solicitud['ajusteFinanciero'] = {'gasto': [], 'tributo': []}
|
| 559 |
+
return True
|
| 560 |
+
|
| 561 |
+
@inicializar_y_capturar_excepciones
|
| 562 |
+
def AjustarLiquidacion(self):
|
| 563 |
+
"Generar y ajustar una liquidaciΓ³n para obtener del CAE"
|
| 564 |
+
# limpio los elementos que no correspondan por estar vacios:
|
| 565 |
+
for item_liq in self.solicitud['itemDetalleAjusteLiquidacion']:
|
| 566 |
+
campo = 'ajusteCompraAsociada'
|
| 567 |
+
if campo in item_liq and not item_liq[campo]:
|
| 568 |
+
del item_liq[campo]
|
| 569 |
+
for campo in self.solicitud.get('ajusteFinanciero', {}).copy():
|
| 570 |
+
if not self.solicitud['ajusteFinanciero'][campo]:
|
| 571 |
+
del self.solicitud['ajusteFinanciero'][campo]
|
| 572 |
+
# llamo al webservice:
|
| 573 |
+
ret = self.client.generarAjuste(
|
| 574 |
+
auth={
|
| 575 |
+
'token': self.Token, 'sign': self.Sign,
|
| 576 |
+
'cuit': self.Cuit, },
|
| 577 |
+
solicitud=self.solicitud,
|
| 578 |
+
)
|
| 579 |
+
# analizo la respusta
|
| 580 |
+
ret = ret['respuesta']
|
| 581 |
+
self.__analizar_errores(ret)
|
| 582 |
+
self.AnalizarLiquidacion(ret)
|
| 583 |
+
return True
|
| 584 |
+
|
| 585 |
+
@inicializar_y_capturar_excepciones
|
| 586 |
+
def ConsultarProvincias(self, sep="||"):
|
| 587 |
+
"Consulta las provincias habilitadas"
|
| 588 |
+
ret = self.client.consultarProvincias(
|
| 589 |
+
auth={
|
| 590 |
+
'token': self.Token, 'sign': self.Sign,
|
| 591 |
+
'cuit': self.Cuit, },
|
| 592 |
+
)['respuesta']
|
| 593 |
+
self.__analizar_errores(ret)
|
| 594 |
+
array = ret.get('provincia', [])
|
| 595 |
+
if sep is None:
|
| 596 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 597 |
+
else:
|
| 598 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 599 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 600 |
+
|
| 601 |
+
@inicializar_y_capturar_excepciones
|
| 602 |
+
def ConsultarLocalidades(self, cod_provincia, sep="||"):
|
| 603 |
+
"Consulta las localidades habilitadas"
|
| 604 |
+
ret = self.client.consultarLocalidadesPorProvincia(
|
| 605 |
+
auth={
|
| 606 |
+
'token': self.Token, 'sign': self.Sign,
|
| 607 |
+
'cuit': self.Cuit, },
|
| 608 |
+
solicitud={'codProvincia': cod_provincia},
|
| 609 |
+
)['respuesta']
|
| 610 |
+
self.__analizar_errores(ret)
|
| 611 |
+
array = ret.get('localidad', [])
|
| 612 |
+
if sep is None:
|
| 613 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 614 |
+
else:
|
| 615 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 616 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 617 |
+
|
| 618 |
+
@inicializar_y_capturar_excepciones
|
| 619 |
+
def ConsultarOperaciones(self, sep="||"):
|
| 620 |
+
"Retorna un listado de cΓ³digo y descripciΓ³n de operaciones permitidas"
|
| 621 |
+
ret = self.client.consultarOperaciones(
|
| 622 |
+
auth={
|
| 623 |
+
'token': self.Token, 'sign': self.Sign,
|
| 624 |
+
'cuit': self.Cuit, },
|
| 625 |
+
)['respuesta']
|
| 626 |
+
self.__analizar_errores(ret)
|
| 627 |
+
array = ret.get('operacion', []) + ret.get('operacionPorcina', [])
|
| 628 |
+
if sep is None:
|
| 629 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 630 |
+
else:
|
| 631 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 632 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 633 |
+
|
| 634 |
+
@inicializar_y_capturar_excepciones
|
| 635 |
+
def ConsultarTributos(self, sep="||"):
|
| 636 |
+
"Retorna un listado de tributos con cΓ³digo, descripciΓ³n y signo."
|
| 637 |
+
ret = self.client.consultarTributos(
|
| 638 |
+
auth={
|
| 639 |
+
'token': self.Token, 'sign': self.Sign,
|
| 640 |
+
'cuit': self.Cuit, },
|
| 641 |
+
)['respuesta']
|
| 642 |
+
self.__analizar_errores(ret)
|
| 643 |
+
array = ret.get('tributo', []) + ret.get('tributoPorcino', [])
|
| 644 |
+
if sep is None:
|
| 645 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 646 |
+
else:
|
| 647 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 648 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 649 |
+
|
| 650 |
+
@inicializar_y_capturar_excepciones
|
| 651 |
+
def ConsultarGastos(self, sep="||"):
|
| 652 |
+
"Retorna un listado de gastos con cΓ³digo y descripciΓ³n"
|
| 653 |
+
ret = self.client.consultarGastos(
|
| 654 |
+
auth={
|
| 655 |
+
'token': self.Token, 'sign': self.Sign,
|
| 656 |
+
'cuit': self.Cuit, },
|
| 657 |
+
)['respuesta']
|
| 658 |
+
self.__analizar_errores(ret)
|
| 659 |
+
array = ret.get('gasto', [])
|
| 660 |
+
if sep is None:
|
| 661 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 662 |
+
else:
|
| 663 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 664 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 665 |
+
|
| 666 |
+
@inicializar_y_capturar_excepciones
|
| 667 |
+
def ConsultarTiposComprobante(self, sep="||"):
|
| 668 |
+
"Retorna un listado de tipos de comprobantes con cΓ³digo y descripciΓ³n"
|
| 669 |
+
ret = self.client.consultarTiposComprobante(
|
| 670 |
+
auth={
|
| 671 |
+
'token': self.Token, 'sign': self.Sign,
|
| 672 |
+
'cuit': self.Cuit, },
|
| 673 |
+
)['respuesta']
|
| 674 |
+
self.__analizar_errores(ret)
|
| 675 |
+
array = ret.get('tipoComprobante', [])
|
| 676 |
+
if sep is None:
|
| 677 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 678 |
+
else:
|
| 679 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 680 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 681 |
+
|
| 682 |
+
@inicializar_y_capturar_excepciones
|
| 683 |
+
def ConsultarTiposLiquidacion(self, sep="||"):
|
| 684 |
+
"Retorna un listado de tipos de liquidaciΓ³n con cΓ³digo y descripciΓ³n"
|
| 685 |
+
ret = self.client.consultarTiposLiquidacion(
|
| 686 |
+
auth={
|
| 687 |
+
'token': self.Token, 'sign': self.Sign,
|
| 688 |
+
'cuit': self.Cuit, },
|
| 689 |
+
)['respuesta']
|
| 690 |
+
self.__analizar_errores(ret)
|
| 691 |
+
array = ret.get('tipoLiquidacion', [])
|
| 692 |
+
if sep is None:
|
| 693 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 694 |
+
else:
|
| 695 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 696 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 697 |
+
|
| 698 |
+
@inicializar_y_capturar_excepciones
|
| 699 |
+
def ConsultarCaracteres(self, sep="||"):
|
| 700 |
+
"Retorna listado de caracteres emisor/receptor (cΓ³digo, descripciΓ³n)"
|
| 701 |
+
ret = self.client.consultarCaracteresParticipante(
|
| 702 |
+
auth={
|
| 703 |
+
'token': self.Token, 'sign': self.Sign,
|
| 704 |
+
'cuit': self.Cuit, },
|
| 705 |
+
)['respuesta']
|
| 706 |
+
self.__analizar_errores(ret)
|
| 707 |
+
array = ret.get('caracter', []) + ret.get('caracterPorcino', [])
|
| 708 |
+
if sep is None:
|
| 709 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 710 |
+
else:
|
| 711 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 712 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 713 |
+
|
| 714 |
+
@inicializar_y_capturar_excepciones
|
| 715 |
+
def ConsultarCategorias(self, sep="||"):
|
| 716 |
+
"Retorna listado de categorΓas existentes (cΓ³digo, descripciΓ³n)"
|
| 717 |
+
ret = self.client.consultarCategorias(
|
| 718 |
+
auth={
|
| 719 |
+
'token': self.Token, 'sign': self.Sign,
|
| 720 |
+
'cuit': self.Cuit, },
|
| 721 |
+
)['respuesta']
|
| 722 |
+
self.__analizar_errores(ret)
|
| 723 |
+
array = ret.get('categoria', []) + ret.get('categoriaPorcina', [])
|
| 724 |
+
if sep is None:
|
| 725 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 726 |
+
else:
|
| 727 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 728 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 729 |
+
|
| 730 |
+
@inicializar_y_capturar_excepciones
|
| 731 |
+
def ConsultarMotivos(self, sep="||"):
|
| 732 |
+
"Retorna listado de motivos existentes (cΓ³digo, descripciΓ³n)"
|
| 733 |
+
ret = self.client.consultarMotivos(
|
| 734 |
+
auth={
|
| 735 |
+
'token': self.Token, 'sign': self.Sign,
|
| 736 |
+
'cuit': self.Cuit, },
|
| 737 |
+
)['respuesta']
|
| 738 |
+
self.__analizar_errores(ret)
|
| 739 |
+
array = ret.get('motivo', [])
|
| 740 |
+
if sep is None:
|
| 741 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 742 |
+
else:
|
| 743 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 744 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 745 |
+
|
| 746 |
+
@inicializar_y_capturar_excepciones
|
| 747 |
+
def ConsultarRazas(self, sep="||"):
|
| 748 |
+
"Retorna listado de razas -vacunas y porcinos- (cΓ³digo, descripciΓ³n)"
|
| 749 |
+
ret = self.client.consultarRazas(
|
| 750 |
+
auth={
|
| 751 |
+
'token': self.Token, 'sign': self.Sign,
|
| 752 |
+
'cuit': self.Cuit, },
|
| 753 |
+
)['respuesta']
|
| 754 |
+
self.__analizar_errores(ret)
|
| 755 |
+
array = ret.get('raza', []) + ret.get('razaPorcina', [])
|
| 756 |
+
if sep is None:
|
| 757 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 758 |
+
else:
|
| 759 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 760 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 761 |
+
|
| 762 |
+
@inicializar_y_capturar_excepciones
|
| 763 |
+
def ConsultarCortes(self, sep="||"):
|
| 764 |
+
"Retorna listado de cortes -carnes- (cΓ³digo, descripciΓ³n)"
|
| 765 |
+
ret = self.client.consultarCortes(
|
| 766 |
+
auth={
|
| 767 |
+
'token': self.Token, 'sign': self.Sign,
|
| 768 |
+
'cuit': self.Cuit, },
|
| 769 |
+
)['respuesta']
|
| 770 |
+
self.__analizar_errores(ret)
|
| 771 |
+
array = ret.get('corte', []) + ret.get('cortePorcino', [])
|
| 772 |
+
if sep is None:
|
| 773 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 774 |
+
else:
|
| 775 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 776 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 777 |
+
|
| 778 |
+
@inicializar_y_capturar_excepciones
|
| 779 |
+
def ConsultarPuntosVentas(self, sep="||"):
|
| 780 |
+
"Retorna los puntos de ventas autorizados para la utilizacion de WS"
|
| 781 |
+
ret = self.client.consultarPuntosVenta(
|
| 782 |
+
auth={
|
| 783 |
+
'token': self.Token, 'sign': self.Sign,
|
| 784 |
+
'cuit': self.Cuit, },
|
| 785 |
+
)['respuesta']
|
| 786 |
+
self.__analizar_errores(ret)
|
| 787 |
+
array = ret.get('puntoVenta', [])
|
| 788 |
+
if sep is None:
|
| 789 |
+
return dict([(it['codigo'], it.get('descripcion', '')) for it in array])
|
| 790 |
+
else:
|
| 791 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 792 |
+
(it['codigo'], it.get('descripcion', '')) for it in array]
|
| 793 |
+
|
| 794 |
+
def MostrarPDF(self, archivo, imprimir=False):
|
| 795 |
+
try:
|
| 796 |
+
if sys.platform == "linux2":
|
| 797 |
+
import subprocess
|
| 798 |
+
subprocess.run(["evince", archivo], check=False)
|
| 799 |
+
else:
|
| 800 |
+
operation = imprimir and "print" or ""
|
| 801 |
+
os.startfile(archivo, operation)
|
| 802 |
+
return True
|
| 803 |
+
except Exception as e:
|
| 804 |
+
self.Excepcion = str(e)
|
| 805 |
+
return False
|
| 806 |
+
|
| 807 |
+
|
| 808 |
+
# busco el directorio de instalaciΓ³n (global para que no cambie si usan otra dll)
|
| 809 |
+
INSTALL_DIR = WSLSP.InstallDir = get_install_dir()
|
| 810 |
+
|
| 811 |
+
|
| 812 |
+
if __name__ == '__main__':
|
| 813 |
+
if '--ayuda' in sys.argv:
|
| 814 |
+
print(LICENCIA)
|
| 815 |
+
print(AYUDA)
|
| 816 |
+
sys.exit(0)
|
| 817 |
+
if '--formato' in sys.argv:
|
| 818 |
+
print("Formato:")
|
| 819 |
+
for msg, formato in []:
|
| 820 |
+
comienzo = 1
|
| 821 |
+
print("=== %s ===" % msg)
|
| 822 |
+
for fmt in formato:
|
| 823 |
+
clave, longitud, tipo = fmt[0:3]
|
| 824 |
+
dec = len(fmt) > 3 and fmt[3] or (tipo == 'I' and '2' or '')
|
| 825 |
+
print(" * Campo: %-20s PosiciΓ³n: %3d Longitud: %4d Tipo: %s Decimales: %s" % (
|
| 826 |
+
clave, comienzo, longitud, tipo, dec))
|
| 827 |
+
comienzo += longitud
|
| 828 |
+
sys.exit(0)
|
| 829 |
+
|
| 830 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 831 |
+
import win32com.server.register
|
| 832 |
+
win32com.server.register.UseCommandLine(WSLSP)
|
| 833 |
+
sys.exit(0)
|
| 834 |
+
|
| 835 |
+
import csv
|
| 836 |
+
from configparser import SafeConfigParser
|
| 837 |
+
|
| 838 |
+
from .wsaa import WSAA
|
| 839 |
+
|
| 840 |
+
try:
|
| 841 |
+
|
| 842 |
+
if "--version" in sys.argv:
|
| 843 |
+
print("VersiΓ³n: ", __version__)
|
| 844 |
+
|
| 845 |
+
if len(sys.argv) > 1 and sys.argv[1].endswith(".ini"):
|
| 846 |
+
CONFIG_FILE = sys.argv[1]
|
| 847 |
+
print("Usando configuracion:", CONFIG_FILE)
|
| 848 |
+
|
| 849 |
+
config = SafeConfigParser()
|
| 850 |
+
config.read(CONFIG_FILE)
|
| 851 |
+
CERT = config.get('WSAA', 'CERT')
|
| 852 |
+
PRIVATEKEY = config.get('WSAA', 'PRIVATEKEY')
|
| 853 |
+
CUIT = config.get('WSLSP', 'CUIT')
|
| 854 |
+
ENTRADA = config.get('WSLSP', 'ENTRADA')
|
| 855 |
+
SALIDA = config.get('WSLSP', 'SALIDA')
|
| 856 |
+
PDF = config.has_option('WSLSP', 'PDF') and config.get('WSLSP', 'PDF') or "liq.pdf"
|
| 857 |
+
|
| 858 |
+
if config.has_option('WSAA', 'URL') and not HOMO:
|
| 859 |
+
WSAA_URL = config.get('WSAA', 'URL')
|
| 860 |
+
else:
|
| 861 |
+
WSAA_URL = None # wsaa.WSAAURL
|
| 862 |
+
if config.has_option('WSLSP', 'URL') and not HOMO:
|
| 863 |
+
WSLSP_URL = config.get('WSLSP', 'URL')
|
| 864 |
+
else:
|
| 865 |
+
WSLSP_URL = WSDL
|
| 866 |
+
|
| 867 |
+
PROXY = config.has_option('WSAA', 'PROXY') and config.get('WSAA', 'PROXY') or None
|
| 868 |
+
CACERT = config.has_option('WSAA', 'CACERT') and config.get('WSAA', 'CACERT') or None
|
| 869 |
+
WRAPPER = config.has_option('WSAA', 'WRAPPER') and config.get('WSAA', 'WRAPPER') or None
|
| 870 |
+
|
| 871 |
+
if config.has_section('DBF'):
|
| 872 |
+
conf_dbf = dict(config.items('DBF'))
|
| 873 |
+
if DEBUG:
|
| 874 |
+
print("conf_dbf", conf_dbf)
|
| 875 |
+
else:
|
| 876 |
+
conf_dbf = {}
|
| 877 |
+
|
| 878 |
+
DEBUG = '--debug' in sys.argv
|
| 879 |
+
XML = '--xml' in sys.argv
|
| 880 |
+
|
| 881 |
+
if DEBUG:
|
| 882 |
+
print("Usando ConfiguraciΓ³n:")
|
| 883 |
+
print("WSAA_URL:", WSAA_URL)
|
| 884 |
+
print("WSLSP_URL:", WSLSP_URL)
|
| 885 |
+
print("CACERT", CACERT)
|
| 886 |
+
print("WRAPPER", WRAPPER)
|
| 887 |
+
# obteniendo el TA
|
| 888 |
+
from .wsaa import WSAA
|
| 889 |
+
wsaa = WSAA()
|
| 890 |
+
ta = wsaa.Autenticar("wslsp", CERT, PRIVATEKEY, wsdl=WSAA_URL,
|
| 891 |
+
proxy=PROXY, wrapper=WRAPPER, cacert=CACERT)
|
| 892 |
+
if not ta:
|
| 893 |
+
pass # sys.exit("Imposible autenticar con WSAA: %s" % wsaa.Excepcion)
|
| 894 |
+
|
| 895 |
+
# cliente soap del web service
|
| 896 |
+
wslsp = WSLSP()
|
| 897 |
+
wslsp.LanzarExcepciones = True
|
| 898 |
+
wslsp.Conectar(url=WSLSP_URL, proxy=PROXY, wrapper=WRAPPER, cacert=CACERT)
|
| 899 |
+
wslsp.SetTicketAcceso(ta)
|
| 900 |
+
wslsp.Cuit = CUIT
|
| 901 |
+
|
| 902 |
+
if '--dummy' in sys.argv:
|
| 903 |
+
ret = wslsp.Dummy()
|
| 904 |
+
print("AppServerStatus", wslsp.AppServerStatus)
|
| 905 |
+
print("DbServerStatus", wslsp.DbServerStatus)
|
| 906 |
+
print("AuthServerStatus", wslsp.AuthServerStatus)
|
| 907 |
+
# sys.exit(0)
|
| 908 |
+
|
| 909 |
+
if '--autorizar' in sys.argv:
|
| 910 |
+
|
| 911 |
+
if '--prueba' in sys.argv:
|
| 912 |
+
print(wslsp.client.help("generarLiquidacion"))
|
| 913 |
+
|
| 914 |
+
# Solicitud 1: Cuenta de Venta y LΓquido Producto - Hacienda
|
| 915 |
+
wslsp.CrearLiquidacion(
|
| 916 |
+
cod_operacion=1,
|
| 917 |
+
fecha_cbte='2017-04-23',
|
| 918 |
+
fecha_op='2017-04-23',
|
| 919 |
+
cod_motivo=6,
|
| 920 |
+
cod_localidad_procedencia=8274,
|
| 921 |
+
cod_provincia_procedencia=1,
|
| 922 |
+
cod_localidad_destino=8274,
|
| 923 |
+
cod_provincia_destino=1,
|
| 924 |
+
lugar_realizacion='CORONEL SUAREZ',
|
| 925 |
+
fecha_recepcion=None, fecha_faena=None,
|
| 926 |
+
datos_adicionales=None)
|
| 927 |
+
if False:
|
| 928 |
+
wslsp.AgregarFrigorifico(cuit=20160000156, nro_planta=1)
|
| 929 |
+
wslsp.AgregarEmisor(
|
| 930 |
+
tipo_cbte=180, pto_vta=3000, nro_cbte=64,
|
| 931 |
+
cod_caracter=5, fecha_inicio_act='2016-01-01',
|
| 932 |
+
iibb='123456789', nro_ruca=305, nro_renspa=None)
|
| 933 |
+
wslsp.AgregarReceptor(cod_caracter=3)
|
| 934 |
+
wslsp.AgregarOperador(cuit=30160000011, iibb=3456,
|
| 935 |
+
# nro_ruca=1011, # Validacion AFIP 1003
|
| 936 |
+
# cuit_autorizado=20160000261, # 1001
|
| 937 |
+
nro_renspa='22.123.1.12345/A4')
|
| 938 |
+
wslsp.AgregarItemDetalle(
|
| 939 |
+
cuit_cliente="20160000199", # 2403
|
| 940 |
+
cod_categoria=51020102,
|
| 941 |
+
tipo_liquidacion=1,
|
| 942 |
+
cantidad=2,
|
| 943 |
+
precio_unitario=10.0,
|
| 944 |
+
alicuota_iva=10.5,
|
| 945 |
+
cod_raza=1,
|
| 946 |
+
cantidad_cabezas=None, # Validacion AFIP 2408
|
| 947 |
+
nro_tropa=None,
|
| 948 |
+
cod_corte=None,
|
| 949 |
+
cantidad_kg_vivo=None,
|
| 950 |
+
precio_recupero=None,
|
| 951 |
+
detalle_raza=None,
|
| 952 |
+
nro_item=1,
|
| 953 |
+
)
|
| 954 |
+
wslsp.AgregarCompraAsociada(tipo_cbte=185, pto_vta=3000,
|
| 955 |
+
nro_cbte=33, cant_asoc=2,
|
| 956 |
+
nro_item=1)
|
| 957 |
+
wslsp.AgregarGuia(nro_guia=1)
|
| 958 |
+
# wslsp.AgregarGuia(nro_guia=2)
|
| 959 |
+
if True:
|
| 960 |
+
wslsp.AgregarDTE(nro_dte="418-1",
|
| 961 |
+
nro_renspa='22.123.1.12345/A5')
|
| 962 |
+
wslsp.AgregarDTE(nro_dte="418-2",
|
| 963 |
+
nro_renspa='22.123.1.12346/A5')
|
| 964 |
+
else:
|
| 965 |
+
wslsp.AgregarDTE(nro_dte="418-3", nro_renspa=None)
|
| 966 |
+
wslsp.AgregarDTE(nro_dte="418-4", nro_renspa=None)
|
| 967 |
+
wslsp.AgregarGasto(cod_gasto=16, base_imponible=230520.60,
|
| 968 |
+
alicuota=3, alicuota_iva=10.5)
|
| 969 |
+
wslsp.AgregarGasto(cod_gasto=99, base_imponible=None,
|
| 970 |
+
alicuota=1, alicuota_iva=0,
|
| 971 |
+
descripcion="Exento WSLSPv1.4.1",
|
| 972 |
+
tipo_iva_nulo="EX")
|
| 973 |
+
wslsp.AgregarTributo(cod_tributo=5, base_imponible=230520.60,
|
| 974 |
+
alicuota=2.5)
|
| 975 |
+
wslsp.AgregarTributo(cod_tributo=3, importe=397)
|
| 976 |
+
else:
|
| 977 |
+
# cargar un archivo de texto:
|
| 978 |
+
with open(ENTRADA, "r") as f:
|
| 979 |
+
wslsp.solicitud = json.load(f, encoding="utf-8")
|
| 980 |
+
|
| 981 |
+
if '--testing' in sys.argv:
|
| 982 |
+
# mensaje de prueba (no realiza llamada remota),
|
| 983 |
+
# usar solo si no estΓ‘ operativo, cargo respuesta:
|
| 984 |
+
wslsp.LoadTestXML("tests/xml/wslsp_liq_ok_response.xml")
|
| 985 |
+
import json
|
| 986 |
+
with open(ENTRADA, "w") as f:
|
| 987 |
+
json.dump(wslsp.solicitud, f, sort_keys=True, indent=4, encoding="utf-8",)
|
| 988 |
+
|
| 989 |
+
print("Liquidacion: pto_vta=%s nro_cbte=%s tipo_cbte=%s" % (
|
| 990 |
+
wslsp.solicitud['emisor']['puntoVenta'],
|
| 991 |
+
wslsp.solicitud['emisor']['nroComprobante'],
|
| 992 |
+
wslsp.solicitud['emisor']['tipoComprobante'],
|
| 993 |
+
))
|
| 994 |
+
|
| 995 |
+
if not '--dummy' in sys.argv:
|
| 996 |
+
print("Autorizando...")
|
| 997 |
+
ret = wslsp.AutorizarLiquidacion()
|
| 998 |
+
|
| 999 |
+
if wslsp.Excepcion:
|
| 1000 |
+
print("EXCEPCION:", wslsp.Excepcion, file=sys.stderr)
|
| 1001 |
+
if DEBUG:
|
| 1002 |
+
print(wslsp.Traceback, file=sys.stderr)
|
| 1003 |
+
print("Errores:", wslsp.Errores)
|
| 1004 |
+
print("CAE", wslsp.CAE)
|
| 1005 |
+
print("NroCodigoBarras", wslsp.NroCodigoBarras)
|
| 1006 |
+
print("FechaProcesoAFIP", wslsp.FechaProcesoAFIP)
|
| 1007 |
+
print("FechaComprobante", wslsp.FechaComprobante)
|
| 1008 |
+
print("NroComprobante", wslsp.NroComprobante)
|
| 1009 |
+
print("ImporteBruto", wslsp.ImporteBruto)
|
| 1010 |
+
print("ImporteTotalNeto", wslsp.ImporteTotalNeto)
|
| 1011 |
+
print("ImporteIVA Sobre Bruto", wslsp.ImporteIVASobreBruto)
|
| 1012 |
+
print("ImporteIVA Sobre Gastos", wslsp.ImporteIVASobreGastos)
|
| 1013 |
+
print("ImporteTotalNeto", wslsp.ImporteTotalNeto)
|
| 1014 |
+
|
| 1015 |
+
pdf = wslsp.GetParametro("pdf")
|
| 1016 |
+
if pdf:
|
| 1017 |
+
open(PDF, "wb").write(pdf)
|
| 1018 |
+
|
| 1019 |
+
if '--testing' in sys.argv:
|
| 1020 |
+
assert wslsp.CAE == "97083467167835"
|
| 1021 |
+
|
| 1022 |
+
if DEBUG:
|
| 1023 |
+
pprint.pprint(wslsp.params_out)
|
| 1024 |
+
|
| 1025 |
+
if '--ajustar' in sys.argv:
|
| 1026 |
+
if '--prueba' in sys.argv:
|
| 1027 |
+
# ejemplo documentaciΓ³n AFIP:
|
| 1028 |
+
if '--testing' in sys.argv:
|
| 1029 |
+
wslsp.LoadTestXML("tests/xml/wslsp_ajuste_test.xml")
|
| 1030 |
+
wslsp.CrearAjuste(tipo_ajuste='C', fecha_cbte='2017-01-06',
|
| 1031 |
+
datos_adicionales='Ajuste sobre liquidacion de compra directa'
|
| 1032 |
+
)
|
| 1033 |
+
wslsp.AgregarEmisor(tipo_cbte=186, pto_vta=3000, nro_cbte=1)
|
| 1034 |
+
wslsp.AgregarComprobanteAAjustar(tipo_cbte=186, pto_vta=2000, nro_cbte=4)
|
| 1035 |
+
wslsp.AgregarItemDetalleAjuste(nro_item_ajustar=1)
|
| 1036 |
+
wslsp.AgregarCompraAsociada(tipo_cbte=185, pto_vta=3000,
|
| 1037 |
+
nro_cbte=33, cant_asoc=2,
|
| 1038 |
+
nro_item=1)
|
| 1039 |
+
# ValidaciΓ³n de AFIP 3002:
|
| 1040 |
+
# No se pueden realizar ajustes fisicos y monetario en un mismo comprobante.
|
| 1041 |
+
wslsp.AgregarAjusteFisico(
|
| 1042 |
+
cantidad=1,
|
| 1043 |
+
cantidad_cabezas=None,
|
| 1044 |
+
cantidad_kg_vivo=None,
|
| 1045 |
+
)
|
| 1046 |
+
wslsp.AgregarAjusteMonetario(
|
| 1047 |
+
precio_unitario=15.995,
|
| 1048 |
+
precio_recupero=None,
|
| 1049 |
+
)
|
| 1050 |
+
wslsp.AgregarAjusteFinanciero()
|
| 1051 |
+
wslsp.AgregarGasto(cod_gasto=16, base_imponible=230520.60,
|
| 1052 |
+
alicuota=3, alicuota_iva=10.5)
|
| 1053 |
+
wslsp.AgregarTributo(cod_tributo=5, base_imponible=230520.60,
|
| 1054 |
+
alicuota=2.5)
|
| 1055 |
+
wslsp.AgregarTributo(cod_tributo=3, importe=397)
|
| 1056 |
+
import json
|
| 1057 |
+
with open(ENTRADA, "w") as f:
|
| 1058 |
+
json.dump(wslsp.solicitud, f, sort_keys=True, indent=4, encoding="utf-8",)
|
| 1059 |
+
else:
|
| 1060 |
+
# cargar un archivo de texto:
|
| 1061 |
+
with open(ENTRADA, "r") as f:
|
| 1062 |
+
wslsp.solicitud = json.load(f, encoding="utf-8")
|
| 1063 |
+
|
| 1064 |
+
wslsp.AjustarLiquidacion()
|
| 1065 |
+
print("CAE:", wslsp.CAE)
|
| 1066 |
+
print("Tipo Ajuste:", wslsp.GetParametro("tipo_ajuste"))
|
| 1067 |
+
print("Modo Ajuste:", wslsp.GetParametro("modo_ajuste"))
|
| 1068 |
+
print("Errores:", wslsp.Errores)
|
| 1069 |
+
if '--testing' in sys.argv:
|
| 1070 |
+
assert wslsp.GetParametro("cae") == "97029023118043"
|
| 1071 |
+
assert wslsp.GetParametro("cbte_ajuste", "tipo_cbte") == '186'
|
| 1072 |
+
assert wslsp.GetParametro("cbte_ajuste", "pto_vta") == '2000'
|
| 1073 |
+
assert wslsp.GetParametro("cbte_ajuste", "nro_cbte") == '3'
|
| 1074 |
+
|
| 1075 |
+
pdf = wslsp.GetParametro("pdf")
|
| 1076 |
+
if pdf:
|
| 1077 |
+
open(PDF, "wb").write(pdf)
|
| 1078 |
+
|
| 1079 |
+
if '--consultar' in sys.argv:
|
| 1080 |
+
tipo_cbte = 180
|
| 1081 |
+
pto_vta = 3000
|
| 1082 |
+
nro_cbte = 1
|
| 1083 |
+
cuit = None
|
| 1084 |
+
try:
|
| 1085 |
+
tipo_cbte = sys.argv[sys.argv.index("--consultar") + 1]
|
| 1086 |
+
pto_vta = sys.argv[sys.argv.index("--consultar") + 2]
|
| 1087 |
+
nro_cbte = sys.argv[sys.argv.index("--consultar") + 3]
|
| 1088 |
+
cuit = sys.argv[sys.argv.index("--consultar") + 4]
|
| 1089 |
+
except IndexError:
|
| 1090 |
+
pass
|
| 1091 |
+
if '--testing' in sys.argv:
|
| 1092 |
+
# mensaje de prueba (no realiza llamada remota),
|
| 1093 |
+
# usar solo si no estΓ‘ operativo, cargo prueba:
|
| 1094 |
+
wslsp.LoadTestXML("tests/xml/wslsp_cons_test.xml")
|
| 1095 |
+
print("Consultando: tipo_cbte=%s pto_vta=%s nro_cbte=%s" % (tipo_cbte, pto_vta, nro_cbte))
|
| 1096 |
+
ret = wslsp.ConsultarLiquidacion(tipo_cbte, pto_vta, nro_cbte,
|
| 1097 |
+
cuit_comprador=cuit)
|
| 1098 |
+
print("CAE", wslsp.CAE)
|
| 1099 |
+
print("Errores:", wslsp.Errores)
|
| 1100 |
+
|
| 1101 |
+
if DEBUG:
|
| 1102 |
+
pprint.pprint(wslsp.params_out)
|
| 1103 |
+
|
| 1104 |
+
if '--mostrar' in sys.argv and pdf:
|
| 1105 |
+
wslsp.MostrarPDF(archivo=pdf,
|
| 1106 |
+
imprimir='--imprimir' in sys.argv)
|
| 1107 |
+
|
| 1108 |
+
if '--ult' in sys.argv:
|
| 1109 |
+
tipo_cbte = 27
|
| 1110 |
+
pto_vta = 1
|
| 1111 |
+
try:
|
| 1112 |
+
tipo_cbte = sys.argv[sys.argv.index("--ult") + 1]
|
| 1113 |
+
pto_vta = sys.argv[sys.argv.index("--ult") + 2]
|
| 1114 |
+
except IndexError:
|
| 1115 |
+
pass
|
| 1116 |
+
|
| 1117 |
+
print("Consultando ultimo nro_cbte para pto_vta=%s" % pto_vta, end=' ')
|
| 1118 |
+
ret = wslsp.ConsultarUltimoComprobante(tipo_cbte, pto_vta)
|
| 1119 |
+
if wslsp.Excepcion:
|
| 1120 |
+
print("EXCEPCION:", wslsp.Excepcion, file=sys.stderr)
|
| 1121 |
+
if DEBUG:
|
| 1122 |
+
print(wslsp.Traceback, file=sys.stderr)
|
| 1123 |
+
print("Ultimo Nro de Comprobante", wslsp.NroComprobante)
|
| 1124 |
+
print("Errores:", wslsp.Errores)
|
| 1125 |
+
sys.exit(0)
|
| 1126 |
+
|
| 1127 |
+
if "--guardar" in sys.argv:
|
| 1128 |
+
# grabar un archivo de texto (intercambio) con el resultado:
|
| 1129 |
+
liq = wslsp.params_out.copy()
|
| 1130 |
+
if "pdf" in liq:
|
| 1131 |
+
del liq["pdf"] # eliminador binario
|
| 1132 |
+
with open(SALIDA, "w") as f:
|
| 1133 |
+
json.dump(liq, f, default=str,
|
| 1134 |
+
indent=2, sort_keys=True, encoding="utf-8")
|
| 1135 |
+
|
| 1136 |
+
# Recuperar parΓ‘metros:
|
| 1137 |
+
|
| 1138 |
+
if '--provincias' in sys.argv:
|
| 1139 |
+
ret = wslsp.ConsultarProvincias()
|
| 1140 |
+
print("\n".join(ret))
|
| 1141 |
+
|
| 1142 |
+
if '--localidades' in sys.argv:
|
| 1143 |
+
try:
|
| 1144 |
+
cod_provincia = sys.argv[sys.argv.index("--localidades") + 1]
|
| 1145 |
+
except BaseException:
|
| 1146 |
+
cod_provincia = input("Codigo Provincia:")
|
| 1147 |
+
ret = wslsp.ConsultarLocalidades(cod_provincia)
|
| 1148 |
+
print("\n".join(ret))
|
| 1149 |
+
|
| 1150 |
+
if '--operaciones' in sys.argv:
|
| 1151 |
+
ret = wslsp.ConsultarOperaciones()
|
| 1152 |
+
print("\n".join(ret))
|
| 1153 |
+
|
| 1154 |
+
if '--tributos' in sys.argv:
|
| 1155 |
+
ret = wslsp.ConsultarTributos()
|
| 1156 |
+
print("\n".join(ret))
|
| 1157 |
+
|
| 1158 |
+
if '--gastos' in sys.argv:
|
| 1159 |
+
ret = wslsp.ConsultarGastos()
|
| 1160 |
+
print("\n".join(ret))
|
| 1161 |
+
|
| 1162 |
+
if '--tipos_cbte' in sys.argv:
|
| 1163 |
+
ret = wslsp.ConsultarTiposComprobante()
|
| 1164 |
+
print("\n".join(ret))
|
| 1165 |
+
|
| 1166 |
+
if '--tipos_liq' in sys.argv:
|
| 1167 |
+
ret = wslsp.ConsultarTiposLiquidacion()
|
| 1168 |
+
print("\n".join(ret))
|
| 1169 |
+
|
| 1170 |
+
if '--caracteres' in sys.argv:
|
| 1171 |
+
ret = wslsp.ConsultarCaracteres()
|
| 1172 |
+
print("\n".join(ret))
|
| 1173 |
+
|
| 1174 |
+
if '--categorias' in sys.argv:
|
| 1175 |
+
ret = wslsp.ConsultarCategorias()
|
| 1176 |
+
print("\n".join(ret))
|
| 1177 |
+
|
| 1178 |
+
if '--motivos' in sys.argv:
|
| 1179 |
+
ret = wslsp.ConsultarMotivos()
|
| 1180 |
+
print("\n".join(ret))
|
| 1181 |
+
|
| 1182 |
+
if '--razas' in sys.argv:
|
| 1183 |
+
ret = wslsp.ConsultarRazas()
|
| 1184 |
+
print("\n".join(ret))
|
| 1185 |
+
|
| 1186 |
+
if '--cortes' in sys.argv:
|
| 1187 |
+
ret = wslsp.ConsultarCortes()
|
| 1188 |
+
print("\n".join(ret))
|
| 1189 |
+
|
| 1190 |
+
if '--puntosventa' in sys.argv:
|
| 1191 |
+
ret = wslsp.ConsultarPuntosVentas()
|
| 1192 |
+
print("\n".join(ret))
|
| 1193 |
+
|
| 1194 |
+
print("hecho.")
|
| 1195 |
+
|
| 1196 |
+
except SoapFault as e:
|
| 1197 |
+
print("Falla SOAP:", e.faultcode, e.faultstring.encode("ascii", "ignore"), file=sys.stderr)
|
| 1198 |
+
sys.exit(3)
|
| 1199 |
+
except Exception as e:
|
| 1200 |
+
try:
|
| 1201 |
+
print(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[0], file=sys.stderr)
|
| 1202 |
+
except BaseException:
|
| 1203 |
+
print("ExcepciΓ³n no disponible:", type(e), file=sys.stderr)
|
| 1204 |
+
if DEBUG:
|
| 1205 |
+
raise
|
| 1206 |
+
sys.exit(5)
|
| 1207 |
+
finally:
|
| 1208 |
+
if XML:
|
| 1209 |
+
open("wslsp_request.xml", "w").write(wslsp.client.xml_request)
|
| 1210 |
+
open("wslsp_response.xml", "w").write(wslsp.client.xml_response)
|
app/pyafipws/wsltv.py
ADDED
|
@@ -0,0 +1,1069 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
import shelve
|
| 15 |
+
import sys
|
| 16 |
+
import datetime
|
| 17 |
+
import decimal
|
| 18 |
+
import os
|
| 19 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, BaseWS, inicializar_y_capturar_excepciones, get_install_dir
|
| 20 |
+
from . import utils
|
| 21 |
+
from fpdf import Template
|
| 22 |
+
from pysimplesoap.client import SoapFault
|
| 23 |
+
import pprint
|
| 24 |
+
import traceback
|
| 25 |
+
"""MΓ³dulo para obtener cΓ³digo de autorizaciΓ³n electrΓ³nica (CAE) para
|
| 26 |
+
LiquidaciΓ³n de Tabaco Verde del web service WSLTV de AFIP
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 30 |
+
__copyright__ = "Copyright (C) 2016 Mariano Reingart"
|
| 31 |
+
__license__ = "GPL 3.0"
|
| 32 |
+
__version__ = "1.06d"
|
| 33 |
+
|
| 34 |
+
LICENCIA = """
|
| 35 |
+
wsltv.py: Interfaz para generar CΓ³digo de AutorizaciΓ³n ElectrΓ³nica (CAE) para
|
| 36 |
+
LiquidaciΓ³n de Tabaco Verde (LtvService)
|
| 37 |
+
Copyright (C) 2016 Mariano Reingart reingart@gmail.com
|
| 38 |
+
http://www.sistemasagiles.com.ar/trac/wiki/LiquidacionPrimariaGranos
|
| 39 |
+
|
| 40 |
+
Este progarma es software libre, se entrega ABSOLUTAMENTE SIN GARANTIA
|
| 41 |
+
y es bienvenido a redistribuirlo respetando la licencia GPLv3.
|
| 42 |
+
|
| 43 |
+
Para informaciΓ³n adicional sobre garantΓa, soporte tΓ©cnico comercial
|
| 44 |
+
e incorporaciΓ³n/distribuciΓ³n en programas propietarios ver PyAfipWs:
|
| 45 |
+
http://www.sistemasagiles.com.ar/trac/wiki/PyAfipWs
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
AYUDA = """
|
| 49 |
+
Opciones:
|
| 50 |
+
--ayuda: este mensaje
|
| 51 |
+
|
| 52 |
+
--debug: modo depuraciΓ³n (detalla y confirma las operaciones)
|
| 53 |
+
--formato: muestra el formato de los archivos de entrada/salida
|
| 54 |
+
--prueba: genera y autoriza una liquidaciΓ³n de prueba (no usar en producciΓ³n!)
|
| 55 |
+
--xml: almacena los requerimientos y respuestas XML (depuraciΓ³n)
|
| 56 |
+
--dbf: utilizar tablas DBF (xBase) para los archivos de intercambio
|
| 57 |
+
--json: utilizar formato json para el archivo de intercambio
|
| 58 |
+
--dummy: consulta estado de servidores
|
| 59 |
+
|
| 60 |
+
--autorizar: Autorizar LiquidaciΓ³n de Tabaco Verde (generarLiquidacion)
|
| 61 |
+
--ajustar: Ajustar (ajustarLiquidacion/generarAjusteFisico)
|
| 62 |
+
--ult: Consulta el ΓΊltimo nΓΊmero de orden registrado en AFIP
|
| 63 |
+
(consultarUltimoComprobanteXPuntoVenta)
|
| 64 |
+
--consultar: Consulta una liquidaciΓ³n registrada en AFIP
|
| 65 |
+
(consultarLiquidacionXNroComprobante / consultarLiquidacionXCAE)
|
| 66 |
+
|
| 67 |
+
--pdf: descarga la liquidaciΓ³n en formato PDF
|
| 68 |
+
--mostrar: muestra el documento PDF generado (usar con --pdf)
|
| 69 |
+
--imprimir: imprime el documento PDF generado (usar con --mostrar y --pdf)
|
| 70 |
+
|
| 71 |
+
--provincias: obtiene el listado de provincias (cΓ³digo/descripciΓ³n)
|
| 72 |
+
--condicionesventa: obtiene el listado de las condiciones de venta
|
| 73 |
+
--tributos: obtiene el listado de los tributos
|
| 74 |
+
--retenciones: obtiene el listado de las retenciones de tabaco
|
| 75 |
+
--variedades: obtiene el listado de las variedades y especies de tabaco
|
| 76 |
+
--depositos: obtiene el listado de los depositos de acopio (para el contrib.)
|
| 77 |
+
--puntosventa: obtiene el listado de puntos de venta habilitados
|
| 78 |
+
|
| 79 |
+
Ver wsltv.ini para parΓ‘metros de configuraciΓ³n (URL, certificados, etc.)"
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# importo funciones compartidas:
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
WSDL = "https://fwshomo.afip.gov.ar/wsltv/LtvService?wsdl"
|
| 87 |
+
#WSDL = "https://serviciosjava.afip.gob.ar/wsltv/LtvService?wsdl"
|
| 88 |
+
|
| 89 |
+
DEBUG = False
|
| 90 |
+
XML = False
|
| 91 |
+
CONFIG_FILE = "wsltv.ini"
|
| 92 |
+
TIMEOUT = 30
|
| 93 |
+
HOMO = False
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class WSLTV(BaseWS):
|
| 97 |
+
"Interfaz para el WebService de LiquidaciΓ³n de Tabaco Verde"
|
| 98 |
+
_public_methods_ = ['Conectar', 'Dummy', 'SetTicketAcceso', 'DebugLog',
|
| 99 |
+
'AutorizarLiquidacion',
|
| 100 |
+
'CrearLiquidacion',
|
| 101 |
+
'AgregarCondicionVenta', 'AgregarReceptor',
|
| 102 |
+
'AgregarRomaneo', 'AgregarFardo', 'AgregarPrecioClase',
|
| 103 |
+
'AgregarRetencion', 'AgregarTributo',
|
| 104 |
+
'AgregarFlete', 'AgregarBonificacion',
|
| 105 |
+
'ConsultarLiquidacion', 'ConsultarUltimoComprobante',
|
| 106 |
+
'CrearAjuste', 'AgregarComprobanteAAjustar',
|
| 107 |
+
'AjustarLiquidacion', 'GenerarAjusteFisico',
|
| 108 |
+
'LeerDatosLiquidacion',
|
| 109 |
+
'ConsultarVariedadesClasesTabaco',
|
| 110 |
+
'ConsultarTributos',
|
| 111 |
+
'ConsultarRetencionesTabacaleras',
|
| 112 |
+
'ConsultarDepositosAcopio',
|
| 113 |
+
'ConsultarPuntosVentas',
|
| 114 |
+
'ConsultarProvincias',
|
| 115 |
+
'ConsultarCondicionesVenta',
|
| 116 |
+
'MostrarPDF',
|
| 117 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 118 |
+
'SetParametros', 'SetParametro', 'GetParametro',
|
| 119 |
+
]
|
| 120 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 121 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 122 |
+
'Excepcion', 'ErrCode', 'ErrMsg', 'LanzarExcepciones', 'Errores',
|
| 123 |
+
'XmlRequest', 'XmlResponse', 'Version', 'Traceback', 'InstallDir',
|
| 124 |
+
'CAE', 'NroComprobante', 'FechaLiquidacion',
|
| 125 |
+
'ImporteNeto', 'TotalRetenciones', 'TotalTributos', 'Total',
|
| 126 |
+
'AlicuotaIVA', 'ImporteIVA', 'Subtotal',
|
| 127 |
+
]
|
| 128 |
+
_reg_progid_ = "WSLTV"
|
| 129 |
+
_reg_clsid_ = "{C6EEAE8A-7560-4538-B29C-76434A8C2DC3}"
|
| 130 |
+
|
| 131 |
+
# Variables globales para BaseWS:
|
| 132 |
+
HOMO = HOMO
|
| 133 |
+
WSDL = WSDL
|
| 134 |
+
LanzarExcepciones = False
|
| 135 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciΓ³n' or '')
|
| 136 |
+
|
| 137 |
+
def inicializar(self):
|
| 138 |
+
BaseWS.inicializar(self)
|
| 139 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 140 |
+
self.errores = []
|
| 141 |
+
self.CAE = ""
|
| 142 |
+
self.NroComprobante = self.FechaLiquidacion = ''
|
| 143 |
+
self.ImporteNeto = ""
|
| 144 |
+
self.ImporteIVA = ""
|
| 145 |
+
self.AlicuotaIVA = ""
|
| 146 |
+
self.TotalRetenciones = ""
|
| 147 |
+
self.TotalTributos = ""
|
| 148 |
+
self.Subtotal = self.Total = ""
|
| 149 |
+
self.datos = {}
|
| 150 |
+
self.reintentos = 0
|
| 151 |
+
|
| 152 |
+
@inicializar_y_capturar_excepciones
|
| 153 |
+
def Conectar(self, cache=None, url="", proxy="", wrapper="", cacert=None, timeout=30):
|
| 154 |
+
"Establecer la conexiΓ³n a los servidores de la AFIP"
|
| 155 |
+
# llamo al constructor heredado:
|
| 156 |
+
ok = BaseWS.Conectar(self, cache, url, proxy, wrapper, cacert, timeout)
|
| 157 |
+
if ok:
|
| 158 |
+
# corrijo ubicaciΓ³n del servidor (puerto htttp 80 en el WSDL)
|
| 159 |
+
location = self.client.services['LtvService']['ports']['LtvEndPoint']['location']
|
| 160 |
+
if location.startswith("http://"):
|
| 161 |
+
print("Corrigiendo WSDL ...", location, end=' ')
|
| 162 |
+
location = location.replace("http://", "https://").replace(":80", ":443")
|
| 163 |
+
self.client.services['LtvService']['ports']['LtvEndPoint']['location'] = location
|
| 164 |
+
print(location)
|
| 165 |
+
return ok
|
| 166 |
+
|
| 167 |
+
def __analizar_errores(self, ret):
|
| 168 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 169 |
+
errores = []
|
| 170 |
+
if 'errores' in ret:
|
| 171 |
+
errores.extend(ret['errores'])
|
| 172 |
+
if errores:
|
| 173 |
+
self.Errores = ["%(codigo)s: %(descripcion)s" % err['error'][0]
|
| 174 |
+
for err in errores]
|
| 175 |
+
self.errores = [
|
| 176 |
+
{'codigo': err['error'][0]['codigo'],
|
| 177 |
+
'descripcion': err['error'][0]['descripcion'].replace("\n", "")
|
| 178 |
+
.replace("\r", "")}
|
| 179 |
+
for err in errores]
|
| 180 |
+
self.ErrCode = ' '.join(self.Errores)
|
| 181 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 182 |
+
|
| 183 |
+
@inicializar_y_capturar_excepciones
|
| 184 |
+
def Dummy(self):
|
| 185 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 186 |
+
results = self.client.dummy()['respuesta']
|
| 187 |
+
self.AppServerStatus = str(results['appserver'])
|
| 188 |
+
self.DbServerStatus = str(results['dbserver'])
|
| 189 |
+
self.AuthServerStatus = str(results['authserver'])
|
| 190 |
+
return True
|
| 191 |
+
|
| 192 |
+
@inicializar_y_capturar_excepciones
|
| 193 |
+
def CrearLiquidacion(self, tipo_cbte, pto_vta, nro_cbte, fecha,
|
| 194 |
+
cod_deposito_acopio, tipo_compra,
|
| 195 |
+
variedad_tabaco, cod_provincia_origen_tabaco,
|
| 196 |
+
puerta=None, nro_tarjeta=None, horas=None, control=None,
|
| 197 |
+
nro_interno=None, iibb_emisor=None, fecha_inicio_actividad=None,
|
| 198 |
+
**kwargs):
|
| 199 |
+
"Inicializa internamente los datos de una liquidaciΓ³n para autorizar"
|
| 200 |
+
# creo el diccionario con los campos generales de la liquidaciΓ³n:
|
| 201 |
+
liq = dict(tipoComprobante=tipo_cbte,
|
| 202 |
+
nroComprobante=nro_cbte,
|
| 203 |
+
puntoVenta=pto_vta,
|
| 204 |
+
iibbEmisor=iibb_emisor,
|
| 205 |
+
codDepositoAcopio=cod_deposito_acopio,
|
| 206 |
+
fechaLiquidacion=fecha,
|
| 207 |
+
tipoCompra=tipo_compra,
|
| 208 |
+
condicionVenta=[],
|
| 209 |
+
variedadTabaco=variedad_tabaco,
|
| 210 |
+
codProvinciaOrigenTabaco=cod_provincia_origen_tabaco,
|
| 211 |
+
puerta=puerta,
|
| 212 |
+
nroTarjeta=nro_tarjeta,
|
| 213 |
+
horas=horas,
|
| 214 |
+
control=control,
|
| 215 |
+
nroInterno=nro_interno,
|
| 216 |
+
fechaInicioActividad=fecha_inicio_actividad,
|
| 217 |
+
)
|
| 218 |
+
self.solicitud = dict(liquidacion=liq,
|
| 219 |
+
receptor={},
|
| 220 |
+
romaneo=[],
|
| 221 |
+
precioClase=[],
|
| 222 |
+
retencion=[],
|
| 223 |
+
tributo=[],
|
| 224 |
+
)
|
| 225 |
+
return True
|
| 226 |
+
|
| 227 |
+
@inicializar_y_capturar_excepciones
|
| 228 |
+
def AgregarCondicionVenta(self, codigo, descripcion, **kwargs):
|
| 229 |
+
"Agrego una o mΓ‘s condicion de venta a la liq."
|
| 230 |
+
cond = {'codigo': codigo, 'descripcion': descripcion}
|
| 231 |
+
self.solicitud['liquidacion']['condicionVenta'].append(cond)
|
| 232 |
+
return True
|
| 233 |
+
|
| 234 |
+
@inicializar_y_capturar_excepciones
|
| 235 |
+
def AgregarReceptor(self, cuit, iibb, nro_socio, nro_fet, **kwargs):
|
| 236 |
+
"Agrego un receptor a la liq."
|
| 237 |
+
rcpt = dict(cuit=cuit, iibb=iibb, nroSocio=nro_socio, nroFET=nro_fet)
|
| 238 |
+
self.solicitud['receptor'] = rcpt
|
| 239 |
+
return True
|
| 240 |
+
|
| 241 |
+
@inicializar_y_capturar_excepciones
|
| 242 |
+
def AgregarRomaneo(self, nro_romaneo, fecha_romaneo, **kwargs):
|
| 243 |
+
"Agrego uno o mΓ‘s romaneos a la liq."
|
| 244 |
+
romaneo = dict(nroRomaneo=nro_romaneo, fechaRomaneo=fecha_romaneo,
|
| 245 |
+
fardo=[])
|
| 246 |
+
self.solicitud['romaneo'].append(romaneo)
|
| 247 |
+
return True
|
| 248 |
+
|
| 249 |
+
@inicializar_y_capturar_excepciones
|
| 250 |
+
def AgregarFardo(self, cod_trazabilidad, clase_tabaco, peso, **kwargs):
|
| 251 |
+
"Agrego un fardo al ΓΊltimo romaneo agregado a la liq."
|
| 252 |
+
fardo = dict(codTrazabilidad=cod_trazabilidad, claseTabaco=clase_tabaco, peso=peso)
|
| 253 |
+
self.solicitud['romaneo'][-1]['fardo'].append(fardo)
|
| 254 |
+
return True
|
| 255 |
+
|
| 256 |
+
@inicializar_y_capturar_excepciones
|
| 257 |
+
def AgregarPrecioClase(self, clase_tabaco, precio, total_kilos=None, total_fardos=None, **kwargs):
|
| 258 |
+
"Agrego un PrecioClase a la liq."
|
| 259 |
+
precioclase = dict(claseTabaco=clase_tabaco, precio=precio,
|
| 260 |
+
totalKilos=total_kilos, totalFardos=total_fardos)
|
| 261 |
+
self.solicitud['precioClase'].append(precioclase)
|
| 262 |
+
return True
|
| 263 |
+
|
| 264 |
+
@inicializar_y_capturar_excepciones
|
| 265 |
+
def AgregarRetencion(self, cod_retencion, descripcion, importe, **kwargs):
|
| 266 |
+
"Agrega la informaciΓ³n referente a las retenciones de la liquidaciΓ³n"
|
| 267 |
+
ret = dict(codRetencion=cod_retencion, descripcion=descripcion, importe=importe)
|
| 268 |
+
self.solicitud['retencion'].append(ret)
|
| 269 |
+
return True
|
| 270 |
+
|
| 271 |
+
@inicializar_y_capturar_excepciones
|
| 272 |
+
def AgregarTributo(self, codigo_tributo, descripcion, base_imponible, alicuota, importe):
|
| 273 |
+
"Agrega la informaciΓ³n referente a las retenciones de la liquidaciΓ³n"
|
| 274 |
+
trib = dict(codigoTributo=codigo_tributo, descripcion=descripcion, baseImponible=base_imponible, alicuota=alicuota, importe=importe)
|
| 275 |
+
self.solicitud['tributo'].append(trib)
|
| 276 |
+
return True
|
| 277 |
+
|
| 278 |
+
@inicializar_y_capturar_excepciones
|
| 279 |
+
def AgregarFlete(self, descripcion, importe):
|
| 280 |
+
"Agrega la informaciΓ³n referente al flete de la liquidaciΓ³n (opcional)"
|
| 281 |
+
flete = dict(descripcion=descripcion, importe=importe)
|
| 282 |
+
self.solicitud['flete'] = flete
|
| 283 |
+
return True
|
| 284 |
+
|
| 285 |
+
@inicializar_y_capturar_excepciones
|
| 286 |
+
def AgregarBonificacion(self, porcentaje, importe):
|
| 287 |
+
"Agrega la informaciΓ³n referente a las bonificaciones de la liquidaciΓ³n (opcional)"
|
| 288 |
+
bonif = dict(porcentaje=porcentaje, importe=importe)
|
| 289 |
+
self.solicitud['bonificacion'] = bonif
|
| 290 |
+
return True
|
| 291 |
+
|
| 292 |
+
@inicializar_y_capturar_excepciones
|
| 293 |
+
def AutorizarLiquidacion(self):
|
| 294 |
+
"Autorizar LiquidaciΓ³n ElectrΓ³nica de Tabaco Verde"
|
| 295 |
+
# limpio los elementos que no correspondan por estar vacios:
|
| 296 |
+
for campo in ["tributo", "retencion"]:
|
| 297 |
+
if not self.solicitud[campo]:
|
| 298 |
+
del self.solicitud[campo]
|
| 299 |
+
# llamo al webservice:
|
| 300 |
+
ret = self.client.generarLiquidacion(
|
| 301 |
+
auth={
|
| 302 |
+
'token': self.Token, 'sign': self.Sign,
|
| 303 |
+
'cuit': self.Cuit, },
|
| 304 |
+
solicitud=self.solicitud,
|
| 305 |
+
)
|
| 306 |
+
# analizo la respusta
|
| 307 |
+
ret = ret['respuesta']
|
| 308 |
+
self.__analizar_errores(ret)
|
| 309 |
+
liqs = ret.get('liquidacion', [])
|
| 310 |
+
liq = liqs[0] if liqs else None
|
| 311 |
+
self.AnalizarLiquidacion(liq)
|
| 312 |
+
return True
|
| 313 |
+
|
| 314 |
+
def AnalizarLiquidacion(self, liq):
|
| 315 |
+
"MΓ©todo interno para analizar la respuesta de AFIP"
|
| 316 |
+
# proceso los datos bΓ‘sicos de la liquidaciΓ³n (devuelto por consultar):
|
| 317 |
+
if liq:
|
| 318 |
+
cab = liq['cabecera']
|
| 319 |
+
self.CAE = str(cab['cae'])
|
| 320 |
+
self.FechaLiquidacion = cab['fechaLiquidacion']
|
| 321 |
+
self.NroComprobante = cab['nroComprobante']
|
| 322 |
+
tot = liq['totalesOperacion']
|
| 323 |
+
self.AlicuotaIVA = tot['alicuotaIVA']
|
| 324 |
+
self.ImporteNeto = tot['importeNeto']
|
| 325 |
+
self.ImporteIVA = tot['importeIVA']
|
| 326 |
+
self.Subtotal = tot['subtotal']
|
| 327 |
+
self.TotalRetenciones = tot['totalRetenciones']
|
| 328 |
+
self.TotalTributos = tot['totalTributos']
|
| 329 |
+
self.Total = tot['total']
|
| 330 |
+
|
| 331 |
+
# parΓ‘metros de salida:
|
| 332 |
+
self.params_out = dict(
|
| 333 |
+
tipo_cbte=liq['cabecera']['tipoComprobante'],
|
| 334 |
+
pto_vta=liq['cabecera']['puntoVenta'],
|
| 335 |
+
nro_cbte=liq['cabecera']['nroComprobante'],
|
| 336 |
+
fecha=liq['cabecera']['fechaLiquidacion'],
|
| 337 |
+
cod_deposito_acopio=liq['cabecera']['codDepositoAcopio'],
|
| 338 |
+
cae=str(liq['cabecera']['cae']),
|
| 339 |
+
domicilio_punto_venta=liq['cabecera']['domicilioPuntoVenta'],
|
| 340 |
+
domicilio_deposito_acopio=liq['cabecera']['domicilioDepositoAcopio'],
|
| 341 |
+
emisor=dict(
|
| 342 |
+
cuit=liq['emisor']['cuit'],
|
| 343 |
+
razon_social=liq['emisor']['razonSocial'],
|
| 344 |
+
situacion_iva=liq['emisor']['situacionIVA'],
|
| 345 |
+
domicilio=liq['emisor']['domicilio'],
|
| 346 |
+
fecha_inicio_actividad=liq['emisor']['fechaInicioActividad'],
|
| 347 |
+
),
|
| 348 |
+
receptor=dict(
|
| 349 |
+
cuit=liq['receptor']['cuit'],
|
| 350 |
+
razon_social=liq['receptor']['razonSocial'],
|
| 351 |
+
nro_fet=liq['receptor'].get('nroFET'),
|
| 352 |
+
nro_socio=liq['receptor'].get('nroSocio'),
|
| 353 |
+
situacion_iva=liq['receptor']['situacionIVA'],
|
| 354 |
+
domicilio=liq['receptor']['domicilio'],
|
| 355 |
+
iibb=liq['receptor'].get('iibb'),
|
| 356 |
+
),
|
| 357 |
+
control=liq['datosOperacion'].get('control'),
|
| 358 |
+
nro_interno=liq['datosOperacion'].get('nroInterno'),
|
| 359 |
+
condicion_venta=liq['datosOperacion'].get('condicionVenta'),
|
| 360 |
+
variedad_tabaco=liq['datosOperacion']['variedadTabaco'],
|
| 361 |
+
puerta=liq['datosOperacion'].get('puerta'),
|
| 362 |
+
nro_tarjeta=liq['datosOperacion'].get('nroTarjeta'),
|
| 363 |
+
horas=liq['datosOperacion'].get('horas'),
|
| 364 |
+
cod_provincia_origen_tabaco=liq['datosOperacion'].get('codProvinciaOrigenTabaco'),
|
| 365 |
+
tipo_compra=liq['datosOperacion'].get('tipoCompra'),
|
| 366 |
+
peso_total_fardos_kg=liq['detalleOperacion']['pesoTotalFardosKg'],
|
| 367 |
+
cantidad_total_fardos=liq['detalleOperacion']['cantidadTotalFardos'],
|
| 368 |
+
romaneos=[],
|
| 369 |
+
alicuota_iva=liq['totalesOperacion']['alicuotaIVA'],
|
| 370 |
+
importe_iva=liq['totalesOperacion']['importeIVA'],
|
| 371 |
+
importe_neto=liq['totalesOperacion']['importeNeto'],
|
| 372 |
+
subtotal=liq['totalesOperacion']['subtotal'],
|
| 373 |
+
total_retenciones=liq['totalesOperacion']['totalRetenciones'],
|
| 374 |
+
total_tributos=liq['totalesOperacion']['totalTributos'],
|
| 375 |
+
total=liq['totalesOperacion']['total'],
|
| 376 |
+
retenciones=[],
|
| 377 |
+
tributos=[],
|
| 378 |
+
cae_ajustado=liq.get("caeAjustado"),
|
| 379 |
+
pdf=liq.get('pdf'),
|
| 380 |
+
)
|
| 381 |
+
for romaneo in liq['detalleOperacion'].get('romaneo', []):
|
| 382 |
+
self.params_out['romaneos'].append(dict(
|
| 383 |
+
fecha_romaneo=romaneo['fechaRomaneo'],
|
| 384 |
+
nro_romaneo=romaneo['nroRomaneo'],
|
| 385 |
+
detalle_clase=[dict(
|
| 386 |
+
cantidad_fardos=det['cantidadFardos'],
|
| 387 |
+
cod_clase=det['codClase'],
|
| 388 |
+
importe=det['importe'],
|
| 389 |
+
peso_fardos_kg=det['pesoFardosKg'],
|
| 390 |
+
precio_x_kg_fardo=det['precioXKgFardo'],
|
| 391 |
+
) for det in romaneo['detalleClase']],
|
| 392 |
+
))
|
| 393 |
+
for ret in liq.get('retencion', []):
|
| 394 |
+
self.params_out['retenciones'].append(dict(
|
| 395 |
+
retencion_codigo=ret['codigo'],
|
| 396 |
+
retencion_importe=ret['importe'],
|
| 397 |
+
))
|
| 398 |
+
for trib in liq.get('tributo', []):
|
| 399 |
+
self.params_out['tributos'].append(dict(
|
| 400 |
+
tributo_descripcion=trib.get('descripcion', ""),
|
| 401 |
+
tributo_base_imponible=trib['baseImponible'],
|
| 402 |
+
tributo_alicuota=trib['alicuota'],
|
| 403 |
+
tributo_codigo=trib['codigo'],
|
| 404 |
+
tributo_importe=trib['importe'],
|
| 405 |
+
))
|
| 406 |
+
if DEBUG:
|
| 407 |
+
import pprint
|
| 408 |
+
pprint.pprint(self.params_out)
|
| 409 |
+
self.params_out['errores'] = self.errores
|
| 410 |
+
|
| 411 |
+
@inicializar_y_capturar_excepciones
|
| 412 |
+
def CrearAjuste(self, tipo_cbte, pto_vta, nro_cbte, fecha,
|
| 413 |
+
cod_deposito_acopio=None, tipo_ajuste=None, cuit_receptor=None,
|
| 414 |
+
iibb_emisor=None, iibb_receptor=None, fecha_inicio_actividad=None,
|
| 415 |
+
**kwargs):
|
| 416 |
+
"Inicializa internamente los datos de una liquidaciΓ³n para ajustar"
|
| 417 |
+
# codDepositoAcopio, tipoAjuste, cuitReceptor obligatorio ajustar liq.
|
| 418 |
+
# WSLTVv1.3 fechaLiquidacion/fechaInicioActividad para ajuste fΓsico
|
| 419 |
+
# creo el diccionario con los campos generales de la liquidaciΓ³n:
|
| 420 |
+
liq = dict(tipoComprobante=tipo_cbte,
|
| 421 |
+
nroComprobante=nro_cbte,
|
| 422 |
+
puntoVenta=pto_vta,
|
| 423 |
+
fechaAjusteLiquidacion=fecha,
|
| 424 |
+
fechaLiquidacion=fecha,
|
| 425 |
+
fechaInicioActividad=fecha_inicio_actividad,
|
| 426 |
+
codDepositoAcopio=cod_deposito_acopio,
|
| 427 |
+
tipoAjuste=tipo_ajuste,
|
| 428 |
+
cuitReceptor=cuit_receptor,
|
| 429 |
+
iibbReceptor=iibb_receptor,
|
| 430 |
+
iibbEmisor=iibb_emisor,
|
| 431 |
+
comprobanteAAjustar=[],
|
| 432 |
+
)
|
| 433 |
+
self.solicitud = dict(liquidacion=liq,
|
| 434 |
+
precioClase=[],
|
| 435 |
+
retencion=[],
|
| 436 |
+
tributo=[],
|
| 437 |
+
)
|
| 438 |
+
return True
|
| 439 |
+
|
| 440 |
+
@inicializar_y_capturar_excepciones
|
| 441 |
+
def AgregarComprobanteAAjustar(self, tipo_cbte, pto_vta, nro_cbte):
|
| 442 |
+
"Agrega comprobante a ajustar"
|
| 443 |
+
cbte = dict(tipoComprobante=tipo_cbte, puntoVenta=pto_vta, nroComprobante=nro_cbte)
|
| 444 |
+
self.solicitud['liquidacion']["comprobanteAAjustar"].append(cbte)
|
| 445 |
+
return True
|
| 446 |
+
|
| 447 |
+
@inicializar_y_capturar_excepciones
|
| 448 |
+
def AjustarLiquidacion(self):
|
| 449 |
+
"Ajustar LiquidaciΓ³n de Tabaco Verde"
|
| 450 |
+
|
| 451 |
+
# renombrar la clave principal de la estructura
|
| 452 |
+
if 'liquidacion' in self.solicitud:
|
| 453 |
+
liq = self.solicitud.pop('liquidacion')
|
| 454 |
+
self.solicitud["liquidacionAjuste"] = liq
|
| 455 |
+
|
| 456 |
+
# llamar al webservice:
|
| 457 |
+
ret = self.client.ajustarLiquidacion(
|
| 458 |
+
auth={
|
| 459 |
+
'token': self.Token, 'sign': self.Sign,
|
| 460 |
+
'cuit': self.Cuit, },
|
| 461 |
+
solicitud=self.solicitud,
|
| 462 |
+
)
|
| 463 |
+
# analizo la respusta
|
| 464 |
+
ret = ret['respuesta']
|
| 465 |
+
self.__analizar_errores(ret)
|
| 466 |
+
liqs = ret.get('liquidacion', [])
|
| 467 |
+
liq = liqs[0] if liqs else None
|
| 468 |
+
self.AnalizarLiquidacion(liq)
|
| 469 |
+
return True
|
| 470 |
+
|
| 471 |
+
@inicializar_y_capturar_excepciones
|
| 472 |
+
def GenerarAjusteFisico(self):
|
| 473 |
+
"Generar Ajuste FΓsico de LiquidaciΓ³n de Tabaco Verde (WSLTVv1.3)"
|
| 474 |
+
|
| 475 |
+
# renombrar la clave principal de la estructura
|
| 476 |
+
if 'liquidacion' in self.solicitud:
|
| 477 |
+
liq = self.solicitud.pop('liquidacion')
|
| 478 |
+
self.solicitud = liq
|
| 479 |
+
|
| 480 |
+
# llamar al webservice:
|
| 481 |
+
ret = self.client.generarAjusteFisico(
|
| 482 |
+
auth={
|
| 483 |
+
'token': self.Token, 'sign': self.Sign,
|
| 484 |
+
'cuit': self.Cuit, },
|
| 485 |
+
solicitud=self.solicitud,
|
| 486 |
+
)
|
| 487 |
+
# analizar el resultado:
|
| 488 |
+
ret = ret['respuesta']
|
| 489 |
+
self.__analizar_errores(ret)
|
| 490 |
+
liqs = ret.get('liquidacion', [])
|
| 491 |
+
liq = liqs[0] if liqs else None
|
| 492 |
+
self.AnalizarLiquidacion(liq)
|
| 493 |
+
return True
|
| 494 |
+
|
| 495 |
+
@inicializar_y_capturar_excepciones
|
| 496 |
+
def ConsultarLiquidacion(self, tipo_cbte=None, pto_vta=None, nro_cbte=None,
|
| 497 |
+
cae=None, pdf="liq.pdf"):
|
| 498 |
+
"Consulta una liquidaciΓ³n por No de Comprobante o CAE"
|
| 499 |
+
if cae:
|
| 500 |
+
ret = self.client.consultarLiquidacionXCAE(
|
| 501 |
+
auth={
|
| 502 |
+
'token': self.Token, 'sign': self.Sign,
|
| 503 |
+
'cuit': self.Cuit, },
|
| 504 |
+
solicitud={
|
| 505 |
+
'cae': cae,
|
| 506 |
+
'pdf': pdf and True or False,
|
| 507 |
+
},
|
| 508 |
+
)
|
| 509 |
+
else:
|
| 510 |
+
ret = self.client.consultarLiquidacionXNroComprobante(
|
| 511 |
+
auth={
|
| 512 |
+
'token': self.Token, 'sign': self.Sign,
|
| 513 |
+
'cuit': self.Cuit, },
|
| 514 |
+
solicitud={
|
| 515 |
+
'puntoVenta': pto_vta,
|
| 516 |
+
'nroComprobante': nro_cbte,
|
| 517 |
+
'tipoComprobante': tipo_cbte,
|
| 518 |
+
'pdf': pdf and True or False,
|
| 519 |
+
},
|
| 520 |
+
)
|
| 521 |
+
ret = ret['respuesta']
|
| 522 |
+
self.__analizar_errores(ret)
|
| 523 |
+
if 'liquidacion' in ret:
|
| 524 |
+
liqs = ret.get('liquidacion', [])
|
| 525 |
+
liq = liqs[0] if liqs else None
|
| 526 |
+
self.AnalizarLiquidacion(liq)
|
| 527 |
+
# guardo el PDF si se indico archivo y vino en la respuesta:
|
| 528 |
+
if pdf and 'pdf' in liq:
|
| 529 |
+
open(pdf, "wb").write(liq['pdf'])
|
| 530 |
+
return True
|
| 531 |
+
|
| 532 |
+
@inicializar_y_capturar_excepciones
|
| 533 |
+
def ConsultarUltimoComprobante(self, tipo_cbte=151, pto_vta=1):
|
| 534 |
+
"Consulta el ΓΊltimo No de Comprobante registrado"
|
| 535 |
+
ret = self.client.consultarUltimoComprobanteXPuntoVenta(
|
| 536 |
+
auth={
|
| 537 |
+
'token': self.Token, 'sign': self.Sign,
|
| 538 |
+
'cuit': self.Cuit, },
|
| 539 |
+
solicitud={
|
| 540 |
+
'puntoVenta': pto_vta,
|
| 541 |
+
'tipoComprobante': tipo_cbte},
|
| 542 |
+
)
|
| 543 |
+
ret = ret['respuesta']
|
| 544 |
+
self.__analizar_errores(ret)
|
| 545 |
+
self.NroComprobante = ret['nroComprobante']
|
| 546 |
+
return True
|
| 547 |
+
|
| 548 |
+
def ConsultarProvincias(self, sep="||"):
|
| 549 |
+
"Consulta las provincias habilitadas"
|
| 550 |
+
ret = self.client.consultarProvincias(
|
| 551 |
+
auth={
|
| 552 |
+
'token': self.Token, 'sign': self.Sign,
|
| 553 |
+
'cuit': self.Cuit, },
|
| 554 |
+
)['respuesta']
|
| 555 |
+
self.__analizar_errores(ret)
|
| 556 |
+
array = ret.get('provincia', [])
|
| 557 |
+
if sep is None:
|
| 558 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 559 |
+
else:
|
| 560 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 561 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 562 |
+
|
| 563 |
+
def ConsultarCondicionesVenta(self, sep="||"):
|
| 564 |
+
"Retorna un listado de cΓ³digos y descripciones de las condiciones de ventas"
|
| 565 |
+
ret = self.client.consultarCondicionesVenta(
|
| 566 |
+
auth={
|
| 567 |
+
'token': self.Token, 'sign': self.Sign,
|
| 568 |
+
'cuit': self.Cuit, },
|
| 569 |
+
)['respuesta']
|
| 570 |
+
self.__analizar_errores(ret)
|
| 571 |
+
array = ret.get('condicionVenta', [])
|
| 572 |
+
if sep is None:
|
| 573 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 574 |
+
else:
|
| 575 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 576 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 577 |
+
|
| 578 |
+
def ConsultarTributos(self, sep="||"):
|
| 579 |
+
"Retorna un listado de tributos con cΓ³digo, descripciΓ³n y signo."
|
| 580 |
+
ret = self.client.consultarTributos(
|
| 581 |
+
auth={
|
| 582 |
+
'token': self.Token, 'sign': self.Sign,
|
| 583 |
+
'cuit': self.Cuit, },
|
| 584 |
+
)['respuesta']
|
| 585 |
+
self.__analizar_errores(ret)
|
| 586 |
+
array = ret.get('tributo', [])
|
| 587 |
+
if sep is None:
|
| 588 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 589 |
+
else:
|
| 590 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 591 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 592 |
+
|
| 593 |
+
def ConsultarVariedadesClasesTabaco(self, sep="||"):
|
| 594 |
+
"Retorna un listado de variedades y clases de tabaco"
|
| 595 |
+
# El listado es una estructura anidada (varias clases por variedad)
|
| 596 |
+
#import dbg; dbg.set_trace()
|
| 597 |
+
ret = self.client.consultarVariedadesClasesTabaco(
|
| 598 |
+
auth={
|
| 599 |
+
'token': self.Token, 'sign': self.Sign,
|
| 600 |
+
'cuit': self.Cuit, },
|
| 601 |
+
)['respuesta']
|
| 602 |
+
self.__analizar_errores(ret)
|
| 603 |
+
self.XmlResponse = self.client.xml_response
|
| 604 |
+
array = ret.get('variedad', [])
|
| 605 |
+
if sep is None:
|
| 606 |
+
# sin separador, devuelve un diccionario con clave cod_variadedad
|
| 607 |
+
# y valor: {"descripcion": ds_variedad, "clases": lista_clases}
|
| 608 |
+
# siendo lista_clases = [{'codigo': ..., 'descripcion': ...}]
|
| 609 |
+
return dict([(it['codigo'], {'descripcion': it['descripcion'],
|
| 610 |
+
'clases': it['clase']})
|
| 611 |
+
for it in array])
|
| 612 |
+
else:
|
| 613 |
+
# con separador, devuelve una lista de strings:
|
| 614 |
+
# || cod.variedad || desc.variedad || desc.clase || cod.clase ||
|
| 615 |
+
ret = []
|
| 616 |
+
for it in array:
|
| 617 |
+
for clase in it['clase']:
|
| 618 |
+
ret.append(
|
| 619 |
+
("%s %%s %s %%s %s %%s %s %%s %s" %
|
| 620 |
+
(sep, sep, sep, sep, sep)) %
|
| 621 |
+
(it['codigo'], it['descripcion'],
|
| 622 |
+
clase['descripcion'], clase['codigo'])
|
| 623 |
+
)
|
| 624 |
+
return ret
|
| 625 |
+
|
| 626 |
+
def ConsultarRetencionesTabacaleras(self, sep="||"):
|
| 627 |
+
"Retorna un listado de retenciones tabacaleras con cΓ³digo y descripciΓ³n"
|
| 628 |
+
ret = self.client.consultarRetencionesTabacaleras(
|
| 629 |
+
auth={
|
| 630 |
+
'token': self.Token, 'sign': self.Sign,
|
| 631 |
+
'cuit': self.Cuit, },
|
| 632 |
+
)['respuesta']
|
| 633 |
+
self.__analizar_errores(ret)
|
| 634 |
+
array = ret.get('retencion', [])
|
| 635 |
+
if sep is None:
|
| 636 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 637 |
+
else:
|
| 638 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 639 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 640 |
+
|
| 641 |
+
def ConsultarDepositosAcopio(self, sep="||"):
|
| 642 |
+
"Retorna los depΓ³sitos de acopio pertenencientes al contribuyente"
|
| 643 |
+
ret = self.client.consultarDepositosAcopio(
|
| 644 |
+
auth={
|
| 645 |
+
'token': self.Token, 'sign': self.Sign,
|
| 646 |
+
'cuit': self.Cuit, },
|
| 647 |
+
)['respuesta']
|
| 648 |
+
self.__analizar_errores(ret)
|
| 649 |
+
array = ret.get('acopio', [])
|
| 650 |
+
if sep is None:
|
| 651 |
+
return array
|
| 652 |
+
else:
|
| 653 |
+
return [("%s %%s %s %%s %s %%s %s %%s %s" % (sep, sep, sep, sep, sep)) %
|
| 654 |
+
(it['codigo'], it['direccion'], it['localidad'], it['codigoPostal'])
|
| 655 |
+
for it in array]
|
| 656 |
+
|
| 657 |
+
def ConsultarPuntosVentas(self, sep="||"):
|
| 658 |
+
"Retorna los puntos de ventas autorizados para la utilizacion de WS"
|
| 659 |
+
ret = self.client.consultarPuntosVentas(
|
| 660 |
+
auth={
|
| 661 |
+
'token': self.Token, 'sign': self.Sign,
|
| 662 |
+
'cuit': self.Cuit, },
|
| 663 |
+
)['respuesta']
|
| 664 |
+
self.__analizar_errores(ret)
|
| 665 |
+
array = ret.get('puntoVenta', [])
|
| 666 |
+
if sep is None:
|
| 667 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 668 |
+
else:
|
| 669 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 670 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 671 |
+
|
| 672 |
+
def MostrarPDF(self, archivo, imprimir=False):
|
| 673 |
+
try:
|
| 674 |
+
if sys.platform == "linux2":
|
| 675 |
+
import subprocess
|
| 676 |
+
subprocess.run(["evince", archivo], check=False)
|
| 677 |
+
else:
|
| 678 |
+
operation = imprimir and "print" or ""
|
| 679 |
+
os.startfile(archivo, operation)
|
| 680 |
+
return True
|
| 681 |
+
except Exception as e:
|
| 682 |
+
self.Excepcion = str(e)
|
| 683 |
+
return False
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
# busco el directorio de instalaciΓ³n (global para que no cambie si usan otra dll)
|
| 687 |
+
INSTALL_DIR = WSLTV.InstallDir = get_install_dir()
|
| 688 |
+
|
| 689 |
+
|
| 690 |
+
if __name__ == '__main__':
|
| 691 |
+
if '--ayuda' in sys.argv:
|
| 692 |
+
print(LICENCIA)
|
| 693 |
+
print(AYUDA)
|
| 694 |
+
sys.exit(0)
|
| 695 |
+
if '--formato' in sys.argv:
|
| 696 |
+
print("Formato:")
|
| 697 |
+
for msg, formato in []:
|
| 698 |
+
comienzo = 1
|
| 699 |
+
print("=== %s ===" % msg)
|
| 700 |
+
for fmt in formato:
|
| 701 |
+
clave, longitud, tipo = fmt[0:3]
|
| 702 |
+
dec = len(fmt) > 3 and fmt[3] or (tipo == 'I' and '2' or '')
|
| 703 |
+
print(" * Campo: %-20s PosiciΓ³n: %3d Longitud: %4d Tipo: %s Decimales: %s" % (
|
| 704 |
+
clave, comienzo, longitud, tipo, dec))
|
| 705 |
+
comienzo += longitud
|
| 706 |
+
sys.exit(0)
|
| 707 |
+
|
| 708 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 709 |
+
import win32com.server.register
|
| 710 |
+
win32com.server.register.UseCommandLine(WSLTV)
|
| 711 |
+
sys.exit(0)
|
| 712 |
+
|
| 713 |
+
import csv
|
| 714 |
+
from configparser import SafeConfigParser
|
| 715 |
+
|
| 716 |
+
from .wsaa import WSAA
|
| 717 |
+
|
| 718 |
+
try:
|
| 719 |
+
|
| 720 |
+
if "--version" in sys.argv:
|
| 721 |
+
print("VersiΓ³n: ", __version__)
|
| 722 |
+
|
| 723 |
+
if len(sys.argv) > 1 and sys.argv[1].endswith(".ini"):
|
| 724 |
+
CONFIG_FILE = sys.argv[1]
|
| 725 |
+
print("Usando configuracion:", CONFIG_FILE)
|
| 726 |
+
|
| 727 |
+
config = SafeConfigParser()
|
| 728 |
+
config.read(CONFIG_FILE)
|
| 729 |
+
CERT = config.get('WSAA', 'CERT')
|
| 730 |
+
PRIVATEKEY = config.get('WSAA', 'PRIVATEKEY')
|
| 731 |
+
CUIT = config.get('WSLTV', 'CUIT')
|
| 732 |
+
ENTRADA = config.get('WSLTV', 'ENTRADA')
|
| 733 |
+
SALIDA = config.get('WSLTV', 'SALIDA')
|
| 734 |
+
PDF = config.has_option('WSLTV', 'PDF') and config.get('WSLTV', 'PDF') or "liq.pdf"
|
| 735 |
+
|
| 736 |
+
if config.has_option('WSAA', 'URL') and not HOMO:
|
| 737 |
+
WSAA_URL = config.get('WSAA', 'URL')
|
| 738 |
+
else:
|
| 739 |
+
WSAA_URL = None # wsaa.WSAAURL
|
| 740 |
+
if config.has_option('WSLTV', 'URL') and not HOMO:
|
| 741 |
+
WSLTV_URL = config.get('WSLTV', 'URL')
|
| 742 |
+
else:
|
| 743 |
+
WSLTV_URL = WSDL
|
| 744 |
+
|
| 745 |
+
PROXY = config.has_option('WSAA', 'PROXY') and config.get('WSAA', 'PROXY') or None
|
| 746 |
+
CACERT = config.has_option('WSAA', 'CACERT') and config.get('WSAA', 'CACERT') or None
|
| 747 |
+
WRAPPER = config.has_option('WSAA', 'WRAPPER') and config.get('WSAA', 'WRAPPER') or None
|
| 748 |
+
|
| 749 |
+
if config.has_option('WSLTV', 'TIMEOUT'):
|
| 750 |
+
TIMEOUT = int(config.get('WSLTV', 'TIMEOUT'))
|
| 751 |
+
|
| 752 |
+
if config.has_section('DBF'):
|
| 753 |
+
conf_dbf = dict(config.items('DBF'))
|
| 754 |
+
if DEBUG:
|
| 755 |
+
print("conf_dbf", conf_dbf)
|
| 756 |
+
else:
|
| 757 |
+
conf_dbf = {}
|
| 758 |
+
|
| 759 |
+
DEBUG = '--debug' in sys.argv
|
| 760 |
+
XML = '--xml' in sys.argv
|
| 761 |
+
|
| 762 |
+
if DEBUG:
|
| 763 |
+
print("Usando ConfiguraciΓ³n:")
|
| 764 |
+
print("WSAA_URL:", WSAA_URL)
|
| 765 |
+
print("WSLTV_URL:", WSLTV_URL)
|
| 766 |
+
print("CACERT", CACERT)
|
| 767 |
+
print("WRAPPER", WRAPPER)
|
| 768 |
+
print("timeout:", TIMEOUT)
|
| 769 |
+
# obteniendo el TA
|
| 770 |
+
from .wsaa import WSAA
|
| 771 |
+
wsaa = WSAA()
|
| 772 |
+
ta = wsaa.Autenticar("wsltv", CERT, PRIVATEKEY, wsdl=WSAA_URL,
|
| 773 |
+
proxy=PROXY, wrapper=WRAPPER, cacert=CACERT)
|
| 774 |
+
if not ta:
|
| 775 |
+
sys.exit("Imposible autenticar con WSAA: %s" % wsaa.Excepcion)
|
| 776 |
+
|
| 777 |
+
# cliente soap del web service
|
| 778 |
+
wsltv = WSLTV()
|
| 779 |
+
wsltv.LanzarExcepciones = True
|
| 780 |
+
wsltv.Conectar(url=WSLTV_URL, proxy=PROXY, wrapper=WRAPPER, cacert=CACERT, timeout=TIMEOUT)
|
| 781 |
+
wsltv.SetTicketAcceso(ta)
|
| 782 |
+
wsltv.Cuit = CUIT
|
| 783 |
+
|
| 784 |
+
if '--dummy' in sys.argv:
|
| 785 |
+
ret = wsltv.Dummy()
|
| 786 |
+
print("AppServerStatus", wsltv.AppServerStatus)
|
| 787 |
+
print("DbServerStatus", wsltv.DbServerStatus)
|
| 788 |
+
print("AuthServerStatus", wsltv.AuthServerStatus)
|
| 789 |
+
# sys.exit(0)
|
| 790 |
+
|
| 791 |
+
if '--json' in sys.argv and os.path.exists("wsltv.json"):
|
| 792 |
+
# cargar un archivo de texto:
|
| 793 |
+
with open(ENTRADA, "r") as f:
|
| 794 |
+
wsltv.solicitud = json.load(f, encoding="utf-8")
|
| 795 |
+
|
| 796 |
+
if '--autorizar' in sys.argv:
|
| 797 |
+
|
| 798 |
+
if '--prueba' in sys.argv:
|
| 799 |
+
|
| 800 |
+
# genero una liquidaciΓ³n de ejemplo:
|
| 801 |
+
|
| 802 |
+
tipo_cbte = 150
|
| 803 |
+
pto_vta = 6
|
| 804 |
+
|
| 805 |
+
if not '--prueba' in sys.argv:
|
| 806 |
+
# consulto el ΓΊltimo nΓΊmero de orden emitido:
|
| 807 |
+
ok = wsltv.ConsultarUltimoComprobante(tipo_cbte, pto_vta)
|
| 808 |
+
if ok:
|
| 809 |
+
nro_cbte = wsltv.NroComprobante + 1
|
| 810 |
+
else:
|
| 811 |
+
nro_cbte = 1
|
| 812 |
+
|
| 813 |
+
# datos de la cabecera:
|
| 814 |
+
fecha = '2016-04-18'
|
| 815 |
+
cod_deposito_acopio = 1000
|
| 816 |
+
tipo_compra = 'CPS'
|
| 817 |
+
variedad_tabaco = 'BR'
|
| 818 |
+
cod_provincia_origen_tabaco = 1
|
| 819 |
+
puerta = 22
|
| 820 |
+
nro_tarjeta = 6569866
|
| 821 |
+
horas = 12
|
| 822 |
+
control = "FFAA"
|
| 823 |
+
nro_interno = "77888"
|
| 824 |
+
fecha_inicio_actividad = "2016-04-01"
|
| 825 |
+
|
| 826 |
+
# cargo la liquidaciΓ³n:
|
| 827 |
+
wsltv.CrearLiquidacion(tipo_cbte, pto_vta, nro_cbte, fecha,
|
| 828 |
+
cod_deposito_acopio, tipo_compra,
|
| 829 |
+
variedad_tabaco, cod_provincia_origen_tabaco,
|
| 830 |
+
puerta, nro_tarjeta, horas, control,
|
| 831 |
+
nro_interno, iibb_emisor=None,
|
| 832 |
+
fecha_inicio_actividad=fecha_inicio_actividad)
|
| 833 |
+
|
| 834 |
+
wsltv.AgregarCondicionVenta(codigo=99, descripcion="otra")
|
| 835 |
+
|
| 836 |
+
# datos del receptor:
|
| 837 |
+
cuit = 20111111112
|
| 838 |
+
iibb = 123456
|
| 839 |
+
nro_socio = 11223
|
| 840 |
+
nro_fet = 22
|
| 841 |
+
wsltv.AgregarReceptor(cuit, iibb, nro_socio, nro_fet)
|
| 842 |
+
|
| 843 |
+
# datos romaneo:
|
| 844 |
+
nro_romaneo = 321
|
| 845 |
+
fecha_romaneo = "2015-12-10"
|
| 846 |
+
wsltv.AgregarRomaneo(nro_romaneo, fecha_romaneo)
|
| 847 |
+
# fardo:
|
| 848 |
+
cod_trazabilidad = 356
|
| 849 |
+
clase_tabaco = 4
|
| 850 |
+
peso = 900
|
| 851 |
+
wsltv.AgregarFardo(cod_trazabilidad, clase_tabaco, peso)
|
| 852 |
+
|
| 853 |
+
# precio clase:
|
| 854 |
+
precio = 190
|
| 855 |
+
wsltv.AgregarPrecioClase(clase_tabaco, precio)
|
| 856 |
+
|
| 857 |
+
# retencion:
|
| 858 |
+
descripcion = "otra retencion"
|
| 859 |
+
cod_retencion = 15
|
| 860 |
+
importe = 12
|
| 861 |
+
wsltv.AgregarRetencion(cod_retencion, descripcion, importe)
|
| 862 |
+
|
| 863 |
+
# tributo:
|
| 864 |
+
codigo_tributo = 99
|
| 865 |
+
descripcion = "Ganancias"
|
| 866 |
+
base_imponible = 15000
|
| 867 |
+
alicuota = 8
|
| 868 |
+
importe = 1200
|
| 869 |
+
wsltv.AgregarTributo(codigo_tributo, descripcion, base_imponible, alicuota, importe)
|
| 870 |
+
|
| 871 |
+
# flete:
|
| 872 |
+
descripcion = "transporte"
|
| 873 |
+
importe = 1000.00
|
| 874 |
+
wsltv.AgregarFlete(descripcion, importe)
|
| 875 |
+
|
| 876 |
+
# bonificacion:
|
| 877 |
+
porcentaje = 10.0
|
| 878 |
+
importe = 100.00
|
| 879 |
+
wsltv.AgregarBonificacion(porcentaje, importe)
|
| 880 |
+
|
| 881 |
+
if '--testing' in sys.argv:
|
| 882 |
+
# mensaje de prueba (no realiza llamada remota),
|
| 883 |
+
# usar solo si no estΓ‘ operativo, cargo respuesta:
|
| 884 |
+
wsltv.LoadTestXML("tests/xml/wsltv_aut_test_pdf.xml")
|
| 885 |
+
|
| 886 |
+
print("Liquidacion: pto_vta=%s nro_cbte=%s tipo_cbte=%s" % (
|
| 887 |
+
wsltv.solicitud['liquidacion']['puntoVenta'],
|
| 888 |
+
wsltv.solicitud['liquidacion']['nroComprobante'],
|
| 889 |
+
wsltv.solicitud['liquidacion']['tipoComprobante'],
|
| 890 |
+
))
|
| 891 |
+
|
| 892 |
+
if not '--dummy' in sys.argv:
|
| 893 |
+
print("Autorizando...")
|
| 894 |
+
ret = wsltv.AutorizarLiquidacion()
|
| 895 |
+
|
| 896 |
+
if wsltv.Excepcion:
|
| 897 |
+
print("EXCEPCION:", wsltv.Excepcion, file=sys.stderr)
|
| 898 |
+
if DEBUG:
|
| 899 |
+
print(wsltv.Traceback, file=sys.stderr)
|
| 900 |
+
print("Errores:", wsltv.Errores)
|
| 901 |
+
print("CAE", wsltv.CAE)
|
| 902 |
+
print("FechaLiquidacion", wsltv.FechaLiquidacion)
|
| 903 |
+
print("NroComprobante", wsltv.NroComprobante)
|
| 904 |
+
print("ImporteNeto", wsltv.ImporteNeto)
|
| 905 |
+
print("AlicuotaIVA", wsltv.AlicuotaIVA)
|
| 906 |
+
print("ImporteIVA", wsltv.ImporteIVA)
|
| 907 |
+
print("Subtotal", wsltv.Subtotal)
|
| 908 |
+
print("TotalRetenciones", wsltv.TotalRetenciones)
|
| 909 |
+
print("TotalTributos", wsltv.TotalTributos)
|
| 910 |
+
print("Total", wsltv.Total)
|
| 911 |
+
|
| 912 |
+
pdf = wsltv.GetParametro("pdf")
|
| 913 |
+
if pdf:
|
| 914 |
+
open(PDF, "wb").write(pdf)
|
| 915 |
+
|
| 916 |
+
if '--testing' in sys.argv:
|
| 917 |
+
assert wsltv.CAE == "85523002502850"
|
| 918 |
+
assert wsltv.Total == 205685.46
|
| 919 |
+
assert wsltv.GetParametro("fecha") == "2016-01-01"
|
| 920 |
+
assert wsltv.GetParametro("peso_total_fardos_kg") == "900"
|
| 921 |
+
assert wsltv.GetParametro("cantidad_total_fardos") == "1"
|
| 922 |
+
assert wsltv.GetParametro("emisor", "domicilio") == 'Peru 100'
|
| 923 |
+
assert wsltv.GetParametro("emisor", "razon_social") == 'JOCKER'
|
| 924 |
+
assert wsltv.GetParametro("receptor", "domicilio") == 'Calle 1'
|
| 925 |
+
assert wsltv.GetParametro("receptor", "razon_social") == 'CUIT PF de Prueba gen\xe9rica'
|
| 926 |
+
assert wsltv.GetParametro("romaneos", 0, "detalle_clase", 0, "cantidad_fardos") == "1"
|
| 927 |
+
assert wsltv.GetParametro("romaneos", 0, "detalle_clase", 0, "cod_clase") == "4"
|
| 928 |
+
assert wsltv.GetParametro("romaneos", 0, "detalle_clase", 0, "importe") == "171000.0"
|
| 929 |
+
assert wsltv.GetParametro("romaneos", 0, "detalle_clase", 0, "peso_fardos_kg") == "900"
|
| 930 |
+
assert wsltv.GetParametro("romaneos", 0, "detalle_clase", 0, "precio_x_kg_fardo") == "190.0"
|
| 931 |
+
assert wsltv.GetParametro("romaneos", 0, "nro_romaneo") == "321"
|
| 932 |
+
assert wsltv.GetParametro("romaneos", 0, "fecha_romaneo") == "2015-12-10"
|
| 933 |
+
|
| 934 |
+
if DEBUG:
|
| 935 |
+
pprint.pprint(wsltv.params_out)
|
| 936 |
+
|
| 937 |
+
if '--generar-ajuste-fisico' in sys.argv:
|
| 938 |
+
if '--prueba' in sys.argv:
|
| 939 |
+
# ejemplo documentaciΓ³n AFIP:
|
| 940 |
+
if '--testing' in sys.argv:
|
| 941 |
+
wsltv.LoadTestXML("tests/xml/wsltv_ajuste_test_pdf.xml")
|
| 942 |
+
wsltv.CrearAjuste(tipo_cbte=151, pto_vta=2, nro_cbte=1,
|
| 943 |
+
fecha='2016-09-09',
|
| 944 |
+
fecha_inicio_actividad="1900-01-01",
|
| 945 |
+
)
|
| 946 |
+
wsltv.AgregarComprobanteAAjustar(tipo_cbte=151, pto_vta=3697, nro_cbte=2)
|
| 947 |
+
wsltv.GenerarAjusteFisico()
|
| 948 |
+
print("CAE Ajustado:", wsltv.GetParametro("cae_ajustado"))
|
| 949 |
+
if '--testing' in sys.argv:
|
| 950 |
+
assert wsltv.GetParametro("cae_ajustado") == "86029002591067"
|
| 951 |
+
|
| 952 |
+
if '--ajustar' in sys.argv:
|
| 953 |
+
if '--prueba' in sys.argv:
|
| 954 |
+
# ejemplo documentaciΓ³n AFIP:
|
| 955 |
+
if '--testing' in sys.argv:
|
| 956 |
+
wsltv.LoadTestXML("tests/xml/wsltv_ajuste_test.xml")
|
| 957 |
+
wsltv.CrearAjuste(tipo_cbte=151, pto_vta=2958, nro_cbte=13,
|
| 958 |
+
fecha='2015-12-31',
|
| 959 |
+
cod_deposito_acopio=201, tipo_ajuste="C",
|
| 960 |
+
cuit_receptor=222222222,
|
| 961 |
+
iibb_receptor=2,
|
| 962 |
+
fecha_inicio_actividad="2010-01-01"
|
| 963 |
+
)
|
| 964 |
+
wsltv.AgregarComprobanteAAjustar(tipo_cbte=151, pto_vta=4521, nro_cbte=12345678)
|
| 965 |
+
wsltv.AgregarPrecioClase(clase_tabaco=111, precio=25, total_kilos=41, total_fardos=1)
|
| 966 |
+
wsltv.AgregarRetencion(cod_retencion=11, descripcion=None, importe=20)
|
| 967 |
+
wsltv.AgregarTributo(codigo_tributo=99, descripcion="Descripcion otros tributos", base_imponible=2, alicuota=2, importe=10)
|
| 968 |
+
wsltv.AjustarLiquidacion()
|
| 969 |
+
print("CAE:", wsltv.CAE)
|
| 970 |
+
if '--testing' in sys.argv:
|
| 971 |
+
assert wsltv.GetParametro("cae") == "86011002510675"
|
| 972 |
+
|
| 973 |
+
if '--consultar' in sys.argv:
|
| 974 |
+
tipo_cbte = 151
|
| 975 |
+
pto_vta = 1
|
| 976 |
+
nro_cbte = 0
|
| 977 |
+
try:
|
| 978 |
+
tipo_cbte = sys.argv[sys.argv.index("--consultar") + 1]
|
| 979 |
+
pto_vta = sys.argv[sys.argv.index("--consultar") + 2]
|
| 980 |
+
nro_cbte = sys.argv[sys.argv.index("--consultar") + 3]
|
| 981 |
+
except IndexError:
|
| 982 |
+
pass
|
| 983 |
+
if '--testing' in sys.argv:
|
| 984 |
+
# mensaje de prueba (no realiza llamada remota),
|
| 985 |
+
# usar solo si no estΓ‘ operativo, cargo prueba:
|
| 986 |
+
wsltv.LoadTestXML("tests/xml/wsltv_cons_test.xml")
|
| 987 |
+
print("Consultando: tipo_cbte=%s pto_vta=%s nro_cbte=%s" % (tipo_cbte, pto_vta, nro_cbte))
|
| 988 |
+
ret = wsltv.ConsultarLiquidacion(tipo_cbte, pto_vta, nro_cbte)
|
| 989 |
+
print("CAE", wsltv.CAE)
|
| 990 |
+
print("Errores:", wsltv.Errores)
|
| 991 |
+
|
| 992 |
+
if DEBUG:
|
| 993 |
+
pprint.pprint(wsltv.params_out)
|
| 994 |
+
|
| 995 |
+
if '--mostrar' in sys.argv and pdf:
|
| 996 |
+
wsltv.MostrarPDF(archivo=pdf,
|
| 997 |
+
imprimir='--imprimir' in sys.argv)
|
| 998 |
+
|
| 999 |
+
if '--json' in sys.argv:
|
| 1000 |
+
import json
|
| 1001 |
+
with open(SALIDA, "w") as f:
|
| 1002 |
+
json.dump(wsltv.solicitud, f, sort_keys=True, indent=4, encoding="utf-8",)
|
| 1003 |
+
|
| 1004 |
+
if '--ult' in sys.argv:
|
| 1005 |
+
tipo_cbte = 151
|
| 1006 |
+
pto_vta = 1
|
| 1007 |
+
try:
|
| 1008 |
+
tipo_cbte = sys.argv[sys.argv.index("--ult") + 1]
|
| 1009 |
+
pto_vta = sys.argv[sys.argv.index("--ult") + 2]
|
| 1010 |
+
except IndexError:
|
| 1011 |
+
pass
|
| 1012 |
+
|
| 1013 |
+
print("Consultando ultimo nro_cbte para pto_vta=%s" % pto_vta, end=' ')
|
| 1014 |
+
ret = wsltv.ConsultarUltimoComprobante(tipo_cbte, pto_vta)
|
| 1015 |
+
if wsltv.Excepcion:
|
| 1016 |
+
print("EXCEPCION:", wsltv.Excepcion, file=sys.stderr)
|
| 1017 |
+
if DEBUG:
|
| 1018 |
+
print(wsltv.Traceback, file=sys.stderr)
|
| 1019 |
+
print("Ultimo Nro de Comprobante", wsltv.NroComprobante)
|
| 1020 |
+
print("Errores:", wsltv.Errores)
|
| 1021 |
+
sys.exit(0)
|
| 1022 |
+
|
| 1023 |
+
# Recuperar parΓ‘metros:
|
| 1024 |
+
|
| 1025 |
+
if '--provincias' in sys.argv:
|
| 1026 |
+
ret = wsltv.ConsultarProvincias()
|
| 1027 |
+
print("\n".join(ret))
|
| 1028 |
+
|
| 1029 |
+
if '--condicionesventa' in sys.argv:
|
| 1030 |
+
ret = wsltv.ConsultarCondicionesVenta()
|
| 1031 |
+
print("\n".join(ret))
|
| 1032 |
+
|
| 1033 |
+
if '--tributos' in sys.argv:
|
| 1034 |
+
ret = wsltv.ConsultarTributos()
|
| 1035 |
+
print("\n".join(ret))
|
| 1036 |
+
|
| 1037 |
+
if '--retenciones' in sys.argv:
|
| 1038 |
+
ret = wsltv.ConsultarRetencionesTabacaleras()
|
| 1039 |
+
print("\n".join(ret))
|
| 1040 |
+
|
| 1041 |
+
if '--variedades' in sys.argv:
|
| 1042 |
+
ret = wsltv.ConsultarVariedadesClasesTabaco()
|
| 1043 |
+
print("\n".join(ret))
|
| 1044 |
+
|
| 1045 |
+
if '--depositos' in sys.argv:
|
| 1046 |
+
ret = wsltv.ConsultarDepositosAcopio()
|
| 1047 |
+
print("\n".join(ret))
|
| 1048 |
+
|
| 1049 |
+
if '--puntosventa' in sys.argv:
|
| 1050 |
+
ret = wsltv.ConsultarPuntosVentas()
|
| 1051 |
+
print("\n".join(ret))
|
| 1052 |
+
|
| 1053 |
+
print("hecho.")
|
| 1054 |
+
|
| 1055 |
+
except SoapFault as e:
|
| 1056 |
+
print("Falla SOAP:", e.faultcode, e.faultstring.encode("ascii", "ignore"), file=sys.stderr)
|
| 1057 |
+
sys.exit(3)
|
| 1058 |
+
except Exception as e:
|
| 1059 |
+
try:
|
| 1060 |
+
print(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[0], file=sys.stderr)
|
| 1061 |
+
except BaseException:
|
| 1062 |
+
print("ExcepciΓ³n no disponible:", type(e), file=sys.stderr)
|
| 1063 |
+
if DEBUG:
|
| 1064 |
+
raise
|
| 1065 |
+
sys.exit(5)
|
| 1066 |
+
finally:
|
| 1067 |
+
if XML:
|
| 1068 |
+
open("wsltv_request.xml", "w").write(wsltv.client.xml_request)
|
| 1069 |
+
open("wsltv_response.xml", "w").write(wsltv.client.xml_response)
|
app/pyafipws/wslum.py
ADDED
|
@@ -0,0 +1,923 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
import shelve
|
| 15 |
+
import sys
|
| 16 |
+
import datetime
|
| 17 |
+
import decimal
|
| 18 |
+
import os
|
| 19 |
+
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, BaseWS, inicializar_y_capturar_excepciones, get_install_dir
|
| 20 |
+
from . import utils
|
| 21 |
+
from fpdf import Template
|
| 22 |
+
from pysimplesoap.client import SoapFault
|
| 23 |
+
import pprint
|
| 24 |
+
import traceback
|
| 25 |
+
"""MΓ³dulo para obtener cΓ³digo de autorizaciΓ³n electrΓ³nica (CAE) para
|
| 26 |
+
LiquidaciΓ³n Γnica Mensual (lecherΓa) del web service WSLUM de AFIP
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 30 |
+
__copyright__ = "Copyright (C) 2016 Mariano Reingart"
|
| 31 |
+
__license__ = "GPL 3.0"
|
| 32 |
+
__version__ = "1.03b"
|
| 33 |
+
|
| 34 |
+
LICENCIA = """
|
| 35 |
+
wslum.py: Interfaz para generar CΓ³digo de AutorizaciΓ³n ElectrΓ³nica (CAE) para
|
| 36 |
+
LiquidaciΓ³n Γnica Mensual de lecherΓa (LumService)
|
| 37 |
+
Copyright (C) 2016 Mariano Reingart reingart@gmail.com
|
| 38 |
+
http://www.sistemasagiles.com.ar/trac/wiki/LiquidacionUnicaMensualLecheria
|
| 39 |
+
|
| 40 |
+
Este progarma es software libre, se entrega ABSOLUTAMENTE SIN GARANTIA
|
| 41 |
+
y es bienvenido a redistribuirlo respetando la licencia GPLv3.
|
| 42 |
+
|
| 43 |
+
Para informaciΓ³n adicional sobre garantΓa, soporte tΓ©cnico comercial
|
| 44 |
+
e incorporaciΓ³n/distribuciΓ³n en programas propietarios ver PyAfipWs:
|
| 45 |
+
http://www.sistemasagiles.com.ar/trac/wiki/PyAfipWs
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
AYUDA = """
|
| 49 |
+
Opciones:
|
| 50 |
+
--ayuda: este mensaje
|
| 51 |
+
|
| 52 |
+
--debug: modo depuraciΓ³n (detalla y confirma las operaciones)
|
| 53 |
+
--formato: muestra el formato de los archivos de entrada/salida
|
| 54 |
+
--prueba: genera y autoriza una liquidaciΓ³n de prueba (no usar en producciΓ³n!)
|
| 55 |
+
--xml: almacena los requerimientos y respuestas XML (depuraciΓ³n)
|
| 56 |
+
--json: utilizar formato json para el archivo de intercambio
|
| 57 |
+
--dummy: consulta estado de servidores
|
| 58 |
+
|
| 59 |
+
--autorizar: Autorizar LiquidaciΓ³n Γnica Mensual (lecherΓa) (generarLiquidacion)
|
| 60 |
+
--ult: Consulta el ΓΊltimo nΓΊmero de orden registrado en AFIP
|
| 61 |
+
(consultarUltimoComprobanteXPuntoVenta)
|
| 62 |
+
--consultar: Consulta una liquidaciΓ³n registrada en AFIP
|
| 63 |
+
(consultarLiquidacionXNroComprobante / consultarLiquidacionXCAE)
|
| 64 |
+
|
| 65 |
+
--pdf: descarga la liquidaciΓ³n en formato PDF
|
| 66 |
+
--mostrar: muestra el documento PDF generado (usar con --pdf)
|
| 67 |
+
--imprimir: imprime el documento PDF generado (usar con --mostrar y --pdf)
|
| 68 |
+
|
| 69 |
+
--provincias: obtiene el listado de provincias (cΓ³digo/descripciΓ³n)
|
| 70 |
+
--localidades: obtiene el listado de localidades para una provincia
|
| 71 |
+
--bonificaciones_penalizaciones: obtiene el listado de tributos
|
| 72 |
+
--otros_impuestos: obtiene el listado de las retenciones de tabaco
|
| 73 |
+
--puntosventa: obtiene el listado de puntos de venta habilitados
|
| 74 |
+
|
| 75 |
+
Ver wslum.ini para parΓ‘metros de configuraciΓ³n (URL, certificados, etc.)"
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# importo funciones compartidas:
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
WSDL = "https://fwshomo.afip.gov.ar/wslum/LumService?wsdl"
|
| 83 |
+
#WSDL = "https://serviciosjava.afip.gov.ar/wslum/LumService?wsdl"
|
| 84 |
+
|
| 85 |
+
DEBUG = False
|
| 86 |
+
XML = False
|
| 87 |
+
CONFIG_FILE = "wslum.ini"
|
| 88 |
+
HOMO = True
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class WSLUM(BaseWS):
|
| 92 |
+
"Interfaz para el WebService de LiquidaciΓ³n Γnica Mensual (lecherΓa)"
|
| 93 |
+
_public_methods_ = ['Conectar', 'Dummy', 'SetTicketAcceso', 'DebugLog',
|
| 94 |
+
'AutorizarLiquidacion',
|
| 95 |
+
'CrearLiquidacion',
|
| 96 |
+
'AgregarTambero', 'AgregarCondicionVenta',
|
| 97 |
+
'AgregarTambo', 'AgregarUbicacionTambo',
|
| 98 |
+
'AgregarBalanceLitrosPorcentajesSolidos',
|
| 99 |
+
'AgregarConceptosBasicosMercadoInterno',
|
| 100 |
+
'AgregarConceptosBasicosMercadoExterno',
|
| 101 |
+
'AgregarBonificacionPenalizacion',
|
| 102 |
+
'AgregarOtroImpuesto',
|
| 103 |
+
'AgregarRemito',
|
| 104 |
+
'ConsultarLiquidacion', 'ConsultarUltimoComprobante',
|
| 105 |
+
'AgregarAjuste',
|
| 106 |
+
'LeerDatosLiquidacion',
|
| 107 |
+
'ConsultarBonificacionesPenalizaciones',
|
| 108 |
+
'ConsultarOtrosImpuestos',
|
| 109 |
+
'ConsultarPuntosVentas',
|
| 110 |
+
'ConsultarProvincias', 'ConsultarLocalidades',
|
| 111 |
+
'MostrarPDF',
|
| 112 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 113 |
+
'SetParametros', 'SetParametro', 'GetParametro',
|
| 114 |
+
]
|
| 115 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 116 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 117 |
+
'Excepcion', 'ErrCode', 'ErrMsg', 'LanzarExcepciones', 'Errores',
|
| 118 |
+
'XmlRequest', 'XmlResponse', 'Version', 'Traceback', 'InstallDir',
|
| 119 |
+
'CAE', 'NroComprobante', 'FechaComprobante',
|
| 120 |
+
'AlicuotaIVA', 'TotalNeto', 'ImporteIVA',
|
| 121 |
+
'TotalBonificacionesCalidad', 'TotalPenalizacionesCalidad',
|
| 122 |
+
'TotalBonificacionesComerciales', 'TotalDebitosComerciales',
|
| 123 |
+
'TotalOtrosImpuestos', 'Total',
|
| 124 |
+
]
|
| 125 |
+
_reg_progid_ = "WSLUM"
|
| 126 |
+
_reg_clsid_ = "{4CBB2DF8-7AAE-434E-916D-9D663BB1CAFC}"
|
| 127 |
+
|
| 128 |
+
# Variables globales para BaseWS:
|
| 129 |
+
HOMO = HOMO
|
| 130 |
+
WSDL = WSDL
|
| 131 |
+
LanzarExcepciones = False
|
| 132 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciΓ³n' or '')
|
| 133 |
+
|
| 134 |
+
def inicializar(self):
|
| 135 |
+
BaseWS.inicializar(self)
|
| 136 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 137 |
+
self.errores = []
|
| 138 |
+
self.CAE = ""
|
| 139 |
+
self.NroComprobante = self.FechaComprobante = ''
|
| 140 |
+
self.AlicuotaIVA = self.TotalNeto = self.ImporteIVA = None
|
| 141 |
+
self.TotalBonificacionesCalidad = None
|
| 142 |
+
self.TotalPenalizacionesCalidad = None
|
| 143 |
+
self.TotalBonificacionesComerciales = None
|
| 144 |
+
self.TotalDebitosComerciales = None
|
| 145 |
+
self.TotalOtrosImpuestos = None
|
| 146 |
+
self.Total = None
|
| 147 |
+
self.datos = {}
|
| 148 |
+
|
| 149 |
+
@inicializar_y_capturar_excepciones
|
| 150 |
+
def Conectar(self, cache=None, url="", proxy="", wrapper="", cacert=None, timeout=30):
|
| 151 |
+
"Establecer la conexiΓ³n a los servidores de la AFIP"
|
| 152 |
+
# llamo al constructor heredado:
|
| 153 |
+
ok = BaseWS.Conectar(self, cache, url, proxy, wrapper, cacert, timeout)
|
| 154 |
+
if False and ok:
|
| 155 |
+
# corrijo ubicaciΓ³n del servidor (puerto htttp 80 en el WSDL)
|
| 156 |
+
location = self.client.services['LumService']['ports']['LumEndPoint']['location']
|
| 157 |
+
if location.startswith("http://"):
|
| 158 |
+
print("Corrigiendo WSDL ...", location, end=' ')
|
| 159 |
+
location = location.replace("http://", "https://").replace(":80", ":443")
|
| 160 |
+
self.client.services['LumService']['ports']['LumEndPoint']['location'] = location
|
| 161 |
+
print(location)
|
| 162 |
+
return ok
|
| 163 |
+
|
| 164 |
+
def __analizar_errores(self, ret):
|
| 165 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 166 |
+
errores = []
|
| 167 |
+
if 'errores' in ret:
|
| 168 |
+
errores.extend(ret['errores'])
|
| 169 |
+
if errores:
|
| 170 |
+
self.Errores = ["%(codigo)s: %(descripcion)s" % err['error'][0]
|
| 171 |
+
for err in errores]
|
| 172 |
+
self.errores = [
|
| 173 |
+
{'codigo': err['error'][0]['codigo'],
|
| 174 |
+
'descripcion': err['error'][0]['descripcion'].replace("\n", "")
|
| 175 |
+
.replace("\r", "")}
|
| 176 |
+
for err in errores]
|
| 177 |
+
self.ErrCode = ' '.join(self.Errores)
|
| 178 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 179 |
+
|
| 180 |
+
@inicializar_y_capturar_excepciones
|
| 181 |
+
def Dummy(self):
|
| 182 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 183 |
+
results = self.client.dummy()['respuesta']
|
| 184 |
+
self.AppServerStatus = str(results['appserver'])
|
| 185 |
+
self.DbServerStatus = str(results['dbserver'])
|
| 186 |
+
self.AuthServerStatus = str(results['authserver'])
|
| 187 |
+
return True
|
| 188 |
+
|
| 189 |
+
@inicializar_y_capturar_excepciones
|
| 190 |
+
def CrearLiquidacion(self, tipo_cbte, pto_vta, nro_cbte, fecha, periodo,
|
| 191 |
+
iibb_adquirente=None, domicilio_sede=None,
|
| 192 |
+
inscripcion_registro_publico=None,
|
| 193 |
+
datos_adicionales=None, alicuota_iva=None, **kwargs):
|
| 194 |
+
"Inicializa internamente los datos de una liquidaciΓ³n para autorizar"
|
| 195 |
+
# creo el diccionario con los campos generales de la liquidaciΓ³n:
|
| 196 |
+
liq = {'tipoComprobante': tipo_cbte, 'puntoVenta': pto_vta,
|
| 197 |
+
'nroComprobante': nro_cbte, 'fechaComprobante': fecha,
|
| 198 |
+
'periodo': periodo, 'iibbAdquirente': iibb_adquirente,
|
| 199 |
+
'domicilioSede': domicilio_sede,
|
| 200 |
+
'inscripcionRegistroPublico': inscripcion_registro_publico,
|
| 201 |
+
'datosAdicionales': datos_adicionales,
|
| 202 |
+
'alicuotaIVA': alicuota_iva,
|
| 203 |
+
}
|
| 204 |
+
liq["condicionVenta"] = []
|
| 205 |
+
self.solicitud = dict(liquidacion=liq,
|
| 206 |
+
bonificacionPenalizacion=[],
|
| 207 |
+
otroImpuesto=[],
|
| 208 |
+
remito=[]
|
| 209 |
+
)
|
| 210 |
+
return True
|
| 211 |
+
|
| 212 |
+
@inicializar_y_capturar_excepciones
|
| 213 |
+
def AgregarCondicionVenta(self, codigo, descripcion=None, **kwargs):
|
| 214 |
+
"Agrego una o mΓ‘s condicion de venta a la liq."
|
| 215 |
+
cond = {'codigo': codigo, 'descripcion': descripcion}
|
| 216 |
+
self.solicitud['liquidacion']['condicionVenta'].append(cond)
|
| 217 |
+
return True
|
| 218 |
+
|
| 219 |
+
@inicializar_y_capturar_excepciones
|
| 220 |
+
def AgregarTambero(self, cuit, iibb=None, **kwargs):
|
| 221 |
+
"Agrego los datos del productor a la liq."
|
| 222 |
+
tambero = {'cuit': cuit, 'iibb': iibb}
|
| 223 |
+
self.solicitud['tambero'] = tambero
|
| 224 |
+
return True
|
| 225 |
+
|
| 226 |
+
@inicializar_y_capturar_excepciones
|
| 227 |
+
def AgregarTambo(self, nro_tambo_interno, nro_renspa,
|
| 228 |
+
fecha_venc_cert_tuberculosis, fecha_venc_cert_brucelosis,
|
| 229 |
+
nro_tambo_provincial=None, **kwargs):
|
| 230 |
+
"Agrego los datos del productor a la liq."
|
| 231 |
+
tambo = {'nroTamboInterno': nro_tambo_interno,
|
| 232 |
+
'nroTamboProvincial': nro_tambo_provincial,
|
| 233 |
+
'nroRenspa': nro_renspa,
|
| 234 |
+
'ubicacionTambo': {},
|
| 235 |
+
'fechaVencCertTuberculosis': fecha_venc_cert_tuberculosis,
|
| 236 |
+
'fechaVencCertBrucelosis': fecha_venc_cert_brucelosis}
|
| 237 |
+
self.solicitud['tambo'] = tambo
|
| 238 |
+
return True
|
| 239 |
+
|
| 240 |
+
@inicializar_y_capturar_excepciones
|
| 241 |
+
def AgregarUbicacionTambo(self, latitud, longitud, domicilio,
|
| 242 |
+
cod_localidad, cod_provincia, codigo_postal,
|
| 243 |
+
nombre_partido_depto, **kwargs):
|
| 244 |
+
"Agrego los datos del productor a la liq."
|
| 245 |
+
ubic_tambo = {'latitud': latitud,
|
| 246 |
+
'longitud': longitud,
|
| 247 |
+
'domicilio': domicilio,
|
| 248 |
+
'codLocalidad': cod_localidad,
|
| 249 |
+
'codProvincia': cod_provincia,
|
| 250 |
+
'nombrePartidoDepto': nombre_partido_depto,
|
| 251 |
+
'codigoPostal': codigo_postal}
|
| 252 |
+
self.solicitud['tambo']['ubicacionTambo'] = ubic_tambo
|
| 253 |
+
return True
|
| 254 |
+
|
| 255 |
+
@inicializar_y_capturar_excepciones
|
| 256 |
+
def AgregarBalanceLitrosPorcentajesSolidos(self, litros_remitidos, litros_decomisados,
|
| 257 |
+
kg_grasa, kg_proteina, **kwargs):
|
| 258 |
+
"Agrega balance litros y porcentajes sΓ³lidos a la liq. (obligatorio)"
|
| 259 |
+
d = {'litrosRemitidos': litros_remitidos,
|
| 260 |
+
'litrosDecomisados': litros_decomisados,
|
| 261 |
+
'kgGrasa': kg_grasa,
|
| 262 |
+
'kgProteina': kg_proteina}
|
| 263 |
+
self.solicitud['balanceLitrosPorcentajesSolidos'] = d
|
| 264 |
+
|
| 265 |
+
@inicializar_y_capturar_excepciones
|
| 266 |
+
def AgregarConceptosBasicosMercadoInterno(self, kg_produccion_gb, precio_por_kg_produccion_gb,
|
| 267 |
+
kg_produccion_pr, precio_por_kg_produccion_pr,
|
| 268 |
+
kg_crecimiento_gb, precio_por_kg_crecimiento_gb,
|
| 269 |
+
kg_crecimiento_pr, precio_por_kg_crecimiento_pr,
|
| 270 |
+
**kwargs):
|
| 271 |
+
"Agrega balance litros y porcentajes sΓ³lidos (mercado interno)"
|
| 272 |
+
d = {'kgProduccionGB': kg_produccion_gb,
|
| 273 |
+
'precioPorKgProduccionGB': precio_por_kg_produccion_gb,
|
| 274 |
+
'kgProduccionPR': kg_produccion_pr,
|
| 275 |
+
'precioPorKgProduccionPR': precio_por_kg_produccion_pr,
|
| 276 |
+
'kgCrecimientoGB': kg_crecimiento_gb,
|
| 277 |
+
'precioPorKgCrecimientoGB': precio_por_kg_crecimiento_gb,
|
| 278 |
+
'kgCrecimientoPR': kg_crecimiento_pr,
|
| 279 |
+
'precioPorKgCrecimientoPR': precio_por_kg_crecimiento_pr}
|
| 280 |
+
self.solicitud['conceptosBasicosMercadoInterno'] = d
|
| 281 |
+
return True
|
| 282 |
+
|
| 283 |
+
@inicializar_y_capturar_excepciones
|
| 284 |
+
def AgregarConceptosBasicosMercadoExterno(self, kg_produccion_gb, precio_por_kg_produccion_gb,
|
| 285 |
+
kg_produccion_pr, precio_por_kg_produccion_pr,
|
| 286 |
+
kg_crecimiento_gb, precio_por_kg_crecimiento_gb,
|
| 287 |
+
kg_crecimiento_pr, precio_por_kg_crecimiento_pr,
|
| 288 |
+
**kwargs):
|
| 289 |
+
"Agrega balance litros y porcentajes sΓ³lidos (mercado externo)"
|
| 290 |
+
d = {'kgProduccionGB': kg_produccion_gb,
|
| 291 |
+
'precioPorKgProduccionGB': precio_por_kg_produccion_gb,
|
| 292 |
+
'kgProduccionPR': kg_produccion_pr,
|
| 293 |
+
'precioPorKgProduccionPR': precio_por_kg_produccion_pr,
|
| 294 |
+
'kgCrecimientoGB': kg_crecimiento_gb,
|
| 295 |
+
'precioPorKgCrecimientoGB': precio_por_kg_crecimiento_gb,
|
| 296 |
+
'kgCrecimientoPR': kg_crecimiento_pr,
|
| 297 |
+
'precioPorKgCrecimientoPR': precio_por_kg_crecimiento_pr}
|
| 298 |
+
self.solicitud['conceptosBasicosMercadoExterno'] = d
|
| 299 |
+
|
| 300 |
+
@inicializar_y_capturar_excepciones
|
| 301 |
+
def AgregarBonificacionPenalizacion(self, codigo, detalle, resultado=None,
|
| 302 |
+
porcentaje=None, importe=None, **kwargs):
|
| 303 |
+
"Agrega la informaciΓ³n referente a las bonificaciones o penalizaciones"
|
| 304 |
+
ret = dict(codBonificacionPenalizacion=codigo, detalle=detalle,
|
| 305 |
+
resultado=resultado, porcentajeAAplicar=porcentaje,
|
| 306 |
+
importe=importe)
|
| 307 |
+
self.solicitud['bonificacionPenalizacion'].append(ret)
|
| 308 |
+
return True
|
| 309 |
+
|
| 310 |
+
@inicializar_y_capturar_excepciones
|
| 311 |
+
def AgregarOtroImpuesto(self, tipo, base_imponible, alicuota, detalle=None):
|
| 312 |
+
"Agrega la informaciΓ³n referente a otros tributos de la liquidaciΓ³n"
|
| 313 |
+
trib = dict(tipo=tipo, baseImponible=base_imponible, alicuota=alicuota,
|
| 314 |
+
detalle=detalle)
|
| 315 |
+
self.solicitud['otroImpuesto'].append(trib)
|
| 316 |
+
return True
|
| 317 |
+
|
| 318 |
+
@inicializar_y_capturar_excepciones
|
| 319 |
+
def AgregarRemito(self, nro_remito):
|
| 320 |
+
"Agrega la informaciΓ³n referente a los remitos (multiples)"
|
| 321 |
+
self.solicitud['remito'].append(nro_remito)
|
| 322 |
+
return True
|
| 323 |
+
|
| 324 |
+
@inicializar_y_capturar_excepciones
|
| 325 |
+
def AutorizarLiquidacion(self):
|
| 326 |
+
"Generar o ajustar una liquidaciΓ³n ΓΊnica y obtener del CAE"
|
| 327 |
+
# limpio los elementos que no correspondan por estar vacios:
|
| 328 |
+
for campo in ["bonificacionPenalizacion", "otroImpuesto"]:
|
| 329 |
+
if campo in self.solicitud and not self.solicitud[campo]:
|
| 330 |
+
del self.solicitud[campo]
|
| 331 |
+
# llamo al webservice:
|
| 332 |
+
ret = self.client.generarLiquidacion(
|
| 333 |
+
auth={
|
| 334 |
+
'token': self.Token, 'sign': self.Sign,
|
| 335 |
+
'cuit': self.Cuit, },
|
| 336 |
+
solicitud=self.solicitud,
|
| 337 |
+
)
|
| 338 |
+
# analizo la respusta
|
| 339 |
+
ret = ret['respuesta']
|
| 340 |
+
self.__analizar_errores(ret)
|
| 341 |
+
liqs = ret.get('liquidacion', [])
|
| 342 |
+
liq = liqs[0] if liqs else None
|
| 343 |
+
self.AnalizarLiquidacion(liq)
|
| 344 |
+
return True
|
| 345 |
+
|
| 346 |
+
def AnalizarLiquidacion(self, liq):
|
| 347 |
+
"MΓ©todo interno para analizar la respuesta de AFIP"
|
| 348 |
+
# proceso los datos bΓ‘sicos de la liquidaciΓ³n (devuelto por consultar):
|
| 349 |
+
if liq:
|
| 350 |
+
cab = liq['encabezado']
|
| 351 |
+
self.CAE = str(cab['cae'])
|
| 352 |
+
self.FechaComprobante = str(cab['fechaComprobante'])
|
| 353 |
+
self.NroComprobante = cab['nroComprobante']
|
| 354 |
+
tot = liq['resumenTotales']
|
| 355 |
+
self.AlicuotaIVA = tot['alicuotaIVA']
|
| 356 |
+
self.TotalNeto = tot['totalNetoLiquidacion']
|
| 357 |
+
self.ImporteIVA = tot['importeIVA']
|
| 358 |
+
self.TotalBonificacionesCalidad = tot['totalBonificacionesCalidad']
|
| 359 |
+
self.TotalPenalizacionesCalidad = tot['totalPenalizacionesCalidad']
|
| 360 |
+
self.TotalBonificacionesComerciales = tot['totalBonificacionesComerciales']
|
| 361 |
+
self.TotalDebitosComerciales = tot['totalDebitosComerciales']
|
| 362 |
+
self.TotalOtrosImpuestos = tot['totalOtrosImpuestos']
|
| 363 |
+
self.Total = tot['totalLiquidacion']
|
| 364 |
+
|
| 365 |
+
# parΓ‘metros de salida:
|
| 366 |
+
self.params_out = dict(
|
| 367 |
+
tipo_cbte=liq['encabezado']['tipoComprobante'],
|
| 368 |
+
pto_vta=liq['encabezado']['puntoVenta'],
|
| 369 |
+
nro_cbte=liq['encabezado']['nroComprobante'],
|
| 370 |
+
fecha=str(liq['encabezado']['fechaComprobante']),
|
| 371 |
+
cae=str(liq['encabezado']['cae']),
|
| 372 |
+
domicilio_comprador=liq['encabezado']['domicilioComprador'],
|
| 373 |
+
tambero=dict(
|
| 374 |
+
cuit=liq['tambero']['cuit'],
|
| 375 |
+
iibb=liq['tambero']['iibb'],
|
| 376 |
+
razon_social=liq['tambero']['razonSocial'],
|
| 377 |
+
situacion_iva=liq['tambero']['situacionIVA'],
|
| 378 |
+
domicilio_fiscal=liq['tambero']['domicilioFiscal'],
|
| 379 |
+
provincia=liq['tambero']['provincia'],
|
| 380 |
+
cod_postal=liq['tambero']['codPostal'],
|
| 381 |
+
),
|
| 382 |
+
resumen_kg_remitidos=liq['resumenKgRemitidos'],
|
| 383 |
+
alicuota_iva=liq['resumenTotales']['alicuotaIVA'],
|
| 384 |
+
importe_iva=liq['resumenTotales']['importeIVA'],
|
| 385 |
+
total_neto=liq['resumenTotales']['totalNetoLiquidacion'],
|
| 386 |
+
total_bonificaciones_calidad=liq['resumenTotales']['totalBonificacionesCalidad'],
|
| 387 |
+
total_penalizaciones_calidad=liq['resumenTotales']['totalPenalizacionesCalidad'],
|
| 388 |
+
total_bonificaciones_comerciales=liq['resumenTotales']['totalBonificacionesComerciales'],
|
| 389 |
+
total_debitos_comerciales=liq['resumenTotales']['totalDebitosComerciales'],
|
| 390 |
+
total_otros_impuestos=liq['resumenTotales']['totalOtrosImpuestos'],
|
| 391 |
+
total=liq['resumenTotales']['totalLiquidacion'],
|
| 392 |
+
bonificacion_penalizacion=[],
|
| 393 |
+
remitos=[],
|
| 394 |
+
otro_impuesto=[],
|
| 395 |
+
pdf=liq.get('pdf'),
|
| 396 |
+
)
|
| 397 |
+
for ret in liq.get('bonificacionPenalizacion', []):
|
| 398 |
+
self.params_out['bonificacion_penalizacion'].append(dict(
|
| 399 |
+
retencion_codigo=ret['codigo'],
|
| 400 |
+
retencion_importe=ret['importe'],
|
| 401 |
+
))
|
| 402 |
+
for trib in liq.get('otroImpuesto', []):
|
| 403 |
+
self.params_out['otro_impuesto'].append(dict(
|
| 404 |
+
tributo_descripcion=trib.get('descripcion', ""),
|
| 405 |
+
tributo_base_imponible=trib['baseImponible'],
|
| 406 |
+
tributo_alicuota=trib['alicuota'],
|
| 407 |
+
tributo_codigo=trib['codigo'],
|
| 408 |
+
tributo_importe=trib['importe'],
|
| 409 |
+
))
|
| 410 |
+
if DEBUG:
|
| 411 |
+
import pprint
|
| 412 |
+
pprint.pprint(self.params_out)
|
| 413 |
+
self.params_out['errores'] = self.errores
|
| 414 |
+
|
| 415 |
+
@inicializar_y_capturar_excepciones
|
| 416 |
+
def AgregarAjuste(self, cai, tipo_cbte, pto_vta, nro_cbte, cae_a_ajustar):
|
| 417 |
+
"Agrega comprobante a ajustar"
|
| 418 |
+
ajuste = self.solicitud['liquidacion']['ajuste'] = {}
|
| 419 |
+
if cae_a_ajustar:
|
| 420 |
+
ajuste['caeAAjustar'] = cae_a_ajustar
|
| 421 |
+
if cai:
|
| 422 |
+
cbte = dict(cai=cai, caeAAjustar=cae_a_ajustar,
|
| 423 |
+
tipoComprobante=tipo_cbte, puntoVenta=pto_vta,
|
| 424 |
+
nroComprobante=nro_cbte)
|
| 425 |
+
ajuste['formularioPapel'] = cbte
|
| 426 |
+
return True
|
| 427 |
+
|
| 428 |
+
@inicializar_y_capturar_excepciones
|
| 429 |
+
def ConsultarLiquidacion(self, tipo_cbte=None, pto_vta=None, nro_cbte=None,
|
| 430 |
+
cae=None, cuit_comprador=None, pdf="liq.pdf"):
|
| 431 |
+
"Consulta una liquidaciΓ³n por No de Comprobante o CAE"
|
| 432 |
+
if cae:
|
| 433 |
+
ret = self.client.consultarLiquidacionPorCae(
|
| 434 |
+
auth={
|
| 435 |
+
'token': self.Token, 'sign': self.Sign,
|
| 436 |
+
'cuit': self.Cuit, },
|
| 437 |
+
solicitud={
|
| 438 |
+
'cae': cae,
|
| 439 |
+
'pdf': pdf and True or False,
|
| 440 |
+
},
|
| 441 |
+
)
|
| 442 |
+
else:
|
| 443 |
+
ret = self.client.consultarLiquidacionPorNroComprobante(
|
| 444 |
+
auth={
|
| 445 |
+
'token': self.Token, 'sign': self.Sign,
|
| 446 |
+
'cuit': self.Cuit, },
|
| 447 |
+
solicitud={
|
| 448 |
+
'cuitComprador': cuit_comprador,
|
| 449 |
+
'puntoVenta': pto_vta,
|
| 450 |
+
'nroComprobante': nro_cbte,
|
| 451 |
+
'tipoComprobante': tipo_cbte,
|
| 452 |
+
'pdf': pdf and True or False,
|
| 453 |
+
},
|
| 454 |
+
)
|
| 455 |
+
ret = ret['respuesta']
|
| 456 |
+
self.__analizar_errores(ret)
|
| 457 |
+
if 'liquidacion' in ret:
|
| 458 |
+
liqs = ret.get('liquidacion', [])
|
| 459 |
+
liq = liqs[0] if liqs else None
|
| 460 |
+
self.AnalizarLiquidacion(liq)
|
| 461 |
+
# guardo el PDF si se indico archivo y vino en la respuesta:
|
| 462 |
+
if pdf and 'pdf' in liq:
|
| 463 |
+
open(pdf, "wb").write(liq['pdf'])
|
| 464 |
+
return True
|
| 465 |
+
|
| 466 |
+
@inicializar_y_capturar_excepciones
|
| 467 |
+
def ConsultarUltimoComprobante(self, tipo_cbte=151, pto_vta=1):
|
| 468 |
+
"Consulta el ΓΊltimo No de Comprobante registrado"
|
| 469 |
+
ret = self.client.consultarUltimoNroComprobantePorPtoVta(
|
| 470 |
+
auth={
|
| 471 |
+
'token': self.Token, 'sign': self.Sign,
|
| 472 |
+
'cuit': self.Cuit, },
|
| 473 |
+
solicitud={
|
| 474 |
+
'puntoVenta': pto_vta,
|
| 475 |
+
'tipoComprobante': tipo_cbte},
|
| 476 |
+
)
|
| 477 |
+
ret = ret['respuesta']
|
| 478 |
+
self.__analizar_errores(ret)
|
| 479 |
+
self.NroComprobante = ret['nroComprobante']
|
| 480 |
+
return True
|
| 481 |
+
|
| 482 |
+
def ConsultarProvincias(self, sep="||"):
|
| 483 |
+
"Consulta las provincias habilitadas"
|
| 484 |
+
ret = self.client.consultarProvincias(
|
| 485 |
+
auth={
|
| 486 |
+
'token': self.Token, 'sign': self.Sign,
|
| 487 |
+
'cuit': self.Cuit, },
|
| 488 |
+
)['respuesta']
|
| 489 |
+
self.__analizar_errores(ret)
|
| 490 |
+
array = ret.get('provincia', [])
|
| 491 |
+
if sep is None:
|
| 492 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 493 |
+
else:
|
| 494 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 495 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 496 |
+
|
| 497 |
+
def ConsultarLocalidades(self, cod_provincia, sep="||"):
|
| 498 |
+
"Consulta las localidades habilitadas"
|
| 499 |
+
ret = self.client.consultarLocalidadesPorProvincia(
|
| 500 |
+
auth={
|
| 501 |
+
'token': self.Token, 'sign': self.Sign,
|
| 502 |
+
'cuit': self.Cuit, },
|
| 503 |
+
solicitud={'codProvincia': cod_provincia},
|
| 504 |
+
)['respuesta']
|
| 505 |
+
self.__analizar_errores(ret)
|
| 506 |
+
array = ret.get('localidad', [])
|
| 507 |
+
if sep is None:
|
| 508 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 509 |
+
else:
|
| 510 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 511 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 512 |
+
|
| 513 |
+
def ConsultarCondicionesVenta(self, sep="||"):
|
| 514 |
+
"Retorna un listado de cΓ³digos y descripciones de las condiciones de ventas"
|
| 515 |
+
ret = self.client.consultarCondicionesVenta(
|
| 516 |
+
auth={
|
| 517 |
+
'token': self.Token, 'sign': self.Sign,
|
| 518 |
+
'cuit': self.Cuit, },
|
| 519 |
+
)['respuesta']
|
| 520 |
+
self.__analizar_errores(ret)
|
| 521 |
+
array = ret.get('condicionVenta', [])
|
| 522 |
+
if sep is None:
|
| 523 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 524 |
+
else:
|
| 525 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 526 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 527 |
+
|
| 528 |
+
def ConsultarOtrosImpuestos(self, sep="||"):
|
| 529 |
+
"Retorna un listado de tributos con cΓ³digo, descripciΓ³n y signo."
|
| 530 |
+
ret = self.client.consultarOtrosImpuestos(
|
| 531 |
+
auth={
|
| 532 |
+
'token': self.Token, 'sign': self.Sign,
|
| 533 |
+
'cuit': self.Cuit, },
|
| 534 |
+
)['respuesta']
|
| 535 |
+
self.__analizar_errores(ret)
|
| 536 |
+
array = ret.get('otroImpuesto', [])
|
| 537 |
+
if sep is None:
|
| 538 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 539 |
+
else:
|
| 540 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 541 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 542 |
+
|
| 543 |
+
def ConsultarBonificacionesPenalizaciones(self, sep="||"):
|
| 544 |
+
"Retorna un listado de bonificaciones/penalizaciones con cΓ³digo y descripciΓ³n"
|
| 545 |
+
ret = self.client.consultarBonificacionesPenalizaciones(
|
| 546 |
+
auth={
|
| 547 |
+
'token': self.Token, 'sign': self.Sign,
|
| 548 |
+
'cuit': self.Cuit, },
|
| 549 |
+
)['respuesta']
|
| 550 |
+
self.__analizar_errores(ret)
|
| 551 |
+
self.XmlResponse = self.client.xml_response
|
| 552 |
+
array = ret.get('tipo', [])
|
| 553 |
+
if sep is None:
|
| 554 |
+
# sin separador, devuelve un diccionario con clave cod_variadedad
|
| 555 |
+
# y valor: {"descripcion": ds_variedad, "clases": lista_clases}
|
| 556 |
+
# siendo lista_clases = [{'codigo': ..., 'descripcion': ...}]
|
| 557 |
+
return dict([(it['codigo'], {'descripcion': it['descripcion'],
|
| 558 |
+
'subtipo': it['subtipo']})
|
| 559 |
+
for it in array])
|
| 560 |
+
else:
|
| 561 |
+
# con separador, devuelve una lista de strings:
|
| 562 |
+
# || cod.variedad || desc.variedad || desc.clase || cod.clase ||
|
| 563 |
+
ret = []
|
| 564 |
+
for it in array:
|
| 565 |
+
for subtipo in it['subtipo']:
|
| 566 |
+
ret.append(
|
| 567 |
+
("%s %%s %s %%s %s %%s %s %%s %s %%s %s %%s %s" %
|
| 568 |
+
(sep, sep, sep, sep, sep, sep, sep)) %
|
| 569 |
+
(it['codigo'], it['descripcion'],
|
| 570 |
+
subtipo['descripcion'], subtipo['codigo'],
|
| 571 |
+
subtipo['valor'], subtipo['signo'])
|
| 572 |
+
)
|
| 573 |
+
return ret
|
| 574 |
+
|
| 575 |
+
def ConsultarPuntosVentas(self, sep="||"):
|
| 576 |
+
"Retorna los puntos de ventas autorizados para la utilizacion de WS"
|
| 577 |
+
ret = self.client.consultarPuntosVenta(
|
| 578 |
+
auth={
|
| 579 |
+
'token': self.Token, 'sign': self.Sign,
|
| 580 |
+
'cuit': self.Cuit, },
|
| 581 |
+
)['respuesta']
|
| 582 |
+
self.__analizar_errores(ret)
|
| 583 |
+
array = ret.get('puntoVenta', [])
|
| 584 |
+
if sep is None:
|
| 585 |
+
return dict([(it['codigo'], it['descripcion']) for it in array])
|
| 586 |
+
else:
|
| 587 |
+
return [("%s %%s %s %%s %s" % (sep, sep, sep)) %
|
| 588 |
+
(it['codigo'], it['descripcion']) for it in array]
|
| 589 |
+
|
| 590 |
+
def MostrarPDF(self, archivo, imprimir=False):
|
| 591 |
+
try:
|
| 592 |
+
if sys.platform == "linux2":
|
| 593 |
+
import subprocess
|
| 594 |
+
subprocess.run(["evince", archivo], check=False)
|
| 595 |
+
else:
|
| 596 |
+
operation = imprimir and "print" or ""
|
| 597 |
+
os.startfile(archivo, operation)
|
| 598 |
+
return True
|
| 599 |
+
except Exception as e:
|
| 600 |
+
self.Excepcion = str(e)
|
| 601 |
+
return False
|
| 602 |
+
|
| 603 |
+
|
| 604 |
+
# busco el directorio de instalaciΓ³n (global para que no cambie si usan otra dll)
|
| 605 |
+
INSTALL_DIR = WSLUM.InstallDir = get_install_dir()
|
| 606 |
+
|
| 607 |
+
|
| 608 |
+
if __name__ == '__main__':
|
| 609 |
+
if '--ayuda' in sys.argv:
|
| 610 |
+
print(LICENCIA)
|
| 611 |
+
print(AYUDA)
|
| 612 |
+
sys.exit(0)
|
| 613 |
+
if '--formato' in sys.argv:
|
| 614 |
+
print("Formato:")
|
| 615 |
+
for msg, formato in []:
|
| 616 |
+
comienzo = 1
|
| 617 |
+
print("=== %s ===" % msg)
|
| 618 |
+
for fmt in formato:
|
| 619 |
+
clave, longitud, tipo = fmt[0:3]
|
| 620 |
+
dec = len(fmt) > 3 and fmt[3] or (tipo == 'I' and '2' or '')
|
| 621 |
+
print(" * Campo: %-20s PosiciΓ³n: %3d Longitud: %4d Tipo: %s Decimales: %s" % (
|
| 622 |
+
clave, comienzo, longitud, tipo, dec))
|
| 623 |
+
comienzo += longitud
|
| 624 |
+
sys.exit(0)
|
| 625 |
+
|
| 626 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 627 |
+
import win32com.server.register
|
| 628 |
+
win32com.server.register.UseCommandLine(WSLUM)
|
| 629 |
+
sys.exit(0)
|
| 630 |
+
|
| 631 |
+
import csv
|
| 632 |
+
from configparser import SafeConfigParser
|
| 633 |
+
|
| 634 |
+
from .wsaa import WSAA
|
| 635 |
+
|
| 636 |
+
try:
|
| 637 |
+
|
| 638 |
+
if "--version" in sys.argv:
|
| 639 |
+
print("VersiΓ³n: ", __version__)
|
| 640 |
+
|
| 641 |
+
if len(sys.argv) > 1 and sys.argv[1].endswith(".ini"):
|
| 642 |
+
CONFIG_FILE = sys.argv[1]
|
| 643 |
+
print("Usando configuracion:", CONFIG_FILE)
|
| 644 |
+
|
| 645 |
+
config = SafeConfigParser()
|
| 646 |
+
config.read(CONFIG_FILE)
|
| 647 |
+
CERT = config.get('WSAA', 'CERT')
|
| 648 |
+
PRIVATEKEY = config.get('WSAA', 'PRIVATEKEY')
|
| 649 |
+
CUIT = config.get('WSLUM', 'CUIT')
|
| 650 |
+
ENTRADA = config.get('WSLUM', 'ENTRADA')
|
| 651 |
+
SALIDA = config.get('WSLUM', 'SALIDA')
|
| 652 |
+
|
| 653 |
+
if config.has_option('WSAA', 'URL') and not HOMO:
|
| 654 |
+
WSAA_URL = config.get('WSAA', 'URL')
|
| 655 |
+
else:
|
| 656 |
+
WSAA_URL = None # wsaa.WSAAURL
|
| 657 |
+
if config.has_option('WSLUM', 'URL') and not HOMO:
|
| 658 |
+
WSLUM_URL = config.get('WSLUM', 'URL')
|
| 659 |
+
else:
|
| 660 |
+
WSLUM_URL = WSDL
|
| 661 |
+
|
| 662 |
+
PROXY = config.has_option('WSAA', 'PROXY') and config.get('WSAA', 'PROXY') or None
|
| 663 |
+
CACERT = config.has_option('WSAA', 'CACERT') and config.get('WSAA', 'CACERT') or None
|
| 664 |
+
WRAPPER = config.has_option('WSAA', 'WRAPPER') and config.get('WSAA', 'WRAPPER') or None
|
| 665 |
+
|
| 666 |
+
if config.has_section('DBF'):
|
| 667 |
+
conf_dbf = dict(config.items('DBF'))
|
| 668 |
+
if DEBUG:
|
| 669 |
+
print("conf_dbf", conf_dbf)
|
| 670 |
+
else:
|
| 671 |
+
conf_dbf = {}
|
| 672 |
+
|
| 673 |
+
DEBUG = '--debug' in sys.argv
|
| 674 |
+
XML = '--xml' in sys.argv
|
| 675 |
+
|
| 676 |
+
if DEBUG:
|
| 677 |
+
print("Usando ConfiguraciΓ³n:")
|
| 678 |
+
print("WSAA_URL:", WSAA_URL)
|
| 679 |
+
print("WSLUM_URL:", WSLUM_URL)
|
| 680 |
+
print("CACERT", CACERT)
|
| 681 |
+
print("WRAPPER", WRAPPER)
|
| 682 |
+
# obteniendo el TA
|
| 683 |
+
from .wsaa import WSAA
|
| 684 |
+
wsaa = WSAA()
|
| 685 |
+
ta = wsaa.Autenticar("wslum", CERT, PRIVATEKEY, wsdl=WSAA_URL,
|
| 686 |
+
proxy=PROXY, wrapper=WRAPPER, cacert=CACERT)
|
| 687 |
+
if not ta:
|
| 688 |
+
pass # sys.exit("Imposible autenticar con WSAA: %s" % wsaa.Excepcion)
|
| 689 |
+
|
| 690 |
+
# cliente soap del web service
|
| 691 |
+
wslum = WSLUM()
|
| 692 |
+
wslum.LanzarExcepciones = True
|
| 693 |
+
wslum.Conectar(url=WSLUM_URL, proxy=PROXY, wrapper=WRAPPER, cacert=CACERT)
|
| 694 |
+
wslum.SetTicketAcceso(ta)
|
| 695 |
+
wslum.Cuit = CUIT
|
| 696 |
+
|
| 697 |
+
if '--dummy' in sys.argv:
|
| 698 |
+
ret = wslum.Dummy()
|
| 699 |
+
print("AppServerStatus", wslum.AppServerStatus)
|
| 700 |
+
print("DbServerStatus", wslum.DbServerStatus)
|
| 701 |
+
print("AuthServerStatus", wslum.AuthServerStatus)
|
| 702 |
+
# sys.exit(0)
|
| 703 |
+
|
| 704 |
+
if '--autorizar' in sys.argv:
|
| 705 |
+
|
| 706 |
+
if '--prueba' in sys.argv:
|
| 707 |
+
|
| 708 |
+
# Solicitud 1: Alta de liquidaciΓ³n
|
| 709 |
+
wslum.CrearLiquidacion(tipo_cbte=27, pto_vta=1, nro_cbte=1,
|
| 710 |
+
fecha="2015-12-31", periodo="2015/12",
|
| 711 |
+
iibb_adquirente="123456789012345",
|
| 712 |
+
domicilio_sede="Domicilio Administrativo",
|
| 713 |
+
inscripcion_registro_publico="Nro IGJ",
|
| 714 |
+
datos_adicionales="Datos Adicionales Varios",
|
| 715 |
+
alicuota_iva=21.00)
|
| 716 |
+
wslum.AgregarCondicionVenta(codigo=1, descripcion=None)
|
| 717 |
+
if '--ajuste' in sys.argv:
|
| 718 |
+
wslum.AgregarAjuste(cai="10000000000000",
|
| 719 |
+
tipo_cbte=0, pto_vta=0, nro_cbte=0,
|
| 720 |
+
cae_a_ajustar="75521002437246")
|
| 721 |
+
|
| 722 |
+
wslum.AgregarTambero(cuit=11111111111, iibb="123456789012345")
|
| 723 |
+
|
| 724 |
+
wslum.AgregarTambo(nro_tambo_interno=123456789,
|
| 725 |
+
nro_renspa="12.345.6.78901/12",
|
| 726 |
+
fecha_venc_cert_tuberculosis="2015-01-01",
|
| 727 |
+
fecha_venc_cert_brucelosis="2015-01-01",
|
| 728 |
+
nro_tambo_provincial=100000000)
|
| 729 |
+
wslum.AgregarUbicacionTambo(
|
| 730 |
+
latitud=-34.62987, longitud=-58.65155,
|
| 731 |
+
domicilio="Domicilio Tambo",
|
| 732 |
+
cod_localidad=10109, cod_provincia=1,
|
| 733 |
+
codigo_postal=1234,
|
| 734 |
+
nombre_partido_depto='Partido Tambo')
|
| 735 |
+
|
| 736 |
+
wslum.AgregarBalanceLitrosPorcentajesSolidos(
|
| 737 |
+
litros_remitidos=11000, litros_decomisados=1000,
|
| 738 |
+
kg_grasa=100.00, kg_proteina=100.00)
|
| 739 |
+
|
| 740 |
+
wslum.AgregarConceptosBasicosMercadoInterno(
|
| 741 |
+
kg_produccion_gb=100, precio_por_kg_produccion_gb=5.00,
|
| 742 |
+
kg_produccion_pr=100, precio_por_kg_produccion_pr=5.00,
|
| 743 |
+
kg_crecimiento_gb=0, precio_por_kg_crecimiento_gb=0.00,
|
| 744 |
+
kg_crecimiento_pr=0, precio_por_kg_crecimiento_pr=0.00)
|
| 745 |
+
|
| 746 |
+
wslum.AgregarConceptosBasicosMercadoExterno(
|
| 747 |
+
kg_produccion_gb=0, precio_por_kg_produccion_gb=0.00,
|
| 748 |
+
kg_produccion_pr=0, precio_por_kg_produccion_pr=0.00,
|
| 749 |
+
kg_crecimiento_gb=0, precio_por_kg_crecimiento_gb=0.00,
|
| 750 |
+
kg_crecimiento_pr=0, precio_por_kg_crecimiento_pr=0.00)
|
| 751 |
+
|
| 752 |
+
wslum.AgregarBonificacionPenalizacion(codigo=1,
|
| 753 |
+
detalle="opcional", resultado="400", porcentaje=10.00)
|
| 754 |
+
wslum.AgregarBonificacionPenalizacion(codigo=10,
|
| 755 |
+
detalle="opcional", resultado="2.5", porcentaje=10.00)
|
| 756 |
+
wslum.AgregarBonificacionPenalizacion(codigo=4,
|
| 757 |
+
detalle="opcional", resultado="400", porcentaje=10.00)
|
| 758 |
+
wslum.AgregarBonificacionPenalizacion(codigo=5,
|
| 759 |
+
detalle="opcional", resultado="En Saneamiento",
|
| 760 |
+
porcentaje=10.00)
|
| 761 |
+
|
| 762 |
+
wslum.AgregarOtroImpuesto(tipo=1, base_imponible=100.00,
|
| 763 |
+
alicuota=10.00, detalle="")
|
| 764 |
+
wslum.AgregarOtroImpuesto(tipo=9, base_imponible=100.00,
|
| 765 |
+
alicuota=10.00,
|
| 766 |
+
detalle="Detalle Otras Percepciones")
|
| 767 |
+
wslum.AgregarOtroImpuesto(tipo=8, base_imponible=100.00,
|
| 768 |
+
alicuota=10.00, detalle="")
|
| 769 |
+
|
| 770 |
+
wslum.AgregarRemito(nro_remito="123456789012")
|
| 771 |
+
wslum.AgregarRemito(nro_remito="123456789")
|
| 772 |
+
|
| 773 |
+
else:
|
| 774 |
+
# cargar un archivo de texto:
|
| 775 |
+
with open("wslum.json", "r") as f:
|
| 776 |
+
wslum.solicitud = json.load(f, encoding="utf-8")
|
| 777 |
+
|
| 778 |
+
if '--testing' in sys.argv:
|
| 779 |
+
# mensaje de prueba (no realiza llamada remota),
|
| 780 |
+
# usar solo si no estΓ‘ operativo, cargo respuesta:
|
| 781 |
+
wslum.LoadTestXML("tests/xml/wslum_liq_test_pdf_response.xml")
|
| 782 |
+
import json
|
| 783 |
+
with open("wslum.json", "w") as f:
|
| 784 |
+
json.dump(wslum.solicitud, f, sort_keys=True, indent=4, encoding="utf-8",)
|
| 785 |
+
|
| 786 |
+
print("Liquidacion: pto_vta=%s nro_cbte=%s tipo_cbte=%s" % (
|
| 787 |
+
wslum.solicitud['liquidacion']['puntoVenta'],
|
| 788 |
+
wslum.solicitud['liquidacion']['nroComprobante'],
|
| 789 |
+
wslum.solicitud['liquidacion']['tipoComprobante'],
|
| 790 |
+
))
|
| 791 |
+
|
| 792 |
+
if not '--dummy' in sys.argv:
|
| 793 |
+
print("Autorizando...")
|
| 794 |
+
ret = wslum.AutorizarLiquidacion()
|
| 795 |
+
|
| 796 |
+
if wslum.Excepcion:
|
| 797 |
+
print("EXCEPCION:", wslum.Excepcion, file=sys.stderr)
|
| 798 |
+
if DEBUG:
|
| 799 |
+
print(wslum.Traceback, file=sys.stderr)
|
| 800 |
+
print("Errores:", wslum.Errores)
|
| 801 |
+
print("CAE", wslum.CAE)
|
| 802 |
+
print("FechaComprobante", wslum.FechaComprobante)
|
| 803 |
+
print("NroComprobante", wslum.NroComprobante)
|
| 804 |
+
print("TotalNeto", wslum.TotalNeto)
|
| 805 |
+
print("AlicuotaIVA", wslum.AlicuotaIVA)
|
| 806 |
+
print("ImporteIVA", wslum.ImporteIVA)
|
| 807 |
+
print("TotalBonificacionesCalidad", wslum.TotalBonificacionesCalidad)
|
| 808 |
+
print("TotalPenalizacionesCalidad", wslum.TotalPenalizacionesCalidad)
|
| 809 |
+
print("TotalBonificacionesComerciales", wslum.TotalBonificacionesComerciales)
|
| 810 |
+
print("TotalDebitosComerciales", wslum.TotalDebitosComerciales)
|
| 811 |
+
print("TotalOtrosImpuestos", wslum.TotalOtrosImpuestos)
|
| 812 |
+
print("Total", wslum.Total)
|
| 813 |
+
|
| 814 |
+
pdf = wslum.GetParametro("pdf")
|
| 815 |
+
if pdf:
|
| 816 |
+
open("liq.pdf", "wb").write(pdf)
|
| 817 |
+
|
| 818 |
+
if '--testing' in sys.argv:
|
| 819 |
+
assert wslum.CAE == "75521002437246"
|
| 820 |
+
|
| 821 |
+
if DEBUG:
|
| 822 |
+
pprint.pprint(wslum.params_out)
|
| 823 |
+
|
| 824 |
+
if "--guardar" in sys.argv:
|
| 825 |
+
# grabar un archivo de texto (intercambio) con el resultado:
|
| 826 |
+
liq = wslum.params_out.copy()
|
| 827 |
+
if "pdf" in liq:
|
| 828 |
+
del liq["pdf"] # eliminador binario
|
| 829 |
+
with open("wslum_salida.json", "w") as f:
|
| 830 |
+
json.dump(liq, f, default=str,
|
| 831 |
+
indent=2, sort_keys=True, encoding="utf-8")
|
| 832 |
+
|
| 833 |
+
if '--consultar' in sys.argv:
|
| 834 |
+
tipo_cbte = 27
|
| 835 |
+
pto_vta = 1
|
| 836 |
+
nro_cbte = 0
|
| 837 |
+
cuit = None
|
| 838 |
+
try:
|
| 839 |
+
tipo_cbte = sys.argv[sys.argv.index("--consultar") + 1]
|
| 840 |
+
pto_vta = sys.argv[sys.argv.index("--consultar") + 2]
|
| 841 |
+
nro_cbte = sys.argv[sys.argv.index("--consultar") + 3]
|
| 842 |
+
cuit = sys.argv[sys.argv.index("--consultar") + 4]
|
| 843 |
+
except IndexError:
|
| 844 |
+
pass
|
| 845 |
+
if '--testing' in sys.argv:
|
| 846 |
+
# mensaje de prueba (no realiza llamada remota),
|
| 847 |
+
# usar solo si no estΓ‘ operativo, cargo prueba:
|
| 848 |
+
wslum.LoadTestXML("tests/xml/wslum_cons_test.xml")
|
| 849 |
+
print("Consultando: tipo_cbte=%s pto_vta=%s nro_cbte=%s" % (tipo_cbte, pto_vta, nro_cbte))
|
| 850 |
+
ret = wslum.ConsultarLiquidacion(tipo_cbte, pto_vta, nro_cbte,
|
| 851 |
+
cuit_comprador=cuit)
|
| 852 |
+
print("CAE", wslum.CAE)
|
| 853 |
+
print("Errores:", wslum.Errores)
|
| 854 |
+
|
| 855 |
+
if DEBUG:
|
| 856 |
+
pprint.pprint(wslum.params_out)
|
| 857 |
+
|
| 858 |
+
if '--mostrar' in sys.argv and pdf:
|
| 859 |
+
wslum.MostrarPDF(archivo=pdf,
|
| 860 |
+
imprimir='--imprimir' in sys.argv)
|
| 861 |
+
|
| 862 |
+
if '--ult' in sys.argv:
|
| 863 |
+
tipo_cbte = 27
|
| 864 |
+
pto_vta = 1
|
| 865 |
+
try:
|
| 866 |
+
tipo_cbte = sys.argv[sys.argv.index("--ult") + 1]
|
| 867 |
+
pto_vta = sys.argv[sys.argv.index("--ult") + 2]
|
| 868 |
+
except IndexError:
|
| 869 |
+
pass
|
| 870 |
+
|
| 871 |
+
print("Consultando ultimo nro_cbte para pto_vta=%s" % pto_vta, end=' ')
|
| 872 |
+
ret = wslum.ConsultarUltimoComprobante(tipo_cbte, pto_vta)
|
| 873 |
+
if wslum.Excepcion:
|
| 874 |
+
print("EXCEPCION:", wslum.Excepcion, file=sys.stderr)
|
| 875 |
+
if DEBUG:
|
| 876 |
+
print(wslum.Traceback, file=sys.stderr)
|
| 877 |
+
print("Ultimo Nro de Comprobante", wslum.NroComprobante)
|
| 878 |
+
print("Errores:", wslum.Errores)
|
| 879 |
+
sys.exit(0)
|
| 880 |
+
|
| 881 |
+
# Recuperar parΓ‘metros:
|
| 882 |
+
|
| 883 |
+
if '--provincias' in sys.argv:
|
| 884 |
+
ret = wslum.ConsultarProvincias()
|
| 885 |
+
print("\n".join(ret))
|
| 886 |
+
|
| 887 |
+
if '--localidades' in sys.argv:
|
| 888 |
+
try:
|
| 889 |
+
cod_provincia = sys.argv[sys.argv.index("--localidades") + 1]
|
| 890 |
+
except BaseException:
|
| 891 |
+
cod_provincia = input("Codigo Provincia:")
|
| 892 |
+
ret = wslum.ConsultarLocalidades(cod_provincia)
|
| 893 |
+
print("\n".join(ret))
|
| 894 |
+
|
| 895 |
+
if '--bonificaciones_penalizaciones' in sys.argv:
|
| 896 |
+
ret = wslum.ConsultarBonificacionesPenalizaciones()
|
| 897 |
+
print("\n".join(ret))
|
| 898 |
+
|
| 899 |
+
if '--otros_impuestos' in sys.argv:
|
| 900 |
+
ret = wslum.ConsultarOtrosImpuestos()
|
| 901 |
+
print("\n".join(ret))
|
| 902 |
+
|
| 903 |
+
if '--puntosventa' in sys.argv:
|
| 904 |
+
ret = wslum.ConsultarPuntosVentas()
|
| 905 |
+
print("\n".join(ret))
|
| 906 |
+
|
| 907 |
+
print("hecho.")
|
| 908 |
+
|
| 909 |
+
except SoapFault as e:
|
| 910 |
+
print("Falla SOAP:", e.faultcode, e.faultstring.encode("ascii", "ignore"), file=sys.stderr)
|
| 911 |
+
sys.exit(3)
|
| 912 |
+
except Exception as e:
|
| 913 |
+
try:
|
| 914 |
+
print(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[0], file=sys.stderr)
|
| 915 |
+
except BaseException:
|
| 916 |
+
print("ExcepciΓ³n no disponible:", type(e), file=sys.stderr)
|
| 917 |
+
if DEBUG:
|
| 918 |
+
raise
|
| 919 |
+
sys.exit(5)
|
| 920 |
+
finally:
|
| 921 |
+
if XML:
|
| 922 |
+
open("wslum_request.xml", "w").write(wslum.client.xml_request)
|
| 923 |
+
open("wslum_response.xml", "w").write(wslum.client.xml_response)
|
app/pyafipws/wsmtx.py
ADDED
|
@@ -0,0 +1,1243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: latin-1 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
"""MοΏ½dulo para obtener cοΏ½digo de autorizaciοΏ½n electrοΏ½nico CAE/CAEA webservice
|
| 14 |
+
WSMTX de AFIP (Factura ElectrοΏ½nica Mercado Interno con codificaciοΏ½n de
|
| 15 |
+
productos) segοΏ½n RG2904 (opciοΏ½n A con detalle) y RG2926/10 (CAE anticipado).
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 19 |
+
__copyright__ = "Copyright (C) 2010-2015 Mariano Reingart"
|
| 20 |
+
__license__ = "GPL 3.0"
|
| 21 |
+
__version__ = "1.14a"
|
| 22 |
+
|
| 23 |
+
import datetime
|
| 24 |
+
import decimal
|
| 25 |
+
import os
|
| 26 |
+
import sys
|
| 27 |
+
from .utils import verifica, inicializar_y_capturar_excepciones, BaseWS, get_install_dir
|
| 28 |
+
|
| 29 |
+
HOMO = False
|
| 30 |
+
LANZAR_EXCEPCIONES = True
|
| 31 |
+
WSDL = "https://fwshomo.afip.gov.ar/wsmtxca/services/MTXCAService?wsdl"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class WSMTXCA(BaseWS):
|
| 35 |
+
"Interfaz para el WebService de Factura ElectrοΏ½nica Mercado Interno WSMTXCA"
|
| 36 |
+
_public_methods_ = ['CrearFactura', 'EstablecerCampoFactura', 'AgregarIva', 'AgregarItem',
|
| 37 |
+
'AgregarTributo', 'AgregarCmpAsoc', 'EstablecerCampoItem', 'AgregarOpcional',
|
| 38 |
+
'AutorizarComprobante', 'CAESolicitar', 'AutorizarAjusteIVA',
|
| 39 |
+
'SolicitarCAEA', 'ConsultarCAEA', 'ConsultarCAEAEntreFechas',
|
| 40 |
+
'InformarComprobanteCAEA', 'InformarAjusteIVACAEA',
|
| 41 |
+
'InformarCAEANoUtilizado', 'InformarCAEANoUtilizadoPtoVta',
|
| 42 |
+
'ConsultarUltimoComprobanteAutorizado', 'CompUltimoAutorizado',
|
| 43 |
+
'ConsultarPtosVtaCAEANoInformados',
|
| 44 |
+
'ConsultarComprobante',
|
| 45 |
+
'ConsultarTiposComprobante',
|
| 46 |
+
'ConsultarTiposDocumento',
|
| 47 |
+
'ConsultarAlicuotasIVA',
|
| 48 |
+
'ConsultarCondicionesIVA',
|
| 49 |
+
'ConsultarMonedas',
|
| 50 |
+
'ConsultarUnidadesMedida',
|
| 51 |
+
'ConsultarTiposTributo', 'ConsultarTiposDatosAdicionales',
|
| 52 |
+
'ConsultarCotizacionMoneda',
|
| 53 |
+
'ConsultarPuntosVentaCAE',
|
| 54 |
+
'ConsultarPuntosVentaCAEA',
|
| 55 |
+
'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 56 |
+
'SetParametros', 'SetTicketAcceso', 'GetParametro',
|
| 57 |
+
'Dummy', 'Conectar', 'DebugLog', 'SetTicketAcceso']
|
| 58 |
+
_public_attrs_ = ['Token', 'Sign', 'Cuit',
|
| 59 |
+
'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 60 |
+
'XmlRequest', 'XmlResponse', 'Version', 'InstallDir', 'LanzarExcepciones',
|
| 61 |
+
'Resultado', 'Obs', 'Observaciones', 'ErrCode', 'ErrMsg',
|
| 62 |
+
'EmisionTipo', 'Reproceso', 'Reprocesar', 'Evento',
|
| 63 |
+
'CAE', 'Vencimiento', 'Evento', 'Errores', 'Traceback', 'Excepcion',
|
| 64 |
+
'CAEA', 'Periodo', 'Orden', 'FchVigDesde', 'FchVigHasta', 'FchTopeInf', 'FchProceso',
|
| 65 |
+
'CbteNro', 'FechaCbte', 'PuntoVenta', 'ImpTotal']
|
| 66 |
+
|
| 67 |
+
_reg_progid_ = "WSMTXCA"
|
| 68 |
+
_reg_clsid_ = "{8128E6AB-FB22-4952-8EA6-BD41C29B17CA}"
|
| 69 |
+
|
| 70 |
+
# Variables globales para BaseWS:
|
| 71 |
+
HOMO = HOMO
|
| 72 |
+
WSDL = WSDL
|
| 73 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciοΏ½n' or '')
|
| 74 |
+
Reprocesar = True # recuperar automaticamente CAE emitidos
|
| 75 |
+
LanzarExcepciones = LANZAR_EXCEPCIONES
|
| 76 |
+
factura = None
|
| 77 |
+
|
| 78 |
+
def inicializar(self):
|
| 79 |
+
BaseWS.inicializar(self)
|
| 80 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 81 |
+
self.Resultado = self.Motivo = self.Reproceso = ''
|
| 82 |
+
self.LastID = self.LastCMP = self.CAE = self.Vencimiento = ''
|
| 83 |
+
self.CAEA = None
|
| 84 |
+
self.Periodo = self.Orden = ""
|
| 85 |
+
self.FchVigDesde = self.FchVigHasta = ""
|
| 86 |
+
self.FchTopeInf = self.FchProceso = ""
|
| 87 |
+
self.CbteNro = self.FechaCbte = ImpTotal = None
|
| 88 |
+
self.EmisionTipo = self.Evento = ''
|
| 89 |
+
self.Reproceso = '' # no implementado
|
| 90 |
+
|
| 91 |
+
def __analizar_errores(self, ret):
|
| 92 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 93 |
+
if 'arrayErrores' in ret:
|
| 94 |
+
errores = ret['arrayErrores']
|
| 95 |
+
for error in errores:
|
| 96 |
+
self.Errores.append("%s: %s" % (
|
| 97 |
+
error['codigoDescripcion']['codigo'],
|
| 98 |
+
error['codigoDescripcion']['descripcion'],
|
| 99 |
+
))
|
| 100 |
+
self.ErrMsg = '\n'.join(self.Errores)
|
| 101 |
+
|
| 102 |
+
def Dummy(self):
|
| 103 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 104 |
+
result = self.client.dummy()
|
| 105 |
+
self.AppServerStatus = result['appserver']
|
| 106 |
+
self.DbServerStatus = result['dbserver']
|
| 107 |
+
self.AuthServerStatus = result['authserver']
|
| 108 |
+
return True
|
| 109 |
+
|
| 110 |
+
def CrearFactura(self, concepto=None, tipo_doc=None, nro_doc=None, tipo_cbte=None, punto_vta=None,
|
| 111 |
+
cbt_desde=None, cbt_hasta=None, imp_total=None, imp_tot_conc=None, imp_neto=None,
|
| 112 |
+
imp_subtotal=None, imp_trib=None, imp_op_ex=None, fecha_cbte=None, fecha_venc_pago=None,
|
| 113 |
+
fecha_serv_desde=None, fecha_serv_hasta=None, # --
|
| 114 |
+
moneda_id=None, moneda_ctz=None, observaciones=None, caea=None, fch_venc_cae=None,
|
| 115 |
+
**kwargs
|
| 116 |
+
):
|
| 117 |
+
"Creo un objeto factura (interna)"
|
| 118 |
+
# Creo una factura electronica de exportaciοΏ½n
|
| 119 |
+
fact = {'tipo_doc': tipo_doc, 'nro_doc': nro_doc,
|
| 120 |
+
'tipo_cbte': tipo_cbte, 'punto_vta': punto_vta,
|
| 121 |
+
'cbt_desde': cbt_desde, 'cbt_hasta': cbt_hasta,
|
| 122 |
+
'imp_total': imp_total, 'imp_tot_conc': imp_tot_conc,
|
| 123 |
+
'imp_neto': imp_neto,
|
| 124 |
+
'imp_subtotal': imp_subtotal, # 'imp_iva': imp_iva,
|
| 125 |
+
'imp_trib': imp_trib, 'imp_op_ex': imp_op_ex,
|
| 126 |
+
'fecha_cbte': fecha_cbte,
|
| 127 |
+
'fecha_venc_pago': fecha_venc_pago,
|
| 128 |
+
'moneda_id': moneda_id, 'moneda_ctz': moneda_ctz,
|
| 129 |
+
'concepto': concepto,
|
| 130 |
+
'observaciones': observaciones,
|
| 131 |
+
'cbtes_asoc': [],
|
| 132 |
+
'tributos': [],
|
| 133 |
+
'iva': [],
|
| 134 |
+
'detalles': [],
|
| 135 |
+
}
|
| 136 |
+
if fecha_serv_desde:
|
| 137 |
+
fact['fecha_serv_desde'] = fecha_serv_desde
|
| 138 |
+
if fecha_serv_hasta:
|
| 139 |
+
fact['fecha_serv_hasta'] = fecha_serv_hasta
|
| 140 |
+
if caea:
|
| 141 |
+
fact['caea'] = caea
|
| 142 |
+
if fch_venc_cae:
|
| 143 |
+
fact['fch_venc_cae'] = fch_venc_cae
|
| 144 |
+
|
| 145 |
+
self.factura = fact
|
| 146 |
+
return True
|
| 147 |
+
|
| 148 |
+
def EstablecerCampoFactura(self, campo, valor):
|
| 149 |
+
if campo in self.factura or campo in ('fecha_serv_desde', 'fecha_serv_hasta', 'caea', 'fch_venc_cae'):
|
| 150 |
+
self.factura[campo] = valor
|
| 151 |
+
return True
|
| 152 |
+
else:
|
| 153 |
+
return False
|
| 154 |
+
|
| 155 |
+
def AgregarCmpAsoc(self, tipo=1, pto_vta=0, nro=0, cuit=None, fecha=None, **kwargs):
|
| 156 |
+
"Agrego un comprobante asociado a una factura (interna)"
|
| 157 |
+
cmp_asoc = {
|
| 158 |
+
'tipo': tipo,
|
| 159 |
+
'pto_vta': pto_vta,
|
| 160 |
+
'nro': nro}
|
| 161 |
+
if cuit is not None:
|
| 162 |
+
cmp_asoc['cuit'] = cuit
|
| 163 |
+
if fecha is not None:
|
| 164 |
+
cmp_asoc['fecha'] = fecha
|
| 165 |
+
self.factura['cbtes_asoc'].append(cmp_asoc)
|
| 166 |
+
return True
|
| 167 |
+
|
| 168 |
+
def AgregarTributo(self, tributo_id, desc, base_imp, alic, importe, **kwargs):
|
| 169 |
+
"Agrego un tributo a una factura (interna)"
|
| 170 |
+
tributo = {
|
| 171 |
+
'tributo_id': tributo_id,
|
| 172 |
+
'desc': desc,
|
| 173 |
+
'base_imp': base_imp,
|
| 174 |
+
'importe': importe,
|
| 175 |
+
}
|
| 176 |
+
self.factura['tributos'].append(tributo)
|
| 177 |
+
return True
|
| 178 |
+
|
| 179 |
+
def AgregarIva(self, iva_id, base_imp, importe, **kwargs):
|
| 180 |
+
"Agrego un tributo a una factura (interna)"
|
| 181 |
+
iva = {
|
| 182 |
+
'iva_id': iva_id,
|
| 183 |
+
'importe': importe,
|
| 184 |
+
}
|
| 185 |
+
self.factura['iva'].append(iva)
|
| 186 |
+
return True
|
| 187 |
+
|
| 188 |
+
def AgregarItem(self, u_mtx=None, cod_mtx=None, codigo=None, ds=None, qty=None, umed=None, precio=None, bonif=None,
|
| 189 |
+
iva_id=None, imp_iva=None, imp_subtotal=None, **kwargs):
|
| 190 |
+
"Agrego un item a una factura (interna)"
|
| 191 |
+
# ds = unicode(ds, "latin1") # convierto a latin1
|
| 192 |
+
# Nota: no se calcula neto, iva, etc (deben venir calculados!)
|
| 193 |
+
umed = int(umed)
|
| 194 |
+
if umed == 99:
|
| 195 |
+
imp_subtotal = -abs(float(imp_subtotal))
|
| 196 |
+
imp_iva = -abs(float(imp_iva))
|
| 197 |
+
item = {
|
| 198 |
+
'u_mtx': u_mtx,
|
| 199 |
+
'cod_mtx': cod_mtx,
|
| 200 |
+
'codigo': codigo,
|
| 201 |
+
'ds': ds,
|
| 202 |
+
'qty': qty if umed != 99 else None,
|
| 203 |
+
'umed': umed,
|
| 204 |
+
'precio': precio if umed != 99 else None,
|
| 205 |
+
'bonif': bonif if umed != 99 else None,
|
| 206 |
+
'iva_id': iva_id,
|
| 207 |
+
'imp_iva': imp_iva,
|
| 208 |
+
'imp_subtotal': imp_subtotal,
|
| 209 |
+
}
|
| 210 |
+
self.factura['detalles'].append(item)
|
| 211 |
+
return True
|
| 212 |
+
|
| 213 |
+
def EstablecerCampoItem(self, campo, valor):
|
| 214 |
+
if self.factura['detalles'] and campo in self.factura['detalles'][-1]:
|
| 215 |
+
self.factura['detalles'][-1][campo] = valor
|
| 216 |
+
return True
|
| 217 |
+
else:
|
| 218 |
+
return False
|
| 219 |
+
|
| 220 |
+
def AgregarOpcional(self, opcional_id=0, valor=None, valor2=None,
|
| 221 |
+
valor3=None, valor4=None, valor5=None,
|
| 222 |
+
valor6=None, **kwarg):
|
| 223 |
+
"Agrego un dato adicional a una factura (interna)"
|
| 224 |
+
op = { 'opcional_id': opcional_id, 'valor': valor, 'valor2': valor2,
|
| 225 |
+
'valor3': valor3, 'valor4': valor4, 'valor5': valor5,
|
| 226 |
+
'valor6': valor6 }
|
| 227 |
+
self.factura['opcionales'].append(op)
|
| 228 |
+
return True
|
| 229 |
+
|
| 230 |
+
@inicializar_y_capturar_excepciones
|
| 231 |
+
def AutorizarComprobante(self):
|
| 232 |
+
f = self.factura
|
| 233 |
+
# contruyo la estructura a convertir en XML:
|
| 234 |
+
fact = {
|
| 235 |
+
'codigoTipoDocumento': f['tipo_doc'], 'numeroDocumento': f['nro_doc'],
|
| 236 |
+
'codigoTipoComprobante': f['tipo_cbte'], 'numeroPuntoVenta': f['punto_vta'],
|
| 237 |
+
'numeroComprobante': f['cbt_desde'], 'numeroComprobante': f['cbt_hasta'],
|
| 238 |
+
'importeTotal': f['imp_total'], 'importeNoGravado': f['imp_tot_conc'],
|
| 239 |
+
'importeGravado': f['imp_neto'],
|
| 240 |
+
'importeSubtotal': f['imp_subtotal'], # 'imp_iva': imp_iva,
|
| 241 |
+
'importeOtrosTributos': f['tributos'] and f['imp_trib'] or None,
|
| 242 |
+
'importeExento': f['imp_op_ex'],
|
| 243 |
+
'fechaEmision': f['fecha_cbte'],
|
| 244 |
+
'codigoMoneda': f['moneda_id'], 'cotizacionMoneda': f['moneda_ctz'],
|
| 245 |
+
'codigoConcepto': f['concepto'],
|
| 246 |
+
'observaciones': f['observaciones'],
|
| 247 |
+
'fechaVencimientoPago': f.get('fecha_venc_pago'),
|
| 248 |
+
'fechaServicioDesde': f.get('fecha_serv_desde'),
|
| 249 |
+
'fechaServicioHasta': f.get('fecha_serv_hasta'),
|
| 250 |
+
'arrayComprobantesAsociados': f['cbtes_asoc'] and [{'comprobanteAsociado': {
|
| 251 |
+
'codigoTipoComprobante': cbte_asoc['tipo'],
|
| 252 |
+
'numeroPuntoVenta': cbte_asoc['pto_vta'],
|
| 253 |
+
'numeroComprobante': cbte_asoc['nro'],
|
| 254 |
+
'cuit': cbte_asoc.get('cuit'),
|
| 255 |
+
'fechaEmision': cbte_asoc.get('fecha'),
|
| 256 |
+
}} for cbte_asoc in f['cbtes_asoc']] or None,
|
| 257 |
+
'arrayOtrosTributos': f['tributos'] and [ {'otroTributo': {
|
| 258 |
+
'codigo': tributo['tributo_id'],
|
| 259 |
+
'descripcion': tributo['desc'],
|
| 260 |
+
'baseImponible': tributo['base_imp'],
|
| 261 |
+
'importe': tributo['importe'],
|
| 262 |
+
}} for tributo in f['tributos']] or None,
|
| 263 |
+
'arraySubtotalesIVA': f['iva'] and [{'subtotalIVA': {
|
| 264 |
+
'codigo': iva['iva_id'],
|
| 265 |
+
'importe': iva['importe'],
|
| 266 |
+
}} for iva in f['iva']] or None,
|
| 267 |
+
'arrayItems': f['detalles'] and [{'item': {
|
| 268 |
+
'unidadesMtx': it['u_mtx'],
|
| 269 |
+
'codigoMtx': it['cod_mtx'],
|
| 270 |
+
'codigo': it['codigo'],
|
| 271 |
+
'descripcion': it['ds'],
|
| 272 |
+
'cantidad': it['qty'],
|
| 273 |
+
'codigoUnidadMedida': it['umed'],
|
| 274 |
+
'precioUnitario': it['precio'],
|
| 275 |
+
'importeBonificacion': it['bonif'],
|
| 276 |
+
'codigoCondicionIVA': it['iva_id'],
|
| 277 |
+
'importeIVA': it['imp_iva'] if int(f['tipo_cbte']) not in (6, 7, 8) and it['imp_iva'] is not None else None,
|
| 278 |
+
'importeItem': it['imp_subtotal'],
|
| 279 |
+
}} for it in f['detalles']] or None,
|
| 280 |
+
'arrayDatosAdicionales': f['opcionales'] and [{'datoAdicional': {
|
| 281 |
+
't': dato['opcional_id'],
|
| 282 |
+
'c1': dato.get('valor'),
|
| 283 |
+
'c2': dato.get('valor2'),
|
| 284 |
+
'c3': dato.get('valor3'),
|
| 285 |
+
'c4': dato.get('valor4'),
|
| 286 |
+
'c5': dato.get('valor5'),
|
| 287 |
+
'c6': dato.get('valor6'),
|
| 288 |
+
}} for dato in f['opcionales']] or None,
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
ret = self.client.autorizarComprobante(
|
| 292 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 293 |
+
comprobanteCAERequest=fact,
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
# Reprocesar en caso de error (recuperar CAE emitido anteriormente)
|
| 297 |
+
if self.Reprocesar and ('arrayErrores' in ret):
|
| 298 |
+
for error in ret['arrayErrores']:
|
| 299 |
+
err_code = error['codigoDescripcion']['codigo']
|
| 300 |
+
if ret['resultado'] == 'R' and err_code == 102:
|
| 301 |
+
# guardo los mensajes xml originales
|
| 302 |
+
xml_request = self.client.xml_request
|
| 303 |
+
xml_response = self.client.xml_response
|
| 304 |
+
cae = self.ConsultarComprobante(f['tipo_cbte'], f['punto_vta'], f['cbt_desde'], reproceso=True)
|
| 305 |
+
if cae and self.EmisionTipo == 'CAE':
|
| 306 |
+
self.Reproceso = 'S'
|
| 307 |
+
self.Resultado = 'A' # verificar O
|
| 308 |
+
return cae
|
| 309 |
+
self.Reproceso = 'N'
|
| 310 |
+
# reestablesco los mensajes xml originales
|
| 311 |
+
self.client.xml_request = xml_request
|
| 312 |
+
self.client.xml_response = xml_response
|
| 313 |
+
|
| 314 |
+
self.Resultado = ret['resultado'] # u'A'
|
| 315 |
+
if ret['resultado'] in ("A", "O"):
|
| 316 |
+
cbteresp = ret['comprobanteResponse']
|
| 317 |
+
self.FechaCbte = cbteresp['fechaEmision'].strftime("%Y/%m/%d")
|
| 318 |
+
self.CbteNro = cbteresp['numeroComprobante'] # 1L
|
| 319 |
+
self.PuntoVenta = cbteresp['numeroPuntoVenta'] # 4000
|
| 320 |
+
# self. = cbteresp['cuit'] # 20267565393L
|
| 321 |
+
# self. = cbteresp['codigoTipoComprobante']
|
| 322 |
+
self.Vencimiento = cbteresp['fechaVencimientoCAE'].strftime("%Y/%m/%d")
|
| 323 |
+
self.CAE = str(cbteresp['CAE']) # 60423794871430L
|
| 324 |
+
self.__analizar_errores(ret)
|
| 325 |
+
|
| 326 |
+
for error in ret.get('arrayObservaciones', []):
|
| 327 |
+
self.Observaciones.append("%(codigo)s: %(descripcion)s" % (
|
| 328 |
+
error['codigoDescripcion']))
|
| 329 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 330 |
+
|
| 331 |
+
if 'evento' in ret:
|
| 332 |
+
self.Evento = '%(codigo)s: %(descripcion)s' % ret['evento']
|
| 333 |
+
return self.CAE
|
| 334 |
+
|
| 335 |
+
@inicializar_y_capturar_excepciones
|
| 336 |
+
def CAESolicitar(self):
|
| 337 |
+
try:
|
| 338 |
+
cae = self.AutorizarComprobante() or ''
|
| 339 |
+
self.Excepcion = "OK!"
|
| 340 |
+
except BaseException:
|
| 341 |
+
cae = "ERR"
|
| 342 |
+
finally:
|
| 343 |
+
return cae
|
| 344 |
+
|
| 345 |
+
@inicializar_y_capturar_excepciones
|
| 346 |
+
def AutorizarAjusteIVA(self):
|
| 347 |
+
"EnvοΏ½a la informaciοΏ½n del comprobante de ajuste de IVA que desea autorizar"
|
| 348 |
+
f = self.factura
|
| 349 |
+
# contruyo la estructura a convertir en XML:
|
| 350 |
+
fact = {
|
| 351 |
+
'codigoTipoDocumento': f['tipo_doc'], 'numeroDocumento': f['nro_doc'],
|
| 352 |
+
'codigoTipoComprobante': f['tipo_cbte'], 'numeroPuntoVenta': f['punto_vta'],
|
| 353 |
+
'numeroComprobante': f['cbt_desde'], 'numeroComprobante': f['cbt_hasta'],
|
| 354 |
+
'importeTotal': f['imp_total'], 'importeNoGravado': f['imp_tot_conc'],
|
| 355 |
+
'importeGravado': f['imp_neto'],
|
| 356 |
+
'importeSubtotal': f['imp_subtotal'], # 'imp_iva': imp_iva,
|
| 357 |
+
'importeOtrosTributos': f['tributos'] and f['imp_trib'] or None,
|
| 358 |
+
'importeExento': f['imp_op_ex'],
|
| 359 |
+
'fechaEmision': f['fecha_cbte'],
|
| 360 |
+
'codigoMoneda': f['moneda_id'], 'cotizacionMoneda': f['moneda_ctz'],
|
| 361 |
+
'codigoConcepto': f['concepto'],
|
| 362 |
+
'observaciones': f['observaciones'],
|
| 363 |
+
'fechaVencimientoPago': f.get('fecha_venc_pago'),
|
| 364 |
+
'fechaServicioDesde': f.get('fecha_serv_desde'),
|
| 365 |
+
'fechaServicioHasta': f.get('fecha_serv_hasta'),
|
| 366 |
+
'arrayComprobantesAsociados': f['cbtes_asoc'] and [{'comprobanteAsociado': {
|
| 367 |
+
'codigoTipoComprobante': cbte_asoc['tipo'],
|
| 368 |
+
'numeroPuntoVenta': cbte_asoc['pto_vta'],
|
| 369 |
+
'numeroComprobante': cbte_asoc['nro'],
|
| 370 |
+
'cuit': cbte_asoc.get('cuit'),
|
| 371 |
+
}} for cbte_asoc in f['cbtes_asoc']] or None,
|
| 372 |
+
'arrayOtrosTributos': f['tributos'] and [{'otroTributo': {
|
| 373 |
+
'codigo': tributo['tributo_id'],
|
| 374 |
+
'descripcion': tributo['desc'],
|
| 375 |
+
'baseImponible': tributo['base_imp'],
|
| 376 |
+
'importe': tributo['importe'],
|
| 377 |
+
}} for tributo in f['tributos']] or None,
|
| 378 |
+
'arraySubtotalesIVA': f['iva'] and [{'subtotalIVA': {
|
| 379 |
+
'codigo': iva['iva_id'],
|
| 380 |
+
'importe': iva['importe'],
|
| 381 |
+
}} for iva in f['iva']] or None,
|
| 382 |
+
'arrayItems': f['detalles'] and [{'item': {
|
| 383 |
+
'unidadesMtx': it['u_mtx'],
|
| 384 |
+
'codigoMtx': it['cod_mtx'],
|
| 385 |
+
'codigo': it['codigo'],
|
| 386 |
+
'descripcion': it['ds'],
|
| 387 |
+
'cantidad': it['qty'],
|
| 388 |
+
'codigoUnidadMedida': it['umed'],
|
| 389 |
+
'precioUnitario': it['precio'],
|
| 390 |
+
'importeBonificacion': it['bonif'],
|
| 391 |
+
'codigoCondicionIVA': it['iva_id'],
|
| 392 |
+
'importeIVA': it['imp_iva'] if int(f['tipo_cbte']) not in (6, 7, 8) and it['imp_iva'] is not None else None,
|
| 393 |
+
'importeItem': it['imp_subtotal'],
|
| 394 |
+
}} for it in f['detalles']] or None,
|
| 395 |
+
'arrayDatosAdicionales': f['opcionales'] and [{'datoAdicional': {
|
| 396 |
+
't': dato['opcional_id'],
|
| 397 |
+
'c1': dato.get('valor'),
|
| 398 |
+
'c2': dato.get('valor2'),
|
| 399 |
+
'c3': dato.get('valor3'),
|
| 400 |
+
'c4': dato.get('valor4'),
|
| 401 |
+
'c5': dato.get('valor5'),
|
| 402 |
+
'c6': dato.get('valor6'),
|
| 403 |
+
}} for dato in f['opcionales']] or None,
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
ret = self.client.autorizarAjusteIVA(
|
| 407 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 408 |
+
comprobanteCAERequest=fact,
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
self.Resultado = ret['resultado'] # u'A'
|
| 412 |
+
if ret['resultado'] in ("A", "O"):
|
| 413 |
+
cbteresp = ret['comprobanteResponse']
|
| 414 |
+
self.FechaCbte = cbteresp['fechaEmision'].strftime("%Y/%m/%d")
|
| 415 |
+
self.CbteNro = cbteresp['numeroComprobante'] # 1L
|
| 416 |
+
self.PuntoVenta = cbteresp['numeroPuntoVenta'] # 4000
|
| 417 |
+
# self. = cbteresp['cuit'] # 20267565393L
|
| 418 |
+
# self. = cbteresp['codigoTipoComprobante']
|
| 419 |
+
self.Vencimiento = cbteresp['fechaVencimientoCAE'].strftime("%Y/%m/%d")
|
| 420 |
+
self.CAE = str(cbteresp['CAE']) # 60423794871430L
|
| 421 |
+
self.__analizar_errores(ret)
|
| 422 |
+
|
| 423 |
+
for error in ret.get('arrayObservaciones', []):
|
| 424 |
+
self.Observaciones.append("%(codigo)s: %(descripcion)s" % (
|
| 425 |
+
error['codigoDescripcion']))
|
| 426 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 427 |
+
|
| 428 |
+
if 'evento' in ret:
|
| 429 |
+
self.Evento = '%(codigo)s: %(descripcion)s' % ret['evento']
|
| 430 |
+
return self.CAE
|
| 431 |
+
|
| 432 |
+
@inicializar_y_capturar_excepciones
|
| 433 |
+
def SolicitarCAEA(self, periodo, orden):
|
| 434 |
+
"Obtener un CAEA y su respectivo perοΏ½odo de vigencia"
|
| 435 |
+
ret = self.client.solicitarCAEA(
|
| 436 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 437 |
+
solicitudCAEA={
|
| 438 |
+
'periodo': periodo,
|
| 439 |
+
'orden': orden},
|
| 440 |
+
)
|
| 441 |
+
|
| 442 |
+
self.__analizar_errores(ret)
|
| 443 |
+
|
| 444 |
+
if 'CAEAResponse' in ret:
|
| 445 |
+
res = ret['CAEAResponse']
|
| 446 |
+
self.CAEA = res['CAEA']
|
| 447 |
+
self.Periodo = res['periodo']
|
| 448 |
+
self.Orden = res['orden']
|
| 449 |
+
self.FchVigDesde = res['fechaDesde']
|
| 450 |
+
self.FchVigHasta = res['fechaHasta']
|
| 451 |
+
self.FchTopeInf = res['fechaTopeInforme']
|
| 452 |
+
self.FchProceso = res['fechaProceso']
|
| 453 |
+
return self.CAEA and str(self.CAEA) or ''
|
| 454 |
+
|
| 455 |
+
@inicializar_y_capturar_excepciones
|
| 456 |
+
def ConsultarCAEA(self, periodo=None, orden=None, caea=None):
|
| 457 |
+
"MοΏ½todo de consulta de CAEA"
|
| 458 |
+
if periodo and orden:
|
| 459 |
+
anio, mes = int(periodo[0:4]), int(periodo[4:6])
|
| 460 |
+
if int(orden) == 1:
|
| 461 |
+
dias = 1, 15
|
| 462 |
+
else:
|
| 463 |
+
if mes in (1, 3, 5, 7, 8, 10, 12):
|
| 464 |
+
dias = 16, 31
|
| 465 |
+
elif mes in (4, 6, 9, 11):
|
| 466 |
+
dias = 16, 30
|
| 467 |
+
else:
|
| 468 |
+
import calendar
|
| 469 |
+
if calendar.isleap(anio):
|
| 470 |
+
dias = 16, 29 # biciesto
|
| 471 |
+
else:
|
| 472 |
+
dias = 16, 28
|
| 473 |
+
|
| 474 |
+
fecha_desde = "%04d-%02d-%02d" % (anio, mes, dias[0])
|
| 475 |
+
fecha_hasta = "%04d-%02d-%02d" % (anio, mes, dias[1])
|
| 476 |
+
|
| 477 |
+
caeas = self.ConsultarCAEAEntreFechas(fecha_desde, fecha_hasta)
|
| 478 |
+
if caeas:
|
| 479 |
+
caea = caeas[0]
|
| 480 |
+
|
| 481 |
+
if caea:
|
| 482 |
+
ret = self.client.consultarCAEA(
|
| 483 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 484 |
+
CAEA=caea,
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
self.__analizar_errores(ret)
|
| 488 |
+
|
| 489 |
+
if 'CAEAResponse' in ret:
|
| 490 |
+
res = ret['CAEAResponse']
|
| 491 |
+
self.CAEA = res['CAEA']
|
| 492 |
+
self.Periodo = res['periodo']
|
| 493 |
+
self.Orden = res['orden']
|
| 494 |
+
self.FchVigDesde = res['fechaDesde']
|
| 495 |
+
self.FchVigHasta = res['fechaHasta']
|
| 496 |
+
self.FchTopeInf = res['fechaTopeInforme']
|
| 497 |
+
self.FchProceso = res['fechaProceso']
|
| 498 |
+
return self.CAEA and str(self.CAEA) or ''
|
| 499 |
+
|
| 500 |
+
@inicializar_y_capturar_excepciones
|
| 501 |
+
def ConsultarCAEAEntreFechas(self, fecha_desde, fecha_hasta):
|
| 502 |
+
"MοΏ½todo de consulta de CAEA"
|
| 503 |
+
|
| 504 |
+
ret = self.client.consultarCAEAEntreFechas(
|
| 505 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 506 |
+
fechaDesde=fecha_desde,
|
| 507 |
+
fechaHasta=fecha_hasta,
|
| 508 |
+
)
|
| 509 |
+
|
| 510 |
+
self.__analizar_errores(ret)
|
| 511 |
+
|
| 512 |
+
caeas = []
|
| 513 |
+
if 'arrayCAEAResponse' in ret:
|
| 514 |
+
return [res['CAEAResponse']['CAEA'] for res in ret['arrayCAEAResponse']]
|
| 515 |
+
return []
|
| 516 |
+
|
| 517 |
+
@inicializar_y_capturar_excepciones
|
| 518 |
+
def InformarComprobanteCAEA(self):
|
| 519 |
+
"EnvοΏ½a la informaciοΏ½n del comprobante emitido y asociado a un CAEA"
|
| 520 |
+
f = self.factura
|
| 521 |
+
# contruyo la estructura a convertir en XML:
|
| 522 |
+
fact = {
|
| 523 |
+
'codigoTipoDocumento': f['tipo_doc'], 'numeroDocumento': f['nro_doc'],
|
| 524 |
+
'codigoTipoComprobante': f['tipo_cbte'], 'numeroPuntoVenta': f['punto_vta'],
|
| 525 |
+
'numeroComprobante': f['cbt_desde'], 'numeroComprobante': f['cbt_hasta'],
|
| 526 |
+
'codigoTipoAutorizacion': 'A',
|
| 527 |
+
'codigoAutorizacion': f['caea'],
|
| 528 |
+
'importeTotal': f['imp_total'], 'importeNoGravado': f['imp_tot_conc'],
|
| 529 |
+
'importeGravado': f['imp_neto'],
|
| 530 |
+
'importeSubtotal': f['imp_subtotal'], # 'imp_iva': imp_iva,
|
| 531 |
+
'importeOtrosTributos': f['tributos'] and f['imp_trib'] or None,
|
| 532 |
+
'importeExento': f['imp_op_ex'],
|
| 533 |
+
'fechaEmision': f['fecha_cbte'],
|
| 534 |
+
'codigoMoneda': f['moneda_id'], 'cotizacionMoneda': f['moneda_ctz'],
|
| 535 |
+
'codigoConcepto': f['concepto'],
|
| 536 |
+
'observaciones': f['observaciones'],
|
| 537 |
+
'fechaVencimientoPago': f.get('fecha_venc_pago'),
|
| 538 |
+
'fechaServicioDesde': f.get('fecha_serv_desde'),
|
| 539 |
+
'fechaServicioHasta': f.get('fecha_serv_hasta'),
|
| 540 |
+
'arrayComprobantesAsociados': f['cbtes_asoc'] and [{'comprobanteAsociado': {
|
| 541 |
+
'codigoTipoComprobante': cbte_asoc['tipo'],
|
| 542 |
+
'numeroPuntoVenta': cbte_asoc['pto_vta'],
|
| 543 |
+
'numeroComprobante': cbte_asoc['nro'],
|
| 544 |
+
'cuit': cbte_asoc.get('cuit'),
|
| 545 |
+
}} for cbte_asoc in f['cbtes_asoc']] or None,
|
| 546 |
+
'arrayOtrosTributos': f['tributos'] and [{'otroTributo': {
|
| 547 |
+
'codigo': tributo['tributo_id'],
|
| 548 |
+
'descripcion': tributo['desc'],
|
| 549 |
+
'baseImponible': tributo['base_imp'],
|
| 550 |
+
'importe': tributo['importe'],
|
| 551 |
+
}} for tributo in f['tributos']] or None,
|
| 552 |
+
'arraySubtotalesIVA': f['iva'] and [{'subtotalIVA': {
|
| 553 |
+
'codigo': iva['iva_id'],
|
| 554 |
+
'importe': iva['importe'],
|
| 555 |
+
}} for iva in f['iva']] or None,
|
| 556 |
+
'arrayItems': f['detalles'] and [{'item': {
|
| 557 |
+
'unidadesMtx': it['u_mtx'],
|
| 558 |
+
'codigoMtx': it['cod_mtx'],
|
| 559 |
+
'codigo': it['codigo'],
|
| 560 |
+
'descripcion': it['ds'],
|
| 561 |
+
'cantidad': it['qty'],
|
| 562 |
+
'codigoUnidadMedida': it['umed'],
|
| 563 |
+
'precioUnitario': it['precio'],
|
| 564 |
+
'importeBonificacion': it['bonif'],
|
| 565 |
+
'codigoCondicionIVA': it['iva_id'],
|
| 566 |
+
'importeIVA': it['imp_iva'] if int(f['tipo_cbte']) not in (6, 7, 8) and it['imp_iva'] is not None else None,
|
| 567 |
+
'importeItem': it['imp_subtotal'],
|
| 568 |
+
}} for it in f['detalles']] or None,
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
# fecha de vencimiento opcional (igual al οΏ½ltimo dοΏ½a de vigencia del CAEA)
|
| 572 |
+
if 'fch_venc_cae' in f:
|
| 573 |
+
fact['fechaVencimiento'] = f['fch_venc_cae']
|
| 574 |
+
|
| 575 |
+
ret = self.client.informarComprobanteCAEA(
|
| 576 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 577 |
+
comprobanteCAEARequest=fact,
|
| 578 |
+
)
|
| 579 |
+
|
| 580 |
+
# Reprocesar en caso de error (verifica datos informado anteriormente)
|
| 581 |
+
if self.Reprocesar and ('arrayErrores' in ret):
|
| 582 |
+
for error in ret['arrayErrores']:
|
| 583 |
+
err_code = error['codigoDescripcion']['codigo']
|
| 584 |
+
if ret['resultado'] == 'R' and err_code == 703:
|
| 585 |
+
# guardo los mensajes xml originales
|
| 586 |
+
xml_request = self.client.xml_request
|
| 587 |
+
xml_response = self.client.xml_response
|
| 588 |
+
cae = self.ConsultarComprobante(f['tipo_cbte'], f['punto_vta'], f['cbt_desde'], reproceso=True)
|
| 589 |
+
if cae == f['caea'] and self.EmisionTipo == 'CAEA':
|
| 590 |
+
self.Reproceso = 'S'
|
| 591 |
+
self.Resultado = 'A' # verificar O
|
| 592 |
+
return cae
|
| 593 |
+
self.Reproceso = 'N'
|
| 594 |
+
# reestablesco los mensajes xml originales
|
| 595 |
+
self.client.xml_request = xml_request
|
| 596 |
+
self.client.xml_response = xml_response
|
| 597 |
+
|
| 598 |
+
self.Resultado = ret['resultado'] # u'A'
|
| 599 |
+
self.Errores = []
|
| 600 |
+
if ret['resultado'] in ("A", "O"):
|
| 601 |
+
cbteresp = ret['comprobanteCAEAResponse']
|
| 602 |
+
self.FchProceso = ret['fechaProceso'].strftime("%Y-%m-%d")
|
| 603 |
+
self.CbteNro = cbteresp['numeroComprobante'] # 1L
|
| 604 |
+
self.PuntoVenta = cbteresp['numeroPuntoVenta'] # 4000
|
| 605 |
+
if 'fechaVencimientoCAE' in cbteresp:
|
| 606 |
+
self.Vencimiento = cbteresp['fechaVencimientoCAE'].strftime("%Y-%m-%d")
|
| 607 |
+
else:
|
| 608 |
+
self.Vencimiento = ""
|
| 609 |
+
self.CAEA = str(cbteresp['CAEA']) # 60423794871430L
|
| 610 |
+
self.EmisionTipo = 'CAEA'
|
| 611 |
+
self.__analizar_errores(ret)
|
| 612 |
+
|
| 613 |
+
for error in ret.get('arrayObservaciones', []):
|
| 614 |
+
self.Observaciones.append("%(codigo)s: %(descripcion)s" % (
|
| 615 |
+
error['codigoDescripcion']))
|
| 616 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 617 |
+
|
| 618 |
+
if 'evento' in ret:
|
| 619 |
+
self.Evento = '%(codigo)s: %(descripcion)s' % ret['evento']
|
| 620 |
+
return self.CAEA
|
| 621 |
+
|
| 622 |
+
@inicializar_y_capturar_excepciones
|
| 623 |
+
def InformarAjusteIVACAEA(self):
|
| 624 |
+
"EnvοΏ½a la informaciοΏ½n del comprobante de ajuste de IVA emitidos"
|
| 625 |
+
f = self.factura
|
| 626 |
+
# contruyo la estructura a convertir en XML:
|
| 627 |
+
fact = {
|
| 628 |
+
'codigoTipoDocumento': f['tipo_doc'], 'numeroDocumento': f['nro_doc'],
|
| 629 |
+
'codigoTipoComprobante': f['tipo_cbte'], 'numeroPuntoVenta': f['punto_vta'],
|
| 630 |
+
'numeroComprobante': f['cbt_desde'], 'numeroComprobante': f['cbt_hasta'],
|
| 631 |
+
'codigoTipoAutorizacion': 'A',
|
| 632 |
+
'codigoAutorizacion': f['caea'],
|
| 633 |
+
'importeTotal': f['imp_total'], 'importeNoGravado': f['imp_tot_conc'],
|
| 634 |
+
'importeGravado': f['imp_neto'],
|
| 635 |
+
'importeSubtotal': f['imp_subtotal'], # 'imp_iva': imp_iva,
|
| 636 |
+
'importeOtrosTributos': f['tributos'] and f['imp_trib'] or None,
|
| 637 |
+
'importeExento': f['imp_op_ex'],
|
| 638 |
+
'fechaEmision': f['fecha_cbte'],
|
| 639 |
+
'codigoMoneda': f['moneda_id'], 'cotizacionMoneda': f['moneda_ctz'],
|
| 640 |
+
'codigoConcepto': f['concepto'],
|
| 641 |
+
'observaciones': f['observaciones'],
|
| 642 |
+
'fechaVencimientoPago': f.get('fecha_venc_pago'),
|
| 643 |
+
'fechaServicioDesde': f.get('fecha_serv_desde'),
|
| 644 |
+
'fechaServicioHasta': f.get('fecha_serv_hasta'),
|
| 645 |
+
'arrayComprobantesAsociados': f['cbtes_asoc'] and [{'comprobanteAsociado': {
|
| 646 |
+
'codigoTipoComprobante': cbte_asoc['tipo'],
|
| 647 |
+
'numeroPuntoVenta': cbte_asoc['pto_vta'],
|
| 648 |
+
'numeroComprobante': cbte_asoc['nro'],
|
| 649 |
+
}} for cbte_asoc in f['cbtes_asoc']] or None,
|
| 650 |
+
'arrayOtrosTributos': f['tributos'] and [{'otroTributo': {
|
| 651 |
+
'codigo': tributo['tributo_id'],
|
| 652 |
+
'descripcion': tributo['desc'],
|
| 653 |
+
'baseImponible': tributo['base_imp'],
|
| 654 |
+
'importe': tributo['importe'],
|
| 655 |
+
}} for tributo in f['tributos']] or None,
|
| 656 |
+
'arraySubtotalesIVA': f['iva'] and [{'subtotalIVA': {
|
| 657 |
+
'codigo': iva['iva_id'],
|
| 658 |
+
'importe': iva['importe'],
|
| 659 |
+
}} for iva in f['iva']] or None,
|
| 660 |
+
'arrayItems': f['detalles'] and [{'item': {
|
| 661 |
+
'unidadesMtx': it['u_mtx'],
|
| 662 |
+
'codigoMtx': it['cod_mtx'],
|
| 663 |
+
'codigo': it['codigo'],
|
| 664 |
+
'descripcion': it['ds'],
|
| 665 |
+
'cantidad': it['qty'],
|
| 666 |
+
'codigoUnidadMedida': it['umed'],
|
| 667 |
+
'precioUnitario': it['precio'],
|
| 668 |
+
'importeBonificacion': it['bonif'],
|
| 669 |
+
'codigoCondicionIVA': it['iva_id'],
|
| 670 |
+
'importeIVA': it['imp_iva'] if int(f['tipo_cbte']) not in (6, 7, 8) and it['imp_iva'] is not None else None,
|
| 671 |
+
'importeItem': it['imp_subtotal'],
|
| 672 |
+
}} for it in f['detalles']] or None,
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
# fecha de vencimiento opcional (igual al οΏ½ltimo dοΏ½a de vigencia del CAEA)
|
| 676 |
+
if 'fch_venc_cae' in f:
|
| 677 |
+
fact['fechaVencimiento'] = f['fch_venc_cae']
|
| 678 |
+
|
| 679 |
+
ret = self.client.informarAjusteIVACAEA(
|
| 680 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 681 |
+
comprobanteCAEARequest=fact,
|
| 682 |
+
)
|
| 683 |
+
|
| 684 |
+
self.Resultado = ret['resultado'] # u'A'
|
| 685 |
+
if ret['resultado'] in ("A", "O"):
|
| 686 |
+
cbteresp = ret['comprobanteCAEAResponse']
|
| 687 |
+
self.FchProceso = ret['fechaProceso'].strftime("%Y-%m-%d")
|
| 688 |
+
self.CbteNro = cbteresp['numeroComprobante'] # 1L
|
| 689 |
+
self.PuntoVenta = cbteresp['numeroPuntoVenta'] # 4000
|
| 690 |
+
# self. = cbteresp['cuit'] # 20267565393L
|
| 691 |
+
# self. = cbteresp['codigoTipoComprobante']
|
| 692 |
+
if 'fechaVencimientoCAE' in cbteresp:
|
| 693 |
+
self.Vencimiento = cbteresp['fechaVencimientoCAE'].strftime("%Y-%m-%d")
|
| 694 |
+
else:
|
| 695 |
+
self.Vencimiento = ""
|
| 696 |
+
self.CAEA = str(cbteresp['CAEA']) # 60423794871430L
|
| 697 |
+
self.EmisionTipo = 'CAEA'
|
| 698 |
+
self.__analizar_errores(ret)
|
| 699 |
+
|
| 700 |
+
for error in ret.get('arrayObservaciones', []):
|
| 701 |
+
self.Observaciones.append("%(codigo)s: %(descripcion)s" % (
|
| 702 |
+
error['codigoDescripcion']))
|
| 703 |
+
self.Obs = '\n'.join(self.Observaciones)
|
| 704 |
+
|
| 705 |
+
if 'evento' in ret:
|
| 706 |
+
self.Evento = '%(codigo)s: %(descripcion)s' % ret['evento']
|
| 707 |
+
return self.CAE
|
| 708 |
+
|
| 709 |
+
@inicializar_y_capturar_excepciones
|
| 710 |
+
def InformarCAEANoUtilizado(self, caea):
|
| 711 |
+
ret = self.client.informarCAEANoUtilizado(
|
| 712 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 713 |
+
CAEA=caea,
|
| 714 |
+
)
|
| 715 |
+
self.Resultado = ret['resultado'] # u'A'
|
| 716 |
+
if ret['resultado'] in ("A", "O"):
|
| 717 |
+
self.FchProceso = ret['fechaProceso'].strftime("%Y-%m-%d")
|
| 718 |
+
self.CAEA = str(ret['CAEA']) # 60423794871430L
|
| 719 |
+
self.EmisionTipo = 'CAEA'
|
| 720 |
+
self.__analizar_errores(ret)
|
| 721 |
+
if 'evento' in ret:
|
| 722 |
+
self.Evento = '%(codigo)s: %(descripcion)s' % ret['evento']
|
| 723 |
+
return self.Resultado
|
| 724 |
+
|
| 725 |
+
@inicializar_y_capturar_excepciones
|
| 726 |
+
def InformarCAEANoUtilizadoPtoVta(self, caea, punto_vta):
|
| 727 |
+
ret = self.client.informarCAEANoUtilizadoPtoVta(
|
| 728 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 729 |
+
CAEA=caea,
|
| 730 |
+
numeroPuntoVenta=punto_vta,
|
| 731 |
+
)
|
| 732 |
+
self.Resultado = ret['resultado'] # u'A'
|
| 733 |
+
if ret['resultado'] in ("A", "O"):
|
| 734 |
+
self.FchProceso = ret['fechaProceso'].strftime("%Y-%m-%d")
|
| 735 |
+
self.CAEA = str(ret['CAEA']) # 60423794871430L
|
| 736 |
+
self.EmisionTipo = 'CAEA'
|
| 737 |
+
self.PuntoVenta = ret['numeroPuntoVenta'] # 4000
|
| 738 |
+
self.__analizar_errores(ret)
|
| 739 |
+
if 'evento' in ret:
|
| 740 |
+
self.Evento = '%(codigo)s: %(descripcion)s' % ret['evento']
|
| 741 |
+
return self.Resultado
|
| 742 |
+
|
| 743 |
+
@inicializar_y_capturar_excepciones
|
| 744 |
+
def ConsultarUltimoComprobanteAutorizado(self, tipo_cbte, punto_vta):
|
| 745 |
+
ret = self.client.consultarUltimoComprobanteAutorizado(
|
| 746 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 747 |
+
consultaUltimoComprobanteAutorizadoRequest={
|
| 748 |
+
'codigoTipoComprobante': tipo_cbte,
|
| 749 |
+
'numeroPuntoVenta': punto_vta},
|
| 750 |
+
)
|
| 751 |
+
nro = ret.get('numeroComprobante')
|
| 752 |
+
self.__analizar_errores(ret)
|
| 753 |
+
self.CbteNro = nro
|
| 754 |
+
return nro is not None and str(nro) or 0
|
| 755 |
+
|
| 756 |
+
CompUltimoAutorizado = ConsultarUltimoComprobanteAutorizado
|
| 757 |
+
|
| 758 |
+
@inicializar_y_capturar_excepciones
|
| 759 |
+
def ConsultarComprobante(self, tipo_cbte, punto_vta, cbte_nro, reproceso=False):
|
| 760 |
+
"Recuperar los datos completos de un comprobante ya autorizado"
|
| 761 |
+
ret = self.client.consultarComprobante(
|
| 762 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 763 |
+
consultaComprobanteRequest={
|
| 764 |
+
'codigoTipoComprobante': tipo_cbte,
|
| 765 |
+
'numeroPuntoVenta': punto_vta,
|
| 766 |
+
'numeroComprobante': cbte_nro,
|
| 767 |
+
},
|
| 768 |
+
)
|
| 769 |
+
# diferencias si hay reproceso:
|
| 770 |
+
difs = []
|
| 771 |
+
# analizo el resultado:
|
| 772 |
+
if 'comprobante' in ret:
|
| 773 |
+
cbteresp = ret['comprobante']
|
| 774 |
+
if reproceso:
|
| 775 |
+
# verifico los campos registrados coincidan con los enviados:
|
| 776 |
+
f = self.factura
|
| 777 |
+
verificaciones = {
|
| 778 |
+
'codigoTipoComprobante': f['tipo_cbte'],
|
| 779 |
+
'numeroPuntoVenta': f['punto_vta'],
|
| 780 |
+
'codigoConcepto': f['concepto'],
|
| 781 |
+
'codigoTipoDocumento': f['tipo_doc'],
|
| 782 |
+
'numeroDocumento': f['nro_doc'],
|
| 783 |
+
'numeroComprobante': f['cbt_desde'],
|
| 784 |
+
'numeroComprobante': f['cbt_hasta'],
|
| 785 |
+
'fechaEmision': f['fecha_cbte'],
|
| 786 |
+
'importeTotal': decimal.Decimal(str(f['imp_total'])),
|
| 787 |
+
'importeNoGravado': decimal.Decimal(str(f['imp_tot_conc'])),
|
| 788 |
+
'importeGravado': decimal.Decimal(str(f['imp_neto'])),
|
| 789 |
+
'importeExento': decimal.Decimal(str(f['imp_op_ex'])),
|
| 790 |
+
'importeOtrosTributos': f['tributos'] and decimal.Decimal(str(f['imp_trib'])) or None,
|
| 791 |
+
'importeSubtotal': f['imp_subtotal'],
|
| 792 |
+
'fechaServicioDesde': f.get('fecha_serv_desde'),
|
| 793 |
+
'fechaServicioHasta': f.get('fecha_serv_hasta'),
|
| 794 |
+
'fechaVencimientoPago': f.get('fecha_venc_pago'),
|
| 795 |
+
'codigoMoneda': f['moneda_id'],
|
| 796 |
+
'cotizacionMoneda': str(decimal.Decimal(str(f['moneda_ctz']))),
|
| 797 |
+
'arrayItems': [
|
| 798 |
+
{'item': {
|
| 799 |
+
'unidadesMtx': it['u_mtx'],
|
| 800 |
+
'codigoMtx': it['cod_mtx'],
|
| 801 |
+
'codigo': it['codigo'],
|
| 802 |
+
'descripcion': it['ds'],
|
| 803 |
+
'cantidad': it['qty'] and decimal.Decimal(str(it['qty'])),
|
| 804 |
+
'codigoUnidadMedida': it['umed'],
|
| 805 |
+
'precioUnitario': it['precio'] is not None and decimal.Decimal(str(it['precio'])) or None,
|
| 806 |
+
# 'importeBonificacion': it['bonif'],
|
| 807 |
+
'codigoCondicionIVA': decimal.Decimal(str(it['iva_id'])),
|
| 808 |
+
'importeIVA': decimal.Decimal(str(it['imp_iva'])) if int(f['tipo_cbte']) not in (6, 7, 8) and it['imp_iva'] is not None else None,
|
| 809 |
+
'importeItem': decimal.Decimal(str(it['imp_subtotal'])),
|
| 810 |
+
}}
|
| 811 |
+
for it in f['detalles']],
|
| 812 |
+
'arrayComprobantesAsociados': [
|
| 813 |
+
{'comprobanteAsociado': {
|
| 814 |
+
'codigoTipoComprobante': cbte_asoc['tipo'],
|
| 815 |
+
'numeroPuntoVenta': cbte_asoc['pto_vta'],
|
| 816 |
+
'numeroComprobante': cbte_asoc['nro']}}
|
| 817 |
+
for cbte_asoc in f['cbtes_asoc']],
|
| 818 |
+
'arrayOtrosTributos': [
|
| 819 |
+
{'otroTributo': {
|
| 820 |
+
'codigo': tributo['tributo_id'],
|
| 821 |
+
'descripcion': tributo['desc'],
|
| 822 |
+
'baseImponible': decimal.Decimal(str(tributo['base_imp'])),
|
| 823 |
+
'importe': decimal.Decimal(str(tributo['importe'])),
|
| 824 |
+
}}
|
| 825 |
+
for tributo in f['tributos']],
|
| 826 |
+
'arraySubtotalesIVA': [
|
| 827 |
+
{'subtotalIVA': {
|
| 828 |
+
'codigo': iva['iva_id'],
|
| 829 |
+
'importe': decimal.Decimal(str(iva['importe'])),
|
| 830 |
+
}}
|
| 831 |
+
for iva in f['iva']],
|
| 832 |
+
}
|
| 833 |
+
verifica(verificaciones, cbteresp, difs)
|
| 834 |
+
if difs:
|
| 835 |
+
print("Diferencias:", difs)
|
| 836 |
+
self.log("Diferencias: %s" % difs)
|
| 837 |
+
self.FechaCbte = cbteresp['fechaEmision'].strftime("%Y/%m/%d")
|
| 838 |
+
self.CbteNro = cbteresp['numeroComprobante'] # 1L
|
| 839 |
+
self.PuntoVenta = cbteresp['numeroPuntoVenta'] # 4000
|
| 840 |
+
self.Vencimiento = cbteresp['fechaVencimiento'].strftime("%Y/%m/%d")
|
| 841 |
+
self.ImpTotal = str(cbteresp['importeTotal'])
|
| 842 |
+
self.CAE = str(cbteresp['codigoAutorizacion']) # 60423794871430L
|
| 843 |
+
self.EmisionTipo = cbteresp['codigoTipoAutorizacion'] == 'A' and 'CAEA' or 'CAE'
|
| 844 |
+
self.__analizar_errores(ret)
|
| 845 |
+
if not difs:
|
| 846 |
+
return self.CAE
|
| 847 |
+
|
| 848 |
+
@inicializar_y_capturar_excepciones
|
| 849 |
+
def ConsultarTiposComprobante(self):
|
| 850 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 851 |
+
ret = self.client.consultarTiposComprobante(
|
| 852 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 853 |
+
)
|
| 854 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 855 |
+
for p in ret['arrayTiposComprobante']]
|
| 856 |
+
|
| 857 |
+
@inicializar_y_capturar_excepciones
|
| 858 |
+
def ConsultarTiposDocumento(self):
|
| 859 |
+
ret = self.client.consultarTiposDocumento(
|
| 860 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 861 |
+
)
|
| 862 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 863 |
+
for p in ret['arrayTiposDocumento']]
|
| 864 |
+
|
| 865 |
+
@inicializar_y_capturar_excepciones
|
| 866 |
+
def ConsultarAlicuotasIVA(self):
|
| 867 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 868 |
+
ret = self.client.consultarAlicuotasIVA(
|
| 869 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 870 |
+
)
|
| 871 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 872 |
+
for p in ret['arrayAlicuotasIVA']]
|
| 873 |
+
|
| 874 |
+
@inicializar_y_capturar_excepciones
|
| 875 |
+
def ConsultarCondicionesIVA(self):
|
| 876 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 877 |
+
ret = self.client.consultarCondicionesIVA(
|
| 878 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 879 |
+
)
|
| 880 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 881 |
+
for p in ret['arrayCondicionesIVA']]
|
| 882 |
+
|
| 883 |
+
@inicializar_y_capturar_excepciones
|
| 884 |
+
def ConsultarMonedas(self):
|
| 885 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 886 |
+
ret = self.client.consultarMonedas(
|
| 887 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 888 |
+
)
|
| 889 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 890 |
+
for p in ret['arrayMonedas']]
|
| 891 |
+
|
| 892 |
+
@inicializar_y_capturar_excepciones
|
| 893 |
+
def ConsultarUnidadesMedida(self):
|
| 894 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 895 |
+
ret = self.client.consultarUnidadesMedida(
|
| 896 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 897 |
+
)
|
| 898 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 899 |
+
for p in ret['arrayUnidadesMedida']]
|
| 900 |
+
|
| 901 |
+
@inicializar_y_capturar_excepciones
|
| 902 |
+
def ConsultarTiposTributo(self):
|
| 903 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 904 |
+
ret = self.client.consultarTiposTributo(
|
| 905 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 906 |
+
)
|
| 907 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 908 |
+
for p in ret['arrayTiposTributo']]
|
| 909 |
+
|
| 910 |
+
@inicializar_y_capturar_excepciones
|
| 911 |
+
def ConsultarTiposDatosAdicionales(self):
|
| 912 |
+
"Este mοΏ½todo permite consultar los tipos de datos adicionales."
|
| 913 |
+
ret = self.client.consultarTiposDatosAdicionales(
|
| 914 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 915 |
+
)
|
| 916 |
+
return ["%(codigo)s: %(descripcion)s" % p['codigoDescripcion']
|
| 917 |
+
for p in ret['arrayTiposDatosAdicionales']]
|
| 918 |
+
|
| 919 |
+
@inicializar_y_capturar_excepciones
|
| 920 |
+
def ConsultarCotizacionMoneda(self, moneda_id):
|
| 921 |
+
"Este mοΏ½todo permite consultar los tipos de comprobantes habilitados en este WS"
|
| 922 |
+
ret = self.client.consultarCotizacionMoneda(
|
| 923 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 924 |
+
codigoMoneda=moneda_id,
|
| 925 |
+
)
|
| 926 |
+
self.__analizar_errores(ret)
|
| 927 |
+
if 'cotizacionMoneda' in ret:
|
| 928 |
+
return str(ret['cotizacionMoneda'])
|
| 929 |
+
|
| 930 |
+
@inicializar_y_capturar_excepciones
|
| 931 |
+
def ConsultarPuntosVentaCAE(self, fmt="%(numeroPuntoVenta)s: bloqueado=%(bloqueado)s baja=%(fechaBaja)s"):
|
| 932 |
+
"Este mοΏ½todo permite consultar los puntos de venta habilitados para CAE en este WS"
|
| 933 |
+
res = self.client.consultarPuntosVentaCAE(
|
| 934 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 935 |
+
)
|
| 936 |
+
ret = []
|
| 937 |
+
for p in res['arrayPuntosVenta']:
|
| 938 |
+
p = p['puntoVenta']
|
| 939 |
+
if 'fechaBaja' not in p:
|
| 940 |
+
p['fechaBaja'] = ""
|
| 941 |
+
ret.append(fmt % p if fmt else p)
|
| 942 |
+
return ret
|
| 943 |
+
|
| 944 |
+
@inicializar_y_capturar_excepciones
|
| 945 |
+
def ConsultarPuntosVentaCAEA(self, fmt="%(numeroPuntoVenta)s: bloqueado=%(bloqueado)s baja=%(fechaBaja)s"):
|
| 946 |
+
"Este mοΏ½todo permite consultar los puntos de venta habilitados para CAEA en este WS"
|
| 947 |
+
res = self.client.consultarPuntosVentaCAEA(
|
| 948 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 949 |
+
)
|
| 950 |
+
ret = []
|
| 951 |
+
for p in res['arrayPuntosVenta']:
|
| 952 |
+
p = p['puntoVenta']
|
| 953 |
+
if 'fechaBaja' not in p:
|
| 954 |
+
p['fechaBaja'] = ""
|
| 955 |
+
ret.append(fmt % p if fmt else p)
|
| 956 |
+
return ret
|
| 957 |
+
|
| 958 |
+
@inicializar_y_capturar_excepciones
|
| 959 |
+
def ConsultarPtosVtaCAEANoInformados(self, caea):
|
| 960 |
+
"Este mοΏ½todo permite consultar que puntos de venta aοΏ½n no fueron informados para un CAEA determinado."
|
| 961 |
+
ret = self.client.consultarPtosVtaCAEANoInformados(
|
| 962 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 963 |
+
CAEA=caea,
|
| 964 |
+
)
|
| 965 |
+
return [" ".join([("%s=%s" % (k, v)) for k, v in list(p['puntoVenta'].items())])
|
| 966 |
+
for p in ret['arrayPuntosVenta']]
|
| 967 |
+
|
| 968 |
+
|
| 969 |
+
def main():
|
| 970 |
+
"FunciοΏ½n principal de pruebas (obtener CAE)"
|
| 971 |
+
import os
|
| 972 |
+
import time
|
| 973 |
+
|
| 974 |
+
DEBUG = '--debug' in sys.argv
|
| 975 |
+
|
| 976 |
+
# obteniendo el TA para pruebas
|
| 977 |
+
from .wsaa import WSAA
|
| 978 |
+
ta = WSAA().Autenticar("wsmtxca", "reingart.crt", "reingart.key")
|
| 979 |
+
|
| 980 |
+
wsmtxca = WSMTXCA()
|
| 981 |
+
wsmtxca.SetTicketAcceso(ta)
|
| 982 |
+
wsmtxca.Cuit = "20267565393"
|
| 983 |
+
|
| 984 |
+
cache = ""
|
| 985 |
+
if "--prod" in sys.argv:
|
| 986 |
+
wsdl = "https://serviciosjava.afip.gob.ar/wsmtxca/services/MTXCAService?wsdl"
|
| 987 |
+
else:
|
| 988 |
+
wsdl = WSDL
|
| 989 |
+
wsmtxca.Conectar(cache, wsdl, cacert="conf/afip_ca_info.crt")
|
| 990 |
+
|
| 991 |
+
if "--dummy" in sys.argv:
|
| 992 |
+
print(wsmtxca.client.help("dummy"))
|
| 993 |
+
wsmtxca.Dummy()
|
| 994 |
+
print("AppServerStatus", wsmtxca.AppServerStatus)
|
| 995 |
+
print("DbServerStatus", wsmtxca.DbServerStatus)
|
| 996 |
+
print("AuthServerStatus", wsmtxca.AuthServerStatus)
|
| 997 |
+
|
| 998 |
+
if "--prueba" in sys.argv:
|
| 999 |
+
# print wsmtxca.client.help("autorizarComprobante").encode("latin1")
|
| 1000 |
+
try:
|
| 1001 |
+
tipo_cbte = 1
|
| 1002 |
+
punto_vta = 4000
|
| 1003 |
+
cbte_nro = wsmtxca.ConsultarUltimoComprobanteAutorizado(tipo_cbte, punto_vta)
|
| 1004 |
+
fecha = datetime.datetime.now().strftime("%Y-%m-%d")
|
| 1005 |
+
concepto = 3
|
| 1006 |
+
tipo_doc = 80
|
| 1007 |
+
nro_doc = "30000000007"
|
| 1008 |
+
cbte_nro = int(cbte_nro) + 1
|
| 1009 |
+
cbt_desde = cbte_nro
|
| 1010 |
+
cbt_hasta = cbt_desde
|
| 1011 |
+
imp_total = "122.00"
|
| 1012 |
+
imp_tot_conc = "0.00"
|
| 1013 |
+
imp_neto = "100.00"
|
| 1014 |
+
imp_trib = "1.00"
|
| 1015 |
+
imp_op_ex = "0.00"
|
| 1016 |
+
imp_subtotal = "100.00"
|
| 1017 |
+
fecha_cbte = fecha
|
| 1018 |
+
fecha_venc_pago = fecha
|
| 1019 |
+
# Fechas del perοΏ½odo del servicio facturado (solo si concepto = 1?)
|
| 1020 |
+
fecha_serv_desde = fecha
|
| 1021 |
+
fecha_serv_hasta = fecha
|
| 1022 |
+
moneda_id = 'PES'
|
| 1023 |
+
moneda_ctz = '1.000'
|
| 1024 |
+
obs = "Observaciones Comerciales, libre"
|
| 1025 |
+
if '--caea' in sys.argv:
|
| 1026 |
+
periodo = fecha.replace("-", "")[:6]
|
| 1027 |
+
orden = 1 if fecha[-2:] < 16 else 2
|
| 1028 |
+
caea = wsmtxca.ConsultarCAEA(periodo, orden)
|
| 1029 |
+
else:
|
| 1030 |
+
caea = None
|
| 1031 |
+
|
| 1032 |
+
wsmtxca.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
|
| 1033 |
+
cbt_desde, cbt_hasta, imp_total, imp_tot_conc, imp_neto,
|
| 1034 |
+
imp_subtotal, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago,
|
| 1035 |
+
fecha_serv_desde, fecha_serv_hasta, # --
|
| 1036 |
+
moneda_id, moneda_ctz, obs, caea)
|
| 1037 |
+
|
| 1038 |
+
#tipo = 19
|
| 1039 |
+
#pto_vta = 2
|
| 1040 |
+
#nro = 1234
|
| 1041 |
+
#wsmtxca.AgregarCmpAsoc(tipo, pto_vta, nro)
|
| 1042 |
+
|
| 1043 |
+
tributo_id = 99
|
| 1044 |
+
desc = 'Impuesto Municipal Matanza'
|
| 1045 |
+
base_imp = "100.00"
|
| 1046 |
+
alic = "1.00"
|
| 1047 |
+
importe = "1.00"
|
| 1048 |
+
wsmtxca.AgregarTributo(tributo_id, desc, base_imp, alic, importe)
|
| 1049 |
+
|
| 1050 |
+
iva_id = 5 # 21%
|
| 1051 |
+
base_imp = 100
|
| 1052 |
+
importe = 21
|
| 1053 |
+
wsmtxca.AgregarIva(iva_id, base_imp, importe)
|
| 1054 |
+
|
| 1055 |
+
u_mtx = 123456
|
| 1056 |
+
cod_mtx = 1234567890123
|
| 1057 |
+
codigo = "P0001"
|
| 1058 |
+
ds = "Descripcion del producto P0001"
|
| 1059 |
+
qty = 2.00
|
| 1060 |
+
umed = 7
|
| 1061 |
+
precio = 100.00
|
| 1062 |
+
bonif = 0.00
|
| 1063 |
+
iva_id = 5
|
| 1064 |
+
imp_iva = 42.00
|
| 1065 |
+
imp_subtotal = 242.00
|
| 1066 |
+
wsmtxca.AgregarItem(u_mtx, cod_mtx, codigo, ds, qty, umed, precio, bonif,
|
| 1067 |
+
iva_id, imp_iva, imp_subtotal)
|
| 1068 |
+
|
| 1069 |
+
if not "--caea" in sys.argv:
|
| 1070 |
+
# ejemplo descuento (sin precio unitario)
|
| 1071 |
+
wsmtxca.AgregarItem(None, None, None, 'bonificacion',
|
| 1072 |
+
None, 99, None, None, 5, -21, -121)
|
| 1073 |
+
# ejemplo item solo descripciοΏ½n:
|
| 1074 |
+
# wsmtxca.AgregarItem(u_mtx, cod_mtx, codigo, ds, 1, umed,
|
| 1075 |
+
# 0, 0, iva_id, 0, 0)
|
| 1076 |
+
|
| 1077 |
+
# datos de Factura de CrοΏ½dito ElectrοΏ½nica MiPyMEs (FCE):
|
| 1078 |
+
if '--fce' in sys.argv:
|
| 1079 |
+
wsmtxca.AgregarOpcional(21, "2850590940090418135201") # CBU
|
| 1080 |
+
|
| 1081 |
+
print(wsmtxca.factura)
|
| 1082 |
+
|
| 1083 |
+
if '--caea' in sys.argv:
|
| 1084 |
+
wsmtxca.InformarComprobanteCAEA()
|
| 1085 |
+
else:
|
| 1086 |
+
wsmtxca.AutorizarComprobante()
|
| 1087 |
+
|
| 1088 |
+
print("Resultado", wsmtxca.Resultado)
|
| 1089 |
+
print("CAE", wsmtxca.CAE)
|
| 1090 |
+
print("Vencimiento", wsmtxca.Vencimiento)
|
| 1091 |
+
print("Reproceso", wsmtxca.Reproceso)
|
| 1092 |
+
|
| 1093 |
+
print(wsmtxca.Excepcion)
|
| 1094 |
+
print(wsmtxca.ErrMsg)
|
| 1095 |
+
|
| 1096 |
+
cae = wsmtxca.CAE
|
| 1097 |
+
|
| 1098 |
+
if cae:
|
| 1099 |
+
|
| 1100 |
+
wsmtxca.ConsultarComprobante(tipo_cbte, punto_vta, cbte_nro)
|
| 1101 |
+
print("CAE consulta", wsmtxca.CAE, wsmtxca.CAE == cae)
|
| 1102 |
+
print("NRO consulta", wsmtxca.CbteNro, wsmtxca.CbteNro == cbte_nro)
|
| 1103 |
+
print("TOTAL consulta", wsmtxca.ImpTotal, wsmtxca.ImpTotal == imp_total)
|
| 1104 |
+
|
| 1105 |
+
wsmtxca.AnalizarXml("XmlResponse")
|
| 1106 |
+
assert wsmtxca.ObtenerTagXml('codigoAutorizacion') == str(wsmtxca.CAE)
|
| 1107 |
+
assert wsmtxca.ObtenerTagXml('codigoConcepto') == str(concepto)
|
| 1108 |
+
assert wsmtxca.ObtenerTagXml('arrayItems', 0, 'item', 'unidadesMtx') == '123456'
|
| 1109 |
+
|
| 1110 |
+
except BaseException:
|
| 1111 |
+
print(wsmtxca.XmlRequest)
|
| 1112 |
+
print(wsmtxca.XmlResponse)
|
| 1113 |
+
print(wsmtxca.ErrCode)
|
| 1114 |
+
print(wsmtxca.ErrMsg)
|
| 1115 |
+
|
| 1116 |
+
if "--ajustar" in sys.argv:
|
| 1117 |
+
# print wsmtxca.client.help("autorizarComprobante").encode("latin1")
|
| 1118 |
+
try:
|
| 1119 |
+
tipo_cbte = 2
|
| 1120 |
+
punto_vta = 4000
|
| 1121 |
+
cbte_nro = wsmtxca.ConsultarUltimoComprobanteAutorizado(tipo_cbte, punto_vta)
|
| 1122 |
+
fecha = datetime.datetime.now().strftime("%Y-%m-%d")
|
| 1123 |
+
concepto = 3
|
| 1124 |
+
tipo_doc = 80
|
| 1125 |
+
nro_doc = "30000000007"
|
| 1126 |
+
cbte_nro = int(cbte_nro) + 1
|
| 1127 |
+
cbt_desde = cbte_nro
|
| 1128 |
+
cbt_hasta = cbt_desde
|
| 1129 |
+
imp_total = "21.00"
|
| 1130 |
+
imp_tot_conc = "0.00"
|
| 1131 |
+
imp_neto = None
|
| 1132 |
+
imp_trib = "0.00"
|
| 1133 |
+
imp_op_ex = "0.00"
|
| 1134 |
+
imp_subtotal = "0.00"
|
| 1135 |
+
fecha_cbte = fecha
|
| 1136 |
+
fecha_venc_pago = fecha
|
| 1137 |
+
# Fechas del perοΏ½odo del servicio facturado (solo si concepto = 1?)
|
| 1138 |
+
fecha_serv_desde = fecha
|
| 1139 |
+
fecha_serv_hasta = fecha
|
| 1140 |
+
moneda_id = 'PES'
|
| 1141 |
+
moneda_ctz = '1.000'
|
| 1142 |
+
obs = "Observaciones Comerciales, libre"
|
| 1143 |
+
caea = "24163778394093"
|
| 1144 |
+
fch_venc_cae = None
|
| 1145 |
+
|
| 1146 |
+
wsmtxca.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
|
| 1147 |
+
cbt_desde, cbt_hasta, imp_total, imp_tot_conc, imp_neto,
|
| 1148 |
+
imp_subtotal, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago,
|
| 1149 |
+
fecha_serv_desde, fecha_serv_hasta, # --
|
| 1150 |
+
moneda_id, moneda_ctz, obs, caea, fch_venc_cae)
|
| 1151 |
+
|
| 1152 |
+
iva_id = 5 # 21%
|
| 1153 |
+
base_imp = 100
|
| 1154 |
+
importe = 21
|
| 1155 |
+
wsmtxca.AgregarIva(iva_id, base_imp, importe)
|
| 1156 |
+
|
| 1157 |
+
u_mtx = 1
|
| 1158 |
+
cod_mtx = 7790001001139
|
| 1159 |
+
codigo = None
|
| 1160 |
+
ds = "Descripcion del producto P0001"
|
| 1161 |
+
qty = None
|
| 1162 |
+
umed = 7
|
| 1163 |
+
precio = None
|
| 1164 |
+
bonif = None
|
| 1165 |
+
iva_id = 5
|
| 1166 |
+
imp_iva = 21.00
|
| 1167 |
+
imp_subtotal = 21.00
|
| 1168 |
+
wsmtxca.AgregarItem(u_mtx, cod_mtx, codigo, ds, qty, umed, precio, bonif,
|
| 1169 |
+
iva_id, imp_iva, imp_subtotal)
|
| 1170 |
+
|
| 1171 |
+
print(wsmtxca.factura)
|
| 1172 |
+
|
| 1173 |
+
if not caea:
|
| 1174 |
+
wsmtxca.AutorizarAjusteIVA()
|
| 1175 |
+
else:
|
| 1176 |
+
wsmtxca.InformarAjusteIVACAEA()
|
| 1177 |
+
|
| 1178 |
+
print("Resultado", wsmtxca.Resultado)
|
| 1179 |
+
print("CAE", wsmtxca.CAE)
|
| 1180 |
+
print("Vencimiento", wsmtxca.Vencimiento)
|
| 1181 |
+
|
| 1182 |
+
print(wsmtxca.Excepcion)
|
| 1183 |
+
print(wsmtxca.ErrMsg)
|
| 1184 |
+
|
| 1185 |
+
except BaseException:
|
| 1186 |
+
print(wsmtxca.XmlRequest)
|
| 1187 |
+
print(wsmtxca.XmlResponse)
|
| 1188 |
+
print(wsmtxca.ErrCode)
|
| 1189 |
+
print(wsmtxca.ErrMsg)
|
| 1190 |
+
raise
|
| 1191 |
+
|
| 1192 |
+
if "--parametros" in sys.argv:
|
| 1193 |
+
print(wsmtxca.ConsultarTiposComprobante())
|
| 1194 |
+
print(wsmtxca.ConsultarTiposDocumento())
|
| 1195 |
+
print(wsmtxca.ConsultarAlicuotasIVA())
|
| 1196 |
+
print(wsmtxca.ConsultarCondicionesIVA())
|
| 1197 |
+
print(wsmtxca.ConsultarMonedas())
|
| 1198 |
+
print(wsmtxca.ConsultarUnidadesMedida())
|
| 1199 |
+
print(wsmtxca.ConsultarTiposTributo())
|
| 1200 |
+
print(wsmtxca.ConsultarTiposDatosAdicionales())
|
| 1201 |
+
|
| 1202 |
+
if "--cotizacion" in sys.argv:
|
| 1203 |
+
print(wsmtxca.ConsultarCotizacionMoneda('DOL'))
|
| 1204 |
+
|
| 1205 |
+
if "--solicitar-caea" in sys.argv:
|
| 1206 |
+
periodo = sys.argv[sys.argv.index("--solicitar-caea") + 1]
|
| 1207 |
+
orden = sys.argv[sys.argv.index("--solicitar-caea") + 2]
|
| 1208 |
+
|
| 1209 |
+
if DEBUG:
|
| 1210 |
+
print("Consultando CAEA para periodo %s orden %s" % (periodo, orden))
|
| 1211 |
+
|
| 1212 |
+
caea = wsmtxca.ConsultarCAEA(periodo, orden)
|
| 1213 |
+
if not caea:
|
| 1214 |
+
print("Solicitando CAEA para periodo %s orden %s" % (periodo, orden))
|
| 1215 |
+
caea = wsmtxca.SolicitarCAEA(periodo, orden)
|
| 1216 |
+
|
| 1217 |
+
print("CAEA:", caea)
|
| 1218 |
+
|
| 1219 |
+
if wsmtxca.Errores:
|
| 1220 |
+
print("Errores:")
|
| 1221 |
+
for error in wsmtxca.Errores:
|
| 1222 |
+
print(error)
|
| 1223 |
+
|
| 1224 |
+
if DEBUG:
|
| 1225 |
+
print("periodo:", wsmtxca.Periodo)
|
| 1226 |
+
print("orden:", wsmtxca.Orden)
|
| 1227 |
+
print("fch_vig_desde:", wsmtxca.FchVigDesde)
|
| 1228 |
+
print("fch_vig_hasta:", wsmtxca.FchVigHasta)
|
| 1229 |
+
print("fch_tope_inf:", wsmtxca.FchTopeInf)
|
| 1230 |
+
print("fch_proceso:", wsmtxca.FchProceso)
|
| 1231 |
+
|
| 1232 |
+
|
| 1233 |
+
# busco el directorio de instalaciοΏ½n (global para que no cambie si usan otra dll)
|
| 1234 |
+
INSTALL_DIR = WSMTXCA.InstallDir = get_install_dir()
|
| 1235 |
+
|
| 1236 |
+
|
| 1237 |
+
if __name__ == '__main__':
|
| 1238 |
+
|
| 1239 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 1240 |
+
import win32com.server.register
|
| 1241 |
+
win32com.server.register.UseCommandLine(WSMTXCA)
|
| 1242 |
+
else:
|
| 1243 |
+
main()
|
app/pyafipws/wsremcarne.py
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
# -*- coding: utf8 -*-
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by the
|
| 5 |
+
# Free Software Foundation; either version 3, or (at your option) any later
|
| 6 |
+
# version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful, but
|
| 9 |
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
| 10 |
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 11 |
+
# for more details.
|
| 12 |
+
|
| 13 |
+
import base64
|
| 14 |
+
import time
|
| 15 |
+
import sys
|
| 16 |
+
import os
|
| 17 |
+
from .utils import json, BaseWS, inicializar_y_capturar_excepciones, get_install_dir
|
| 18 |
+
from . import utils
|
| 19 |
+
from pysimplesoap.client import SoapFault
|
| 20 |
+
import traceback
|
| 21 |
+
from .utils import date
|
| 22 |
+
"""MΓ³dulo para obtener Remito Electronico Carnico:
|
| 23 |
+
del web service WSRemCarne versiΓ³n 1.0 de AFIP (RG4256/18 y RG4303/18)
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
__author__ = "Mariano Reingart <reingart@gmail.com>"
|
| 27 |
+
__copyright__ = "Copyright (C) 2018 Mariano Reingart"
|
| 28 |
+
__license__ = "LGPL 3.0"
|
| 29 |
+
__version__ = "1.01a"
|
| 30 |
+
|
| 31 |
+
LICENCIA = """
|
| 32 |
+
wsremcarne.py: Interfaz para generar Remito ElectrΓ³nico CΓ‘rnico AFIP v1.0
|
| 33 |
+
Remito de Carnes y subproductos derivados de la faena de bovinos y porcinos
|
| 34 |
+
ResoluciΓ³n General 4256/18 y ResoluciΓ³n General 4303/18.
|
| 35 |
+
Copyright (C) 2018 Mariano Reingart reingart@gmail.com
|
| 36 |
+
http://www.sistemasagiles.com.ar/trac/wiki/RemitoElectronicoCarnico
|
| 37 |
+
|
| 38 |
+
Este progarma es software libre, se entrega ABSOLUTAMENTE SIN GARANTIA
|
| 39 |
+
y es bienvenido a redistribuirlo bajo la licencia GPLv3.
|
| 40 |
+
|
| 41 |
+
Para informaciΓ³n adicional sobre garantΓa, soporte tΓ©cnico comercial
|
| 42 |
+
e incorporaciΓ³n/distribuciΓ³n en programas propietarios ver PyAfipWs:
|
| 43 |
+
http://www.sistemasagiles.com.ar/trac/wiki/PyAfipWs
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
AYUDA = """
|
| 47 |
+
Opciones:
|
| 48 |
+
--ayuda: este mensaje
|
| 49 |
+
|
| 50 |
+
--debug: modo depuraciΓ³n (detalla y confirma las operaciones)
|
| 51 |
+
--prueba: genera y autoriza una rec de prueba (no usar en producciΓ³n!)
|
| 52 |
+
--xml: almacena los requerimientos y respuestas XML (depuraciΓ³n)
|
| 53 |
+
--dummy: consulta estado de servidores
|
| 54 |
+
|
| 55 |
+
--generar: generar un remito
|
| 56 |
+
--emitir: emite un remito
|
| 57 |
+
--anular: anula un remito
|
| 58 |
+
--autorizar: autoriza un remito
|
| 59 |
+
|
| 60 |
+
--ult: consulta ultimo nro remito emitido
|
| 61 |
+
--consultar: consulta un remito generado
|
| 62 |
+
|
| 63 |
+
--tipos_comprobante: tabla de parametros para tipo de comprobante
|
| 64 |
+
--tipos_contingencia: tipo de contingencia que puede reportar
|
| 65 |
+
--tipos_categoria_emisor: tipos de categorΓas de emisor
|
| 66 |
+
--tipos_categoria_receptor: tipos de categorΓas de receptor
|
| 67 |
+
--tipos_estados: estados posibles en los que puede estar un remito cΓ‘rnico
|
| 68 |
+
--grupos_carne' grupos de los distintos tipos de cortes de carne
|
| 69 |
+
--tipos_carne': tipos de corte de carne
|
| 70 |
+
--codigos_domicilio: codigos de depositos habilitados para el cuit
|
| 71 |
+
|
| 72 |
+
Ver wsremcarne.ini para parΓ‘metros de configuraciΓ³n (URL, certificados, etc.)"
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# importo funciones compartidas:
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# constantes de configuraciΓ³n (producciΓ³n/homologaciΓ³n):
|
| 80 |
+
|
| 81 |
+
WSDL = ["https://serviciosjava.afip.gob.ar/wsremcarne/RemCarneService?wsdl",
|
| 82 |
+
"https://fwshomo.afip.gov.ar/wsremcarne/RemCarneService?wsdl"]
|
| 83 |
+
|
| 84 |
+
DEBUG = False
|
| 85 |
+
XML = False
|
| 86 |
+
CONFIG_FILE = "wsremcarne.ini"
|
| 87 |
+
HOMO = False
|
| 88 |
+
ENCABEZADO = []
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class WSRemCarne(BaseWS):
|
| 92 |
+
"Interfaz para el WebService de Remito Electronico Carnico (Version 3)"
|
| 93 |
+
_public_methods_ = ['Conectar', 'Dummy', 'SetTicketAcceso', 'DebugLog',
|
| 94 |
+
'GenerarRemito', 'EmitirRemito', 'AutorizarRemito', 'AnularRemito', 'ConsultarRemito',
|
| 95 |
+
'InformarContingencia', 'ModificarViaje', 'RegistrarRecepcion', 'ConsultarUltimoRemitoEmitido',
|
| 96 |
+
'CrearRemito', 'AgregarViaje', 'AgregarVehiculo', 'AgregarMercaderia',
|
| 97 |
+
'AgregarDatosAutorizacion', 'AgregarContingencia',
|
| 98 |
+
'ConsultarTiposCarne', 'ConsultarTiposCategoriaEmisor', 'ConsultarTiposCategoriaReceptor',
|
| 99 |
+
'ConsultarTiposComprobante', 'ConsultarTiposContingencia', 'ConsultarTiposEstado',
|
| 100 |
+
'ConsultarCodigosDomicilio', 'ConsultarGruposCarne, ConsultarPuntosEmision',
|
| 101 |
+
'SetParametros', 'SetParametro', 'GetParametro', 'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML',
|
| 102 |
+
]
|
| 103 |
+
_public_attrs_ = ['XmlRequest', 'XmlResponse', 'Version', 'Traceback', 'Excepcion', 'LanzarExcepciones',
|
| 104 |
+
'Token', 'Sign', 'Cuit', 'AppServerStatus', 'DbServerStatus', 'AuthServerStatus',
|
| 105 |
+
'CodRemito', 'TipoComprobante', 'PuntoEmision',
|
| 106 |
+
'NroRemito', 'CodAutorizacion', 'FechaVencimiento', 'FechaEmision', 'Estado', 'Resultado', 'QR',
|
| 107 |
+
'ErrCode', 'ErrMsg', 'Errores', 'ErroresFormato', 'Observaciones', 'Obs', 'Evento', 'Eventos',
|
| 108 |
+
]
|
| 109 |
+
_reg_progid_ = "WSRemCarne"
|
| 110 |
+
_reg_clsid_ = "{71DB0CB9-2ED7-4226-A1E6-C3FA7FB18F41}"
|
| 111 |
+
|
| 112 |
+
# Variables globales para BaseWS:
|
| 113 |
+
HOMO = HOMO
|
| 114 |
+
WSDL = WSDL[HOMO]
|
| 115 |
+
LanzarExcepciones = False
|
| 116 |
+
Version = "%s %s" % (__version__, HOMO and 'HomologaciΓ³n' or '')
|
| 117 |
+
|
| 118 |
+
def Conectar(self, *args, **kwargs):
|
| 119 |
+
ret = BaseWS.Conectar(self, *args, **kwargs)
|
| 120 |
+
return ret
|
| 121 |
+
|
| 122 |
+
def inicializar(self):
|
| 123 |
+
self.AppServerStatus = self.DbServerStatus = self.AuthServerStatus = None
|
| 124 |
+
self.CodRemito = self.TipoComprobante = self.PuntoEmision = None
|
| 125 |
+
self.NroRemito = self.CodAutorizacion = self.FechaVencimiento = self.FechaEmision = None
|
| 126 |
+
self.Estado = self.Resultado = self.QR = None
|
| 127 |
+
self.Errores = []
|
| 128 |
+
self.ErroresFormato = []
|
| 129 |
+
self.Observaciones = []
|
| 130 |
+
self.Eventos = []
|
| 131 |
+
self.Evento = self.ErrCode = self.ErrMsg = self.Obs = ""
|
| 132 |
+
|
| 133 |
+
def __analizar_errores(self, ret):
|
| 134 |
+
"Comprueba y extrae errores si existen en la respuesta XML"
|
| 135 |
+
self.Errores = [err['codigoDescripcion'] for err in ret.get('arrayErrores', [])]
|
| 136 |
+
self.ErroresFormato = [err['codigoDescripcionString'] for err in ret.get('arrayErroresFormato', [])]
|
| 137 |
+
errores = self.Errores + self.ErroresFormato
|
| 138 |
+
self.ErrCode = ' '.join(["%(codigo)s" % err for err in errores])
|
| 139 |
+
self.ErrMsg = '\n'.join(["%(codigo)s: %(descripcion)s" % err for err in errores])
|
| 140 |
+
|
| 141 |
+
def __analizar_observaciones(self, ret):
|
| 142 |
+
"Comprueba y extrae observaciones si existen en la respuesta XML"
|
| 143 |
+
self.Observaciones = [obs["codigoDescripcion"] for obs in ret.get('arrayObservaciones', [])]
|
| 144 |
+
self.Obs = '\n'.join(["%(codigo)s: %(descripcion)s" % obs for obs in self.Observaciones])
|
| 145 |
+
|
| 146 |
+
def __analizar_evento(self, ret):
|
| 147 |
+
"Comprueba y extrae el wvento informativo si existen en la respuesta XML"
|
| 148 |
+
evt = ret.get('evento')
|
| 149 |
+
if evt:
|
| 150 |
+
self.Eventos = [evt]
|
| 151 |
+
self.Evento = "%(codigo)s: %(descripcion)s" % evt
|
| 152 |
+
|
| 153 |
+
@inicializar_y_capturar_excepciones
|
| 154 |
+
def CrearRemito(self, tipo_comprobante, punto_emision, tipo_movimiento, categoria_emisor, cuit_titular_mercaderia, cod_dom_origen,
|
| 155 |
+
tipo_receptor, categoria_receptor=None, cuit_receptor=None, cuit_depositario=None,
|
| 156 |
+
cod_dom_destino=None, cod_rem_redestinar=None, cod_remito=None, estado=None,
|
| 157 |
+
**kwargs):
|
| 158 |
+
"Inicializa internamente los datos de un remito para autorizar"
|
| 159 |
+
self.remito = {'tipoComprobante': tipo_comprobante, 'puntoEmision': punto_emision, 'categoriaEmisor': categoria_emisor,
|
| 160 |
+
'cuitTitularMercaderia': cuit_titular_mercaderia, 'cuitDepositario': cuit_depositario,
|
| 161 |
+
'tipoReceptor': tipo_receptor, 'categoriaReceptor': categoria_receptor, 'cuitReceptor': cuit_receptor,
|
| 162 |
+
'codDomOrigen': cod_dom_origen, 'codDomDestino': cod_dom_destino, 'tipoMovimiento': tipo_movimiento,
|
| 163 |
+
'estado': estado, 'codRemito': cod_remito,
|
| 164 |
+
'codRemRedestinado': cod_rem_redestinar,
|
| 165 |
+
'arrayMercaderias': [], 'arrayContingencias': [],
|
| 166 |
+
}
|
| 167 |
+
return True
|
| 168 |
+
|
| 169 |
+
@inicializar_y_capturar_excepciones
|
| 170 |
+
def AgregarViaje(self, cuit_transportista=None, cuit_conductor=None, fecha_inicio_viaje=None, distancia_km=None, **kwargs):
|
| 171 |
+
"Agrega la informaciΓ³n referente al viaje del remito electrΓ³nico cΓ‘rnico"
|
| 172 |
+
self.remito['viaje'] = {'cuitTransportista': cuit_transportista,
|
| 173 |
+
'cuitConductor': cuit_conductor,
|
| 174 |
+
'fechaInicioViaje': fecha_inicio_viaje,
|
| 175 |
+
'distanciaKm': distancia_km,
|
| 176 |
+
'vehiculo': {}
|
| 177 |
+
}
|
| 178 |
+
return True
|
| 179 |
+
|
| 180 |
+
@inicializar_y_capturar_excepciones
|
| 181 |
+
def AgregarVehiculo(self, dominio_vehiculo=None, dominio_acoplado=None, **kwargs):
|
| 182 |
+
"Agrega la informaciΓ³n referente al vehiculo usado en el viaje del remito electrΓ³nico cΓ‘rnico"
|
| 183 |
+
self.remito['viaje']['vehiculo'] = {'dominioVehiculo': dominio_vehiculo, 'dominioAcoplado': dominio_acoplado}
|
| 184 |
+
return True
|
| 185 |
+
|
| 186 |
+
@inicializar_y_capturar_excepciones
|
| 187 |
+
def AgregarMercaderia(self, orden=None, cod_tipo_prod=None, cantidad=None, unidades=None, tropa=None, **kwargs):
|
| 188 |
+
"Agrega la informaciΓ³n referente a la mercaderΓa del remito electrΓ³nico cΓ‘rnico"
|
| 189 |
+
mercaderia = dict(orden=orden, tropa=tropa, codTipoProd=cod_tipo_prod, cantidad=cantidad, unidadMedida=unidades)
|
| 190 |
+
self.remito['arrayMercaderias'].append(dict(mercaderia=mercaderia))
|
| 191 |
+
return True
|
| 192 |
+
|
| 193 |
+
@inicializar_y_capturar_excepciones
|
| 194 |
+
def AgregarDatosAutorizacion(self, nro_remito=None, cod_autorizacion=None, fecha_emision=None, fecha_vencimiento=None, **kwargs):
|
| 195 |
+
"Agrega la informaciΓ³n referente a los datos de autorizaciΓ³n del remito electrΓ³nico cΓ‘rnico"
|
| 196 |
+
self.remito['datosEmision'] = dict(nroRemito=nro_remito, codAutorizacion=cod_autorizacion,
|
| 197 |
+
fechaEmision=fecha_emision, fechaVencimiento=fecha_vencimiento,
|
| 198 |
+
)
|
| 199 |
+
return True
|
| 200 |
+
|
| 201 |
+
@inicializar_y_capturar_excepciones
|
| 202 |
+
def AgregarContingencias(self, tipo=None, observacion=None, **kwargs):
|
| 203 |
+
"Agrega la informaciΓ³n referente a los opcionales de la liq. seq."
|
| 204 |
+
contingencia = dict(tipoContingencia=tipo, observacion=observacion)
|
| 205 |
+
self.remito['arrayContingencias'].append(dict(contingencia=contingencia))
|
| 206 |
+
return True
|
| 207 |
+
|
| 208 |
+
@inicializar_y_capturar_excepciones
|
| 209 |
+
def GenerarRemito(self, id_req, archivo="qr.png"):
|
| 210 |
+
"Informar los datos necesarios para la generaciΓ³n de un remito nuevo"
|
| 211 |
+
if not self.remito['arrayContingencias']:
|
| 212 |
+
del self.remito['arrayContingencias']
|
| 213 |
+
response = self.client.generarRemito(
|
| 214 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 215 |
+
idReq=id_req, remito=self.remito)
|
| 216 |
+
ret = response.get("generarRemitoReturn")
|
| 217 |
+
if ret:
|
| 218 |
+
self.__analizar_errores(ret)
|
| 219 |
+
self.__analizar_observaciones(ret)
|
| 220 |
+
self.__analizar_evento(ret)
|
| 221 |
+
self.AnalizarRemito(ret, archivo)
|
| 222 |
+
return bool(self.CodRemito)
|
| 223 |
+
|
| 224 |
+
def AnalizarRemito(self, ret, archivo=None):
|
| 225 |
+
"Extrae el resultado del remito, si existen en la respuesta XML"
|
| 226 |
+
if ret:
|
| 227 |
+
self.CodRemito = ret.get("codRemito")
|
| 228 |
+
self.TipoComprobante = ret.get("tipoComprobante")
|
| 229 |
+
self.PuntoEmision = ret.get("puntoEmision")
|
| 230 |
+
datos_aut = ret.get('datosAutorizacion')
|
| 231 |
+
if datos_aut:
|
| 232 |
+
self.NroRemito = datos_aut.get('nroRemito')
|
| 233 |
+
self.CodAutorizacion = datos_aut.get('codAutorizacion')
|
| 234 |
+
self.FechaEmision = datos_aut.get('fechaEmision')
|
| 235 |
+
self.FechaVencimiento = datos_aut.get('fechaVencimiento')
|
| 236 |
+
self.Estado = ret.get('estado')
|
| 237 |
+
self.Resultado = ret.get('resultado')
|
| 238 |
+
self.QR = ret.get('qr') or ""
|
| 239 |
+
if archivo:
|
| 240 |
+
qr = base64.b64decode(self.QR)
|
| 241 |
+
f = open(archivo, "wb")
|
| 242 |
+
f.write(qr)
|
| 243 |
+
f.close()
|
| 244 |
+
|
| 245 |
+
@inicializar_y_capturar_excepciones
|
| 246 |
+
def EmitirRemito(self, archivo="qr.png"):
|
| 247 |
+
"Emitir Remitos que se encuentren en estado Pendiente de Emitir."
|
| 248 |
+
response = self.client.emitirRemito(
|
| 249 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 250 |
+
codRemito=self.remito['codRemito'],
|
| 251 |
+
viaje=self.remito.get('viaje'))
|
| 252 |
+
ret = response.get("emitirRemitoReturn")
|
| 253 |
+
if ret:
|
| 254 |
+
self.__analizar_errores(ret)
|
| 255 |
+
self.__analizar_observaciones(ret)
|
| 256 |
+
self.__analizar_evento(ret)
|
| 257 |
+
self.AnalizarRemito(ret, archivo)
|
| 258 |
+
return bool(self.CodRemito)
|
| 259 |
+
|
| 260 |
+
@inicializar_y_capturar_excepciones
|
| 261 |
+
def AutorizarRemito(self, archivo="qr.png"):
|
| 262 |
+
"Autorizar o denegar un remito (cuando corresponde autorizacion) por parte del titular/depositario"
|
| 263 |
+
response = self.client.autorizarRemito(
|
| 264 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 265 |
+
codRemito=self.remito['codRemito'],
|
| 266 |
+
estado=self.remito['estado'])
|
| 267 |
+
ret = response.get("autorizarRemitoReturn")
|
| 268 |
+
if ret:
|
| 269 |
+
self.__analizar_errores(ret)
|
| 270 |
+
self.__analizar_observaciones(ret)
|
| 271 |
+
self.__analizar_evento(ret)
|
| 272 |
+
self.AnalizarRemito(ret, archivo)
|
| 273 |
+
return bool(self.CodRemito)
|
| 274 |
+
|
| 275 |
+
@inicializar_y_capturar_excepciones
|
| 276 |
+
def AnularRemito(self):
|
| 277 |
+
"Anular un remito generado que aΓΊn no haya sido emitido"
|
| 278 |
+
response = self.client.anularRemito(
|
| 279 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 280 |
+
codRemito=self.remito['codRemito'])
|
| 281 |
+
ret = response.get("anularRemitoReturn")
|
| 282 |
+
if ret:
|
| 283 |
+
self.__analizar_errores(ret)
|
| 284 |
+
self.__analizar_observaciones(ret)
|
| 285 |
+
self.__analizar_evento(ret)
|
| 286 |
+
self.AnalizarRemito(ret)
|
| 287 |
+
return bool(self.CodRemito)
|
| 288 |
+
|
| 289 |
+
@inicializar_y_capturar_excepciones
|
| 290 |
+
def ConsultarUltimoRemitoEmitido(self, tipo_comprobante=995, punto_emision=1):
|
| 291 |
+
"Obtener el ΓΊltimo nΓΊmero de remito que se emitiΓ³ por tipo de comprobante y punto de emisiΓ³n"
|
| 292 |
+
response = self.client.consultarUltimoRemitoEmitido(
|
| 293 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 294 |
+
tipoComprobante=tipo_comprobante,
|
| 295 |
+
puntoEmision=punto_emision)
|
| 296 |
+
ret = response.get("consultarUltimoRemitoReturn", {})
|
| 297 |
+
id_req = ret.get("idReq", 0)
|
| 298 |
+
rec = ret.get("remito", {})
|
| 299 |
+
self.__analizar_errores(ret)
|
| 300 |
+
self.__analizar_observaciones(ret)
|
| 301 |
+
self.__analizar_evento(ret)
|
| 302 |
+
self.AnalizarRemito(rec)
|
| 303 |
+
return id_req
|
| 304 |
+
|
| 305 |
+
@inicializar_y_capturar_excepciones
|
| 306 |
+
def ConsultarRemito(self, cod_remito=None, id_req=None,
|
| 307 |
+
tipo_comprobante=None, punto_emision=None, nro_comprobante=None):
|
| 308 |
+
"Obtener los datos de un remito generado"
|
| 309 |
+
print((self.client.help("consultarRemito")))
|
| 310 |
+
response = self.client.consultarRemito(
|
| 311 |
+
authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit},
|
| 312 |
+
codRemito=cod_remito,
|
| 313 |
+
idReq=id_req,
|
| 314 |
+
tipoComprobante=tipo_comprobante,
|
| 315 |
+
puntoEmision=punto_emision,
|
| 316 |
+
nroComprobante=nro_comprobante)
|
| 317 |
+
ret = response.get("consultarRemitoReturn", {})
|
| 318 |
+
id_req = ret.get("idReq", 0)
|
| 319 |
+
self.remito = rec = ret.get("remito", {})
|
| 320 |
+
self.__analizar_errores(ret)
|
| 321 |
+
self.__analizar_observaciones(ret)
|
| 322 |
+
self.__analizar_evento(ret)
|
| 323 |
+
self.AnalizarRemito(rec)
|
| 324 |
+
return id_req
|
| 325 |
+
|
| 326 |
+
@inicializar_y_capturar_excepciones
|
| 327 |
+
def Dummy(self):
|
| 328 |
+
"Obtener el estado de los servidores de la AFIP"
|
| 329 |
+
results = self.client.dummy()['dummyReturn']
|
| 330 |
+
self.AppServerStatus = str(results['appserver'])
|
| 331 |
+
self.DbServerStatus = str(results['dbserver'])
|
| 332 |
+
self.AuthServerStatus = str(results['authserver'])
|
| 333 |
+
|
| 334 |
+
@inicializar_y_capturar_excepciones
|
| 335 |
+
def ConsultarTiposComprobante(self, sep="||"):
|
| 336 |
+
"Obtener el cΓ³digo y descripciΓ³n para tipo de comprobante"
|
| 337 |
+
ret = self.client.consultarTiposComprobante(
|
| 338 |
+
authRequest={
|
| 339 |
+
'token': self.Token, 'sign': self.Sign,
|
| 340 |
+
'cuitRepresentada': self.Cuit, },
|
| 341 |
+
)['consultarTiposComprobanteReturn']
|
| 342 |
+
self.__analizar_errores(ret)
|
| 343 |
+
array = ret.get('arrayTiposComprobante', [])
|
| 344 |
+
lista = [it['codigoDescripcion'] for it in array]
|
| 345 |
+
return [("%s {codigo} %s {descripcion} %s" % (sep, sep, sep)).format(**it) if sep else it for it in lista]
|
| 346 |
+
|
| 347 |
+
@inicializar_y_capturar_excepciones
|
| 348 |
+
def ConsultarTiposContingencia(self, sep="||"):
|
| 349 |
+
"Obtener el cΓ³digo y descripciΓ³n para cada tipo de contingencia que puede reportar"
|
| 350 |
+
ret = self.client.consultarTiposContingencia(
|
| 351 |
+
authRequest={
|
| 352 |
+
'token': self.Token, 'sign': self.Sign,
|
| 353 |
+
'cuitRepresentada': self.Cuit, },
|
| 354 |
+
)['consultarTiposContingenciaReturn']
|
| 355 |
+
self.__analizar_errores(ret)
|
| 356 |
+
array = ret.get('arrayTiposContingencia', [])
|
| 357 |
+
lista = [it['codigoDescripcion'] for it in array]
|
| 358 |
+
return [("%s {codigo} %s {descripcion} %s" % (sep, sep, sep)).format(**it) if sep else it for it in lista]
|
| 359 |
+
|
| 360 |
+
@inicializar_y_capturar_excepciones
|
| 361 |
+
def ConsultarTiposCategoriaEmisor(self, sep="||"):
|
| 362 |
+
"Obtener el cΓ³digo y descripciΓ³n para tipos de categorΓas de emisor"
|
| 363 |
+
ret = self.client.consultarTiposCategoriaEmisor(
|
| 364 |
+
authRequest={
|
| 365 |
+
'token': self.Token, 'sign': self.Sign,
|
| 366 |
+
'cuitRepresentada': self.Cuit, },
|
| 367 |
+
)['consultarCategoriasEmisorReturn']
|
| 368 |
+
self.__analizar_errores(ret)
|
| 369 |
+
array = ret.get('arrayCategoriasEmisor', [])
|
| 370 |
+
lista = [it['codigoDescripcionString'] for it in array]
|
| 371 |
+
return [("%s {codigo} %s {descripcion} %s" % (sep, sep, sep)).format(**it) if sep else it for it in lista]
|
| 372 |
+
|
| 373 |
+
@inicializar_y_capturar_excepciones
|
| 374 |
+
def ConsultarTiposCategoriaReceptor(self, sep="||"):
|
| 375 |
+
"Obtener el cΓ³digo y descripciΓ³n para cada tipos de categorΓas de receptor"
|
| 376 |
+
ret = self.client.consultarTiposCategoriaReceptor(
|
| 377 |
+
authRequest={
|
| 378 |
+
'token': self.Token, 'sign': self.Sign,
|
| 379 |
+
'cuitRepresentada': self.Cuit, },
|
| 380 |
+
)['consultarCategoriasReceptorReturn']
|
| 381 |
+
self.__analizar_errores(ret)
|
| 382 |
+
array = ret.get('arrayCategoriasReceptor', [])
|
| 383 |
+
lista = [it['codigoDescripcionString'] for it in array]
|
| 384 |
+
return [("%s {codigo} %s {descripcion} %s" % (sep, sep, sep)).format(**it) if sep else it for it in lista]
|
| 385 |
+
|
| 386 |
+
@inicializar_y_capturar_excepciones
|
| 387 |
+
def ConsultarTiposEstado(self, sep="||"):
|
| 388 |
+
"Obtener el cΓ³digo y descripciΓ³n para cada estado posibles en los que puede estar un remito cΓ‘rnico"
|
| 389 |
+
ret = self.client.consultarTiposEstado(
|
| 390 |
+
authRequest={
|
| 391 |
+
'token': self.Token, 'sign': self.Sign,
|
| 392 |
+
'cuitRepresentada': self.Cuit, },
|
| 393 |
+
)['consultarTiposEstadoReturn']
|
| 394 |
+
self.__analizar_errores(ret)
|
| 395 |
+
array = ret.get('arrayTiposEstado', [])
|
| 396 |
+
lista = [it['codigoDescripcionString'] for it in array]
|
| 397 |
+
return [("%s {codigo} %s {descripcion} %s" % (sep, sep, sep)).format(**it) if sep else it for it in lista]
|
| 398 |
+
|
| 399 |
+
@inicializar_y_capturar_excepciones
|
| 400 |
+
def ConsultarGruposCarne(self, sep="||"):
|
| 401 |
+
"Obtener el cΓ³digo y descripciΓ³n para los grupos de los distintos tipos de cortes de carne"
|
| 402 |
+
ret = self.client.consultarGruposCarne(
|
| 403 |
+
authRequest={
|
| 404 |
+
'token': self.Token, 'sign': self.Sign,
|
| 405 |
+
'cuitRepresentada': self.Cuit, },
|
| 406 |
+
)['consultarGruposCarneReturn']
|
| 407 |
+
self.__analizar_errores(ret)
|
| 408 |
+
array = ret.get('arrayGruposCarne', [])
|
| 409 |
+
lista = [it['codigoDescripcionString'] for it in array]
|
| 410 |
+
return [("%s {codigo} %s {descripcion} %s" % (sep, sep, sep)).format(**it) if sep else it for it in lista]
|
| 411 |
+
|
| 412 |
+
@inicializar_y_capturar_excepciones
|
| 413 |
+
def ConsultarTiposCarne(self, cod_grupo_carne=1, sep="||"):
|
| 414 |
+
"Obtener el cΓ³digo y descripciΓ³n para tipos de corte de carne"
|
| 415 |
+
ret = self.client.consultarTiposCarne(
|
| 416 |
+
authRequest={
|
| 417 |
+
'token': self.Token, 'sign': self.Sign,
|
| 418 |
+
'cuitRepresentada': self.Cuit, },
|
| 419 |
+
codGrupoCarne=cod_grupo_carne,
|
| 420 |
+
)['consultarTiposCarneReturn']
|
| 421 |
+
self.__analizar_errores(ret)
|
| 422 |
+
array = ret.get('arrayTiposCarne', [])
|
| 423 |
+
lista = [it['codigoDescripcionString'] for it in array]
|
| 424 |
+
return [("%s {codigo} %s {descripcion} %s" % (sep, sep, sep)).format(**it) if sep else it for it in lista]
|
| 425 |
+
|
| 426 |
+
@inicializar_y_capturar_excepciones
|
| 427 |
+
def ConsultarCodigosDomicilio(self, cuit_titular=1, sep="||"):
|
| 428 |
+
"Obtener el cΓ³digo de depositos que tiene habilitados para operar el cuit informado"
|
| 429 |
+
ret = self.client.consultarCodigosDomicilio(
|
| 430 |
+
authRequest={
|
| 431 |
+
'token': self.Token, 'sign': self.Sign,
|
| 432 |
+
'cuitRepresentada': self.Cuit, },
|
| 433 |
+
cuitTitularDomicilio=cuit_titular,
|
| 434 |
+
)['consultarCodigosDomicilioReturn']
|
| 435 |
+
self.__analizar_errores(ret)
|
| 436 |
+
array = ret.get('arrayDomicilios', [])
|
| 437 |
+
lista = [it['codigoDescripcion'] for it in array]
|
| 438 |
+
return [("%s {codigo} %s {descripcion} %s" % (sep, sep, sep)).format(**it) if sep else it for it in lista]
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
# busco el directorio de instalaciΓ³n (global para que no cambie si usan otra dll)
|
| 442 |
+
if not hasattr(sys, "frozen"):
|
| 443 |
+
basepath = __file__
|
| 444 |
+
elif sys.frozen == 'dll':
|
| 445 |
+
import win32api
|
| 446 |
+
basepath = win32api.GetModuleFileName(sys.frozendllhandle)
|
| 447 |
+
else:
|
| 448 |
+
basepath = sys.executable
|
| 449 |
+
INSTALL_DIR = WSRemCarne.InstallDir = get_install_dir()
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
if __name__ == '__main__':
|
| 453 |
+
if '--ayuda' in sys.argv:
|
| 454 |
+
print(LICENCIA)
|
| 455 |
+
print(AYUDA)
|
| 456 |
+
sys.exit(0)
|
| 457 |
+
|
| 458 |
+
if "--register" in sys.argv or "--unregister" in sys.argv:
|
| 459 |
+
import win32com.server.register
|
| 460 |
+
win32com.server.register.UseCommandLine(WSRemCarne)
|
| 461 |
+
sys.exit(0)
|
| 462 |
+
|
| 463 |
+
from configparser import SafeConfigParser
|
| 464 |
+
|
| 465 |
+
try:
|
| 466 |
+
|
| 467 |
+
if "--version" in sys.argv:
|
| 468 |
+
print("VersiΓ³n: ", __version__)
|
| 469 |
+
|
| 470 |
+
for arg in sys.argv[1:]:
|
| 471 |
+
if arg.startswith("--"):
|
| 472 |
+
break
|
| 473 |
+
print("Usando configuraciΓ³n:", arg)
|
| 474 |
+
CONFIG_FILE = arg
|
| 475 |
+
|
| 476 |
+
config = SafeConfigParser()
|
| 477 |
+
config.read(CONFIG_FILE)
|
| 478 |
+
CERT = config.get('WSAA', 'CERT')
|
| 479 |
+
PRIVATEKEY = config.get('WSAA', 'PRIVATEKEY')
|
| 480 |
+
CUIT = config.get('WSRemCarne', 'CUIT')
|
| 481 |
+
ENTRADA = config.get('WSRemCarne', 'ENTRADA')
|
| 482 |
+
SALIDA = config.get('WSRemCarne', 'SALIDA')
|
| 483 |
+
|
| 484 |
+
if config.has_option('WSAA', 'URL') and not HOMO:
|
| 485 |
+
wsaa_url = config.get('WSAA', 'URL')
|
| 486 |
+
else:
|
| 487 |
+
wsaa_url = None
|
| 488 |
+
if config.has_option('WSRemCarne', 'URL') and not HOMO:
|
| 489 |
+
wsremcarne_url = config.get('WSRemCarne', 'URL')
|
| 490 |
+
else:
|
| 491 |
+
wsremcarne_url = WSDL[HOMO]
|
| 492 |
+
|
| 493 |
+
if config.has_section('DBF'):
|
| 494 |
+
conf_dbf = dict(config.items('DBF'))
|
| 495 |
+
if DEBUG:
|
| 496 |
+
print("conf_dbf", conf_dbf)
|
| 497 |
+
else:
|
| 498 |
+
conf_dbf = {}
|
| 499 |
+
|
| 500 |
+
DEBUG = '--debug' in sys.argv
|
| 501 |
+
XML = '--xml' in sys.argv
|
| 502 |
+
|
| 503 |
+
if DEBUG:
|
| 504 |
+
print("Usando ConfiguraciΓ³n:")
|
| 505 |
+
print("wsaa_url:", wsaa_url)
|
| 506 |
+
print("wsremcarne_url:", wsremcarne_url)
|
| 507 |
+
|
| 508 |
+
# obteniendo el TA
|
| 509 |
+
from .wsaa import WSAA
|
| 510 |
+
wsaa = WSAA()
|
| 511 |
+
ta = wsaa.Autenticar("wsremcarne", CERT, PRIVATEKEY, wsaa_url, debug=DEBUG)
|
| 512 |
+
if not ta:
|
| 513 |
+
sys.exit("Imposible autenticar con WSAA: %s" % wsaa.Excepcion)
|
| 514 |
+
|
| 515 |
+
# cliente soap del web service
|
| 516 |
+
wsremcarne = WSRemCarne()
|
| 517 |
+
wsremcarne.Conectar(wsdl=wsremcarne_url)
|
| 518 |
+
wsremcarne.SetTicketAcceso(ta)
|
| 519 |
+
wsremcarne.Cuit = CUIT
|
| 520 |
+
ok = None
|
| 521 |
+
|
| 522 |
+
if '--dummy' in sys.argv:
|
| 523 |
+
ret = wsremcarne.Dummy()
|
| 524 |
+
print("AppServerStatus", wsremcarne.AppServerStatus)
|
| 525 |
+
print("DbServerStatus", wsremcarne.DbServerStatus)
|
| 526 |
+
print("AuthServerStatus", wsremcarne.AuthServerStatus)
|
| 527 |
+
sys.exit(0)
|
| 528 |
+
|
| 529 |
+
if '--ult' in sys.argv:
|
| 530 |
+
try:
|
| 531 |
+
pto_emision = int(sys.argv[sys.argv.index("--ult") + 1])
|
| 532 |
+
except IndexError as ValueError:
|
| 533 |
+
pto_emision = 1
|
| 534 |
+
try:
|
| 535 |
+
tipo_cbte = int(sys.argv[sys.argv.index("--ult") + 1])
|
| 536 |
+
except IndexError as ValueError:
|
| 537 |
+
tipo_comprobante = 995
|
| 538 |
+
rec = {}
|
| 539 |
+
print("Consultando ultimo remito pto_emision=%s tipo_comprobante=%s" % (pto_emision, tipo_comprobante))
|
| 540 |
+
ok = wsremcarne.ConsultarUltimoRemitoEmitido(tipo_comprobante, pto_emision)
|
| 541 |
+
if wsremcarne.Excepcion:
|
| 542 |
+
print("EXCEPCION:", wsremcarne.Excepcion, file=sys.stderr)
|
| 543 |
+
if DEBUG:
|
| 544 |
+
print(wsremcarne.Traceback, file=sys.stderr)
|
| 545 |
+
print("Ultimo Nro de Remito", wsremcarne.NroRemito)
|
| 546 |
+
print("Errores:", wsremcarne.Errores)
|
| 547 |
+
|
| 548 |
+
if '--consultar' in sys.argv:
|
| 549 |
+
try:
|
| 550 |
+
cod_remito = sys.argv[sys.argv.index("--consultar") + 1]
|
| 551 |
+
except IndexError as ValueError:
|
| 552 |
+
cod_remito = None
|
| 553 |
+
rec = {}
|
| 554 |
+
print("Consultando remito cod_remito=%s" % (cod_remito, ))
|
| 555 |
+
ok = wsremcarne.ConsultarRemito(cod_remito)
|
| 556 |
+
if wsremcarne.Excepcion:
|
| 557 |
+
print("EXCEPCION:", wsremcarne.Excepcion, file=sys.stderr)
|
| 558 |
+
if DEBUG:
|
| 559 |
+
print(wsremcarne.Traceback, file=sys.stderr)
|
| 560 |
+
print("Ultimo Nro de Remito", wsremcarne.NroRemito)
|
| 561 |
+
print("Errores:", wsremcarne.Errores)
|
| 562 |
+
if DEBUG:
|
| 563 |
+
import pprint
|
| 564 |
+
pprint.pprint(wsremcarne.remito)
|
| 565 |
+
|
| 566 |
+
if '--prueba' in sys.argv:
|
| 567 |
+
rec = dict(tipo_comprobante=995, punto_emision=1, categoria_emisor=1,
|
| 568 |
+
tipo_movimiento='ENV', # ENV: Envio Normal, PLA: Retiro en planta, REP: Reparto, RED: Redestino
|
| 569 |
+
cuit_titular_mercaderia='20222222223', cod_dom_origen=1,
|
| 570 |
+
tipo_receptor='EM', # 'EM': DEPOSITO EMISOR, 'MI': MERCADO INTERNO, 'RP': REPARTO
|
| 571 |
+
categoria_receptor=1, id_req=int(time.time()),
|
| 572 |
+
cuit_receptor='20111111112', cuit_depositario=None,
|
| 573 |
+
cod_dom_destino=1, cod_rem_redestinar=None,
|
| 574 |
+
cod_remito=30,
|
| 575 |
+
)
|
| 576 |
+
if "--autorizar" in sys.argv:
|
| 577 |
+
rec["estado"] = 'A' # 'A': Autorizar, 'D': Denegar
|
| 578 |
+
rec['viaje'] = dict(cuit_transportista='20333333334', cuit_conductor='20333333334',
|
| 579 |
+
fecha_inicio_viaje='2018-10-01', distancia_km=999)
|
| 580 |
+
rec['viaje']['vehiculo'] = dict(dominio_vehiculo='AAA000', dominio_acoplado='ZZZ000')
|
| 581 |
+
rec['mercaderias'] = [dict(orden=1, tropa=1, cod_tipo_prod='2.13', cantidad=10, unidades=1)]
|
| 582 |
+
rec['datos_autorizacion'] = None # dict(nro_remito=None, cod_autorizacion=None, fecha_emision=None, fecha_vencimiento=None)
|
| 583 |
+
rec['contingencias'] = [dict(tipo=1, observacion="anulacion")]
|
| 584 |
+
with open(ENTRADA, "w") as archivo:
|
| 585 |
+
json.dump(rec, archivo, sort_keys=True, indent=4)
|
| 586 |
+
|
| 587 |
+
if '--cargar' in sys.argv:
|
| 588 |
+
with open(ENTRADA, "r") as archivo:
|
| 589 |
+
rec = json.load(archivo)
|
| 590 |
+
wsremcarne.CrearRemito(**rec)
|
| 591 |
+
wsremcarne.AgregarViaje(**rec['viaje'])
|
| 592 |
+
wsremcarne.AgregarVehiculo(**rec['viaje']['vehiculo'])
|
| 593 |
+
for mercaderia in rec['mercaderias']:
|
| 594 |
+
wsremcarne.AgregarMercaderia(**mercaderia)
|
| 595 |
+
datos_aut = rec['datos_autorizacion']
|
| 596 |
+
if datos_aut:
|
| 597 |
+
wsremcarne.AgregarDatosAutorizacion(**datos_aut)
|
| 598 |
+
for contingencia in rec['contingencias']:
|
| 599 |
+
wsremcarne.AgregarContingencias(**contingencia)
|
| 600 |
+
|
| 601 |
+
if '--generar' in sys.argv:
|
| 602 |
+
if '--testing' in sys.argv:
|
| 603 |
+
wsremcarne.LoadTestXML("tests/xml/wsremcarne_generar_response_ok_beta.xml") # cargo respuesta
|
| 604 |
+
|
| 605 |
+
ok = wsremcarne.GenerarRemito(id_req=rec['id_req'])
|
| 606 |
+
|
| 607 |
+
if '--emitir' in sys.argv:
|
| 608 |
+
ok = wsremcarne.EmitirRemito()
|
| 609 |
+
|
| 610 |
+
if '--autorizar' in sys.argv:
|
| 611 |
+
ok = wsremcarne.AutorizarRemito()
|
| 612 |
+
|
| 613 |
+
if '--anular' in sys.argv:
|
| 614 |
+
ok = wsremcarne.AnularRemito()
|
| 615 |
+
|
| 616 |
+
if ok is not None:
|
| 617 |
+
print("Resultado: ", wsremcarne.Resultado)
|
| 618 |
+
print("Cod Remito: ", wsremcarne.CodRemito)
|
| 619 |
+
if wsremcarne.CodAutorizacion:
|
| 620 |
+
print("Numero Remito: ", wsremcarne.NroRemito)
|
| 621 |
+
print("Cod Autorizacion: ", wsremcarne.CodAutorizacion)
|
| 622 |
+
print("Fecha Emision", wsremcarne.FechaEmision)
|
| 623 |
+
print("Fecha Vencimiento", wsremcarne.FechaVencimiento)
|
| 624 |
+
print("Estado: ", wsremcarne.Estado)
|
| 625 |
+
print("Observaciones: ", wsremcarne.Observaciones)
|
| 626 |
+
print("Errores:", wsremcarne.Errores)
|
| 627 |
+
print("Errores Formato:", wsremcarne.ErroresFormato)
|
| 628 |
+
print("Evento:", wsremcarne.Evento)
|
| 629 |
+
rec['cod_remito'] = wsremcarne.CodRemito
|
| 630 |
+
rec['resultado'] = wsremcarne.Resultado
|
| 631 |
+
rec['observaciones'] = wsremcarne.Observaciones
|
| 632 |
+
rec['fecha_emision'] = wsremcarne.FechaEmision
|
| 633 |
+
rec['fecha_vencimiento'] = wsremcarne.FechaVencimiento
|
| 634 |
+
rec['errores'] = wsremcarne.Errores
|
| 635 |
+
rec['errores_formato'] = wsremcarne.ErroresFormato
|
| 636 |
+
rec['evento'] = wsremcarne.Evento
|
| 637 |
+
|
| 638 |
+
if '--grabar' in sys.argv:
|
| 639 |
+
with open(SALIDA, "w") as archivo:
|
| 640 |
+
json.dump(rec, archivo, sort_keys=True, indent=4)
|
| 641 |
+
|
| 642 |
+
# Recuperar parΓ‘metros:
|
| 643 |
+
|
| 644 |
+
if '--tipos_comprobante' in sys.argv:
|
| 645 |
+
ret = wsremcarne.ConsultarTiposComprobante()
|
| 646 |
+
print("\n".join(ret))
|
| 647 |
+
|
| 648 |
+
if '--tipos_contingencia' in sys.argv:
|
| 649 |
+
ret = wsremcarne.ConsultarTiposContingencia()
|
| 650 |
+
print("\n".join(ret))
|
| 651 |
+
|
| 652 |
+
if '--tipos_categoria_emisor' in sys.argv:
|
| 653 |
+
ret = wsremcarne.ConsultarTiposCategoriaEmisor()
|
| 654 |
+
print("\n".join(ret))
|
| 655 |
+
|
| 656 |
+
if '--tipos_categoria_receptor' in sys.argv:
|
| 657 |
+
ret = wsremcarne.ConsultarTiposCategoriaReceptor()
|
| 658 |
+
print("\n".join(ret))
|
| 659 |
+
|
| 660 |
+
if '--tipos_estados' in sys.argv:
|
| 661 |
+
ret = wsremcarne.ConsultarTiposEstado()
|
| 662 |
+
print("\n".join(ret))
|
| 663 |
+
|
| 664 |
+
if '--grupos_carne' in sys.argv:
|
| 665 |
+
ret = wsremcarne.ConsultarGruposCarne()
|
| 666 |
+
print("\n".join(ret))
|
| 667 |
+
|
| 668 |
+
if '--tipos_carne' in sys.argv:
|
| 669 |
+
for grupo_carne in wsremcarne.ConsultarGruposCarne(sep=None):
|
| 670 |
+
ret = wsremcarne.ConsultarTiposCarne(grupo_carne['codigo'])
|
| 671 |
+
print("\n".join(ret))
|
| 672 |
+
|
| 673 |
+
if '--codigos_domicilio' in sys.argv:
|
| 674 |
+
cuit = input("Cuit Titular Domicilio: ")
|
| 675 |
+
ret = wsremcarne.ConsultarCodigosDomicilio(cuit)
|
| 676 |
+
print("\n".join(ret))
|
| 677 |
+
|
| 678 |
+
if wsremcarne.Errores or wsremcarne.ErroresFormato:
|
| 679 |
+
print("Errores:", wsremcarne.Errores, wsremcarne.ErroresFormato)
|
| 680 |
+
|
| 681 |
+
print("hecho.")
|
| 682 |
+
|
| 683 |
+
except SoapFault as e:
|
| 684 |
+
print("Falla SOAP:", e.faultcode, e.faultstring.encode("ascii", "ignore"))
|
| 685 |
+
sys.exit(3)
|
| 686 |
+
except Exception as e:
|
| 687 |
+
ex = utils.exception_info()
|
| 688 |
+
print(ex)
|
| 689 |
+
if DEBUG:
|
| 690 |
+
raise
|
| 691 |
+
sys.exit(5)
|
app/reports/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
ο»Ώ
|
app/reports/builders.py
ADDED
|
@@ -0,0 +1,682 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Report Builders β Transform raw scraper data into structured report models.
|
| 3 |
+
Each builder handles a specific report type.
|
| 4 |
+
"""
|
| 5 |
+
import logging
|
| 6 |
+
from typing import Any, Optional
|
| 7 |
+
from datetime import datetime, timezone
|
| 8 |
+
|
| 9 |
+
from app.reports.schemas import (
|
| 10 |
+
ScoreHistorial, ScoreCrediticio,
|
| 11 |
+
PersonReport, CompanyReport, Identificacion, Contacto, Domicilio,
|
| 12 |
+
DatosFiscales, DatosFinancieros, DatosSocietarios, DatosPatrimoniales,
|
| 13 |
+
DatosJudiciales, PublicacionBO, BcraHistorial, ChequeRechazado,
|
| 14 |
+
CausaJudicial, Vehiculo, InfraccionTransito, MarcaINPI, Vinculo,
|
| 15 |
+
DatosPrevisionales, DatosAcademicos, TituloAcademico, IndicadorRiesgo,
|
| 16 |
+
Inmueble, Inhibicion, DatosRegistroCivil, GroupReport, VehicleReport,
|
| 17 |
+
PropertyReport, ReportMeta, ActividadFiscal, CompanyIdentificacion, RedSocial,
|
| 18 |
+
DatosIGJ, ViasSalud, ContratoEstado, MatriculaProfesional,
|
| 19 |
+
DatosBilleterasVirtuales, BilleteraVirtual, TimelineEvent, DeudorAlimentario, Monotributo
|
| 20 |
+
)
|
| 21 |
+
from app.reports.schemas import (
|
| 22 |
+
CompanyReport, VehicleReport, PropertyReport, GroupReport
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
logger = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# βββ Helper Functions βββ
|
| 29 |
+
|
| 30 |
+
def _safe_get(data: dict, key: str, default=None):
|
| 31 |
+
"""Obtiene valor de dict de forma segura."""
|
| 32 |
+
if not isinstance(data, dict):
|
| 33 |
+
return default
|
| 34 |
+
return data.get(key, default)
|
| 35 |
+
|
| 36 |
+
def _safe_list(data: dict, key: str) -> list:
|
| 37 |
+
"""Obtiene lista de forma segura."""
|
| 38 |
+
val = _safe_get(data, key)
|
| 39 |
+
return val if isinstance(val, list) else []
|
| 40 |
+
|
| 41 |
+
def _safe_dict(data: dict, key: str) -> dict:
|
| 42 |
+
val = _safe_get(data, key)
|
| 43 |
+
return val if isinstance(val, dict) else {}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
# βββ Builders βββ
|
| 47 |
+
|
| 48 |
+
def build_identificacion(data: dict, cuit: str) -> Identificacion:
|
| 49 |
+
"""Construye secciΓ³n de identificaciΓ³n."""
|
| 50 |
+
return Identificacion(
|
| 51 |
+
cuit=cuit,
|
| 52 |
+
tipo_clave=_safe_get(data, "tipo_clave"),
|
| 53 |
+
tipo_documento=_safe_get(data, "tipo_documento"),
|
| 54 |
+
numero_documento=_safe_get(data, "numero_documento"),
|
| 55 |
+
nombres=_safe_get(data, "nombres"),
|
| 56 |
+
apellido=_safe_get(data, "apellido"),
|
| 57 |
+
nombres_completo=_safe_get(data, "nombres_completo"),
|
| 58 |
+
fecha_nacimiento=_safe_get(data, "fecha_nacimiento"),
|
| 59 |
+
fecha_fallecimiento=_safe_get(data, "fecha_fallecimiento"),
|
| 60 |
+
sexo=_safe_get(data, "sexo"),
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def build_contacto(data: dict) -> Contacto:
|
| 65 |
+
"""Construye secciΓ³n de contacto."""
|
| 66 |
+
# Intentar desde redes sociales
|
| 67 |
+
redes = _safe_list(data.get("redes_sociales", {}), "redes_sociales") if isinstance(data.get("redes_sociales"), dict) else _safe_list(data, "redes_sociales")
|
| 68 |
+
emails = []
|
| 69 |
+
phones = []
|
| 70 |
+
for r in redes:
|
| 71 |
+
if isinstance(r, dict):
|
| 72 |
+
if r.get("tipo") == "email":
|
| 73 |
+
emails.append(r.get("valor", ""))
|
| 74 |
+
elif r.get("tipo") == "telefono":
|
| 75 |
+
phones.append(r.get("valor", ""))
|
| 76 |
+
|
| 77 |
+
return Contacto(
|
| 78 |
+
emails=emails,
|
| 79 |
+
telefonos=phones,
|
| 80 |
+
redes_sociales=_safe_list(data, "redes_sociales"),
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def build_domicilios(data: dict) -> list[Domicilio]:
|
| 85 |
+
"""Construye lista de domicilios."""
|
| 86 |
+
domicilios_raw = _safe_list(data, "domicilios")
|
| 87 |
+
domicilios = []
|
| 88 |
+
for d in domicilios_raw:
|
| 89 |
+
if isinstance(d, dict):
|
| 90 |
+
domicilios.append(Domicilio(
|
| 91 |
+
calle=_safe_get(d, "calle"),
|
| 92 |
+
numero=_safe_get(d, "numero"),
|
| 93 |
+
piso=_safe_get(d, "piso"),
|
| 94 |
+
depto=_safe_get(d, "depto"),
|
| 95 |
+
localidad=_safe_get(d, "localidad"),
|
| 96 |
+
provincia=_safe_get(d, "provincia"),
|
| 97 |
+
codigo_postal=_safe_get(d, "codigo_postal"),
|
| 98 |
+
tipo=_safe_get(d, "tipo"),
|
| 99 |
+
))
|
| 100 |
+
return domicilios
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def build_datos_fiscales(arca_data: dict, cuit: str, actividades: list[ActividadFiscal] = None) -> DatosFiscales:
|
| 104 |
+
"""Construye datos fiscales desde ARCA/AFIP."""
|
| 105 |
+
if not isinstance(arca_data, dict):
|
| 106 |
+
arca_data = {}
|
| 107 |
+
|
| 108 |
+
domicilios = build_domicilios(arca_data)
|
| 109 |
+
domicilio_fiscal_raw = _safe_get(arca_data, "domicilio_fiscal")
|
| 110 |
+
domicilio_fiscal = Domicilio(**domicilio_fiscal_raw) if isinstance(domicilio_fiscal_raw, dict) and domicilio_fiscal_raw else None
|
| 111 |
+
|
| 112 |
+
if domicilio_fiscal and not domicilios:
|
| 113 |
+
domicilios = [domicilio_fiscal]
|
| 114 |
+
|
| 115 |
+
monotributo_raw = _safe_get(arca_data, "monotributo")
|
| 116 |
+
monotributo_obj = Monotributo(**monotributo_raw) if isinstance(monotributo_raw, dict) else None
|
| 117 |
+
|
| 118 |
+
return DatosFiscales(
|
| 119 |
+
cuit=arca_data.get("cuit", cuit),
|
| 120 |
+
tipo_clave=_safe_get(arca_data, "tipo_clave"),
|
| 121 |
+
tipo_documento=_safe_get(arca_data, "tipo_documento"),
|
| 122 |
+
numero_documento=_safe_get(arca_data, "numero_documento"),
|
| 123 |
+
nombres=_safe_get(arca_data, "nombres"),
|
| 124 |
+
apellido=_safe_get(arca_data, "apellido"),
|
| 125 |
+
nombres_completo=_safe_get(arca_data, "nombres_completo"),
|
| 126 |
+
condicion_iva=_safe_get(arca_data, "condicion_iva"),
|
| 127 |
+
estado_afip=_safe_get(arca_data, "estado_afip"),
|
| 128 |
+
fecha_inicio_actividad=_safe_get(arca_data, "fecha_inicio_actividad"),
|
| 129 |
+
fecha_nacimiento=_safe_get(arca_data, "fecha_nacimiento"),
|
| 130 |
+
fecha_contrato_social=_safe_get(arca_data, "fecha_contrato_social"),
|
| 131 |
+
fecha_fallecimiento=_safe_get(arca_data, "fecha_fallecimiento"),
|
| 132 |
+
forma_juridica=_safe_get(arca_data, "forma_juridica"),
|
| 133 |
+
mes_cierre=_safe_get(arca_data, "mes_cierre"),
|
| 134 |
+
periodo_actividad_principal=_safe_get(arca_data, "periodo_actividad_principal"),
|
| 135 |
+
actividades=actividades or [],
|
| 136 |
+
domicilios=domicilios,
|
| 137 |
+
domicilio_fiscal=domicilio_fiscal,
|
| 138 |
+
monotributo=monotributo_obj,
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def build_datos_financieros(bcra_data: dict, historial_financiero_anterior: dict = None) -> DatosFinancieros:
|
| 143 |
+
"""Construye datos financieros desde BCRA + fallback histΓ³rico."""
|
| 144 |
+
if not isinstance(bcra_data, dict):
|
| 145 |
+
bcra_data = {}
|
| 146 |
+
|
| 147 |
+
deudas = []
|
| 148 |
+
for ent in _safe_list(bcra_data, "entidades"):
|
| 149 |
+
if isinstance(ent, dict):
|
| 150 |
+
deudas.append(BcraHistorial(
|
| 151 |
+
entidad=ent.get("entidad"),
|
| 152 |
+
situacion=ent.get("situacion"),
|
| 153 |
+
fecha=ent.get("fecha"),
|
| 154 |
+
monto=ent.get("monto"),
|
| 155 |
+
tipo=ent.get("tipo"),
|
| 156 |
+
))
|
| 157 |
+
|
| 158 |
+
cheques = []
|
| 159 |
+
for ch in _safe_list(bcra_data, "cheques_rechazados"):
|
| 160 |
+
if isinstance(ch, dict):
|
| 161 |
+
cheques.append(ChequeRechazado(
|
| 162 |
+
fecha=ch.get("fecha"),
|
| 163 |
+
banco=ch.get("banco"),
|
| 164 |
+
sucursal=ch.get("sucursal"),
|
| 165 |
+
monto=ch.get("monto"),
|
| 166 |
+
motivo=ch.get("motivo"),
|
| 167 |
+
))
|
| 168 |
+
|
| 169 |
+
# Fallback histΓ³rico si BCRA fallΓ³
|
| 170 |
+
if not _safe_get(bcra_data, "entidades") and historial_financiero_anterior:
|
| 171 |
+
hfa = historial_financiero_anterior
|
| 172 |
+
situacion = hfa.get("bcra_situacion_actual")
|
| 173 |
+
if situacion:
|
| 174 |
+
bcra_data = {
|
| 175 |
+
"bcra_situacion_actual": situacion,
|
| 176 |
+
"entidades": hfa.get("entidades", []),
|
| 177 |
+
"cheques_rechazados": hfa.get("cheques_rechazados", []),
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
situacion = _safe_get(bcra_data, "bcra_situacion_actual")
|
| 181 |
+
if situacion is None:
|
| 182 |
+
situacion = 1 # Normal por defecto
|
| 183 |
+
|
| 184 |
+
return DatosFinancieros(
|
| 185 |
+
bcra_situacion_actual=situacion,
|
| 186 |
+
bcra_situacion_descripcion=SITUACIONES_BCRA.get(situacion, "Desconocida"),
|
| 187 |
+
bcra_historial=_safe_list(bcra_data, "entidades"),
|
| 188 |
+
bcra_total_deuda_miles=_safe_get(bcra_data, "bcra_total_deuda_miles", 0.0),
|
| 189 |
+
bcra_dias_atraso_max=_safe_get(bcra_data, "bcra_dias_atraso_max", 0),
|
| 190 |
+
cheques_rechazados=_safe_list(bcra_data, "cheques_rechazados"),
|
| 191 |
+
tiene_deuda=_safe_get(bcra_data, "tiene_deuda", False),
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def build_datos_societarios(igj_data: dict, inpi_data: dict, compras_data: dict) -> DatosSocietarios:
|
| 196 |
+
"""Construye datos societarios."""
|
| 197 |
+
return DatosSocietarios(
|
| 198 |
+
igj=_safe_dict(igj_data, "igj"),
|
| 199 |
+
inpi=_safe_dict(inpi_data, "inpi"),
|
| 200 |
+
compras_estatales=_safe_dict(compras_data, "compras_estatales"),
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def build_datos_patrimoniales(
|
| 205 |
+
arba_auto_data: dict, arba_catastro_data: dict,
|
| 206 |
+
dnrpa_data: dict, infracciones_data: dict,
|
| 207 |
+
carto_arba_data: dict, sinais_data: dict
|
| 208 |
+
) -> DatosPatrimoniales:
|
| 209 |
+
"""Construye datos patrimoniales."""
|
| 210 |
+
inmuebles = []
|
| 211 |
+
for inm in _safe_list(arba_catastro_data, "inmuebles"):
|
| 212 |
+
if isinstance(inm, dict):
|
| 213 |
+
inmuebles.append(Inmueble(
|
| 214 |
+
partida=inm.get("partida"),
|
| 215 |
+
nomenclatura=inm.get("nomenclatura"),
|
| 216 |
+
direccion=inm.get("direccion"),
|
| 217 |
+
superficie=inm.get("superficie"),
|
| 218 |
+
valuacion=inm.get("valuacion"),
|
| 219 |
+
tipo=inm.get("tipo"),
|
| 220 |
+
))
|
| 221 |
+
|
| 222 |
+
vehiculos = []
|
| 223 |
+
for v in _safe_list(dnrpa_data, "vehiculos"):
|
| 224 |
+
if isinstance(v, dict):
|
| 225 |
+
vehiculos.append(Vehiculo(
|
| 226 |
+
dominio=v.get("dominio"),
|
| 227 |
+
marca=v.get("marca"),
|
| 228 |
+
modelo=v.get("modelo"),
|
| 229 |
+
anio=v.get("anio"),
|
| 230 |
+
tipo=v.get("tipo"),
|
| 231 |
+
registro=v.get("registro"),
|
| 232 |
+
titular=v.get("titular"),
|
| 233 |
+
estado=v.get("estado"),
|
| 234 |
+
))
|
| 235 |
+
|
| 236 |
+
infracciones = []
|
| 237 |
+
for inf in _safe_list(infracciones_data, "infracciones"):
|
| 238 |
+
if isinstance(inf, dict):
|
| 239 |
+
infracciones.append(InfraccionTransito(
|
| 240 |
+
fecha=inf.get("fecha"),
|
| 241 |
+
tipo=inf.get("tipo"),
|
| 242 |
+
jurisdiccion=inf.get("jurisdiccion"),
|
| 243 |
+
monto=inf.get("monto"),
|
| 244 |
+
estado=inf.get("estado"),
|
| 245 |
+
))
|
| 246 |
+
|
| 247 |
+
return DatosPatrimoniales(
|
| 248 |
+
inmuebles=inmuebles,
|
| 249 |
+
vehiculos=vehiculos,
|
| 250 |
+
infracciones_transito=infracciones,
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def build_datos_judiciales(
|
| 255 |
+
pj_data: dict, pj_prov_data: dict,
|
| 256 |
+
bo_data: dict, bo_prov_data: dict,
|
| 257 |
+
inhibiciones_data: dict, juba_data: dict,
|
| 258 |
+
siscop_data: dict
|
| 259 |
+
) -> DatosJudiciales:
|
| 260 |
+
"""Construye datos judiciales."""
|
| 261 |
+
causas = []
|
| 262 |
+
for c in _safe_list(pj_data, "causas"):
|
| 263 |
+
if isinstance(c, dict):
|
| 264 |
+
causas.append(CausaJudicial(
|
| 265 |
+
numero=c.get("numero"),
|
| 266 |
+
fuero=c.get("fuero"),
|
| 267 |
+
caratula=c.get("caratula"),
|
| 268 |
+
estado=c.get("estado"),
|
| 269 |
+
fecha=c.get("fecha"),
|
| 270 |
+
rol=c.get("rol"),
|
| 271 |
+
))
|
| 272 |
+
|
| 273 |
+
publicaciones = []
|
| 274 |
+
for p in _safe_list(bo_data, "publicaciones"):
|
| 275 |
+
if isinstance(p, dict):
|
| 276 |
+
publicaciones.append(PublicacionBO(
|
| 277 |
+
fecha=p.get("fecha"),
|
| 278 |
+
seccion=p.get("seccion"),
|
| 279 |
+
texto=p.get("texto"),
|
| 280 |
+
url=p.get("url"),
|
| 281 |
+
))
|
| 282 |
+
|
| 283 |
+
inhibiciones = []
|
| 284 |
+
for inh in _safe_list(inhibiciones_data, "inhibiciones"):
|
| 285 |
+
if isinstance(inh, dict):
|
| 286 |
+
inhibiciones.append(Inhibicion(
|
| 287 |
+
numero=inh.get("numero"),
|
| 288 |
+
juzgado=inh.get("juzgado"),
|
| 289 |
+
fecha=inh.get("fecha"),
|
| 290 |
+
monto=inh.get("monto"),
|
| 291 |
+
estado=inh.get("estado"),
|
| 292 |
+
))
|
| 293 |
+
|
| 294 |
+
return DatosJudiciales(
|
| 295 |
+
causas=causas,
|
| 296 |
+
publicaciones_bo=publicaciones,
|
| 297 |
+
inhibiciones=inhibiciones,
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def build_vinculos(igj_data: dict, domicilios: list[Domicilio]) -> list[Vinculo]:
|
| 302 |
+
"""Detecta vΓnculos por domicilio compartido (IGJ)."""
|
| 303 |
+
vinculos = []
|
| 304 |
+
if not igj_data or not domicilios:
|
| 305 |
+
return vinculos
|
| 306 |
+
|
| 307 |
+
doms_persona = set()
|
| 308 |
+
for d in domicilios:
|
| 309 |
+
parts = [d.calle, d.numero, d.localidad]
|
| 310 |
+
normalized = " ".join(p.lower().strip() for p in parts if p).strip()
|
| 311 |
+
if normalized and len(normalized) > 5:
|
| 312 |
+
doms_persona.add(normalized)
|
| 313 |
+
|
| 314 |
+
for socio in igj_data.get("socios_directivos", []):
|
| 315 |
+
if not isinstance(socio, dict):
|
| 316 |
+
continue
|
| 317 |
+
dom_socio = socio.get("domicilio", "")
|
| 318 |
+
if dom_socio:
|
| 319 |
+
dom_norm = " ".join(dom_socio.lower().split()[:5])
|
| 320 |
+
for dom_p in doms_persona:
|
| 321 |
+
if dom_norm and dom_p and (dom_norm in dom_p or dom_p in dom_norm):
|
| 322 |
+
vinculos.append(Vinculo(
|
| 323 |
+
cuit=socio.get("cuil"),
|
| 324 |
+
nombre=socio.get("nombre", ""),
|
| 325 |
+
tipo="Domiciliario",
|
| 326 |
+
detalle=f"Domicilio compartido: {dom_socio}"
|
| 327 |
+
))
|
| 328 |
+
break
|
| 329 |
+
return vinculos
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def build_riesgo_indicadores(data: dict, all_results: dict) -> list[IndicadorRiesgo]:
|
| 333 |
+
"""Construye indicadores de riesgo."""
|
| 334 |
+
indicadores = []
|
| 335 |
+
|
| 336 |
+
# BCRA
|
| 337 |
+
financiero = _safe_dict(data, "financiero")
|
| 338 |
+
situacion = financiero.get("bcra_situacion_actual")
|
| 339 |
+
if situacion and situacion > 1:
|
| 340 |
+
indicadores.append(IndicadorRiesgo(
|
| 341 |
+
codigo="BCRA_SITUACION",
|
| 342 |
+
nivel="Alto" if situacion >= 4 else "Medio",
|
| 343 |
+
descripcion=f"SituaciΓ³n BCRA: {SITUACIONES_BCRA.get(situacion, 'Desconocida')}",
|
| 344 |
+
detalle=f"SituaciΓ³n actual: {situacion}",
|
| 345 |
+
))
|
| 346 |
+
|
| 347 |
+
# Judicial
|
| 348 |
+
judicial = _safe_dict(data, "judicial")
|
| 349 |
+
causas = _safe_list(judicial, "causas")
|
| 350 |
+
if len(causas) > 3:
|
| 351 |
+
indicadores.append(IndicadorRiesgo(
|
| 352 |
+
codigo="JUDICIAL_CAUSAS_MULTIPLES",
|
| 353 |
+
nivel="Medio",
|
| 354 |
+
descripcion="MΓΊltiples causas judiciales",
|
| 355 |
+
detalle=f"Se detectaron {len(causas)} causas judiciales",
|
| 356 |
+
))
|
| 357 |
+
|
| 358 |
+
# Inhibiciones
|
| 359 |
+
inhibiciones = _safe_list(judicial, "inhibiciones")
|
| 360 |
+
if inhibiciones:
|
| 361 |
+
indicadores.append(IndicadorRiesgo(
|
| 362 |
+
codigo="INHIBICIONES",
|
| 363 |
+
nivel="Alto",
|
| 364 |
+
descripcion="Inhibiciones registradas",
|
| 365 |
+
detalle=f"{len(inhibiciones)} inhibiciΓ³n(es) activa(s)",
|
| 366 |
+
))
|
| 367 |
+
|
| 368 |
+
# Deudores alimentarios
|
| 369 |
+
deudores = _safe_list(data.get("deudores_alimentarios", {}), "deudores")
|
| 370 |
+
if deudores:
|
| 371 |
+
indicadores.append(IndicadorRiesgo(
|
| 372 |
+
codigo="DEUDOR_ALIMENTARIO",
|
| 373 |
+
nivel="Alto",
|
| 374 |
+
descripcion="Registro de deudor alimentario moroso",
|
| 375 |
+
detalle="Aparece en RDAM",
|
| 376 |
+
))
|
| 377 |
+
|
| 378 |
+
# BCRA cheques rechazados
|
| 379 |
+
cheques = _safe_list(financiero, "cheques_rechazados")
|
| 380 |
+
if cheques:
|
| 381 |
+
indicadores.append(IndicadorRiesgo(
|
| 382 |
+
codigo="CHEQUES_RECHAZADOS",
|
| 383 |
+
nivel="Medio",
|
| 384 |
+
descripcion="Cheques rechazados registrados",
|
| 385 |
+
detalle=f"{len(cheques)} cheque(s) rechazado(s)",
|
| 386 |
+
))
|
| 387 |
+
|
| 388 |
+
return indicadores
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
def build_person_report(
|
| 392 |
+
cuit: str,
|
| 393 |
+
data: dict,
|
| 394 |
+
results: dict,
|
| 395 |
+
force_refresh: bool = False,
|
| 396 |
+
) -> PersonReport:
|
| 397 |
+
"""Construye reporte completo de persona."""
|
| 398 |
+
|
| 399 |
+
# Datos base
|
| 400 |
+
arca_data = _safe_dict(results.get("arca_afip", {}), "data") if "arca_afip" in results else _safe_dict(data, "fiscal")
|
| 401 |
+
bcra_data = _safe_dict(results.get("bcra", {}), "data") if "bcra" in results else _safe_dict(data, "financiero")
|
| 402 |
+
igj_data = _safe_dict(results.get("igj", {}), "data") if "igj" in results else _safe_dict(data, "societario", {}).get("igj", {})
|
| 403 |
+
inpi_data = _safe_dict(results.get("inpi", {}), "data") if "inpi" in results else _safe_dict(data, "societario", {}).get("inpi", {})
|
| 404 |
+
compras_data = _safe_dict(results.get("compras_estatales", {}), "data") if "compras_estatales" in results else _safe_dict(data, "societario", {}).get("compras_estatales", {})
|
| 405 |
+
|
| 406 |
+
# IdentificaciΓ³n
|
| 407 |
+
identificacion = build_identificacion(arca_data, cuit)
|
| 408 |
+
contacto = build_contacto(_safe_dict(data, "contacto"))
|
| 409 |
+
domicilios = build_domicilios(arca_data)
|
| 410 |
+
|
| 411 |
+
# Secciones principales
|
| 412 |
+
fiscal = build_datos_fiscales(arca_data, cuit)
|
| 413 |
+
financiero = build_datos_financieros(bcra_data, _safe_dict(data, "financiero"))
|
| 414 |
+
societario = build_datos_societarios(igj_data, inpi_data, compras_data)
|
| 415 |
+
patrimonial = build_datos_patrimoniales(
|
| 416 |
+
_safe_dict(data, "arba_automotores"),
|
| 417 |
+
_safe_dict(data, "arba_catastro"),
|
| 418 |
+
_safe_dict(data, "dnrpa"),
|
| 419 |
+
_safe_dict(data, "infracciones"),
|
| 420 |
+
_safe_dict(data, "carto_arba"),
|
| 421 |
+
_safe_dict(data, "sinai"),
|
| 422 |
+
)
|
| 423 |
+
judicial = build_datos_judiciales(
|
| 424 |
+
_safe_dict(data, "poder_judicial"),
|
| 425 |
+
_safe_dict(data, "poder_judicial_provincial"),
|
| 426 |
+
_safe_dict(data, "boletin_oficial"),
|
| 427 |
+
_safe_dict(data, "boletines_provinciales"),
|
| 428 |
+
_safe_dict(data, "inhibiciones"),
|
| 429 |
+
_safe_dict(data, "juba"),
|
| 430 |
+
_safe_dict(data, "siscop"),
|
| 431 |
+
)
|
| 432 |
+
previsional = DatosPrevisionales(
|
| 433 |
+
anses=_safe_dict(data, "anses"),
|
| 434 |
+
monotributo_historial=_safe_dict(data, "monotributo_historial"),
|
| 435 |
+
)
|
| 436 |
+
academico = DatosAcademicos(
|
| 437 |
+
sgarhu=_safe_dict(data, "sgarhu"),
|
| 438 |
+
titulos=[],
|
| 439 |
+
)
|
| 440 |
+
registro_civil = DatosRegistroCivil(
|
| 441 |
+
siscop=_safe_dict(data, "siscop"),
|
| 442 |
+
defunciones=[],
|
| 443 |
+
)
|
| 444 |
+
billeteras = DatosBilleterasVirtuales(
|
| 445 |
+
billeteras=_safe_list(data.get("billeteras_virtuales", {}), "billeteras"),
|
| 446 |
+
)
|
| 447 |
+
compras_estatales = ContratoEstado(**_safe_dict(data, "compras_estatales")) if data.get("compras_estatales") else None
|
| 448 |
+
|
| 449 |
+
# Marcas INPI
|
| 450 |
+
marcas_inpi = []
|
| 451 |
+
for m in _safe_list(inpi_data, "marcas"):
|
| 452 |
+
if isinstance(m, dict):
|
| 453 |
+
marcas_inpi.append(MarcaINPI(
|
| 454 |
+
numero=m.get("numero"),
|
| 455 |
+
denominacion=m.get("denominacion"),
|
| 456 |
+
clase=m.get("clase"),
|
| 457 |
+
estado=m.get("estado"),
|
| 458 |
+
titular=m.get("titular"),
|
| 459 |
+
fecha_presentacion=m.get("fecha_presentacion"),
|
| 460 |
+
))
|
| 461 |
+
|
| 462 |
+
# VΓnculos
|
| 463 |
+
domicilios_objs = [Domicilio(**d) if isinstance(d, dict) else d for d in _safe_list(arca_data, "domicilios")]
|
| 464 |
+
vinculos = build_vinculos(igj_data, domicilios_objs)
|
| 465 |
+
|
| 466 |
+
# Riesgo
|
| 467 |
+
riesgo = build_riesgo_indicadores(data, _safe_dict(data, "resultados"))
|
| 468 |
+
|
| 469 |
+
# Timeline BO
|
| 470 |
+
timeline = []
|
| 471 |
+
for t in _safe_list(data.get("timeline_boa", {}), "eventos"):
|
| 472 |
+
if isinstance(t, dict):
|
| 473 |
+
timeline.append(TimelineEvent(
|
| 474 |
+
fecha=t.get("fecha"),
|
| 475 |
+
tipo=t.get("tipo"),
|
| 476 |
+
descripcion=t.get("descripcion"),
|
| 477 |
+
fuente=t.get("fuente"),
|
| 478 |
+
))
|
| 479 |
+
|
| 480 |
+
# Deudores alimentarios
|
| 481 |
+
deudores = []
|
| 482 |
+
for d in _safe_list(data.get("deudores_alimentarios", {}), "deudores"):
|
| 483 |
+
if isinstance(d, dict):
|
| 484 |
+
deudores.append(DeudorAlimentario(
|
| 485 |
+
dni=d.get("dni"),
|
| 486 |
+
nombre=d.get("nombre"),
|
| 487 |
+
estado=d.get("estado"),
|
| 488 |
+
detalle=d.get("detalle"),
|
| 489 |
+
))
|
| 490 |
+
|
| 491 |
+
# Monotributo historial
|
| 492 |
+
monotributo = None
|
| 493 |
+
mh = _safe_dict(data, "monotributo_historial")
|
| 494 |
+
if mh:
|
| 495 |
+
monotributo = Monotributo(
|
| 496 |
+
historial_categorias=_safe_list(mh, "historial_categorias"),
|
| 497 |
+
categoria_actual=mh.get("categoria_actual"),
|
| 498 |
+
es_monotributista=mh.get("es_monotributista", False),
|
| 499 |
+
nota=mh.get("nota"),
|
| 500 |
+
)
|
| 501 |
+
|
| 502 |
+
# Registro conductores
|
| 503 |
+
licencia = None
|
| 504 |
+
rc = _safe_dict(data, "registro_conductores")
|
| 505 |
+
if rc:
|
| 506 |
+
licencia = {
|
| 507 |
+
"numero": rc.get("numero"),
|
| 508 |
+
"categoria": rc.get("categoria"),
|
| 509 |
+
"vencimiento": rc.get("vencimiento"),
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
# Score
|
| 513 |
+
score = compute_score(PersonReport(
|
| 514 |
+
meta=ReportMeta(report_id="", generated_at=datetime.now(timezone.utc), sources=[]),
|
| 515 |
+
identificacion=identificacion,
|
| 516 |
+
contacto=contacto,
|
| 517 |
+
domicilios=domicilios,
|
| 518 |
+
fiscal=fiscal,
|
| 519 |
+
financiero=financiero,
|
| 520 |
+
societario=societario,
|
| 521 |
+
patrimonial=patrimonial,
|
| 522 |
+
judicial=judicial,
|
| 523 |
+
previsional=previsional,
|
| 524 |
+
academico=academico,
|
| 525 |
+
registro_civil=registro_civil,
|
| 526 |
+
billeteras_virtuales=billeteras,
|
| 527 |
+
compras_estatales=compras_estatales,
|
| 528 |
+
marcas_inpi=marcas_inpi,
|
| 529 |
+
timeline=timeline,
|
| 530 |
+
deudores_alimentarios=deudores,
|
| 531 |
+
monotributo_historial=monotributo,
|
| 532 |
+
licencia_conducir=licencia,
|
| 533 |
+
vinculos=[],
|
| 534 |
+
riesgo=riesgo,
|
| 535 |
+
))
|
| 536 |
+
|
| 537 |
+
return PersonReport(
|
| 538 |
+
meta=ReportMeta(
|
| 539 |
+
report_id=uuid.uuid4().hex[:12],
|
| 540 |
+
generated_at=datetime.now(timezone.utc),
|
| 541 |
+
sources=[s for s in ["arca_afip", "bcra", "igj", "inpi", "bcra", "dnrpa", "poder_judicial",
|
| 542 |
+
"renaper", "anses", "cnv", "uif", "boletin_oficial", "boletines_provinciales",
|
| 543 |
+
"anses", "sinai", "inpi", "juba", "inhibiciones", "poder_judicial_provincial",
|
| 544 |
+
"siscop", "billeteras_virtuales", "ruido", "sgarhu", "contratar",
|
| 545 |
+
"colegios_profesionales", "renaper_facial", "archive_org", "padron_electoral",
|
| 546 |
+
"compras_estatales", "cnv"] if s in results],
|
| 547 |
+
),
|
| 548 |
+
score=score,
|
| 549 |
+
identificacion=identificacion,
|
| 550 |
+
contacto=contacto,
|
| 551 |
+
domicilios=domicilios,
|
| 552 |
+
fiscal=fiscal,
|
| 553 |
+
financiero=financiero,
|
| 554 |
+
societario=societario,
|
| 555 |
+
patrimonial=patrimonial,
|
| 556 |
+
judicial=judicial,
|
| 557 |
+
previsional=previsional,
|
| 558 |
+
academico=academico,
|
| 559 |
+
registro_civil=registro_civil,
|
| 560 |
+
billeteras_virtuales=billeteras,
|
| 561 |
+
compras_estatales=compras_estatales,
|
| 562 |
+
marcas_inpi=marcas_inpi,
|
| 563 |
+
timeline=timeline,
|
| 564 |
+
deudores_alimentarios=deudores,
|
| 565 |
+
monotributo_historial=monotributo,
|
| 566 |
+
registro_conductores=licencia,
|
| 567 |
+
vinculos=vinculos,
|
| 568 |
+
riesgo=riesgo,
|
| 569 |
+
)
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
def build_company_report(cuit: str, data: dict, results: dict) -> CompanyReport:
|
| 573 |
+
"""Construye reporte de empresa."""
|
| 574 |
+
# Similar a build_person_report pero para empresa
|
| 575 |
+
# ... implementaciΓ³n simplificada
|
| 576 |
+
return CompanyReport(
|
| 577 |
+
meta=ReportMeta(report_id=uuid.uuid4().hex[:12], generated_at=datetime.now(timezone.utc), sources=[]),
|
| 578 |
+
score=compute_company_score(CompanyReport(
|
| 579 |
+
meta=ReportMeta(report_id="", generated_at=datetime.now(timezone.utc), sources=[]),
|
| 580 |
+
identificacion=CompanyIdentificacion(cuit=cuit, razon_social=""),
|
| 581 |
+
fiscal=DatosFiscales(cuit=cuit),
|
| 582 |
+
financiero=DatosFinancieros(bcra_situacion_actual=1, bcra_situacion_descripcion="Normal"),
|
| 583 |
+
societario=DatosSocietarios(),
|
| 584 |
+
judicial=DatosJudiciales(),
|
| 585 |
+
patrimonial=DatosPatrimoniales(),
|
| 586 |
+
billeteras_virtuales=DatosBilleterasVirtuales(),
|
| 587 |
+
riesgo=[],
|
| 588 |
+
)),
|
| 589 |
+
identificacion=CompanyIdentificacion(cuit=cuit, razon_social=""),
|
| 590 |
+
fiscal=DatosFiscales(cuit=cuit),
|
| 591 |
+
financiero=DatosFinancieros(bcra_situacion_actual=1, bcra_situacion_descripcion="Normal"),
|
| 592 |
+
societario=DatosSocietarios(),
|
| 593 |
+
judicial=DatosJudiciales(),
|
| 594 |
+
patrimonial=DatosPatrimoniales(),
|
| 595 |
+
billeteras_virtuales=DatosBilleterasVirtuales(),
|
| 596 |
+
riesgo=[],
|
| 597 |
+
)
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
def build_vehicle_report(dominio: str, data: dict, results: dict) -> VehicleReport:
|
| 601 |
+
"""Construye reporte de vehΓculo."""
|
| 602 |
+
dnrpa = _safe_dict(data, "dnrpa")
|
| 603 |
+
infracciones = _safe_dict(data, "infracciones")
|
| 604 |
+
siniestros = _safe_dict(data, "siniestros")
|
| 605 |
+
sinais = _safe_dict(data, "sinais")
|
| 606 |
+
|
| 607 |
+
vehiculo = None
|
| 608 |
+
for v in _safe_list(dnrpa, "vehiculos"):
|
| 609 |
+
if isinstance(v, dict) and v.get("dominio") == dominio:
|
| 610 |
+
vehiculo = Vehiculo(
|
| 611 |
+
dominio=v.get("dominio"),
|
| 612 |
+
marca=v.get("marca"),
|
| 613 |
+
modelo=v.get("modelo"),
|
| 614 |
+
anio=v.get("anio"),
|
| 615 |
+
tipo=v.get("tipo"),
|
| 616 |
+
registro=v.get("registro"),
|
| 617 |
+
titular=v.get("titular"),
|
| 618 |
+
estado=v.get("estado"),
|
| 619 |
+
)
|
| 620 |
+
break
|
| 621 |
+
|
| 622 |
+
infracciones_list = []
|
| 623 |
+
for inf in _safe_list(infracciones, "infracciones"):
|
| 624 |
+
if isinstance(inf, dict):
|
| 625 |
+
infracciones_list.append(InfraccionTransito(
|
| 626 |
+
fecha=inf.get("fecha"),
|
| 627 |
+
tipo=inf.get("tipo"),
|
| 628 |
+
jurisdiccion=inf.get("jurisdiccion"),
|
| 629 |
+
monto=inf.get("monto"),
|
| 630 |
+
estado=inf.get("estado"),
|
| 631 |
+
))
|
| 632 |
+
|
| 633 |
+
return VehicleReport(
|
| 634 |
+
meta=ReportMeta(report_id=uuid.uuid4().hex[:12], generated_at=datetime.now(timezone.utc), sources=list(results.keys())),
|
| 635 |
+
vehiculo=vehiculo,
|
| 636 |
+
infracciones=infracciones_list,
|
| 637 |
+
siniestros=[],
|
| 638 |
+
riesgo=[],
|
| 639 |
+
)
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
def build_property_report(calle: str, numero: str, localidad: str, provincia: str, data: dict, results: dict) -> PropertyReport:
|
| 643 |
+
"""Construye reporte de propiedad."""
|
| 644 |
+
arba_catastro = _safe_dict(data, "arba_catastro")
|
| 645 |
+
carto_arba = _safe_dict(data, "carto_arba")
|
| 646 |
+
bo_data = _safe_dict(data, "boletin_oficial")
|
| 647 |
+
bo_prov = _safe_dict(data, "boletin_oficial_pba")
|
| 648 |
+
|
| 649 |
+
inmuebles = []
|
| 650 |
+
for inm in _safe_list(arba_catastro, "inmuebles"):
|
| 651 |
+
if isinstance(inm, dict):
|
| 652 |
+
inmuebles.append(Inmueble(
|
| 653 |
+
partida=inm.get("partida"),
|
| 654 |
+
nomenclatura=inm.get("nomenclatura"),
|
| 655 |
+
direccion=inm.get("direccion"),
|
| 656 |
+
superficie=inm.get("superficie"),
|
| 657 |
+
valuacion=inm.get("valuacion"),
|
| 658 |
+
tipo=inm.get("tipo"),
|
| 659 |
+
))
|
| 660 |
+
|
| 661 |
+
return PropertyReport(
|
| 662 |
+
meta=ReportMeta(report_id=uuid.uuid4().hex[:12], generated_at=datetime.now(timezone.utc), sources=list(results.keys())),
|
| 663 |
+
direccion=f"{calle} {numero}, {localidad}, {provincia}",
|
| 664 |
+
inmuebles=inmuebles,
|
| 665 |
+
publicaciones_bo=[],
|
| 666 |
+
riesgo=[],
|
| 667 |
+
)
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
def build_group_report(cuit: str, data: dict, results: dict) -> GroupReport:
|
| 671 |
+
"""Construye reporte de grupo econΓ³mico."""
|
| 672 |
+
persona = build_person_report(cuit, data, results)
|
| 673 |
+
|
| 674 |
+
# TODO: Empresas vinculadas, vehΓculos, inmuebles
|
| 675 |
+
return GroupReport(
|
| 676 |
+
meta=ReportMeta(report_id=uuid.uuid4().hex[:12], generated_at=datetime.now(timezone.utc), sources=list(results.keys())),
|
| 677 |
+
score_grupo=compute_group_score(PersonReport(...), [], [], []), # Simplificado
|
| 678 |
+
persona=persona,
|
| 679 |
+
empresas=[],
|
| 680 |
+
vehiculos=[],
|
| 681 |
+
inmuebles=[],
|
| 682 |
+
)
|
app/reports/fallback.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Fallback Logic β Aplica datos histΓ³ricos cuando scrapers fallan.
|
| 3 |
+
"""
|
| 4 |
+
import logging
|
| 5 |
+
from typing import Any, Optional
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
SITUACIONES_BCRA = {
|
| 10 |
+
1: "Normal",
|
| 11 |
+
2: "Con seguimiento especial / Riesgo bajo",
|
| 12 |
+
3: "Con problemas / Riesgo medio",
|
| 13 |
+
4: "Con alto riesgo de insolvencia / Riesgo alto",
|
| 14 |
+
5: "Irrecuperable",
|
| 15 |
+
6: "Irrecuperable por disposiciΓ³n tΓ©cnica",
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
async def apply_fallbacks(
|
| 22 |
+
cuit: str,
|
| 23 |
+
results: dict,
|
| 24 |
+
cached_report: Optional[dict] = None,
|
| 25 |
+
) -> dict:
|
| 26 |
+
"""
|
| 27 |
+
Aplica fallbacks desde reporte cacheado anterior cuando scrapers fallan.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
cuit: CUIT consultado
|
| 31 |
+
results: Resultados de scrapers actuales (puede tener errores)
|
| 32 |
+
cached_report: Reporte cacheado anterior (ya parseado a dict)
|
| 33 |
+
|
| 34 |
+
Returns:
|
| 35 |
+
dict con datos combinados (actuales + fallback)
|
| 36 |
+
"""
|
| 37 |
+
if not cached_report:
|
| 38 |
+
logger.debug(f"No cached report for fallback: {cuit}")
|
| 39 |
+
return {r.name: r.data for r in results.values() if r.data}
|
| 40 |
+
|
| 41 |
+
merged = {}
|
| 42 |
+
|
| 43 |
+
for name, result in results.items():
|
| 44 |
+
if result.data:
|
| 45 |
+
merged[name] = result.data
|
| 46 |
+
continue
|
| 47 |
+
|
| 48 |
+
# Scraper fallΓ³ - intentar fallback
|
| 49 |
+
cached_key = name
|
| 50 |
+
if name in cached_report:
|
| 51 |
+
cached_data = cached_report[name]
|
| 52 |
+
if cached_data:
|
| 53 |
+
logger.info(f"[FALLBACK] Using cached data for {name} ({cuit})")
|
| 54 |
+
merged[name] = cached_data
|
| 55 |
+
else:
|
| 56 |
+
merged[name] = {}
|
| 57 |
+
else:
|
| 58 |
+
merged[name] = {}
|
| 59 |
+
|
| 60 |
+
# Fallback especΓfico BCRA con lΓ³gica de negocio
|
| 61 |
+
if "bcra" not in merged or not merged["bcra"]:
|
| 62 |
+
merged["bcra"] = _bcra_fallback(cached_report)
|
| 63 |
+
|
| 64 |
+
return merged
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _bcra_fallback(cached_report: Optional[dict]) -> dict:
|
| 68 |
+
"""Aplica lΓ³gica de fallback especΓfica para BCRA."""
|
| 69 |
+
if not cached_report or "bcra" not in cached_report:
|
| 70 |
+
return {
|
| 71 |
+
"denominacion": "",
|
| 72 |
+
"bcra_situacion_actual": 1,
|
| 73 |
+
"bcra_situacion_descripcion": SITUACIONES_BCRA[1],
|
| 74 |
+
"bcra_historial": [],
|
| 75 |
+
"bcra_total_deuda_miles": 0.0,
|
| 76 |
+
"bcra_dias_atraso_max": 0,
|
| 77 |
+
"cheques_rechazados": [],
|
| 78 |
+
"tiene_deuda": False,
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
old = cached_report["bcra"]
|
| 82 |
+
if not isinstance(old, dict):
|
| 83 |
+
return {"bcra_situacion_actual": 1}
|
| 84 |
+
|
| 85 |
+
# Si la situaciΓ³n histΓ³rica es peor (mayor nΓΊmero), usar esa
|
| 86 |
+
# Si la actual fallΓ³, usar la histΓ³rica
|
| 87 |
+
situacion_actual = old.get("bcra_situacion_actual", 1)
|
| 88 |
+
|
| 89 |
+
return {
|
| 90 |
+
"denominacion": old.get("denominacion", ""),
|
| 91 |
+
"bcra_situacion_actual": situacion_actual,
|
| 92 |
+
"bcra_situacion_descripcion": SITUACIONES_BCRA.get(situacion_actual, "Normal"),
|
| 93 |
+
"bcra_historial": old.get("bcra_historial", []),
|
| 94 |
+
"bcra_total_deuda_miles": old.get("bcra_total_deuda_miles", 0.0),
|
| 95 |
+
"bcra_dias_atraso_max": old.get("bcra_dias_atraso_max", 0),
|
| 96 |
+
"cheques_rechazados": old.get("cheques_rechazados", []),
|
| 97 |
+
"tiene_deuda": old.get("tiene_deuda", False),
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
async def apply_fallbacks_to_person_report(
|
| 102 |
+
cuit: str,
|
| 103 |
+
raw_results: dict,
|
| 104 |
+
cached_report: Optional[dict] = None,
|
| 105 |
+
) -> dict:
|
| 106 |
+
"""
|
| 107 |
+
Aplica fallbacks a resultados de reporte persona.
|
| 108 |
+
Retorna datos listos para build_person_report.
|
| 109 |
+
"""
|
| 110 |
+
merged = await apply_fallbacks(cuit, raw_results, cached_report)
|
| 111 |
+
return merged
|
app/reports/kyc_service.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Servicio KYC (Know Your Customer) Simplificado β CrowData.
|
| 3 |
+
|
| 4 |
+
Combina datos de RENAPER, BCRA y AFIP para ofrecer verificaciΓ³n
|
| 5 |
+
de identidad simplificada para empresas de fintech, inmobiliarias, etc.
|
| 6 |
+
|
| 7 |
+
NOTA: Este es un KYC simplificado basado en datos pΓΊblicos.
|
| 8 |
+
No reemplaza la debida diligencia legal requerida por normativas.
|
| 9 |
+
"""
|
| 10 |
+
import logging
|
| 11 |
+
from dataclasses import dataclass, field
|
| 12 |
+
from typing import Optional
|
| 13 |
+
from app.scrapers.arca_afip import ArcaAfipScraper
|
| 14 |
+
from app.scrapers.bcra import BcraScraper
|
| 15 |
+
from app.scrapers.anses import AnsesScraper
|
| 16 |
+
from app.scrapers.renaper import RenaperScraper
|
| 17 |
+
from app.scrapers.renaper_facial import RenaperFacialScraper
|
| 18 |
+
|
| 19 |
+
logger = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@dataclass
|
| 23 |
+
class KYCResult:
|
| 24 |
+
"""Resultado de verificaciΓ³n KYC simplificada."""
|
| 25 |
+
cuit: str
|
| 26 |
+
nombre_completo: str = ""
|
| 27 |
+
dni: str = ""
|
| 28 |
+
estado_civil: str = ""
|
| 29 |
+
fecha_nacimiento: str = ""
|
| 30 |
+
sexo: str = ""
|
| 31 |
+
nacionalidad: str = ""
|
| 32 |
+
|
| 33 |
+
# Verificaciones
|
| 34 |
+
verificado_renaper: bool = False
|
| 35 |
+
verificado_afip: bool = False
|
| 36 |
+
verificado_bcra: bool = False
|
| 37 |
+
verificado_anses: bool = False
|
| 38 |
+
|
| 39 |
+
# Riesgo
|
| 40 |
+
nivel_riesgo: str = "BAJO"
|
| 41 |
+
score_kyc: int = 0
|
| 42 |
+
observaciones: list = field(default_factory=list)
|
| 43 |
+
|
| 44 |
+
# Datos financieros
|
| 45 |
+
situacion_bcra: str = ""
|
| 46 |
+
estado_afip: str = ""
|
| 47 |
+
condicion_iva: str = ""
|
| 48 |
+
categoria_monotributo: str = ""
|
| 49 |
+
|
| 50 |
+
# Fuentes consultadas
|
| 51 |
+
fuentes_consultadas: list = field(default_factory=list)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class KYCService:
|
| 55 |
+
"""Servicio KYC simplificado."""
|
| 56 |
+
|
| 57 |
+
async def verify(self, cuit: str) -> KYCResult:
|
| 58 |
+
"""
|
| 59 |
+
Realiza verificaciΓ³n KYC simplificada.
|
| 60 |
+
Retorna resultado con verificaciones y nivel de riesgo.
|
| 61 |
+
"""
|
| 62 |
+
cuit_clean = cuit.replace("-", "").strip()
|
| 63 |
+
if len(cuit_clean) != 11 or not cuit_clean.isdigit():
|
| 64 |
+
return KYCResult(
|
| 65 |
+
cuit=cuit,
|
| 66 |
+
nivel_riesgo="ALTO",
|
| 67 |
+
observaciones=["CUIT invΓ‘lido"],
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
result = KYCResult(cuit=cuit)
|
| 71 |
+
score = 0
|
| 72 |
+
|
| 73 |
+
# 1. Verificar con RENAPER
|
| 74 |
+
try:
|
| 75 |
+
renaper = RenaperScraper()
|
| 76 |
+
renaper_data = await renaper.fetch(cuit_clean)
|
| 77 |
+
if renaper_data and renaper_data.get("dni"):
|
| 78 |
+
result.verificado_renaper = True
|
| 79 |
+
result.dni = renaper_data.get("dni", "")
|
| 80 |
+
result.nombre_completo = renaper_data.get("nombre_completo", "")
|
| 81 |
+
result.fecha_nacimiento = renaper_data.get("fecha_nacimiento", "")
|
| 82 |
+
result.sexo = renaper_data.get("sexo", "")
|
| 83 |
+
result.nacionalidad = renaper_data.get("nacionalidad", "ARGENTINA")
|
| 84 |
+
score += 30
|
| 85 |
+
result.fuentes_consultadas.append("RENAPER")
|
| 86 |
+
except Exception as e:
|
| 87 |
+
logger.debug(f"[KYC] RENAPER fallΓ³: {e}")
|
| 88 |
+
|
| 89 |
+
# 2. Verificar con ARCA/AFIP
|
| 90 |
+
try:
|
| 91 |
+
arca = ArcaAfipScraper()
|
| 92 |
+
arca_data = await arca.fetch(cuit_clean)
|
| 93 |
+
if arca_data and arca_data.get("estado"):
|
| 94 |
+
result.verificado_afip = True
|
| 95 |
+
result.estado_afip = arca_data.get("estado", "")
|
| 96 |
+
result.condicion_iva = arca_data.get("condicion_iva", "")
|
| 97 |
+
result.categoria_monotributo = arca_data.get("monotributo", {}).get("categoria", "")
|
| 98 |
+
if not result.nombre_completo and arca_data.get("nombre"):
|
| 99 |
+
result.nombre_completo = f"{arca_data.get('apellido', '')} {arca_data.get('nombre', '')}".strip()
|
| 100 |
+
score += 25
|
| 101 |
+
result.fuentes_consultadas.append("ARCA/AFIP")
|
| 102 |
+
except Exception as e:
|
| 103 |
+
logger.debug(f"[KYC] ARCA fallΓ³: {e}")
|
| 104 |
+
|
| 105 |
+
# 3. Verificar con BCRA
|
| 106 |
+
try:
|
| 107 |
+
bcra = BcraScraper()
|
| 108 |
+
bcra_data = await bcra.fetch(cuit_clean)
|
| 109 |
+
if bcra_data:
|
| 110 |
+
result.verificado_bcra = True
|
| 111 |
+
result.situacion_bcra = bcra_data.get("situacion", "")
|
| 112 |
+
score += 25
|
| 113 |
+
result.fuentes_consultadas.append("BCRA")
|
| 114 |
+
|
| 115 |
+
# Evaluar riesgo financiero
|
| 116 |
+
situacion = bcra_data.get("situacion_normal", 0)
|
| 117 |
+
if situacion and situacion > 3:
|
| 118 |
+
result.nivel_riesgo = "ALTO"
|
| 119 |
+
result.observaciones.append(f"BCRA situaciΓ³n: {situacion}/5")
|
| 120 |
+
elif situacion and situacion > 1:
|
| 121 |
+
result.nivel_riesgo = "MEDIO"
|
| 122 |
+
result.observaciones.append(f"BCRA situaciΓ³n: {situacion}/5")
|
| 123 |
+
except Exception as e:
|
| 124 |
+
logger.debug(f"[KYC] BCRA fallΓ³: {e}")
|
| 125 |
+
|
| 126 |
+
# 4. Verificar con ANSES
|
| 127 |
+
try:
|
| 128 |
+
anses = AnsesScraper()
|
| 129 |
+
anses_data = await anses.fetch(cuit_clean)
|
| 130 |
+
if anses_data and anses_data.get("resultado"):
|
| 131 |
+
result.verificado_anses = True
|
| 132 |
+
score += 20
|
| 133 |
+
result.fuentes_consultadas.append("ANSES")
|
| 134 |
+
except Exception as e:
|
| 135 |
+
logger.debug(f"[KYC] ANSES fallΓ³: {e}")
|
| 136 |
+
|
| 137 |
+
# Calcular score final
|
| 138 |
+
result.score_kyc = min(score, 100)
|
| 139 |
+
|
| 140 |
+
# Determinar nivel de riesgo basado en score
|
| 141 |
+
if result.score_kyc >= 70:
|
| 142 |
+
result.nivel_riesgo = "BAJO"
|
| 143 |
+
elif result.score_kyc >= 40:
|
| 144 |
+
result.nivel_riesgo = "MEDIO"
|
| 145 |
+
else:
|
| 146 |
+
result.nivel_riesgo = "ALTO"
|
| 147 |
+
|
| 148 |
+
# Agregar observaciones
|
| 149 |
+
if not result.verificado_renaper:
|
| 150 |
+
result.observaciones.append("No verificado por RENAPER")
|
| 151 |
+
if not result.verificado_afip:
|
| 152 |
+
result.observaciones.append("No verificado por ARCA/AFIP")
|
| 153 |
+
if not result.verificado_bcra:
|
| 154 |
+
result.observaciones.append("No verificado por BCRA")
|
| 155 |
+
|
| 156 |
+
return result
|
app/reports/models.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, String, Text, DateTime, Boolean, Integer
|
| 2 |
+
from sqlalchemy.sql import func
|
| 3 |
+
from app.database import Base
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class ReportCache(Base):
|
| 7 |
+
__tablename__ = "report_cache"
|
| 8 |
+
|
| 9 |
+
id = Column(String(36), primary_key=True)
|
| 10 |
+
cache_key = Column(String(255), unique=True, index=True, nullable=False)
|
| 11 |
+
report_type = Column(String(50), nullable=False) # persona | empresa | vehiculo
|
| 12 |
+
identifier = Column(String(100), nullable=False, index=True) # cuit / dominio
|
| 13 |
+
data = Column(Text, nullable=False) # JSON string
|
| 14 |
+
sources_used = Column(String(500)) # comma-separated sources
|
| 15 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 16 |
+
expires_at = Column(DateTime(timezone=True))
|
| 17 |
+
hit_count = Column(Integer, default=0)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class SearchHistory(Base):
|
| 21 |
+
__tablename__ = "search_history"
|
| 22 |
+
|
| 23 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 24 |
+
user_id = Column(String(36), index=True, nullable=False)
|
| 25 |
+
identifier = Column(String(100), nullable=False) # CUIT / Dominio
|
| 26 |
+
type = Column(String(50)) # persona | empresa | vehiculo
|
| 27 |
+
name = Column(String(255)) # Razon social / Nombre
|
| 28 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 29 |
+
report_id = Column(String(36)) # Link to cache
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class MonitorTask(Base):
|
| 33 |
+
__tablename__ = "monitor_tasks"
|
| 34 |
+
|
| 35 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 36 |
+
user_id = Column(String(36), index=True, nullable=False)
|
| 37 |
+
identifier = Column(String(100), nullable=False) # CUIT / Dominio
|
| 38 |
+
type = Column(String(50)) # persona | empresa | vehiculo
|
| 39 |
+
last_check = Column(DateTime(timezone=True), server_default=func.now())
|
| 40 |
+
active = Column(Boolean, default=True)
|
| 41 |
+
alert_count = Column(Integer, default=0)
|
| 42 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
app/reports/orchestrator.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
orchestrator.py β Parallel orchestrator for report generation.
|
| 3 |
+
Executes scrapers in waves respecting dependencies, with timeout and circuit breaker.
|
| 4 |
+
"""
|
| 5 |
+
import asyncio
|
| 6 |
+
import logging
|
| 7 |
+
import time
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
from typing import Any, Optional
|
| 10 |
+
from datetime import datetime, timezone
|
| 11 |
+
|
| 12 |
+
from app.reports.registry import registry, ScraperDef
|
| 13 |
+
from app.utils.circuit_breaker import get_circuit_breaker, CircuitBreakerConfig, CircuitState
|
| 14 |
+
from app.utils.telemetry import record_scraper_result
|
| 15 |
+
from app.cache.redis_client import cache_get, cache_set
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@dataclass
|
| 21 |
+
class ScraperResult:
|
| 22 |
+
"""Resultado de ejecuciΓ³n de un scraper."""
|
| 23 |
+
name: str
|
| 24 |
+
status: str # "ok", "empty", "error", "blocked", "timeout", "skipped"
|
| 25 |
+
latency_ms: float
|
| 26 |
+
records_found: int = 0
|
| 27 |
+
data: Any = None
|
| 28 |
+
error: Optional[str] = None
|
| 29 |
+
circuit_open: bool = False
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class ScraperOrchestrator:
|
| 33 |
+
"""
|
| 34 |
+
Orquestador de ejecuciΓ³n paralela de scrapers.
|
| 35 |
+
- Ejecuta en waves respetando dependencias
|
| 36 |
+
- Maneja timeouts por scraper
|
| 37 |
+
- Integra circuit breaker
|
| 38 |
+
- Soporta cachΓ© (hit/miss) y fallback
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
def __init__(self, report_type: str, identifier: str, force_refresh: bool = False):
|
| 42 |
+
self.report_type = report_type
|
| 43 |
+
self.identifier = identifier
|
| 44 |
+
self.force_refresh = force_refresh
|
| 45 |
+
self.cache_prefix = f"{report_type}:{identifier}"
|
| 46 |
+
self.start_time = time.time()
|
| 47 |
+
self.results: dict[str, ScraperResult] = {}
|
| 48 |
+
|
| 49 |
+
def _get_cache_key(self, scraper_name: str) -> str:
|
| 50 |
+
return f"{self.cache_prefix}:{scraper_name}"
|
| 51 |
+
|
| 52 |
+
async def _check_cache(self, scraper: ScraperDef) -> Optional[Any]:
|
| 53 |
+
"""Verifica cachΓ© si no es force_refresh."""
|
| 54 |
+
if self.force_refresh:
|
| 55 |
+
return None
|
| 56 |
+
key = self._get_cache_key(scraper.name)
|
| 57 |
+
return await cache_get(key)
|
| 58 |
+
|
| 59 |
+
async def _save_cache(self, scraper: ScraperDef, data: Any) -> None:
|
| 60 |
+
if data:
|
| 61 |
+
key = self._get_cache_key(scraper.name)
|
| 62 |
+
await cache_set(key, data, ttl=86400) # 24h TTL
|
| 63 |
+
|
| 64 |
+
def _get_circuit_breaker(self, scraper: ScraperDef):
|
| 65 |
+
return get_circuit_breaker(
|
| 66 |
+
scraper.name,
|
| 67 |
+
CircuitBreakerConfig(
|
| 68 |
+
failure_threshold=5,
|
| 69 |
+
timeout=60.0,
|
| 70 |
+
excluded_exceptions=(),
|
| 71 |
+
)
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
async def _execute_single(self, scraper: ScraperDef) -> ScraperResult:
|
| 75 |
+
"""Ejecuta un solo scraper con circuit breaker y timeout."""
|
| 76 |
+
# Check circuit breaker
|
| 77 |
+
cb = self._get_circuit_breaker(scraper)
|
| 78 |
+
if cb.state == CircuitState.OPEN:
|
| 79 |
+
logger.warning(f"Circuit breaker OPEN for {scraper.name}, skipping")
|
| 80 |
+
return ScraperResult(
|
| 81 |
+
name=scraper.name, status="skipped", latency_ms=0,
|
| 82 |
+
error="Circuit breaker open", circuit_open=True
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
# Check cache first
|
| 86 |
+
cached = await self._check_cache(scraper)
|
| 87 |
+
if cached is not None:
|
| 88 |
+
logger.debug(f"Cache HIT for {scraper.name}")
|
| 89 |
+
return ScraperResult(
|
| 90 |
+
name=scraper.name, status="ok", latency_ms=1,
|
| 91 |
+
data=cached, records_found=len(cached) if isinstance(cached, list) else 1
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
# Execute with circuit breaker
|
| 95 |
+
start = time.time()
|
| 96 |
+
try:
|
| 97 |
+
async def _fetch():
|
| 98 |
+
return await scraper.instance.safe_fetch(self.identifier)
|
| 99 |
+
|
| 100 |
+
result = await asyncio.wait_for(
|
| 101 |
+
cb.call(_fetch),
|
| 102 |
+
timeout=scraper.timeout
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
latency = (time.time() - start) * 1000
|
| 106 |
+
|
| 107 |
+
# Record success in circuit breaker
|
| 108 |
+
async with cb._lock:
|
| 109 |
+
cb._failure_count = 0
|
| 110 |
+
if cb._state == CircuitState.HALF_OPEN:
|
| 111 |
+
cb._success_count += 1
|
| 112 |
+
if cb._success_count >= cb.config.success_threshold:
|
| 113 |
+
cb._state = CircuitState.CLOSED
|
| 114 |
+
cb._success_count = 0
|
| 115 |
+
|
| 116 |
+
# Save to cache
|
| 117 |
+
await self._save_cache(scraper, result)
|
| 118 |
+
|
| 119 |
+
records = 0
|
| 120 |
+
if result:
|
| 121 |
+
if isinstance(result, list):
|
| 122 |
+
records = len(result)
|
| 123 |
+
elif isinstance(result, dict):
|
| 124 |
+
list_keys = [k for k, v in result.items() if isinstance(v, list)]
|
| 125 |
+
records = sum(len(result[k]) for k in list_keys) if list_keys else 1
|
| 126 |
+
else:
|
| 127 |
+
records = 1
|
| 128 |
+
|
| 129 |
+
status = "ok" if records > 0 else "empty"
|
| 130 |
+
record_scraper_result(scraper.name, status, latency, records_found=records)
|
| 131 |
+
|
| 132 |
+
return ScraperResult(
|
| 133 |
+
name=scraper.name, status=status, latency_ms=latency,
|
| 134 |
+
records_found=records, data=result
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
except asyncio.TimeoutError:
|
| 138 |
+
return ScraperResult(
|
| 139 |
+
name=scraper.name, status="timeout",
|
| 140 |
+
latency_ms=(time.time() - start) * 1000,
|
| 141 |
+
error=f"Timeout after {scraper.timeout}s"
|
| 142 |
+
)
|
| 143 |
+
except Exception as e:
|
| 144 |
+
return ScraperResult(
|
| 145 |
+
name=scraper.name, status="error",
|
| 146 |
+
latency_ms=(time.time() - start) * 1000,
|
| 147 |
+
error=str(e)
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
async def execute_wave(self, scrapers: list[ScraperDef]) -> list[ScraperResult]:
|
| 151 |
+
"""Ejecuta una wave de scrapers en paralelo."""
|
| 152 |
+
logger.info(f"Executing wave with {len(scrapers)} scrapers: {[s.name for s in scrapers]}")
|
| 153 |
+
|
| 154 |
+
tasks = [self._execute_single(s) for s in scrapers]
|
| 155 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 156 |
+
|
| 157 |
+
results_list = []
|
| 158 |
+
for i, result in enumerate(results):
|
| 159 |
+
if isinstance(result, Exception):
|
| 160 |
+
logger.error(f"Scraper {scrapers[i].name} failed with exception: {result}")
|
| 161 |
+
results_list.append(ScraperResult(
|
| 162 |
+
name=scrapers[i].name, status="error", latency_ms=0,
|
| 163 |
+
error=str(result)
|
| 164 |
+
))
|
| 165 |
+
else:
|
| 166 |
+
results_list.append(result)
|
| 167 |
+
|
| 168 |
+
return results_list
|
| 169 |
+
|
| 170 |
+
async def run(self) -> dict[str, ScraperResult]:
|
| 171 |
+
"""Ejecuta todos los scrapers segΓΊn waves de dependencias.
|
| 172 |
+
|
| 173 |
+
Aplica un tope duro global de 10 minutos (asyncio.wait_for). Si Render
|
| 174 |
+
free-tier (Cloudflare corta a ~80s en CDN) o algΓΊn scraper
|
| 175 |
+
extremadamente lento no termina en ese plazo, cortamos y devolvemos
|
| 176 |
+
el reporte parcial con los scrapers que sΓ terminaron. Los
|
| 177 |
+
scrapers individuales siguen respetando SAFE_FETCH_TIMEOUT y
|
| 178 |
+
max_retries (esos NO se modifican).
|
| 179 |
+
"""
|
| 180 |
+
ORCHESTRATOR_TIMEOUT = 600 # 10 min tope global (NO bajar - respetar scrapers)
|
| 181 |
+
|
| 182 |
+
waves = registry.get_execution_groups()
|
| 183 |
+
logger.info(f"Starting orchestration for {self.report_type}:{self.identifier} "
|
| 184 |
+
f"({len(waves)} waves, {len(registry.all())} total scrapers)")
|
| 185 |
+
|
| 186 |
+
async def _run_all_waves():
|
| 187 |
+
for wave_idx, wave in enumerate(waves):
|
| 188 |
+
active_scrapers = [s for s in wave if self._is_active_for_report(s)]
|
| 189 |
+
if not active_scrapers:
|
| 190 |
+
continue
|
| 191 |
+
|
| 192 |
+
wave_results = await self.execute_wave(active_scrapers)
|
| 193 |
+
for r in wave_results:
|
| 194 |
+
self.results[r.name] = r
|
| 195 |
+
|
| 196 |
+
logger.info(f"Wave {wave_idx + 1}/{len(waves)} completed: "
|
| 197 |
+
f"{sum(1 for r in wave_results if r.status in ('ok', 'empty'))}/{len(wave_results)} ok")
|
| 198 |
+
|
| 199 |
+
try:
|
| 200 |
+
await asyncio.wait_for(_run_all_waves(), timeout=ORCHESTRATOR_TIMEOUT)
|
| 201 |
+
except asyncio.TimeoutError:
|
| 202 |
+
logger.warning(
|
| 203 |
+
f"Orchestration for {self.report_type}:{self.identifier} timed out "
|
| 204 |
+
f"despuΓ©s de {ORCHESTRATOR_TIMEOUT}s. Devolviendo reporte parcial con "
|
| 205 |
+
f"{len([r for r in self.results.values() if r.status in ('ok', 'empty')])}/{len(self.results)} scrapers OK."
|
| 206 |
+
)
|
| 207 |
+
# Marcar scrapers que quedaron sin terminar como 'timeout' para que se vean en meta.failures.
|
| 208 |
+
executed_names = {r.name for r in self.results.values()}
|
| 209 |
+
for s in registry.all():
|
| 210 |
+
if self._is_active_for_report(s) and s.name not in executed_names:
|
| 211 |
+
self.results[s.name] = ScraperResult(
|
| 212 |
+
name=s.name, status="timeout", latency_ms=0,
|
| 213 |
+
error=f"Orchestrator timeout ({ORCHESTRATOR_TIMEOUT}s): scraper no ejecutado"
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
total_time = (time.time() - self.start_time) * 1000
|
| 217 |
+
logger.info(f"Orchestration completed in {total_time:.0f}ms ({len(self.results)} scrapers)")
|
| 218 |
+
|
| 219 |
+
return self.results
|
| 220 |
+
|
| 221 |
+
def _is_active_for_report(self, scraper: ScraperDef) -> bool:
|
| 222 |
+
"""Determina si un scraper aplica para este tipo de reporte."""
|
| 223 |
+
# Por ahora todos activos; se puede especializar por report_type
|
| 224 |
+
return True
|
| 225 |
+
|
| 226 |
+
def get_summary(self) -> dict:
|
| 227 |
+
"""Resumen de resultados para logging/mΓ©tricas."""
|
| 228 |
+
ok = sum(1 for r in self.results.values() if r.status == "ok")
|
| 229 |
+
empty = sum(1 for r in self.results.values() if r.status == "empty")
|
| 230 |
+
errors = sum(1 for r in self.results.values() if r.status == "error")
|
| 231 |
+
timeouts = sum(1 for r in self.results.values() if r.status == "timeout")
|
| 232 |
+
blocked = sum(1 for r in self.results.values() if r.status == "blocked")
|
| 233 |
+
skipped = sum(1 for r in self.results.values() if r.status == "skipped")
|
| 234 |
+
|
| 235 |
+
return {
|
| 236 |
+
"total": len(self.results),
|
| 237 |
+
"ok": ok,
|
| 238 |
+
"empty": empty,
|
| 239 |
+
"errors": errors,
|
| 240 |
+
"timeouts": timeouts,
|
| 241 |
+
"blocked": blocked,
|
| 242 |
+
"skipped": skipped,
|
| 243 |
+
"total_latency_ms": (time.time() - self.start_time) * 1000,
|
| 244 |
+
}
|
app/reports/registry.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Scraper Registry β Central configuration registry.py β Registry of all available scrapers for report generation.
|
| 3 |
+
Centralizes scraper definitions and metadata.
|
| 4 |
+
"""
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Callable, Any, Optional
|
| 7 |
+
from enum import Enum
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class ScraperCategory(Enum):
|
| 11 |
+
"""CategorΓas de scrapers por criticidad y tipo."""
|
| 12 |
+
CORE = "core" # CrΓticos: ARCA, BCRA, DNRPA, PJ, RENAPER, IGJ, INPI, UIF
|
| 13 |
+
FINANCIAL = "financial" # ARBA, Boletines, ANSES, CNV, COMPR.AR, Monotributo, Registro Conductores
|
| 14 |
+
JUDICIAL = "judicial" # Infracciones, Deudores Alimentarios, JUBA, Inhibiciones
|
| 15 |
+
PATRIMONIAL = "patrimonial" # Catastro, CartografΓa, SINAI, SISCOP
|
| 16 |
+
OSINT = "osint" # Google Images, Redes Sociales, TelefonΓa, Name Search
|
| 17 |
+
OTHER = "other" # Billeteras, Ruido, SGARHU, CONTRATAR, Colegios, Timeline BO, Archive.org
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@dataclass
|
| 21 |
+
class ScraperDef:
|
| 22 |
+
"""DefiniciΓ³n de un scraper con metadatos."""
|
| 23 |
+
name: str # Nombre ΓΊnico (ej: "bcra")
|
| 24 |
+
display_name: str # Nombre legible (ej: "BCRA")
|
| 25 |
+
category: ScraperCategory
|
| 26 |
+
instance: Any # Instancia del scraper
|
| 27 |
+
timeout: float = 60.0 # Timeout en segundos
|
| 28 |
+
critical: bool = False # Si falla, ΒΏes crΓtico para el reporte?
|
| 29 |
+
dependencies: list[str] = None # Nombres de scrapers que deben ejecutarse antes
|
| 30 |
+
fallback_supported: bool = True # Si se puede usar cachΓ© como fallback
|
| 31 |
+
|
| 32 |
+
def __post_init__(self):
|
| 33 |
+
if self.dependencies is None:
|
| 34 |
+
self.dependencies = []
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class ScraperRegistry:
|
| 38 |
+
"""Registro central de todos los scrapers disponibles."""
|
| 39 |
+
|
| 40 |
+
def __init__(self):
|
| 41 |
+
self._scrapers: dict[str, ScraperDef] = {}
|
| 42 |
+
self._categories: dict[ScraperCategory, list[str]] = {c: [] for c in ScraperCategory}
|
| 43 |
+
|
| 44 |
+
def register(self, defn: ScraperDef) -> None:
|
| 45 |
+
"""Registra un scraper."""
|
| 46 |
+
self._scrapers[defn.name] = defn
|
| 47 |
+
self._categories[defn.category].append(defn.name)
|
| 48 |
+
|
| 49 |
+
def get(self, name: str) -> Optional[ScraperDef]:
|
| 50 |
+
return self._scrapers.get(name)
|
| 51 |
+
|
| 52 |
+
def all(self) -> list[ScraperDef]:
|
| 53 |
+
return list(self._scrapers.values())
|
| 54 |
+
|
| 55 |
+
def by_category(self, category: ScraperCategory) -> list[ScraperDef]:
|
| 56 |
+
return [self._scrapers[n] for n in self._categories[category]]
|
| 57 |
+
|
| 58 |
+
def critical(self) -> list[ScraperDef]:
|
| 59 |
+
return [s for s in self._scrapers.values() if s.critical]
|
| 60 |
+
|
| 61 |
+
def ordered_by_dependencies(self) -> list[ScraperDef]:
|
| 62 |
+
"""Ordena scrapers respetando dependencias (topological sort simplificado)."""
|
| 63 |
+
# Para simplificar: critical first, luego por category priority
|
| 64 |
+
priority = {
|
| 65 |
+
ScraperCategory.CORE: 0,
|
| 66 |
+
ScraperCategory.FINANCIAL: 1,
|
| 67 |
+
ScraperCategory.JUDICIAL: 2,
|
| 68 |
+
ScraperCategory.PATRIMONIAL: 3,
|
| 69 |
+
ScraperCategory.OSINT: 4,
|
| 70 |
+
ScraperCategory.OTHER: 5,
|
| 71 |
+
}
|
| 72 |
+
return sorted(self.all(), key=lambda s: (not s.critical, priority[s.category]))
|
| 73 |
+
|
| 74 |
+
def get_execution_groups(self) -> list[list[ScraperDef]]:
|
| 75 |
+
"""
|
| 76 |
+
Agrupa scrapers por waves de ejecuciΓ³n paralela.
|
| 77 |
+
Respeta dependencias: un scraper no se ejecuta hasta que sus dependencias terminen.
|
| 78 |
+
"""
|
| 79 |
+
executed = set()
|
| 80 |
+
waves = []
|
| 81 |
+
remaining = {s.name: s for s in self.ordered_by_dependencies()}
|
| 82 |
+
|
| 83 |
+
while remaining:
|
| 84 |
+
wave = []
|
| 85 |
+
for name, s in list(remaining.items()):
|
| 86 |
+
deps_met = all(d in executed for d in s.dependencies)
|
| 87 |
+
if deps_met:
|
| 88 |
+
wave.append(s)
|
| 89 |
+
executed.add(name)
|
| 90 |
+
|
| 91 |
+
if not wave:
|
| 92 |
+
# Circular dependency or missing - force execute
|
| 93 |
+
wave.append(next(iter(remaining.values())))
|
| 94 |
+
executed.add(wave[0].name)
|
| 95 |
+
|
| 96 |
+
for s in wave:
|
| 97 |
+
remaining.pop(s.name, None)
|
| 98 |
+
waves.append(wave)
|
| 99 |
+
|
| 100 |
+
return waves
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
# Instancia global del registry
|
| 104 |
+
registry = ScraperRegistry()
|
app/reports/router.py
ADDED
|
@@ -0,0 +1,763 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException, Depends, Query, Response
|
| 2 |
+
from sqlalchemy import update
|
| 3 |
+
from app.reports.schemas import (
|
| 4 |
+
PersonReport, CompanyReport, VehicleReport, GroupReport,
|
| 5 |
+
PropertyReport, SearchPropertyRequest,
|
| 6 |
+
SearchPersonaRequest, SearchEmpresaRequest,
|
| 7 |
+
HistoryItem, MonitorItem,
|
| 8 |
+
)
|
| 9 |
+
from app.reports import service
|
| 10 |
+
from app.utils.pdf_generator import generate_report_pdf
|
| 11 |
+
from app.auth.router import current_active_user
|
| 12 |
+
from app.auth.models import User
|
| 13 |
+
from app.scrapers.base import AntiBotBlockedError
|
| 14 |
+
from app.utils.security import mask_cuit, mask_email
|
| 15 |
+
import logging
|
| 16 |
+
import re
|
| 17 |
+
from datetime import datetime
|
| 18 |
+
|
| 19 |
+
logger = logging.getLogger(__name__)
|
| 20 |
+
router = APIRouter(prefix="/reports", tags=["reports"])
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
from app.utils.dni_to_cuit import find_real_cuit
|
| 24 |
+
|
| 25 |
+
async def validate_cuit(cuit: str) -> str:
|
| 26 |
+
"""Valida y normaliza formato CUIT/CUIL. Si es DNI, intenta resolver el CUIT real."""
|
| 27 |
+
clean = re.sub(r"[^0-9]", "", cuit)
|
| 28 |
+
|
| 29 |
+
if len(clean) in (7, 8):
|
| 30 |
+
real_cuit = await find_real_cuit(clean)
|
| 31 |
+
if real_cuit:
|
| 32 |
+
return real_cuit
|
| 33 |
+
else:
|
| 34 |
+
raise HTTPException(status_code=404, detail="No se encontrΓ³ un CUIT asociado a este DNI en AFIP.")
|
| 35 |
+
|
| 36 |
+
if len(clean) != 11:
|
| 37 |
+
raise HTTPException(status_code=422, detail="CUIT/CUIL invΓ‘lido. Debe tener 11 dΓgitos o ser un DNI vΓ‘lido.")
|
| 38 |
+
return clean
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
async def validate_cuit_empresa(cuit: str) -> str:
|
| 42 |
+
"""Valida CUIT de empresa (debe tener prefijo 30 o 33)."""
|
| 43 |
+
cuit_clean = await validate_cuit(cuit)
|
| 44 |
+
prefijo = cuit_clean[:2]
|
| 45 |
+
if prefijo not in ("30", "33"):
|
| 46 |
+
raise HTTPException(
|
| 47 |
+
status_code=422,
|
| 48 |
+
detail=f"El CUIT {cuit_clean} no corresponde a una empresa (prefijo {prefijo}). "
|
| 49 |
+
"Los CUIT de empresas comienzan con 30 o 33."
|
| 50 |
+
)
|
| 51 |
+
return cuit_clean
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@router.get("/public/persona/{cuit}", response_model=PersonReport, summary="Informe pΓΊblico (modelo/ejemplo)")
|
| 55 |
+
async def get_public_persona_report(cuit: str):
|
| 56 |
+
cuit_clean = await validate_cuit(cuit)
|
| 57 |
+
if cuit_clean != "20301234562":
|
| 58 |
+
raise HTTPException(status_code=403, detail="Acceso denegado. Este informe requiere suscripciΓ³n.")
|
| 59 |
+
report = await service.get_person_report(cuit_clean)
|
| 60 |
+
report.meta.cached = True
|
| 61 |
+
return report
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@router.get("/persona/{cuit}", response_model=PersonReport, summary="Informe de Persona FΓsica")
|
| 65 |
+
async def get_persona_report(
|
| 66 |
+
cuit: str,
|
| 67 |
+
force_refresh: bool = Query(False, description="Si es true, ignora el cachΓ© y regenera el informe completo"),
|
| 68 |
+
skip_redes: bool = Query(False, description="Si es true, omite el scraper de redes sociales (OSINT)"),
|
| 69 |
+
current_user: User = Depends(current_active_user),
|
| 70 |
+
):
|
| 71 |
+
"""
|
| 72 |
+
Genera un informe completo de persona fΓsica consultando:
|
| 73 |
+
- ARCA/AFIP (datos fiscales)
|
| 74 |
+
- BCRA (situaciΓ³n crediticia)
|
| 75 |
+
- BoletΓn Oficial (publicaciones)
|
| 76 |
+
"""
|
| 77 |
+
cuit_clean = await validate_cuit(cuit)
|
| 78 |
+
|
| 79 |
+
if current_user.credits <= 0 and current_user.plan == "free":
|
| 80 |
+
logger.info(f"Informe de muestra enviado por email a {mask_email(current_user.email)} para CUIT {mask_cuit(cuit_clean)}")
|
| 81 |
+
return {
|
| 82 |
+
"meta": {
|
| 83 |
+
"report_id": "limit_reached",
|
| 84 |
+
"generated_at": str(datetime.now()),
|
| 85 |
+
"message": "Se ha alcanzado el lΓmite de 1 bΓΊsqueda gratuita. Se ha enviado una muestra reducida por email.",
|
| 86 |
+
"action": "upgrade_plan",
|
| 87 |
+
"sample_sent_to": current_user.email
|
| 88 |
+
},
|
| 89 |
+
"identificacion": {"nota": "Para ver nombre, DNI y fecha de nacimiento adquirΓ un plan."},
|
| 90 |
+
"fiscal": {"nota": "Para ver actividades y situaciΓ³n en AFIP adquirΓ un plan."}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
# Si force_refresh=True, eliminar cachΓ© antes de generar
|
| 94 |
+
if force_refresh:
|
| 95 |
+
from app.cache.redis_client import cache_delete
|
| 96 |
+
await cache_delete(f"persona:{cuit_clean}")
|
| 97 |
+
logger.info(f"[force_refresh] CachΓ© invalidado para persona:{mask_cuit(cuit_clean)}")
|
| 98 |
+
|
| 99 |
+
try:
|
| 100 |
+
from app.database import AsyncSessionLocal
|
| 101 |
+
report = await service.get_person_report(cuit_clean, skip_redes=skip_redes)
|
| 102 |
+
# Descontar crΓ©dito solo si no fue cacheado y es plan free
|
| 103 |
+
if not getattr(report.meta, "cached", False) and current_user.plan == "free":
|
| 104 |
+
async with AsyncSessionLocal() as db:
|
| 105 |
+
await db.execute(
|
| 106 |
+
update(User)
|
| 107 |
+
.where(User.id == current_user.id)
|
| 108 |
+
.values(credits=User.credits - 1)
|
| 109 |
+
)
|
| 110 |
+
await db.commit()
|
| 111 |
+
logger.info(f"CrΓ©dito descontado para {mask_email(current_user.email)}. Nuevo saldo: {current_user.credits - 1}")
|
| 112 |
+
|
| 113 |
+
# Guardar en historial
|
| 114 |
+
await service.save_search_history(
|
| 115 |
+
user_id=str(current_user.id),
|
| 116 |
+
identifier=cuit_clean,
|
| 117 |
+
report_type="persona",
|
| 118 |
+
name=f"{report.identificacion.apellido or ''} {report.identificacion.nombres or ''}".strip(),
|
| 119 |
+
report_id=report.meta.report_id
|
| 120 |
+
)
|
| 121 |
+
return report
|
| 122 |
+
except AntiBotBlockedError as e:
|
| 123 |
+
logger.warning(f"Anti-Bot blocked during persona report: {e}")
|
| 124 |
+
raise HTTPException(status_code=503, detail="La fuente de datos estΓ‘ bloqueando consultas. IntentΓ‘ mΓ‘s tarde.")
|
| 125 |
+
except Exception as e:
|
| 126 |
+
logger.error(f"Error generating persona report for {cuit}: {e}", exc_info=True)
|
| 127 |
+
raise HTTPException(status_code=500, detail="Error al generar el informe. IntentΓ‘ nuevamente.")
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
@router.get("/empresa/{cuit}", response_model=CompanyReport, summary="Informe de Empresa")
|
| 131 |
+
async def get_empresa_report(
|
| 132 |
+
cuit: str,
|
| 133 |
+
force_refresh: bool = Query(False, description="Si es true, ignora el cachΓ© y regenera el informe completo"),
|
| 134 |
+
current_user: User = Depends(current_active_user),
|
| 135 |
+
):
|
| 136 |
+
"""
|
| 137 |
+
Genera un informe completo de persona jurΓdica consultando:
|
| 138 |
+
- ARCA/AFIP (datos fiscales)
|
| 139 |
+
- BCRA (situaciΓ³n crediticia)
|
| 140 |
+
- BoletΓn Oficial (publicaciones)
|
| 141 |
+
"""
|
| 142 |
+
cuit_clean = await validate_cuit_empresa(cuit)
|
| 143 |
+
|
| 144 |
+
if current_user.credits <= 0 and current_user.plan == "free":
|
| 145 |
+
logger.info(f"Informe de muestra enviado por email a {mask_email(current_user.email)} para Empresa CUIT {mask_cuit(cuit_clean)}")
|
| 146 |
+
return {
|
| 147 |
+
"meta": {
|
| 148 |
+
"report_id": "limit_reached",
|
| 149 |
+
"generated_at": str(datetime.now()),
|
| 150 |
+
"message": "Se ha alcanzado el lΓmite de 1 bΓΊsqueda gratuita.",
|
| 151 |
+
"action": "upgrade_plan",
|
| 152 |
+
"sample_sent_to": current_user.email
|
| 153 |
+
},
|
| 154 |
+
"identificacion": {"nota": "Para ver razΓ³n social y fecha de constituciΓ³n adquirΓ un plan."}
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
try:
|
| 158 |
+
from app.database import AsyncSessionLocal
|
| 159 |
+
report = await service.get_company_report(cuit_clean)
|
| 160 |
+
if not getattr(report.meta, "cached", False) and current_user.plan == "free":
|
| 161 |
+
async with AsyncSessionLocal() as db:
|
| 162 |
+
await db.execute(
|
| 163 |
+
update(User)
|
| 164 |
+
.where(User.id == current_user.id)
|
| 165 |
+
.values(credits=User.credits - 1)
|
| 166 |
+
)
|
| 167 |
+
await db.commit()
|
| 168 |
+
|
| 169 |
+
# Guardar en historial
|
| 170 |
+
await service.save_search_history(
|
| 171 |
+
user_id=str(current_user.id),
|
| 172 |
+
identifier=cuit_clean,
|
| 173 |
+
report_type="empresa",
|
| 174 |
+
name=report.identificacion.razon_social or cuit_clean,
|
| 175 |
+
report_id=report.meta.report_id
|
| 176 |
+
)
|
| 177 |
+
return report
|
| 178 |
+
except AntiBotBlockedError as e:
|
| 179 |
+
logger.warning(f"Anti-Bot blocked during empresa report: {e}")
|
| 180 |
+
raise HTTPException(status_code=503, detail="La fuente de datos estΓ‘ bloqueando consultas. IntentΓ‘ mΓ‘s tarde.")
|
| 181 |
+
except Exception as e:
|
| 182 |
+
logger.error(f"Error generating empresa report for {cuit}: {e}", exc_info=True)
|
| 183 |
+
raise HTTPException(status_code=500, detail="Error al generar el informe. IntentΓ‘ nuevamente.")
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
@router.get("/vehiculo/{dominio}", response_model=VehicleReport, summary="Obtener informe de vehΓculo")
|
| 187 |
+
async def get_vehiculo_report(
|
| 188 |
+
dominio: str,
|
| 189 |
+
current_user: User = Depends(current_active_user),
|
| 190 |
+
):
|
| 191 |
+
try:
|
| 192 |
+
report = await service.get_vehicle_report(dominio)
|
| 193 |
+
|
| 194 |
+
# Cobrar crΓ©dito si no es cacheado
|
| 195 |
+
if not getattr(report.meta, "cached", False) and current_user.plan == "free":
|
| 196 |
+
from app.database import AsyncSessionLocal
|
| 197 |
+
async with AsyncSessionLocal() as db:
|
| 198 |
+
await db.execute(
|
| 199 |
+
update(User)
|
| 200 |
+
.where(User.id == current_user.id)
|
| 201 |
+
.values(credits=User.credits - 1)
|
| 202 |
+
)
|
| 203 |
+
await db.commit()
|
| 204 |
+
logger.info(f"CrΓ©dito descontado para {mask_email(current_user.email)} (VehΓculo). Nuevo saldo: {current_user.credits - 1}")
|
| 205 |
+
|
| 206 |
+
# Guardar en historial
|
| 207 |
+
await service.save_search_history(
|
| 208 |
+
user_id=str(current_user.id),
|
| 209 |
+
identifier=dominio.upper(),
|
| 210 |
+
report_type="vehiculo",
|
| 211 |
+
name=f"{report.vehiculo.marca or ''} {report.vehiculo.modelo or ''}".strip() or "VehΓculo",
|
| 212 |
+
report_id=report.meta.report_id
|
| 213 |
+
)
|
| 214 |
+
return report
|
| 215 |
+
except AntiBotBlockedError as e:
|
| 216 |
+
logger.warning(f"Anti-Bot blocked during vehicle report: {e}")
|
| 217 |
+
raise HTTPException(status_code=503, detail="La fuente de datos estΓ‘ bloqueando consultas. IntentΓ‘ mΓ‘s tarde.")
|
| 218 |
+
except Exception as e:
|
| 219 |
+
logger.error(f"Error generating vehicle report for {dominio}: {e}", exc_info=True)
|
| 220 |
+
raise HTTPException(status_code=500, detail="Error al generar el informe. IntentΓ‘ nuevamente.")
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
@router.get("/vehiculo/{dominio}/pdf", summary="Descargar PDF de VehΓculo")
|
| 224 |
+
async def get_vehiculo_pdf(
|
| 225 |
+
dominio: str,
|
| 226 |
+
current_user: User = Depends(current_active_user),
|
| 227 |
+
):
|
| 228 |
+
# Check credits for free users
|
| 229 |
+
if current_user.credits <= 0 and current_user.plan == "free":
|
| 230 |
+
raise HTTPException(status_code=429, detail="Alcanzaste el lΓmite de informes. ActualizΓ‘ tu plan para continuar.")
|
| 231 |
+
|
| 232 |
+
try:
|
| 233 |
+
report = await service.get_vehicle_report(dominio)
|
| 234 |
+
|
| 235 |
+
# Deduct credit for free users (only if not cached)
|
| 236 |
+
if not getattr(report.meta, "cached", False) and current_user.plan == "free":
|
| 237 |
+
from app.database import AsyncSessionLocal
|
| 238 |
+
async with AsyncSessionLocal() as db:
|
| 239 |
+
await db.execute(
|
| 240 |
+
update(User)
|
| 241 |
+
.where(User.id == current_user.id)
|
| 242 |
+
.values(credits=User.credits - 1)
|
| 243 |
+
)
|
| 244 |
+
await db.commit()
|
| 245 |
+
|
| 246 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="vehiculo")
|
| 247 |
+
return Response(
|
| 248 |
+
content=bytes(pdf_bytes),
|
| 249 |
+
media_type="application/pdf",
|
| 250 |
+
headers={"Content-Disposition": f"attachment; filename=CrowData_Vehiculo_{dominio}.pdf"}
|
| 251 |
+
)
|
| 252 |
+
except HTTPException:
|
| 253 |
+
raise
|
| 254 |
+
except Exception as e:
|
| 255 |
+
logger.error(f"Error generating PDF for vehicle {dominio}: {e}")
|
| 256 |
+
raise HTTPException(status_code=500, detail="Error al generar PDF")
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
@router.get("/group/{cuit}", response_model=GroupReport, summary="Informe Consolidado de Grupo EconΓ³mico")
|
| 260 |
+
async def get_group_economic_report(
|
| 261 |
+
cuit: str,
|
| 262 |
+
current_user: User = Depends(current_active_user),
|
| 263 |
+
):
|
| 264 |
+
try:
|
| 265 |
+
cuit_clean = await validate_cuit(cuit)
|
| 266 |
+
return await service.get_group_report(cuit_clean)
|
| 267 |
+
except Exception as e:
|
| 268 |
+
logger.error(f"Error generating group report: {e}")
|
| 269 |
+
raise HTTPException(status_code=500, detail="Error al consolidar grupo econΓ³mico")
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
@router.get("/group/{cuit}/pdf", summary="Descargar PDF de Grupo EconΓ³mico")
|
| 273 |
+
async def get_group_pdf(
|
| 274 |
+
cuit: str,
|
| 275 |
+
current_user: User = Depends(current_active_user),
|
| 276 |
+
):
|
| 277 |
+
cuit_clean = await validate_cuit(cuit)
|
| 278 |
+
try:
|
| 279 |
+
report = await service.get_group_report(cuit_clean)
|
| 280 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="grupo")
|
| 281 |
+
return Response(
|
| 282 |
+
content=bytes(pdf_bytes),
|
| 283 |
+
media_type="application/pdf",
|
| 284 |
+
headers={"Content-Disposition": f"attachment; filename=CrowData_Grupo_{cuit_clean}.pdf"}
|
| 285 |
+
)
|
| 286 |
+
except Exception as e:
|
| 287 |
+
logger.error(f"Error generating group PDF for {cuit}: {e}")
|
| 288 |
+
raise HTTPException(status_code=500, detail="Error al generar PDF de grupo")
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
@router.post("/propiedad", response_model=PropertyReport, summary="Obtener informe de propiedad por direcciΓ³n")
|
| 292 |
+
async def get_property_report(
|
| 293 |
+
req: SearchPropertyRequest,
|
| 294 |
+
current_user: User = Depends(current_active_user),
|
| 295 |
+
):
|
| 296 |
+
try:
|
| 297 |
+
report = await service.get_property_report(req.calle, req.numero, req.localidad, req.provincia)
|
| 298 |
+
|
| 299 |
+
# Cobrar crΓ©dito si no es cacheado
|
| 300 |
+
if not getattr(report.meta, "cached", False) and current_user.plan == "free":
|
| 301 |
+
from app.database import AsyncSessionLocal
|
| 302 |
+
async with AsyncSessionLocal() as db:
|
| 303 |
+
await db.execute(
|
| 304 |
+
update(User)
|
| 305 |
+
.where(User.id == current_user.id)
|
| 306 |
+
.values(credits=User.credits - 1)
|
| 307 |
+
)
|
| 308 |
+
await db.commit()
|
| 309 |
+
logger.info(f"CrΓ©dito descontado para {mask_email(current_user.email)} (Propiedad). Nuevo saldo: {current_user.credits - 1}")
|
| 310 |
+
|
| 311 |
+
# Guardar en historial
|
| 312 |
+
await service.save_search_history(
|
| 313 |
+
user_id=str(current_user.id),
|
| 314 |
+
identifier=f"{req.calle} {req.numero}",
|
| 315 |
+
report_type="propiedad",
|
| 316 |
+
name=f"Inmueble: {req.calle} {req.numero}",
|
| 317 |
+
report_id=report.meta.report_id
|
| 318 |
+
)
|
| 319 |
+
return report
|
| 320 |
+
except AntiBotBlockedError as e:
|
| 321 |
+
logger.warning(f"Anti-Bot blocked during property search: {e}")
|
| 322 |
+
raise HTTPException(status_code=503, detail="La fuente de datos estΓ‘ bloqueando consultas. IntentΓ‘ mΓ‘s tarde.")
|
| 323 |
+
except Exception as e:
|
| 324 |
+
logger.error(f"Error searching property: {e}")
|
| 325 |
+
raise HTTPException(status_code=500, detail="Error al buscar propiedad")
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
@router.get("/search/persona", summary="Buscar Persona por nombre/DNI")
|
| 329 |
+
async def search_persona(
|
| 330 |
+
apellido: str | None = Query(None),
|
| 331 |
+
nombres: str | None = Query(None),
|
| 332 |
+
dni: str | None = Query(None),
|
| 333 |
+
cuit: str | None = Query(None),
|
| 334 |
+
provincia: str | None = Query(None),
|
| 335 |
+
current_user: User = Depends(current_active_user),
|
| 336 |
+
):
|
| 337 |
+
"""BΓΊsqueda de personas por apellido, nombre o DNI. Retorna lista de coincidencias bΓ‘sicas."""
|
| 338 |
+
if not any([apellido, nombres, dni, cuit]):
|
| 339 |
+
raise HTTPException(status_code=422, detail="IngresΓ‘ al menos un criterio de bΓΊsqueda.")
|
| 340 |
+
|
| 341 |
+
# Si viene CUIT directo de 11 dΓgitos, retornar como resultado directo
|
| 342 |
+
if cuit:
|
| 343 |
+
cuit_clean = re.sub(r"[^0-9]", "", cuit)
|
| 344 |
+
if len(cuit_clean) == 11:
|
| 345 |
+
# Retornar como resultado ΓΊnico para que el frontend navegue directamente
|
| 346 |
+
return {"results": [], "redirect_cuit": cuit_clean, "type": "direct"}
|
| 347 |
+
|
| 348 |
+
# Si viene DNI, intentar resolver directo a CUIT sin pasar por el buscador de nombres
|
| 349 |
+
if dni and not apellido and not nombres:
|
| 350 |
+
dni_clean = re.sub(r"[^0-9]", "", dni)
|
| 351 |
+
if len(dni_clean) in (7, 8):
|
| 352 |
+
real_cuit = await find_real_cuit(dni_clean)
|
| 353 |
+
if real_cuit:
|
| 354 |
+
return {"results": [], "redirect_cuit": real_cuit, "type": "direct"}
|
| 355 |
+
|
| 356 |
+
# BΓΊsqueda real usando NameSearchScraper
|
| 357 |
+
query = f"{apellido or ''} {nombres or ''}".strip() or dni
|
| 358 |
+
try:
|
| 359 |
+
from app.scrapers.name_search import NameSearchScraper
|
| 360 |
+
ns_scraper = NameSearchScraper()
|
| 361 |
+
results = await ns_scraper.fetch(query)
|
| 362 |
+
return {"results": results}
|
| 363 |
+
except Exception as e:
|
| 364 |
+
logger.error(f"Error in name search: {e}")
|
| 365 |
+
return {"results": [], "message": "Error en el motor de bΓΊsqueda."}
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
@router.get("/search/empresa", summary="Buscar Empresa por nombre/CUIT")
|
| 369 |
+
async def search_empresa(
|
| 370 |
+
razon_social: str | None = Query(None),
|
| 371 |
+
cuit: str | None = Query(None),
|
| 372 |
+
current_user: User = Depends(current_active_user),
|
| 373 |
+
):
|
| 374 |
+
"""BΓΊsqueda de empresas por razΓ³n social o CUIT."""
|
| 375 |
+
if not any([razon_social, cuit]):
|
| 376 |
+
raise HTTPException(status_code=422, detail="IngresΓ‘ razΓ³n social o CUIT.")
|
| 377 |
+
|
| 378 |
+
if cuit:
|
| 379 |
+
return {"redirect": f"/reports/empresa/{cuit}", "type": "direct"}
|
| 380 |
+
|
| 381 |
+
query = razon_social
|
| 382 |
+
try:
|
| 383 |
+
from app.scrapers.name_search import NameSearchScraper
|
| 384 |
+
ns_scraper = NameSearchScraper()
|
| 385 |
+
results = await ns_scraper.fetch(query)
|
| 386 |
+
return {"results": results}
|
| 387 |
+
except Exception as e:
|
| 388 |
+
logger.error(f"Error in company name search: {e}")
|
| 389 |
+
return {"results": [], "message": "Error en el motor de bΓΊsqueda."}
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
@router.get("/persona/{cuit}/pdf", summary="Descargar PDF de Persona")
|
| 393 |
+
async def get_persona_pdf(
|
| 394 |
+
cuit: str,
|
| 395 |
+
current_user: User = Depends(current_active_user),
|
| 396 |
+
force_refresh: bool = Query(False, description="Forzar regeneracion"),
|
| 397 |
+
):
|
| 398 |
+
cuit_clean = await validate_cuit(cuit)
|
| 399 |
+
|
| 400 |
+
# Check credits for free users
|
| 401 |
+
if current_user.credits <= 0 and current_user.plan == "free":
|
| 402 |
+
raise HTTPException(status_code=429, detail="Alcanzaste el lΓmite de informes. ActualizΓ‘ tu plan para continuar.")
|
| 403 |
+
|
| 404 |
+
try:
|
| 405 |
+
if force_refresh:
|
| 406 |
+
from app.cache.redis_client import cache_delete
|
| 407 |
+
await cache_delete(f"persona:{cuit_clean}")
|
| 408 |
+
report = await service.get_person_report(cuit_clean)
|
| 409 |
+
|
| 410 |
+
# Deduct credit for free users (only if not cached)
|
| 411 |
+
if not getattr(report.meta, "cached", False) and current_user.plan == "free":
|
| 412 |
+
from app.database import AsyncSessionLocal
|
| 413 |
+
async with AsyncSessionLocal() as db:
|
| 414 |
+
await db.execute(
|
| 415 |
+
update(User)
|
| 416 |
+
.where(User.id == current_user.id)
|
| 417 |
+
.values(credits=User.credits - 1)
|
| 418 |
+
)
|
| 419 |
+
await db.commit()
|
| 420 |
+
|
| 421 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="persona")
|
| 422 |
+
return Response(
|
| 423 |
+
content=bytes(pdf_bytes),
|
| 424 |
+
media_type="application/pdf",
|
| 425 |
+
headers={"Content-Disposition": f"attachment; filename=CrowData_Persona_{cuit_clean}.pdf"}
|
| 426 |
+
)
|
| 427 |
+
except HTTPException:
|
| 428 |
+
raise
|
| 429 |
+
except Exception as e:
|
| 430 |
+
logger.error(f"Error generating PDF for {cuit}: {e}")
|
| 431 |
+
raise HTTPException(status_code=500, detail="Error al generar PDF")
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
@router.get("/empresa/{cuit}/pdf", summary="Descargar PDF de Empresa")
|
| 435 |
+
async def get_empresa_pdf(
|
| 436 |
+
cuit: str,
|
| 437 |
+
current_user: User = Depends(current_active_user),
|
| 438 |
+
):
|
| 439 |
+
cuit_clean = await validate_cuit(cuit)
|
| 440 |
+
|
| 441 |
+
# Check credits for free users
|
| 442 |
+
if current_user.credits <= 0 and current_user.plan == "free":
|
| 443 |
+
raise HTTPException(status_code=429, detail="Alcanzaste el lΓmite de informes. ActualizΓ‘ tu plan para continuar.")
|
| 444 |
+
|
| 445 |
+
try:
|
| 446 |
+
report = await service.get_company_report(cuit_clean)
|
| 447 |
+
|
| 448 |
+
# Deduct credit for free users (only if not cached)
|
| 449 |
+
if not getattr(report.meta, "cached", False) and current_user.plan == "free":
|
| 450 |
+
from app.database import AsyncSessionLocal
|
| 451 |
+
async with AsyncSessionLocal() as db:
|
| 452 |
+
await db.execute(
|
| 453 |
+
update(User)
|
| 454 |
+
.where(User.id == current_user.id)
|
| 455 |
+
.values(credits=User.credits - 1)
|
| 456 |
+
)
|
| 457 |
+
await db.commit()
|
| 458 |
+
|
| 459 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="empresa")
|
| 460 |
+
return Response(
|
| 461 |
+
content=bytes(pdf_bytes),
|
| 462 |
+
media_type="application/pdf",
|
| 463 |
+
headers={"Content-Disposition": f"attachment; filename=CrowData_Empresa_{cuit_clean}.pdf"}
|
| 464 |
+
)
|
| 465 |
+
except HTTPException:
|
| 466 |
+
raise
|
| 467 |
+
except Exception as e:
|
| 468 |
+
logger.error(f"Error generating PDF for {cuit}: {e}")
|
| 469 |
+
raise HTTPException(status_code=500, detail="Error al generar PDF")
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
@router.post("/persona/{cuit}/email-pdf", summary="Enviar PDF de Persona por email")
|
| 473 |
+
async def email_persona_pdf(
|
| 474 |
+
cuit: str,
|
| 475 |
+
current_user: User = Depends(current_active_user),
|
| 476 |
+
):
|
| 477 |
+
"""Genera el PDF de persona y lo envΓa por email al usuario."""
|
| 478 |
+
from app.utils.email_service import send_pdf_report
|
| 479 |
+
cuit_clean = await validate_cuit(cuit)
|
| 480 |
+
try:
|
| 481 |
+
report = await service.get_person_report(cuit_clean)
|
| 482 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="persona")
|
| 483 |
+
sent = await send_pdf_report(
|
| 484 |
+
to_email=current_user.email,
|
| 485 |
+
report_type="persona",
|
| 486 |
+
identifier=cuit_clean,
|
| 487 |
+
pdf_bytes=bytes(pdf_bytes),
|
| 488 |
+
user_name=current_user.full_name,
|
| 489 |
+
)
|
| 490 |
+
if sent:
|
| 491 |
+
return {"message": f"Informe enviado a {current_user.email}"}
|
| 492 |
+
raise HTTPException(status_code=500, detail="Error al enviar email")
|
| 493 |
+
except HTTPException:
|
| 494 |
+
raise
|
| 495 |
+
except Exception as e:
|
| 496 |
+
logger.error(f"Error emailing persona PDF for {mask_cuit(cuit)}: {e}")
|
| 497 |
+
raise HTTPException(status_code=500, detail="Error al generar o enviar PDF")
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
@router.post("/empresa/{cuit}/email-pdf", summary="Enviar PDF de Empresa por email")
|
| 501 |
+
async def email_empresa_pdf(
|
| 502 |
+
cuit: str,
|
| 503 |
+
current_user: User = Depends(current_active_user),
|
| 504 |
+
):
|
| 505 |
+
"""Genera el PDF de empresa y lo envΓa por email al usuario."""
|
| 506 |
+
from app.utils.email_service import send_pdf_report
|
| 507 |
+
cuit_clean = await validate_cuit(cuit)
|
| 508 |
+
try:
|
| 509 |
+
report = await service.get_company_report(cuit_clean)
|
| 510 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="empresa")
|
| 511 |
+
sent = await send_pdf_report(
|
| 512 |
+
to_email=current_user.email,
|
| 513 |
+
report_type="empresa",
|
| 514 |
+
identifier=cuit_clean,
|
| 515 |
+
pdf_bytes=bytes(pdf_bytes),
|
| 516 |
+
user_name=current_user.full_name,
|
| 517 |
+
)
|
| 518 |
+
if sent:
|
| 519 |
+
return {"message": f"Informe enviado a {current_user.email}"}
|
| 520 |
+
raise HTTPException(status_code=500, detail="Error al enviar email")
|
| 521 |
+
except HTTPException:
|
| 522 |
+
raise
|
| 523 |
+
except Exception as e:
|
| 524 |
+
logger.error(f"Error emailing empresa PDF for {mask_cuit(cuit)}: {e}")
|
| 525 |
+
raise HTTPException(status_code=500, detail="Error al generar o enviar PDF")
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
@router.post("/vehiculo/{dominio}/email-pdf", summary="Enviar PDF de VehΓculo por email")
|
| 529 |
+
async def email_vehiculo_pdf(
|
| 530 |
+
dominio: str,
|
| 531 |
+
current_user: User = Depends(current_active_user),
|
| 532 |
+
):
|
| 533 |
+
"""Genera el PDF de vehΓculo y lo envΓa por email al usuario."""
|
| 534 |
+
from app.utils.email_service import send_pdf_report
|
| 535 |
+
try:
|
| 536 |
+
report = await service.get_vehicle_report(dominio.upper())
|
| 537 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="vehiculo")
|
| 538 |
+
sent = await send_pdf_report(
|
| 539 |
+
to_email=current_user.email,
|
| 540 |
+
report_type="vehiculo",
|
| 541 |
+
identifier=dominio.upper(),
|
| 542 |
+
pdf_bytes=bytes(pdf_bytes),
|
| 543 |
+
user_name=current_user.full_name,
|
| 544 |
+
)
|
| 545 |
+
if sent:
|
| 546 |
+
return {"message": f"Informe enviado a {current_user.email}"}
|
| 547 |
+
raise HTTPException(status_code=500, detail="Error al enviar email")
|
| 548 |
+
except HTTPException:
|
| 549 |
+
raise
|
| 550 |
+
except Exception as e:
|
| 551 |
+
logger.error(f"Error emailing vehiculo PDF for {dominio}: {e}")
|
| 552 |
+
raise HTTPException(status_code=500, detail="Error al generar o enviar PDF")
|
| 553 |
+
|
| 554 |
+
|
| 555 |
+
@router.get("/propiedad/pdf", summary="Descargar PDF de Propiedad por direcciΓ³n")
|
| 556 |
+
async def get_propiedad_pdf(
|
| 557 |
+
calle: str = Query(..., description="Nombre de la calle"),
|
| 558 |
+
numero: str = Query(..., description="NΓΊmero de puerta"),
|
| 559 |
+
localidad: str = Query("", description="Localidad"),
|
| 560 |
+
provincia: str = Query("", description="Provincia"),
|
| 561 |
+
current_user: User = Depends(current_active_user),
|
| 562 |
+
):
|
| 563 |
+
"""Genera el PDF de propiedad y lo retorna como descarga."""
|
| 564 |
+
try:
|
| 565 |
+
report = await service.get_property_report(calle, numero, localidad, provincia)
|
| 566 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="propiedad")
|
| 567 |
+
identifier = f"{calle} {numero}"
|
| 568 |
+
return Response(
|
| 569 |
+
content=bytes(pdf_bytes),
|
| 570 |
+
media_type="application/pdf",
|
| 571 |
+
headers={"Content-Disposition": f"attachment; filename=CrowData_Propiedad_{identifier.replace(' ', '_')}.pdf"}
|
| 572 |
+
)
|
| 573 |
+
except Exception as e:
|
| 574 |
+
logger.error(f"Error generating PDF for property {calle} {numero}: {e}")
|
| 575 |
+
raise HTTPException(status_code=500, detail="Error al generar PDF")
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
@router.post("/propiedad/email-pdf", summary="Enviar PDF de Propiedad por email")
|
| 579 |
+
async def email_propiedad_pdf(
|
| 580 |
+
body: dict,
|
| 581 |
+
current_user: User = Depends(current_active_user),
|
| 582 |
+
):
|
| 583 |
+
"""Genera el PDF de propiedad y lo envΓa por email al usuario."""
|
| 584 |
+
from app.utils.email_service import send_pdf_report
|
| 585 |
+
try:
|
| 586 |
+
calle = body.get("calle", "")
|
| 587 |
+
numero = body.get("numero", "")
|
| 588 |
+
localidad = body.get("localidad", "")
|
| 589 |
+
provincia = body.get("provincia", "")
|
| 590 |
+
report = await service.get_property_report(calle, numero, localidad, provincia)
|
| 591 |
+
identifier = f"{calle} {numero}"
|
| 592 |
+
pdf_bytes = generate_report_pdf(report.model_dump(), report_type="propiedad")
|
| 593 |
+
sent = await send_pdf_report(
|
| 594 |
+
to_email=current_user.email,
|
| 595 |
+
report_type="propiedad",
|
| 596 |
+
identifier=identifier,
|
| 597 |
+
pdf_bytes=bytes(pdf_bytes),
|
| 598 |
+
user_name=current_user.full_name,
|
| 599 |
+
)
|
| 600 |
+
if sent:
|
| 601 |
+
return {"message": f"Informe enviado a {current_user.email}"}
|
| 602 |
+
raise HTTPException(status_code=500, detail="Error al enviar email")
|
| 603 |
+
except HTTPException:
|
| 604 |
+
raise
|
| 605 |
+
except Exception as e:
|
| 606 |
+
logger.error(f"Error emailing propiedad PDF: {e}")
|
| 607 |
+
raise HTTPException(status_code=500, detail="Error al generar o enviar PDF")
|
| 608 |
+
|
| 609 |
+
|
| 610 |
+
@router.get("/empresa/{cuit}/contrataciones", summary="Contrataciones pΓΊblicas de una empresa")
|
| 611 |
+
async def get_contrataciones(
|
| 612 |
+
cuit: str,
|
| 613 |
+
current_user: User = Depends(current_active_user),
|
| 614 |
+
):
|
| 615 |
+
"""Consulta contrataciones pΓΊblicas (CONTRATAR/COMPR.AR) por CUIT vΓa datos.gob.ar."""
|
| 616 |
+
cuit_clean = await validate_cuit(cuit)
|
| 617 |
+
from app.reports.service import contratar_scraper
|
| 618 |
+
try:
|
| 619 |
+
result = await contratar_scraper.safe_fetch(cuit_clean)
|
| 620 |
+
return {"cuit": cuit_clean, "contrataciones": result}
|
| 621 |
+
except Exception as e:
|
| 622 |
+
logger.error(f"Error fetching contrataciones for {cuit}: {e}")
|
| 623 |
+
raise HTTPException(status_code=500, detail="Error al consultar contrataciones")
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
@router.get("/persona/{dni}/deudores-alimentarios", summary="Registro deudores alimentarios (PBA)")
|
| 627 |
+
async def get_deudores_alimentarios(
|
| 628 |
+
dni: str,
|
| 629 |
+
sexo: str = "M",
|
| 630 |
+
current_user: User = Depends(current_active_user),
|
| 631 |
+
):
|
| 632 |
+
"""Consulta el RDAM de Provincia de Buenos Aires por DNI y sexo."""
|
| 633 |
+
dni_clean = dni.replace("-", "").replace(".", "").strip()
|
| 634 |
+
if not dni_clean.isdigit() or len(dni_clean) < 7:
|
| 635 |
+
raise HTTPException(status_code=400, detail="DNI invΓ‘lido")
|
| 636 |
+
if sexo.upper() not in ("M", "F", "NB", "NC"):
|
| 637 |
+
raise HTTPException(status_code=400, detail="Sexo invΓ‘lido. Use M, F, NB o NC")
|
| 638 |
+
from app.reports.service import deudores_alimentarios_scraper
|
| 639 |
+
try:
|
| 640 |
+
result = await deudores_alimentarios_scraper.safe_fetch(dni_clean, sexo=sexo.upper())
|
| 641 |
+
return {"dni": dni_clean, "deudores_alimentarios": result}
|
| 642 |
+
except Exception as e:
|
| 643 |
+
logger.error(f"Error fetching deudores alimentarios for DNI {dni}: {e}")
|
| 644 |
+
raise HTTPException(status_code=500, detail="Error al consultar deudores alimentarios")
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
@router.get("/user/history", response_model=list[HistoryItem], summary="Historial de bΓΊsquedas")
|
| 648 |
+
async def get_history(
|
| 649 |
+
current_user: User = Depends(current_active_user),
|
| 650 |
+
):
|
| 651 |
+
"""Retorna el historial de bΓΊsquedas del usuario actual."""
|
| 652 |
+
try:
|
| 653 |
+
return await service.get_user_history(str(current_user.id))
|
| 654 |
+
except Exception as e:
|
| 655 |
+
logger.error(f"Error fetching history: {e}")
|
| 656 |
+
raise HTTPException(status_code=500, detail="Error al obtener historial")
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
@router.post("/user/monitor/{report_type}/{identifier}", summary="Activar monitoreo para un CUIT")
|
| 660 |
+
async def start_monitoring(
|
| 661 |
+
report_type: str,
|
| 662 |
+
identifier: str,
|
| 663 |
+
current_user: User = Depends(current_active_user),
|
| 664 |
+
):
|
| 665 |
+
try:
|
| 666 |
+
await service.add_monitor_task(str(current_user.id), identifier, report_type)
|
| 667 |
+
return {"message": f"Monitoreo activado para {identifier}"}
|
| 668 |
+
except Exception as e:
|
| 669 |
+
logger.error(f"Error starting monitor: {e}")
|
| 670 |
+
raise HTTPException(status_code=500, detail="Error al activar monitoreo")
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
@router.get("/user/monitor", response_model=list[MonitorItem], summary="Lista de CUITs monitoreados")
|
| 674 |
+
async def get_monitored_items(
|
| 675 |
+
current_user: User = Depends(current_active_user),
|
| 676 |
+
):
|
| 677 |
+
try:
|
| 678 |
+
return await service.get_monitor_tasks(str(current_user.id))
|
| 679 |
+
except Exception as e:
|
| 680 |
+
logger.error(f"Error fetching monitor list: {e}")
|
| 681 |
+
raise HTTPException(status_code=500, detail="Error al obtener lista de monitoreo")
|
| 682 |
+
|
| 683 |
+
|
| 684 |
+
# βββ TelemetrΓa y Salud de Scrapers βββββββββββββββββββββββββββββββββββββββββββ
|
| 685 |
+
|
| 686 |
+
from app.utils.telemetry import get_system_health_summary
|
| 687 |
+
|
| 688 |
+
|
| 689 |
+
@router.get("/scrapers/health", summary="Estado de salud de los scrapers", tags=["telemetry"])
|
| 690 |
+
async def get_scrapers_health(
|
| 691 |
+
current_user: User = Depends(current_active_user),
|
| 692 |
+
):
|
| 693 |
+
"""
|
| 694 |
+
Retorna el estado de salud de los 31 scrapers disponibles en CrowData.
|
| 695 |
+
|
| 696 |
+
Incluye para cada fuente:
|
| 697 |
+
- Estado del ΓΊltimo run (ok / empty / error / blocked)
|
| 698 |
+
- Timestamp del ΓΊltimo run
|
| 699 |
+
- Tasa de Γ©xito en las ΓΊltimas 24h
|
| 700 |
+
- Latencia promedio
|
| 701 |
+
- Cantidad de registros encontrados
|
| 702 |
+
|
| 703 |
+
Solo disponible para usuarios autenticados.
|
| 704 |
+
"""
|
| 705 |
+
try:
|
| 706 |
+
summary = get_system_health_summary()
|
| 707 |
+
return summary
|
| 708 |
+
except Exception as e:
|
| 709 |
+
logger.error(f"Error obteniendo health de scrapers: {e}")
|
| 710 |
+
raise HTTPException(status_code=500, detail="Error al obtener estado de scrapers")
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
@router.get("/scrapers/health/public", summary="Estado pΓΊblico de scrapers (resumen)", tags=["telemetry"])
|
| 714 |
+
async def get_scrapers_health_public():
|
| 715 |
+
"""
|
| 716 |
+
VersiΓ³n pΓΊblica del estado de salud: muestra cuΓ‘ntos scrapers estΓ‘n operativos
|
| 717 |
+
sin detalles individuales (para mostrar en la landing page).
|
| 718 |
+
"""
|
| 719 |
+
try:
|
| 720 |
+
summary = get_system_health_summary()
|
| 721 |
+
return {
|
| 722 |
+
"total_scrapers": summary["total_scrapers"],
|
| 723 |
+
"operativos": summary["operativos"],
|
| 724 |
+
"health_pct": summary["health_pct"],
|
| 725 |
+
}
|
| 726 |
+
except Exception as e:
|
| 727 |
+
logger.error(f"Error obteniendo health pΓΊblico: {e}")
|
| 728 |
+
raise HTTPException(status_code=500, detail="Error al obtener estado")
|
| 729 |
+
|
| 730 |
+
|
| 731 |
+
@router.get("/scrapers/alerts", summary="Alertas de scrapers con problemas", tags=["telemetry"])
|
| 732 |
+
async def get_scrapers_alerts(
|
| 733 |
+
current_user: User = Depends(current_active_user),
|
| 734 |
+
):
|
| 735 |
+
"""
|
| 736 |
+
Detecta scrapers con problemas consistentes:
|
| 737 |
+
- Errores activos mΓΊltiples
|
| 738 |
+
- Bloqueos antibot
|
| 739 |
+
- Tasa de Γ©xito baja (< 30%)
|
| 740 |
+
- Scrapers que nunca corrieron
|
| 741 |
+
"""
|
| 742 |
+
from app.utils.telemetry import get_scrapers_alerts as _get_alerts
|
| 743 |
+
try:
|
| 744 |
+
return {"alerts": _get_alerts()}
|
| 745 |
+
except Exception as e:
|
| 746 |
+
logger.error(f"Error obteniendo alertas de scrapers: {e}")
|
| 747 |
+
raise HTTPException(status_code=500, detail="Error al obtener alertas")
|
| 748 |
+
|
| 749 |
+
|
| 750 |
+
@router.get("/boletin", summary="Buscar publicaciones en el BoletΓn Oficial")
|
| 751 |
+
async def search_boletin(
|
| 752 |
+
q: str = Query(..., min_length=1, description="TΓ©rmino de bΓΊsqueda"),
|
| 753 |
+
):
|
| 754 |
+
"""BΓΊsqueda standalone del BoletΓn Oficial de la NaciΓ³n."""
|
| 755 |
+
from app.scrapers.boletin_oficial import BoletinOficialScraper
|
| 756 |
+
scraper = BoletinOficialScraper()
|
| 757 |
+
try:
|
| 758 |
+
result = await scraper.safe_fetch(q)
|
| 759 |
+
publicaciones = result.get("publicaciones", []) if isinstance(result, dict) else []
|
| 760 |
+
return {"publicaciones": publicaciones, "total": len(publicaciones)}
|
| 761 |
+
except Exception as e:
|
| 762 |
+
logger.error(f"Error buscando en BO: {e}")
|
| 763 |
+
raise HTTPException(status_code=502, detail="Error consultando el BoletΓn Oficial.")
|
app/reports/schema_validators.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Validadores Pydantic para campos numΓ©ricos y crΓticos
|
| 3 |
+
CorrecciΓ³n de auditorΓa tΓ©cnica - 05/07/2026
|
| 4 |
+
|
| 5 |
+
Agregar estos validadores a schemas.py para prevenir datos malformados
|
| 6 |
+
"""
|
| 7 |
+
from pydantic import field_validator
|
| 8 |
+
import logging
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 14 |
+
# VALIDADORES PARA BcraHistorial
|
| 15 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 16 |
+
|
| 17 |
+
class BcraHistorialValidators:
|
| 18 |
+
"""
|
| 19 |
+
Agregar estos validadores a la clase BcraHistorial en schemas.py
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
@field_validator('monto_deuda', mode='before')
|
| 23 |
+
@classmethod
|
| 24 |
+
def coerce_monto_deuda(cls, v):
|
| 25 |
+
"""Convierte string a float si es necesario"""
|
| 26 |
+
if v is None:
|
| 27 |
+
return None
|
| 28 |
+
if isinstance(v, (int, float)):
|
| 29 |
+
return float(v)
|
| 30 |
+
if isinstance(v, str):
|
| 31 |
+
# Limpiar formato de moneda
|
| 32 |
+
clean = v.replace('$', '').replace(',', '').replace('.', '').replace('k', '000').strip()
|
| 33 |
+
try:
|
| 34 |
+
return float(clean) if clean else None
|
| 35 |
+
except ValueError:
|
| 36 |
+
logger.warning(f"No se pudo convertir monto_deuda: {v}")
|
| 37 |
+
return None
|
| 38 |
+
return None
|
| 39 |
+
|
| 40 |
+
@field_validator('situacion', mode='before')
|
| 41 |
+
@classmethod
|
| 42 |
+
def validate_situacion(cls, v):
|
| 43 |
+
"""Valida que situaciΓ³n estΓ© entre 1-6"""
|
| 44 |
+
if v is None:
|
| 45 |
+
return 1 # Default Normal
|
| 46 |
+
try:
|
| 47 |
+
sit = int(v)
|
| 48 |
+
if 1 <= sit <= 6:
|
| 49 |
+
return sit
|
| 50 |
+
logger.warning(f"SituaciΓ³n BCRA fuera de rango: {sit}, usando 1 (Normal)")
|
| 51 |
+
return 1
|
| 52 |
+
except (ValueError, TypeError):
|
| 53 |
+
logger.warning(f"SituaciΓ³n BCRA invΓ‘lida: {v}, usando 1 (Normal)")
|
| 54 |
+
return 1
|
| 55 |
+
|
| 56 |
+
@field_validator('dias_atraso', mode='before')
|
| 57 |
+
@classmethod
|
| 58 |
+
def coerce_dias_atraso(cls, v):
|
| 59 |
+
"""Convierte a int de forma segura"""
|
| 60 |
+
if v is None:
|
| 61 |
+
return None
|
| 62 |
+
try:
|
| 63 |
+
return int(v)
|
| 64 |
+
except (ValueError, TypeError):
|
| 65 |
+
logger.warning(f"dias_atraso invΓ‘lido: {v}")
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 70 |
+
# VALIDADORES PARA ChequeRechazado
|
| 71 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 72 |
+
|
| 73 |
+
class ChequeRechazadoValidators:
|
| 74 |
+
"""
|
| 75 |
+
Agregar estos validadores a la clase ChequeRechazado en schemas.py
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
@field_validator('monto', mode='before')
|
| 79 |
+
@classmethod
|
| 80 |
+
def coerce_monto(cls, v):
|
| 81 |
+
"""Convierte string a float si es necesario"""
|
| 82 |
+
if v is None:
|
| 83 |
+
return None
|
| 84 |
+
if isinstance(v, (int, float)):
|
| 85 |
+
return float(v)
|
| 86 |
+
if isinstance(v, str):
|
| 87 |
+
clean = v.replace('$', '').replace(',', '').replace('.', '').strip()
|
| 88 |
+
try:
|
| 89 |
+
return float(clean) if clean else None
|
| 90 |
+
except ValueError:
|
| 91 |
+
logger.warning(f"No se pudo convertir monto de cheque: {v}")
|
| 92 |
+
return None
|
| 93 |
+
return None
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 97 |
+
# VALIDADORES PARA Inmueble
|
| 98 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 99 |
+
|
| 100 |
+
class InmuebleValidators:
|
| 101 |
+
"""
|
| 102 |
+
Agregar estos validadores a la clase Inmueble en schemas.py
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
@field_validator('valuacion_fiscal', mode='before')
|
| 106 |
+
@classmethod
|
| 107 |
+
def coerce_valuacion_fiscal(cls, v):
|
| 108 |
+
"""Convierte string a float si es necesario"""
|
| 109 |
+
if v is None:
|
| 110 |
+
return None
|
| 111 |
+
if isinstance(v, (int, float)):
|
| 112 |
+
return float(v)
|
| 113 |
+
if isinstance(v, str):
|
| 114 |
+
clean = v.replace('$', '').replace(',', '').replace('.', '').strip()
|
| 115 |
+
try:
|
| 116 |
+
return float(clean) if clean else None
|
| 117 |
+
except ValueError:
|
| 118 |
+
logger.warning(f"No se pudo convertir valuacion_fiscal: {v}")
|
| 119 |
+
return None
|
| 120 |
+
return None
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 124 |
+
# VALIDADORES PARA InfraccionTransito
|
| 125 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 126 |
+
|
| 127 |
+
class InfraccionTransitoValidators:
|
| 128 |
+
"""
|
| 129 |
+
Agregar estos validadores a la clase InfraccionTransito en schemas.py
|
| 130 |
+
"""
|
| 131 |
+
|
| 132 |
+
@field_validator('monto', mode='before')
|
| 133 |
+
@classmethod
|
| 134 |
+
def coerce_monto(cls, v):
|
| 135 |
+
"""Convierte string a float si es necesario"""
|
| 136 |
+
if v is None:
|
| 137 |
+
return None
|
| 138 |
+
if isinstance(v, (int, float)):
|
| 139 |
+
return float(v)
|
| 140 |
+
if isinstance(v, str):
|
| 141 |
+
clean = v.replace('$', '').replace(',', '').replace('.', '').strip()
|
| 142 |
+
try:
|
| 143 |
+
return float(clean) if clean else None
|
| 144 |
+
except ValueError:
|
| 145 |
+
logger.warning(f"No se pudo convertir monto de infracciΓ³n: {v}")
|
| 146 |
+
return None
|
| 147 |
+
return None
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 151 |
+
# EJEMPLO DE USO EN schemas.py
|
| 152 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 153 |
+
|
| 154 |
+
"""
|
| 155 |
+
# EN EL ARCHIVO schemas.py, AGREGAR:
|
| 156 |
+
|
| 157 |
+
from pydantic import field_validator
|
| 158 |
+
import logging
|
| 159 |
+
|
| 160 |
+
logger = logging.getLogger(__name__)
|
| 161 |
+
|
| 162 |
+
class BcraHistorial(BaseModel):
|
| 163 |
+
periodo: Optional[str] = None
|
| 164 |
+
situacion: Optional[int] = None
|
| 165 |
+
entidad: Optional[str] = None
|
| 166 |
+
monto_deuda: Optional[float] = None
|
| 167 |
+
denominacion: Optional[str] = None
|
| 168 |
+
dias_atraso: Optional[int] = None
|
| 169 |
+
# ... resto de campos
|
| 170 |
+
|
| 171 |
+
# VALIDADORES
|
| 172 |
+
@field_validator('monto_deuda', mode='before')
|
| 173 |
+
@classmethod
|
| 174 |
+
def coerce_monto_deuda(cls, v):
|
| 175 |
+
if v is None:
|
| 176 |
+
return None
|
| 177 |
+
if isinstance(v, (int, float)):
|
| 178 |
+
return float(v)
|
| 179 |
+
if isinstance(v, str):
|
| 180 |
+
clean = v.replace('$', '').replace(',', '').replace('.', '').replace('k', '000').strip()
|
| 181 |
+
try:
|
| 182 |
+
return float(clean) if clean else None
|
| 183 |
+
except ValueError:
|
| 184 |
+
logger.warning(f"No se pudo convertir monto_deuda: {v}")
|
| 185 |
+
return None
|
| 186 |
+
return None
|
| 187 |
+
|
| 188 |
+
@field_validator('situacion', mode='before')
|
| 189 |
+
@classmethod
|
| 190 |
+
def validate_situacion(cls, v):
|
| 191 |
+
if v is None:
|
| 192 |
+
return 1
|
| 193 |
+
try:
|
| 194 |
+
sit = int(v)
|
| 195 |
+
if 1 <= sit <= 6:
|
| 196 |
+
return sit
|
| 197 |
+
logger.warning(f"SituaciΓ³n BCRA fuera de rango: {sit}, usando 1")
|
| 198 |
+
return 1
|
| 199 |
+
except (ValueError, TypeError):
|
| 200 |
+
return 1
|
| 201 |
+
|
| 202 |
+
# REPETIR PARA CADA CLASE QUE NECESITE VALIDACIΓN
|
| 203 |
+
"""
|
app/reports/schemas.py
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field, field_validator
|
| 2 |
+
from typing import Optional
|
| 3 |
+
from datetime import date, datetime
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# ββ IdentificaciΓ³n ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 7 |
+
class Domicilio(BaseModel):
|
| 8 |
+
tipo: Optional[str] = None # fiscal | real
|
| 9 |
+
calle: Optional[str] = None
|
| 10 |
+
numero: Optional[str] = None
|
| 11 |
+
piso: Optional[str] = None
|
| 12 |
+
dpto: Optional[str] = None
|
| 13 |
+
torre: Optional[str] = None
|
| 14 |
+
manzana: Optional[str] = None
|
| 15 |
+
sector: Optional[str] = None
|
| 16 |
+
localidad: Optional[str] = None
|
| 17 |
+
provincia: Optional[str] = None
|
| 18 |
+
id_provincia: Optional[int] = None
|
| 19 |
+
cp: Optional[str] = None
|
| 20 |
+
dato_adicional: Optional[str] = None
|
| 21 |
+
tipo_dato_adicional: Optional[str] = None
|
| 22 |
+
estado: Optional[str] = None
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Identificacion(BaseModel):
|
| 26 |
+
dni: Optional[str] = None
|
| 27 |
+
cuil: Optional[str] = None
|
| 28 |
+
apellido: Optional[str] = None
|
| 29 |
+
nombres: Optional[str] = None
|
| 30 |
+
fecha_nacimiento: Optional[str] = None
|
| 31 |
+
fecha_defuncion: Optional[str] = None
|
| 32 |
+
sexo: Optional[str] = None
|
| 33 |
+
nacionalidad: Optional[str] = None
|
| 34 |
+
foto_perfil: Optional[str] = None
|
| 35 |
+
foto_perfil_fuente: Optional[str] = None # "linkedin", "ui_avatars", None
|
| 36 |
+
validado_renaper: bool = False
|
| 37 |
+
# PadrΓ³n Electoral
|
| 38 |
+
seccion_electoral: Optional[str] = None # Distrito/secciΓ³n del padrΓ³n
|
| 39 |
+
lugar_votacion: Optional[str] = None # Escuela/establecimiento de votaciΓ³n
|
| 40 |
+
mesa_votacion: Optional[str] = None # NΓΊmero de mesa
|
| 41 |
+
orden_padron: Optional[str] = None # Orden en el padrΓ³n
|
| 42 |
+
cruzado_con_busqueda: bool = False
|
| 43 |
+
busqueda_tipo: Optional[str] = None
|
| 44 |
+
# Contacto adicional de IGJ
|
| 45 |
+
pagina_web: Optional[str] = None
|
| 46 |
+
email_contacto: Optional[str] = None
|
| 47 |
+
linkedin: Optional[str] = None
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class Contacto(BaseModel):
|
| 51 |
+
domicilios: list[Domicilio] = []
|
| 52 |
+
telefonos: list[str] = []
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# ββ Fiscal ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 56 |
+
class ActividadFiscal(BaseModel):
|
| 57 |
+
codigo_clae: Optional[str] = None
|
| 58 |
+
descripcion: Optional[str] = None
|
| 59 |
+
es_principal: bool = False
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class Monotributo(BaseModel):
|
| 63 |
+
categoria: Optional[str] = None
|
| 64 |
+
actividad_principal: Optional[str] = None
|
| 65 |
+
fecha_alta: Optional[str] = None
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class DatosFiscales(BaseModel):
|
| 69 |
+
cuit: Optional[str] = None
|
| 70 |
+
tipo_clave: Optional[str] = None
|
| 71 |
+
tipo_documento: Optional[str] = None
|
| 72 |
+
numero_documento: Optional[str] = None
|
| 73 |
+
nombres: Optional[str] = None
|
| 74 |
+
apellido: Optional[str] = None
|
| 75 |
+
nombres_completo: Optional[str] = None
|
| 76 |
+
condicion_iva: Optional[str] = None
|
| 77 |
+
estado_afip: Optional[str] = None
|
| 78 |
+
fecha_inicio_actividad: Optional[str] = None
|
| 79 |
+
fecha_nacimiento: Optional[str] = None
|
| 80 |
+
fecha_contrato_social: Optional[str] = None
|
| 81 |
+
fecha_fallecimiento: Optional[str] = None
|
| 82 |
+
forma_juridica: Optional[str] = None
|
| 83 |
+
mes_cierre: Optional[int] = None
|
| 84 |
+
periodo_actividad_principal: Optional[int] = None
|
| 85 |
+
actividades: list[ActividadFiscal] = []
|
| 86 |
+
domicilios: list[Domicilio] = []
|
| 87 |
+
domicilio_fiscal: Optional[Domicilio] = None
|
| 88 |
+
monotributo: Optional[Monotributo] = None
|
| 89 |
+
riesgo_sircreb: Optional[str] = None
|
| 90 |
+
riesgo_sircupa: Optional[str] = None
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
# ββ Financiero ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 94 |
+
class BcraHistorial(BaseModel):
|
| 95 |
+
periodo: Optional[str] = None
|
| 96 |
+
situacion: Optional[int] = None
|
| 97 |
+
entidad: Optional[str] = None
|
| 98 |
+
monto_deuda: Optional[float] = None
|
| 99 |
+
denominacion: Optional[str] = None
|
| 100 |
+
dias_atraso: Optional[int] = None
|
| 101 |
+
fecha_sit1: Optional[str] = None
|
| 102 |
+
refinanciaciones: Optional[bool] = None
|
| 103 |
+
situacion_juridica: Optional[str] = None
|
| 104 |
+
recategorizacion: Optional[bool] = None
|
| 105 |
+
irrec_disp_tecnica: Optional[bool] = None
|
| 106 |
+
en_revision: Optional[bool] = None
|
| 107 |
+
proceso_judicial: Optional[bool] = None
|
| 108 |
+
|
| 109 |
+
@field_validator('situacion_juridica', mode='before')
|
| 110 |
+
@classmethod
|
| 111 |
+
def coerce_situacion_juridica(cls, v):
|
| 112 |
+
if v is None:
|
| 113 |
+
return None
|
| 114 |
+
if isinstance(v, bool):
|
| 115 |
+
return "SΓ" if v else "No"
|
| 116 |
+
return str(v)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class ChequeRechazado(BaseModel):
|
| 120 |
+
causal: Optional[str] = None
|
| 121 |
+
entidad_codigo: Optional[str] = None
|
| 122 |
+
nro_cheque: Optional[str] = None
|
| 123 |
+
fecha_rechazo: Optional[str] = None
|
| 124 |
+
monto: Optional[float] = None
|
| 125 |
+
fecha: Optional[str] = None
|
| 126 |
+
banco: Optional[str] = None
|
| 127 |
+
estado: Optional[str] = None
|
| 128 |
+
fecha_pago: Optional[str] = None
|
| 129 |
+
fecha_pago_multa: Optional[str] = None
|
| 130 |
+
estado_multa: Optional[str] = None
|
| 131 |
+
cta_personal: Optional[bool] = None
|
| 132 |
+
denom_juridica: Optional[str] = None
|
| 133 |
+
en_revision: Optional[bool] = None
|
| 134 |
+
proceso_judicial: Optional[bool] = None
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
class BilleteraVirtual(BaseModel):
|
| 138 |
+
entidad_bcra: Optional[str] = None
|
| 139 |
+
marca: Optional[str] = None
|
| 140 |
+
tipo: Optional[str] = None
|
| 141 |
+
rubro: Optional[str] = None
|
| 142 |
+
situacion: Optional[int] = None
|
| 143 |
+
situacion_desc: Optional[str] = None
|
| 144 |
+
monto: Optional[float] = None
|
| 145 |
+
periodo: Optional[str] = None
|
| 146 |
+
es_fintech: bool = False
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class DatosBilleterasVirtuales(BaseModel):
|
| 150 |
+
total_deuda_fintech: float = 0
|
| 151 |
+
cantidad_wallets_con_deuda: int = 0
|
| 152 |
+
cantidad_fintech_detectadas: int = 0
|
| 153 |
+
detalle: list[BilleteraVirtual] = []
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
class DatosFinancieros(BaseModel):
|
| 157 |
+
denominacion: Optional[str] = None
|
| 158 |
+
bcra_situacion_actual: Optional[int] = None
|
| 159 |
+
bcra_situacion_descripcion: Optional[str] = None
|
| 160 |
+
bcra_historial: list[BcraHistorial] = []
|
| 161 |
+
bcra_total_deuda_miles: Optional[float] = None
|
| 162 |
+
bcra_dias_atraso_max: Optional[int] = None
|
| 163 |
+
cheques_rechazados: list[ChequeRechazado] = []
|
| 164 |
+
tiene_deuda: bool = False
|
| 165 |
+
total_deuda_miles: Optional[float] = None
|
| 166 |
+
dias_atraso_max: Optional[int] = None
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
# ββ Societario ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 170 |
+
class ParticipacionSocietaria(BaseModel):
|
| 171 |
+
cuit_empresa: Optional[str] = None
|
| 172 |
+
razon_social: Optional[str] = None
|
| 173 |
+
rol: Optional[str] = None
|
| 174 |
+
fecha_desde: Optional[str] = None
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
class MatriculaProfesional(BaseModel):
|
| 178 |
+
consejo: Optional[str] = None
|
| 179 |
+
matricula: Optional[str] = None
|
| 180 |
+
estado: Optional[str] = None
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
class DatosSocietarios(BaseModel):
|
| 184 |
+
participaciones: list[ParticipacionSocietaria] = []
|
| 185 |
+
matriculas_profesionales: list[MatriculaProfesional] = []
|
| 186 |
+
cnv_registros: list[dict] = []
|
| 187 |
+
|
| 188 |
+
class TituloAcademico(BaseModel):
|
| 189 |
+
titulo: str
|
| 190 |
+
institucion: Optional[str] = None
|
| 191 |
+
anio_graduacion: Optional[str] = None
|
| 192 |
+
nivel: Optional[str] = None # Grado | Posgrado | TΓ©cnico
|
| 193 |
+
|
| 194 |
+
class DatosAcademicos(BaseModel):
|
| 195 |
+
titulos: list[TituloAcademico] = []
|
| 196 |
+
certificaciones: list[str] = []
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
# ββ Patrimonial βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 200 |
+
class Vehiculo(BaseModel):
|
| 201 |
+
dominio: Optional[str] = None
|
| 202 |
+
marca: Optional[str] = None
|
| 203 |
+
modelo: Optional[str] = None
|
| 204 |
+
anio: Optional[int] = None
|
| 205 |
+
tipo: Optional[str] = None
|
| 206 |
+
radicacion: Optional[str] = None
|
| 207 |
+
registro: Optional[str] = None
|
| 208 |
+
localidad: Optional[str] = None
|
| 209 |
+
provincia: Optional[str] = None
|
| 210 |
+
validez_vtv: Optional[str] = None
|
| 211 |
+
tiene_seguro: bool = True
|
| 212 |
+
estado: Optional[str] = None
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
class Inmueble(BaseModel):
|
| 216 |
+
partido: Optional[str] = None
|
| 217 |
+
matricula: Optional[str] = None
|
| 218 |
+
tipo: Optional[str] = None
|
| 219 |
+
descripcion: Optional[str] = None
|
| 220 |
+
provincia: Optional[str] = None
|
| 221 |
+
superficie: Optional[str] = None
|
| 222 |
+
nro_partida: Optional[str] = None
|
| 223 |
+
# Nuevos campos
|
| 224 |
+
valuacion_fiscal: Optional[float] = None
|
| 225 |
+
fuente: Optional[str] = None
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
class Inhibicion(BaseModel):
|
| 229 |
+
tipo: Optional[str] = None
|
| 230 |
+
organismo: Optional[str] = None
|
| 231 |
+
fecha: Optional[str] = None
|
| 232 |
+
monto: Optional[float] = None
|
| 233 |
+
descripcion: Optional[str] = None
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
class DatosPatrimoniales(BaseModel):
|
| 237 |
+
vehiculos: list[Vehiculo] = []
|
| 238 |
+
inmuebles: list[Inmueble] = []
|
| 239 |
+
inhibiciones: list[Inhibicion] = []
|
| 240 |
+
deuda_patentes: list[dict] = []
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
# ββ Judicial ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 244 |
+
class CausaJudicial(BaseModel):
|
| 245 |
+
expediente: Optional[str] = None
|
| 246 |
+
fuero: Optional[str] = None
|
| 247 |
+
juzgado: Optional[str] = None
|
| 248 |
+
caratula: Optional[str] = None
|
| 249 |
+
fecha: Optional[str] = None
|
| 250 |
+
estado: Optional[str] = None
|
| 251 |
+
jurisdiccion: Optional[str] = None
|
| 252 |
+
caratula_completa: Optional[str] = None
|
| 253 |
+
voces: Optional[str] = None
|
| 254 |
+
sumario: Optional[str] = None
|
| 255 |
+
magistrados: Optional[str] = None
|
| 256 |
+
tipo_fallo: Optional[str] = None
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
class DatosJudiciales(BaseModel):
|
| 260 |
+
causas: list[CausaJudicial] = []
|
| 261 |
+
antecedentes_penales: bool = False
|
| 262 |
+
concursos: list[dict] = []
|
| 263 |
+
quiebras: list[dict] = []
|
| 264 |
+
inhibiciones_embargos: list[Inhibicion] = []
|
| 265 |
+
|
| 266 |
+
class DatosRegistroCivil(BaseModel):
|
| 267 |
+
fallecido: bool = False
|
| 268 |
+
fecha_defuncion: Optional[str] = None
|
| 269 |
+
lugar_defuncion: Optional[str] = None
|
| 270 |
+
actas_disponibles: list[str] = [] # Nacimiento, Matrimonio, DefunciΓ³n
|
| 271 |
+
numero_acta: Optional[str] = None
|
| 272 |
+
# Nuevos campos
|
| 273 |
+
fecha_nacimiento: Optional[str] = None
|
| 274 |
+
lugar_nacimiento: Optional[str] = None
|
| 275 |
+
estado_civil: Optional[str] = None # "Soltero/a", "Casado/a", "Divorciado/a", "Viudo/a"
|
| 276 |
+
fecha_matrimonio: Optional[str] = None
|
| 277 |
+
conyuge_nombre: Optional[str] = None
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
# ββ BoletΓn Oficial βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 281 |
+
class PublicacionBO(BaseModel):
|
| 282 |
+
fecha: Optional[str] = None
|
| 283 |
+
seccion: Optional[str] = None
|
| 284 |
+
rubro: Optional[str] = None
|
| 285 |
+
texto: Optional[str] = None
|
| 286 |
+
numero_boletin: Optional[str] = None
|
| 287 |
+
url: Optional[str] = None
|
| 288 |
+
fuente: Optional[str] = None
|
| 289 |
+
tipo: Optional[str] = None
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
# ββ Otros βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 293 |
+
class MarcaINPI(BaseModel):
|
| 294 |
+
denominacion: Optional[str] = None
|
| 295 |
+
clase: Optional[str] = None
|
| 296 |
+
estado: Optional[str] = None
|
| 297 |
+
fecha_solicitud: Optional[str] = None
|
| 298 |
+
acta: Optional[str] = None
|
| 299 |
+
titulares: Optional[str] = None
|
| 300 |
+
tipo_marca: Optional[str] = None
|
| 301 |
+
numero_resolucion: Optional[str] = None
|
| 302 |
+
fecha_vencimiento: Optional[str] = None
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
class InfraccionTransito(BaseModel):
|
| 306 |
+
fecha: Optional[str] = None
|
| 307 |
+
tipo: Optional[str] = None
|
| 308 |
+
motivo: Optional[str] = None # alias legible del tipo
|
| 309 |
+
organismo: Optional[str] = None
|
| 310 |
+
jurisdiccion: Optional[str] = None # requerido por frontend
|
| 311 |
+
monto: Optional[float] = None
|
| 312 |
+
estado: Optional[str] = None
|
| 313 |
+
acta: Optional[str] = None
|
| 314 |
+
dominio: Optional[str] = None
|
| 315 |
+
nro_causa: Optional[str] = None
|
| 316 |
+
vencimiento: Optional[str] = None
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
class RedSocial(BaseModel):
|
| 320 |
+
plataforma: str # LinkedIn | Facebook | Instagram | Twitter
|
| 321 |
+
usuario: str
|
| 322 |
+
url: Optional[str] = None
|
| 323 |
+
seguidores: Optional[str] = None
|
| 324 |
+
snippet: Optional[str] = None # Extracto del resultado de bΓΊsqueda (Google/DDG)
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
class Vinculo(BaseModel):
|
| 328 |
+
cuit: Optional[str] = None
|
| 329 |
+
nombre: Optional[str] = None
|
| 330 |
+
tipo: Optional[str] = None # Familiar | Societario | Domiciliario
|
| 331 |
+
detalle: Optional[str] = None
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
class DatosPrevisionales(BaseModel):
|
| 335 |
+
tiene_aportes: bool = False
|
| 336 |
+
aportes_al_dia: Optional[bool] = None # None = no consultado
|
| 337 |
+
ultimo_empleador: Optional[str] = None
|
| 338 |
+
obra_social: Optional[str] = None
|
| 339 |
+
fecha_alta_obra_social: Optional[str] = None # Fecha de alta en la obra social
|
| 340 |
+
tipo_beneficiario: Optional[str] = None # Dependiente / AutΓ³nomo / Monotributista
|
| 341 |
+
estado_padron: Optional[str] = None
|
| 342 |
+
beneficios_sociales: list[str] = [] # AUH, Progresar, etc.
|
| 343 |
+
jubilaciones_pensiones: list[str] = []
|
| 344 |
+
fecha_proximo_cobro: Optional[str] = None
|
| 345 |
+
lugar_cobro: Optional[str] = None
|
| 346 |
+
historial_coberturas: list[dict] = []
|
| 347 |
+
diagnostics: dict = {}
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
class IndicadorRiesgo(BaseModel):
|
| 351 |
+
codigo: str
|
| 352 |
+
nivel: str # Bajo | Medio | Alto | Critico
|
| 353 |
+
descripcion: str
|
| 354 |
+
hallazgo: Optional[str] = None
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
class ScoreHistorial(BaseModel):
|
| 358 |
+
fecha: str
|
| 359 |
+
valor: int
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
class ScoreCrediticio(BaseModel):
|
| 363 |
+
valor: int # 1-100
|
| 364 |
+
nivel: str # Excelente / Bueno / Regular / Malo / CrΓtico
|
| 365 |
+
historial: list[ScoreHistorial] = []
|
| 366 |
+
factores: list[str] = [] # ["BCRA sit=5 (Irrecuperable) -40pts", ...]
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
# ββ Meta del informe ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 370 |
+
class ReportMeta(BaseModel):
|
| 371 |
+
report_id: str
|
| 372 |
+
generated_at: str
|
| 373 |
+
sources: list[str] = []
|
| 374 |
+
failures: list[str] = []
|
| 375 |
+
empty_sources: list[str] = []
|
| 376 |
+
diagnostics: dict = {}
|
| 377 |
+
cached: bool = False
|
| 378 |
+
version: str = "1.0"
|
| 379 |
+
action: Optional[str] = None
|
| 380 |
+
message: Optional[str] = None
|
| 381 |
+
sample_sent_to: Optional[str] = None
|
| 382 |
+
|
| 383 |
+
class ViasSalud(BaseModel):
|
| 384 |
+
cuit_consultado: Optional[str] = None
|
| 385 |
+
cobertura_activa: bool = False
|
| 386 |
+
detalles: dict = {}
|
| 387 |
+
|
| 388 |
+
class ContratoEstado(BaseModel):
|
| 389 |
+
cuit_proveedor: Optional[str] = None
|
| 390 |
+
razon_social: Optional[str] = None
|
| 391 |
+
estado_inscripcion: Optional[str] = None
|
| 392 |
+
rubro_principal: Optional[str] = None
|
| 393 |
+
registro: Optional[str] = None
|
| 394 |
+
adjudicaciones: list[dict] = []
|
| 395 |
+
contratos: list[dict] = []
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
class TimelineEvent(BaseModel):
|
| 400 |
+
fecha: str # YYYY-MM-DD o YYYY-MM
|
| 401 |
+
titulo: str # "Monotributo Cat. C"
|
| 402 |
+
descripcion: Optional[str] = None # "Alta en monotributo categorΓa C"
|
| 403 |
+
categoria: str = "general" # fiscal | financiero | judicial | previsional | patrimonial | general
|
| 404 |
+
icono: Optional[str] = None # emoji override
|
| 405 |
+
color: Optional[str] = None # green | yellow | red | blue | gray
|
| 406 |
+
fuente: Optional[str] = None # ARCA, BCRA, BO, etc.
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 410 |
+
# DEUDORES ALIMENTARIOS
|
| 411 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 412 |
+
class DeudorAlimentario(BaseModel):
|
| 413 |
+
dni: Optional[str] = None
|
| 414 |
+
nombre_completo: Optional[str] = None
|
| 415 |
+
sexo: Optional[str] = None
|
| 416 |
+
resultado: Optional[str] = None # CERTIFICADO_GENERADO, REQUERIR_NOMBRE, NO_REGISTRADO
|
| 417 |
+
renaper_validado: bool = False
|
| 418 |
+
pdf_path: Optional[str] = None
|
| 419 |
+
mensaje: Optional[str] = None
|
| 420 |
+
fuente: Optional[str] = None
|
| 421 |
+
url: Optional[str] = None
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 425 |
+
# WEB HISTORIAL (ARCHIVE.ORG)
|
| 426 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 427 |
+
class WebHistorialItem(BaseModel):
|
| 428 |
+
timestamp: Optional[str] = None
|
| 429 |
+
fecha: Optional[str] = None
|
| 430 |
+
status: Optional[str] = None
|
| 431 |
+
tipo: Optional[str] = None
|
| 432 |
+
url_wayback: Optional[str] = None
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
class WebHistorial(BaseModel):
|
| 436 |
+
url_consultada: Optional[str] = None
|
| 437 |
+
snapshot_mas_cercano: Optional[dict] = None
|
| 438 |
+
historial: list[WebHistorialItem] = []
|
| 439 |
+
total_snapshots: int = 0
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 443 |
+
# PERSONA FΓSICA
|
| 444 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 445 |
+
class PersonReport(BaseModel):
|
| 446 |
+
meta: ReportMeta
|
| 447 |
+
score: Optional[ScoreCrediticio] = None
|
| 448 |
+
identificacion: Identificacion = Field(default_factory=Identificacion)
|
| 449 |
+
contacto: Contacto = Field(default_factory=Contacto)
|
| 450 |
+
fiscal: DatosFiscales = Field(default_factory=DatosFiscales)
|
| 451 |
+
financiero: DatosFinancieros = Field(default_factory=DatosFinancieros)
|
| 452 |
+
societario: DatosSocietarios = Field(default_factory=DatosSocietarios)
|
| 453 |
+
patrimonial: DatosPatrimoniales = Field(default_factory=DatosPatrimoniales)
|
| 454 |
+
judicial: DatosJudiciales = Field(default_factory=DatosJudiciales)
|
| 455 |
+
boletin_oficial: list[PublicacionBO] = []
|
| 456 |
+
infracciones_transito: list[InfraccionTransito] = []
|
| 457 |
+
previsional: DatosPrevisionales = Field(default_factory=DatosPrevisionales)
|
| 458 |
+
vinculos: list[Vinculo] = []
|
| 459 |
+
riesgo: list[IndicadorRiesgo] = []
|
| 460 |
+
billeteras_virtuales: DatosBilleterasVirtuales = Field(default_factory=DatosBilleterasVirtuales)
|
| 461 |
+
registro_civil: DatosRegistroCivil = Field(default_factory=DatosRegistroCivil)
|
| 462 |
+
academico: DatosAcademicos = Field(default_factory=DatosAcademicos)
|
| 463 |
+
historial_domicilios: list[Domicilio] = []
|
| 464 |
+
telefonos: list[str] = []
|
| 465 |
+
redes_sociales: list[RedSocial] = []
|
| 466 |
+
salud: Optional[ViasSalud] = None
|
| 467 |
+
compras_estatales: list[ContratoEstado] = []
|
| 468 |
+
peps: list[dict] = []
|
| 469 |
+
marcas_inpi: list[MarcaINPI] = []
|
| 470 |
+
timeline: list[TimelineEvent] = []
|
| 471 |
+
deudores_alimentarios: Optional[DeudorAlimentario] = None
|
| 472 |
+
web_historial: Optional[WebHistorial] = None
|
| 473 |
+
monotributo_historial: Optional[dict] = None
|
| 474 |
+
registro_conductores: Optional[dict] = None
|
| 475 |
+
|
| 476 |
+
@field_validator('timeline', mode='before')
|
| 477 |
+
@classmethod
|
| 478 |
+
def coerce_timeline(cls, v):
|
| 479 |
+
return v if v is not None else []
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 483 |
+
# PERSONA JURΓDICA / EMPRESA
|
| 484 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 485 |
+
class SocioDirectivo(BaseModel):
|
| 486 |
+
cuil: Optional[str] = None
|
| 487 |
+
nombre: Optional[str] = None
|
| 488 |
+
rol: Optional[str] = None
|
| 489 |
+
participacion_pct: Optional[float] = None
|
| 490 |
+
fecha_designacion: Optional[str] = None
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
class Balance(BaseModel):
|
| 494 |
+
periodo: Optional[str] = None
|
| 495 |
+
fecha_presentacion: Optional[str] = None
|
| 496 |
+
resultado: Optional[float] = None
|
| 497 |
+
tipo: Optional[str] = None
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
class DatosIGJ(BaseModel):
|
| 501 |
+
numero_inscripcion: Optional[str] = None
|
| 502 |
+
razon_social: Optional[str] = None
|
| 503 |
+
tipo_societario: Optional[str] = None
|
| 504 |
+
estado: Optional[str] = None
|
| 505 |
+
objeto_social: Optional[str] = None
|
| 506 |
+
socios_directivos: list[SocioDirectivo] = []
|
| 507 |
+
balances: list[Balance] = []
|
| 508 |
+
domicilio_legal: Optional[Domicilio] = None
|
| 509 |
+
pagina_web: Optional[str] = None
|
| 510 |
+
email_contacto: Optional[str] = None
|
| 511 |
+
linkedin: Optional[str] = None
|
| 512 |
+
contacto_raw: Optional[str] = None
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
class CompanyIdentificacion(BaseModel):
|
| 516 |
+
cuit: Optional[str] = None
|
| 517 |
+
razon_social: Optional[str] = None
|
| 518 |
+
nombre_fantasia: Optional[str] = None
|
| 519 |
+
tipo_societario: Optional[str] = None
|
| 520 |
+
fecha_constitucion: Optional[str] = None
|
| 521 |
+
pagina_web: Optional[str] = None
|
| 522 |
+
telefono: Optional[str] = None
|
| 523 |
+
email_contacto: Optional[str] = None
|
| 524 |
+
linkedin: Optional[str] = None
|
| 525 |
+
contacto_institucional: Optional[str] = None
|
| 526 |
+
fecha_constitucion: Optional[str] = None
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
class CompanyReport(BaseModel):
|
| 530 |
+
meta: ReportMeta
|
| 531 |
+
score: Optional[ScoreCrediticio] = None
|
| 532 |
+
identificacion: CompanyIdentificacion = Field(default_factory=CompanyIdentificacion)
|
| 533 |
+
igj: DatosIGJ = Field(default_factory=DatosIGJ)
|
| 534 |
+
fiscal: DatosFiscales = Field(default_factory=DatosFiscales)
|
| 535 |
+
financiero: DatosFinancieros = Field(default_factory=DatosFinancieros)
|
| 536 |
+
patrimonial: DatosPatrimoniales = Field(default_factory=DatosPatrimoniales)
|
| 537 |
+
judicial: DatosJudiciales = Field(default_factory=DatosJudiciales)
|
| 538 |
+
boletin_oficial: list[PublicacionBO] = []
|
| 539 |
+
marcas_inpi: list[MarcaINPI] = []
|
| 540 |
+
vinculos: list[Vinculo] = []
|
| 541 |
+
riesgo: list[IndicadorRiesgo] = []
|
| 542 |
+
compras_estatales: list[ContratoEstado] = []
|
| 543 |
+
peps: list[dict] = []
|
| 544 |
+
cnv_registros: list[dict] = []
|
| 545 |
+
web_historial: Optional[WebHistorial] = None
|
| 546 |
+
timeline: list[TimelineEvent] = []
|
| 547 |
+
|
| 548 |
+
|
| 549 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 550 |
+
# VEHΓCULO
|
| 551 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 552 |
+
class VehicleReport(BaseModel):
|
| 553 |
+
meta: ReportMeta
|
| 554 |
+
vehiculo: Vehiculo = Field(default_factory=Vehiculo)
|
| 555 |
+
titular: Optional[Identificacion] = None
|
| 556 |
+
prendas: list[dict] = []
|
| 557 |
+
denuncias_robo: list[dict] = []
|
| 558 |
+
infracciones: list[InfraccionTransito] = []
|
| 559 |
+
riesgo: list[IndicadorRiesgo] = []
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
class PropertyReport(BaseModel):
|
| 563 |
+
meta: ReportMeta
|
| 564 |
+
direccion: str
|
| 565 |
+
geolocalizacion: Optional[dict] = None
|
| 566 |
+
datos_catastrales: Optional[dict] = None
|
| 567 |
+
titulares_detectados: list[Identificacion] = []
|
| 568 |
+
historial_boletin: list[PublicacionBO] = []
|
| 569 |
+
riesgo: list[IndicadorRiesgo] = []
|
| 570 |
+
valor_estimado: Optional[float] = None
|
| 571 |
+
deudas_impositivas: Optional[dict] = None
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
# ββ Inputs de bΓΊsqueda ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 575 |
+
class SearchPersonaRequest(BaseModel):
|
| 576 |
+
cuit: Optional[str] = None
|
| 577 |
+
dni: Optional[str] = None
|
| 578 |
+
apellido: Optional[str] = None
|
| 579 |
+
nombres: Optional[str] = None
|
| 580 |
+
provincia: Optional[str] = None
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
class SearchEmpresaRequest(BaseModel):
|
| 584 |
+
cuit: Optional[str] = None
|
| 585 |
+
razon_social: Optional[str] = None
|
| 586 |
+
provincia: Optional[str] = None
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
class SearchVehiculoRequest(BaseModel):
|
| 590 |
+
dominio: str
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
class SearchPropertyRequest(BaseModel):
|
| 594 |
+
calle: str
|
| 595 |
+
numero: str
|
| 596 |
+
localidad: str
|
| 597 |
+
provincia: str = ""
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
class GroupReport(BaseModel):
|
| 601 |
+
meta: ReportMeta
|
| 602 |
+
score: Optional[ScoreCrediticio] = None
|
| 603 |
+
target: PersonReport
|
| 604 |
+
empresas_vinculadas: list[CompanyReport] = []
|
| 605 |
+
vehiculos_vinculados: list[VehicleReport] = []
|
| 606 |
+
total_patrimonio_estimado: Optional[float] = None
|
| 607 |
+
riesgo_consolidado: list[IndicadorRiesgo] = []
|
| 608 |
+
|
| 609 |
+
|
| 610 |
+
class HistoryItem(BaseModel):
|
| 611 |
+
id: int
|
| 612 |
+
identifier: str
|
| 613 |
+
type: str
|
| 614 |
+
name: Optional[str] = None
|
| 615 |
+
created_at: datetime
|
| 616 |
+
report_id: Optional[str] = None
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
class MonitorItem(BaseModel):
|
| 620 |
+
id: int
|
| 621 |
+
identifier: str
|
| 622 |
+
type: str
|
| 623 |
+
active: bool
|
| 624 |
+
alert_count: int
|
| 625 |
+
last_check: datetime
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
|
| 629 |
+
|