diff --git a/source/app/controllers/configpac.py b/source/app/controllers/configpac.py index 1f559f5..9e0cae1 100644 --- a/source/app/controllers/configpac.py +++ b/source/app/controllers/configpac.py @@ -49,6 +49,7 @@ def finkok(debug): PASS = FINKOK['PASS'] TOKEN = '' auth = { + 'DEBUG': debug, 'USER': '', 'PASS': TOKEN or PASS, 'RESELLER': {'USER': USER, 'PASS': PASS} @@ -58,6 +59,7 @@ def finkok(debug): PASS = '' TOKEN = '5c9a88da105bff9a8c430cb713f6d35269f51674bdc5963c1501b7316366' auth = { + 'DEBUG': debug, 'USER': USER, 'PASS': TOKEN or PASS, 'RESELLER': { diff --git a/source/app/controllers/pac.py b/source/app/controllers/pac.py index b99fb87..31acc20 100644 --- a/source/app/controllers/pac.py +++ b/source/app/controllers/pac.py @@ -324,21 +324,16 @@ class Finkok(object): try: result = client.service.query_pending( self._auth['USER'], self._auth['PASS'], uuid) - #~ print (result.date) - #~ tree = parseString(unescape(result.xml)) - #~ response = tree.toprettyxml(encoding='utf-8').decode('utf-8') return result.status except Fault as e: self.error = str(e) return '' - def cancel_xml(self, rfc, uuids, path_cer, path_key): - for u in uuids: - if not self._validate_uuid(u): - return '' + def cancel_xml(self, rfc, uuid, cer, key): + # ~ for u in uuids: + # ~ if not self._validate_uuid(u): + # ~ return '' - cer = self._load_file(path_cer) - key = self._load_file(path_key) method = 'cancel' client = Client( URL[method], transport=self._transport, plugins=self._plugins) @@ -346,7 +341,7 @@ class Finkok(object): sa = client.get_type('ns0:stringArray') args = { - 'UUIDS': uuid_type(uuids=sa(string=uuids)), + 'UUIDS': uuid_type(uuids=sa(string=uuid)), 'username': self._auth['USER'], 'password': self._auth['PASS'], 'taxpayer_id': rfc, diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 78f1eb7..6077607 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -41,7 +41,8 @@ from settings import DEBUG, MV, log, template_lookup, COMPANIES, DB_SAT, \ PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \ PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES, DIR_FACTURAS -from settings import SEAFILE_SERVER +from settings import SEAFILE_SERVER, USAR_TOKEN +from .configpac import AUTH def _call(args): @@ -416,7 +417,7 @@ class Certificado(object): data['hasta'] = hasta.replace(tzinfo=None) return data - def _get_p12(self, password, rfc): + def _get_p12(self, password, rfc, token): tmp_cer = tempfile.mkstemp()[1] tmp_key = tempfile.mkstemp()[1] tmp_p12 = tempfile.mkstemp()[1] @@ -428,7 +429,7 @@ class Certificado(object): args = 'openssl pkcs12 -export -in "{}" -inkey "{}" -name "{}" ' \ '-passout pass:"{}" -out "{}"' - _call(args.format(tmp_cer, tmp_key, rfc, _get_md5(rfc), tmp_p12)) + _call(args.format(tmp_cer, tmp_key, rfc, token, tmp_p12)) data = read_file(tmp_p12) self._kill(tmp_cer) @@ -437,7 +438,7 @@ class Certificado(object): return data - def _get_info_key(self, password, rfc): + def _get_info_key(self, password, rfc, token): data = {} args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:"{}"' @@ -455,17 +456,23 @@ class Certificado(object): self.error = 'Los archivos no son pareja' return data - args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:"{}" | ' \ - 'openssl rsa -des3 -passout pass:"{}"'.format( - self._path_key, password, _get_md5(rfc)) + args = "openssl pkcs8 -inform DER -in '{}' -passin pass:'{}' | " \ + "openssl rsa -des3 -passout pass:'{}'".format( + self._path_key, password, token) key_enc = _call(args) data['key'] = read_file(self._path_key) data['key_enc'] = key_enc - data['p12'] = self._get_p12(password, rfc) + data['p12'] = self._get_p12(password, rfc, token) return data - def validate(self, password, rfc): + def validate(self, password, rfc, auth): + token = _get_md5(rfc) + if USAR_TOKEN: + token = auth['PASS'] + if AUTH['DEBUG']: + token = AUTH['PASS'] + if not self._path_key or not self._path_cer: self.error = 'Error en las rutas temporales del certificado' return {} @@ -474,7 +481,7 @@ class Certificado(object): if not data: return {} - llave = self._get_info_key(password, data['rfc']) + llave = self._get_info_key(password, rfc, token) if not llave: return {} @@ -485,9 +492,15 @@ class Certificado(object): return data -def make_xml(data, certificado): +def make_xml(data, certificado, auth): from .cfdi_xml import CFDI + token = _get_md5(certificado.rfc) + if USAR_TOKEN: + token = auth['PASS'] + if AUTH['DEBUG']: + token = AUTH['PASS'] + if DEBUG: data['emisor']['Rfc'] = certificado.rfc data['emisor']['RegimenFiscal'] = '603' @@ -501,7 +514,7 @@ def make_xml(data, certificado): 'xml': save_temp(xml, 'w'), 'openssl': PATH_OPENSSL, 'key': save_temp(certificado.key_enc, 'w'), - 'pass': _get_md5(certificado.rfc) + 'pass': token, } args = '"{xsltproc}" "{xslt}" "{xml}" | ' \ '"{openssl}" dgst -sha256 -sign "{key}" -passin pass:"{pass}" | ' \ @@ -517,13 +530,10 @@ def make_xml(data, certificado): def timbra_xml(xml, auth): from .pac import Finkok as PAC - if DEBUG: - auth = {} - else: - if not auth: - msg = 'Sin datos para timbrar' - result = {'ok': True, 'error': msg} - return result + if not DEBUG and not auth: + msg = 'Sin datos para timbrar' + result = {'ok': True, 'error': msg} + return result result = {'ok': True, 'error': ''} pac = PAC(auth) @@ -1299,9 +1309,46 @@ def upload_file(rfc, opt, file_obj): return {'status': 'error', 'ok': False} -def cancel_cfdi(uuid, pk12, rfc, auth): +def cancel_xml(auth, uuid, certificado): from .pac import Finkok as PAC + if DEBUG: + auth = {} + else: + if not auth: + msg = 'Sin datos para cancelar' + data = {'ok': False, 'error': msg} + return data, result + + msg = 'Factura cancelada correctamente' + data = {'ok': True, 'msg': msg, 'row': {'estatus': 'Cancelada'}} + pac = PAC(auth) + result = pac.cancel_xml(certificado.rfc, str(uuid).upper(), + certificado.cer_pem.encode(), certificado.key_enc.encode()) + if result: + codes = {None: '', + 'Could not get UUID Text': 'UUID no encontrado', + 'Invalid Passphrase': 'Contraseña inválida', + } + 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 + + +def cancel_signature(uuid, pk12, rfc, auth): + from .pac import Finkok as PAC + + token = _get_md5(rfc) + if USAR_TOKEN: + token = auth['PASS'] + if AUTH['DEBUG']: + token = AUTH['PASS'] + template = read_file(TEMPLATE_CANCEL, 'r') data = { 'rfc': rfc, @@ -1313,7 +1360,7 @@ def cancel_cfdi(uuid, pk12, rfc, auth): data = { 'xmlsec': PATH_XMLSEC, 'pk12': save_temp(pk12), - 'pass': _get_md5(rfc), + 'pass': token, 'template': save_temp(template, 'w'), } args = '"{xmlsec}" --sign --pkcs12 "{pk12}" --pwd {pass} ' \ diff --git a/source/app/models/main.py b/source/app/models/main.py index 7ff1479..a2b89a4 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -16,7 +16,8 @@ if __name__ == '__main__': from controllers import util from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \ - INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO + INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \ + CANCEL_SIGNATURE FORMAT = '{0:.2f}' @@ -639,7 +640,8 @@ class Certificado(BaseModel): obj = cls.get_(cls) paths = Configuracion.get_({'fields': 'path_cer'}) cert = util.Certificado(paths) - data = cert.validate(values['contra'], session['rfc']) + auth = Emisor.get_auth() + data = cert.validate(values['contra'], session['rfc'], auth) if data: msg = 'Certificado guardado correctamente' q = Certificado.update(**data).where(Certificado.id==obj.id) @@ -2202,11 +2204,34 @@ class Facturas(BaseModel): @classmethod def cancel(cls, id): + if CANCEL_SIGNATURE: + return cls._cancel_signature(cls, id) + return cls._cancel_xml(cls, id) + + def _cancel_xml(self, id): msg = 'Factura cancelada correctamente' auth = Emisor.get_auth() certificado = Certificado.select()[0] obj = Facturas.get(Facturas.id==id) - data, result = util.cancel_cfdi( + data, result = util.cancel_xml(auth, obj.uuid, certificado) + if data['ok']: + obj.estatus = 'Cancelada' + obj.error = '' + obj.cancelada = True + obj.fecha_cancelacion = result['Fecha'] + obj.acuse = result['Acuse'] + self._actualizar_saldo_cliente(self, obj, True) + else: + obj.error = data['msg'] + obj.save() + return data + + def _cancel_signature(self, id): + msg = 'Factura cancelada correctamente' + auth = Emisor.get_auth() + certificado = Certificado.select()[0] + obj = Facturas.get(Facturas.id==id) + data, result = util.cancel_signature( obj.uuid, certificado.p12, certificado.rfc, auth) if data['ok']: obj.estatus = 'Cancelada' @@ -2214,6 +2239,7 @@ class Facturas(BaseModel): obj.cancelada = True obj.fecha_cancelacion = result['Fecha'] obj.acuse = result['Acuse'] + self._actualizar_saldo_cliente(self, obj, True) else: obj.error = data['msg'] obj.save() @@ -2395,7 +2421,7 @@ class Facturas(BaseModel): return @util.run_in_thread - def _actualizar_saldo_cliente(self, invoice): + def _actualizar_saldo_cliente(self, invoice, cancel=False): if invoice.tipo_comprobante == 'T': return @@ -2406,6 +2432,9 @@ class Facturas(BaseModel): if invoice.tipo_comprobante == 'E': importe *= -1 + if cancel: + importe *= -1 + q = (Socios .update(saldo_cliente=Socios.saldo_cliente + importe) .where(Socios.id==invoice.cliente.id) @@ -2811,7 +2840,7 @@ class Facturas(BaseModel): data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg} return data - def _make_xml(self, invoice): + def _make_xml(self, invoice, auth): emisor = Emisor.select()[0] certificado = Certificado.select()[0] @@ -3000,7 +3029,7 @@ class Facturas(BaseModel): 'donativo': donativo, 'complementos': complementos, } - return util.make_xml(data, certificado) + return util.make_xml(data, certificado, auth) @classmethod def get_status_sat(cls, id): @@ -3047,13 +3076,13 @@ class Facturas(BaseModel): @classmethod def timbrar(cls, id): + auth = Emisor.get_auth() obj = Facturas.get(Facturas.id == id) - obj.xml = cls._make_xml(cls, obj) + obj.xml = cls._make_xml(cls, obj, auth) obj.estatus = 'Generada' obj.save() enviar_correo = util.get_bool(Configuracion.get_('correo_directo')) - auth = Emisor.get_auth() anticipo = False msg = 'Factura timbrada correctamente' diff --git a/source/app/settings.py b/source/app/settings.py index 58a0807..df168c1 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -115,4 +115,6 @@ IMPUESTOS = { 'CEDULAR': '000', } DEFAULT_SAT_PRODUCTO = '01010101' -DIR_FACTURAS = 'facturas' \ No newline at end of file +DIR_FACTURAS = 'facturas' +USAR_TOKEN = False +CANCEL_SIGNATURE = True \ No newline at end of file diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index e53b8e6..3cd09a1 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -511,9 +511,6 @@ var tab_options = { view: 'tabview', id: 'tab_options', multiview: true, - //~ tabbar: {options: [ - //~ 'Plantillas', - //~ 'Otros']}, animate: true, cells: [ {id: 'Plantillas', rows: options_templates},