diff --git a/CHANGELOG.md b/CHANGELOG.md
index ddd8eb4..a0602ed 100644
--- a/CHANGELOG.md
+++ b/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.
diff --git a/VERSION b/VERSION
index 720c738..0eed1a2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.11.1
+1.12.0
diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py
index f01e117..ec369ee 100644
--- a/source/app/controllers/cfdi_xml.py
+++ b/source/app/controllers/cfdi_xml.py
@@ -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 .
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')
diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py
index 1003d85..7cea456 100644
--- a/source/app/controllers/main.py
+++ b/source/app/controllers/main.py
@@ -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
diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py
index 67426ac..f21dc87 100644
--- a/source/app/controllers/util.py
+++ b/source/app/controllers/util.py
@@ -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
diff --git a/source/app/main.py b/source/app/main.py
index b43ff83..7c21cc2 100644
--- a/source/app/main.py
+++ b/source/app/main.py
@@ -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
diff --git a/source/app/models/db.py b/source/app/models/db.py
index 41ff929..9faffbf 100644
--- a/source/app/models/db.py
+++ b/source/app/models/db.py
@@ -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)
diff --git a/source/app/models/main.py b/source/app/models/main.py
index b7112b8..7778e03 100644
--- a/source/app/models/main.py
+++ b/source/app/models/main.py
@@ -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)
diff --git a/source/app/settings.py b/source/app/settings.py
index 8156b47..d94ec16 100644
--- a/source/app/settings.py
+++ b/source/app/settings.py
@@ -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 .
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
diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js
index 09e7656..05e1efa 100644
--- a/source/static/js/controller/bancos.js
+++ b/source/static/js/controller/bancos.js
@@ -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 .
+
+
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)
}
-}
\ No newline at end of file
+}
+
+
+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
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)
+ }
+
+}
diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js
index 004011f..de96b83 100644
--- a/source/static/js/controller/main.js
+++ b/source/static/js/controller/main.js
@@ -39,6 +39,8 @@ function configuracion_inicial(){
add_config({'key': 'decimales_precios', 'value': values.decimales_precios})
})
+ get_way_payment()
+
}
diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js
index 7340349..822a985 100644
--- a/source/static/js/controller/util.js
+++ b/source/static/js/controller/util.js
@@ -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 .
+
+
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
+
diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js
index 248f6d2..7936869 100644
--- a/source/static/js/ui/admin.js
+++ b/source/static/js/ui/admin.js
@@ -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: [{},
diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js
index b97128b..5d161af 100644
--- a/source/static/js/ui/bancos.js
+++ b/source/static/js/ui/bancos.js
@@ -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 .
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: 'Este depósito: '},
+ {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: 'Facturas de pago de este depósito: '},
+ grid_cfdi_pay,
+ {view: 'label', label: 'Facturas relacionadas en este pago: '},
+ 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]}
],
}
diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js
index af3e8e8..516bdc2 100644
--- a/source/static/js/ui/invoices.js
+++ b/source/static/js/ui/invoices.js
@@ -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
diff --git a/source/templates/plantilla_pagos.ods b/source/templates/plantilla_pagos.ods
new file mode 100644
index 0000000..975e9a7
Binary files /dev/null and b/source/templates/plantilla_pagos.ods differ
diff --git a/source/xslt/cadena.xslt b/source/xslt/cadena.xslt
index 8ed4715..81d45ac 100644
--- a/source/xslt/cadena.xslt
+++ b/source/xslt/cadena.xslt
@@ -12,6 +12,7 @@
+
diff --git a/source/xslt/pagos10.xslt b/source/xslt/pagos10.xslt
new file mode 100644
index 0000000..98b41f2
--- /dev/null
+++ b/source/xslt/pagos10.xslt
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+