Procedures reutilizados en varios forms en programación en capas

597 views
Skip to first unread message

Jorge González

unread,
Jun 13, 2016, 10:00:43 PM6/13/16
to Comunidad de Visual Foxpro en Español
Hola muchachos, tiempo sin escribir pero siempre leyendo los post.

Sigo desarrollando el nuevo sistema pero ahora mas estructurado y en programación en capas.
Hay solicitudes comunes en varios módulos o formularios como lo es:
Validar el código de un cliente, de un proveedor, de un productos
asi como, mostrar una lista de ellos según criterio o
validar o traer el registro de un correlativo de factura dado.
Me he dado cuenta que tener estas pequeñas rutinas en una capa de datos que contenga el manejo completo de un formulario resulta algo tedioso porque habría que instanciar un objeto a toda esa capa solo por dos o tres procedures.

Sería conveniente usar una capa con solo esos procedures e instanciarlo a un objeto?

Por ejemplo:
Tomemos el caso de un formulario de registro de factura. En la capa de negocios tengo lo siguiente:

DEFINE CLASS cl_modfact_bus as CUSTOM
oDatos=NULL
oDb=NULL
oDatosComun=NULL
oDbComun=NULL

PROCEDURE INIT
   This.oDb=NEWOBJECT('cl_modfact_db','modfact_db.prg')
   This.oDb=NEWOBJECT('cl_ComunProcedures_db','ComunProcedures_db')
ENDPROC

PROCEDURE ValidarCodigoCliente
   PARAMETERS CodigoCliente
   RETURN This.oDbComun.ValidarCodigoCliente(CodigoCliente)
ENDPROC

PROCEDURE SolicitaValidarCodigoCliente
   PARAMETERS CodigoCliente
   This.oDatosComun=This.ValidarCodigoCliente(CodigoCliente)
ENDPROC
ENDDEFINE

Explico:
El objeto oDbComun está instaciado a una capa de datos contiene los procedures que retornan información sobre estas tablas que son utilizadas en la mayoría de los módulos o formularios
y el objeto oDb está instaciado a una capa de datos que manejan procedures que solo son utilizados en el módulo actual.

Está técnica estaría bien aplicada o no?

Gracias

Ernesto

unread,
Jul 1, 2016, 2:42:09 PM7/1/16
to Comunidad de Visual Foxpro en Español
Esta bien lo que haces de mantener la lógica de negocio en una capa aparte (cl_modfact_bus) 

Lo que estas haciendo mal es que estas instanciando la clase en cada formulario, 
en su lugar tenes que agregar los objetos al _screen de visual foxpro .

El _Screen de visual foxpro es la raíz de todo, es como el "namespace" en java o c# o como $_SERVER['DOCUMENT_ROOT'] en php. 

_screen.NewObject('cl_modfact_db','modfact_db.prg')

Tenes que tener un main PRG que es el que se encarga de arrancar la aplicación e ir instanciando  todos los objetos una sola vez y dejarlos disponibles para que sean accesibles desde cualquier lugar de la aplicacion.

_screen.cl_modfact_db.metodo()
_screen.cl_modfact_db.propiedad()

esto lo podes acceder desde cualquier formulario, o  desde cualquier otro objeto 

Incluso yo te recomendaría que dividas los mas que puedas toda la lógica del negocio, en por lo menos las siguientes partes

  1. Objeto De Acceso a Datos 
    1. Objeto de Validación de Datos (Validacion de datos como el que pones en el ejemplo)
    2. Objeto de Definicion de Datos (instrucciones ALTER TABLE, CREATE TABLE, etc)
    3. Objeto de Consulta de Datos (instrucciones SELECT )
    4. Objeto de Actualizacion de Datos (instrucciones INSERT UPDATE DELETE )
  2. Objeto de Seguridad (Encriptar y Desencriptar contraseña, Login Form etc)
  3. Objeto Visuales 
    1. Listados (muestra la tabla que le envies en un grid)
    2. PanelSingle(muestra un registro para edicion y actualizacion )
  4. Objetos Reporteador (prepara los cursores para cada archivo FRX)
En fin podes crear la Jerarquía que necesites, con esto ya vas construyendo tu propio Framework y cada que tenes un nuevo desarrollo solo reutilizas todo lo que ya tengas hecho. 

Otra cosa que yo no te recomendaría es que crees la definicion de la clase en un prg, yo prefiero el diseñador visual de clases, para las clase que no tienen representación visual se usa la base class custom y ahi podes ir agregando los metodos y propiedades visualmente y agregando el código etc no se vos pero yo lo siento mejor. 

Una desventaja de Foxpro es que no tenemos clases abstractas y por eso hay que instanciar de antemano todos los objetos que vamos a utilizar con lo cual el uso de memoria aumenta, no como en los lenguajes modernos donde se va cargando a memoria lo que se va usando. 

espero te sirva !

Jorge González

unread,
Jul 2, 2016, 9:14:21 AM7/2/16
to publice...@googlegroups.com
Gracias por interesarte en mi post amigo.
Me llama la atención de crear capas de todas las consultas que pueda hacer, pero, para la actualización de datos me dices que cree una capa para la actualización de todas las tablas?
Ahora fijate algo:
Yo Tengo un PRG inicial del sistema y no instancio los objetos en los formularios, yo lo hago es en el prg principal de cada módulo, es decir, para el módulo de facturación tengo un prg principal o que inicia dicho módulo: factmain.prg y en ese prg instancio los objetos y luego activo el form de facturación. Eso no sería practicamente lo mismo que me explicas?
Entonces por cada módulo tengo una capa de negocios y una capa de datos.
Modulo de clientes: clientemain.prg / cl_clientes_bus.prg / cl_cliente_db.prg
Módulo de facturación: factmain.prg / cl_fact_bus.prg / cl_fact_db.prg

Te adjunto una gráfica con lo explicado de manera general

Lo que si me gusta es tener capas disponibles para consultas. Y las capas disponibles para actualización de datos creo que me ayudaría a simplificar los códigos en las capas de datos de cada módulo.
--
Jorge González
GraficoCapas.png

Fernando D. Bozzo

unread,
Jul 3, 2016, 2:41:52 PM7/3/16
to Jorge González, publice...@googlegroups.com
Hola Jorge:

Hay varias formas de modularizar un sistema, que dependiendo del tamaño puede convenir hacerlo de una forma u otra.

En tu caso, una posible modularización que podrías usar es la basada en un objeto público oApp, del que cuelgue todo lo demás, como te voy a detallar a continuación, pero antes de hacerlo te comento que, como comenta Ernesto, también podrías hacerlo colgar desde _screen. Yo prefiero un objeto oApp público porque es más rápido su acceso y además se libera más rápido y mejor. Estructuras complejas de objetos o incluso ciertos manejos especiales con los mismos, hacen que _screen no sea una buena opción porque hay ocasiones en las que las cosas pueden no funcionar como deberían (me ha pasado).

Bueno, te comento un posible modelo de objetos que podrías usar, partiendo de oApp:

oApp
   
/Interfaz
   
/Bus
     
/Cliente
             
/Datos
     
/Factura
             
/Datos
     
/Proovedor
               
/Datos
     
/Producto
             
/Datos
     
/Comun
           
/Datos



oApp sería el objeto principal de la aplicación, cargado por el programa principal. oApp se encargaría de cargar los objetos Bus e Interfaz.

Bus te podría dar acceso a los objetos de negocio, que también debería cargar, e Interfaz te permitiría cargar los forms.

La ventaja de este modelo es que al ser oApp público, te permite acceder desde cualquier parte del sistema. Por ejemplo, dentro de un form, podrías validar el mismo código de cliente de tu ejemplo con una sintaxis del tipo:

oApp.Bus.Cliente.ValidarCodigo(cCodigo)

También podrías hacer un listado de proveedores con:

oApp.Bus.Proveedor.ListarAlfabetico(cCodProv)

o guardar los datos de una factura con:

oApp.Bus.Factura.Guardar( oDatos )

o cargar un form de productos con:

oApp.Interfaz.CargarFormProductos()


En fin, realmente hay muchas variantes que se pueden usar. A mí una cosa que me gusta de este modelo es que, bien implementado, te permite controlar toda la aplicación e incluso te permite implementar procesos masivos o Batch usando lo mismo. Por ejemplo, un proceso masivo normalmente no lleva formulario, sino que son métodos de negocio que trabajan sobre un grupo de datos y que como mucho pueden mostrar un indicador de progreso.


Respecto de lo que comenta Ernesto, te propone algunas buenas opciones, aunque en otras se equivoca, por ejemplo:


> Lo que estas haciendo mal es que estas instanciando la clase en cada formulario, en su lugar tenes que agregar los objetos al _screen de visual foxpro
No está "mal" instanciar las clases en cada formulario, simplemente es otra forma de hacerlo.


> Otra cosa que yo no te recomendaría es que crees la definicion de la clase en un prg, yo prefiero el diseñador visual de clases
Esto es cuestión de preferencias. La ventaja de un PRG es que no se corrompe y además lo podes usar directamente para comparar con versiones anteriores si usás Control de Código Fuente


> Una desventaja de Foxpro es que no tenemos clases abstractas y por eso hay que instanciar de antemano todos los objetos que vamos a utilizar con lo cual el uso de memoria aumenta

Aquí hay 2 temas distintos y mal entendidos:

1) FoxPro sí permite clases abstractas (definición de Wikipedia). Una clase abstracta simplemente es una clase "sin código", o sea, sin implementar los métodos que define, lo que se usa para que las clases que hereden de la misma puedan tener los mismos nombres de métodos y facilitar así el polimorfismo. De hecho una de las cosas que te podría servir de este tema es hacer una clase abstracta para los objetos BUS (Clientes, Productos, Facturas, etc) donde compartan una interfaz comun (interfaz en sentido de métodos de negocio, no de interfaz gráfica). Por ejemplo, todas las clases Bus pueden tener en común métodos como "Inicializar", "Guardar", "Buscar", "Recuperar", etc., y luego hacer una subclase de esa clase abstracta con el nombre final.

2) El uso de memoria no aumenta por subclasar una misma clase varias veces en distintos formularios. Quiero decir, si estuviéramos en 1980 con un ATARI y unos pocos Kilobytes de memoria, entonces sí podríamos preocuparnos de eso, pero con las PC actuales que no tienen menos de 2 GB de memoria RAM, preocuparse por unos pocos MB no tiene sentido. De hecho, cuando vos instanciás varias veces la misma clase en distintos formularios, FoxPro "reutiliza" las definiciones de librerías que haya en memoria, y no vuelve a cargarlas en memoria "otra vez". Es lo mismo que cuando hacés un SET PROCEDURE, que carga las librería que indiques en memoria, aunque uses un solo procedimiento. Lo que realmente consume "memoria" RAM son los datos, tanto de las variables locales, provadas o públicas, como las guardadas en propiedades, pero lo que realmente gasta más memoria son los datos se las tablas que se carguen en memoria mediante Select-SQL y otros mecanismos. Es aquí donde realmente hay que ser cuidadoso, y no hacer por ejemplo "select * from clientes" solamente para saber cuántos clientes hay, ya que estarías levantando toda la tabla en memoria para un simple COUNT.

3) FoxPro también permite cargar en memoria partes de la aplicación a medida que se necesitan. Simplemente es cuestión de las técnicas de programación que se usen.



Saludos!




---------- Mensaje reenviado ----------
De: Jorge González <jrockg...@gmail.com>
Fecha: 2 de julio de 2016, 8:44
Asunto: Re: [vfp] Re: Procedures reutilizados en varios forms en programación en capas
Para: publice...@googlegroups.com



--
Jorge González

mapner

unread,
Jul 4, 2016, 2:31:57 PM7/4/16
to Comunidad de Visual Foxpro en Español
Una simple observación, Clase Abstracta es la que no puede ser instanciada en forma directa sino a través de las clases que la hereden, en VFP no existe un real uso de esto ya que no hay una cláusula Abstract en la definición y toda clase que sea declarada puede ser usada. 
Las "clases" que no tienen código de implementación en sus métods son las llamadas "Interfaces" tal como se las usa en Java, C# y otros lenguajes OOP. 

Saludos 

Fernando D. Bozzo

unread,
Jul 4, 2016, 4:27:30 PM7/4/16
to publice...@googlegroups.com
Hola Mapner:

Creo que tenés razón en parte. Si bien es cierto que las clases abstractas "no pueden" implementarse porque el propio lenguaje no lo permite (en general solo lenguajes fuertemente tipados), se pueden "simular" fácilemente en los demás lenguajes poniendo esa restricción del lado del diseño, es decir, conviniendo que ciertas clases se usarán como abstractas y no se instanciarán nunca directamente.
Incluso estas clases abstractas pueden tener algunos métodos abstractos y algunos no.

Respecto de las interfaces, aquí sí que no pueden contener código, pero eso no es lo único que las define, sino que además no tienen estado, o sea que no tienen propiedades que mantengan valores, por eso en FoxPro la única forma de hacer interfaces es mediante objetos COM+, mientras que para hacer (o simular) clases abstractas --que sí pueden tener propiedades--, se pueden hacer en código simple sin necesidad de hacer COM.






Jorge González

unread,
Jul 4, 2016, 4:53:12 PM7/4/16
to publice...@googlegroups.com
Estuve investigando acerca de que la capa de datos sea con los procedimientos almacenados en la BD de Postgresql y al parecer es mas sencillo de lo que imaginaba.

Para empezar en PostgreSql a diferencia de otros motores de bases de datos, no existe una distinción explicita entre una función y un procedimiento almacenado. En PostgreSql solo existen funciones, claro que estas pueden ser usadas a modo de una función o de un procedimiento almacenado

Con ayuda del pgadmin III se pueden crear estas funciones, se agregan en el schema de la base de datos que se quiere trabajar y estas pueden ser llamadas desde la capa de negocios con el comando SQLEXEC() con el nombre de la función y los parametros solicitados

Por ejemplo, el siguiente boque de código se programaría en el editor SQL del PGadmin III

CREATE OR REPLACE FUNCTION ginscripcion(xcontrol character, xnombre character, xdocumento character, xf_nacimiento date, xiglesia integer)
  RETURNS character varying AS
$BODY$
 declare

 
    
BEGIN

    IF xcontrol = 'I' THEN
INSERT INTO inscritos(nombre,documento,f_nacimiento,iglesia)
VALUES (xnombre,xdocumento,xf_nacimiento,xiglesia);
    ELSE
--UPDATE extminist set cargo = xcargo,ministerio =xministerio where extminist.idextr = xidextr; 
    END IF;
  

RETURN xcontrol;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
ALTER FUNCTION ginscripcion(character, character, character, date, integer)
  OWNER TO postgres;

Luego se agregaría como function al squema y desde la capa de negocios (pienso que debería ser así) se hace el llamado:

Procedure SolicitaRegistroActualiz
  PARAMETERS xcontrol, xnombre, xdocumento, xf_nacimiento, xiglesia
  sqlResult = SQLEXEC(mCnnBD, 'SELECT ginscritos(?xcontrol, ?xnombre, ?xdocumento,   ?xf_nacimiento, ?xiglesia)
ENDPROC

Fuente: http://edgartec.com/procedimientos-almacenados-en-postgresql/


--
Jorge González
Funciones en Postgresql.docx

Jorge González

unread,
Jul 4, 2016, 5:02:21 PM7/4/16
to publice...@googlegroups.com
--
Jorge González

mapner

unread,
Jul 4, 2016, 7:05:48 PM7/4/16
to Comunidad de Visual Foxpro en Español
F.B.:

Simplemente lo observaba ya que mencionabas que "Una clase abstracta simplemente es una clase sin código" (?) en esto último creo que se confunde "clase abstracta" con el concepto de "interfaz".

Clase abstracta: no se puede implementar en forma directa sino a través de las clases que la hereden
Interfaz: Forma de protocolizar que métodos (nombre, parámetros y tipos) intervendrán en las clases que las suscriban. La interfaz no implementa código en los métodos y de hecho no tiene estado dado que no es una clase. 

Yuri Ernesto Calderón

unread,
Jul 5, 2016, 9:25:15 AM7/5/16
to publice...@googlegroups.com

Entonces mejor aún por que ya tenes todo bien estructurado,  lo que debes de hacer es tener jerarquía,  en mis inicios con fox pro yo compre un Framework,  y ahí vi como ellos trabajan,  lo que te he dicho lo he tomado de ahí el Framework todavia existe

http://www.promatrix.com/index.php/products

La versión standard es free, para que podas observarla  hoy ya no la uso por que ya he desarrollado bastante mi propio Framework (aunque me falta) sin. Embargo men sirvió bastante para entender a como trabajar con clases y objetos.

F.  Ernesto Calderón
www.tiservicios.net

Jorge González

unread,
Jul 5, 2016, 11:40:12 AM7/5/16
to publice...@googlegroups.com
Amigos del Foro, después de tanto investigar he aqui el código en lenguaje plpssql que va en la función dentro de Postgresql

CREATE OR REPLACE FUNCTION retornanombrecliente(
    IN ccodigo character,
    OUT cnombre character)

  RETURNS character AS
$BODY$
BEGIN
    SELECT into cnombre clientes.rsocial_cliente FROM clientes WHERE codigo_cliente=ccodigo;  
END;
$BODY$
  LANGUAGE plpgsql


Para verificarlo aplico el siguiente comando SQL dentro de postgresql

select * from retornanombrecliente('000001')

y desde VFP:

PUBLIC cHandle, lcConnect, a1
LOCAL loEx AS Exception
cHandle=0
lcConnect='DRIVER={POSTGRESQL ANSI};DATABASE=db01;SERVER=localhost;PORT=5432;UID=postgres;PWD=******;'
cHandle=SQLSTRINGCONNECT(lcConnect,.t.)
CLEAR

******************************************************************************************
*                                            Llamando a una función almacenado en postgresql                                                                                   
******************************************************************************************
LOCAL cNombreDeCliente
TEXT TO Linea_cmd NOSHOW PRETEXT 15
   SELECT * from retornanombrecliente('000001') && <== aqui está el llamado con el nombre de la función y el parametro condic.
ENDTEXT
  a1=SQLEXEC(cHandle,Linea_Cmd,'qclientes')
   If a1 < 0
      = Aerror( laError )
      Messagebox(;
        "Error llamado de funcion almacenada" + Chr(13)+ ;
        "Nº de Error: " + Transform( laError( 1 ) ) + Chr(13)+ ;
        laError( 2 )  + Chr(13)+ ;
        laError( 3 ), 0 + 16, "Atención!" )
      Cancel
   ENDIF
   cNombreDeCliente=qClientes.cnombre
   WAIT WINDOW ALLTRIM(cNombreDeCliente)
--
Jorge González

Fernando D. Bozzo

unread,
Jul 5, 2016, 1:10:05 PM7/5/16
to publice...@googlegroups.com
Bien Jorge! :D


Jorge González

unread,
Jul 13, 2016, 2:54:46 PM7/13/16
to publice...@googlegroups.com
Tengo dudas con respecto al comportamiento que tienen algunos objetos declarados en la capa de negocios.
Es el caso del objeto oDatos.
Cuando se solicita datos vacíos los mismos se crean pero cuando corre el procedure.SolicitaLlenarCursorDetalleFactura() al finalizar el procedimiento estos dejan de ser objetos.

Desde un formulario hago el siguiente llamado:

oApp.factventas.SolicitaPrepararEntornoFacturar()
oApp.tarifario.SolictaCursorTarifAct()
oApp.tarifario.SolicitaLlenarCursorTarifario(ALLTRIM(ThisForm.Txtclientmanag1.VALUE))
oApp.factventas.SolicitaLlenarCursorDetalleFactura()
oApp.factventas.SolicitaLlenarDatosVacios(ALLTRIM(ThisForm.Txtclientmanag1.VALUE), ThisForm.Avdtextbox9.value)

Creo que el hecho de usar el objeto oDatos para ser el retorno del llenado del cursor detalle este es afectado.
Porque desaparecen estos objetos?
Debería usar otro objeto para usarlo en procedimientos en los que no use los objetos ya creados y asi no se alteren?

Ver imagenes del debug adjuntos


Aqui están las capas de negocio y datos

*- factmanag_bus
DEFINE CLASS cl_managfact_bus AS Custom
oDatos=NULL
oDb=NULL

PROCEDURE INIT
   This.odb=NEWOBJECT('cl_managfact_db','factmanag_db.prg')
ENDPROC

PROCEDURE RetornaDatosVacios()
   RETURN This.oDb.RetornaDatosVacios()
ENDPROC

PROCEDURE SolicitaDatosVacios
   This.oDatos=This.RetornaDatosVacios()
ENDPROC

PROCEDURE RetornaDatosEncabezadoLlenos
    PARAMETERS cCodigoCliente, cCodigoTranferencia
    RETURN This.oDb.LlenaDatosVacios(cCodigoCliente, cCodigoTranferencia)
ENDPROC    

PROCEDURE SolicitaLlenarDatosVacios
   PARAMETERS cCodigoCliente, cCodigoTranferencia
   This.oDatos=This.RetornaDatosEncabezadoLlenos(cCodigoCliente, cCodigoTranferencia)   
ENDPROC

PROCEDURE SolicitaCrearCursorDetalleFactura
   RETURN This.oDb.CreaCursorDetalleFactura()
ENDPROC

PROCEDURE SolicitaCrearCursorProductosNoTarifados
   RETURN This.oDb.CreaCursorpnt()
ENDPROC


PROCEDURE SolicitaPrepararEntornoFacturar
   PARAMETERS cCodigoCliente, cCodigoTranferencia
   This.oDatos=This.SolicitaCrearCursorDetalleFactura()
   This.oDatos=This.SolicitaCrearCursorProductosNoTarifadoS()
   This.SolicitaDatosVacios()
ENDPROC


------------------------------------------------------------------------------------------------------
*-factmanag_db.prg
DEFINE CLASS cl_managfact_db AS Custom

PROCEDURE RetornaDatosVacios
   LOCAL loReg
   loReg=CREATEOBJECT('EMPTY')
   ADDPROPERTY(loReg,'numero_factura','')
   ADDPROPERTY(loReg,'fecha_factura','')
   ADDPROPERTY(loReg,'control_factura','')
   ADDPROPERTY(loReg,'codigo_cliente','')  
   ADDPROPERTY(loReg,'rsocial_cliente','') 
   ADDPROPERTY(loReg,'codigo_credito','')
   ADDPROPERTY(loReg,'politica_credito','')
   ADDPROPERTY(loReg,'fecha_vence',{})
   ADDPROPERTY(loReg,'codigo_zona','')
   ADDPROPERTY(loReg,'nombre_zona','')
   ADDPROPERTY(loReg,'codigo_camion','')
   ADDPROPERTY(loReg,'codigo_tranfer','')
   ADDPROPERTY(loReg,'exento_factura',0.00)
   ADDPROPERTY(loReg,'baseimponible_factura',0.00)
   ADDPROPERTY(loReg,'iva_alicuota_factura',0.00)
   ADDPROPERTY(loReg,'iva_monto_factura',0.00)
   ADDPROPERTY(loReg,'total_factura',0.00)
   RETURN loReg
ENDPROC

PROCEDURE LlenaDatosVacios
  PARAMETERS cCodigoCliente, cCodigoTranferencia
  LOCAL m.CodigoZona, m.CodigoCredito, m.NombreZona, m.PoliticaCredito, m.RSocialCliente
   **************************************************
   * CLIENTE *
   **************************************************
   TEXT TO Linea_cmd NOSHOW PRETEXT 15 
       SELECT rsocial_cliente, codigo_zona, codigo_credito FROM clientes WHERE codigo_cliente=?cCodigoCliente
   ENDTEXT
   a1=SQLEXEC(nConexion,Linea_Cmd,'qpgCliente')
   If a1 < 0
 = Aerror( laError )
 Messagebox(;
"Error: Select Cliente!" + Chr(13)+ ;
"Nº de Error: " + Transform( laError( 1 ) ) + Chr(13)+ ;
laError( 2 )  + Chr(13)+ ;
laError( 3 ), 0 + 16, "Atención!" )
 CANCEL
   ELSE
     m.CodigoZona=qpgCliente.codigo_zona
     m.CodigoCredito=qpgCliente.codigo_credito
     m.RSocialCliente=qpgCliente.rsocial_cliente
   ENDIF
   **************************************************
   * zona *
   **************************************************
   TEXT TO Linea_cmd NOSHOW PRETEXT 15 
       SELECT nombre_zona FROM zonas WHERE codigo_zona=?m.CodigoZona
   ENDTEXT
   a1=SQLEXEC(nConexion,Linea_Cmd,'qpgzona')
   If a1 < 0
 = Aerror( laError )
 Messagebox(;
"Error: Select Zona!" + Chr(13)+ ;
"Nº de Error: " + Transform( laError( 1 ) ) + Chr(13)+ ;
laError( 2 )  + Chr(13)+ ;
laError( 3 ), 0 + 16, "Atención!" )
 CANCEL
ELSE
 m.NombreZona=qpgzona.nombre_zona
   ENDIF
   **************************************************
   * Política de crédito *
   **************************************************
   TEXT TO Linea_cmd NOSHOW PRETEXT 15 
       SELECT concepto_credito, dias_credito FROM clicred WHERE codigo_credito=?m.CodigoCredito
   ENDTEXT
   a1=SQLEXEC(nConexion,Linea_Cmd,'qpgpcredito')
   If a1 < 0
 = Aerror( laError )
 Messagebox(;
"Error: Select Política de crédito!" + Chr(13)+ ;
"Nº de Error: " + Transform( laError( 1 ) ) + Chr(13)+ ;
laError( 2 )  + Chr(13)+ ;
laError( 3 ), 0 + 16, "Atención!" )
 CANCEL
ELSE
 m.PoliticaCredito=qpgpcredito.concepto_credito
 m.ndiascredito=qpgpcredito.dias_credito
   ENDIF
   ************************************************** 
   oApp.FactVentas.oDatos.fecha_factura   =DATE()
   oApp.FactVentas.oDatos.codigo_cliente  =cCodigoCliente
   oApp.FactVentas.oDatos.rsocial_cliente =m.RSocialCliente
   oApp.FactVentas.oDatos.codigo_credito  =m.CodigoCredito
   oApp.FactVentas.oDatos.politica_credito=m.PoliticaCredito
   oApp.FactVentas.oDatos.codigo_tranfer  =cCodigoTranferencia
   oApp.FactVentas.oDatos.nombre_zona     =m.NombreZona
   oApp.FactVentas.oDatos.fecha_vence  =oApp.FactVentas.oDatos.fecha_factura+m.ndiascredito
   RETURN
ENDPROC 

PROCEDURE CreaCursorDetalleFactura
   CREATE CURSOR curdFacven(numero_factura c(6), codigo_producto c(6),  nombre_producto c(80), pesobruto_producto n(10,3), cantidad_cestas n(5,0), peso_cestas n(9,3), cantidad_bolsas n(9,0),;
   pesoneto_producto n(10,3), precio_producto n(12,2), codigo_alicuota c(2), alicuota_producto n(5,2), iva_detalle n(12,2), subtotal_detalle n(12,2), total_detalle n(12,2))
ENDPROC

PROCEDURE CreaCursorpnt  &&Cursor Productos que no están en tarifario
   CREATE CURSOR curpnt(codigo_producto c(6), nombre_producto c(80))
ENDPROC

PROCEDURE LlenaCursorDetalleFactura
   SELECT detalle  && CURSOR QUE SE CREO EN LA CAPA DE TRANFERENCIAS
   SCAN
     SELECT qProductos
     LOCATE FOR codigo_producto=detalle.codigo_producto
     SELECT curTarifario
     LOCATE FOR codigo_producto=detalle.codigo_producto
     IF FOUND()
        INSERT INTO curdFacven(codigo_producto, nombre_producto, pesobruto_producto, cantidad_cestas, peso_cestas, cantidad_bolsas,;
         pesoneto_producto, precio_producto, codigo_alicuota, alicuota_producto) VALUES(detalle.codigo_producto, detalle.nombre_producto, detalle.peso_despacho, detalle.cantidad_cesta,;
         detalle.peso_cesta, detalle.nbolsas, detalle.peso_recibido, curTarifario.precio_actual, qproductos.codigo_alicuota, qproductos.alicuota_producto)
         SELECT curDfacven
         REPLACE subtotal_detalle WITH pesoneto_producto*precio_producto
         REPLACE iva_detalle WITH subtotal_detalle*(alicuota_producto/100)
         REPLACE total_detalle WITH subtotal_detalle+iva_detalle
     ELSE
        INSERT INTO curpnt(codigo_producto, nombre_producto) VALUES(detalle.codigo_producto, detalle.nombre_producto)
     ENDIF
   ENDSCAN
ENDPROC

--
Jorge González
debug_1.png
debu_2.jpg

Antonio Meza

unread,
Jul 13, 2016, 3:18:50 PM7/13/16
to Comunidad de Visual Foxpro en Español
Hola te recomiendo darle una leída a un articulo sobre el manejo de datos entre capas!!!


NOTA: No resuelve tu problema, pero te puede cambiar la idea y aplicar lo que comento en el articulo, en vez de crear y limpiar propiedades de un objeto usar cursores.

saludos
Antonio Meza


El miércoles, 13 de julio de 2016, 13:54:46 (UTC-5), Jorge González escribió:
Tengo dudas con respecto al comportamiento que tienen algunos objetos declarados en la capa de negocios.
Es el caso del objeto oDatos.
Cuando se solicita datos vacíos los mismos se crean pero cuando corre el procedure.SolicitaLlenarCursorDetalleFactura() al finalizar el procedimiento estos dejan de ser objetos.

.................

Yuri Ernesto Calderón

unread,
Jul 13, 2016, 3:59:36 PM7/13/16
to publice...@googlegroups.com

No se si ayude pero yo creo todos los objetos anclandolos al objeto  _screen de visual fox,  yo lo trabajo así y no me da problema,  mira los post iniciales ahí lo explico detalladamente

Jorge González

unread,
Jul 13, 2016, 4:12:49 PM7/13/16
to publice...@googlegroups.com
Hola Antonio.
Esta muy bueno tu post.
Yo uso mucho cursores, ya sea para consulta  mantener datos para esto y sobre todo para el manejo del grid.
Y uso objetos para encabezados. Pudiera usar un cursor de un solo registro para los encabezados también, pero como tu dices, es como plantear un estilo de programación respetando las buenas costumbres.
Estoy estudiando el lenguaje pgsql porque quiero usar las funciones de postgresql en el servidor como capa de datos, pero como tengo que entregar fases de este sistema, haría eso en la fase de mantenimiento cuando tenga un dominio mas amplio de este lenguaje.
Tengo una duda con respecto a lo que explicas en tu post:

...
Si usamos el Cursor devuelto por el origen de datos como si fuera un Objeto, entonces el nombre del cursor pasaría a ser el nombre del objeto y los campos del cursor serian las propiedades del Objeto, por lo tanto nuestra clase oDatos_Clientes quedaría así de simple.

Define Class oDatos_clientes as Custom
     * Propiedades de Datos
          * Usar el cursor CLIENTES
     Procedure Obtener_Datos
        * Abrimos la tabla
           Select id, cliente from midatabase!clientes into cursor clientes readwrite
        * Asignamos las propiedades de Datos
           * Usar el cursor Clientes
        * Cerramos la tabla
           * No cerrar el cursor creado
     EndProc
EndDefine
en asterisco colocas * Asignamos las propiedades de datos y debajo, usar el cursor. 
Lo que entiendo es que en vez de asignar datos a objetos, simplemente usamos el cursor como si fuera un objeto pero no con el tratado directo de un objeto sino como se trata un cursor.
es asi?

De todas maneras. Me interesa saber como es el comportamiento del objeto que planteo. Es importante saberlo


--
Jorge González

Jorge González

unread,
Jul 13, 2016, 4:20:06 PM7/13/16
to publice...@googlegroups.com
Yuri.
Gracias por tu respuesta.
Lo que has colocado en tu primera respuesta me ayudado bastante en la lógica. Pero como dijo Fernando, ya tenía planteado lo del objeto principal declarado en el programa principal y de allí los demás objetos de acuerdo a su identidad.
Inclusive antes yo declaraba los objetos en un PRG main de cada módulo, pero esto me trajo como confusión la reutilización de código y de objetos. nunca los declaré en un form. En los formulario me limito solo hacer solicitudes a la capa de negocio y al comportamiento de la interface como tal, es decir, el menos código posible.

Ahora el plantearme el uso del _screen en estos momentos me traería replantear toda la lógica que he hecho hasta ahora y el tiempo no me lo permite porque tengo que entregar la primera fase planteada a mi cliente.

Pero tu información como la siguiente:
  1. Objeto De Acceso a Datos 
    1. Objeto de Validación de Datos (Validacion de datos como el que pones en el ejemplo)
    2. Objeto de Definicion de Datos (instrucciones ALTER TABLE, CREATE TABLE, etc)
    3. Objeto de Consulta de Datos (instrucciones SELECT )
    4. Objeto de Actualizacion de Datos (instrucciones INSERT UPDATE DELETE )
  2. Objeto de Seguridad (Encriptar y Desencriptar contraseña, Login Form etc)
  3. Objeto Visuales 
    1. Listados (muestra la tabla que le envies en un grid)
    2. PanelSingle(muestra un registro para edicion y actualizacion )
  4. Objetos Reporteador (prepara los cursores para cada archivo FRX)
fue algo que me ayudó bastante. Lo que hice fue sumarlo a la lógica que he venido manejando y profundizando mas en lo que Fernando ha planteado en otros post.

Ahora lo que quiero dominar es el comportamiento de los objetos en caso especídico el que yo declaro como oDatos que es creo que es el que recibe los return
--
Jorge González

Fernando D. Bozzo

unread,
Jul 13, 2016, 4:49:26 PM7/13/16
to publice...@googlegroups.com
Hola Jorge:

Te comento que está un poco confuso montado, y la separación de responsabilidades no se respeta, ya que el objeto oDatos puede acceder a su padre (bus) para cambiarle datos por medio de la referencia oApp.FactVentas.oDatos con el método LlenaDatosVacios().
Para ese caso tenés al menos 2 soluciones:

1) Que el método devuelva un objeto oDatos con los datos completados

2) (Mejor) Si ese método solo debe completar algunos datos y respetar otros pre-existentes, entonces lo que podrías hacer es pasarle por referencia un parámetros con el objeto oDatos para que los complete sobre la misma referencia, y de esa forma no se sale de su ámbito. Por ejemplo: LlenaDatosVacios(oApp.FacVentas.oDatos, otros-parametros), luego este método, que puede recibir este parámetro como toDatos, completaría los datos en la forma toDatos.fecha_factura = DATE() en vez de hacerlo como oApp.FactVentas.oDatos.fecha_factura = DATE()

Por otro lado, no sé que hace el método SolicitaLlenarCursorDetalleFactura() que te provoca el problema, ya que no lo pusiste, pero supongo que en algún momento borrará o reasignará el objeto oDatos y por eso te desaparece
.

Jorge González

unread,
Jul 13, 2016, 4:56:04 PM7/13/16
to publice...@googlegroups.com
Fernando. Fijate este cambio que hice en la capa de negocios:

PROCEDURE SolicitaLlenarDatosVacios
   PARAMETERS cCodigoCliente, cCodigoTranferencia
   *This.oDatos=This.RetornaDatosEncabezadoLlenos(cCodigoCliente, cCodigoTranferencia) 
   RETURN This.oDb.LlenaDatosVacios(cCodigoCliente, cCodigoTranferencia)  
ENDPROC

y funcionó pareciera que si uso this.odatos= reasigna el objeto. Tiene lógica.

Cual es la lógica de usar los statement:

This oDatos=this.ProcedimientoDeReturn()

y luego en

PROCEDURE ProcedimientoDeReturn()
   RETURN ProcedimientoEnCapaDatos()
ENDPROC

Si no voy a devolver ningun dato desde la capa de datos?

Por ejemplo si en un procedimiento en capa de datos voy a crear un cursor no le veo la lógica de usar This.oDatos=This.ProcedientoDeReturn()

Espero explicarme bien.

Yo me pierdo en el seguimiento de estos objetos

--
Jorge González

Jorge González

unread,
Jul 13, 2016, 4:59:02 PM7/13/16
to publice...@googlegroups.com
Tomando en cuenta tu suferencia:
Si ese método solo debe completar algunos datos y respetar otros pre-existentes, entonces lo que podrías hacer es pasarle por referencia un parámetros con el objeto oDatos para que los complete sobre la misma referencia, y de esa forma no se sale de su ámbito. Por ejemplo: LlenaDatosVacios(oApp.FacVentas.oDatos, otros-parametros), luego este método, que puede recibir este parámetro como toDatos, completaría los datos en la forma toDatos.fecha_factura = DATE() en vez de hacerlo como oApp.FactVentas.oDatos.fecha_factura = DATE()

Significa que por cada objeto sería un parametro?
No entiendo mucho el planteamiento del objeto toDatos, coloca un ejemplo
--
Jorge González

Fernando D. Bozzo

unread,
Jul 13, 2016, 6:11:49 PM7/13/16
to publice...@googlegroups.com
Hola Jorge:

Parte del problema es que estás haciendo el código directamente sobre la marcha sin hacer un pequeño esquema de diseño, y estás liando los objetos y las responsabilidades.

Tomando esto de tu código y reduciéndolo, fijate lo que estás haciendo:

1) Dentro de la capa de negocio estás asignando oDatos con lo que retorne el método SolicitaLlenarDatosVacios()

PROCEDURE SolicitaLlenarDatosVacios
   PARAMETERS cCodigoCliente, cCodigoTranferencia
   This.oDatos=This.RetornaDatosEncabezadoLlenos(cCodigoCliente, cCodigoTranferencia)   
ENDPROC


2) El método RetornaDatosEncabezadoLlenos() a su vez devuelve el resultado de LlenaDatosVacios():

PROCEDURE RetornaDatosEncabezadoLlenos
    PARAMETERS cCodigoCliente, cCodigoTranferencia
    RETURN This.oDb.LlenaDatosVacios(cCodigoCliente, cCodigoTranferencia)
ENDPROC


3) LlenaDatosVacios() tiene a su vez 2 problemas (3.a y 3.b):

PROCEDURE LlenaDatosVacios  PARAMETERS cCodigoCliente, cCodigoTranferencia
   * Consulta SQL
   oApp.FactVentas.oDatos.xxxxx = yyyyy
   RETURN
ENDPROC

3.a) Está "rellenando manualmente" el objeto oDatos, rompiendo la separación de responsabilidades, ya que no debería "asignar" nada fuera de este método, y el objeto oDatos está en otro objeto

3.b) Tu problema final: Luego de que se rellena oDatos manualmente, devolvés .T. (el RETURN del final), que al ser el retorno "oficial" de LlenaDatosVacios(), es devuelto a su vez por RetornaDatosEncabezadoLlenos() y éste lo devuelve a su vez a SolicitaLlenarDatosVacios() del principio, quien finalmente lo asigna a oDatos (la primera asignación de donde parte todo), dejando el objeto como una propiedad común con valor = .T.

Por eso perdés los datos y por eso un método de datos jamás debe asignar nada, sino solo devolver datos o asignar valores a un objeto pasado por parámetro.

Esto último es lo mismo que cuando te dan un formulario para rellenar, vos lo rellenás y lo devolvés. No te inventás un nuevo formulario vacío para sustituir al original.

Parte de la confusión que tenés también es provocada por la nomenclatura que elegiste, que no ayuda a entender los conceptos que estás manejando.

Por ejemplo, yo lo vería más claro así:

* En el programa o en un formulario
oApp.FacVentas.ObtenerDatosTransferenciaCliente(tcCodCliente, tcCodTransferencia)


* en el BUS:
PROCEDURE ObtenerDatosTransferenciaCliente(tcCodCliente, tcCodTransferencia)
   THIS.oDB.ObtenerDatosTransferenciaCliente(THIS.oDatos, tcCodCliente, tcCodTransferencia)
ENDPROC


* En DB:
PROCEDURE ObtenerDatosTransferenciaCliente(toDatos, tcCodCliente, tcCodTransferencia)
   * Consulta SQL
   toDatos.Fecha_Factura = DATE()
   ...demás asignaciones de toDatos
ENDPROC


Fijate que simple:

1) Se le pide al negocio (bus) obtener los datos de una transferencia de un cliente, y se le pasa el código de cliente y de transferencia

2) El negocio llama al método de mismo nombre de la capa de datos, pasándole el objeto a completar (THIS.oDatos)

3) La capa de datos recibe la petición y el objeto pasado como toDatos, hace la consulta SQL y finalmente rellena en toDatos las propiedades necesarias

Como ves, la capa de datos "no tiene ningún conocimiento" de nada fuera de ella, solo recibe un parámetro y lo completa, nada más, ni sabe ni le interesa que "allá afuera" existe un bus u otros objetos.
En este caso el objeto "se rellena" porque asumo que te interesa crearlo antes en otro sitio y luego agregarle datos, pero en un escenario donde eso no sea necesario, la capa de datos podría devolver directamente un objeto de datos nuevo relleno y listo para ser usado.


Ahora comparemos el antes/después:

ANTES:
- Cuando llamas a SolicitarDatosVacios() no se sabe "de qué tipo de datos" se está hablando, con lo que tampoco queda claro "qué uso" o utilidad puedan tener esos datos, y además estás solicitando "datos vacíos", lo que es es una contradicción, porque realmente estás pidiendo unos datos que no van a ser vacíos => confunde

- Luego llamas a otro método RetornaDatosEncabezadoLlenos(), donde tampoco queda claro "qué tipo de encabezado" estás retornando => más confusión

- Finalmente, desde la capa de datos ejecutás LlenarDatosVacios(), que realmente puede rellenar cualquier objeto de datos aunque ya tenga datos asignados. En este método asignás "externamente" el objeto de datos (rompe encapsulación) y finalmente devolvés .T. (el RETURN) que termina pisando los datos antes asignados. (Aunque quites el RETURN FoxPro devuelve .T. por defecto y te volvería a pisar los datos otra vez)


DESPUÉS:

- Llamo a un método de negocio ObtenerDatosTransferenciaCliente(), cuyo nombre me indica que va a "obtener datos" y que son referidos a una "transferencia" de un "cliente", lo que en si mismo me está dando toda la información necesaria para saber qué debería hacer el método.

- Finalmente llamo a un método (de mismo nombre entendible) en la capa de datos y pasándole como parámetro el objeto de datos a completar y los demás parámetros necesarios para la consulta SQL, que no ejecuta ni asigna nada fuera de su ámbito (está encapsulado)


Espero que esto te aclare un poco más la idea.


Antonio Meza

unread,
Jul 13, 2016, 6:32:10 PM7/13/16
to Comunidad de Visual Foxpro en Español
Hola Jorge!!!

Efectivamente, te evitas crear las propiedades que harán referencia al cursor, por ejemplo si tienes una tabla clientes en tu clase datos necesitas crear una propiedad por cada campo de la tabla clientes, entonces usando el cursor evitas 2 cosas, la primera crear las propiedades que harán referencia a los campos de la tabla y la segunda una vez que obtienes un registro pasar los valores de los campos a las propiedades de la clase.

* Lo normal y correcto seria el nombre de la clase y su propiedad
? oClientes.id

Aprovechando los cursores seria el nombre de la tabla y su campo

saludos
Antonio Meza

Jorge González

unread,
Jul 13, 2016, 6:42:32 PM7/13/16
to publice...@googlegroups.com
Aclara y bastante Fernando. Sobre todo la parte de manejo de objetos. Mi desconocimiento estaba en que enviando como parametro this.oDatos desde la capa de negocio y recibiéndola como toDatos en la capa de datos pues esta recibía las propiedades de oDatos pero como ToDatos. Es decir. ToDatos es la planilla que Bus envía a la capa de datos para que está lo rellene. Ahora, donde asigno los nuevos valores de oDatos obtenidos desde la capa de datos como toDatos? estos son automáticos?

Porque creo primero a oDatos con propiedades vacías y luego las relleno?
Porque voy a dar la posibilidad de dos formas de facturar por lo que estos procedure deben ir separados. Si usara solo una forma, pues cuando cree las propiedades de oDatos las relleno de una vez.

Con respecto a la nomenclatura pues estás en lo cierto, debo elegir nombres mas especificos a su naturaleza, ya lo voya replantear antes de avanzar porque si avanzo sería aún mas confuso.
--
Jorge González

Jorge González

unread,
Jul 14, 2016, 12:41:58 AM7/14/16
to publice...@googlegroups.com
Tengo una duda sobre responsabilidades

Tengo un procedimiento almacenado en la base de datos Postgresql que me retorna el nombre de un chofer. Este nombre lo quiero almacenar en oApp.Tranf.oDatos.Nombre_chofer

Hago el llamado a la capa de negocios de esta manera:

oApp.Tranf.oDatos.Nombre_chofer=oApp.choferes.SolocitaNombreChofer(oApp.Tranf.oDatos.Codigo_Chofer)

y en la capa de negocios tengo lo siguiente:

*- choferes_bus.prd
DEFINE CLASS cl_choferes_bus AS Custom

PROCEDURE SolocitaNombreChofer
  PARAMETERS cCodigoChofer
  LOCAL cNombreDeChofer
  TEXT TO Linea_cmd NOSHOW PRETEXT 15
    SELECT * from retornanombrechofer(?cCodigoChofer)  &&retornanombrechofer es el proceso almacenado
  ENDTEXT
  a1=SQLEXEC(nConexion,Linea_Cmd,'qnombrechofer')
   If a1 < 0
 = Aerror( laError )
 Messagebox(;
"Error llamado de funcion RetornaNombreChofer almacenada" + Chr(13)+ ;
"Nº de Error: " + Transform( laError( 1 ) ) + Chr(13)+ ;
laError( 2 )  + Chr(13)+ ;
laError( 3 ), 0 + 16, "Atención!" )
 Cancel
   ENDIF
   cNombreDeChofer=ALLTRIM(qnombrechofer.cnombre)
   USE IN SELECT('qnombrechofer')
   RETURN cNombreDeChofer
ENDDEFINE

Mi duda es si está bien planteado? ya que estoy colocando los procedimientos almacenados en postgresql como capa de datos o es necesario igual crear una capa de datos en VFP?
--
Jorge González

Fernando D. Bozzo

unread,
Jul 14, 2016, 2:37:45 AM7/14/16
to publice...@googlegroups.com

Sobre esta pregunta, cuando creas oDatos por primera vez, ya se debe crear con todas sus propiedades, por ejemplo con un método DatosNuevo() que cree el objeto, agregue las propiedades y lo devuelva.

Fernando D. Bozzo

unread,
Jul 14, 2016, 2:45:03 AM7/14/16
to publice...@googlegroups.com

En esta parte los datos deberían ser recuperados por la capa de datos, no por el negocio.

La asignación está bien hecha, aunque si tenés que actualizar varias propiedades te conviene guardar una referencia de objeto en una variable, que es más cómodo y corto, por ej:

loDatos = oApp.Transf.oDatos
loDatos.propiedad = valor

Te recomiendo que veas el proyecto de antonio para manejo de datos, que creo que te puede ser muy util para tu caso.

Saludos

Jorge González

unread,
Jul 14, 2016, 7:04:08 AM7/14/16
to publice...@googlegroups.com
Fernando.
el objeto oDatos en
oApp.Tranf.oDatos.cObjeto
está declarado en la capa de negocio tranf_bus.prg. No vi la necesidad de detallarlo en el ejemplo de la duda
Pero como uno de los datos es de la tabla choferes lo recupero desde las capas de choferes, pero puse el ejemplo usando store procedure de Postgresql (que como comenté en un post anterior, Postgresql no usa store procedure, usa funciones)

sobre tu segunda asesoría creo que asi mismo lo hice como tu lo planteas:

loDatos = oApp.Transf.oDatos
loDatos.propiedad = valor

y mi asignación es:
oApp.Tranf.oDatos.Nombre_chofer=oApp.choferes.SolicitaNombreChofer(oApp.Tranf.oDatos.Codigo_Chofer)

Como expliqué arriba oDatos.Nombre_chofer viene de la capa de negocios Tranf y el nombre es retornado por la capa de negocios choferes por el procedure SolicitaNombreChofer ==>  oApp.choferes.SolicitaNombreChofer

Entonces igualmente usaría en el VFP las tres capas y la capa de datos hace el llamado a las funciones dentro de PostgreSQL. Tiene lógica, por eso nació mi duda porque no se estila la manipulación de datos dentro de la capa de negocios. Es como si se trabajara entonces en cuatro capas y la cuarta serían las funciones dentro del postgresql.
Mi idea es usar funciones dentro de postgresql y que sean llamadas desde la capa de datos de VFP.
Solo que todavía me falta aprender varias cositas, luego las aplico.

Hay un lógica que no he explicado que es la siguiente:

EL nucleo de este sistema son las transferencia de mercancía entre cavas, las cavas están codificadas, se manejan cinco cavas

Compra de mercancia, entran en una cava
Compra de marcancia  ===> Nota de pesaje de ingreso a Codigo XX de cava  (Tabla de tranferencia con referencia de ingreso

Hay tranferencias en la semana entre cavas
Sale de una cava y entra en otra cava con nota de pesaje (tabla de tranferencia con referencia de tranferencia entre cavas

Tranferencia por producción
salidas de productos enteros y entradas de productos procesados o despresados por sala de producción, esto con referencia de salida y entrada por producción

Tranferencia de salida por ventas
Sale por medio de una nota de pesaje de salida (Tabla de tranferencia con referencia de salida por venta)

Las referencia son código que almaceno en un campo titulado tipo_tranferencia donde:
1=compras
2=entrada por devolución
3= salida por devolución
4=tranferencia entre cavas
5=salida por ventas
6=entrada y salida por producción

El movimiento de inventario se hace por cierre diario donde se toma toda esta información y se actualiza el inventario por medio de dos fases:
Fase de validación donde se ven los resumenes de movimiento. (Se puede visualizar por medio de un reporte los movimientos detallados)

Fase de actualización en donde se actualiza la tabla inventario

Facturación
Nota de pesaje de salida se factura.


El procedmiento es que el usuario Registra la nota de pesaje de salida por ventas en la tabla de tranferencia y este formulario tiene la opción de que dicha tranferencia sea facturada de una vez por medio de un botón titulado Facturar

Entonces cuando se realiza una tranferencia por ventas solo se usan los cursores y objetos para ello y si el usuario desea facturar en el momento al darle el clic al botón de facturar se hacen los llamados a las capas de facturación
En el programa app tengo lo siguiente:

*- app.prg
DEFINE CLASS cl_app AS Custom
clientes    =NULL
tranf        =NULL
tranfventas    =NULL
factventas    =NULL
tarifario    =NULL
choferes    =NULL
productos    =NULL

PROCEDURE INIT
   This.clientes    =NEWOBJECT('cl_clientes_bus','clientes_bus.prg')
   This.tranf       =NEWOBJECT('cl_tranfer_bus','tranfer_bus.prg')
   This.tranfventas =NEWOBJECT('cl_tranfv_bus','tranfv_bus.prg')
   This.choferes    =NEWOBJECT('cl_chofer_bus','chof_bus.prg')
   This.productos    =NEWOBJECT('cl_prod_bus','productos_bus.prg')
   This.factventas  =NEWOBJECT('cl_managfact_bus','factmanag_bus.prg')
   This.tarifario   =NEWOBJECT('cl_modulotarif_bus','tarifmod_bus.prg')
   This.choferes    =NEWOBJECT('cl_choferes_bus','choferes_bus.prg')
ENDPROC

ENDDEFINE

en cada archivo bus.prg asigno los objetos oDatos y oDb y cada oDb lo instancio a su respectiva capa de datos.
El objeto tranf está instanciado a las capas referentes a tranferencias
Ahora, existen tranferencia de entrada por compras de mercancía, tranferencia entre cavas por movimientos internos, ya que hay cavas de enfriamiento y cavas congeladoras, lo que estoy previsualizando es el tamaño de esta de tranferencias.

Estoy analizando si es mejor separar las capas por tipo de movimiento de mercancía, lo que hay que tomar en cuenta es que al momento de registrar en la tabla de tranferencias es lo  mismo, lo único que varía es que en uno de los movimientos se llenan unos campos y otros no.




--
Jorge González

Fernando D. Bozzo

unread,
Jul 14, 2016, 8:42:44 AM7/14/16
to publice...@googlegroups.com
Más allá de cómo hagas la separación que comentás al final, lo importante es entender que cuando hablamos de "capas" es a nivel lógico, o sea, decimos "interfaz, negocio, datos", pero eso no significa que tenga que haber una correspondencia de una librería por capa. Puede haber múltiples librerías por cada capa (lo podés ver como subcapas más específicas, o supcapas de apoyo a la principal).

Por ejemplo, la capa de datos puede tener varias librerías que se encarguen de distintas tablas, y en este caso, la parte de la capa de negocio se comunica con la capa de datos y esta última a su vez se comunica con la capa de datos de la BDD.

Desde la perspectiva de negocio local, al BUS le da igual si su capa de datos usa un DBF o hace una consulta SQL a Oracle, ya que por eso se usan nombres significativos y conceptuales como "ObtenerDatosTransferenciaCliente", donde negocio sabe lo que pide, pero no sabe (ni le importa) cómo hará la capa de datos para obtenerlos.

Por la parte de datos, al ser llamada desde el negocio siempre de la misma forma, eso te permite que lo que ahora llamás con alguna consulta SQL local a un DBF, mañana cambies la implementación y llames a una BDD externa (como lo que hacés). Para negocio eso debe ser transparente y justamente en esa separación negocio / datos es donde podés comprobar que un cambio de motor de datos solo repercute en la capa de datos (si el diseño del sistema está correcto, claro).

En tu ejemplo, fijate que si hay que cambiar algún parámetro de la consulta del chofer, terminás teniendo que modificar la implementación del metodo "SolicitarNombreChofer" en la capa de negocio, cuando eso no debería afectarte por ser de datos, y esa es justamente la indicación de que eso no debe hacerse ahí. Si afecta a Datos, no debe modificarse el negocio (excepto para agregar nuevos parámetros de entrada/salida)

Otra cosa:
Veo que usás mucho "MESSAGEBOX" y "CANCEL" en la capa de negocio. Eso está mal, ya que tanto la capa Datos como Negocio solamente deben devolver el error a la interfaz, que es quien debe mostrar cualquier mensaje necesario, lo que también muestra otro punto flojo, que es la ausencia de controles de errores.

Te haría falta una implementación de TRY/CATCH con recolección de basura y relanzamiento de errores hasta la interfaz para mostrar los MESSAGEBOX (o cualquier implementación de mensajes), ya que hoy es un messagebox y mañana puede ser cualquier otra visualización.

Si quisieras hacer un proceso tipo "BATCH" para procesar datos, no podrías, porque cualquier problema o validación que devuelva un error ahora mismo mostraría un MESSAGEBOX interrumpiendo el flujo. Nunca un elemento de interfaz puede ir en una capa distinta a la de la interfaz.

En el blog hice algunos ejemplos sobre el tema de errores y cómo llevarlo desde el negocio/datos a la interfaz:







Jorge González

unread,
Jul 15, 2016, 6:34:04 AM7/15/16
to publice...@googlegroups.com
Si fernando. Tengo tus manuales impresos, son un buen soporte.
Estas rutinas de messagebox las usé a modo de monitoreo pero por estar presionado por el tiempo lo dejé así pero ya voy a separar esa responsabilidad y colocarla donde es.
Como se hace en el caso de que yo quisiera retornar mas de un dato? voy a volver a revisar tus guías porque creo que lo leí por allí. Lo que hago actualmente es que creo un objeto local en la capa de datos y le agrego tantas propiedades como datos quiero pasar y luego paso el objeto y consulto las propiedades
--
Jorge González

Fernando D. Bozzo

unread,
Jul 15, 2016, 7:45:12 AM7/15/16
to publice...@googlegroups.com

Siempre podés retornar un array dentro del objeto de datos

Jorge González

unread,
Jul 27, 2016, 4:37:51 PM7/27/16
to publice...@googlegroups.com
Siguiendo con la programación en capas. Tengo una duda
Cuando corro un prg que carga objetos, instancias y un formulario. En el botón de salida del formulario tengo programado una solicitud de destrucción del entorno de datos y luego de esto un thisform.release:

oApp.oMatriculas.SolicitaDestruyeEntornoPrincipal()
THISFORM.RELEASE

y el formulario se libera pero sucede que el programa se mantiene en ejecución obligándome a ir a cada rato al menú Program opcion Cancel.

Porque sucede esto?

En el programa main tengo lo siguiente:

SET DELETED ON 
SET EXCLUSIVE OFF
SET DATE TO DMY
SET STATUS BAR OFF
SET TALK OFF
SET MULTILOCKS ON
SET PATH TO fuentes;fuentes\progs;fuentes\forms
PUBLIC m.compania, oApp
m.compania="ESCUELA DE MUSICA JOSE REYNA"
oApp=NEWOBJECT('cl_app','oApp.prg')
MODIFY WINDOW SCREEN TITLE "SISTEMA CONTROL DE ESTUDIOS ESCUELA DE MÚSICA JOSE REYNA" 
ZOOM WINDOW SCREEN MAX 
DO matriculasmain
READ EVENTS
CLEAR ALL

En el programa MatriculasMain tengo lo siguiente:
*-MatriculasMain
oApp.oMatriculas=NEWOBJECT('cl_matric_bus','matric_bus.prg')
oApp.oMatriculas.SolicitaCrearCursores()
oApp.oMatriculas.SolicitaLlenarCursor()
DO FORM fMatriculas
CLEAR EVENTS 

En oApp.prg instancio el objeto que carga la clase para los alumnos porque es una clase que se usa en todos los módulos:

DEFINE CLASS cl_app AS Custom 
oAlumnos=NULL
oMatriculas=NULL
&& aqui van mas objetos que los voy especificando a medida que voy programando

PROCEDURE INIT
   This.oAlumnos=NEWOBJECT('cl_alumnos','falumnos_bus.prg')
ENDPROC

ENDDEFINE
El formulario está configurado en modo Modal

En este programa preferí instanciar los objetos cuando lo necesito y no el PRG inicial.

--
Jorge González

Antonio Meza

unread,
Jul 27, 2016, 5:34:21 PM7/27/16
to Comunidad de Visual Foxpro en Español
Honestamente tienes un verdadero mole mexicano jejej

Comentas que estas usando un Formulario Modal, por lo tanto al ejecutar este formulario ya no necesitas el Read Events, puesto que ese formulario ya tiene el control, una vez que aplicas Thisform.Release liberas el formulario pero se ejecuta la linea del Read Events y ahí se queda atrapado el proceso y nunca se llega a ejecutar el Clear Events.

Otro detalle dices que tienes un botón donde aplicas el cierre de los objetos y del formulario, si tienes habilitado el botón de cerrar formulario la tradicional X entonces si el usuario presiona la X del formulario ya no se ejecutara el cierre de objetos, por lo tanto te recomiendo poner en el QueryUnload del formulario la linea siguiente.

oApp.oMatriculas.SolicitaDestruyeEntornoPrincipal()

De esa forma si cierras el formulario desde el tu boton o desde la X se destruyan los objetos.

saludos
Antonio Meza



</d

Jorge González

unread,
Jul 27, 2016, 5:40:52 PM7/27/16
to publice...@googlegroups.com
Gracia Antonio.
No lo cierro por la X porque yo modifico la clase con el controlbox a false. Tengo una botonera y uno de los botones es para salir con el código que coloqué. Y tienes razón, se me escapó ese detalle

Muchas gracias Antonio
--
Jorge González
Reply all
Reply to author
Forward
0 new messages