forked from elmau/empresa-libre
Generate XML cfdi pay
This commit is contained in:
parent
282f31497f
commit
6a724ac845
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,3 +1,17 @@
|
|||
v 1.12.0 [31-ago-2018]
|
||||
----------------------
|
||||
- Soporte para facturas (complemento) de pago.
|
||||
|
||||
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
|
||||
|
||||
```
|
||||
git pull origin master
|
||||
|
||||
cd source/app/models
|
||||
|
||||
python main.py -m
|
||||
```
|
||||
|
||||
v 1.11.1 [21-ago-2018]
|
||||
----------------------
|
||||
- Fix - Quitar columna en tabla facturaspagos.
|
||||
|
|
|
@ -99,6 +99,7 @@ class CFDI(object):
|
|||
self._donativo = False
|
||||
self._ine = False
|
||||
self._edu = False
|
||||
self._pagos = False
|
||||
self._is_nomina = False
|
||||
self.error = ''
|
||||
|
||||
|
@ -149,6 +150,7 @@ class CFDI(object):
|
|||
if datos['complementos']:
|
||||
if 'ine' in datos['complementos']:
|
||||
self._ine = True
|
||||
self._pagos = datos['complementos'].get('pagos', False)
|
||||
|
||||
if 'nomina' in datos:
|
||||
self._is_nomina = True
|
||||
|
@ -427,7 +429,6 @@ class CFDI(object):
|
|||
complemento = ET.SubElement(self._cfdi, '{}:Complemento'.format(self._pre))
|
||||
pre = 'pago10'
|
||||
datos = datos.pop('pagos')
|
||||
pago = datos.pop('pago')
|
||||
relacionados = datos.pop('relacionados')
|
||||
|
||||
attributes = {}
|
||||
|
@ -437,11 +438,11 @@ class CFDI(object):
|
|||
'http://www.sat.gob.mx/Pagos ' \
|
||||
'http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd'
|
||||
attributes.update(datos)
|
||||
|
||||
pagos = ET.SubElement(
|
||||
complemento, '{}:Pagos'.format(pre), attributes)
|
||||
|
||||
node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), pago)
|
||||
|
||||
node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), datos)
|
||||
for row in relacionados:
|
||||
ET.SubElement(node_pago, '{}:DoctoRelacionado'.format(pre), row)
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ from settings import log, DEBUG, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI,
|
|||
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
|
||||
CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \
|
||||
DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \
|
||||
DEFAULT_SERIE_CFDIPAY
|
||||
DEFAULT_SERIE_CFDIPAY, DEFAULT_TYPE_CFDIPAY
|
||||
|
||||
|
||||
FORMAT = '{0:.2f}'
|
||||
|
@ -897,6 +897,10 @@ class Certificado(BaseModel):
|
|||
def __str__(self):
|
||||
return self.serie
|
||||
|
||||
@classmethod
|
||||
def get_cert(cls, is_fiel=False):
|
||||
return Certificado.get(Certificado.es_fiel==is_fiel)
|
||||
|
||||
@classmethod
|
||||
def get_data(cls):
|
||||
obj = cls.get_(cls)
|
||||
|
@ -5248,7 +5252,9 @@ class CfdiPagos(BaseModel):
|
|||
folio = IntegerField(default=0)
|
||||
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
|
||||
fecha_timbrado = DateTimeField(null=True)
|
||||
tipo_comprobante = TextField(default=DEFAULT_TYPE_CFDIPAY)
|
||||
lugar_expedicion = TextField(default='')
|
||||
regimen_fiscal = TextField(default='')
|
||||
tipo_relacion = TextField(default='')
|
||||
uuid_relacionado = UUIDField(null=True)
|
||||
xml = TextField(default='')
|
||||
|
@ -5303,6 +5309,7 @@ class CfdiPagos(BaseModel):
|
|||
|
||||
partner = partner[0]
|
||||
partner_name = related[0].factura.cliente.nombre
|
||||
regimen_fiscal = related[0].factura.regimen_fiscal
|
||||
|
||||
filters = (
|
||||
(CfdiPagos.movimiento==id_mov) &
|
||||
|
@ -5310,9 +5317,14 @@ class CfdiPagos(BaseModel):
|
|||
)
|
||||
previous = CfdiPagos.select().where(filters)
|
||||
if previous:
|
||||
msg = 'Hay una factura activa, es necesario cancelarla primero'
|
||||
data = {'ok': False, 'msg': msg}
|
||||
return data
|
||||
previous = previous[0]
|
||||
if previous.uuid:
|
||||
msg = 'Hay una factura activa, es necesario cancelarla primero'
|
||||
data = {'ok': False, 'msg': msg}
|
||||
return data
|
||||
else:
|
||||
data = {'ok': True, 'new': False}
|
||||
return data
|
||||
|
||||
emisor = Emisor.select()[0]
|
||||
serie = Configuracion.get_('txt_config_cfdipay_serie') or DEFAULT_SERIE_CFDIPAY
|
||||
|
@ -5322,33 +5334,165 @@ class CfdiPagos(BaseModel):
|
|||
fields['serie'] = serie
|
||||
fields['folio'] = self._get_folio(self, serie)
|
||||
fields['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
|
||||
fields['regimen_fiscal'] = regimen_fiscal
|
||||
|
||||
# ~ with database_proxy.atomic() as txn:
|
||||
# ~ obj = CfdiPagos.create(**fields)
|
||||
with database_proxy.atomic() as txn:
|
||||
obj = CfdiPagos.create(**fields)
|
||||
|
||||
# ~ row = {
|
||||
# ~ 'id': obj.id,
|
||||
# ~ 'serie': obj.serie,
|
||||
# ~ 'folio': obj.folio,
|
||||
# ~ 'uuid': obj.uuid,
|
||||
# ~ 'fecha': obj.fecha,
|
||||
# ~ 'tipo_comprobante': 'P',
|
||||
# ~ 'estatus': obj.estatus,
|
||||
# ~ 'cliente': partner_name,
|
||||
# ~ }
|
||||
row = {
|
||||
'id': 1,
|
||||
'serie': 'FP',
|
||||
'folio': 1,
|
||||
'uuid': '',
|
||||
'fecha': util.now(),
|
||||
'tipo_comprobante': 'P',
|
||||
'estatus': 'Timbrada',
|
||||
'id': obj.id,
|
||||
'serie': obj.serie,
|
||||
'folio': obj.folio,
|
||||
'uuid': obj.uuid,
|
||||
'fecha': obj.fecha,
|
||||
'tipo_comprobante': obj.tipo_comprobante,
|
||||
'estatus': obj.estatus,
|
||||
'cliente': partner_name,
|
||||
}
|
||||
data = {'ok': True, 'row': row}
|
||||
data = {'ok': True, 'row': row, 'new': True}
|
||||
return data
|
||||
|
||||
def _get_related_xml(self, id_mov):
|
||||
filters = (FacturasPagos.movimiento==id_mov)
|
||||
related = tuple(FacturasPagos.select(
|
||||
Facturas.uuid.alias('IdDocumento'),
|
||||
Facturas.serie.alias('Serie'),
|
||||
Facturas.folio.alias('Folio'),
|
||||
Facturas.moneda.alias('MonedaDR'),
|
||||
Facturas.tipo_cambio.alias('TipoCambioDR'),
|
||||
Facturas.metodo_pago.alias('MetodoDePagoDR'),
|
||||
FacturasPagos.numero.alias('NumParcialidad'),
|
||||
FacturasPagos.saldo_anterior.alias('ImpSaldoAnt'),
|
||||
FacturasPagos.importe.alias('ImpPagado'),
|
||||
FacturasPagos.saldo.alias('ImpSaldoInsoluto'),
|
||||
).join(Facturas).switch(FacturasPagos)
|
||||
.where(filters)
|
||||
.dicts())
|
||||
|
||||
for r in related:
|
||||
r['IdDocumento'] = str(r['IdDocumento'])
|
||||
r['Folio'] = str(r['Folio'])
|
||||
r['NumParcialidad'] = str(r['NumParcialidad'])
|
||||
r['TipoCambioDR'] = FORMAT.format(r['TipoCambioDR'])
|
||||
r['ImpSaldoAnt'] = FORMAT.format(r['ImpSaldoAnt'])
|
||||
r['ImpPagado'] = FORMAT.format(r['ImpPagado'])
|
||||
r['ImpSaldoInsoluto'] = FORMAT.format(r['ImpSaldoInsoluto'])
|
||||
|
||||
return related
|
||||
|
||||
def _generate_xml(self, invoice, auth):
|
||||
emisor = Emisor.select()[0]
|
||||
cert = Certificado.get_cert()
|
||||
|
||||
cfdi = {}
|
||||
related = {}
|
||||
cfdi['Serie'] = invoice.serie
|
||||
cfdi['Folio'] = str(invoice.folio)
|
||||
cfdi['Fecha'] = invoice.fecha.isoformat()[:19]
|
||||
cfdi['NoCertificado'] = cert.serie
|
||||
cfdi['Certificado'] = cert.cer_txt
|
||||
cfdi['SubTotal'] = '0'
|
||||
cfdi['Moneda'] = 'XXX'
|
||||
cfdi['Total'] = '0'
|
||||
cfdi['TipoDeComprobante'] = invoice.tipo_comprobante
|
||||
cfdi['LugarExpedicion'] = invoice.lugar_expedicion
|
||||
|
||||
if invoice.tipo_relacion:
|
||||
related = {
|
||||
'tipo': invoice.tipo_relacion,
|
||||
'cfdis': (invoice.uuid_relacionado,),
|
||||
}
|
||||
|
||||
emisor = {
|
||||
'Rfc': emisor.rfc,
|
||||
'Nombre': emisor.nombre,
|
||||
'RegimenFiscal': invoice.regimen_fiscal,
|
||||
}
|
||||
|
||||
receptor = {
|
||||
'Rfc': invoice.socio.rfc,
|
||||
'Nombre': invoice.socio.nombre,
|
||||
'UsoCFDI': 'P01',
|
||||
}
|
||||
if invoice.socio.tipo_persona == 4:
|
||||
if invoice.socio.pais:
|
||||
receptor['ResidenciaFiscal'] = invoice.socio.pais
|
||||
if invoice.socio.id_fiscal:
|
||||
receptor['NumRegIdTrib'] = invoice.socio.id_fiscal
|
||||
|
||||
conceptos = ({
|
||||
'ClaveProdServ': '84111506',
|
||||
'Cantidad': '1',
|
||||
'ClaveUnidad': 'ACT',
|
||||
'Descripcion': 'Pago',
|
||||
'ValorUnitario': '0',
|
||||
'Importe': '0',
|
||||
},)
|
||||
|
||||
impuestos = {}
|
||||
|
||||
mov = invoice.movimiento
|
||||
pagos = {
|
||||
'FechaPago': mov.fecha.isoformat()[:19],
|
||||
'FormaDePagoP': mov.forma_pago.key,
|
||||
'MonedaP': mov.cuenta.moneda.key,
|
||||
'Monto': FORMAT.format(mov.deposito),
|
||||
'relacionados': self._get_related_xml(self, invoice.movimiento),
|
||||
}
|
||||
|
||||
complementos = {'pagos': pagos}
|
||||
data = {
|
||||
'comprobante': cfdi,
|
||||
'relacionados': related,
|
||||
'emisor': emisor,
|
||||
'receptor': receptor,
|
||||
'conceptos': conceptos,
|
||||
'impuestos': impuestos,
|
||||
'donativo': {},
|
||||
'edu': False,
|
||||
'complementos': complementos,
|
||||
}
|
||||
return util.make_xml(data, cert, auth)
|
||||
|
||||
def _stamp(self, values):
|
||||
id_mov = int(values['id_mov'])
|
||||
|
||||
auth = Emisor.get_auth()
|
||||
filters = (
|
||||
(CfdiPagos.movimiento==id_mov) &
|
||||
(CfdiPagos.uuid.is_null(True))
|
||||
)
|
||||
obj = CfdiPagos.get(filters)
|
||||
obj.xml = self._generate_xml(self, obj, auth)
|
||||
obj.estatus = 'Generada'
|
||||
obj.save()
|
||||
# ~ result = util.timbra_xml(obj.xml, auth)
|
||||
data = {'ok': True, 'row': {}}
|
||||
return data
|
||||
|
||||
def _get_related(self, values):
|
||||
id_mov = int(values['id_mov'])
|
||||
filters = (
|
||||
(CfdiPagos.movimiento==id_mov)
|
||||
)
|
||||
rows = tuple(CfdiPagos.select(
|
||||
CfdiPagos.id,
|
||||
CfdiPagos.serie,
|
||||
CfdiPagos.folio,
|
||||
CfdiPagos.uuid,
|
||||
CfdiPagos.fecha,
|
||||
CfdiPagos.tipo_comprobante,
|
||||
CfdiPagos.estatus,
|
||||
Socios.nombre.alias('cliente'),
|
||||
).join(Socios).switch(CfdiPagos)
|
||||
.where(filters).dicts())
|
||||
return {'ok': True, 'rows': rows}
|
||||
|
||||
@classmethod
|
||||
def get_values(cls, values):
|
||||
opt = values.pop('opt')
|
||||
return getattr(cls, '_get_{}'.format(opt))(cls, values)
|
||||
|
||||
|
||||
class PreFacturasImpuestos(BaseModel):
|
||||
factura = ForeignKeyField(PreFacturas)
|
||||
|
@ -7627,12 +7771,16 @@ def _migrate_tables():
|
|||
serie = TextField(default='')
|
||||
folio = IntegerField(default=0)
|
||||
lugar_expedicion = TextField(default='')
|
||||
regimen_fiscal = TextField(default='')
|
||||
tipo_comprobante = TextField(default=DEFAULT_TYPE_CFDIPAY)
|
||||
error = TextField(default='')
|
||||
tipo_relacion = TextField(default='')
|
||||
uuid_relacionado = UUIDField(null=True)
|
||||
migrations.append(migrator.add_column('cfdipagos', 'serie', serie))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'folio', folio))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'lugar_expedicion', lugar_expedicion))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'regimen_fiscal', regimen_fiscal))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'tipo_comprobante', tipo_comprobante))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'error', error))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'tipo_relacion', tipo_relacion))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'uuid_relacionado', uuid_relacionado))
|
||||
|
|
|
@ -47,7 +47,7 @@ except ImportError:
|
|||
|
||||
|
||||
DEBUG = DEBUG
|
||||
VERSION = '1.11.1'
|
||||
VERSION = '1.12.0'
|
||||
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
||||
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
|
||||
|
||||
|
@ -155,6 +155,7 @@ IMPUESTOS = {
|
|||
DEFAULT_SAT_PRODUCTO = '01010101'
|
||||
DEFAULT_SERIE_TICKET = 'T'
|
||||
DEFAULT_SERIE_CFDIPAY = 'FP'
|
||||
DEFAULT_TYPE_CFDIPAY = 'P'
|
||||
DIR_FACTURAS = 'facturas'
|
||||
USAR_TOKEN = False
|
||||
CANCEL_SIGNATURE = False
|
||||
|
|
|
@ -728,6 +728,20 @@ function set_data_pay(row){
|
|||
}
|
||||
})
|
||||
|
||||
$$('grid_cfdi_pay').clearAll()
|
||||
webix.ajax().get('/cfdipay', {'opt': 'related', 'id_mov': row.id}, {
|
||||
error:function(text, data, XmlHttpRequest){
|
||||
msg = 'Ocurrio un error, consulta a soporte técnico'
|
||||
msg_error(msg)
|
||||
},
|
||||
success:function(text, data, XmlHttpRequest){
|
||||
var values = data.json()
|
||||
if(values.ok){
|
||||
$$('grid_cfdi_pay').parse(values.rows, 'json')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -759,10 +773,50 @@ function validate_cfdi_pay(form){
|
|||
return false
|
||||
}
|
||||
|
||||
var grid = $$('grid_pay_related')
|
||||
if(grid.count() == 0){
|
||||
msg_error('El depósito no tiene facturas relacionadas')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
function update_grid_cfdi_pay(row){
|
||||
var g = $$('grid_cfdi_pay')
|
||||
|
||||
g.add(result.row)
|
||||
if (g.count() == 1){
|
||||
g.adjustColumn('index')
|
||||
g.adjustColumn('serie')
|
||||
g.adjustColumn('folio')
|
||||
g.adjustColumn('fecha')
|
||||
g.adjustColumn('cliente')
|
||||
g.adjustColumn('xml')
|
||||
g.adjustColumn('pdf')
|
||||
g.adjustColumn('email')
|
||||
}
|
||||
}
|
||||
|
||||
function send_stamp_cfdi_pay(id_mov){
|
||||
var data = {'opt': 'stamp', 'id_mov': id_mov}
|
||||
|
||||
webix.ajax().sync().post('cfdipay', data, {
|
||||
error:function(text, data, XmlHttpRequest){
|
||||
msg = 'Ocurrio un error, consulta a soporte técnico'
|
||||
msg_error(msg)
|
||||
},
|
||||
success:function(text, data, XmlHttpRequest){
|
||||
result = data.json();
|
||||
if(result.ok){
|
||||
msg = 'Factura timbrada correctamente'
|
||||
msg_ok(msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function save_cfdi_pay(form){
|
||||
var values = form.getValues()
|
||||
var data = {'opt': 'new', 'id_mov': values.id_mov}
|
||||
|
@ -775,8 +829,13 @@ function save_cfdi_pay(form){
|
|||
success:function(text, data, XmlHttpRequest){
|
||||
result = data.json();
|
||||
if(result.ok){
|
||||
msg_ok('Factura guardada correctamente<BR>Enviando a timbrar...')
|
||||
$$('grid_cfdi_pay').add(result.row)
|
||||
if(result.new){
|
||||
msg_ok('Factura guardada correctamente<BR>Enviando a timbrar...')
|
||||
update_grid_cfdi_pay(result.row)
|
||||
}else{
|
||||
msg_ok('Enviando a timbrar...')
|
||||
}
|
||||
send_stamp_cfdi_pay(values.id_mov)
|
||||
}else{
|
||||
msg_error(result.msg)
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ var toolbar_movimientos_banco = [
|
|||
{view: 'button', id: 'cmd_agregar_deposito', label: 'Depósito',
|
||||
type: 'iconButton', autowidth: true, icon: 'plus'},
|
||||
{},
|
||||
{view: 'button', id: 'cmd_complemento_pago', label: 'Complemento de Pago',
|
||||
{view: 'button', id: 'cmd_complemento_pago', label: 'Factura de Pago',
|
||||
type: 'iconButton', autowidth: true, icon: 'file-code-o'},
|
||||
{},
|
||||
{view: 'button', id: 'cmd_cancelar_movimiento', label: 'Cancelar',
|
||||
|
@ -118,8 +118,7 @@ var grid_cfdi_pago_cols = [
|
|||
{id: 'index', header: '#', adjust: 'data', css: 'right',
|
||||
footer: {content: 'countRows', colspan: 3, css: 'right'}},
|
||||
{id: "id", header:"ID", hidden:true},
|
||||
{id: 'serie', header: ["Serie"], adjust: "data",
|
||||
sort: 'string', template: '{common.subrow()} #serie#'},
|
||||
{id: 'serie', header: ["Serie"], adjust: "data", sort: 'string'},
|
||||
{id: 'folio', header: ['Folio'], adjust: 'data',
|
||||
sort: 'int', css: 'right', footer: {text: 'Facturas', colspan: 3}},
|
||||
{id: "uuid", header: ["UUID"], adjust: "data",
|
||||
|
|
Loading…
Reference in New Issue