Cuentas de banco para clientes
This commit is contained in:
parent
5ac614e079
commit
4f34599a6f
|
@ -1,6 +1,9 @@
|
|||
v 1.20.0 [04-oct-2018]
|
||||
v 1.20.0 [08-oct-2018]
|
||||
----------------------
|
||||
- Error #295
|
||||
- Mejora: Cuentas de banco para clientes
|
||||
|
||||
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
|
||||
|
||||
|
||||
v 1.19.1 [03-oct-2018]
|
||||
|
|
|
@ -6,8 +6,11 @@ siempre actualizado.** Solo se da soporte sobre la ultima versión de **Empresa
|
|||
Libre**.
|
||||
|
||||
|
||||
### 1.20.0 [04-oct-2018]
|
||||
### 1.20.0 [08-oct-2018]
|
||||
- Error [#295](https://gitlab.com/mauriciobaeza/empresa-libre/issues/295)
|
||||
- Mejora - Cuentas de banco para clientes
|
||||
|
||||
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
|
||||
|
||||
|
||||
### 1.19.1 [03-oct-2018]
|
||||
|
|
|
@ -550,13 +550,29 @@ class AppSATBancos(object):
|
|||
def __init__(self, db):
|
||||
self._db = db
|
||||
|
||||
# ~ def on_get(self, req, resp):
|
||||
# ~ values = req.params
|
||||
# ~ req.context['result'] = self._db.get_sat_bancos(values)
|
||||
# ~ resp.status = falcon.HTTP_200
|
||||
def on_get(self, req, resp):
|
||||
values = req.params
|
||||
req.context['result'] = self._db.get_satbancos(values)
|
||||
resp.status = falcon.HTTP_200
|
||||
|
||||
def on_post(self, req, resp):
|
||||
values = req.params
|
||||
req.context['result'] = self._db.satbancos(values)
|
||||
resp.status = falcon.HTTP_200
|
||||
|
||||
|
||||
class AppSociosCuentasBanco(object):
|
||||
|
||||
def __init__(self, db):
|
||||
self._db = db
|
||||
|
||||
def on_get(self, req, resp):
|
||||
values = req.params
|
||||
req.context['result'] = self._db.get_partners_accounts_bank(values)
|
||||
resp.status = falcon.HTTP_200
|
||||
|
||||
def on_post(self, req, resp):
|
||||
values = req.params
|
||||
req.context['result'] = self._db.partners_accounts_bank(values)
|
||||
resp.status = falcon.HTTP_200
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ from controllers.main import (AppEmpresas,
|
|||
AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios,
|
||||
AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco,
|
||||
AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina,
|
||||
AppInvoicePay, AppCfdiPay, AppSATBancos
|
||||
AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco
|
||||
)
|
||||
|
||||
|
||||
|
@ -59,6 +59,7 @@ api.add_route('/nomina', AppNomina(db))
|
|||
api.add_route('/invoicepay', AppInvoicePay(db))
|
||||
api.add_route('/cfdipay', AppCfdiPay(db))
|
||||
api.add_route('/satbancos', AppSATBancos(db))
|
||||
api.add_route('/socioscb', AppSociosCuentasBanco(db))
|
||||
|
||||
|
||||
# ~ Activa si usas waitress y NO estas usando servidor web
|
||||
|
|
|
@ -436,6 +436,12 @@ class StorageEngine(object):
|
|||
def get_cfdipay(self, values):
|
||||
return main.CfdiPagos.get_values(values)
|
||||
|
||||
def get_satbancos(self, values):
|
||||
return main.SATBancos.get_values(values)
|
||||
|
||||
def get_partners_accounts_bank(self, values):
|
||||
return main.SociosCuentasBanco.get_values(values)
|
||||
|
||||
def cfdipay(self, values):
|
||||
return main.CfdiPagos.post(values)
|
||||
|
||||
|
@ -444,3 +450,6 @@ class StorageEngine(object):
|
|||
|
||||
def satbancos(self, values):
|
||||
return main.SATBancos.post(values)
|
||||
|
||||
def partners_accounts_bank(self, values):
|
||||
return main.SociosCuentasBanco.post(values)
|
||||
|
|
|
@ -1536,6 +1536,21 @@ class SATBancos(BaseModel):
|
|||
opt = values.pop('opt')
|
||||
return getattr(cls, '_{}'.format(opt))(cls, values)
|
||||
|
||||
@classmethod
|
||||
def get_values(cls, values):
|
||||
opt = values.pop('opt')
|
||||
return getattr(cls, '_get_{}'.format(opt))(cls, values)
|
||||
|
||||
def _get_active(cls, values):
|
||||
rows = (SATBancos
|
||||
.select(
|
||||
SATBancos.id,
|
||||
SATBancos.name.alias('value'))
|
||||
.where(SATBancos.activo==True)
|
||||
.dicts()
|
||||
)
|
||||
return tuple(rows)
|
||||
|
||||
@classmethod
|
||||
def get_(cls):
|
||||
rows = SATBancos.select().dicts()
|
||||
|
@ -1575,6 +1590,16 @@ class SATBancos(BaseModel):
|
|||
log.error(msg)
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def get_by_name(cls, name):
|
||||
try:
|
||||
obj = SATBancos.get(SATBancos.name==name)
|
||||
return obj
|
||||
except SATBancos.DoesNotExist:
|
||||
msg = 'SATBancos no existe: {}'.format(key)
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
|
||||
class SATNivelesEducativos(BaseModel):
|
||||
name = TextField(index=True)
|
||||
|
@ -2697,6 +2722,73 @@ class Socios(BaseModel):
|
|||
return {'ok': False}
|
||||
|
||||
|
||||
class SociosCuentasBanco(BaseModel):
|
||||
socio = ForeignKeyField(Socios)
|
||||
banco = ForeignKeyField(SATBancos)
|
||||
cuenta = TextField(default='')
|
||||
clabe = TextField(default='')
|
||||
moneda = ForeignKeyField(SATMonedas, null=True)
|
||||
|
||||
class Meta:
|
||||
order_by = ('socio', 'banco', 'cuenta')
|
||||
indexes = (
|
||||
(('socio', 'banco', 'cuenta'), True),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '{} ({})'.format(self.banco.name, self.cuenta[-4:])
|
||||
|
||||
@classmethod
|
||||
def get_values(cls, values):
|
||||
opt = values.pop('opt')
|
||||
return getattr(cls, '_get_{}'.format(opt))(cls, values)
|
||||
|
||||
def _get_by_partner(self, values):
|
||||
id = int(values['id_partner'])
|
||||
rows = (SociosCuentasBanco.select(
|
||||
SociosCuentasBanco.id,
|
||||
SQL(" '-' AS delete"),
|
||||
SATBancos.name.alias('banco'),
|
||||
SociosCuentasBanco.cuenta,
|
||||
SociosCuentasBanco.clabe)
|
||||
.join(SATBancos).switch(SociosCuentasBanco)
|
||||
.where(SociosCuentasBanco.socio==id)
|
||||
.dicts())
|
||||
return tuple(rows)
|
||||
|
||||
@classmethod
|
||||
def post(cls, values):
|
||||
opt = values.pop('opt')
|
||||
return getattr(cls, '_{}'.format(opt))(cls, values)
|
||||
|
||||
def _new(self, values):
|
||||
values = util.loads(values['values'])
|
||||
bank = SATBancos.get_by_name(values['banco'])
|
||||
fields = {
|
||||
'socio': values['id_partner'],
|
||||
'banco': bank,
|
||||
'cuenta': values['cuenta'],
|
||||
'clabe': values['clabe'],
|
||||
}
|
||||
try:
|
||||
obj = SociosCuentasBanco.create(**fields)
|
||||
except IntegrityError as e:
|
||||
msg = 'Ya existe esta cuenta'
|
||||
data = {'ok': False, 'msg': msg}
|
||||
return data
|
||||
|
||||
msg = 'Cuenta de banco agregada correctamente'
|
||||
data = {'ok': True, 'id': obj.id, 'msg': msg}
|
||||
return data
|
||||
|
||||
def _delete(self, values):
|
||||
values = util.loads(values['values'])
|
||||
id = int(values['id'])
|
||||
q = SociosCuentasBanco.delete().where(SociosCuentasBanco.id==id)
|
||||
result = bool(q.execute())
|
||||
msg = 'Cuenta borrada correctamente'
|
||||
return {'ok': result, 'msg': msg}
|
||||
|
||||
|
||||
class Contactos(BaseModel):
|
||||
socio = ForeignKeyField(Socios)
|
||||
|
@ -8123,8 +8215,9 @@ def _crear_tablas(rfc):
|
|||
SATOrigenRecurso, SATTipoContrato, SATTipoDeduccion, SATTipoHoras,
|
||||
SATTipoIncapacidad, SATTipoJornada, SATTipoNomina, SATTipoOtroPago,
|
||||
SATTipoPercepcion, SATTipoRegimen,
|
||||
Socios, Contactos, ContactoCorreos, ContactoDirecciones, Empleados,
|
||||
ContactoTelefonos, Departamentos, Puestos,
|
||||
Socios, SociosCuentasBanco, Contactos, ContactoCorreos,
|
||||
ContactoDirecciones, Empleados, ContactoTelefonos, Departamentos,
|
||||
Puestos,
|
||||
Tags, Roles, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco,
|
||||
TipoCorreo, TipoDireccion, TipoPariente, TipoResponsable, TipoTelefono,
|
||||
TipoTitulo, TipoMovimientoAlumno, TipoMovimientoAlmacen,
|
||||
|
@ -8190,7 +8283,8 @@ def _migrate_tables(rfc=''):
|
|||
CfdiNominaHorasExtra, CfdiNominaIncapacidad, CfdiNominaJubilacion,
|
||||
CfdiNominaOtroPago, CfdiNominaOtros, CfdiNominaPercepciones,
|
||||
CfdiNominaRelacionados, CfdiNominaSeparacion, CfdiNominaSubcontratos,
|
||||
CfdiNominaTotales, SATNivelesEducativos, Roles, Permisos
|
||||
CfdiNominaTotales, SATNivelesEducativos, Roles, Permisos,
|
||||
SociosCuentasBanco
|
||||
]
|
||||
log.info('Creando tablas nuevas...')
|
||||
database_proxy.create_tables(tablas, True)
|
||||
|
|
|
@ -21,12 +21,12 @@ var cfg_partners = new Object()
|
|||
var partners_controllers = {
|
||||
init: function(){
|
||||
$$('cmd_new_partner').attachEvent('onItemClick', cmd_new_partner_click);
|
||||
$$('cmd_new_contact').attachEvent('onItemClick', cmd_new_contact_click);
|
||||
//~ $$('cmd_new_contact').attachEvent('onItemClick', cmd_new_contact_click);
|
||||
$$('cmd_edit_partner').attachEvent('onItemClick', cmd_edit_partner_click);
|
||||
$$('cmd_delete_partner').attachEvent('onItemClick', cmd_delete_partner_click);
|
||||
$$('cmd_save_partner').attachEvent('onItemClick', cmd_save_partner_click);
|
||||
$$('cmd_cancel_partner').attachEvent('onItemClick', cmd_cancel_partner_click);
|
||||
$$('cmd_cancel_contact').attachEvent('onItemClick', cmd_cancel_contact_click);
|
||||
//~ $$('cmd_cancel_contact').attachEvent('onItemClick', cmd_cancel_contact_click);
|
||||
//~ $$('cmd_partner_zero').attachEvent('onItemClick', cmd_partner_zero_click);
|
||||
$$('codigo_postal').attachEvent('onKeyPress', postal_code_key_press);
|
||||
$$('codigo_postal').attachEvent('onTimedKeyPress', postal_code_key_up);
|
||||
|
@ -40,6 +40,8 @@ var partners_controllers = {
|
|||
//~ $$('grid_partners').attachEvent('onSelectChange', grid_partners_on_select_change)
|
||||
|
||||
$$('partner_balance').attachEvent('onChange', partner_balance_on_change)
|
||||
$$('cmd_partner_add_account_bank').attachEvent('onItemClick', cmd_partner_add_account_bank_click)
|
||||
$$('grid_partner_account_bank').attachEvent('onItemClick', grid_partner_account_bank_click)
|
||||
default_config_partners()
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +91,8 @@ function cmd_new_partner_click(id, e, node){
|
|||
query = table_usocfdi.chain().find({fisica: true}).data()
|
||||
$$('lst_uso_cfdi_socio').getList().parse(query)
|
||||
$$('partner_balance').define('readonly', !cfg_partners['chk_config_change_balance_partner'])
|
||||
get_partner_banks()
|
||||
get_partner_accounts_bank(0)
|
||||
}
|
||||
|
||||
|
||||
|
@ -136,12 +140,14 @@ function cmd_edit_partner_click(){
|
|||
if(values.es_proveedor){
|
||||
$$('cuenta_proveedor').enable()
|
||||
}
|
||||
get_partner_accounts_bank(row['id'])
|
||||
}
|
||||
})
|
||||
|
||||
$$('multi_partners').setValue('partners_new')
|
||||
$$('tab_partner').setValue('Datos Fiscales')
|
||||
};
|
||||
get_partner_banks()
|
||||
}
|
||||
|
||||
|
||||
function cmd_delete_partner_click(id, e, node){
|
||||
|
@ -387,57 +393,6 @@ function is_supplier_change(new_value, old_value){
|
|||
}
|
||||
|
||||
|
||||
//~ function partner_reset_saldo(id){
|
||||
//~ webix.ajax().post('/partners', {opt: 'reset', id: 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){
|
||||
//~ msg = 'Saldo actualizado correctamente'
|
||||
//~ $$('grid_partners').updateItem(id, {saldo_cliente: 0.0})
|
||||
//~ $$('cmd_partner_zero').disable()
|
||||
//~ msg_ok(msg)
|
||||
//~ }
|
||||
//~ }
|
||||
//~ })
|
||||
//~ }
|
||||
|
||||
|
||||
//~ function cmd_partner_zero_click(){
|
||||
//~ var g = $$('grid_partners')
|
||||
//~ var row = g.getSelectedItem()
|
||||
//~ var saldo = row.saldo_cliente.to_float()
|
||||
|
||||
//~ if(saldo){
|
||||
//~ msg = '¿Estas seguro de poner en cero el saldo del cliente?<BR><BR>'
|
||||
//~ msg += 'ESTA ACCIÓN NO SE PUEDE DESHACER'
|
||||
//~ webix.confirm({
|
||||
//~ title: 'Saldo Cliente',
|
||||
//~ ok: 'Si',
|
||||
//~ cancel: 'No',
|
||||
//~ type: 'confirm-error',
|
||||
//~ text: msg,
|
||||
//~ callback:function(result){
|
||||
//~ if (result){
|
||||
//~ partner_reset_saldo(row.id)
|
||||
//~ }
|
||||
//~ }
|
||||
//~ })
|
||||
//~ }else{
|
||||
//~ $$('cmd_partner_zero').disable()
|
||||
//~ }
|
||||
//~ }
|
||||
|
||||
|
||||
//~ function grid_partners_on_select_change(){
|
||||
//~ $$('cmd_partner_zero').enable()
|
||||
//~ }
|
||||
|
||||
|
||||
|
||||
function rfc_lost_focus(prev_view){
|
||||
//~ var form = this.getFormView()
|
||||
//~ var values = form.getValues()
|
||||
|
@ -493,3 +448,162 @@ function partner_balance_on_change(new_value, old_value){
|
|||
this.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function cmd_partner_add_account_bank_click(){
|
||||
var form = $$('form_partner_account_bank')
|
||||
|
||||
if (!form.validate()){
|
||||
msg = 'Valores inválidos'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
var values = form.getValues()
|
||||
var id_partner = $$('form_partner').getValues().id
|
||||
|
||||
var account = {
|
||||
id_partner: id_partner,
|
||||
delete: '-',
|
||||
banco: $$('lst_partner_bank').getText(),
|
||||
cuenta: values.partner_account.trim(),
|
||||
clabe: values.partner_clabe.trim(),
|
||||
}
|
||||
|
||||
if(!account.cuenta){
|
||||
msg = 'La cuenta es requerida'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if(!account.cuenta.is_number()){
|
||||
msg = 'Solo digitos en la cuenta'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if(account.cuenta.length < 9){
|
||||
msg = 'Longitud incorrecta de la cuenta'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if(!account.clabe){
|
||||
msg = 'La CLABE es requerida'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if(account.clabe.length != 18){
|
||||
msg = 'La CLABE debe ser de 18 digitos'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if(!account.clabe.is_number()){
|
||||
msg = 'Solo digitos en la CLABE'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
var grid = $$('grid_partner_account_bank')
|
||||
|
||||
if(id_partner){
|
||||
partner_new_account_bank(account, grid)
|
||||
}else{
|
||||
grid.add(account)
|
||||
}
|
||||
|
||||
form.setValues({})
|
||||
}
|
||||
|
||||
|
||||
function get_partner_banks(){
|
||||
webix.ajax().get('/satbancos', {opt: 'active'}, function(text, data){
|
||||
var values = data.json()
|
||||
$$('lst_partner_bank').getList().parse(values)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function partner_new_account_bank(account, grid){
|
||||
webix.ajax().post('/socioscb', {opt: 'new', values: account}, {
|
||||
error: function(text, data, xhr) {
|
||||
msg = 'Error al guardar'
|
||||
msg_error(msg)
|
||||
},
|
||||
success: function(text, data, xhr) {
|
||||
var values = data.json()
|
||||
if(values.ok){
|
||||
account['id'] = values.id
|
||||
grid.add(account)
|
||||
msg_ok(values.msg)
|
||||
}else{
|
||||
msg_error(values.msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function get_partner_accounts_bank(id_partner){
|
||||
var grid = $$('grid_partner_account_bank')
|
||||
grid.clearAll()
|
||||
|
||||
if(id_partner){
|
||||
var data = {opt: 'by_partner', id_partner: id_partner}
|
||||
webix.ajax().get('/socioscb', data, {
|
||||
error: function(text, data, xhr) {
|
||||
msg = 'Error al consultar'
|
||||
msg_error(msg)
|
||||
},
|
||||
success: function(text, data, xhr) {
|
||||
var values = data.json()
|
||||
grid.parse(values)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function grid_partner_account_bank_click(id, e, node){
|
||||
if(id.column != 'delete'){
|
||||
return
|
||||
}
|
||||
|
||||
var msg = '¿Estás seguro de eliminar la cuenta de banco seleccionada?<BR><BR>'
|
||||
msg += 'ESTA ACCION NO SE PUEDE DESHACER'
|
||||
webix.confirm({
|
||||
title: 'Eliminar cuenta de banco',
|
||||
ok: 'Si',
|
||||
cancel: 'No',
|
||||
type: 'confirm-error',
|
||||
text: msg,
|
||||
callback:function(result){
|
||||
if (result){
|
||||
partner_delete_account_bank(id.row)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function partner_delete_account_bank(row){
|
||||
var grid = $$('grid_partner_account_bank')
|
||||
|
||||
webix.ajax().post('/socioscb', {opt: 'delete', values: {id: row}}, {
|
||||
error: function(text, data, xhr) {
|
||||
msg = 'Error al eliminar'
|
||||
msg_error(msg)
|
||||
},
|
||||
success: function(text, data, xhr) {
|
||||
var values = data.json()
|
||||
if(values.ok){
|
||||
grid.remove(row)
|
||||
msg_ok(values.msg)
|
||||
}else{
|
||||
msg_error(values.msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -165,6 +165,50 @@ var controls_others = [
|
|||
]
|
||||
|
||||
|
||||
var grid_partner_account_bank_cols = [
|
||||
{id: 'id', header: 'ID', hidden: true},
|
||||
{id: 'delete', header: '', width: 30, css: 'delete'},
|
||||
{id: 'banco', header: 'Banco', fillspace: 1},
|
||||
{id: 'cuenta', header: 'Cuenta', fillspace: 1},
|
||||
{id: 'clabe', header: 'CLABE', fillspace: 1},
|
||||
//~ {id: 'moneda', header: 'Moneda', fillspace: 1},
|
||||
]
|
||||
|
||||
|
||||
var grid_partner_account_bank = {
|
||||
view: 'datatable',
|
||||
id: 'grid_partner_account_bank',
|
||||
select: 'row',
|
||||
adjust: true,
|
||||
autoheight: true,
|
||||
columns: grid_partner_account_bank_cols,
|
||||
}
|
||||
|
||||
|
||||
var controls_partner_bank = [
|
||||
{template: 'Agregar cuenta de banco', type: 'section'},
|
||||
{view: 'form', id: 'form_partner_account_bank', rows: [
|
||||
{cols: [
|
||||
{view: 'richselect', id: 'lst_partner_bank', name: 'partner_bank',
|
||||
label: 'Banco: ', required: true, options: []},
|
||||
{view: 'text', id: 'partner_account', name: 'partner_account',
|
||||
label: 'Cuenta: ', required: true},
|
||||
{view: 'text', id: 'partner_clabe', name: 'partner_clabe',
|
||||
label: 'CLABE: ', required: true},
|
||||
]},
|
||||
{minHeight: 10},
|
||||
{cols: [{},
|
||||
{view: 'button', id: 'cmd_partner_add_account_bank', maxWidth: 200,
|
||||
label: 'Agregar cuenta'},
|
||||
{}]},
|
||||
],
|
||||
},
|
||||
{minHeight: 20, maxHeight: 20},
|
||||
{template: 'Cuentas de banco existentes', type: 'section'},
|
||||
grid_partner_account_bank,
|
||||
{minHeight: 50},
|
||||
]
|
||||
|
||||
|
||||
var toolbar_contacts = [
|
||||
{view: 'button', id: 'cmd_new_contact', label: 'Nuevo', type: 'iconButton',
|
||||
|
@ -302,11 +346,12 @@ var controls_partner = [
|
|||
{
|
||||
view: 'tabview',
|
||||
id: 'tab_partner',
|
||||
tabbar: {options: ['Datos Fiscales', 'Otros Datos', 'Contactos']}, animate: true,
|
||||
tabbar: {ptions: ['Datos Fiscales', 'Otros Datos', 'Cuentas de Banco']},
|
||||
animate: true,
|
||||
cells: [
|
||||
{id: 'Datos Fiscales', rows: controls_fiscales},
|
||||
{id: 'Otros Datos', rows: controls_others},
|
||||
{id: 'Contactos', rows: [multi_contacts]},
|
||||
{id: 'Cuentas de Banco', rows: controls_partner_bank}
|
||||
]
|
||||
},
|
||||
{rows: [
|
||||
|
|
Loading…
Reference in New Issue