l10n-ar-api 2.8.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- l10n_ar_api/__init__.py +7 -0
- l10n_ar_api/afip_webservices/__init__.py +8 -0
- l10n_ar_api/afip_webservices/config.py +19 -0
- l10n_ar_api/afip_webservices/ws_sr_padron/__init__.py +1 -0
- l10n_ar_api/afip_webservices/ws_sr_padron/ws_sr_padron.py +53 -0
- l10n_ar_api/afip_webservices/wsaa/__init__.py +3 -0
- l10n_ar_api/afip_webservices/wsaa/certificate.py +54 -0
- l10n_ar_api/afip_webservices/wsaa/tokens.py +145 -0
- l10n_ar_api/afip_webservices/wsaa/wsaa.py +32 -0
- l10n_ar_api/afip_webservices/wsbfe/__init__.py +3 -0
- l10n_ar_api/afip_webservices/wsbfe/error.py +9 -0
- l10n_ar_api/afip_webservices/wsbfe/invoice.py +118 -0
- l10n_ar_api/afip_webservices/wsbfe/wsbfe.py +328 -0
- l10n_ar_api/afip_webservices/wsct/__init__.py +3 -0
- l10n_ar_api/afip_webservices/wsct/error.py +9 -0
- l10n_ar_api/afip_webservices/wsct/invoice.py +172 -0
- l10n_ar_api/afip_webservices/wsct/wsct.py +465 -0
- l10n_ar_api/afip_webservices/wsfe/__init__.py +4 -0
- l10n_ar_api/afip_webservices/wsfe/error.py +9 -0
- l10n_ar_api/afip_webservices/wsfe/invoice.py +156 -0
- l10n_ar_api/afip_webservices/wsfe/qr_generator.py +30 -0
- l10n_ar_api/afip_webservices/wsfe/wsfe.py +395 -0
- l10n_ar_api/afip_webservices/wsfex/__init__.py +3 -0
- l10n_ar_api/afip_webservices/wsfex/error.py +9 -0
- l10n_ar_api/afip_webservices/wsfex/invoice.py +140 -0
- l10n_ar_api/afip_webservices/wsfex/wsfex.py +343 -0
- l10n_ar_api/afip_webservices/wstesimbrefiscal/__init__.py +2 -0
- l10n_ar_api/afip_webservices/wstesimbrefiscal/error.py +9 -0
- l10n_ar_api/afip_webservices/wstesimbrefiscal/wstesimbrefiscal.py +145 -0
- l10n_ar_api/anmat_webservices/__init__.py +2 -0
- l10n_ar_api/anmat_webservices/anmat.py +129 -0
- l10n_ar_api/anmat_webservices/config.py +4 -0
- l10n_ar_api/arba_webservices/__init__.py +21 -0
- l10n_ar_api/arba_webservices/config.py +4 -0
- l10n_ar_api/arba_webservices/cot/__init__.py +1 -0
- l10n_ar_api/arba_webservices/cot/config.py +38 -0
- l10n_ar_api/arba_webservices/cot/cot.py +635 -0
- l10n_ar_api/arba_webservices/iibb.py +56 -0
- l10n_ar_api/documents/__init__.py +4 -0
- l10n_ar_api/documents/invoice.py +22 -0
- l10n_ar_api/documents/tax.py +22 -0
- l10n_ar_api/documents/tests/__init__.py +3 -0
- l10n_ar_api/documents/tests/test_gross_income.py +41 -0
- l10n_ar_api/documents/tests/test_profit.py +61 -0
- l10n_ar_api/documents/tests/test_tribute.py +11 -0
- l10n_ar_api/documents/tribute.py +111 -0
- l10n_ar_api/padron/__init__.py +2 -0
- l10n_ar_api/padron/banks.py +164 -0
- l10n_ar_api/padron/config.py +1 -0
- l10n_ar_api/padron/contributor.py +35 -0
- l10n_ar_api/padron/test/__init__.py +2 -0
- l10n_ar_api/padron/test/test_banks.py +30 -0
- l10n_ar_api/padron/test/test_contributors.py +30 -0
- l10n_ar_api/presentations/__init__.py +2 -0
- l10n_ar_api/presentations/presentation.py +79 -0
- l10n_ar_api/presentations/presentation_line.py +4396 -0
- l10n_ar_api/presentations/test/__init__.py +4 -0
- l10n_ar_api/presentations/test/test_purchases_sales_presentation.py +196 -0
- l10n_ar_api/presentations/test/test_purchases_sales_presentation_line.py +183 -0
- l10n_ar_api/presentations/test/test_purchases_sales_vat_digital_book.py +173 -0
- l10n_ar_api/presentations/test/test_purchases_sales_vat_digital_book_line.py +161 -0
- l10n_ar_api-2.8.3.dist-info/METADATA +27 -0
- l10n_ar_api-2.8.3.dist-info/RECORD +65 -0
- l10n_ar_api-2.8.3.dist-info/WHEEL +5 -0
- l10n_ar_api-2.8.3.dist-info/top_level.txt +1 -0
l10n_ar_api/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
authorization_urls = dict(
|
|
2
|
+
homologation='https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl',
|
|
3
|
+
production='https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl',
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
service_urls = dict(
|
|
7
|
+
wstesimbrefiscal_homologation='https://wsaduhomoext.afip.gob.ar/diav2/wgestimbrefiscalelectronico/wgestimbrefiscalelectronico.asmx?WSDL',
|
|
8
|
+
wstesimbrefiscal_production='https://wswadu.afip.gob.ar/DIAV2/wgestimbrefiscalelectronico/wgestimbrefiscalelectronico.asmx?WSDL',
|
|
9
|
+
wsct_homologation='https://fwshomo.afip.gov.ar/wsct/CTService?WSDL',
|
|
10
|
+
wsct_production='https://serviciosjava.afip.gob.ar/wsct/CTService?WSDL',
|
|
11
|
+
wsbfev1_homologation='https://wswhomo.afip.gov.ar/wsbfev1/service.asmx?WSDL',
|
|
12
|
+
wsbfev1_production='https://servicios1.afip.gov.ar/wsbfev1/service.asmx?WSDL',
|
|
13
|
+
wsfev1_homologation='https://wswhomo.afip.gov.ar/wsfev1/service.asmx?wsdl',
|
|
14
|
+
wsfev1_production='https://servicios1.afip.gov.ar/wsfev1/service.asmx?wsdl',
|
|
15
|
+
wsfexv1_homologation='https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?wsdl',
|
|
16
|
+
wsfexv1_production='https://servicios1.afip.gov.ar/wsfexv1/service.asmx?wsdl',
|
|
17
|
+
ws_sr_padron_a5_homologation='https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA5?WSDL',
|
|
18
|
+
ws_sr_padron_a5_production='https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA5?WSDL',
|
|
19
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import ws_sr_padron
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Segun RG 2485 – Proyecto FE v2.8 - 12/09/2016
|
|
3
|
+
import sys
|
|
4
|
+
sys.path.append("..")
|
|
5
|
+
from zeep import Client
|
|
6
|
+
from .. import config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InvalidIdException(Exception):
|
|
10
|
+
def __init__(self, msg):
|
|
11
|
+
super(Exception, self).__init__(msg)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WsSrPadron(object):
|
|
15
|
+
"""
|
|
16
|
+
Consulta Padron A5.
|
|
17
|
+
|
|
18
|
+
:param access_token: AccessToken - Token de acceso
|
|
19
|
+
:param cuit: Cuit de la empresa
|
|
20
|
+
:param homologation: Homologacion si es True
|
|
21
|
+
:param url: Url de servicios para WsSrPadron
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, access_token, cuit, homologation=True, url=None):
|
|
25
|
+
if not url:
|
|
26
|
+
self.url = config.service_urls.get('ws_sr_padron_a5_homologation') if homologation\
|
|
27
|
+
else config.service_urls.get('ws_sr_padron_a5_production')
|
|
28
|
+
else:
|
|
29
|
+
self.url = url
|
|
30
|
+
|
|
31
|
+
self.accessToken = access_token
|
|
32
|
+
self.cuit = cuit
|
|
33
|
+
|
|
34
|
+
def get_partner_data(self, vat):
|
|
35
|
+
"""
|
|
36
|
+
:param vat: Numero de documentos a consultar
|
|
37
|
+
:returns str: Respuesta de AFIP sobre el numero de documento
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = Client(self.url).service.getPersona(
|
|
42
|
+
token=self.accessToken.token,
|
|
43
|
+
sign=self.accessToken.sign,
|
|
44
|
+
cuitRepresentada=self.cuit,
|
|
45
|
+
idPersona=vat
|
|
46
|
+
)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
if e.args[0] == 'No existe persona con ese Id':
|
|
49
|
+
raise InvalidIdException("El numero de documento {} es inexistente.".format(vat))
|
|
50
|
+
if e.args[0] == 'El Id de la persona no es valido':
|
|
51
|
+
raise InvalidIdException("El numero de documento {} no es valido.".format(vat))
|
|
52
|
+
raise Exception(e.args[0])
|
|
53
|
+
return response
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from OpenSSL import crypto
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WsaaCertificate(object):
|
|
5
|
+
|
|
6
|
+
def __init__(self, key):
|
|
7
|
+
self.key = key
|
|
8
|
+
self.country_code = None
|
|
9
|
+
self.state_name = None
|
|
10
|
+
self.company_name = None
|
|
11
|
+
self.company_vat = None
|
|
12
|
+
|
|
13
|
+
def validate_values(self):
|
|
14
|
+
""" Validamos que esten todos los campos necesarios seteados """
|
|
15
|
+
values = vars(self)
|
|
16
|
+
for value in values:
|
|
17
|
+
if not values.get(value):
|
|
18
|
+
raise AttributeError('Falta configurar alguno de los siguientes campos:\n'
|
|
19
|
+
'Codigo de pais, Provincia, Nombre de la empresa o CUIT')
|
|
20
|
+
|
|
21
|
+
def generate_certificate_request(self, hash_sign='sha256', ou='odoo'):
|
|
22
|
+
|
|
23
|
+
self.validate_values()
|
|
24
|
+
|
|
25
|
+
# Utilizamos la libreria de crypto para generar el pedido de certificado
|
|
26
|
+
req = crypto.X509Req()
|
|
27
|
+
req.get_subject().C = self.country_code
|
|
28
|
+
req.get_subject().ST = self.state_name
|
|
29
|
+
req.get_subject().O = self.company_name
|
|
30
|
+
req.get_subject().OU = ou
|
|
31
|
+
req.get_subject().CN = self.company_name
|
|
32
|
+
req.get_subject().serialNumber = 'CUIT {}'.format(self.company_vat)
|
|
33
|
+
|
|
34
|
+
# Validamos el formato de la key
|
|
35
|
+
key = crypto.load_privatekey(crypto.FILETYPE_PEM, self.key)
|
|
36
|
+
self.key = crypto.dump_privatekey(crypto.FILETYPE_PEM, key)
|
|
37
|
+
|
|
38
|
+
# Firmamos con la key y el hash el certificado
|
|
39
|
+
req.set_pubkey(key)
|
|
40
|
+
req.sign(key, hash_sign)
|
|
41
|
+
|
|
42
|
+
return crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class WsaaPrivateKey(object):
|
|
46
|
+
|
|
47
|
+
def __init__(self, length=2048):
|
|
48
|
+
self.length = length
|
|
49
|
+
self.key = None
|
|
50
|
+
|
|
51
|
+
def generate_rsa_key(self):
|
|
52
|
+
pkey = crypto.PKey()
|
|
53
|
+
pkey.generate_key(crypto.TYPE_RSA, self.length)
|
|
54
|
+
self.key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import pytz
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import time
|
|
5
|
+
from lxml import etree
|
|
6
|
+
from M2Crypto import BIO, SMIME
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AccessRequerimentToken(object):
|
|
11
|
+
|
|
12
|
+
def __init__(self, service, tz=pytz.utc):
|
|
13
|
+
self._service = service
|
|
14
|
+
self._timezone = tz
|
|
15
|
+
|
|
16
|
+
def _create_tra(self):
|
|
17
|
+
"""
|
|
18
|
+
uniqueId: Entero de 32 bits sin signo que junto con generationTime identifica el
|
|
19
|
+
requerimiento.
|
|
20
|
+
|
|
21
|
+
generationTime: Momento en que fue generado el requerimiento. La tolerancia de aceptacion
|
|
22
|
+
sera de hasta 24 horas previas al requerimiento de acceso
|
|
23
|
+
|
|
24
|
+
expirationTime: Momento en el que expira la solicitud. La tolerancia de aceptacion sera de
|
|
25
|
+
hasta 24 horas posteriores al requerimiento de acceso.
|
|
26
|
+
|
|
27
|
+
service: Identificacion del WSN para el cual se solicita el TA.
|
|
28
|
+
|
|
29
|
+
:return: Ticket de Requerimiento de Acceso
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
ticket_request = etree.Element('loginTicketRequest')
|
|
33
|
+
ticket_request.set('version', '1.0')
|
|
34
|
+
header = etree.SubElement(ticket_request, 'header')
|
|
35
|
+
|
|
36
|
+
# uniqueId
|
|
37
|
+
uniqueid = etree.SubElement(header, 'uniqueId')
|
|
38
|
+
# Traemos la fecha actual
|
|
39
|
+
timestamp = int(time.mktime(datetime.now().timetuple()))
|
|
40
|
+
uniqueid.text = str(timestamp)
|
|
41
|
+
|
|
42
|
+
# generationTime
|
|
43
|
+
# Generamos el token con media hora atrasada
|
|
44
|
+
tsgen = datetime.fromtimestamp(timestamp-1800)
|
|
45
|
+
tsgen = pytz.utc.localize(tsgen).astimezone(self.timezone)
|
|
46
|
+
gentime = etree.SubElement(header, 'generationTime')
|
|
47
|
+
gentime.text = tsgen.isoformat()
|
|
48
|
+
|
|
49
|
+
# expirationTime
|
|
50
|
+
# El token vence 4.5 horas despues
|
|
51
|
+
tsexp = datetime.fromtimestamp(timestamp+14400)
|
|
52
|
+
tsexp = pytz.utc.localize(tsexp).astimezone(self.timezone)
|
|
53
|
+
exptime = etree.SubElement(header, 'expirationTime')
|
|
54
|
+
exptime.text = tsexp.isoformat()
|
|
55
|
+
exptime.tail = None
|
|
56
|
+
|
|
57
|
+
# service
|
|
58
|
+
serv = etree.SubElement(ticket_request, 'service')
|
|
59
|
+
serv.text = self.service
|
|
60
|
+
|
|
61
|
+
return etree.tostring(ticket_request)
|
|
62
|
+
|
|
63
|
+
def sign_tra(self, private_key, certificate):
|
|
64
|
+
|
|
65
|
+
smime = SMIME.SMIME()
|
|
66
|
+
ks = BIO.MemoryBuffer(private_key.encode('ascii'))
|
|
67
|
+
cs = BIO.MemoryBuffer(certificate.encode('ascii'))
|
|
68
|
+
bf = BIO.MemoryBuffer(self._create_tra().decode('ascii').encode('ascii'))
|
|
69
|
+
out = BIO.MemoryBuffer()
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
smime.load_key_bio(ks, cs)
|
|
73
|
+
except Exception:
|
|
74
|
+
raise Exception('Error en el formato del certificado o clave privada')
|
|
75
|
+
|
|
76
|
+
sbf = smime.sign(bf)
|
|
77
|
+
smime.write(out, sbf)
|
|
78
|
+
|
|
79
|
+
head, body, end = out.read().split(b'\n\n')
|
|
80
|
+
return body
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def service(self):
|
|
84
|
+
return self._service
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def timezone(self):
|
|
88
|
+
return self._timezone
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class AccessToken(object):
|
|
92
|
+
|
|
93
|
+
""" Ejemplo de token:
|
|
94
|
+
|
|
95
|
+
<?xml version="1.0" encoding="UTF8"?>
|
|
96
|
+
<loginTicketResponse version="1.0">
|
|
97
|
+
<header>
|
|
98
|
+
<source>cn=wsaa,o=afip,c=ar,serialNumber=CUIT 33693450239</source>
|
|
99
|
+
<destination>cn=srv1,ou=facturacion,o=empresa s.a.,c=ar,serialNumber=CUIT 30123456789</destination>
|
|
100
|
+
<uniqueId>383953094</uniqueId>
|
|
101
|
+
<generationTime>20011231T12:00:0203:00</generationTime>
|
|
102
|
+
<expirationTime>20020101T00:00:0203:00</expirationTime>
|
|
103
|
+
</header>
|
|
104
|
+
<credentials>
|
|
105
|
+
<token>cES0SSuWIIPlfe5/dLtb0Qeg2jQuvYuuSEDOrz+w2EnAQiEeS86gzYf7ehiU3UaYit5FRb9z/3zq</token>
|
|
106
|
+
<sign>a6QSSZBgLf0TTcktSNteeSg3qXsMVjo/F5py/Gtw7xucTrUWbsrVCdIoGE8Cm1bixpuVPlr58k6n</sign>
|
|
107
|
+
</credentials>
|
|
108
|
+
</loginTicketResponse>
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(self):
|
|
112
|
+
# Header
|
|
113
|
+
self.source = None
|
|
114
|
+
self.destination = None
|
|
115
|
+
self.uniqueId = None
|
|
116
|
+
self.generation_time = None
|
|
117
|
+
self.expiration_time = None
|
|
118
|
+
|
|
119
|
+
# Credentials
|
|
120
|
+
self.token = None
|
|
121
|
+
self.sign = None
|
|
122
|
+
|
|
123
|
+
def create_token_from_login(self, login_fault):
|
|
124
|
+
"""
|
|
125
|
+
Asigna los atributos al token desde el login
|
|
126
|
+
:param login_fault: XML con el login autorizado
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
if sys.version_info[0] >= 3:
|
|
130
|
+
login_fault_tree = etree.fromstring(login_fault.encode('utf-8'))
|
|
131
|
+
else:
|
|
132
|
+
login_fault_tree = etree.fromstring(str(login_fault))
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
|
|
136
|
+
self.source = login_fault_tree.find('header/source').text
|
|
137
|
+
self.destination = login_fault_tree.find('header/destination').text
|
|
138
|
+
self.generation_time = login_fault_tree.find('header/generationTime').text
|
|
139
|
+
self.expiration_time = login_fault_tree.find('header/expirationTime').text
|
|
140
|
+
|
|
141
|
+
self.token = login_fault_tree.find('credentials/token').text
|
|
142
|
+
self.sign = login_fault_tree.find('credentials/sign').text
|
|
143
|
+
|
|
144
|
+
except AttributeError:
|
|
145
|
+
raise AttributeError("Error al generar el token de acceso")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from zeep import Client
|
|
2
|
+
from requests import Session
|
|
3
|
+
from zeep.transports import Transport
|
|
4
|
+
|
|
5
|
+
from l10n_ar_api.afip_webservices import config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Wsaa(object):
|
|
9
|
+
|
|
10
|
+
def __init__(self, homologation=True, url=None):
|
|
11
|
+
|
|
12
|
+
if not url:
|
|
13
|
+
self.url = config.authorization_urls.get('homologation') if homologation\
|
|
14
|
+
else config.authorization_urls.get('production')
|
|
15
|
+
else:
|
|
16
|
+
self.url = url
|
|
17
|
+
|
|
18
|
+
def login(self, tra):
|
|
19
|
+
"""
|
|
20
|
+
:param tra: TRA que se usara para el logeo
|
|
21
|
+
:return: XML con el login autorizado
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
session = Session()
|
|
26
|
+
session.verify = False
|
|
27
|
+
transport = Transport(session=session)
|
|
28
|
+
login_fault = Client(self.url, transport=transport).service.loginCms(tra)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
raise Exception("Error al autenticarse\n{}".format(e))
|
|
31
|
+
|
|
32
|
+
return login_fault
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from l10n_ar_api.documents import invoice
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FiscalElectronicBondValidator:
|
|
7
|
+
|
|
8
|
+
@classmethod
|
|
9
|
+
def validate_invoice(cls, invoice):
|
|
10
|
+
'''
|
|
11
|
+
Valida que los campos de la factura cumplan con los requisitos de la AFIP
|
|
12
|
+
:param invoices: Factura a validar, objeto FiscalElectronicBond
|
|
13
|
+
'''
|
|
14
|
+
|
|
15
|
+
cls._validate_document_date(invoice)
|
|
16
|
+
cls._validate_amount(invoice)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def _validate_document_date(invoice):
|
|
20
|
+
assert invoice.document_date, "El documento no tiene fecha"
|
|
21
|
+
|
|
22
|
+
invoice_date = datetime.strptime(invoice.document_date, '%Y%m%d').date()
|
|
23
|
+
|
|
24
|
+
if abs(date.today() - invoice_date).days > 5:
|
|
25
|
+
raise AttributeError("La fecha del documento debe ser\
|
|
26
|
+
mayor o menor a 5 dias de la fecha de generacion")
|
|
27
|
+
|
|
28
|
+
date_today = date.today().replace(day=1)
|
|
29
|
+
new_invoice_date = invoice_date.replace(day=1)
|
|
30
|
+
if date_today != new_invoice_date:
|
|
31
|
+
raise AttributeError("La fecha del documento no debe exceder el mes de la fecha de\
|
|
32
|
+
envío del pedido de autorización")
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def _validate_amount(invoice):
|
|
36
|
+
if invoice.get_total_items_amount() > invoice.get_total_amount():
|
|
37
|
+
raise AttributeError("La Suma de los items de la factura\
|
|
38
|
+
debe ser menor o igual al total de la misma")
|
|
39
|
+
|
|
40
|
+
class FiscalElectronicBondItem(object):
|
|
41
|
+
|
|
42
|
+
def __init__(self, codigo_prod_ncm, description, quantity = None, measurement_unit = None, unit_price = None, bonification = None, iva_id = None):
|
|
43
|
+
self.codigo_prod_ncm = codigo_prod_ncm
|
|
44
|
+
self.description = description
|
|
45
|
+
self.quantity = quantity
|
|
46
|
+
self.measurement_unit = measurement_unit
|
|
47
|
+
self.unit_price = unit_price
|
|
48
|
+
self.bonification = bonification
|
|
49
|
+
self.iva_id = iva_id
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def total_price(self):
|
|
53
|
+
assert self.quantity, "No existe cantidad para el item"
|
|
54
|
+
assert self.unit_price, "No existe precio para el item"
|
|
55
|
+
|
|
56
|
+
return (self.quantity * self.unit_price) - self.bonification
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class FiscalElectronicBond(invoice.Invoice):
|
|
60
|
+
|
|
61
|
+
def __init__(self, document_code):
|
|
62
|
+
self.zone_id = 0
|
|
63
|
+
self.reception_amount = None
|
|
64
|
+
self.municipal_reception_amount = None
|
|
65
|
+
self.array_items = []
|
|
66
|
+
|
|
67
|
+
# Cliente
|
|
68
|
+
self.customer_name = None
|
|
69
|
+
self.customer_address = None
|
|
70
|
+
self.customer_document_type = None
|
|
71
|
+
self.customer_document_number = None
|
|
72
|
+
|
|
73
|
+
# Tributos
|
|
74
|
+
self.total_amount = None
|
|
75
|
+
self.pay_off_tax_amount = None
|
|
76
|
+
self.rni_pay_off_tax_amount = None
|
|
77
|
+
self.internal_tax_amount = None
|
|
78
|
+
self.iibb_amount = None
|
|
79
|
+
|
|
80
|
+
# Moneda
|
|
81
|
+
self.mon_id = None
|
|
82
|
+
self.mon_cotiz = None
|
|
83
|
+
|
|
84
|
+
# Opcionales
|
|
85
|
+
self.array_optionals = []
|
|
86
|
+
|
|
87
|
+
# Comprobantes asociados
|
|
88
|
+
self.associated_documents = []
|
|
89
|
+
|
|
90
|
+
self._payment_due_date = None
|
|
91
|
+
|
|
92
|
+
super(FiscalElectronicBond, self).__init__(document_code)
|
|
93
|
+
|
|
94
|
+
# Override
|
|
95
|
+
def get_total_amount(self):
|
|
96
|
+
return self.total_amount
|
|
97
|
+
|
|
98
|
+
def get_total_items_amount(self):
|
|
99
|
+
return sum(item.total_price for item in self.array_items)
|
|
100
|
+
|
|
101
|
+
def add_item(self, value):
|
|
102
|
+
self.array_items.append(value)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def document_date(self):
|
|
106
|
+
return self._document_date
|
|
107
|
+
|
|
108
|
+
@document_date.setter
|
|
109
|
+
def document_date(self, value):
|
|
110
|
+
self._document_date = value.strftime('%Y%m%d')
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def payment_due_date(self):
|
|
114
|
+
return self._payment_due_date
|
|
115
|
+
|
|
116
|
+
@payment_due_date.setter
|
|
117
|
+
def payment_due_date(self, value):
|
|
118
|
+
self._payment_due_date = value.strftime('%Y%m%d')
|