Llegó el día H y la hora H; subía las escaleras despacio, meditando todos los detalles detenidamente, masticándolos. Releí mi agenda y revisé los puntos claves. Quería ser convincente y que mis palabras fueran acertadas, sin dar lugar a titubeos.
Abrí la puerta con decisión. Allí estaban los dos, esperándome. Entré…
-Buenas tardes… (por lo bajo)
El gerente levantó la vista de un paquete de hojas que pendían de su mano y concluyó…
-Pasa y cuéntanos…

Así, como ésta,  podría comenzar cualquier historia. Y es así, como iniciaremos esta segunda parte de reflexiones. Lo dejaremos ahora en ese punto y recapitularemos, para crear un entramado acorde a la situación. 

Supondremos lo siguiente:: 

Al programador le habían encargado una pequeña aplicación para poder controlar el coste de cada producto terminado. La idea era la de codificar las materias primas  y los procesos de producción, haciendo una valoración de los mismos y asignándolos, finalmente, a cada producto.
Se establecía una asignación directa, a través de campos calculados. De tal forma que, cualquier variación del valor de una materia prima o de un proceso de producción repercutiera directamente, actualizando el valor del producto terminado

Nuestro amigo tenía bastante claro lo que quería y para ello inició el diseño de la base de datos. Básicamente era algo así: 

Una tabla de Proveedores
Una tabla de Materias Primas
Una tabla de Procesos Especiales
Una tabla de Productos Terminados
Una tabla de Relación Materias-Primas & Productos Terminados
Una tabla de Relación Procesos Especiales & Productos Terminados

Estableció las siguientes relaciones entre ellas:

Una relación de una a varios entre:
Tabla de Proveedores                        y         Tabla de Materias Primas.
Tabla de Materias Primas                   y         Tabla de Relación MP & PT.
Tabla de Productos Terminados         y         Tabla de Relación MP & PT.
Tabla de Procesos Especiales            y          Tabla de Relación PE & PT.
Tabla de Productos Terminados         y          Tabla de Relación PE & PT. 

Vamos a mostrar la interfaz grafica de la ventana que iba a gestionar todo lo relacionado con los productos terminados: altas, bajas, asignación de materias primas y procesos especiales, etc…: 

frmProductosTerminados

Esta primera figura muestra las relaciones que han establecido las distintas tablas: 

  1. Todas las casillas no encerradas en alguno de los círculos son casillas de edición de la clase TDBEdit, que apuntan a la tabla de productos terminados. Dicha tabla establece una relación maestro-detalle con las dos tablas de relación, asignadas a las dos rejillas (TDBGrid). Esto permite al cambiar el registro activo en la tabla de productos que las dos rejillas nos muestren las materias primas y los procesos asignados.

  2. La rejilla superior esta asignada a la tabla de Relación mp & pt. La relación de uno a varios establecida con la tabla de materias primas nos permite tener acceso al campo precio, que nos valorará la cantidad de materia prima asignada a tal producto. Es un lookup sobre la tabla de materias primas.

  3. Así mismo, la segunda rejilla esta asignada a la tabla de Relación pe & pt.  La relación de uno a varios establecida con la tabla de procesos especiales nos permite tener acceso al campo precio, que nos valorará la cantidad de proceso especial asignado a dicho producto. Es un lookup sobre la tabla de procesos especiales.

  4. El circulo inferior nos engloba múltiples casillas TDBText que impiden la edición. Están asignadas a la tabla de proveedores. Dicha tabla establece una relación de uno a varios con la tabla de materias primas, y por consiguiente, mediante un campo lookup en la rejilla, quedan enlazadas al registro activo de la tabla de relación de materias primas. Al cambiar el registro de la rejilla superior asignara el proveedor que corresponda a dicha materia prima.

  5. El desplazamiento sobre los registros existentes quedan garantizados mediante el navegador, con los cuatro botones básicos y un cuadro de edición anexo que permite una búsqueda rápida del registro.

¿Y la asignación de las materias primas y los procesos? ¿Por las rejillas… no? 

Nuestro programador hoy se ha levantado con ganas de trabajar…;-)  veréis lo que se le ha ocurrido:

Figura B:

frmAsignacion

Procedimiento perteneciente al modulo de productos terminados y asignado a las dos rejillas, al evento DobleClick:procedure TfrmProductosTerminados.db_grid_relacion_pt_mpDblClick(
Sender: TObject);
var
Relaciones: TFrmRelaciones;
begin
// creamos la ventana dinámicamente
Relaciones:= TFrmRelaciones.Create(Self);
// cerramos las tablas de relacion asignadas a las rejillas.
// De esa manera garantizamos la actualización al cerrar la ventana creada
Modulo1.DS_tabla_relacion_pt_mp.DataSet.Close;
Modulo1.DS_tabla_relacion_pt_pe.DataSet.close;
try
 // la visualizamos impidiendo devolver el foco de control mientras no se cierre
Relaciones.showModal;
finally
//liberamos la memoria asignada a la variable. El proceso queda garantizado
  // mediante el uso de finally
Relaciones.Free;
with Modulo1 do // con el modulo de datos que recoge todas las tablas
begin
// procedemos de nuevo a la apertura al transferir de nuevo el control a los productos terminados
DS_tabla_relacion_pt_mp.DataSet.Open;
DS_tabla_relacion_pt_pe.DataSet.Open;
// si esta en estado de edición o de inserción
    if DS_tabla_productos_maestra.state in [dsInsert, dsEdit] then
   //recalculamos los campos calculados
tabla_productos_maestraCalcFields(DS_tabla_productos_maestra.DataSet)
else // en caso contrario
begin
   // ponemos la tabla en estado de edición para poder recalcular los campos calculados
DS_tabla_productos_maestra.DataSet.Edit;
// efectuamos el calculo
tabla_productos_maestraCalcFields(DS_tabla_productos_maestra.DataSet);
// volvemos a poner la tabla en el estado inicial que el de Visualización
DS_tabla_productos_maestra.DataSet.CheckBrowseMode;
end;
end// endBegin with…do
end;// endTry
end

La idea es la siguiente: al crear la ventana, iremos rellenando los tres TListView que intervienen. Uno para las materias primas, otro para los procesos especiales y un tercero para las materias primas y procesos especiales ya asignados al producto. Una vez que es activada la ventana debemos permitir mediante arrastre rellenar el tercer ListView. El usuario arrastrará un icono de las materias primas a dicho contenedor. Finalizado el proceso de arrastre, visualizará un cuadro de edición para rellenar la cantidad de materia prima asignada al producto. De igual forma con los procesos especiales.
En el caso de error al asignar puede con el ratón arrastrar el icono a la papelera.
Cuando finalmente, se cierra la ventana (que era modal) el procedimiento OnClose se encarga de grabar a las tablas de relación las distintas asignaciones efectuadas, incluyendo la cantidad insertada.
Dado que pueden existir miles de materias primas, incorporamos el filtro del campo categorías y un cuadro de edición para la búsqueda rápida, mostrándonos tan solo aquellos items que cumplan dichas condiciones.
Un proceso rápido y cómodo para el usuario.

Codigo del Modulo frmRelaciones:

unit Relaciones;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Buttons, ExtCtrls, ComCtrls, Db, DBTables, CheckLst, ImgList;

type
  TfrmRelaciones = class(TForm)
    panHerramientas: TPanel;
    labRegistro: TLabel;
    lvw_materiasprimas: TListView;
    labMateriasPrimas: TLabel;
    DS_ConsultaRelaciones: TDataSource;
    lvw_procesosespeciales: TListView;
    labProcesosEspeciales: TLabel;
    DS_enlacept: TDataSource;
    Consulta_Relaciones: TQuery;
    chklbx_categoria: TCheckListBox;
    labCategoria: TLabel;
    lvw_destino: TListView;
    Image1: TImage;
    Button1: TButton;
    Button2: TButton;
    imglst_3232: TImageList;
    imglst_1616: TImageList;
    Button3: TButton;
    Button4: TButton;
    ediCategoria: TEdit;
    Label1: TLabel;
    ediCodigoMateprim: TEdit;
    labRotuloCategoria: TLabel;
    labRotuloCodigo: TLabel;
    panCantidad: TPanel;
    labCantidad: TLabel;
    labRotulo: TLabel;
    ediCantidad: TEdit;
    procedure FormShow(Sender: TObject);
    procedure lvw_destinoDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure lvw_destinoDragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure ediCategoriaKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure ediCategoriaKeyPress(Sender: TObject; var Key: Char);
    procedure Image1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure Image1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ediCantidadExit(Sender: TObject);
    procedure ediCantidadKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure ediCantidadKeyPress(Sender: TObject; var Key: Char);
    procedure lvw_destinoMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmRelaciones: TfrmRelaciones;

implementation
 uses shellapi;

{$R *.DFM}

{En este procedimiento, correspondiente a la asignación del evento OnShow de la ventana procederemos al relleno de los distintos TListView y la nota de despiece asignada a la categoría. Podemos observar, que puesto que la ventana se visualiza de forma Modal nos da absolutamente igual que todo este código perteneciera al evento OnCreate o al OnActivate puesto que no va a perder el foco y dichos eventos tan solo se producirán una sola vez en la vida de la ventana.}
procedure TfrmRelaciones.FormShow(Sender: TObject);
var
F: TextFile;
NombreFichero: String;
begin
// asignamos a la casilla de edición el valor del campo categoría de la tabla de productos terminados
ediCategoria.text:= ds_enlacept.DataSet.fields[9].asString;

//Rellenamos el listview de Materias Primas
{Utilizaremos un TQuery para extraer los datos de las tablas y añadir un elemento en el TListview por cada uno de los registros. Este TQuery será utilizado en todas las tablas cambiando los parámetros de SQL.strings}
with consulta_Relaciones do
  begin
  Sql.clear; // vaciamos los strings
  Close;
  // seleccionamos la tabla y los campos
  Sql.Add(‘Select COD_MATPRIM from MATERIASPRIMAS’);
  // con la condición de que el campo categoría sea el del producto elegido
  Sql.Add(‘Where CATEGORIA = ‘ + ds_enlacept.DataSet.fields[9].asString);
  Sql.Add(‘order by COD_MATPRIM’); // la ordenamos
  Open;
  First; // vamos al primer registro
  end;
while not consulta_Relaciones.eof do // mientras hayan registros
  begin
  with lvW_materiasprimas.items.add do // añadimos un item
    begin
   //asignamos al titulo del icono el valor del código de la materia prima
    Caption:= Consulta_Relaciones.fields[0].asString;
    ImageIndex:= 0; // utilizamos como imagen la asignada con índice 0
    end;
  Consulta_Relaciones.next; // siguiente registro mientras no llegue al final
  end;

{Ahora efectuamos el mismo proceso pero con los registros de la tabla de procesos especiales. Estos procesos son independientes de la categoría del producto }
//Rellenamos el listview de Procesos Especiales
with consulta_Relaciones do
  begin
  Sql.clear; // vaciamos los strings
  Close;
  // selecionamos la tabla y los campos
  Sql.Add(‘Select CODIGO_PROC_ESP from PROCESOS_ESPECIALES’);
  Sql.Add(‘order by CODIGO_PROC_ESP’); // la ordenamos
  Open;
  First; // vamos al primer registro
  end;
while not consulta_Relaciones.eof do // mientras hayan registros
  begin
  with lvw_ProcesosEspeciales.items.add do // añadimos un item
    begin
    //asignamos al titulo del icono el valor del código de proceso.
    Caption:= Consulta_Relaciones.fields[0].asString;
    ImageIndex:= 1; // utilizamos como imagen la asignada con índice 1
    end;
  Consulta_Relaciones.next; // siguiente registro mientras no llegue al final
  end;

//Rellenamos el listview de Productos Terminados
// primero con las materias primas
with consulta_Relaciones do
  begin
  Sql.clear; // vaciamos los strings
  Close;
  // seleccionamos la tabla y los campos
  Sql.Add(‘Select * from RELACION_PT_MP‘);
  // con la condición de que el campo código del producto sea el del producto elegido. Se filtran solo aquellos materias primas que correspondan a ese producto
  Sql.Add(‘Where CODIGO_PROD_TER = ‘ + Chr(39)+    ds_enlacept.DataSet.fields[0].asString + Chr(39));
  Sql.Add(‘order by CODIGO_MATEPRIM’); // la ordenamos
  Open;
  First; // vamos al primer registro
  end;
while not consulta_Relaciones.eof do // mientras hayan registros
  begin
  with lvW_destino.items.add do // añadimos un item
    begin
    //asignamos al titulo del icono el valor del código de materia prima.
    Caption:= Consulta_Relaciones.fields[2].asString;
    ImageIndex:= 0; // utilizamos como imagen la asignada con índice 0
    // añadimos como subitem la cantidad existente en el campo [3] que es precisamente la cantidad de materia primas asignada a ese producto
    SubItems.Add(Consulta_Relaciones.fields[3].asString);
    end;
  Consulta_Relaciones.next; // siguiente registro mientras no llegue al final
  end;

//Ahora con los procesos especiales
with consulta_Relaciones do
  begin
  Sql.clear; // vaciamos los strings
  Close;
  // seleccionamos la tabla y los campos
  Sql.Add(‘Select * from RELACION_PT_PE‘);
   // con la condición de que el campo código del producto sea el del producto elegido. Se filtran solo aquellos procesos especiales que correspondan a ese producto
  Sql.Add(‘Where CODIGO_PROD_TER = ‘ + Chr(39)+ ds_enlacept.DataSet.fields[0].asString + Chr(39));
  Sql.Add(‘order by CODIGO_PROCESOS_ESP’); // la ordenamos
  Open;
  First; // vamos al primer registro
  end;
while not consulta_Relaciones.eof do // mientras hayan registros
  begin
  with lvW_destino.items.add do // añadimos un item
    begin
    //asignamos al titulo del icono el valor del codigo de proceso.
    Caption:= Consulta_Relaciones.fields[2].asString;
    ImageIndex:= 1; // utilizamos como imagen la asignada con índice 1
    // añadimos como subitem la cantidad existente en el campo [3] que es precisamente la cantidad de proceso asignada a ese producto
    SubItems.Add(Consulta_Relaciones.fields[3].asString);
    end;
  Consulta_Relaciones.next; // siguiente registro mientras no llegue al final
  end;

// visualizamos en la etiqueta superior el código del producto terminado sobre el que estamos realizando las asignaciónes
labRegistro.Caption:= ds_enlacept.DataSet.fields[0].asString;
// visualizamos en la etiqueta de categoría la asignada a ese producto en concreto
labCategoria.caption:=’CATEGORIA: ‘ + ds_enlacept.DataSet.fields[9].asString;
{vaciamos todo el contenido del CheckListBox. Este componente nos ayudará a mostrar al usuario el despiece habitual correspondiente a dicha categoría y que tan solo servirá como guía para realizar las asignaciones. Este listado será creado por el usuario y será almacenado en un fichero de texto y será único por cada categoría. Abrimos también la posibilidad, al tener la funcionalidad del checkbox, para futuros usos no previstos.}
chklbx_categoria.Items.clear;
// si exite el fichero éste es cargado
if FileExists(ExtractFilePath(Application.exename) +
              ‘CATEGORIA_’ +
              ds_enlacept.DataSet.fields[9].asString +
              ‘.TXT’) then
chklbx_categoria.Items.LoadFromFile(ExtractFilePath(Application.exename) +
                                    ‘CATEGORIA_’ +
                                    ds_enlacept.DataSet.fields[9].asString +
                                    ‘.TXT’)
else // en el caso de que no exista
  begin
  // visualizamos un cuadro de diálogo y le preguntamos si desea crearlo
  if Messagedlg(‘El fichero ‘ +
             ExtractFilePath(Application.exename) + ‘CATEGORIA_’ +
             ds_enlacept.Dataset.fields[9].asString + ‘ no existe: ‘ + #10 +
             ‘¿Desea crearlo…?’, mtWarning, [mbOk, mbCancel], 0) = mrOk then
    begin
    NombreFichero:= ExtractFilePath(Application.exename) + ‘CATEGORIA_’ +  ds_enlacept.DataSet.fields[9].asString +  ‘.TXT’;
    AssignFile(F, NombreFichero);  // Asignamos la variable F al nombre del fichero
    try
       Rewrite(F);  // Inicializamos para escribir sobre el
       Append(F);   // Preparamos el acceso a la escritura
       Writeln(F, ‘ESCRIBE EN ESTE LUGAR LA LISTA CORRESPONDIENTE A ESTA CATEGORIA.’); // y escribimos la primera linea
    finally
       CloseFile(F); // liberamos la variable
    end;
    // abrimos el fichero con el programa asociado
    ShellExecute(handle,
                 ‘open’,
                 PChar(NombreFichero),
                 nil,
                 nil,
                 SW_SHOWNORMAL);
    // rellenamos los items de la chuleta
    chklbx_categoria.Items.LoadFromFile(NombreFichero);
    end;
  end;
end;

{Este procedimiento es empleado para aceptar el icono arrastrado sobre el tercer ListView tan solo si dicho objeto pertenece a la clase TListView. Utilizamos el operador IS, que como sabéis es utilizado en las Clases para saber si pertenece o no pertenece}
procedure TfrmRelaciones.lvw_destinoDragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
begin
// podemos ver el detalle de que dicha aceptación no esta restringida a que el objeto pertenezca a cualquiera de los TListView, incluso a si misma.
if source is TListView then
   Accept:= true
else
   Accept:= False;
end;

{Procedimiento de arrastre}
procedure TfrmRelaciones.lvw_destinoDragDrop(Sender, Source: TObject; X,
  Y: Integer);
begin
if source is TListView then // si el origen es de la clase TListView
  begin
  with TListView(sender).Items.add do // utilizamos la variable sender para saber sobre que contenedor es soltado y añadimos un item al mismo
    begin
    // rellenamos su caption con el caption del elemento arrastrado
    caption:= TListview(source).Selected.Caption;
    // rellenamos el rotulo del panel que mostrará el edit para la inserción de la cantidad asignada
    labRotulo.caption:= TListview(source).Selected.Caption;
    // seleccionamos para este nuevo icono la misma imagen
    ImageIndex:= TListview(source).Selected.ImageIndex;
    end;
  {ajustamos los valores a dicho panel para su presentación}
  panCantidad.left:= (screen.ActiveForm.ClientWidth div 2) – (panCantidad.Width div 2);
  panCantidad.top:= (screen.ActiveForm.ClientHeight div 2) – (panCantidad.Height div 2);
  // Lo visualizamos
  panCantidad.visible:= true;
  // le trasferimos el foco a la casilla de edición de dicho panel.
  ediCantidad.setfocus;
  end;
end;

// Estos cuatro procedimientos todavía estan provisionales. La idea del programador es que al pulsar el botón derecho del ratón sobre cada ListView aparezca un donde pueda elegir las cuatro presentaciones. Permanecen mientras está en fase de pruebas y de hecho, como podeis comprobar, puesto que no están definidas las columnas del TlistView la presentación Report no tiene sentido
//*****************************************************
procedure TfrmRelaciones.Button1Click(Sender: TObject);
begin
lvW_materiasprimas.ViewStyle:= vsIcon;
lvw_ProcesosEspeciales.ViewStyle:= vsIcon;
lvw_Destino.ViewStyle:= vsIcon;
end;

procedure TfrmRelaciones.Button2Click(Sender: TObject);
begin
lvW_materiasprimas.ViewStyle:= vsSmallIcon;
lvw_ProcesosEspeciales.ViewStyle:= vsSmallIcon;
lvw_Destino.ViewStyle:= vsSmallIcon;
end;

procedure TfrmRelaciones.Button3Click(Sender: TObject);
begin
lvW_materiasprimas.ViewStyle:= vsList;
lvw_ProcesosEspeciales.ViewStyle:= vsList;
lvw_Destino.ViewStyle:= vsList;
end;

procedure TfrmRelaciones.Button4Click(Sender: TObject);
begin
lvW_materiasprimas.ViewStyle:= vsReport;
lvw_ProcesosEspeciales.ViewStyle:= vsReport;
lvw_Destino.ViewStyle:= vsReport;
end;
//*****************************************************

{Este procedimiento corresponde a la pulsación del teclado sobre la casilla de edición Categoría, que se encargará de efectuar un filtro a los items del ListView donde le enseñamos al usuario las materias primas existentes}
procedure TfrmRelaciones.ediCategoriaKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
if key= vk_returnthen // si es pulsado el Enter
  begin
  // si el texto de la casilla es vacío o contiene un carácter en blanco
  if ((ediCategoria.text = ”) or (ediCategoria.text = ‘ ‘)) then
    begin
   // volvemos a asignar un valor correcto (el valor por defecto)
    ediCategoria.text:= ds_enlacept.DataSet.fields[9].asString;
    // avisamos al usuario
    ShowMessage(‘Atención: Filtro de Categoría no puede tener valor o ‘);
    end;
  lvW_materiasprimas.items.clear; // vaciamos el contenedor

//Rellenamos el listview de Materias primas
// en el caso de que el filtro por texto este vació repetimos el proceso de creación e inserción de iconos ya comentado en el evento OnShow.
if ((ediCodigoMatePrim.text = ”) or (ediCodigoMatePrim.text = ‘ ‘)) then
  begin
  with consulta_Relaciones do
    begin
    Sql.clear;
    Close;
    Sql.Add(‘Select * from MATERIASPRIMAS’);
    Sql.Add(‘Where CATEGORIA = ‘ + EdiCategoria.text);
    Sql.Add(‘order by COD_MATPRIM’);
    Open;
    First;
    end;
  end
else // en el caso de que estemos filtrando
  begin
  with consulta_Relaciones do
    begin
    Sql.clear;
    Close;
    Sql.Add(‘Select * from MATERIASPRIMAS’);
    // Tenemos en consideración ambos filtros
    Sql.Add(‘Where CATEGORIA = ‘ + EdiCategoria.text + ‘ AND ‘);
    Sql.Add(‘COD_MATPRIM >= ‘ + Chr(39) + EdiCodigoMateprim.text + Chr(39));
    Sql.Add(‘order by COD_MATPRIM’);
    Open;
    First;
    end;
  end;
{ una vez seleccionados los registros de la tabla procedemos  a la creación del icono y la asignación del caption, que nos devolverá el nombre de cada materia prima}
while not consulta_Relaciones.Eof do
  begin
  with lvW_materiasprimas.items.add do
    begin
    Caption:= Consulta_Relaciones.fields[0].asString;
    ImageIndex:= 0;
    end;
  Consulta_Relaciones.next;
  end;
  end;
end;

{Procedimiento correspondiente a la pulsación del cuadro edición del filtro de categorías. Una cosa que podemos destacar y que entra dentro de la sintaxis de Object Pascal es la utilización de parámetros por referencia en procedimientos. Podemos quedarnos con la idea de que cualquier procedimiento que declara Var un parámetro nos va a dejar la opción de poder redefinir dicho parámetro. El motivo es evidente:
Cuando un parámetro es pasado por referencia se establece un puntero a dicha variable y la modificación efectuada sobre el mismo es directa. En cambio, si el parámetro es pasado por valor, lo habitual, dicha variable declarada obtiene una copia del valor original y dicho valor será temporal y existirá localmente.}
procedure TfrmRelaciones.ediCategoriaKeyPress(Sender: TObject;
  var Key: Char);
begin
if key <> #8 then // si es distinto de la tecla de retroceso
if ((key > ‘9’) or (key < ‘0’)) then // si es distinto de número
key := #0; // ignoramos la pulsación
end;

{Procedimiento que corresponde al momento en el que arrastramos un objeto sobre la papelera}
procedure TfrmRelaciones.Image1DragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
begin
if (source is TListView) then
if ((source as TListView).tag = 3) then
   // solo aceptamos el objeto si es perteneciente al ListView que contiene los objetos asignados al producto
   Accept:= true
else
   Accept:= False;
end;

{Procedimiento que corresponde al momento de soltar un objeto sobre la papelera.
Vamos a explicar el código siguiente:
<TListView(source).Items.Delete(TListView(source).Selected.Index);>
Moldeamos la variable source, que como sabemos nos recoge el objeto origen del arrastre. Dicho moldeo nos permite acceder a la propiedad items, y sobre esta ejecutamos el método Delete. Dicho método espera el índice del objeto a borrar en esa lista y para proporcionarlo volvemos a moldear dicho objeto pero esta vez aplicando el método Selected.Index, que nos devolverá el índice de dicho icono.}
procedure TfrmRelaciones.Image1DragDrop(Sender, Source: TObject; X,
  Y: Integer);
begin
if source <> nil then
if source is TListView then
// solo aceptamos el objeto si es perteneciente al ListView que contiene los objetos asignados al producto
if ((source as TListView).tag = 3) then
  begin
  if messagedlg(‘¿Quieres borrar el registro ‘ + TListView(source).Selected.Caption, mtWarning, [mbOk, mbCancel], 0) = mrOk then
    begin
    // Procedemos al borrado de dicho item
    TListView(source).Items.Delete(TListView(source).Selected.Index);
    end;
  end;
end;

{Procedimiento correspondiente al perder el foco la casilla de edición que recogerá la cantidades asignadas. Necesitamos implementar este procedimiento para que dicho panel sea ocultado una vez sea pulsado el enter o hecho click con el puntero del ratón en otro elemento de la ventana.}
procedure TfrmRelaciones.ediCantidadExit(Sender: TObject);
var
decimal: currency;
begin
  // nos aseguramos que el numero es correcto. No podemos admitir números como ‘1,24,30’ puesto que no controlamos la cantidad de comas que hay incluidas en el numero
  try
  decimal:= StrToFloat(ediCantidad.text);
  lvw_destino.items.Item[lvw_destino.Items.count – 1].SubItems.Add(FloatToStr(decimal))
  except
  on exception do
    begin
    // si se produce una excepción tomamos como predeterminado el valor 0
    ediCantidad.text:= ‘0’;
    end;
  end;
// ocultamos el panel y todos los elementos que contiene
panCantidad.visible:= false;
ediCantidad.text:= ‘0’; // ponemos a cero la cantidad
end;

{Procedimiento que corresponde a la pulsación del teclado sobre el cuadro de edición que recogerá las cantidades asignadas. Este procedimiento es compañero del inmediato anterior y entre ambos controlan la visibilidad del panel y la inserción en el icono destino de un subitem con la nueva cantidad asignada}
procedure TfrmRelaciones.ediCantidadKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
decimal: currency;
begin
if key = vk_return then // si es pulsado el enter
  begin
  // hacemos las mismas comprobaciones que en el procedimiento anterior
  try
  decimal:= StrToFloat(ediCantidad.text);
  // añadimos el nuevo subitem con la cantidad asignada
  lvw_destino.items.Item[lvw_destino.Items.count – 1].SubItems.Add(FloatToStr(decimal))
  except
  on exception do
    begin
    ShowMessage(‘Introduce un numero correcto, por favor…’);
    ediCantidad.text:= ‘0’;
    Exit;
    end;
  end;
  panCantidad.visible:= false;
  ediCantidad.text:= ‘0’;
  end;
end;

{Procedimiento que corresponde al pulsar el teclado sobre el cuadro de edición que recogerá las cantidades asignadas}
procedure TfrmRelaciones.ediCantidadKeyPress(Sender: TObject;
  var Key: Char);
begin
// si es pulsada una tecla distinta de todas estas es ignorada
if key <> #13 then // distinta de enter
  if key <> #8 then // distinta de retorceso
    if key <> ‘,’ then // distinta de coma
      if ((key > ‘9’) or (key < ‘0’)) then // o distinta de número
        key := #0;
end;

{Al situar el cursor del ratón sobre un icono del listview de asignación al producto}
procedure TfrmRelaciones.lvw_destinoMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
try
// asignamos a la propiedad hint del listview el valor de la cantidad introducida en el subitem del objeto
lvW_destino.hint:= lvw_destino.GetItemAt(x,y).SubItems.Strings[0];
except
lvW_destino.hint:= ”;
end;
end;

{En este procedimiento debemos transferir a la tabla de productos las asignaciones efectuadas.}
procedure TfrmRelaciones.FormClose(Sender: TObject;
  var Action: TCloseAction);
var
xIndice: Integer;
numero: integer;
codigoProducto: String;
begin
// esta variable nos recogerá el valor del código del producto
CodigoProducto:= ds_enlacept.DataSet.fields[0].asString;
//Rellenamos el listview de Productos Terminados
with consulta_Relaciones do
  begin
  Sql.clear;
  Close;
  Sql.Add(‘Select * from RELACION_PT_MP’);
  Sql.Add(‘Where CODIGO_PROD_TER = ‘ + Chr(39)+ ds_enlacept.DataSet.fields[0].asString + Chr(39));
  Sql.Add(‘order by CODIGO_MATEPRIM’);
  Open;
  First;
  end;
// borramos todos los registros de materias primas asignados originalmente en la tabla de relación
while not consulta_Relaciones.eof do
  begin
  consulta_relaciones.Delete; // borramos todos los registros para ese producto
 end;
// la variable numero nos devolvera la cantidad de items en el listview
numero:= lvw_destino.Items.Count;
for xIndice:= 0 to numero – 1 do // recorremos un bucle con ese valor
  begin
  // si el item pertenece a materias primas (su imageindex es 0)
  if lvW_destino.items.item[xIndice].ImageIndex = 0 then
    begin
    consulta_Relaciones.insert; // insertamos un registro
    // damos valor a todos sus campos según este item
    consulta_relaciones.fields[1].asString:= CodigoProducto;
    consulta_relaciones.Fields[2].asString:= lvW_destino.items.item[xIndice].Caption;
   // si subitems no esta asignado (cantidad)
    if lvW_destino.items.item[xIndice].SubItems = nil then
     // asignamos al campo el valor 0 para que no genere una excepción
       consulta_relaciones.Fields[3].asString:= ‘0’
    else // en el caso de que sí esté asignada
     // le damos el valor que ha proporcionado el usuario
       consulta_relaciones.Fields[3].asString:= lvW_destino.items.item[xIndice].SubItems.Strings[0];
    // validamos la tabla
    consulta_relaciones.Post;
    end;
  end;

  //Ahora procedemos con los procesos especiales
with consulta_Relaciones do
  begin
  Sql.clear;
  Close;
  Sql.Add(‘Select * from RELACION_PT_PE’);
  Sql.Add(‘Where CODIGO_PROD_TER = ‘ + Chr(39)+ ds_enlacept.DataSet.fields[0].asString + Chr(39));
  Sql.Add(‘order by CODIGO_PROCESOS_ESP’);
  Open;
  First;
  end;

// borramos todos los registros de procesos especiales asignados originalmente en la tabla de relación
while not consulta_Relaciones.eof do
  begin
  consulta_relaciones.Delete; // borramos todos los registros para ese producto
  end;
// la variable numero nos devolverá la cantidad de items en el listview
numero:= lvw_destino.Items.Count;
for xIndice:= 0 to numero – 1 do // recorremos un bucle con ese valor
  begin
  // si el item pertenece a procesos especiales (su imageindex es 1)
  if lvW_destino.items.item[xIndice].ImageIndex = 1 then
    begin
    consulta_Relaciones.insert; // insertamos un registro
    // damos valor a todos sus campos según este item
    consulta_relaciones.fields[1].asString:= CodigoProducto;
    consulta_relaciones.Fields[2].asString:= lvW_destino.items.item[xIndice].Caption;
   // si subitems no esta asignado (cantidad)
    if lvW_destino.items.item[xIndice].SubItems = nil then
     // asignamos al campo el valor 0 para que no genere una excepción
       consulta_relaciones.Fields[3].asString:= ‘0’
    else // en el caso de que sí esté asignada
     // le damos el valor que ha proporcionado el usuario
       consulta_relaciones.Fields[3].asString:= lvW_destino.items.item[xIndice].SubItems.Strings[0];
    // validamos la tabla
    consulta_relaciones.Post;
    end;
  end;
end;

end.

A medida que avanzaba en las explicaciones iba analizando los gestos de sus interlocutores, la atención que parecía mostrar el gerente hacia todo lo que  narraba. Inició su perorata en el registro de proveedores y la intención era acabar con el plato fuerte: asignaciones por ratón…  Pero la mirada de la srta. administrativo era mas bien de cierta preocupación…
Cuando hubo acabado y se disponía a iniciar los postres y el champán (figuradamente) -tenía como complemento a la cita una estupenda caja de habanos para facilitar las relaciones- se oyó la voz de la srta. como de lejos, augurando algún chaparrón inesperado.
-Por cierto, -increpó con cierta ironía- entonces, en el caso de que un producto terminado se me acabe convirtiendo en materia prima de otro producto que tengo que hacer… ¿lo podré arrastrar, no?
Me quedé pensativo durante aproximadamente 5 o 6 segundos, momento en el que el gerente aprovechó para interferir:
-Pues claro… (y esbozó una sonrisa como diciéndome -lo he hecho bien ¿no?-)
Aquello no era una llovizna, ni siquiera un chaparrón… ¡era el diluvio!.

Vamos a analizar la situación para intentar comprender la preocupación de nuestro amigo: 

Como pudimos ver en la parte primera de estas observaciones, que hablaban acerca de la tejedora de croched, la realidad se nos presenta como un todo, carente de incertidumbre, como acción plena. Cualquier intento por nuestra parte de simularla nos obliga a adoptar unas reglas y unas convenciones para acotar su ámbito y poder hacer la representación.
Si tu afirmas:
-La tensión del hilo influirá en el modelo de cosido intercalando 3 posiciones…
ya te estás equivocando, pues supones que la tensión va a ser semejante en distintos tipos de hilatura. Dependerá del grosor, de la rigidez, de la fragilidad, de la elasticidad, de que nuestro tejedor no lo frene con contrapeso.
Así mismo, en el modelo de producción la diferencia entre la consideración de un producto como materia prima es formal. Es parte de nuestra visión de esa realidad. Pero la realidad no crea incertidumbre:
Si tu afirmas:
– Pedro es hijo de Juan, Pedro, en términos reales, nunca podrá ser padre de Juan
Esto, que en términos reales es un perogrullada, en términos formales tenemos que garantizarlo, puesto que si admitimos, como es este caso, que un producto puede llegar a convertirse en materia prima de otro producto, no tendríamos garantía de que no se pudiera asignar otra materia que ya lo contuviese y eso sería falso por definición.
Y además, queda un tema pendiente:
Partimos del supuesto de que la aplicación debe actualizar automáticamente el valor de los productos al producirse un incremento o decremento en el valor de las materia que lo componen Y esto tampoco queda garantizado. Puesto que se puede producir una asignación que viole el principio de la lógica, y dado que la actualización del valor de los productos se produciría mediante campos calculados, se generarían referencias circulares que desbordarían la pila: si se produce un aumento en el valor del producto, como se compone de si mismo, generaría un incremento en su valor como materia prima que volvería a repercutir en si mismo como producto… y así, en un ciclo sin fin. 

Aquí ya tenemos material para un nuevo ejercicio. Intentaremos buscar una solución y para ello pondremos nuestra mirada en la misma VCL intentando buscar paralelismos que nos ayuden. 

Se despidió con un ligero apretón de manos del gerente.
– Descuide, en unos días tendré todo esto perfilado… – dijo con poca convicción-
Y mientras bajaba cabizbajo las escaleras, pensó para si, me ha faltado contundencia…
Se paró en el último escalón, abrió su agenda y apunto:
-Para el día 30 unos bombones y un ramo de rosas… ¡no faltaba mas!.

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