commit
9081d54865
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,3 +1,19 @@
|
|||
v 1.13.0 [10-sep-2018]
|
||||
----------------------
|
||||
- Cancelar factura 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 -bk
|
||||
|
||||
python main.py -m
|
||||
```
|
||||
|
||||
v 1.12.0 [31-ago-2018]
|
||||
----------------------
|
||||
- Soporte para facturas (complemento) de pago.
|
||||
|
|
|
@ -420,7 +420,8 @@ class AppMovimientosBanco(object):
|
|||
|
||||
def on_post(self, req, resp):
|
||||
values = req.params
|
||||
req.context['result'] = self._db.add_movbanco(values)
|
||||
# ~ req.context['result'] = self._db.add_movbanco(values)
|
||||
req.context['result'] = self._db.bankmovement(values)
|
||||
resp.status = falcon.HTTP_200
|
||||
|
||||
def on_delete(self, req, resp):
|
||||
|
|
|
@ -2509,6 +2509,26 @@ def local_copy(files):
|
|||
return
|
||||
|
||||
|
||||
def sync_files(files, auth={}):
|
||||
if not MV:
|
||||
return
|
||||
|
||||
path_bk = _join(str(Path.home()), DIR_FACTURAS)
|
||||
if not os.path.isdir(path_bk):
|
||||
msg = 'No existe la carpeta: facturas'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
for obj, name, target in files:
|
||||
path = _validar_directorios(path_bk, target)
|
||||
path_file = _join(path, name)
|
||||
m = 'wb'
|
||||
if name.endswith('xml'):
|
||||
m = 'w'
|
||||
save_file(path_file, obj, m)
|
||||
return
|
||||
|
||||
|
||||
def sync_cfdi(auth, files):
|
||||
local_copy(files)
|
||||
|
||||
|
|
|
@ -389,8 +389,8 @@ class StorageEngine(object):
|
|||
def cuentasbanco(self, values):
|
||||
return main.CuentasBanco.add(values)
|
||||
|
||||
def add_movbanco(self, values):
|
||||
return main.MovimientosBanco.add(values)
|
||||
# ~ def add_movbanco(self, values):
|
||||
# ~ return main.MovimientosBanco.add(values)
|
||||
|
||||
def get_cuentasbanco(self, values):
|
||||
return main.CuentasBanco.get_(values)
|
||||
|
@ -422,3 +422,6 @@ class StorageEngine(object):
|
|||
|
||||
def cfdipay(self, values):
|
||||
return main.CfdiPagos.post(values)
|
||||
|
||||
def bankmovement(self, values):
|
||||
return main.MovimientosBanco.post(values)
|
||||
|
|
|
@ -1856,8 +1856,18 @@ class MovimientosBanco(BaseModel):
|
|||
return saldo
|
||||
|
||||
@classmethod
|
||||
def add(cls, values):
|
||||
def post(cls, values):
|
||||
opt = values.pop('opt')
|
||||
return getattr(cls, '_{}'.format(opt))(cls, values)
|
||||
|
||||
def _add(self, values):
|
||||
ids = values.pop('ids', '')
|
||||
|
||||
if ids and not Facturas.validate_count_partners(util.loads(ids)):
|
||||
msg = 'Facturas relacionadas a diferentes clientes'
|
||||
data = {'ok': False, 'msg': msg}
|
||||
return data
|
||||
|
||||
actualizar = False
|
||||
if 'saldo' in values:
|
||||
saldo = values['saldo']
|
||||
|
@ -1870,8 +1880,8 @@ class MovimientosBanco(BaseModel):
|
|||
values['deposito'] = util.get_float(values['deposito'])
|
||||
values['forma_pago'] = int(values['forma_pago'])
|
||||
|
||||
ultimo_saldo = cls._ultimo_saldo(
|
||||
cls, values['cuenta'], values['fecha'])
|
||||
ultimo_saldo = self._ultimo_saldo(
|
||||
self, values['cuenta'], values['fecha'])
|
||||
values['saldo'] = \
|
||||
ultimo_saldo - values['retiro'] + values['deposito']
|
||||
|
||||
|
@ -1883,31 +1893,66 @@ class MovimientosBanco(BaseModel):
|
|||
return {'ok': False, 'msg': msg}
|
||||
|
||||
if actualizar:
|
||||
saldo = cls._actualizar_saldos(cls, obj)
|
||||
saldo = self._actualizar_saldos(self, obj)
|
||||
if ids:
|
||||
FacturasPagos.add(obj, util.loads(ids))
|
||||
|
||||
return {'ok': True, 'saldo': saldo}
|
||||
|
||||
@classmethod
|
||||
def remove(cls, id):
|
||||
def _cancel(self, values):
|
||||
id = int(values['id'])
|
||||
try:
|
||||
obj = MovimientosBanco.get(MovimientosBanco.id==id)
|
||||
except MovimientosBanco.DoesNotExist:
|
||||
return False
|
||||
msg = 'No se encontró el movimiento'
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
if obj.conciliado or obj.cancelado:
|
||||
return False
|
||||
if obj.cancelado:
|
||||
msg = 'El movimiento ya esta cancelado'
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
if obj.conciliado:
|
||||
msg = 'El movimiento esta conciliado, no se puede cancelar'
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
filters = (CfdiPagos.movimiento==obj)
|
||||
cp = CfdiPagos.select().where(filters).count()
|
||||
if cp > 0:
|
||||
msg = 'El movimiento tiene Factura de Pago, no se puede cancelar'
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
with database_proxy.transaction():
|
||||
obj.cancelado = True
|
||||
obj.save()
|
||||
FacturasPagos.cancelar(obj)
|
||||
|
||||
obj = cls._movimiento_anterior(cls, obj.cuenta, obj.fecha)
|
||||
cls._actualizar_saldos(cls, obj)
|
||||
obj = self._movimiento_anterior(self, obj.cuenta, obj.fecha)
|
||||
self._actualizar_saldos(self, obj)
|
||||
|
||||
return True
|
||||
balance = CuentasBanco.get_saldo(obj.cuenta.id)
|
||||
|
||||
msg = 'Movimiento cancelado correctamente'
|
||||
return {'ok': True, 'msg': msg, 'balance': balance}
|
||||
|
||||
# ~ @classmethod
|
||||
# ~ def remove(cls, id):
|
||||
# ~ try:
|
||||
# ~ obj = MovimientosBanco.get(MovimientosBanco.id==id)
|
||||
# ~ except MovimientosBanco.DoesNotExist:
|
||||
# ~ return False
|
||||
|
||||
# ~ if obj.conciliado or obj.cancelado:
|
||||
# ~ return False
|
||||
|
||||
# ~ with database_proxy.transaction():
|
||||
# ~ obj.cancelado = True
|
||||
# ~ obj.save()
|
||||
# ~ FacturasPagos.cancelar(obj)
|
||||
|
||||
# ~ obj = cls._movimiento_anterior(cls, obj.cuenta, obj.fecha)
|
||||
# ~ cls._actualizar_saldos(cls, obj)
|
||||
|
||||
# ~ return True
|
||||
|
||||
@classmethod
|
||||
def con(cls, id):
|
||||
|
@ -3220,6 +3265,17 @@ class Facturas(BaseModel):
|
|||
class Meta:
|
||||
order_by = ('fecha',)
|
||||
|
||||
@classmethod
|
||||
def validate_count_partners(cls, ids):
|
||||
filters = (Facturas.id.in_(tuple(ids.keys())))
|
||||
partners = (Facturas.select(fn.COUNT(Facturas.cliente))
|
||||
.where(filters)
|
||||
.group_by(Facturas.cliente)
|
||||
.order_by(Facturas.cliente).tuples())
|
||||
if len(partners) > 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def cancel(cls, id):
|
||||
obj = Facturas.get(Facturas.id==id)
|
||||
|
@ -5279,6 +5335,7 @@ class CfdiPagos(BaseModel):
|
|||
notas = TextField(default='')
|
||||
error = TextField(default='')
|
||||
cancelada = BooleanField(default=False)
|
||||
fecha_cancelacion = DateTimeField(null=True)
|
||||
|
||||
class Meta:
|
||||
order_by = ('movimiento',)
|
||||
|
@ -5288,6 +5345,46 @@ class CfdiPagos(BaseModel):
|
|||
opt = values.pop('opt')
|
||||
return getattr(cls, '_{}'.format(opt))(cls, values)
|
||||
|
||||
def _cancel(self, values):
|
||||
id_mov = int(values['id_mov'])
|
||||
|
||||
filters = (
|
||||
(CfdiPagos.movimiento==id_mov) &
|
||||
(CfdiPagos.cancelada==False)
|
||||
)
|
||||
last = CfdiPagos.select().where(filters)
|
||||
if not last:
|
||||
msg = 'El depósito no tiene facturas de pago activas'
|
||||
data = {'ok': False, 'msg': msg}
|
||||
return data
|
||||
|
||||
if len(last) > 1:
|
||||
msg = 'Hay más de una factura activa para este depósito'
|
||||
data = {'ok': False, 'msg': msg}
|
||||
return data
|
||||
|
||||
last = last[0]
|
||||
if not last.uuid:
|
||||
msg = 'La factura no esta timbrada'
|
||||
data = {'ok': False, 'msg': msg}
|
||||
return data
|
||||
|
||||
auth = Emisor.get_auth()
|
||||
cert = Certificado.get_cert()
|
||||
|
||||
data, result = util.cancel_xml(auth, last.uuid, cert)
|
||||
if data['ok']:
|
||||
last.estatus = 'Cancelada'
|
||||
last.error = ''
|
||||
last.cancelada = True
|
||||
last.fecha_cancelacion = result['Fecha']
|
||||
msg = 'Factura cancelada correctamente'
|
||||
else:
|
||||
last.error = msg = data['msg']
|
||||
last.save()
|
||||
|
||||
return {'ok': data['ok'], 'msg': msg, 'id': last.id}
|
||||
|
||||
def _get_folio(self, serie):
|
||||
folio = int(Configuracion.get_('txt_config_cfdipay_folio') or '0')
|
||||
start = (CfdiPagos
|
||||
|
@ -5341,9 +5438,19 @@ class CfdiPagos(BaseModel):
|
|||
data = {'ok': True, 'new': False}
|
||||
return data
|
||||
|
||||
fields = {}
|
||||
filters = (
|
||||
(CfdiPagos.movimiento==id_mov) &
|
||||
(CfdiPagos.cancelada==True)
|
||||
)
|
||||
previous = CfdiPagos.select().where(filters).order_by(CfdiPagos.id.desc())
|
||||
if previous:
|
||||
previous = previous[0]
|
||||
fields['tipo_relacion'] = DEFAULT_CFDIPAY['TYPE_RELATION']
|
||||
fields['uuid_relacionado'] = previous.uuid
|
||||
|
||||
emisor = Emisor.select()[0]
|
||||
serie = Configuracion.get_('txt_config_cfdipay_serie') or DEFAULT_CFDIPAY['SERIE']
|
||||
fields = {}
|
||||
fields['movimiento'] = id_mov
|
||||
fields['socio'] = partner
|
||||
fields['serie'] = serie
|
||||
|
@ -5417,7 +5524,7 @@ class CfdiPagos(BaseModel):
|
|||
if invoice.tipo_relacion:
|
||||
related = {
|
||||
'tipo': invoice.tipo_relacion,
|
||||
'cfdis': (invoice.uuid_relacionado,),
|
||||
'cfdis': (str(invoice.uuid_relacionado),),
|
||||
}
|
||||
|
||||
emisor = {
|
||||
|
@ -5508,6 +5615,8 @@ class CfdiPagos(BaseModel):
|
|||
'id': obj.id,
|
||||
'row': row,
|
||||
}
|
||||
if result['ok']:
|
||||
self._sync(self, obj.id)
|
||||
return result
|
||||
|
||||
def _get_related(self, values):
|
||||
|
@ -5533,6 +5642,14 @@ class CfdiPagos(BaseModel):
|
|||
obj = CfdiPagos.get(CfdiPagos.id==id)
|
||||
folio = str(obj.folio).zfill(6)
|
||||
name = '{}{}_{}.xml'.format(obj.serie, folio, obj.socio.rfc)
|
||||
|
||||
emisor = Emisor.select()[0]
|
||||
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
|
||||
files = (
|
||||
(obj.xml, name, target),
|
||||
)
|
||||
cls._sync_files(cls, files)
|
||||
|
||||
return obj.xml, name
|
||||
|
||||
def _get_not_in_xml(self, invoice, emisor):
|
||||
|
@ -5575,14 +5692,31 @@ class CfdiPagos(BaseModel):
|
|||
if obj.uuid is None:
|
||||
return b'', name
|
||||
|
||||
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
|
||||
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)
|
||||
|
||||
files = (
|
||||
(doc, name, target),
|
||||
)
|
||||
cls._sync_files(cls, files)
|
||||
|
||||
return doc, name
|
||||
|
||||
@util.run_in_thread
|
||||
def _sync(self, id):
|
||||
CfdiPagos.get_file_xml(id)
|
||||
CfdiPagos.get_file_pdf(id)
|
||||
return
|
||||
|
||||
@util.run_in_thread
|
||||
def _sync_files(self, files):
|
||||
util.sync_files(files)
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def get_values(cls, values):
|
||||
opt = values.pop('opt')
|
||||
|
@ -7821,14 +7955,17 @@ def _crear_tablas(rfc):
|
|||
return True
|
||||
|
||||
|
||||
def _migrate_tables():
|
||||
def _migrate_tables(rfc=''):
|
||||
from playhouse.migrate import PostgresqlMigrator, migrate
|
||||
|
||||
rfc = input('Introduce el RFC: ').strip().upper()
|
||||
if not rfc:
|
||||
msg = 'El RFC es requerido'
|
||||
log.error(msg)
|
||||
return
|
||||
if rfc:
|
||||
rfc = rfc.strip().upper()
|
||||
else:
|
||||
rfc = input('Introduce el RFC: ').strip().upper()
|
||||
if not rfc:
|
||||
msg = 'El RFC es requerido'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
args = util.get_con(rfc)
|
||||
if not args:
|
||||
|
@ -7931,6 +8068,10 @@ def _migrate_tables():
|
|||
migrations.append(migrator.drop_column('cfdipagos', 'cancelado'))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'cancelada', cancelada))
|
||||
|
||||
if not 'fecha_cancelacion' in columns:
|
||||
fecha_cancelacion = DateTimeField(null=True)
|
||||
migrations.append(migrator.add_column('cfdipagos', 'fecha_cancelacion', fecha_cancelacion))
|
||||
|
||||
if migrations:
|
||||
with database_proxy.atomic() as txn:
|
||||
migrate(*migrations)
|
||||
|
@ -8840,7 +8981,7 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña,
|
|||
sys.exit(0)
|
||||
|
||||
if opt['migrar_bd']:
|
||||
_migrate_tables()
|
||||
_migrate_tables(rfc)
|
||||
sys.exit(0)
|
||||
|
||||
if opt['nuevo_superusuario']:
|
||||
|
|
|
@ -47,7 +47,7 @@ except ImportError:
|
|||
|
||||
|
||||
DEBUG = DEBUG
|
||||
VERSION = '1.12.0'
|
||||
VERSION = '1.13.0'
|
||||
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
||||
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
|
||||
|
||||
|
@ -164,6 +164,7 @@ DEFAULT_CFDIPAY = {
|
|||
'KEYSAT': '84111506',
|
||||
'UNITKEY': 'ACT',
|
||||
'DESCRIPTION': 'Pago',
|
||||
'TYPE_RELATION': '04',
|
||||
}
|
||||
DIR_FACTURAS = 'facturas'
|
||||
USAR_TOKEN = False
|
||||
|
|
|
@ -47,6 +47,13 @@
|
|||
font-size: 125%;
|
||||
}
|
||||
|
||||
.link_forum {
|
||||
font-weight: bold;
|
||||
color: #610B0B;
|
||||
text-decoration: none;
|
||||
}
|
||||
.link_forum:hover {text-decoration:underline;}
|
||||
|
||||
.delete {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
|
|
|
@ -282,6 +282,7 @@ function guardar_retiro(values){
|
|||
|
||||
var importe = get_float(values.retiro_importe)
|
||||
var data = new Object()
|
||||
data['opt'] = 'add'
|
||||
data['cuenta'] = $$('lst_cuentas_banco').getValue()
|
||||
data['fecha'] = values.retiro_fecha
|
||||
data['hora'] = $$('time_retiro').getText()
|
||||
|
@ -519,6 +520,7 @@ function guardar_deposito(values){
|
|||
var grid = $$('grid_cfdi_este_deposito')
|
||||
|
||||
var data = new Object()
|
||||
data['opt'] = 'add'
|
||||
data['cuenta'] = $$('lst_cuentas_banco').getValue()
|
||||
data['fecha'] = values.deposito_fecha
|
||||
data['hora'] = $$('time_deposito').getText()
|
||||
|
@ -610,17 +612,24 @@ function cmd_guardar_deposito_click(){
|
|||
|
||||
|
||||
function cancelar_movimiento(id){
|
||||
var grid = $$('grid_cuentabanco')
|
||||
|
||||
webix.ajax().del('/movbanco', {id: id}, function(text, xml, xhr){
|
||||
if(xhr.status == 200){
|
||||
get_estado_cuenta()
|
||||
get_saldo_cuenta()
|
||||
msg_ok('Movimiento cancelado correctamente')
|
||||
}else{
|
||||
msg_error('No se pudo eliminar')
|
||||
var data = {'opt': 'cancel', 'id': id}
|
||||
webix.ajax().post('/movbanco', data, {
|
||||
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){
|
||||
get_estado_cuenta()
|
||||
$$('txt_cuenta_saldo').setValue(values.balance)
|
||||
msg_ok(values.msg)
|
||||
}else{
|
||||
msg_error(values.msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -871,7 +880,7 @@ function save_cfdi_pay(form){
|
|||
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?'
|
||||
msg = '¿Estás seguro de enviar a timbrar este pago?<BR><BR>EL MOVIMIENTO YA NO PODRÁ SER MODIFICADO'
|
||||
|
||||
if (!validate_cfdi_pay(form)){
|
||||
return
|
||||
|
@ -893,6 +902,43 @@ function cmd_pay_stamp_click(){
|
|||
|
||||
|
||||
function cmd_pay_cancel_click(){
|
||||
var form = $$('form_bank_pay')
|
||||
var values = form.getValues()
|
||||
var data = {'opt': 'cancel', 'id_mov': values.id_mov}
|
||||
|
||||
var grid = $$('grid_cfdi_pay')
|
||||
if(grid.count() == 0){
|
||||
msg_error('El depósito no tiene facturas de pago activas')
|
||||
return
|
||||
}
|
||||
|
||||
msg = '¿Estás seguro de cancelar esta factura?\n\nESTA ACCIÓN NO SE PUEDE DESHACER'
|
||||
webix.confirm({
|
||||
title: 'Cancelar Factura',
|
||||
ok: 'Si',
|
||||
cancel: 'No',
|
||||
type: 'confirm-error',
|
||||
text: msg,
|
||||
callback:function(result){
|
||||
if(result){
|
||||
webix.ajax().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){
|
||||
values = data.json();
|
||||
if(values.ok){
|
||||
grid.updateItem(values.id, {'estatus': 'Cancelada'})
|
||||
msg_ok(values.msg)
|
||||
}else{
|
||||
msg_error(values.msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -336,8 +336,9 @@ var controls_banco_deposito = [
|
|||
]},
|
||||
{cols: [
|
||||
{view: 'label', label: '<b>Facturas por pagar: </b>'},
|
||||
{view: 'button', id: 'cmd_invoice_payed', label: 'Pagada directamente',
|
||||
type: 'iconButton', autowidth: true, icon: 'check-circle'},
|
||||
{view: 'button', id: 'cmd_invoice_payed', label: 'Solo marcar pagada',
|
||||
type: 'iconButton', autowidth: true, icon: 'check-circle',
|
||||
tooltip: 'No afecta a saldos'},
|
||||
]},
|
||||
grid_cfdi_por_pagar,
|
||||
{view: 'label', label: '<b>Facturas a pagar en este depósito: </b>'},
|
||||
|
|
|
@ -59,6 +59,9 @@ var menu_user = {
|
|||
}
|
||||
|
||||
|
||||
var link_forum = "<a class='link_forum' target='_blank' href='https://gitlab.com/mauriciobaeza/empresa-libre/issues'>Foro de Soporte</a>";
|
||||
|
||||
|
||||
var ui_main = {
|
||||
rows: [
|
||||
{view: 'toolbar', padding: 3, elements: [
|
||||
|
@ -68,7 +71,7 @@ var ui_main = {
|
|||
}
|
||||
},
|
||||
{view: 'label', id: 'lbl_title_main', label: '<b>Empresa Libre</b>'},
|
||||
{},
|
||||
{view: 'label', id: 'lbl_forum', label: link_forum, align: 'right'},
|
||||
menu_user,
|
||||
{view: 'button', id: 'cmd_update_timbres', type: 'icon', width: 45,
|
||||
css: 'app_button', icon: 'bell-o', badge: 0},
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<body>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
webix.debug = true;
|
||||
webix.debug = false;
|
||||
webix.i18n.setLocale("es-MX");
|
||||
</script>
|
||||
|
||||
|
|
Loading…
Reference in New Issue