unit uContactosController;

interface


uses
  Windows, Forms, Classes, Controls, Contnrs, SysUtils, uDADataTable,
  uBizContactos, uBizContactosDatosBancarios, uIDataModuleContactos;

type
  IContactosController = interface
    ['{43F9A4ED-6563-4F95-9A56-330CF9C66731}']
    function Buscar(const ID: Integer): IBizContacto;
    function BuscarTodos: IBizContacto;
    procedure Ver(AContacto : IBizContacto);
    procedure VerTodos(AContactos: IBizContacto);
    function Nuevo : IBizContacto;
    procedure Anadir(AContacto : IBizContacto);
    procedure Eliminar(const ID : Integer); overload;
    function Eliminar(AContacto : IBizContacto): Boolean; overload;
    function Guardar(AContacto : IBizContacto): Boolean;
    procedure DescartarCambios(AContacto : IBizContacto);
    function Existe(const ID: Integer) : Boolean;
    function Duplicar(AContacto : IBizContacto) : IBizContacto;
    function Localizar(AContactos: IBizContacto; const ID : Integer): Boolean;
    function ElegirContacto(AContactos : IBizContacto;
      AMensaje: String; AMultiSelect: Boolean): IBizContacto;

    function ExtraerSeleccionados(AContactos: IBizContacto) : IBizContacto;
  end;

  TContactosController = class(TInterfacedObject, IContactosController)
  protected
    FDataModule : IDataModuleContactos;
    procedure AsignarID(AContacto: IBizContacto; const IDNuevo : Integer); virtual;
    procedure AsignarIDDetalles(AContacto: IBizContacto;
      const IDCabecera : Integer; ADataTable : TDADataTable);
    procedure FiltrarEmpresa(AContacto : IBizContacto); virtual;
    function _Vacio : IBizContacto; virtual;
    function ValidarContacto(AContacto : IBizContacto): Boolean; virtual;
  public
    constructor Create; virtual;
    destructor Destroy; override;

    procedure Eliminar(const ID : Integer); overload; virtual;
    function Eliminar(AContacto : IBizContacto): Boolean; overload; virtual;
    function Guardar(AContacto : IBizContacto): Boolean; 
    procedure DescartarCambios(AContacto : IBizContacto); virtual;
    function Existe(const ID: Integer) : Boolean; virtual;
    procedure Anadir(AContacto : IBizContacto); virtual;
    function Duplicar(AContacto: IBizContacto): IBizContacto; virtual;
    function ExtraerSeleccionados(AContactos: IBizContacto) : IBizContacto; virtual;
    function Localizar(AContactos: IBizContacto; const ID : Integer): Boolean;
    
    // Se implementa en los controladores hijos
    function Buscar(const ID: Integer): IBizContacto; virtual; abstract;
    function BuscarTodos: IBizContacto; virtual; abstract;
    function Nuevo : IBizContacto; virtual; abstract;
    procedure Ver(AContacto : IBizContacto); virtual; abstract;
    procedure VerTodos(AContactos: IBizContacto); virtual; abstract;
    function ElegirContacto(AContactos : IBizContacto;
      AMensaje: String; AMultiSelect: Boolean): IBizContacto; virtual; abstract;
  end;

implementation

uses
  Dialogs, uEditorRegistryUtils, cxControls, DB, uDAInterfaces, uDataTableUtils,
  uDataModuleUsuarios, schContactosClient_Intf;

{ TContactosController }

procedure TContactosController.Anadir(AContacto: IBizContacto);
begin
  AContacto.Insert;
end;

procedure TContactosController.AsignarID(AContacto: IBizContacto; const IDNuevo : Integer);
begin
  if not Assigned(AContacto) then
    raise Exception.Create ('Contacto no asignado');

  if AContacto.EsNuevo then
  begin
    { ¡¡¡ OJO !!!
      Primero cambiamos el ID de las tablas detalles
      porque si cambiamos antes el ID de la cabecera
      deja de funcionar la relacion M/D y no
      encontraríamos las filas detalle.

        -->  MASTER.ID = DETAIL.ID_CONTACTO <--
    }

    with AContacto._Categorias do
    begin
      if not DataTable.Active then
        DataTable.Active := True;
      Insert;
      ID_CONTACTO := IDNuevo;
      ID_CATEGORIA := AContacto.ID_CATEGORIA;
      Post;
    end;
  end;

  { Los datos bancarios hay que comprobarlos siempre
    tanto en inserción como en modificación. }
  if Assigned(AContacto.DatosBancarios) then
    AsignarIDDetalles(AContacto, IDNuevo, AContacto.DatosBancarios.DataTable);


  { Las direcciones extras hay que comprobarlas siempre
    tanto en inserción como en modificación. }
  if Assigned(AContacto.Direcciones) then
    AsignarIDDetalles(AContacto, IDNuevo, AContacto.Direcciones.DataTable);


  if AContacto.EsNuevo then
  begin
    AContacto.Edit;
    AContacto.ID := IDNuevo;
    AContacto.ID_EMPRESA := dmUsuarios.IDEmpresaActual;
    AContacto.Post;
  end;
end;


function TContactosController.Duplicar(AContacto: IBizContacto): IBizContacto;
begin
  Result := Self._Vacio;
  ShowHourglassCursor;
  try
    DuplicarRegistros(AContacto.DataTable, Result.DataTable, mdrActual);
    DuplicarRegistros(AContacto.DatosBancarios.DataTable, Result.DatosBancarios.DataTable, mdrTodos);
    if Assigned(AContacto.Direcciones) then
       DuplicarRegistros(AContacto.Direcciones.DataTable, Result.Direcciones.DataTable, mdrTodos);

    // Hay que dejar algunos campos como si fuera un contacto nuevo
    Result.Edit;
    with Result do
    begin
      ID_EMPRESA := dmUsuarios.IDEmpresaActual;
      USUARIO := dmUsuarios.LoginInfo.Usuario;
    end;

    Result.Post;
  finally
    HideHourglassCursor;
  end;
end;

constructor TContactosController.Create;
begin
  FDataModule := NIL;
end;

procedure TContactosController.DescartarCambios(AContacto: IBizContacto);
begin
  if not Assigned(AContacto) then
    raise Exception.Create ('Contacto no asignado');

  ShowHourglassCursor;
  try
    if (AContacto.State in dsEditModes) then
      AContacto.Cancel;

    AContacto.DataTable.CancelUpdates;
  finally
    HideHourglassCursor;
  end;
end;

destructor TContactosController.Destroy;
begin
  FDataModule := NIL;
  inherited;
end;

function TContactosController.Eliminar(AContacto: IBizContacto): Boolean;
begin
  Result := False;

  if not Assigned(AContacto) then
    raise Exception.Create ('Contacto no asignado');

  ShowHourglassCursor;
  try
    if (AContacto.State in dsEditModes) then
      AContacto.Cancel;

    AContacto.Delete;
    AContacto.DataTable.ApplyUpdates;
    HideHourglassCursor;
    Result := True;
  except
    on E: Exception do
    begin
      AContacto.DataTable.CancelUpdates;
      HideHourglassCursor;
      if (Pos('FOREIGN KEY', E.Message) > 0) then
         MessageBox(0, 'No se puede borrar este contacto porque tiene documentos dados de alta (como presupuestos, albaranes, facturas, etc)', 'Atención', MB_ICONWARNING or MB_OK);
    end;
  end;
end;

procedure TContactosController.Eliminar(const ID: Integer);
var
  AContacto : IBizContacto;
begin
  AContacto := Buscar(ID);

  if not Assigned(AContacto) then
    raise Exception.Create(Format('No se ha encontrado el contacto con ID = %d', [ID]));

  Eliminar(AContacto);
  AContacto := NIL;
end;

function TContactosController.Existe(const ID: Integer): Boolean;
var
  AContacto : IBizContacto;
begin
  try
    AContacto := Buscar(ID);
    Result := Assigned(AContacto) and (AContacto.ID = ID);
  finally
    AContacto := NIL;
  end;
end;

function TContactosController.ExtraerSeleccionados(
  AContactos: IBizContacto): IBizContacto;
var
  ASeleccionados : IBizContacto;
begin
  ASeleccionados := Self.Buscar(ID_NULO);
  CopyDataTable(AContactos.DataTable, ASeleccionados.DataTable, True);
  Result := ASeleccionados;
end;

function TContactosController.Guardar(AContacto: IBizContacto): Boolean;
var
  NuevoID : Integer;
begin
  Result := False;

  if ValidarContacto(AContacto) then
  begin
    ShowHourglassCursor;
    try
      if AContacto.EsNuevo then
        NuevoID := FDataModule.GetNextID(AContacto.DataTable.LogicalName)
      else
        NuevoID := AContacto.ID;

      AsignarID(AContacto, NuevoID);
      AContacto.DataTable.ApplyUpdates;

      Result := True;      
    finally
      HideHourglassCursor;
    end;
  end;
end;

function TContactosController.Localizar(AContactos: IBizContacto;
  const ID: Integer): Boolean;
begin
  Result := True;
  ShowHourglassCursor;
  try
    with AContactos.DataTable do
    begin
      DisableControls;
      First;
      if not Locate(fld_ContactosID, ID, []) then
         Result := False;
      EnableControls;
    end;
  finally
    HideHourglassCursor;
  end;
end;

procedure TContactosController.AsignarIDDetalles (AContacto: IBizContacto; const IDCabecera : Integer; ADataTable : TDADataTable);
var
  AContador : Integer;
begin
  if not ADataTable.Active then
    ADataTable.Active := True;

  if AContacto.EsNuevo then
  begin
    { ¡¡¡¡ OJO !!!!
      Para asignar el ID en los detalles hay
      que tener en cuenta una cosa:
      Si se cambia el ID, ese detalle ya no
      pertenece a esa cabecera porque ya no se
      cumple la condición de la relacion:
        Master.ID = Detail.ID_CONTACTO.

      Por esa razón no sirve hacer un recorrido
      desde el principio hasta el final porque
      las detalles van desapareciendo según asignamos
      el valor al campo ID y nos mueve aleatoriamente
      la posición del registro actual.

      Es mejor hacer un bucle sencillo hasta que
      "se gasten" todos los detalles. Cuando el
      RecordCount llegue a 0 quiere decir que hemos
      tratado todos los detalles.
    }
    while ADataTable.RecordCount > 0 do
    begin
      ADataTable.First;
      ADataTable.Edit;
      ADataTable.FieldByName('ID').AsInteger := FDataModule.GetNextID(ADataTable.LogicalName);
//      ADataTable.FieldByName('ID_CONTACTO').AsInteger := IDCabecera;
      //OJO SI UN DIA SON VARIOS LOS CAMPOS FORANEOS, SOLO FUNCIONA CUANDO ES UN ÚNICO CAMPO DE CLAVE FORANEA
      ADataTable.FieldByName(ADataTable.DetailFields).AsInteger := IDCabecera;
      ADataTable.Post;
    end;
  end
  else begin
    { En este caso es un recorrido normal y corriente. }
    ADataTable.First;
    AContador := ADataTable.RecordCount;

    while (AContador > 0) do
    begin
      // ¿Es nuevo?
      if (ADataTable.FieldByName('ID').AsInteger < 0) then
      begin
        ADataTable.Edit;
        ADataTable.FieldByName('ID').AsInteger := FDataModule.GetNextID(ADataTable.LogicalName);
        ADataTable.Post;
      end;
      AContador := AContador - 1;
      ADataTable.Next;
    end;
    ADataTable.First;
  end;
end;


function TContactosController.ValidarContacto(AContacto: IBizContacto): Boolean;
begin
  Result := False;

  if not Assigned(AContacto) then
    raise Exception.Create ('Contacto no asignado');

  if (AContacto.DataTable.State in dsEditModes) then
    AContacto.DataTable.Post;

  if Length(AContacto.NOMBRE) = 0 then
    raise Exception.Create('Debe indicar al menos el nombre de este contacto.');

  // Asegurarse de valores en campos "automáticos"
  AContacto.Edit;
  AContacto.USUARIO := dmUsuarios.LoginInfo.Usuario;
  AContacto.Post;
  
  Result := True;
end;

function TContactosController._Vacio: IBizContacto;
begin
  Result := Buscar(ID_NULO);
end;

procedure TContactosController.FiltrarEmpresa(AContacto: IBizContacto);
begin
  if AContacto.DataTable.Active then
    AContacto.DataTable.Active := False;

  // Filtrar los contactos actuales por empresa
  with AContacto.DataTable.Where do
  begin
    if NotEmpty then
      AddOperator(opAND);
    AddCondition(fld_ContactosID_EMPRESA, cEqual, dmUsuarios.IDEmpresaActual);
  end;
end;

end.
