forked from elmau/empresa-libre
New class for Pac Comercio Digital
This commit is contained in:
parent
95399798f8
commit
aae856bb74
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from .comerciodigital import PACComercioDigital
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from .comercio import PACComercioDigital
|
|
@ -0,0 +1,377 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# ~
|
||||||
|
# ~ PAC
|
||||||
|
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
|
||||||
|
# ~
|
||||||
|
# ~ 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 of the License, 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
|
||||||
|
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# ~ GNU General Public License for more details.
|
||||||
|
# ~
|
||||||
|
# ~ You should have received a copy of the GNU General Public License
|
||||||
|
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import lxml.etree as ET
|
||||||
|
import requests
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
|
from .conf import DEBUG, AUTH
|
||||||
|
|
||||||
|
|
||||||
|
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
LOG_DATE = '%d/%m/%Y %H:%M:%S'
|
||||||
|
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
|
||||||
|
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
|
||||||
|
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
|
||||||
|
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
logging.getLogger('requests').setLevel(logging.ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
|
class PACComercioDigital(object):
|
||||||
|
ws = 'https://{}.comercio-digital.mx/{}'
|
||||||
|
api = 'https://app2.comercio-digital.mx/{}'
|
||||||
|
URL = {
|
||||||
|
'timbra': ws.format('ws', 'timbre/timbrarV5.aspx'),
|
||||||
|
'cancel': ws.format('cancela', 'cancela3/cancelarUuid'),
|
||||||
|
'cancelxml': ws.format('cancela', 'cancela3/cancelarXml'),
|
||||||
|
'status': ws.format('cancela', 'arws/consultaEstatus'),
|
||||||
|
'client': api.format('x3/altaEmpresa'),
|
||||||
|
'saldo': api.format('x3/saldo'),
|
||||||
|
'timbres': api.format('x3/altaTimbres'),
|
||||||
|
}
|
||||||
|
CODES = {
|
||||||
|
'000': '000 Exitoso',
|
||||||
|
'004': '004 RFC {} ya esta dado de alta con Estatus=A',
|
||||||
|
'704': '704 Usuario Invalido',
|
||||||
|
'702': '702 Error rfc/empresa invalido',
|
||||||
|
}
|
||||||
|
NS_CFDI = {
|
||||||
|
'cfdi': 'http://www.sat.gob.mx/cfd/3',
|
||||||
|
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
|
||||||
|
}
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
ws = 'https://pruebas.comercio-digital.mx/{}'
|
||||||
|
ws6 = 'https://pruebas6.comercio-digital.mx/arws/{}'
|
||||||
|
URL = {
|
||||||
|
'timbra': ws.format('timbre/timbrarV5.aspx'),
|
||||||
|
'cancel': ws.format('cancela3/cancelarUuid'),
|
||||||
|
'cancelxml': ws.format('cancela3/cancelarXml'),
|
||||||
|
'status': ws6.format('consultaEstatus'),
|
||||||
|
'client': api.format('x3/altaEmpresa'),
|
||||||
|
'saldo': api.format('x3/saldo'),
|
||||||
|
'timbres': api.format('x3/altaTimbres'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.error = ''
|
||||||
|
# ~ self.cfdi_uuid = ''
|
||||||
|
# ~ self.date_stamped = ''
|
||||||
|
|
||||||
|
def _error(self, msg):
|
||||||
|
self.error = str(msg)
|
||||||
|
log.error(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _post(self, url, data, headers={}):
|
||||||
|
result = None
|
||||||
|
headers['host'] = url.split('/')[2]
|
||||||
|
headers['Content-type'] = 'text/plain'
|
||||||
|
headers['Connection'] = 'Keep-Alive'
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT)
|
||||||
|
except ConnectionError as e:
|
||||||
|
self._error(e)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _validate_cfdi(self, xml):
|
||||||
|
"""
|
||||||
|
Comercio Digital solo soporta la declaración con doble comilla
|
||||||
|
"""
|
||||||
|
tree = ET.fromstring(xml.encode())
|
||||||
|
xml = ET.tostring(tree,
|
||||||
|
pretty_print=True, doctype='<?xml version="1.0" encoding="utf-8"?>')
|
||||||
|
return xml
|
||||||
|
|
||||||
|
def stamp(self, cfdi, auth={}):
|
||||||
|
if DEBUG or not auth:
|
||||||
|
auth = AUTH
|
||||||
|
|
||||||
|
url = self.URL['timbra']
|
||||||
|
headers = {
|
||||||
|
'usrws': auth['user'],
|
||||||
|
'pwdws': auth['pass'],
|
||||||
|
'tipo': 'XML',
|
||||||
|
}
|
||||||
|
cfdi = self._validate_cfdi(cfdi)
|
||||||
|
result = self._post(url, cfdi, headers)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.status_code != 200:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if 'errmsg' in result.headers:
|
||||||
|
self._error(result.headers['errmsg'])
|
||||||
|
return ''
|
||||||
|
|
||||||
|
xml = result.content
|
||||||
|
tree = ET.fromstring(xml)
|
||||||
|
cfdi_uuid = tree.xpath(
|
||||||
|
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
|
||||||
|
namespaces=self.NS_CFDI)
|
||||||
|
date_stamped = tree.xpath(
|
||||||
|
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@FechaTimbrado)',
|
||||||
|
namespaces=self.NS_CFDI)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'xml': xml.decode(),
|
||||||
|
'uuid': cfdi_uuid,
|
||||||
|
'date': date_stamped,
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _get_data_cancel(self, cfdi, info, auth):
|
||||||
|
NS_CFDI = {
|
||||||
|
'cfdi': 'http://www.sat.gob.mx/cfd/3',
|
||||||
|
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
|
||||||
|
}
|
||||||
|
tree = ET.fromstring(cfdi.encode())
|
||||||
|
tipo = tree.xpath(
|
||||||
|
'string(//cfdi:Comprobante/@TipoDeComprobante)',
|
||||||
|
namespaces=NS_CFDI)
|
||||||
|
total = tree.xpath(
|
||||||
|
'string(//cfdi:Comprobante/@Total)',
|
||||||
|
namespaces=NS_CFDI)
|
||||||
|
rfc_emisor = tree.xpath(
|
||||||
|
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
|
||||||
|
namespaces=NS_CFDI)
|
||||||
|
rfc_receptor = tree.xpath(
|
||||||
|
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
|
||||||
|
namespaces=NS_CFDI)
|
||||||
|
uid = tree.xpath(
|
||||||
|
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
|
||||||
|
namespaces=NS_CFDI)
|
||||||
|
data = (
|
||||||
|
f"USER={auth['user']}",
|
||||||
|
f"PWDW={auth['pass']}",
|
||||||
|
f"RFCE={rfc_emisor}",
|
||||||
|
f"UUID={uid}",
|
||||||
|
f"PWDK={info['pass']}",
|
||||||
|
f"KEYF={info['key']}",
|
||||||
|
f"CERT={info['cer']}",
|
||||||
|
f"TIPO={info['tipo']}",
|
||||||
|
f"ACUS=SI",
|
||||||
|
f"RFCR={rfc_receptor}",
|
||||||
|
f"TIPOC={tipo}",
|
||||||
|
f"TOTAL={total}",
|
||||||
|
)
|
||||||
|
return '\n'.join(data)
|
||||||
|
|
||||||
|
def cancel(self, cfdi, info, auth={}):
|
||||||
|
if not auth:
|
||||||
|
auth = AUTH
|
||||||
|
url = self.URL['cancel']
|
||||||
|
data = self._get_data_cancel(cfdi, info, auth)
|
||||||
|
|
||||||
|
result = self._post(url, data)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.status_code != 200:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.headers['codigo'] != '000':
|
||||||
|
self._error(result.headers['errmsg'])
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return result.text
|
||||||
|
|
||||||
|
def _get_headers_cancel_xml(self, cfdi, info, auth):
|
||||||
|
NS_CFDI = {
|
||||||
|
'cfdi': 'http://www.sat.gob.mx/cfd/3',
|
||||||
|
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
|
||||||
|
}
|
||||||
|
tree = ET.fromstring(cfdi.encode())
|
||||||
|
tipocfdi = tree.xpath(
|
||||||
|
'string(//cfdi:Comprobante/@TipoDeComprobante)',
|
||||||
|
namespaces=NS_CFDI)
|
||||||
|
total = tree.xpath(
|
||||||
|
'string(//cfdi:Comprobante/@Total)',
|
||||||
|
namespaces=NS_CFDI)
|
||||||
|
rfc_receptor = tree.xpath(
|
||||||
|
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
|
||||||
|
namespaces=NS_CFDI)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'usrws': auth['user'],
|
||||||
|
'pwdws': auth['pass'],
|
||||||
|
'rfcr': rfc_receptor,
|
||||||
|
'total': total,
|
||||||
|
'tipocfdi': tipocfdi,
|
||||||
|
}
|
||||||
|
headers.update(info)
|
||||||
|
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def cancel_xml(self, cfdi, xml, info, auth={}):
|
||||||
|
if not auth:
|
||||||
|
auth = AUTH
|
||||||
|
url = self.URL['cancelxml']
|
||||||
|
headers = self._get_headers_cancel_xml(cfdi, info, auth)
|
||||||
|
result = self._post(url, xml, headers)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.status_code != 200:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.headers['codigo'] != '000':
|
||||||
|
self._error(result.headers['errmsg'])
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return result.text
|
||||||
|
|
||||||
|
def status(self, data, auth={}):
|
||||||
|
if not auth:
|
||||||
|
auth = AUTH
|
||||||
|
url = self.URL['status']
|
||||||
|
|
||||||
|
data = (
|
||||||
|
f"USER={auth['user']}",
|
||||||
|
f"PWDW={auth['pass']}",
|
||||||
|
f"RFCR={data['rfc_receptor']}",
|
||||||
|
f"RFCE={data['rfc_emisor']}",
|
||||||
|
f"TOTAL={data['total']}",
|
||||||
|
f"UUID={data['uuid']}",
|
||||||
|
)
|
||||||
|
data = '\n'.join(data)
|
||||||
|
result = self._post(url, data)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.status_code != 200:
|
||||||
|
self._error(result.status_code)
|
||||||
|
return self.error
|
||||||
|
|
||||||
|
return result.text
|
||||||
|
|
||||||
|
def _get_data_client(self, auth, values):
|
||||||
|
data = [f"usr_ws={auth['user']}", f"pwd_ws={auth['pass']}"]
|
||||||
|
fields = (
|
||||||
|
'rfc_contribuyente',
|
||||||
|
'nombre_contribuyente',
|
||||||
|
'calle',
|
||||||
|
'noExterior',
|
||||||
|
'noInterior',
|
||||||
|
'colonia',
|
||||||
|
'localidad',
|
||||||
|
'municipio',
|
||||||
|
'estado',
|
||||||
|
'pais',
|
||||||
|
'cp',
|
||||||
|
'contacto',
|
||||||
|
'telefono',
|
||||||
|
'email',
|
||||||
|
'rep_nom',
|
||||||
|
'rep_rfc',
|
||||||
|
'email_fact',
|
||||||
|
'pwd_asignado',
|
||||||
|
)
|
||||||
|
data += [f"{k}={values[k]}" for k in fields]
|
||||||
|
|
||||||
|
return '\n'.join(data)
|
||||||
|
|
||||||
|
def client_add(self, data):
|
||||||
|
auth = AUTH
|
||||||
|
url = self.URL['client']
|
||||||
|
data = self._get_data_client(auth, data)
|
||||||
|
|
||||||
|
result = self._post(url, data)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if result.status_code != 200:
|
||||||
|
self._error(f'Code: {result.status_code}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if result.text != self.CODES['000']:
|
||||||
|
self._error(result.text)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def client_balance(self, data):
|
||||||
|
url = self.URL['saldo']
|
||||||
|
host = url.split('/')[2]
|
||||||
|
headers = {
|
||||||
|
'Content-type': 'text/plain',
|
||||||
|
'Host': host,
|
||||||
|
'Connection' : 'Keep-Alive',
|
||||||
|
}
|
||||||
|
data = {'usr': data['rfc'], 'pwd': data['password']}
|
||||||
|
try:
|
||||||
|
result = requests.get(url, params=data, headers=headers, timeout=TIMEOUT)
|
||||||
|
except ConnectionError as e:
|
||||||
|
self._error(e)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.status_code != 200:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.text == self.CODES['704']:
|
||||||
|
self._error(result.text)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if result.text == self.CODES['702']:
|
||||||
|
self._error(result.text)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return result.text
|
||||||
|
|
||||||
|
def client_add_timbres(self, data, auth={}):
|
||||||
|
if not auth:
|
||||||
|
auth = AUTH
|
||||||
|
url = self.URL['timbres']
|
||||||
|
data = '\n'.join((
|
||||||
|
f"usr_ws={auth['user']}",
|
||||||
|
f"pwd_ws={auth['pass']}",
|
||||||
|
f"rfc_recibir={data['rfc']}",
|
||||||
|
f"num_timbres={data['timbres']}"
|
||||||
|
))
|
||||||
|
|
||||||
|
result = self._post(url, data)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if result.status_code != 200:
|
||||||
|
self._error(f'Code: {result.status_code}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if result.text != self.CODES['000']:
|
||||||
|
self._error(result.text)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# ~
|
||||||
|
# ~ PAC
|
||||||
|
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
|
||||||
|
# ~
|
||||||
|
# ~ 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 of the License, 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
|
||||||
|
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# ~ GNU General Public License for more details.
|
||||||
|
# ~
|
||||||
|
# ~ You should have received a copy of the GNU General Public License
|
||||||
|
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
# ~ Siempre consulta la documentación de PAC
|
||||||
|
# ~ AUTH = Las credenciales de timbrado proporcionadas por el PAC
|
||||||
|
# ~ NO cambies las credenciales de prueba
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
|
||||||
|
AUTH = {
|
||||||
|
'user': '',
|
||||||
|
'pass': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
AUTH = {
|
||||||
|
'user': 'AAA010101AAA',
|
||||||
|
'pass': 'PWD',
|
||||||
|
}
|
|
@ -546,14 +546,14 @@ class Certificado(object):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def make_xml(data, certificado, auth):
|
def make_xml(data, certificado):
|
||||||
from .cfdi_xml import CFDI
|
from .cfdi_xml import CFDI
|
||||||
|
|
||||||
token = _get_md5(certificado.rfc)
|
token = _get_md5(certificado.rfc)
|
||||||
if USAR_TOKEN:
|
# ~ if USAR_TOKEN:
|
||||||
token = auth['PASS']
|
# ~ token = auth['PASS']
|
||||||
if AUTH['DEBUG']:
|
# ~ if AUTH['DEBUG']:
|
||||||
token = AUTH['PASS']
|
# ~ token = AUTH['PASS']
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
data['emisor']['Rfc'] = certificado.rfc
|
data['emisor']['Rfc'] = certificado.rfc
|
||||||
|
@ -2702,12 +2702,12 @@ def local_copy(files):
|
||||||
log.error(msg)
|
log.error(msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
args = 'df -P {} | tail -1 | cut -d" " -f 1'.format(path_bk)
|
# ~ args = 'df -P {} | tail -1 | cut -d" " -f 1'.format(path_bk)
|
||||||
try:
|
# ~ try:
|
||||||
result = _call(args)
|
# ~ result = _call(args)
|
||||||
# ~ log.info(result)
|
# ~ log.info(result)
|
||||||
except:
|
# ~ except:
|
||||||
pass
|
# ~ pass
|
||||||
# ~ if result != 'empresalibre\n':
|
# ~ if result != 'empresalibre\n':
|
||||||
# ~ log.info(result)
|
# ~ log.info(result)
|
||||||
# ~ msg = 'Asegurate de que exista la carpeta para sincronizar'
|
# ~ msg = 'Asegurate de que exista la carpeta para sincronizar'
|
||||||
|
@ -2752,20 +2752,20 @@ def sync_files(files, auth={}):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def sync_cfdi(auth, files):
|
def sync_cfdi(rfc, files):
|
||||||
local_copy(files)
|
local_copy(files)
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not auth['REPO'] or not SEAFILE_SERVER:
|
# ~ if not auth['REPO'] or not SEAFILE_SERVER:
|
||||||
return
|
# ~ return
|
||||||
|
|
||||||
seafile = SeaFileAPI(SEAFILE_SERVER['URL'], auth['USER'], auth['PASS'])
|
# ~ seafile = SeaFileAPI(SEAFILE_SERVER['URL'], auth['USER'], auth['PASS'])
|
||||||
if seafile.is_connect:
|
# ~ if seafile.is_connect:
|
||||||
for f in files:
|
# ~ for f in files:
|
||||||
seafile.update_file(
|
# ~ seafile.update_file(
|
||||||
f, auth['REPO'], 'Facturas/{}/'.format(f[2]), auth['PASS'])
|
# ~ f, auth['REPO'], 'Facturas/{}/'.format(f[2]), auth['PASS'])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ from dateutil import parser
|
||||||
import seafileapi
|
import seafileapi
|
||||||
|
|
||||||
from settings import DEBUG, DB_COMPANIES, PATHS
|
from settings import DEBUG, DB_COMPANIES, PATHS
|
||||||
from .comercio import PACComercioDigital
|
from .pacs import PACComercioDigital
|
||||||
from .pac import Finkok as PACFinkok
|
from .pac import Finkok as PACFinkok
|
||||||
# ~ from .finkok import PACFinkok
|
# ~ from .finkok import PACFinkok
|
||||||
|
|
||||||
|
@ -584,26 +584,24 @@ def get_pass():
|
||||||
return True, password
|
return True, password
|
||||||
|
|
||||||
|
|
||||||
def xml_stamp(xml, auth, name):
|
def xml_stamp(xml, auth):
|
||||||
if not DEBUG and not auth:
|
if not DEBUG and not auth:
|
||||||
msg = 'Sin datos para timbrar'
|
msg = 'Sin datos para timbrar'
|
||||||
result = {'ok': False, 'error': msg}
|
result = {'ok': False, 'error': msg}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
result = {'ok': True, 'error': ''}
|
result = {'ok': True, 'error': ''}
|
||||||
auth = {'user': auth['USER'], 'pass': auth['PASS']}
|
|
||||||
|
|
||||||
pac = PACS[name]()
|
pac = PACS[auth['pac']]()
|
||||||
xml_stamped = pac.stamp(xml, auth)
|
response = pac.stamp(xml, auth)
|
||||||
|
|
||||||
if not xml_stamped:
|
if not response:
|
||||||
result['ok'] = False
|
result['ok'] = False
|
||||||
result['error'] = pac.error
|
result['error'] = pac.error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
result['xml'] = xml_stamped
|
result.update(response)
|
||||||
result['uuid'] = pac.cfdi_uuid
|
|
||||||
result['fecha'] = pac.date_stamped
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -516,6 +516,18 @@ class Configuracion(BaseModel):
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
def _get_pac_auth(cls):
|
||||||
|
pac = cls.get_('lst_pac').lower()
|
||||||
|
user = cls.get_(f'user_timbrado_{pac}')
|
||||||
|
token = cls.get_(f'token_timbrado_{pac}')
|
||||||
|
data = {}
|
||||||
|
print(1, pac, user, token)
|
||||||
|
if pac and user and token:
|
||||||
|
data['pac'] = pac
|
||||||
|
data['user'] = user
|
||||||
|
data['pass'] = token
|
||||||
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_(cls, keys):
|
def get_(cls, keys):
|
||||||
if isinstance(keys, str):
|
if isinstance(keys, str):
|
||||||
|
@ -534,6 +546,7 @@ class Configuracion(BaseModel):
|
||||||
'folios',
|
'folios',
|
||||||
'correo',
|
'correo',
|
||||||
'admin_config_users',
|
'admin_config_users',
|
||||||
|
'pac_auth',
|
||||||
)
|
)
|
||||||
opt = keys['fields']
|
opt = keys['fields']
|
||||||
if opt in options:
|
if opt in options:
|
||||||
|
@ -4250,8 +4263,8 @@ class Facturas(BaseModel):
|
||||||
return Facturas.send(id, rfc)
|
return Facturas.send(id, rfc)
|
||||||
|
|
||||||
@util.run_in_thread
|
@util.run_in_thread
|
||||||
def _sync(self, id, auth):
|
def _sync(self, id, rfc):
|
||||||
return Facturas.sync(id, auth)
|
return Facturas.sync(id, rfc)
|
||||||
|
|
||||||
@util.run_in_thread
|
@util.run_in_thread
|
||||||
def _sync_pdf(self, pdf, name_pdf, target):
|
def _sync_pdf(self, pdf, name_pdf, target):
|
||||||
|
@ -4350,21 +4363,21 @@ class Facturas(BaseModel):
|
||||||
return {'ok': True, 'msg': msg}
|
return {'ok': True, 'msg': msg}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sync(cls, id, auth):
|
def sync(cls, id, rfc):
|
||||||
obj = Facturas.get(Facturas.id==id)
|
obj = Facturas.get(Facturas.id==id)
|
||||||
if obj.uuid is None:
|
if obj.uuid is None:
|
||||||
msg = 'La factura no esta timbrada'
|
msg = 'La factura no esta timbrada'
|
||||||
return
|
return
|
||||||
|
|
||||||
emisor = Emisor.select()[0]
|
# ~ emisor = Emisor.select()[0]
|
||||||
pdf, name_pdf = cls.get_pdf(id, auth['RFC'], False)
|
pdf, name_pdf = cls.get_pdf(id, rfc, False)
|
||||||
name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
||||||
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
|
target = rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
|
||||||
files = (
|
files = (
|
||||||
(obj.xml, name_xml, target),
|
(obj.xml, name_xml, target),
|
||||||
(pdf, name_pdf, target),
|
(pdf, name_pdf, target),
|
||||||
)
|
)
|
||||||
util.sync_cfdi(auth, files)
|
util.sync_cfdi(rfc, files)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _get_filter_folios(self, values):
|
def _get_filter_folios(self, values):
|
||||||
|
@ -4841,7 +4854,7 @@ class Facturas(BaseModel):
|
||||||
FacturasComplementos.create(**data)
|
FacturasComplementos.create(**data)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _make_xml(self, invoice, auth):
|
def _make_xml(self, invoice):
|
||||||
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
|
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
|
||||||
decimales_precios = Configuracion.get_bool('chk_config_decimales_precios')
|
decimales_precios = Configuracion.get_bool('chk_config_decimales_precios')
|
||||||
invoice_by_ticket = Configuracion.get_bool('chk_config_invoice_by_ticket')
|
invoice_by_ticket = Configuracion.get_bool('chk_config_invoice_by_ticket')
|
||||||
|
@ -5118,7 +5131,7 @@ class Facturas(BaseModel):
|
||||||
'complementos': complementos,
|
'complementos': complementos,
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.make_xml(data, certificado, auth)
|
return util.make_xml(data, certificado)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_status_sat(cls, id):
|
def get_status_sat(cls, id):
|
||||||
|
@ -5195,38 +5208,35 @@ class Facturas(BaseModel):
|
||||||
id = int(values['id'])
|
id = int(values['id'])
|
||||||
update = util.loads(values.get('update', 'true'))
|
update = util.loads(values.get('update', 'true'))
|
||||||
|
|
||||||
auth = Emisor.get_auth()
|
rfc = Emisor.select()[0].rfc
|
||||||
obj = Facturas.get(Facturas.id == id)
|
obj = Facturas.get(Facturas.id == id)
|
||||||
obj.xml = cls._make_xml(cls, obj, auth)
|
obj.xml = cls._make_xml(cls, obj)
|
||||||
obj.estatus = 'Generada'
|
obj.estatus = 'Generada'
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
enviar_correo = util.get_bool(Configuracion.get_('correo_directo'))
|
enviar_correo = util.get_bool(Configuracion.get_('correo_directo'))
|
||||||
pac = Configuracion.get_('lst_pac').lower()
|
auth = Configuracion.get_({'fields': 'pac_auth'})
|
||||||
|
|
||||||
anticipo = False
|
anticipo = False
|
||||||
msg = 'Factura timbrada correctamente'
|
msg = 'Factura timbrada correctamente'
|
||||||
# ~ if pac:
|
result = utils.xml_stamp(obj.xml, auth)
|
||||||
result = utils.xml_stamp(obj.xml, auth, pac)
|
|
||||||
# ~ else:
|
|
||||||
# ~ result = util.timbra_xml(obj.xml, auth)
|
|
||||||
|
|
||||||
if result['ok']:
|
if result['ok']:
|
||||||
obj.xml = result['xml']
|
obj.xml = result['xml']
|
||||||
obj.uuid = result['uuid']
|
obj.uuid = result['uuid']
|
||||||
obj.fecha_timbrado = result['fecha']
|
obj.fecha_timbrado = result['date']
|
||||||
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'}
|
||||||
if enviar_correo:
|
if enviar_correo:
|
||||||
cls._send(cls, id, auth['RFC'])
|
cls._send(cls, id, rfc)
|
||||||
if obj.tipo_comprobante == 'I' and obj.tipo_relacion == '07':
|
if obj.tipo_comprobante == 'I' and obj.tipo_relacion == '07':
|
||||||
anticipo = True
|
anticipo = True
|
||||||
cls._actualizar_saldo_cliente(cls, obj)
|
cls._actualizar_saldo_cliente(cls, obj)
|
||||||
if update:
|
if update:
|
||||||
cls._update_inventory(cls, obj)
|
cls._update_inventory(cls, obj)
|
||||||
cls._sync(cls, id, auth)
|
cls._sync(cls, id, rfc)
|
||||||
|
|
||||||
m = 'T {}'.format(obj.id)
|
m = 'T {}'.format(obj.id)
|
||||||
_save_log(user.usuario, m, 'F')
|
_save_log(user.usuario, m, 'F')
|
||||||
|
|
Loading…
Reference in New Issue