Generate XML cfdi pay

This commit is contained in:
Mauricio Baeza 2018-08-28 01:02:35 -05:00
parent 282f31497f
commit 6a724ac845
7 changed files with 256 additions and 34 deletions

View File

@ -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.

View File

@ -1 +1 @@
1.11.1
1.12.0

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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)
}

View File

@ -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",