diff --git a/.coveragerc b/.coveragerc
index dc1fc53f1..e1e47d277 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -6,7 +6,7 @@ omit =
*nsis*
*/padron.py
*pyemail*
- *pyfepdf*
+ *formatos*
*pyi25*
*pyqr*
*rece1*
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index a93e4d282..9291731aa 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -46,6 +46,9 @@ jobs:
- name: Fix OpenSSL "dh key too small"
run: |
sudo cp .github/openssl.cnf /etc/ssl/openssl.cnf
+ - name: Copy rece.ini file
+ run: |
+ sudo cp conf/rece.ini rece.ini
- name: Test with pytest
run: |
pytest --html=report.html --self-contained-html
diff --git a/conf/rece.ini b/conf/rece.ini
index bcb197368..c2df91123 100644
--- a/conf/rece.ini
+++ b/conf/rece.ini
@@ -1,8 +1,8 @@
-# EJEMPLO de archivo de configuraci髇 de la interfaz PyAfipWs
+# EJEMPLO de archivo de configuraci贸n de la interfaz PyAfipWs
# DEBE CAMBIAR Certificado (CERT) y Clave Privada (PRIVATEKEY)
-# Para producci髇 debe descomentar las URL (sacar ##)
-# M醩 informaci髇:
-# http://www.sistemasagiles.com.ar/trac/wiki/ManualPyAfipWs#Configuraci髇
+# Para producci贸n debe descomentar las URL (sacar ##)
+# M谩s informaci贸n:
+# http://www.sistemasagiles.com.ar/trac/wiki/ManualPyAfipWs#Configuraci贸n
[WSAA]
CERT=reingart.crt
@@ -78,7 +78,7 @@ LOCALE=Spanish_Argentina.1252
FMT_CANTIDAD=0.4
FMT_PRECIO=0.3
CANT_POS=izq
-ENTRADA=factura.txt
+ENTRADA=facturas.txt
SALIDA=factura.pdf
[PDF]
diff --git a/formatos/__init__.py b/formatos/__init__.py
new file mode 100644
index 000000000..fac38cef1
--- /dev/null
+++ b/formatos/__init__.py
@@ -0,0 +1,17 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+
+"""M贸dulo para acceder a web services de la afip
+"""
+__author__ = "Mariano Reingart (mariano@gmail.com)"
+__copyright__ = "Copyright (C) 2008-2021 Mariano Reingart"
+__license__ = "LGPL-3.0-or-later"
diff --git a/formatos/formato_json.py b/formatos/formato_json.py
index d8ab08940..e5b020a14 100644
--- a/formatos/formato_json.py
+++ b/formatos/formato_json.py
@@ -36,7 +36,7 @@ def leer(fn="entrada.json"):
return regs
-def escribir(filas, fn="salida.json"):
+def escribir(filas, fn="salida.json", **kwargs):
"Dado una lista de comprobantes (diccionarios), escribe JSON"
import codecs
@@ -46,6 +46,6 @@ def escribir(filas, fn="salida.json"):
jsonfile,
sort_keys=True,
indent=4,
- encoding="utf-8",
+ **kwargs
)
jsonfile.close()
diff --git a/formatos/formato_txt.py b/formatos/formato_txt.py
index c4cc1c858..4217b9ad6 100644
--- a/formatos/formato_txt.py
+++ b/formatos/formato_txt.py
@@ -21,6 +21,7 @@
__license__ = "LGPL-3.0-or-later"
from decimal import Decimal
+import sys
CHARSET = "latin1"
@@ -117,7 +118,7 @@
("imp_iva", 15, I),
("despacho", 20, A),
("u_mtx", 10, N),
- ("cod_mtx", 30, A),
+ ("cod_mtx", 30, N),
("dato_a", 15, A),
("dato_b", 15, A),
("dato_c", 15, A),
@@ -229,8 +230,9 @@ def escribir_linea_txt(dic, formato):
valor = dic.get(clave, "")
if not isinstance(valor, basestring):
valor = str(valor)
- if isinstance(valor, str):
- valor = valor.encode(CHARSET, "replace")
+ if sys.version_info[0] < 3 :
+ if isinstance(valor, str):
+ valor = valor.encode(CHARSET, "replace")
if valor == "None":
valor = ""
if tipo == N and valor and valor != "NULL":
@@ -257,7 +259,7 @@ def escribir_linea_txt(dic, formato):
def leer(fn="entrada.txt"):
"Analiza un archivo TXT y devuelve un diccionario"
- f_entrada = open(fn, "r")
+ f_entrada = open(fn, "rb")
try:
regs = []
reg = None
diff --git a/pyfepdf.py b/pyfepdf.py
index 3899cb019..00e94f3df 100644
--- a/pyfepdf.py
+++ b/pyfepdf.py
@@ -1651,7 +1651,7 @@ def GenerarPDF(self, archivo=""):
@utils.inicializar_y_capturar_excepciones_simple
def MostrarPDF(self, archivo, imprimir=False):
- if sys.platform.startswith(("linux2", "java")):
+ if sys.platform.startswith(("linux2", "java", "linux")):
os.system("evince " "%s" "" % archivo)
else:
operation = imprimir and "print" or ""
@@ -2040,7 +2040,10 @@ def main():
archivo = conf_fact.get("entrada", "entrada.txt")
if DEBUG:
print("Escribiendo", archivo)
- regs = formato_json.escribir([reg], archivo)
+ if sys.version_info[0] < 3:
+ regs = formato_json.escribir([reg], archivo, encoding='utf-8')
+ else:
+ regs = formato_json.escribir([reg], archivo)
else:
from .formatos import formato_txt
@@ -2096,5 +2099,7 @@ def main():
if "--mostrar" in sys.argv:
fepdf.MostrarPDF(archivo=salida, imprimir="--imprimir" in sys.argv)
+ return fepdf
+
if __name__ == "__main__":
main()
\ No newline at end of file
diff --git a/pyqr.py b/pyqr.py
index 31e9f8fed..8b0d2bc6e 100644
--- a/pyqr.py
+++ b/pyqr.py
@@ -120,7 +120,7 @@ def GenerarImagen(
# convertir a representaci贸n json y codificar en base64:
datos_cmp_json = json.dumps(datos_cmp)
- url = self.URL % (base64.b64encode(datos_cmp_json))
+ url = self.URL % (base64.b64encode(datos_cmp_json.encode('ascii')).decode('ascii'))
qr = qrcode.QRCode(
version=self.qr_ver,
diff --git a/requirements-dev.txt b/requirements-dev.txt
index cbc3610c8..018acc6ed 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -4,4 +4,6 @@ pytest-html==1.22.1; python_version <= '2.7'
pytest-html==3.1.1; python_version > '3'
pytest-vcr==1.0.2
pytest-cov==2.12.1
-pytest-freezegun==0.4.2
\ No newline at end of file
+pytest-freezegun==0.4.2
+pytest-mock==2.0.0; python_version <= '2.7'
+pytest-mock==3.6.1; python_version > '2.7'
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 3b3ff0986..d5c2387f7 100644
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,7 @@
"(soap, com/dll, pdf, dbf, xml, etc.)"
)
kwargs["package_dir"] = {"pyafipws": "."}
-kwargs["packages"] = ["pyafipws"]
+kwargs["packages"] = ["pyafipws", "pyafipws.formatos"]
opts = {}
data_files = [("pyafipws/plantillas", glob.glob("plantillas/*"))]
diff --git a/tests/facturas.json b/tests/facturas.json
new file mode 100644
index 000000000..ffb73e581
--- /dev/null
+++ b/tests/facturas.json
@@ -0,0 +1,102 @@
+[
+ {
+ "cae": "61233038185853",
+ "cbt_numero": "7",
+ "cbte_nro": "7",
+ "concepto": "1",
+ "condicion_frente_iva": "Exento",
+ "cuit": "20205766",
+ "datos": [
+ {
+ "campo": "domicilio",
+ "pagina": "",
+ "valor": null
+ },
+ {
+ "campo": "nombre",
+ "pagina": "",
+ "valor": null
+ },
+ {
+ "campo": "telefono",
+ "pagina": "",
+ "valor": null
+ },
+ {
+ "campo": "categoria",
+ "pagina": "",
+ "valor": null
+ },
+ {
+ "campo": "localidad",
+ "pagina": "",
+ "valor": null
+ }
+ ],
+ "detalles": [
+ {
+ "codigo": "P1675G",
+ "ds": "PRUEBA ART",
+ "imp_iva": "0.00",
+ "importe": "1076.68",
+ "iva_id": "0",
+ "numero_despacho": "110170P",
+ "precio": "1076.68",
+ "qty": "1.0",
+ "umed": "07"
+ }
+ ],
+ "domicilio_cliente": "Patricia 1 - Cdad de Buenos Aires - 1405 - Capital Federal - Argentina",
+ "email": "mariano@sistemasagiles.com.ar",
+ "fecha_cbte": "20110609",
+ "fecha_serv_desde": "",
+ "fecha_serv_hasta": "",
+ "fecha_venc_pago": "",
+ "fecha_vto": "20110619",
+ "forma_pago": "30 Dias",
+ "id": "1",
+ "id_impositivo": null,
+ "idioma": "1",
+ "imp_iva": "186.86",
+ "imp_neto": "889.82",
+ "imp_op_ex": "0.00",
+ "imp_tot_conc": "0.00",
+ "imp_total": "1085.57",
+ "imp_trib": "8.89",
+ "ivas": [
+ {
+ "base_imp": "889.82",
+ "importe": "186.86",
+ "iva_id": "5"
+ }
+ ],
+ "localidad_cliente": null,
+ "moneda_ctz": "1.000000",
+ "moneda_id": "PES",
+ "motivo": "",
+ "nombre_cliente": "Cliente XXX",
+ "nro_doc": "30500010912",
+ "numero_cliente": "21601192",
+ "numero_cotizacion": "82016336",
+ "numero_orden_compra": "6443",
+ "numero_remito": "00008001",
+ "obs_comerciales": null,
+ "obs_generales": null,
+ "provincia_cliente": null,
+ "punto_vta": "5",
+ "reproceso": "S",
+ "resultado": "A",
+ "telefono_cliente": null,
+ "tipo_cbte": "6",
+ "tipo_doc": "80",
+ "tributos": [
+ {
+ "alic": "1.00",
+ "base_imp": "889.82",
+ "desc": "Impuesto municipal matanza",
+ "importe": "8.89",
+ "tributo_id": "99"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/tests/facturas.txt b/tests/facturas.txt
new file mode 100644
index 000000000..13b8218cf
--- /dev/null
+++ b/tests/facturas.txt
@@ -0,0 +1,17 @@
+0 2021080520400012345678 212Joao Da Silva 8030000000007Rua 76 km 34.5 Alagoas PJ54482221-l 000000000127000000000000003000000000000100000 000000000002000 000000000001000PES0001000000Observaciones Comerciales
texto libre Observaciones Generales
linea2
linea3 30 dias FOB 20210805 20210805202108056112302292585520110320A Factura individual, DocTipo: 80, DocNro 30000000007 no se encuentra registrado en los padrones de AFIP. 000000000000000 Hurlingham Buenos Aires OK 000000000000000 3 000000000021000 20104000
+1P0001 000000000100070000001331000000000013310000005Descripcion del producto P0001Lorem ipsum sit amet Lorem ipsum sit amet Lorem ipsum sit amet Lorem ipsum sit amet Lorem ipsum sit amet Lorem ipsum sit amet Lorem ipsum sit amet Lorem ipsum sit amet Lorem ipsum sit amet Lorem ipsum sit amet 000000000000000 N潞 123456 0000123456000000000000000001234567890123Dato A
+1 99 -000000001210000005Bonificaci贸n/Descuento 10%
+1 00 Descripci贸n Ejemplo
+3091000200001234
+3091000200001235
+3091000200001236
+3005000200001234
+400005000000000100000000000000021000
+400004000000000000000000000000000000
+400006000000000000000000000000000000
+500099Impuesto Municipal Matanza 000000000100000000000000000100000000000001000
+500004Impuestos Internos 000000000000000
+9custom-nro-cli Cod.123 T
+9custom-pedido 1234 T
+9custom-remito 12345 T
+9custom-transporte Camiones Ej. T
diff --git a/tests/test_pyfepdf.py b/tests/test_pyfepdf.py
new file mode 100644
index 000000000..359404b73
--- /dev/null
+++ b/tests/test_pyfepdf.py
@@ -0,0 +1,355 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 3, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+"""Test para FEPDF"""
+
+__author__ = "Mariano Reingart "
+__copyright__ = "Copyright (C) 2010-2019 Mariano Reingart"
+__license__ = "GPL 3.0"
+
+import os
+import sys
+import datetime
+import pytest
+from pyafipws.wsaa import WSAA
+from pyafipws.pyfepdf import FEPDF
+from pyafipws.pyfepdf import main
+from builtins import str
+from pyafipws.utils import SafeConfigParser
+import shutil
+
+
+CERT = "reingart.crt"
+PKEY = "reingart.key"
+CONFIG_FILE = "rece.ini"
+
+
+fepdf = FEPDF()
+
+pytestmark = [pytest.mark.dontusefix]
+shutil.copy("tests/facturas.json", "facturas.json")
+
+
+def test_crear_factura():
+ """Test de creaci贸n de una factura (Interna)."""
+
+ tipo_cbte = 19 if "--expo" in sys.argv else 201
+ punto_vta = 4000
+ fecha = datetime.datetime.now().strftime("%Y%m%d")
+ concepto = 3
+ tipo_doc = 80
+ nro_doc = "30000000007"
+ cbte_nro = 12345678
+ imp_total = "127.00"
+ imp_tot_conc = "3.00"
+ imp_neto = "100.00"
+ imp_iva = "21.00"
+ imp_trib = "1.00"
+ imp_op_ex = "2.00"
+ imp_subtotal = "105.00"
+ fecha_cbte = fecha
+ fecha_venc_pago = fecha
+ # Fechas del per铆odo del servicio facturado (solo si concepto> 1)
+ fecha_serv_desde = fecha
+ fecha_serv_hasta = fecha
+ # campos p/exportaci贸n (ej): DOL para USD, indicando cotizaci贸n:
+ moneda_id = "DOL" if "--expo" in sys.argv else "PES"
+ moneda_ctz = 1 if moneda_id == "PES" else 14.90
+ incoterms = "FOB" # solo exportaci贸n
+ idioma_cbte = 1 # 1: es, 2: en, 3: pt
+
+ # datos adicionales del encabezado:
+ nombre_cliente = "Joao Da Silva"
+ domicilio_cliente = "Rua 76 km 34.5 Alagoas"
+ pais_dst_cmp = 212 # 200: Argentina, ver tabla
+ id_impositivo = "PJ54482221-l" # cat. iva (mercado interno)
+ forma_pago = "30 dias"
+
+ obs_generales = "Observaciones Generales
linea2
linea3"
+ obs_comerciales = "Observaciones Comerciales
texto libre"
+
+ # datos devueltos por el webservice (WSFEv1, WSMTXCA, etc.):
+ motivo_obs = "Factura individual, DocTipo: 80, DocNro 30000000007 no se encuentra registrado en los padrones de AFIP."
+ cae = "61123022925855"
+ fch_venc_cae = "20110320"
+
+ fact = fepdf.CrearFactura(
+ concepto,
+ tipo_doc,
+ nro_doc,
+ tipo_cbte,
+ punto_vta,
+ cbte_nro,
+ imp_total,
+ imp_tot_conc,
+ imp_neto,
+ imp_iva,
+ imp_trib,
+ imp_op_ex,
+ fecha_cbte,
+ fecha_venc_pago,
+ fecha_serv_desde,
+ fecha_serv_hasta,
+ moneda_id,
+ moneda_ctz,
+ cae,
+ fch_venc_cae,
+ id_impositivo,
+ nombre_cliente,
+ domicilio_cliente,
+ pais_dst_cmp,
+ obs_comerciales,
+ obs_generales,
+ forma_pago,
+ incoterms,
+ idioma_cbte,
+ motivo_obs,
+ )
+
+ assert fact == True
+
+
+def test_agregar_detalle_item():
+ """Test de agregando un art铆culo a una factura (interna).."""
+
+ tipo_cbte = 19 if "--expo" in sys.argv else 201
+
+ test_crear_factura()
+
+ # detalle de art铆culos:
+ u_mtx = 123456
+ cod_mtx = 1234567890123
+ codigo = "P0001"
+ ds = "Descripcion del producto P0001\n" + "Lorem ipsum sit amet " * 10
+ qty = 1.00
+ umed = 7
+ if tipo_cbte in (1, 2, 3, 4, 5, 34, 39, 51, 52, 53, 54, 60, 64):
+ # discriminar IVA si es clase A / M
+ precio = 110.00
+ imp_iva = 23.10
+ else:
+ # no discriminar IVA si es clase B (importe final iva incluido)
+ precio = 133.10
+ imp_iva = None
+ bonif = 0.00
+ iva_id = 5
+ importe = 133.10
+ despacho = u"N潞 123456"
+ dato_a = "Dato A"
+ chk1 = fepdf.AgregarDetalleItem(
+ u_mtx,
+ cod_mtx,
+ codigo,
+ ds,
+ qty,
+ umed,
+ precio,
+ bonif,
+ iva_id,
+ imp_iva,
+ importe,
+ despacho,
+ dato_a,
+ )
+
+ # descuento general (a tasa 21%):
+ u_mtx = cod_mtx = codigo = None
+ ds = u"Bonificaci贸n/Descuento 10%"
+ qty = precio = bonif = None
+ umed = 99
+ iva_id = 5
+ if tipo_cbte in (1, 2, 3, 4, 5, 34, 39, 51, 52, 53, 54, 60, 64):
+ # discriminar IVA si es clase A / M
+ imp_iva = -2.21
+ else:
+ imp_iva = None
+ importe = -12.10
+ chk2 = fepdf.AgregarDetalleItem(
+ u_mtx,
+ cod_mtx,
+ codigo,
+ ds,
+ qty,
+ umed,
+ precio,
+ bonif,
+ iva_id,
+ imp_iva,
+ importe,
+ "",
+ )
+
+ # descripci贸n (sin importes ni cantidad):
+ u_mtx = cod_mtx = codigo = None
+ qty = precio = bonif = iva_id = imp_iva = importe = None
+ umed = 0
+ ds = u"Descripci贸n Ejemplo"
+ chk3 = fepdf.AgregarDetalleItem(
+ u_mtx,
+ cod_mtx,
+ codigo,
+ ds,
+ qty,
+ umed,
+ precio,
+ bonif,
+ iva_id,
+ imp_iva,
+ importe,
+ "",
+ )
+
+ assert chk1 == True
+ assert chk2 == True
+ assert chk3 == True
+
+
+def test_agregar_iva():
+ iva_id = 5 # 21%
+ base_imp = 100
+ importe = 21
+ chk = fepdf.AgregarIva(iva_id, base_imp, importe)
+ assert chk == True
+
+
+def test_agregar_tributo():
+ tributo_id = 99
+ desc = "Impuesto Municipal Matanza"
+ base_imp = "100.00"
+ alic = "1.00"
+ importe = "1.00"
+ chk = fepdf.AgregarTributo(tributo_id, desc, base_imp, alic, importe)
+
+ assert chk == True
+
+
+def test_agregar_cmp_asoc():
+ tipo = 5
+ pto_vta = 2
+ nro = 1234
+ chk = fepdf.AgregarCmpAsoc(tipo, pto_vta, nro)
+
+ assert chk == True
+
+
+def test_crear_plantilla():
+ sys.argv = []
+
+ config = SafeConfigParser()
+ config.read(CONFIG_FILE)
+ conf_fact = dict(config.items("FACTURA"))
+
+ fepdf.CrearPlantilla(
+ papel=conf_fact.get("papel", "legal"),
+ orientacion=conf_fact.get("orientacion", "portrait"),
+ )
+
+
+def test_procesar_plantilla():
+ sys.argv = []
+ sys.argv.append("--debug")
+
+ config = SafeConfigParser()
+ config.read(CONFIG_FILE)
+ conf_fact = dict(config.items("FACTURA"))
+
+ chk = fepdf.ProcesarPlantilla(
+ num_copias=int(conf_fact.get("copias", 1)),
+ lineas_max=int(conf_fact.get("lineas_max", 24)),
+ qty_pos=conf_fact.get("cant_pos") or "izq",
+ )
+
+ assert chk == False
+
+
+def test_generar_qr():
+ fepdf.CUIT = "30000000007"
+ url = fepdf.GenerarQR()
+ assert url.startswith("https://www.afip.gob.ar/fe/qr/")
+
+
+def test_main_prueba():
+ sys.argv = []
+ sys.argv.append("--prueba")
+ sys.argv.append("--debug")
+ main()
+
+
+def test_main_cargar():
+ sys.argv = []
+ sys.argv.append("--cargar")
+ sys.argv.append("--entrada")
+ sys.argv.append("tests/facturas.txt")
+ f = main()
+ assert f.factura.get("cbte_nro") == 12345678
+ assert f.factura.get("tipo_cbte") == 201
+ assert f.factura.get("tipo_doc") == 80
+ assert f.factura.get("nro_doc") == 30000000007
+
+
+def test_main_cargar_json():
+ sys.argv = []
+ sys.argv.append("--cargar")
+ sys.argv.append("--json")
+ sys.argv.append("--entrada")
+ sys.argv.append("facturas.json")
+ f = main()
+ assert f.factura.get("cbte_nro") == '7'
+ assert f.factura.get("cbt_numero") == '7'
+ assert f.factura.get("cae") == '61233038185853'
+
+
+def test_main_grabar():
+ sys.argv = []
+ sys.argv.append("--prueba")
+ sys.argv.append("--grabar")
+ # sys.argv.append("--debug")
+ main()
+ #TO-DO : compare the generated facturas.txt with the original file
+ # f1 = open("facturas.txt", "r")
+ # f2 = open("tests/facturas.txt", "r")
+ # d1 = f1.readlines()
+ # d2 = f2.readlines()
+ # f1.close()
+ # f2.close()
+ # diff = [x for x in d1 if x not in d2]
+ # assert diff == []
+
+
+def test_main_grabar_json():
+ sys.argv = []
+ sys.argv.append("--prueba")
+ sys.argv.append("--grabar")
+ sys.argv.append("--json")
+ sys.argv.append("--debug")
+ main()
+ f1 = open("facturas.json", "r")
+ f2 = open("tests/facturas.json", "r")
+ d1 = f1.readlines()
+ d2 = f2.readlines()
+ f1.close()
+ f2.close()
+ diff = [x for x in d1 if x not in d2]
+ assert diff == []
+
+
+def test_mostrar_pdf(mocker):
+ sys.argv = []
+ mocker.patch("os.system")
+ config = SafeConfigParser()
+ config.read(CONFIG_FILE)
+ conf_fact = dict(config.items("FACTURA"))
+
+ salida = conf_fact.get("salida", "")
+ fepdf.MostrarPDF(archivo=salida)
+ if sys.platform.startswith("linux" or "linux2"):
+ os.system.assert_called_with("evince %s" % salida)