Merge branch 'pagos' into develop

Soporte para complemento de pagos
This commit is contained in:
Mauricio Baeza 2018-08-30 19:54:46 -05:00
commit b202827ee0
18 changed files with 1277 additions and 38 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

@ -1,4 +1,20 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# ~ Empresa Libre
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.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 datetime
from xml.etree import ElementTree as ET
@ -6,8 +22,6 @@ from xml.dom.minidom import parseString
from logbook import Logger
#~ from settings import DEBUG
log = Logger('XML')
CFDI_ACTUAL = 'cfdi33'
@ -64,6 +78,12 @@ SAT = {
'xmlns': 'http://www.sat.gob.mx/iedu',
'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/ine/iedu.xsd',
},
'pagos': {
'version': '1.0',
'prefix': 'pago10',
'xmlns': 'http://www.sat.gob.mx/Pagos',
'schema': ' http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd',
},
}
@ -79,6 +99,7 @@ class CFDI(object):
self._donativo = False
self._ine = False
self._edu = False
self._pagos = False
self._is_nomina = False
self.error = ''
@ -129,6 +150,7 @@ class CFDI(object):
if datos['complementos']:
if 'ine' in datos['complementos']:
self._ine = True
self._pagos = bool(datos['complementos'].get('pagos', False))
if 'nomina' in datos:
self._is_nomina = True
@ -174,9 +196,15 @@ class CFDI(object):
attributes[name] = SAT['nomina']['xmlns']
schema_nomina = SAT['nomina']['schema']
schema_pagos = ''
if self._pagos:
name = 'xmlns:{}'.format(SAT['pagos']['prefix'])
attributes[name] = SAT['pagos']['xmlns']
schema_pagos = SAT['pagos']['schema']
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
schema_locales + schema_donativo + schema_ine + schema_edu + \
schema_nomina
schema_nomina + schema_pagos
attributes.update(datos)
if not 'Version' in attributes:
@ -403,6 +431,17 @@ class CFDI(object):
atributos.update(datos['ine'])
ET.SubElement(self._complemento, 'ine:INE', atributos)
if 'pagos' in datos:
datos = datos.pop('pagos')
relacionados = datos.pop('relacionados')
pre = SAT['pagos']['prefix']
attributes = {'Version': SAT['pagos']['version']}
pagos = ET.SubElement(
self._complemento, '{}:Pagos'.format(pre), attributes)
node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), datos)
for row in relacionados:
ET.SubElement(node_pago, '{}:DoctoRelacionado'.format(pre), row)
if 'ce' in datos:
pre = 'cce11'
datos = datos.pop('ce')

View File

@ -514,3 +514,31 @@ class AppDocumentos(object):
'attachment; filename={}'.format(file_name))
resp.content_type = content_type
resp.status = falcon.HTTP_200
# ~ Revisado
class AppInvoicePay(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_invoicepay(values)
resp.status = falcon.HTTP_200
class AppCfdiPay(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_cfdipay(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.cfdipay(values)
resp.status = falcon.HTTP_200

View File

@ -320,6 +320,9 @@ def get_template_ods(name, default='plantilla_factura.ods'):
if is_file(path):
return path
if 'pagos' in name:
default='plantilla_pagos.ods'
path = _join(PATH_TEMPLATES, default)
if is_file(path):
return path
@ -954,6 +957,9 @@ class LIBO(object):
self._set_cell('{total_cantidades}', str(self._total_cantidades))
if self._pagos:
return
cell_title = self._set_cell('{subtotal.titulo}', 'SubTotal')
value = data['subtotal']
cell_value = self._set_cell('{subtotal}', value, value=True)
@ -1150,16 +1156,114 @@ class LIBO(object):
return
def _cfdipays(self, data):
related = data.pop('related', [])
for k, v in data.items():
if k.lower() in ('monto',):
self._set_cell('{pago.%s}' % k, v, value=True)
else:
self._set_cell('{pago.%s}' % k, v)
col1 = []
col2 = []
col3 = []
col4 = []
col5 = []
col6 = []
col7 = []
col8 = []
col9 = []
count = len(related)
for i, doc in enumerate(related):
uuid = doc['IdDocumento'].upper()
serie = doc['Serie']
folio = doc['Folio']
metodo_pago = doc['MetodoDePagoDR']
moneda = doc['MonedaDR']
parcialidad = doc['NumParcialidad']
saldo_anterior = doc['ImpSaldoAnt']
importe_pagado = doc['ImpPagado']
saldo_insoluto = doc['ImpSaldoInsoluto']
if i == 0:
cell_1 = self._set_cell('{doc.uuid}', uuid)
cell_2 = self._set_cell('{doc.serie}', serie)
cell_3 = self._set_cell('{doc.folio}', folio)
cell_4 = self._set_cell('{doc.metodopago}', metodo_pago)
cell_5 = self._set_cell('{doc.moneda}', moneda)
cell_6 = self._set_cell('{doc.parcialidad}', parcialidad)
cell_7 = self._set_cell('{doc.saldoanterior}', saldo_anterior, value=True)
cell_8 = self._set_cell('{doc.importepagado}', importe_pagado, value=True)
cell_9 = self._set_cell('{doc.saldoinsoluto}', saldo_insoluto, value=True)
else:
col1.append((uuid,))
col2.append((serie,))
col3.append((folio,))
col4.append((metodo_pago,))
col5.append((moneda,))
col6.append((parcialidad,))
col7.append((float(saldo_anterior),))
col8.append((float(importe_pagado),))
col9.append((float(saldo_insoluto),))
if count == 1:
return
count -= 1
row1 = cell_1.getCellAddress().Row + 1
row2 = row1 + count - 1
self._sheet.getRows().insertByIndex(row1, count)
self._copy_paste_rows(cell_1, count)
# ~ style_7 = self._get_style(cell_7)
# ~ style_8 = self._get_style(cell_8)
# ~ style_9 = self._get_style(cell_9)
col = cell_1.getCellAddress().Column
target1 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
col = cell_2.getCellAddress().Column
target2 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
col = cell_3.getCellAddress().Column
target3 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
col = cell_4.getCellAddress().Column
target4 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
col = cell_5.getCellAddress().Column
target5 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
col = cell_6.getCellAddress().Column
target6 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
col = cell_7.getCellAddress().Column
target7 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
col = cell_8.getCellAddress().Column
target8 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
col = cell_9.getCellAddress().Column
target9 = self._sheet.getCellRangeByPosition(col, row1, col, row2)
target1.setFormulaArray(tuple(col1))
target2.setDataArray(tuple(col2))
target3.setFormulaArray(tuple(col3))
target4.setDataArray(tuple(col4))
target5.setDataArray(tuple(col5))
target6.setDataArray(tuple(col6))
target7.setDataArray(tuple(col7))
target8.setDataArray(tuple(col8))
target9.setDataArray(tuple(col9))
return
def _render(self, data):
self._set_search()
self._es_pre = data.pop('es_pre', False)
self._is_ticket = data.pop('is_ticket', False)
self._currency = data['totales']['moneda']
self._pagos = data.pop('pagos', False)
self._comprobante(data['comprobante'])
self._emisor(data['emisor'])
self._receptor(data['receptor'])
self._conceptos(data['conceptos'])
if self._pagos:
self._cfdipays(data['pays'])
if 'nomina' in data and data['nomina']:
self._nomina(data['nomina'])
else:
@ -1167,6 +1271,7 @@ class LIBO(object):
self._timbre(data['timbre'])
self._donataria(data['donataria'])
self._ine(data['ine'])
self._cancelado(data['cancelada'])
self._clean()
return
@ -1444,13 +1549,17 @@ def to_pdf(data, emisor_rfc, ods=False):
if 'nomina' in data and data['nomina']:
version = '{}_{}'.format(data['nomina']['version'], version)
pagos = ''
if data.get('pagos', False):
pagos = '_pagos_'
if APP_LIBO:
app = LIBO()
if app.is_running:
donativo = ''
if data['donativo']:
donativo = '_donativo'
name = '{}_{}{}.ods'.format(rfc.lower(), version, donativo)
name = '{}_{}{}{}.ods'.format(rfc.lower(), pagos, version, donativo)
path = get_template_ods(name)
if path:
return app.pdf(path, data, ods)
@ -1553,6 +1662,7 @@ def _comprobante(doc, options):
'I': 'ingreso',
'E': 'egreso',
'T': 'traslado',
'P': 'pago',
}
data['tipodecomprobante'] = tipos.get(data['tipodecomprobante'])
data['lugarexpedicion'] = \
@ -1854,6 +1964,27 @@ def _nomina(doc, data, values, version_cfdi):
return info
def _cfdipays(doc, data, version):
node = doc.find('{}Complemento/{}Pagos'.format(PRE[version], PRE['pagos']))
if node is None:
return {}
info = CaseInsensitiveDict(node.attrib.copy())
related = []
for n1 in node:
info.update(CaseInsensitiveDict(n1.attrib.copy()))
for n2 in n1:
related.append(CaseInsensitiveDict(n2.attrib.copy()))
info['related'] = related
data['comprobante']['totalenletras'] = to_letters(
float(info['monto']), info['monedap'])
data['comprobante']['moneda'] = info['monedap']
return info
def get_data_from_xml(invoice, values):
data = {'cancelada': invoice.cancelada, 'donativo': False}
if hasattr(invoice, 'donativo'):
@ -1878,6 +2009,9 @@ def get_data_from_xml(invoice, values):
data['comprobante'].update(data['timbre'])
data['nomina'] = _nomina(doc, data, values, version)
data['pagos'] = values.get('pagos', False)
if data['pagos']:
data['pays'] = _cfdipays(doc, data, version)
return data

View File

@ -16,7 +16,8 @@ from controllers.main import (AppEmpresas,
AppLogin, AppLogout, AppAdmin, AppEmisor, AppConfig,
AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios,
AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco,
AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina
AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina,
AppInvoicePay, AppCfdiPay
)
@ -55,6 +56,8 @@ api.add_route('/movbanco', AppMovimientosBanco(db))
api.add_route('/students', AppStudents(db))
api.add_route('/employees', AppEmployees(db))
api.add_route('/nomina', AppNomina(db))
api.add_route('/invoicepay', AppInvoicePay(db))
api.add_route('/cfdipay', AppCfdiPay(db))
# ~ Activa si usas waitress y NO estas usando servidor web

View File

@ -412,3 +412,13 @@ class StorageEngine(object):
def importar_bdfl(self):
return main.importar_bdfl()
# ~ Revisado
def get_invoicepay(self, values):
return main.FacturasPagos.get_values(values)
def get_cfdipay(self, values):
return main.CfdiPagos.get_values(values)
def cfdipay(self, values):
return main.CfdiPagos.post(values)

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# ~ Empresa Libre
# ~ Copyright (C) 2018 Mauricio Baeza Servin (web@correolibre.net)
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.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
@ -34,7 +34,8 @@ from controllers import util
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_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \
DEFAULT_SERIE_CFDIPAY, DEFAULT_TYPE_CFDIPAY
FORMAT = '{0:.2f}'
@ -213,6 +214,10 @@ def get_doc(type_doc, id, rfc):
data, file_name = util.get_log('nomina')
elif type_doc == 'nompdf':
data, file_name = CfdiNomina.get_pdf(id, rfc)
elif type_doc == 'xmlpago':
data, file_name = CfdiPagos.get_file_xml(id)
elif type_doc == 'pdfpago':
data, file_name = CfdiPagos.get_file_pdf(id)
return data, file_name, content_type
@ -891,10 +896,15 @@ class Certificado(BaseModel):
rfc = TextField(default='')
desde = DateTimeField(null=True)
hasta = DateTimeField(null=True)
es_fiel = BooleanField(default=False)
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)
@ -1933,11 +1943,13 @@ class MovimientosBanco(BaseModel):
.select(
MovimientosBanco.id,
MovimientosBanco.fecha,
SATFormaPago.name.alias('way_payment'),
MovimientosBanco.numero_operacion,
MovimientosBanco.descripcion,
MovimientosBanco.retiro,
MovimientosBanco.deposito,
MovimientosBanco.saldo)
.join(SATFormaPago).switch(MovimientosBanco)
.where(filtros)
.dicts()
)
@ -1945,21 +1957,6 @@ class MovimientosBanco(BaseModel):
return {'ok': True, 'rows': rows}
class CfdiPagos(BaseModel):
movimiento = ForeignKeyField(MovimientosBanco)
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
fecha_timbrado = DateTimeField(null=True)
xml = TextField(default='')
uuid = UUIDField(null=True)
estatus = TextField(default='Guardado')
estatus_sat = TextField(default='')
notas = TextField(default='')
cancelado = BooleanField(default=False)
class Meta:
order_by = ('movimiento',)
class SATUsoCfdi(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
@ -5220,6 +5217,366 @@ class FacturasPagos(BaseModel):
cls._actualizar_saldo_cliente(cls, fac.cliente, importe * -1)
return
def _get_related(self, values):
id = int(values['id'])
filters = (FacturasPagos.movimiento==id)
rows = tuple(FacturasPagos
.select(
Facturas.id,
Facturas.serie,
Facturas.folio,
Facturas.uuid,
Facturas.fecha,
Facturas.tipo_comprobante,
Facturas.estatus,
Socios.nombre.alias('cliente'),
Facturas.total,
FacturasPagos.saldo,
FacturasPagos.importe,
)
.join(Facturas).switch(FacturasPagos)
.join(Socios, on=(Facturas.cliente==Socios.id))
.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 CfdiPagos(BaseModel):
movimiento = ForeignKeyField(MovimientosBanco)
socio = ForeignKeyField(Socios)
serie = TextField(default='')
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='')
uuid = UUIDField(null=True)
estatus = TextField(default='Guardada')
estatus_sat = TextField(default='')
notas = TextField(default='')
error = TextField(default='')
cancelada = BooleanField(default=False)
class Meta:
order_by = ('movimiento',)
@classmethod
def post(cls, values):
opt = values.pop('opt')
return getattr(cls, '_{}'.format(opt))(cls, values)
def _get_folio(self, serie):
folio = int(Configuracion.get_('txt_config_cfdipay_folio') or '0')
start = (CfdiPagos
.select(fn.Max(CfdiPagos.folio).alias('mf'))
.where(CfdiPagos.serie==serie)
.order_by(SQL('mf'))
.scalar())
if start is None:
next_folio = 1
else:
next_folio = start + 1
if folio > next_folio:
next_folio = folio
return next_folio
def _new(self, values):
id_mov = int(values['id_mov'])
filters = (FacturasPagos.movimiento==id_mov)
related = FacturasPagos.select().where(filters)
if not related:
msg = 'El pago no tiene facturas relacionadas'
data = {'ok': False, 'msg': msg}
return data
partner = tuple(set([f.factura.cliente.id for f in related]))
if len(partner) > 1:
msg = 'Facturas relacionadas a diferentes clientes'
data = {'ok': False, 'msg': msg}
return data
partner = partner[0]
partner_name = related[0].factura.cliente.nombre
regimen_fiscal = related[0].factura.regimen_fiscal
filters = (
(CfdiPagos.movimiento==id_mov) &
(CfdiPagos.cancelado==False)
)
previous = CfdiPagos.select().where(filters)
if previous:
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
fields = {}
fields['movimiento'] = id_mov
fields['socio'] = partner
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)
row = {
'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, 'new': True}
return data
def _get_related_xml(self, id_mov, currency):
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:
# ~ print('\n\nMONEDA', currency, r['MonedaDR'])
r['IdDocumento'] = str(r['IdDocumento'])
r['Folio'] = str(r['Folio'])
r['NumParcialidad'] = str(r['NumParcialidad'])
# ~ r['TipoCambioDR'] = FORMAT.format(r['TipoCambioDR'])
r['MetodoDePagoDR'] = 'PDD'
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
currency = mov.cuenta.moneda.key
related_docs = self._get_related_xml(self, invoice.movimiento, currency)
pagos = {
'FechaPago': mov.fecha.isoformat()[:19],
'FormaDePagoP': mov.forma_pago.key,
'MonedaP': currency,
'Monto': FORMAT.format(mov.deposito),
'relacionados': related_docs,
}
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()
msg = 'Factura timbrada correctamente'
result = util.timbra_xml(obj.xml, auth)
if result['ok']:
obj.xml = result['xml']
obj.uuid = result['uuid']
obj.fecha_timbrado = result['fecha']
obj.estatus = 'Timbrada'
obj.error = ''
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
else:
msg = result['error']
obj.estatus = 'Error'
obj.error = msg
row = {'estatus': 'Error'}
obj.save()
result = {
'ok': result['ok'],
'msg': msg,
'id': obj.id,
'row': row,
}
return result
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_file_xml(cls, id):
obj = CfdiPagos.get(CfdiPagos.id==id)
folio = str(obj.folio).zfill(6)
name = '{}{}_{}.xml'.format(obj.serie, folio, obj.socio.rfc)
return obj.xml, name
def _get_not_in_xml(self, invoice, emisor):
values = {}
values['notas'] = invoice.notas
values['fechadof'] = None
obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal)
values['regimenfiscal'] = str(obj)
obj = SATUsoCfdi.get(SATUsoCfdi.key=='P01')
values['usocfdi'] = str(obj)
values['moneda'] = 'XXX'
if invoice.tipo_relacion:
obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion)
values['tiporelacion'] = str(obj)
receptor = Socios.select().where(Socios.id==invoice.socio.id).dicts()[0]
values['receptor'] = {}
for k, v in receptor.items():
values['receptor'][k] = v
values['pagos'] = True
return values
@classmethod
def get_file_pdf(cls, id):
try:
emisor = Emisor.select()[0]
except IndexError:
return b'', 'sin_datos_de_emisor.pdf'
obj = CfdiPagos.get(CfdiPagos.id==id)
folio = str(obj.folio).zfill(6)
name = '{}{}_{}.pdf'.format(obj.serie, folio, obj.socio.rfc)
if obj.uuid is None:
return b'', name
values = cls._get_not_in_xml(cls, obj, emisor)
data = util.get_data_from_xml(obj, values)
obj = SATFormaPago.get(SATFormaPago.key==data['pays']['FormaDePagoP'])
data['pays']['formadepago'] = '{} ({})'.format(obj.name, obj.key)
doc = util.to_pdf(data, emisor.rfc)
return doc, name
@classmethod
def get_values(cls, values):
opt = values.pop('opt')
return getattr(cls, '_get_{}'.format(opt))(cls, values)
class PreFacturasImpuestos(BaseModel):
factura = ForeignKeyField(PreFacturas)
@ -7487,6 +7844,35 @@ def _migrate_tables():
if 'cancelado' in columns:
migrations.append(migrator.drop_column('facturaspagos', 'cancelado'))
columns = [c.name for c in database_proxy.get_columns('certificado')]
if not 'es_fiel' in columns:
es_fiel = BooleanField(default=False)
migrations.append(migrator.add_column('certificado', 'es_fiel', es_fiel))
columns = [c.name for c in database_proxy.get_columns('cfdipagos')]
if not 'serie' in columns:
socio = ForeignKeyField(Socios, null=True, to_field=Socios.id)
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)
cancelada = BooleanField(default=False)
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))
migrations.append(migrator.add_column('cfdipagos', 'socio_id', socio))
migrations.append(migrator.drop_column('cfdipagos', 'cancelado'))
migrations.append(migrator.add_column('cfdipagos', 'cancelada', cancelada))
if migrations:
with database_proxy.atomic() as txn:
migrate(*migrations)

View File

@ -1,4 +1,20 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# ~ Empresa Libre
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.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 logbook
import os
@ -31,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)
@ -116,7 +132,8 @@ PRE = {
'NOMINA': {
'1.1': '{http://www.sat.gob.mx/nomina}',
'1.2': '{http://www.sat.gob.mx/nomina12}',
}
},
'pagos': '{http://www.sat.gob.mx/Pagos}',
}
CURRENT_CFDI = '3.3'
@ -138,6 +155,8 @@ 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

@ -1,3 +1,20 @@
//~ Empresa Libre
//~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.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/>.
var msg = ''
var msg_importe = ''
@ -7,6 +24,7 @@ var bancos_controllers = {
$$('lst_cuentas_banco').attachEvent('onChange', lst_cuentas_banco_change)
$$('cmd_agregar_retiro').attachEvent('onItemClick', cmd_agregar_retiro_click)
$$('cmd_agregar_deposito').attachEvent('onItemClick', cmd_agregar_deposito_click)
$$('cmd_complemento_pago').attachEvent('onItemClick', cmd_complemento_pago_click)
$$('cmd_guardar_retiro').attachEvent('onItemClick', cmd_guardar_retiro_click)
$$('cmd_guardar_deposito').attachEvent('onItemClick', cmd_guardar_deposito_click)
$$('cmd_cancelar_movimiento').attachEvent('onItemClick', cmd_cancelar_movimiento_click)
@ -20,6 +38,11 @@ var bancos_controllers = {
$$('filter_cuenta_year').attachEvent('onChange', filter_cuenta_change)
$$('filter_cuenta_month').attachEvent('onChange', filter_cuenta_change)
$$('filter_cuenta_dates').attachEvent('onChange', filter_cuenta_dates_change)
$$('cmd_pay_stamp').attachEvent('onItemClick', cmd_pay_stamp_click)
$$('cmd_pay_cancel').attachEvent('onItemClick', cmd_pay_cancel_click)
$$('grid_cfdi_pay').attachEvent('onItemClick', grid_cfdi_pay_click)
set_year_month()
}
}
@ -666,8 +689,202 @@ function filter_cuenta_change(){
get_estado_cuenta()
}
function filter_cuenta_dates_change(range){
if(range.start != null && range.end != null){
get_estado_cuenta(range)
}
}
}
function set_data_pay(row){
var form = $$('form_bank_pay')
var dt = row.fecha.split(' ')
var grid = $$('grid_pay_related')
grid.clearAll()
set_way_payment('pay_way_payment')
var wp = table_waypayment.findOne({'value': row.way_payment})
form.setValues({
id_mov: row.id,
pay_date: dt[0],
pay_time: dt[1],
pay_reference: row.numero_operacion,
pay_way_payment: wp.id,
pay_import: row.deposito,
pay_description: row.descripcion
})
webix.ajax().get('/invoicepay', {'opt': 'related', 'id': 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.parse(values.rows, 'json')
}
}
})
$$('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')
}
}
})
}
function cmd_complemento_pago_click(){
var grid = $$('grid_cuentabanco')
var row = grid.getSelectedItem()
if(row == undefined){
msg_error('Selecciona un movimiento de depósito')
return
}
if(row.descripcion == 'Saldo inicial'){
msg_error('No es posible generar un pago del Saldo Inicial')
return
}
if(row.deposito == 0){
msg_error('Selecciona un movimiento de depósito')
return
}
set_data_pay(row)
$$('multi_bancos').setValue('bank_pay')
}
function validate_cfdi_pay(form){
if(!form.validate()) {
msg_error('Valores inválidos')
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 g = $$('grid_cfdi_pay')
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){
g.updateItem(result.id, result.row)
msg_ok(result.msg)
}else{
msg_error(result.msg)
}
}
})
}
function save_cfdi_pay(form){
var values = form.getValues()
var data = {'opt': 'new', 'id_mov': values.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){
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)
}
}
})
}
function cmd_pay_stamp_click(){
var form = $$('form_bank_pay')
var title = 'Timbrar Factura de Pago'
msg = '¿Estás seguro de enviar a timbrar este pago?'
if (!validate_cfdi_pay(form)){
return
}
webix.confirm({
title: title,
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
save_cfdi_pay(form)
}
}
})
}
function cmd_pay_cancel_click(){
}
function grid_cfdi_pay_click(id, e, node){
var row = this.getItem(id)
if(id.column == 'xml'){
location = '/doc/xmlpago/' + row.id
}else if(id.column == 'pdf'){
window.open('/doc/pdfpago/' + row.id, '_blank')
}else if(id.column == 'email'){
//~ enviar_correo(row)
}
}

View File

@ -39,6 +39,8 @@ function configuracion_inicial(){
add_config({'key': 'decimales_precios', 'value': values.decimales_precios})
})
get_way_payment()
}

View File

@ -1,3 +1,20 @@
//~ Empresa Libre
//~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.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/>.
var PUBLICO = "Público en general";
var RFC_PUBLICO = "XAXX010101000";
var RFC_EXTRANJERO = "XEXX010101000";
@ -16,6 +33,7 @@ var table_totals = db.addCollection('totals', {unique: ['tax']})
var table_series = db.addCollection('series')
var table_usocfdi = db.addCollection('usocfdi')
var table_relaciones = db.addCollection('relaciones')
var table_waypayment = db.addCollection('waypayment')
var msg = ''
@ -408,6 +426,21 @@ function get_forma_pago(control){
}
function get_way_payment(){
webix.ajax().get('/values/formapago', {key: true}, function(text, data){
var values = data.json()
table_waypayment.clear()
table_waypayment.insert(values)
})
}
function set_way_payment(control){
var values = table_waypayment.chain().data()
$$(control).getList().parse(values)
}
function validate_regexp(value, pattern){
re = new RegExp(pattern, 'i');
if(value.match(re)){
@ -443,3 +476,7 @@ function pause(milliseconds) {
var dt = new Date();
while ((new Date()) - dt <= milliseconds) { /* Do nothing */ }
}
//~ Revisado

View File

@ -233,18 +233,38 @@ var emisor_otros_datos= [
]
var emisor_certificado = [
var col_sello = {rows: [
{template: 'Certificado actual', type: 'section'},
{view: 'form', id: 'form_cert', rows: [
{cols: [{view: 'text', id: 'cert_rfc', name: 'cert_rfc',
label: 'RFC: ', readonly: true, placeholder: 'Ninguno'}, {}]},
label: 'RFC: ', readonly: true, placeholder: 'Ninguno'}]},
{cols: [{view: 'text', id: 'cert_serie', name: 'cert_serie',
label: 'Serie: ', readonly: true, placeholder: 'Ninguno'}, {}]},
label: 'Serie: ', readonly: true, placeholder: 'Ninguno'}]},
{cols: [{view: 'text', id: 'cert_desde', name: 'cert_desde',
label: 'Vigente desde: ', readonly: true}, {}]},
label: 'Vigente desde: ', readonly: true}]},
{cols: [{view: 'text', id: 'cert_hasta', name: 'cert_hasta',
label: 'Vigente hasta: ', readonly: true}, {}]},
]},
label: 'Vigente hasta: ', readonly: true}]},
]}
]}
var col_fiel = {rows: [
{template: 'Fiel actual', type: 'section'},
{view: 'form', id: 'form_fiel', rows: [
{cols: [{view: 'text', id: 'fiel_rfc', name: 'fiel_rfc',
label: 'RFC: ', readonly: true, placeholder: 'Ninguno'}]},
{cols: [{view: 'text', id: 'fiel_serie', name: 'fiel_serie',
label: 'Serie: ', readonly: true, placeholder: 'Ninguno'}]},
{cols: [{view: 'text', id: 'fiel_desde', name: 'fiel_desde',
label: 'Vigente desde: ', readonly: true}]},
{cols: [{view: 'text', id: 'fiel_hasta', name: 'fiel_hasta',
label: 'Vigente hasta: ', readonly: true}]},
]}
]}
var emisor_certificado = [
{cols: [col_sello, col_fiel]},
{template: 'Cargar Certificado', type: 'section'},
{view: 'form', id: 'form_upload', rows: [
{cols: [{},

View File

@ -1,3 +1,18 @@
//~ Empresa Libre
//~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.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/>.
var toolbar_banco = [
@ -26,6 +41,9 @@ 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: 'Factura de Pago',
type: 'iconButton', autowidth: true, icon: 'file-code-o'},
{},
{view: 'button', id: 'cmd_cancelar_movimiento', label: 'Cancelar',
type: 'iconButton', autowidth: true, icon: 'ban'},
]
@ -35,6 +53,7 @@ var grid_cuentabanco_cols = [
{id: 'id', header:'ID', hidden: true},
{id: 'fecha', header: 'Fecha', width: 150},
{id: 'numero_operacion', header: 'Referencia'},
{id: 'way_payment', header: 'Forma de Pago', hidden: true},
{id: 'descripcion', header: ['Descripción', {content: 'textFilter'}],
fillspace:true},
{id: 'retiro', header: ['Retiro', {content: 'numberFilter'}],
@ -95,6 +114,49 @@ var grid_cfdi_este_deposito_cols = [
]
var grid_cfdi_pay_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'},
{id: 'folio', header: ['Folio'], adjust: 'data',
sort: 'int', css: 'right', footer: {text: 'Facturas', colspan: 3}},
{id: "uuid", header: ["UUID"], adjust: "data",
sort:"string", hidden:true},
{id: "fecha", header: ["Fecha y Hora"],
adjust: "data", sort: "string"},
{id: "tipo_comprobante", header: ["Tipo"],
adjust: 'header', sort: 'string'},
{id: "estatus", header: ["Estatus"],
adjust: "data", sort:"string"},
{id: 'cliente', header: ['Razón Social'], fillspace: true},
{id: 'xml', header: 'XML', adjust: 'data', template: get_icon('xml')},
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
{id: 'email', header: '', adjust: 'data', template: get_icon('email')}
]
var grid_pay_related_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right'},
{id: 'id', header: 'ID', hidden: true},
{id: 'serie', header: 'Serie', adjust: 'data'},
{id: 'folio', header: 'Folio', adjust: 'data', css: 'right'},
{id: 'uuid', header: 'UUID', width: 250, hidden: true},
{id: 'fecha', header: 'Fecha y Hora', width: 150, sort: 'date'},
{id: 'tipo_comprobante', header: 'Tipo', adjust: 'data'},
{id: 'estatus', header: 'Estatus', adjust: 'header'},
{id: 'cliente', header: ['Razón Social'], fillspace: true},
{id: 'total', header: ['Total'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right'},
{id: 'saldo', header: ['Saldo'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right', css: 'right',
footer: 'Importe Depósito'},
{id: 'importe', header: ['Este pago'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right',
footer: {content: 'summColumn', css: 'right'}},
]
var grid_cfdi_por_pagar = {
view: 'datatable',
id: 'grid_cfdi_por_pagar',
@ -139,6 +201,47 @@ var grid_cfdi_este_deposito = {
}
var grid_cfdi_pay = {
view: 'datatable',
id: 'grid_cfdi_pay',
select: 'row',
autoConfig: false,
adjust: true,
autoheight: true,
resizeColumn: true,
headermenu: true,
columns: grid_cfdi_pay_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var grid_pay_related = {
view: 'datatable',
id: 'grid_pay_related',
select: 'row',
autoConfig: false,
adjust: true,
autoheight: true,
resizeColumn: true,
headermenu: true,
footer: true,
columns: grid_pay_related_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var toolbar_banco_retiro = [
{view: 'label', label: 'Agregar retiro de banco'},
{},
@ -159,6 +262,19 @@ var toolbar_banco_deposito = [
]
var toolbar_bank_pay = [
{view: 'label', label: 'Factura de pago'},
{},
{view: 'button', id: 'cmd_pay_stamp', label: 'Timbrar',
type: 'iconButton', autowidth: true, icon: 'ticket'},
{view: 'button', id: 'cmd_pay_cancel', label: 'Cancelar',
type: 'iconButton', autowidth: true, icon: 'ban'},
{},
{view: 'icon', click: '$$("multi_bancos").setValue("banco_home")',
icon: 'times-circle'}
]
var controls_banco_retiro = [
{view: 'toolbar', elements: toolbar_banco_retiro},
{cols: [
@ -229,6 +345,41 @@ var controls_banco_deposito = [
]
var controls_bank_pay = [
{view: 'toolbar', elements: toolbar_bank_pay},
{view: 'label', label: '<b>Este depósito: </b>'},
{cols: [
{view: 'datepicker', id: 'pay_date', name: 'pay_date',
label: 'Fecha', format: '%d-%M-%Y', labelAlign: 'right',
required: true, invalidMessage: 'Selecciona una fecha',
labelWidth: 125, readonly: true},
{view: 'search', id: 'pay_time', name: 'pay_time', label: 'Hora',
icon: 'clock-o', labelAlign: 'right', required: true,
readonly: true,
invalidMessage: 'Captura una hora'},
{view: 'text', id: 'pay_reference', name: 'pay_reference',
label: 'Referencia', labelAlign: 'right', readonly: true},
]},
{cols: [
{view: 'richselect', id: 'pay_way_payment', readonly: true,
name: 'pay_way_payment', label: 'Forma de Pago', required: true,
options: [], labelWidth: 125, labelAlign: 'right'},
{view: 'currency', type: 'text', id: 'pay_import', name: 'pay_import',
label: 'Importe', labelAlign: 'right', required: true, readonly: true,
invalidMessage: 'Captura un valor númerico', inputAlign: 'right'}
]},
{cols: [
{view: 'textarea', id: 'pay_description', label: 'Descripción',
name: 'pay_description', labelAlign: 'right', required: true,
labelWidth: 125, height: 70, readonly: true},
]},
{view: 'label', label: '<b>Facturas de pago de este depósito: </b>'},
grid_cfdi_pay,
{view: 'label', label: '<b>Facturas relacionadas en este pago: </b>'},
grid_pay_related
]
var form_banco_retiro = {
type: 'space',
responsive: true,
@ -255,6 +406,19 @@ var form_banco_deposito = {
}
var form_bank_pay = {
type: 'space',
responsive: true,
cols: [{
view: 'form',
id: 'form_bank_pay',
complexData: true,
scroll: true,
elements: controls_bank_pay,
}],
}
var multi_bancos = {
id: 'multi_bancos',
animate: true,
@ -266,7 +430,8 @@ var multi_bancos = {
grid_cuentabanco,
]},
{id: 'banco_retiro', rows: [form_banco_retiro]},
{id: 'banco_deposito', rows: [form_banco_deposito]}
{id: 'banco_deposito', rows: [form_banco_deposito]},
{id: 'bank_pay', rows: [form_bank_pay]}
],
}

View File

@ -1,5 +1,5 @@
//~ Empresa Libre
//~ Copyright (C) 2018 Mauricio Baeza Servin (web@correolibre.net)
//~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.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

Binary file not shown.

View File

@ -12,6 +12,7 @@
<xsl:include href="donat11.xslt"/>
<xsl:include href="ine11.xslt"/>
<xsl:include href="iedu.xslt"/>
<xsl:include href="pagos10.xslt"/>
<!--
<xsl:include href="ecc11.xslt"/>
<xsl:include href="Divisas.xslt"/>
@ -30,7 +31,6 @@
<xsl:include href="obrasarteantiguedades.xslt"/>
<xsl:include href="ventavehiculos11.xslt"/>
<xsl:include href="terceros11.xslt"/>
<xsl:include href="Pagos10.xslt"/>
-->

165
source/xslt/pagos10.xslt Normal file
View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:pago10="http://www.sat.gob.mx/Pagos">
<xsl:template match="pago10:Pagos">
<!--Manejador de Atributos Pagos-->
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Version" />
</xsl:call-template>
<!-- Iniciamos el manejo de los elementos hijo en la secuencia -->
<xsl:for-each select="./pago10:Pago">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="pago10:Pago">
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@FechaPago" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@FormaDePagoP" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@MonedaP" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@TipoCambioP" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Monto" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumOperacion" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@RfcEmisorCtaOrd" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NomBancoOrdExt" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@CtaOrdenante" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@RfcEmisorCtaBen" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@CtaBeneficiario" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@TipoCadPago" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@CertPago" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@CadPago" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@SelloPago" />
</xsl:call-template>
<!-- Iniciamos el tratamiento de los atributos de pago10:DocumentoRelacionado-->
<xsl:for-each select="./pago10:DoctoRelacionado">
<xsl:apply-templates select="."/>
</xsl:for-each>
<xsl:for-each select="./pago10:Impuestos">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="pago10:DoctoRelacionado">
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@IdDocumento" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Serie" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Folio" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@MonedaDR" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@TipoCambioDR" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@MetodoDePagoDR" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumParcialidad" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@ImpSaldoAnt" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@ImpPagado" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@ImpSaldoInsoluto" />
</xsl:call-template>
</xsl:template>
<xsl:template match="pago10:Impuestos">
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@TotalImpuestosRetenidos" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@TotalImpuestosTrasladados" />
</xsl:call-template>
<xsl:apply-templates select="./pago10:Retenciones"/>
<xsl:apply-templates select="./pago10:Traslados"/>
</xsl:template>
<xsl:template match="pago10:Retenciones">
<xsl:for-each select="./pago10:Retencion">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="pago10:Traslados">
<xsl:for-each select="./pago10:Traslado">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="pago10:Retencion">
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Impuesto" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Importe" />
</xsl:call-template>
</xsl:template>
<xsl:template match="pago10:Traslado">
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Impuesto" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@TipoFactor" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@TasaOCuota" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Importe" />
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>