Realidad3

Como ya comentábamos en el ejercicio anterior, nuestra pretensión ahora, es modificar la aplicación, de tal forma que siga manteniendo las funcionalidades dentro del nuevo marco. Pero veamos esquemáticamente en que nos ha variado:

Marco Antiguo:
PRODUCTO ALFA = MATERIA A + MATERIA B

Marco Nuevo:
PRODUCTO ALFA = MATERIA A + MATERIA B
PRODUCTO BETA = (PRODUCTO ALFA)’ + MATERIA C

En el nuevo marco de estudio consideramos una relación nueva que puede ser establecida y que nos dice que un producto terminado puede llegar a convertirse en materia prima de otro producto terminado distinto.
Aquí es donde empiezan realmente los problemas. Vamos a suponer un tercer producto:

PRODUCTO GAMMA = (PRODUCTO BETA)’ + MATERIA H

Imaginemos que nuestro usuario anda algo despistadillo, ¡horror! ¡va a modificar la primera igualdad!
Ahora:
PRODUCTO ALFA = (PRODUCTO GAMMA)’ + MATERIA B

Puesto que (PRODUCTO GAMMA)’ existe como materia prima no hay ninguna razón que nos diga que no pueda ser asignada a PRODUCTO ALFA.

Hemos creado, de esta forma y sin darnos cuenta, las referencias circulares que nos hechas al traste nuestra pequeña aplicación, porque:
si consideramos una subida en el precio de la materia prima B, entonces aumentara el precio del PRODUCTO ALFA. A su vez, el nuevo precio del PRODUCTO ALFA generará un nuevo precio en la Materia Prima (PRODUCTO ALFA)’.
El PRODUCTO BETA, depende de la anterior y se verá incrementado y hará incrementar al PRODUCTO GAMMA, que será el causante de que aumente de nuevo el precio del PRODUCTO ALFA, puesto que como hemos afirmado:
PRODUCTO ALFA = (PRODUCTO GAMMA)’ + MATERIA B.

¿Qué os parece…?

Cuando finalicé la redacción del tercer ejercicio me preguntaba como podría garantizar que se pudieran cumplir las propiedades de la herencia: ningún derivado puede formar parte de la definición del objeto que le dio origen. Lógicamente nuestro producto debía de conocer de alguna forma las materias primas iban a entrar en su composición. Cualquier asignación de nuevas materias primas o modificación de las existentes debía de ser reconocida por el y aceptada o no según procediera.

Lo primero que se me ocurrió fue que se podría dar un identificador único a cada materia prima en el momento de su creación y que nuestro producto dispusiera en un campo, una cadena de caracteres que resultara de la unión de todos los identificadores asignados. Así pues, considerando:

MATERIA A = 345456 MATERIA B = 444343 (PRODUCTOALFA)’ = 345456444343

PRODUCTO ALFA dispondría de un campo en la tabla de Productos Terminados cuyo nombre podría ser, por ejemplo DERIVADOS y que recogería el valor de la materia prima que se ha generado con él. En el momento de realizar una nueva asignación, tal que

PRODUCTO ALFA = (PRODUCTO GAMMA)’ + MATERIA B

podríamos buscar en esa cadena, si en el identificador de (PRODUCTO GAMMA)’ se encuentra incluido el identificador de PRODUCTO ALFA. Si esto fuera así podríamos cancelar el proceso y garantizar de esa manera la integridad de las relaciones.

ah… no todo es perfecto. Aparentemente todo funciona bien. Si os fijáis esto es tan solo aparentemente ya que a medida que nuestra base de datos crezca, y crezca el numero de estructuras anidadas, crecerá proporcionalmente la dificultad de garantizar la integridad de las relaciones.
Los identificadores verán incrementada su longitud y habrá que recorrerlos íntegramente. Os imagináis que nuestra base de datos en vez de 200 registros tuviera 5000, o 10000 o 500000.
Además, se presenta otro problema. En el caso de querer modificar las asignaciones efectuadas en un producto cualquiera, por definición tendríamos que variar también los identificadores de todos aquellos productos que sean derivados a partir de este. Os imagináis ya la dificultad…

Tenía que haber otra forma… ¡claro! la misma VCL nos daba un idea mejor. Ella misma mantenía el sistema de Propietarios (Owner) con distintas funcionalidades, liberar memoria al destruir cada objeto, etc… Cada componente sabía quien era su Propietario…
Me dijé para mi: si creo un campo en la que informe al producto, en el caso de que éste se haya derivado, de cual va a ser el nombre del mismo como Materia Prima,
y creo en la un campo que pueda informar, a cada materia, de que Producto es el que la ha originado todo queda resuelto… el producto sabe la materia prima en la que es derivado y la materia cual es su origen.

Manos a la obra. Vamos a crear el algoritmo que nos devuelva verdadero o falso en el caso de que la materia prima a asignar tenga relaciones con el producto sobre el que va a recaer la asignación.
Podemos ya percibir que la aplicación de dicho algoritmo en un lugar concreto va a ser un acto libre para nosotros, que nos va a permitir hacer varias elecciones posibles. Como recordáis por el ejercicio anterior, las asignaciones se realizaban en un ventana creada a tal efecto, en la que depositábamos en un contenedor un icono por cada una de las materias primas. El arrastre de éstas a un segundo contenedor produciría el reconocimiento de dichas asignaciones al ser cerrada la ventana. El lugar más lógico para nuestro algoritmo podría ser el evento de creación de la ventana. En el momento de la creación filtraríamos a través de dicho algoritmo los iconos disponibles para ese producto en concreto. No existiendo el icono no puede ser asignado. Esto nos obligaría además, a un nuevo filtrado cada vez que el botón de visualización de materias primas por categoría y nombre fueran activados.
Opcionalmente podemos optar por una segunda vía: ejecutar nuestra función cada vez que sea soltada una materia prima sobre el contenedor del producto, impidiendo la aceptación de dicha materia prima y visualizando un mensaje de aviso al usuario. Podemos optar por introducir un nuevo icono que nos identifique aquellas materias que devuelven true nuestra función para mejorar la capacidad de nuestro usuario para no intentar asignarlas.

Vamos a intentar explicar el mecanismo de operación del algoritmo:

*Partimos de un contenedor vació.
*Fijamos un producto ALFA que será el que reciba las asignaciones
*Fijamos una materia prima (A)’ que será la que intentaremos asignar al producto.
*Si (A)’ no proviene de ningún producto entonces R=False (Fin)
*Si (A)’ proviene de un producto entonces
***Contenedor(1) = A
***Bucle hasta nº registros en contenedor = 0 o R= True
******Contenedor(1) = (B)’ + (C)’
******Si (B)’ = (A)’ ó (C)’ = (A)’ entonces R=True (Fin)
******Si (B)’ proviene de un producto entonces Contenedor(2) = B
******Si (C)’ proviene de un producto entonces Contenedor(3) = C
***Borrado Contenedor(1)
*R=False

function TfrmRelaciones.BuscaDerivado( Producto_asignado: String; Materia_arrastrada: String): Boolean;
var
actualizador: TQuery;
comprobador: TQuery;
contenedor: TStringList;
derivados: String;
materia_derivada: String;
begin
// creamos los dos querys que necesitamos
actualizador:= TQuery.Create(self);
comprobador:= TQuery.Create(self);
// creamos un contendor de strings
contenedor:= TStringList.Create;
// asignamos un valor inicial a la función para que no pueda quedar indeterminada
result:= false;
try
// preparamos el query asignando sus campos. Este query nos devolverá una vista maestro detalle //entre la tabla de productos y la de relación, pudiendo saber de este modo, que materias primas //componen cada uno de los productos
with actualizador do
begin
actualizador.DatabaseName:= ‘escanda0.mdb’;
actualizador.RequestLive:= true;
end;
// preparamos el segundo query que nos devolverá todos los registros de la tabla de productos.
with comprobador do
begin
DatabaseName:= ‘escanda0.mdb’;
RequestLive:= true;
SQL.Add(‘Select CODIGO_PROD_FIN, DERIVADO FROM PRODUCTOS_TERMINADOS’);
SQL.Add(‘ORDER BY PRODUCTOS_TERMINADOS.CODIGO_PROD_FIN’);
Open;
end;

//si no existe un producto en la tabla de productos que tenga como derivado la materia prima arrastrada
if comprobador.locate(‘DERIVADO’, Materia_arrastrada, []) = FALSE then
begin //garantizamos que el string corresponde a un registro
Result:= false;
//puesto que no existe damos por acabada la funcion
Exit;
end
else //en el caso contrario
begin
//añadimos el código del producto al contenedor
contenedor.add(comprobador.Fields[0].asString);
// asignamos a la variable materia_derivada el nombre del derivado del producto
if comprobador.locate(‘CODIGO_PROD_FIN’, Producto_Asignado, []) then
materia_derivada:= comprobador.Fields[1].asString;
//*******************
// Iniciamos el bucle principal
// mientras haya registros en el contenedor
while (contenedor.Count > 0) do
begin
derivados:= contenedor.strings[0]; // asignamos el código del producto
// configuramos el query filtrando sus registros al producto escrito en el contenedor
with actualizador do
begin
SQL.clear;
SQL.Add(‘Select PRODUCTOS_TERMINADOS.CODIGO_PROD_FIN, PRODUCTOS_TERMINADOS.DERIVADO, RELACION_PT_MP.CODIGO_MATEPRIM FROM PRODUCTOS_TERMINADOS, RELACION_PT_MP’);
SQL.Add(‘WHERE PRODUCTOS_TERMINADOS.CODIGO_PROD_FIN = RELACION_PT_MP.CODIGO_PROD_TER AND ‘);
SQL.add(‘PRODUCTOS_TERMINADOS.CODIGO_PROD_FIN = ‘ + Chr(39) + derivados + Chr(39));
SQL.Add(‘ORDER BY PRODUCTOS_TERMINADOS.CODIGO_PROD_FIN’);
Open;
First;
end;
//hasta que no lleguemos al ultimo registro y recorramos todas las materias primas
while not actualizador.Eof do
begin
// por cada registro que encontremos si corresponde con la materia_derivada finalizamos y devolvemos true en la función puesto que hemos hallado que si que existen relaciones entre ambas
if (actualizador.fields[1].asString = materia_derivada) then
begin
Result:= true;
Exit;
end;
// si no existen relaciones, antes de pasar al siguiente registro, añadimos al contenedor el nombre del producto que ha originado la materia prima para que sea analizado en el siguiente bucle que revisa si contador no ha llegado a 0
if comprobador.locate(‘DERIVADO’, actualizador.Fields[2].asString, []) then
begin
contenedor.Add(comprobador.fields[0].asString);
end;
// pasamos al siguiente registro
actualizador.next;
end;
// borramos el registro con índice 0 que es el primero de la lista. Dicho borrado producirá que se resituen los registros de nuevo y que asciendan un numero en la lista para adaptarse a la nueva situación. Lo que nos permitirá analizar un nuevo producto mientras queden items en el contenedor
contenedor.Delete(0);
end;
//*******************
// si llega a esta linea es porque no ha encontrado ningun derivado y el
// contenedor está vacio
Result:= false
end;//endbegin

finally
// nos aseguramos de que es liberada la memoria asignada dinámicamente
actualizador.free;
comprobador.free;
contenedor.free;
end;
end;

Si habéis llegado a entender con claridad el desarrollo del algoritmo quizá pensareis que siguiendo un procedimiento parecido a éste, se podría montar un árbol binario a partir de considerar las relaciones propietario/poseedor. Este algoritmo podría ser utilizado para recomponer una situación inicial en una aplicación orientada a objetos en donde cada objeto puede necesitar interrelacionarse con otros.

Vamos a modificar la ficha de productos terminado ampliando los nuevos campos de los que vamos a disponer, así como el nuevo botón de actualizar precios, que nos producirá las actualizaciones en cascada. Disponemos además de un nuevo campo llamado incremento que vendrá a sumar al precio de coste del producto un margen de beneficio previo a la consideración de materia prima. Su precio como materia prima será el equivalente a un precio de venta mínimo de producto.

Tal y como queda la nueva ficha:

prod_tot

Nos queda todavía pendiente una cosa. Necesitamos mantener la funcionalidad de nuestra aplicación y garantizar que los productos, a cualquier nivel estructural, serán actualizados ante la modificación del precio de las materias primas.
Para ello nos valdremos de los campos calculados y generaremos un procedimiento, que asociado a un botón de nuestra ventana, nos actualice automáticamente a cualquier nivel de estructura, todos los precios.
Si prestáis atención no os parecerá difícil. Describámoslo:
*El proceso da comienzo situándonos en el primer registro de la tabla de productos, por lo que nos valemos de una marca (Bookmark) para señalizar el registro activo previo al inicio del proceso. Al final del mismo volveremos a activar dicho registro.
*Bucle mientras la variable RepetirProceso sea true
***RepetirProceso = False
***Cerramos y Abrimos la tabla generando todos los campos calculados
***Recorremos uno a uno todos los registros de la tabla
***Durante el recorrido comprobamos si el valor total del producto coincide con el valor de la materia prima generada por ese producto.
***En el caso de ser distinto, procedemos a modificar el valor de la materia prima. Si hemos encontrado esas diferencias ponemos a true la variable RepetirProceso para que vuelva a efectuar un nuevo bucle

Como podemos ver es muy importante el que se abra la tabla antes de hacer las comprobaciones ya que nos permite que en la siguiente pasada del bucle, siempre encuentre actualizado el valor del producto por las modificaciones efectuadas en el precio de las materias primas. Aunque nosotros hayamos trasladado la operativa del algoritmo a un solo nivel, virtualmente, en cada una de las pasadas del bucle es semejante a movernos a un nivel estructural superior.

El proceso lo podemos adornar, tal y como se hace en este procedimiento de un panel que integra dos barras de progreso. La primera mantiene el progreso en el bucle que recorre todos los registros de la tabla de productos. La segunda, fuera del bucle, mantiene el nivel estructural actual. Es decir, que un ciclo completo en la barra primera aumenta en un paso la barra de progreso numero dos.

procedure TfrmProductosTerminados.bib_ActualizarClick(Sender: TObject);
var
repetirproceso: boolean;
xIndice: Integer;
apuntado: TBookmark;
panelcontenedor: TPanel;
barraprogreso1, barraprogreso2: TProgressBar;
etiqueta1, etiqueta2: TLabel;
actualizador: TQuery;
begin
if bib_Actualizar.down then
begin
EstadoControles(False);
//en el caso de que no esté en estado de visualización abortamos el proceso
if Modulo1.tabla_productos_maestra.state in [dsEdit, dsInsert, dsCalcFields] then
begin
ShowMessage(‘Atención: El registro de productos terminados debe estar’#10 +
‘en estado de Visualización.’#10 +
‘PROCESO CANCELADO…’);
bib_actualizar.down:= false;
// ejecutamos una procedimiento que simplemente inutilizamos los botones que pueden interferir en nuestro proceso. Intentamos aislarlo. En este caso el procedimiento al tomar como variable True lo que hace es lo contrario. Devuelve el control a la ventana, pues la tabla de productos solamente podía estar en estado de visualización
EstadoControles(True);
Exit;
end;
// damos una oportunidad al usuario a que pueda cancelar el proceso
if Messagedlg(‘El siguiente proceso puede durar un par de minutos… ‘#10 +
‘Si decides continuar se revisaran todas aquellas actualizaciones’#10 +
‘pendientes. Pusla Ok para continuar.’, mtWarning, [mbOk, mbCancel], 0) = mrCancel then
begin
bib_actualizar.down:= false; // activamos el botón de lanzamiento de este procedimiento
EstadoControles(True); // devolvemos el control a la ventana
Exit;
end
else
begin
// aquí empieza ya el desarrollo importante del procedimiento
// procedemos a la creacion del panel, las 2 barras de progreso y las 2 etiquetas
panelcontenedor:= Tpanel.Create(self);
barraprogreso1:= TProgressBar.Create(self);
etiqueta1:= TLabel.Create(self);
barraprogreso2:= TProgressBar.Create(self);
etiqueta2:= TLabel.Create(self);
//señalamos el registro actual
apuntado:= modulo1.tabla_productos_maestra.getbookmark;

try
with panelcontenedor do
begin
parent:= frmProductosTerminados;
caption:= ”;
bevelInner:= bvLowered;
bevelOuter:= bvRaised;
BevelWidth:= 2;
BorderStyle:= bsSingle;
BorderWidth:= 2;
height:= 100;
top:= screen.height div 2 – panelcontenedor.height div 2;
Width:= 400;
Left:= screen.Width div 2 – panelcontenedor.Width div 2;
end;

with barraprogreso1 do
begin
parent:= panelcontenedor;
top:= 20;
left:= 10;
height:= 15;
width:= 380;
step:= 1;
max:= modulo1.tabla_productos_maestra.recordcount;
end;

with etiqueta1 do
begin
parent:= panelcontenedor;
Font.Style:= [fsBold];
caption:= ‘Actualización en curso’;
autosize:= false;
top:= 5;
left:= 10;
height:= 15;
width:= 380;
end;

with barraprogreso2 do
begin
parent:= panelcontenedor;
top:= 70;
left:= 10;
height:= 15;
width:= 380;
step:= 1;
max:= 100;
end;

with etiqueta2 do
begin
parent:= panelcontenedor;
Font.Style:= [fsBold];
caption:= ‘Niveles de Estructura’;
autosize:= false;
top:= 55;
left:= 10;
height:= 15;
width:= 380;
end;

repetirproceso:= true;
screen.Cursor:= crHourGlass;
panelcontenedor.Repaint;
// aqui se inicia el bucle principal
while repetirproceso do
begin
xIndice:= 0;
barraprogreso1.position:= 0;
modulo1.tabla_productos_maestra.close;
// garantizamos que en cada pasada del bucle se hayan generado todos los campos calculados
modulo1.tabla_productos_maestra.open;
// inicio del recorrido
modulo1.tabla_productos_maestra.First;
while not modulo1.tabla_productos_maestra.Eof do
begin
application.ProcessMessages;
// si el precio del producto es distinto al precio de la materia prima
if (Abs((modulo1.tabla_productos_maestraTOTALABSOLUTO.AsCurrency + modulo1.tabla_productos_maestraINCREMENTO_DERIVADO.asCurrency) –
modulo1.tabla_productos_maestraPRECIO_VENTA_TARIFA1.asCurrency) > epsilon) then
begin
modulo1.tabla_productos_maestra.edit;
// modificamos el precio de venta en la tabla de producto
modulo1.tabla_productos_maestraPRECIO_VENTA_TARIFA1.AsCurrency:=
modulo1.tabla_productos_maestraTOTALABSOLUTO.AsCurrency +
modulo1.tabla_productos_maestraINCREMENTO_DERIVADO.asCurrency;
modulo1.tabla_productos_maestra.post;

Inc(xIndice);
end;
// comprobamos que el registro actual tiene una materia derivada
if modulo1.tabla_productos_maestraDERIVADO.AsString <> ” then
begin
actualizador:= TQuery.Create(nil);
try
actualizador.DatabaseName:= ‘escanda0.mdb’;
actualizador.RequestLive:= true;
actualizador.SQL.Add(‘Select * FROM MATERIASPRIMAS’);
actualizador.SQL.Add(‘WHERE COD_MATPRIM = ‘ + Chr(39) + modulo1.tabla_productos_maestraDERIVADO.AsString + Chr(39));
actualizador.open;
// si el precio de la materia prima es distinto del precio de venta fijado anteriormente
if Abs(actualizador.fields[4].asCurrency – modulo1.tabla_productos_maestraPRECIO_VENTA_TARIFA1.AsCurrency) > epsilon then
begin
actualizador.edit;
// modificamos el precio de la materia prima para que al generarse un nuevo bucle, los campos calculados que mantienen los valores totales de los productos varíen y encuentre nuevas diferencias
actualizador.Fields[4].asCurrency:= modulo1.tabla_productos_maestraPRECIO_VENTA_TARIFA1.AsCurrency;
actualizador.FlushBuffers;
actualizador.Close;
Inc(xIndice);
end;
finally
actualizador.free; // liberamos la memoria
end;
end;

modulo1.tabla_productos_maestra.Next;
barraprogreso1.stepit;
end;

if xIndice = 0 then repetirproceso:= false;
// le damos al usuario una oportunidad de poder cancelar este proceso en el caso de que por alguna razón entrara en un bucle sin fin
if not bib_actualizar.down then repetirproceso:= false;
barraprogreso2.StepIt;
application.ProcessMessages;
end;

finally
// nos aseguramos de que se producirá la liberación de la memoria asignada al inicio de proceso y puesto que al llegar aquí el proceso esta concluido, procedemos a recomponer la situación inicial.
modulo1.tabla_productos_maestra.Gotobookmark(apuntado);
modulo1.tabla_productos_maestra.FreeBookMark(apuntado);
barraprogreso1.free;
barraprogreso2.free;
etiqueta1.free;
etiqueta2.free;
panelcontenedor.free;
screen.Cursor:= crDefault; // devolvemos el cursor normal
bib_Actualizar.down:= false;
EstadoControles(True);
end;
end; //endbegin if Message
end;
end;

Me queda tan solo comentar una cosa: para poder garantizar un funcionamiento óptimo, tenemos que restringir ciertas acciones al usuario tales como:

*El usuario no puede modificar el precio de una materia prima que sea derivada de un producto terminado. Esto es clave. De hecho el proceso de derivación de un producto terminado lo proporcionaremos de forma automática para que no se produzca dicha intervención. El usuario dispondrá de una ventana en la que aparecen todos aquellos productos que todavía no se han convertido en materia prima y sobre estos realizará una elección.
*El usuario no puede eliminar un producto que se haya derivado. Previo al borrado deberá eliminar las dependencias de la materia prima.

Otras consideraciones son de obligado cumplimiento:
*Debemos velar para que cualquier modificación en el nombre de una materia prima derivada sea recogida también en el valor del campo derivado de la tabla de productos
*Asimismo, cualquier modificación en el nombre de un producto terminado será recogida en el campo de propietario de la tabla de materias primas.

Todas estas consideraciones nos permitirán garantizar la integridad del proceso.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Blog de WordPress.com.

Subir ↑

Recetas y consejos nutricionales

Indicadas para personas con diabetes, recomendadas para todos.

¡Buen camino!

ANÉCDOTAS Y REFLEXIONES SOBRE UN VIAJE A SANTIAGO…

http://lfgonzalez.visiblogs.com/

Algunas reflexiones y comentarios sobre Delphi

It's All About Code!

A blog about Delphi and related technologies

The Podcast at Delphi.org

The Podcast about the Delphi programming language, tools, news and community.

Blog de Carlos G

Algunas reflexiones y comentarios sobre Delphi

The Road to Delphi

Delphi - Free Pascal - Oxygene

La web de Seoane

Algunas reflexiones y comentarios sobre Delphi

El blog de cadetill

Cosas de programación....... y de la vida

Delphi-losophy

A Lover of Delphi Wisdom

Delphi en Movimiento

Algunas reflexiones y comentarios sobre Delphi

marcocantu.blog

Algunas reflexiones y comentarios sobre Delphi

/*Prog*/ Delphi-Neftalí /*finProg*/

Blog sobre programación de Neftalí -Germán Estévez-

Press F9

Algunas reflexiones y comentarios sobre Delphi

El blog de jachguate

Un blog sobre tecnología y la vida en general

A %d blogueros les gusta esto: