Cancelar facturas con xml firmado
This commit is contained in:
parent
f958227f56
commit
9e50033b9f
|
@ -371,7 +371,7 @@ class Finkok(object):
|
||||||
if os.path.isfile(file_xml):
|
if os.path.isfile(file_xml):
|
||||||
root = etree.parse(file_xml).getroot()
|
root = etree.parse(file_xml).getroot()
|
||||||
else:
|
else:
|
||||||
root = etree.fromstring(file_xml)
|
root = etree.fromstring(file_xml.encode())
|
||||||
|
|
||||||
xml = etree.tostring(root)
|
xml = etree.tostring(root)
|
||||||
|
|
||||||
|
@ -385,8 +385,12 @@ class Finkok(object):
|
||||||
'store_pending': True,
|
'store_pending': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
result = client.service.cancel_signature(**args)
|
try:
|
||||||
return result
|
result = client.service.cancel_signature(**args)
|
||||||
|
return result
|
||||||
|
except Fault as e:
|
||||||
|
self.error = str(e)
|
||||||
|
return ''
|
||||||
|
|
||||||
def get_acuse(self, rfc, uuids, type_acuse='C'):
|
def get_acuse(self, rfc, uuids, type_acuse='C'):
|
||||||
for u in uuids:
|
for u in uuids:
|
||||||
|
|
|
@ -33,7 +33,8 @@ from dateutil import parser
|
||||||
|
|
||||||
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice
|
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice
|
||||||
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
|
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
|
||||||
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE
|
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
|
||||||
|
PATH_XMLSEC, TEMPLATE_CANCEL
|
||||||
|
|
||||||
|
|
||||||
#~ def _get_hash(password):
|
#~ def _get_hash(password):
|
||||||
|
@ -1048,6 +1049,52 @@ def get_date(value, next_day=False):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def cancel_cfdi(uuid, pk12, rfc, auth):
|
||||||
|
from .pac import Finkok as PAC
|
||||||
|
|
||||||
|
template = read_file(TEMPLATE_CANCEL, 'r')
|
||||||
|
data = {
|
||||||
|
'rfc': rfc,
|
||||||
|
'fecha': datetime.datetime.now().isoformat()[:19],
|
||||||
|
'uuid': str(uuid).upper(),
|
||||||
|
}
|
||||||
|
template = template.format(**data)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'xmlsec': PATH_XMLSEC,
|
||||||
|
'pk12': _save_temp(pk12),
|
||||||
|
'pass': _get_md5(rfc),
|
||||||
|
'template': _save_temp(template, 'w'),
|
||||||
|
}
|
||||||
|
args = '"{xmlsec}" --sign --pkcs12 "{pk12}" --pwd {pass} ' \
|
||||||
|
'"{template}"'.format(**data)
|
||||||
|
xml_sign = _call(args)
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
auth = {}
|
||||||
|
else:
|
||||||
|
if not auth:
|
||||||
|
msg = 'Sin datos para cancelar'
|
||||||
|
result = {'ok': False, 'error': msg}
|
||||||
|
return result
|
||||||
|
|
||||||
|
msg = 'Factura cancelada correctamente'
|
||||||
|
data = {'ok': True, 'msg': msg, 'row': {'estatus': 'Cancelada'}}
|
||||||
|
pac = PAC(auth)
|
||||||
|
result = pac.cancel_signature(xml_sign)
|
||||||
|
if result:
|
||||||
|
codes = {None: '',
|
||||||
|
'Could not get UUID Text': 'UUID no encontrado'}
|
||||||
|
if not result['CodEstatus'] is None:
|
||||||
|
data['ok'] = False
|
||||||
|
data['msg'] = codes.get(result['CodEstatus'], result['CodEstatus'])
|
||||||
|
else:
|
||||||
|
data['ok'] = False
|
||||||
|
data['msg'] = pac.error
|
||||||
|
|
||||||
|
return data, result
|
||||||
|
|
||||||
|
|
||||||
class ImportFacturaLibre(object):
|
class ImportFacturaLibre(object):
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
|
|
|
@ -32,6 +32,9 @@ class StorageEngine(object):
|
||||||
def send_email(self, values, session):
|
def send_email(self, values, session):
|
||||||
return main.Facturas.send(values['id'], session['rfc'])
|
return main.Facturas.send(values['id'], session['rfc'])
|
||||||
|
|
||||||
|
def _get_cancelinvoice(self, values):
|
||||||
|
return main.Facturas.cancel(values['id'])
|
||||||
|
|
||||||
def _get_statussat(self, values):
|
def _get_statussat(self, values):
|
||||||
return main.Facturas.get_status_sat(values['id'])
|
return main.Facturas.get_status_sat(values['id'])
|
||||||
|
|
||||||
|
|
|
@ -723,7 +723,7 @@ class Socios(BaseModel):
|
||||||
.where((Socios.id==id) & (Socios.es_cliente==True))
|
.where((Socios.id==id) & (Socios.es_cliente==True))
|
||||||
.dicts()
|
.dicts()
|
||||||
)
|
)
|
||||||
print (id, row)
|
#~ print (id, row)
|
||||||
if len(row):
|
if len(row):
|
||||||
return {'ok': True, 'row': row[0]}
|
return {'ok': True, 'row': row[0]}
|
||||||
return {'ok': False}
|
return {'ok': False}
|
||||||
|
@ -1009,6 +1009,8 @@ class Facturas(BaseModel):
|
||||||
notas = TextField(default='')
|
notas = TextField(default='')
|
||||||
pagada = BooleanField(default=False)
|
pagada = BooleanField(default=False)
|
||||||
cancelada = BooleanField(default=False)
|
cancelada = BooleanField(default=False)
|
||||||
|
fecha_cancelacion = DateTimeField(null=True)
|
||||||
|
acuse = TextField(default='')
|
||||||
donativo = BooleanField(default=False)
|
donativo = BooleanField(default=False)
|
||||||
tipo_relacion = TextField(default='')
|
tipo_relacion = TextField(default='')
|
||||||
error = TextField(default='')
|
error = TextField(default='')
|
||||||
|
@ -1016,6 +1018,25 @@ class Facturas(BaseModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
order_by = ('fecha',)
|
order_by = ('fecha',)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cancel(cls, id):
|
||||||
|
msg = 'Factura cancelada correctamente'
|
||||||
|
auth = Emisor.get_auth()
|
||||||
|
certificado = Certificado.select()[0]
|
||||||
|
obj = Facturas.get(Facturas.id==id)
|
||||||
|
data, result = util.cancel_cfdi(
|
||||||
|
obj.uuid, certificado.p12, certificado.rfc, auth)
|
||||||
|
if data['ok']:
|
||||||
|
obj.estatus = 'Cancelada'
|
||||||
|
obj.error = ''
|
||||||
|
obj.cancelada = True
|
||||||
|
obj.fecha_cancelacion = result['Fecha']
|
||||||
|
obj.acuse = result['Acuse']
|
||||||
|
else:
|
||||||
|
obj.error = data['msg']
|
||||||
|
obj.save()
|
||||||
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter_years(cls):
|
def filter_years(cls):
|
||||||
data = [{'id': -1, 'value': 'Todos'}]
|
data = [{'id': -1, 'value': 'Todos'}]
|
||||||
|
@ -1025,7 +1046,8 @@ class Facturas(BaseModel):
|
||||||
.order_by(Facturas.fecha.year)
|
.order_by(Facturas.fecha.year)
|
||||||
.scalar(as_tuple=True)
|
.scalar(as_tuple=True)
|
||||||
)
|
)
|
||||||
data += [{'id': int(row), 'value': int(row)} for row in rows]
|
if not rows is None:
|
||||||
|
data += [{'id': int(row), 'value': int(row)} for row in rows]
|
||||||
return tuple(data)
|
return tuple(data)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1489,7 +1511,7 @@ class Facturas(BaseModel):
|
||||||
|
|
||||||
auth = Emisor.get_auth()
|
auth = Emisor.get_auth()
|
||||||
|
|
||||||
error = False
|
#~ error = False
|
||||||
msg = 'Factura timbrada correctamente'
|
msg = 'Factura timbrada correctamente'
|
||||||
result = util.timbra_xml(obj.xml, auth)
|
result = util.timbra_xml(obj.xml, auth)
|
||||||
if result['ok']:
|
if result['ok']:
|
||||||
|
@ -1498,10 +1520,10 @@ class Facturas(BaseModel):
|
||||||
obj.fecha_timbrado = result['fecha']
|
obj.fecha_timbrado = result['fecha']
|
||||||
obj.estatus = 'Timbrada'
|
obj.estatus = 'Timbrada'
|
||||||
obj.error = ''
|
obj.error = ''
|
||||||
obj.save()
|
#~ obj.save()
|
||||||
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
|
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
|
||||||
else:
|
else:
|
||||||
error = True
|
#~ error = True
|
||||||
msg = result['error']
|
msg = result['error']
|
||||||
obj.estatus = 'Error'
|
obj.estatus = 'Error'
|
||||||
obj.error = msg
|
obj.error = msg
|
||||||
|
|
|
@ -25,6 +25,8 @@ DB_SAT = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'sat.db'))
|
||||||
|
|
||||||
IV = 'valores_iniciales.json'
|
IV = 'valores_iniciales.json'
|
||||||
INIT_VALUES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', IV))
|
INIT_VALUES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', IV))
|
||||||
|
CT = 'cancel_template.xml'
|
||||||
|
TEMPLATE_CANCEL = os.path.abspath(os.path.join(PATH_TEMPLATES, CT))
|
||||||
|
|
||||||
PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt'))
|
PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt'))
|
||||||
PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin'))
|
PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin'))
|
||||||
|
@ -69,9 +71,11 @@ log = Logger(LOG_NAME)
|
||||||
|
|
||||||
PATH_XSLTPROC = 'xsltproc'
|
PATH_XSLTPROC = 'xsltproc'
|
||||||
PATH_OPENSSL = 'openssl'
|
PATH_OPENSSL = 'openssl'
|
||||||
|
PATH_XMLSEC = 'xmlsec1'
|
||||||
if 'win' in sys.platform:
|
if 'win' in sys.platform:
|
||||||
PATH_XSLTPROC = os.path.join(PATH_BIN, 'xsltproc.exe')
|
PATH_XSLTPROC = os.path.join(PATH_BIN, 'xsltproc.exe')
|
||||||
PATH_OPENSSL = os.path.join(PATH_BIN, 'openssl.exe')
|
PATH_OPENSSL = os.path.join(PATH_BIN, 'openssl.exe')
|
||||||
|
PATH_XMLSEC = os.path.join(PATH_BIN, 'xmlsec.exe')
|
||||||
|
|
||||||
|
|
||||||
PRE = {
|
PRE = {
|
||||||
|
|
|
@ -695,7 +695,19 @@ function grid_invoices_click(id, e, node){
|
||||||
|
|
||||||
|
|
||||||
function send_cancel(id){
|
function send_cancel(id){
|
||||||
show(id)
|
webix.ajax().get('/values/cancelinvoice', {id: id}, function(text, data){
|
||||||
|
var values = data.json()
|
||||||
|
if(values.ok){
|
||||||
|
msg_sucess(values.msg)
|
||||||
|
gi.updateItem(id, values.row)
|
||||||
|
}else{
|
||||||
|
webix.alert({
|
||||||
|
title: 'Error al Cancelar',
|
||||||
|
text: values.msg,
|
||||||
|
type: 'alert-error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmd_invoice_cancelar_click(){
|
function cmd_invoice_cancelar_click(){
|
||||||
|
@ -715,6 +727,11 @@ function cmd_invoice_cancelar_click(){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(row.estatus == 'Cancelada'){
|
||||||
|
msg_error('La factura ya esta cancelada')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
msg = '¿Estás seguro de enviar a cancelar esta factura?<BR><BR> \
|
msg = '¿Estás seguro de enviar a cancelar esta factura?<BR><BR> \
|
||||||
ESTA ACCIÓN NO SE PUEDE DESHACER'
|
ESTA ACCIÓN NO SE PUEDE DESHACER'
|
||||||
webix.confirm({
|
webix.confirm({
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Cancelacion RfcEmisor="{rfc}" Fecha="{fecha}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://cancelacfd.sat.gob.mx">
|
||||||
|
<Folios>
|
||||||
|
<UUID>{uuid}</UUID>
|
||||||
|
</Folios>
|
||||||
|
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<SignedInfo>
|
||||||
|
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
|
||||||
|
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
|
||||||
|
<Reference URI="">
|
||||||
|
<Transforms>
|
||||||
|
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
|
||||||
|
</Transforms>
|
||||||
|
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
|
||||||
|
<DigestValue />
|
||||||
|
</Reference>
|
||||||
|
</SignedInfo>
|
||||||
|
<SignatureValue />
|
||||||
|
<KeyInfo>
|
||||||
|
<X509Data>
|
||||||
|
<X509SubjectName />
|
||||||
|
<X509IssuerSerial />
|
||||||
|
<X509Certificate />
|
||||||
|
</X509Data>
|
||||||
|
<KeyValue />
|
||||||
|
</KeyInfo>
|
||||||
|
</Signature>
|
||||||
|
</Cancelacion>
|
Loading…
Reference in New Issue