unit uFacturasClienteController;

interface


uses
  Windows, Classes, SysUtils, Variants, uDADataTable, uControllerBase, uIDataModuleFacturasCliente,
  uClientesController, uDetallesFacturaClienteController, uBizAlbaranesCliente,
  uBizFacturasCliente, uBizDireccionesContacto;

type
  IFacturasClienteController = interface(IControllerBase)
  ['{CAD20B4E-6D0B-4A1C-9306-B195824B6CAD}']
    function GetClienteController: IClientesController;
    procedure SetClienteController(const Value: IClientesController);
    property ClienteController: IClientesController read GetClienteController write SetClienteController;

    function GetDetallesController: IDetallesFacturaClienteController;
    procedure SetDetallesController(const Value: IDetallesFacturaClienteController);
    property DetallesController: IDetallesFacturaClienteController read GetDetallesController write SetDetallesController;

    function Buscar(const ID: Integer): IBizFacturaCliente;
    function BuscarTodos: IBizFacturaCliente;
    function BuscarTodasPendientesComision(IdAgente: Integer; IdComision: Integer; IdFacturasAsociadas: String): IBizFacturaCliente;
    procedure Ver(AFactura : IBizFacturaCliente);
    procedure VerTodos(AFacturas: IBizFacturaCliente);
    function Nuevo (WithInsert: Boolean = True) : IBizFacturaCliente;
    function Anadir(AFactura : IBizFacturaCliente) : Boolean; overload;
    function AnadirAbono(AFactura : IBizFacturaCliente) : Boolean;
    function Anadir(AFacturas : IBizFacturaCliente; AListaAlbaranes : IBizAlbaranCliente): Boolean; overload;

    function Eliminar(const ID : Integer): Boolean; overload;
    function Eliminar(AFactura : IBizFacturaCliente; AllItems: Boolean = false; AListaCausas: TStringList = nil): Boolean; overload;
    function Guardar(AFactura : IBizFacturaCliente): Boolean;
    procedure DescartarCambios(AFactura : IBizFacturaCliente);
    function Existe(const ID: Integer) : Boolean;
    procedure RecuperarCliente(AFactura : IBizFacturaCliente);
    function Duplicar(AFactura: IBizFacturaCliente): IBizFacturaCliente;
    function GenerarAbono(AFactura: IBizFacturaCliente): IBizFacturaCliente;

    procedure Preview(AFactura : IBizFacturaCliente; AllItems: Boolean = false; AVerDatosCliente: Boolean = true);
    procedure Print(AFactura : IBizFacturaCliente; AllItems: Boolean = false; AVerDatosCliente: Boolean = true);
    procedure EnviarFacturaPorEMail(AFactura : IBizFacturaCliente);
    procedure GenerarPDF(AFactura : IBizFacturaCliente; AllItems: Boolean = false; AFirmado: Boolean = False);

    procedure RecalcularImportes(AFactura: IBizFacturaCliente);

    function ElegirFacturas(AFacturas : IBizFacturaCliente; AMensaje: String; AMultiSelect: Boolean): IBizFacturaCliente;
    function ExtraerSeleccionados(ARecibosCliente: IBizFacturaCliente) : IBizFacturaCliente;

    procedure CopiarDireccion (const ADireccionEnvio: IBizDireccionesContacto; AFactura: IBizFacturaCliente);
    procedure SetIgnorarContabilidad (AFactura: IBizFacturaCliente; const Ignorar: Integer);

    function DarListaAnosFacturas: TStringList;
    procedure FiltrarAno(AFactura: IBizFacturaCliente; ADynWhereDataTable: WideString; const Ano: String);
    function CambiarSituacion(AFactura : IBizFacturaCliente; AllItems: Boolean = false; ASituacion: String = CTE_PENDIENTE; AListaCausas: TStringList = nil): Boolean;
    function AsignarLOPD(AFacturas: IBizFacturaCliente): Boolean;
  end;

  TFacturasClienteController = class(TControllerBase, IFacturasClienteController)
  private
    FDataModule : IDataModuleFacturasCliente;
    FClienteController : IClientesController;
    FDetallesController : IDetallesFacturaClienteController;

    function GetClienteController: IClientesController;
    procedure SetClienteController(const Value: IClientesController);

    function GetDetallesController: IDetallesFacturaClienteController;
    procedure SetDetallesController(const Value: IDetallesFacturaClienteController);

    function CreateEditor(const AName : String; const IID: TGUID; out Intf): Boolean;
    function _Vacio : IBizFacturaCliente;

    procedure FiltrarEmpresa(AFactura: IBizFacturaCliente);

    function ValidarFactura(AFactura: IBizFacturaCliente): Boolean;
    procedure GenerarRecibos(AFactura: IBizFacturaCliente);

  protected
    procedure RecibirAviso(ASujeto: ISujeto; ADataTable: IDAStronglyTypedDataTable); override;

  public
    property ClienteController: IClientesController read GetClienteController write SetClienteController;
    property DetallesController: IDetallesFacturaClienteController read GetDetallesController write SetDetallesController;

    constructor Create; override;
    destructor Destroy; override;

    function Eliminar(const ID : Integer): Boolean; overload;
    function Eliminar(AFactura : IBizFacturaCliente; AllItems: Boolean = false; AListaCausas: TStringList = nil): Boolean; overload;
    function Guardar(AFactura : IBizFacturaCliente): Boolean;
    procedure DescartarCambios(AFactura : IBizFacturaCliente); virtual;
    function Existe(const ID: Integer) : Boolean; virtual;
    function Anadir(AFactura : IBizFacturaCliente): Boolean; overload;
    function AnadirAbono(AFactura : IBizFacturaCliente) : Boolean;
    function Anadir(AFacturas : IBizFacturaCliente; AListaAlbaranes : IBizAlbaranCliente): Boolean; overload;
    function Buscar(const ID: Integer): IBizFacturaCliente;
    function BuscarTodos: IBizFacturaCliente;
    function BuscarTodasPendientesComision(IdAgente: Integer; IdComision: Integer; IdFacturasAsociadas: String): IBizFacturaCliente;
    function Nuevo (WithInsert: Boolean = True) : IBizFacturaCliente;
    procedure Ver(AFactura : IBizFacturaCliente);
    procedure VerTodos(AFacturas: IBizFacturaCliente);
    function Duplicar(AFactura: IBizFacturaCliente): IBizFacturaCliente;
    function GenerarAbono(AFactura: IBizFacturaCliente): IBizFacturaCliente;
    procedure RecuperarCliente(AFactura : IBizFacturaCliente);

    procedure Preview(AFactura : IBizFacturaCliente; AllItems: Boolean = false; AVerDatosCliente: Boolean = true);
    procedure Print(AFactura : IBizFacturaCliente; AllItems: Boolean = false; AVerDatosCliente: Boolean = true);
    procedure EnviarFacturaPorEMail(AFactura : IBizFacturaCliente);
    procedure GenerarPDF(AFactura : IBizFacturaCliente; AllItems: Boolean = false; AFirmado: Boolean = False);

    procedure RecalcularImportes(AFactura: IBizFacturaCliente);
    function EsModificable(AFactura: IBizFacturaCliente; var AComentario: Variant): Boolean;
    function EsEliminable(AFactura: IBizFacturaCliente; var AComentario: Variant): Boolean;

    function ElegirFacturas(AFacturas : IBizFacturaCliente; AMensaje: String; AMultiSelect: Boolean): IBizFacturaCliente;
    function ExtraerSeleccionados(AFacturasCliente: IBizFacturaCliente) : IBizFacturaCliente;

    procedure CopiarDireccion (const ADireccionEnvio: IBizDireccionesContacto; AFactura: IBizFacturaCliente);

    procedure SetIgnorarContabilidad (AFactura: IBizFacturaCliente; const Ignorar: Integer);

    function DarListaAnosFacturas: TStringList;
    procedure FiltrarAno(AFactura: IBizFacturaCliente; ADynWhereDataTable: WideString; const Ano: String);

    function CambiarSituacion(AFactura : IBizFacturaCliente; AllItems: Boolean = false; ASituacion: String = CTE_PENDIENTE; AListaCausas: TStringList = nil): Boolean;
    function AsignarLOPD(AFacturas: IBizFacturaCliente): Boolean;
  end;

implementation

uses
  uDADelta, uDateUtils, uNumUtils, Controls, cxControls, DB, uEditorRegistryUtils, schFacturasClienteClient_Intf,
  uBizContactos, uIEditorFacturasCliente, uIEditorFacturaCliente, uFactuGES_App,
  uDataModuleFacturasCliente, uBizDetallesFacturaCliente, uControllerDetallesBase,
  uDataModuleUsuarios, uDAInterfaces, uDataTableUtils, uAlbaranesClienteController,
  schAlbaranesClienteClient_Intf, uROTypes, uDetallesAlbaranClienteController,
  uBizDetallesAlbaranCliente, uFacturasClienteReportController, DateUtils, Forms,
  Dialogs, uIntegerListUtils, uFormasPagoController, uBizFormasPago, uIEditorElegirFacturasCliente,
  uBizEjercicios, uRecibosClienteController, uBizRecibosCliente,
  uSistemaFunc, uEMailUtils, uDialogElegirEMail, uDialogUtils;
  //, uDialogUtils, Windows;


procedure CopiarArticulosAlbaran(AOrigen: IBizDetallesAlbaranCliente;
  ADestino : IBizDetallesFacturaCliente);
var
  i : integer;
  ADetallesController : IDetallesFacturaClienteController;
begin
  if not Assigned(AOrigen) then
    raise Exception.Create ('Origen no asignado (CopiarArticulosAlbaran)');

  if not Assigned(ADestino) then
    raise Exception.Create ('Destino no asignado (CopiarArticulosAlbaran)');

  if not AOrigen.DataTable.Active then
    AOrigen.DataTable.Active := True;

  if not ADestino.DataTable.Active then
    ADestino.DataTable.Active := True;

  ADetallesController := TDetallesFacturaClienteController.Create;
  try
    //OJO IMPORTANTE
    //Siempre que vayamos a trabajar con los detalles debemos hacer un beginupdate de los mismos y un endupdate para
    //obligarle siempre a recalcular los detalles una sola vez
    ADetallesController.BeginUpdate(ADestino);

    AOrigen.DataTable.First;
    for i := 0 to AOrigen.DataTable.RecordCount - 1 do
    begin
      ADetallesController.Add(ADestino, TIPO_DETALLE_CONCEPTO);
      ADestino.Edit;
//      ADestino.REFERENCIA := AOrigen.REFERENCIA;
      if AOrigen.ID_ARTICULO > 0 then
         ADestino.ID_ARTICULO := AOrigen.ID_ARTICULO;
      ADestino.CONCEPTO := AOrigen.CONCEPTO;
      ADestino.CANTIDAD := AOrigen.CANTIDAD;
      ADestino.IMPORTE_UNIDAD := AOrigen.IMPORTE_UNIDAD;
      ADestino.IMPORTE_TOTAL := AOrigen.IMPORTE_TOTAL;
      ADestino.DESCUENTO := AOrigen.DESCUENTO;
      ADestino.IMPORTE_PORTE := AOrigen.IMPORTE_PORTE;
      ADestino.VISIBLE := AOrigen.VISIBLE;
//      ADestino.REFERENCIA_PROVEEDOR := AOrigen.REFERENCIA_PROVEEDOR;
      ADestino.Post;
      AOrigen.Next;
    end;
  finally
    ADetallesController.EndUpdate(ADestino);  
    ADetallesController := NIL;
  end;
end;


{ TFacturasClienteController }

function TFacturasClienteController.Anadir(AFactura: IBizFacturaCliente) : Boolean;
begin
  if not Assigned(AFactura) then
     raise Exception.Create ('Factura no asignada (Anadir)');
  AFactura.Insert;
  Result := True;
end;

function TFacturasClienteController.AnadirAbono(AFactura: IBizFacturaCliente): Boolean;
begin
  Anadir(AFactura);
  AFactura.TIPO := CTE_TIPO_ABONO;
  Result := True;
end;

function TFacturasClienteController.AsignarLOPD(AFacturas: IBizFacturaCliente): Boolean;
var
  ACliente : IBizCliente;
  AResultado : Boolean;
begin
  Result := false;
  if Assigned(AFacturas) then
  begin
     with AFacturas.DataTable do
     begin
       First;
       while not EOF do
       begin
         ACliente := (FClienteController.Buscar(AFacturas.ID_Cliente)  as IBizCliente);
         AResultado := FClienteController.AsignarLOPD(ACliente);
         if not Result then
            Result := AResultado;
         Next;
       end;
     end;
  end;
end;

function TFacturasClienteController.Anadir(AFacturas: IBizFacturaCliente;
  AListaAlbaranes: IBizAlbaranCliente): Boolean;
var
  AFacturaActual : IBizFacturaCliente;
  AAlbaranesController : IAlbaranesClienteController;
  I: Integer;
  bEnEdicion : Boolean;
begin
  Result := False;

  // ATENCIÓN!!! AFacturas tiene que estar vacio para no pisar facturas
  // ya generadas.
  if not Assigned(AFacturas) then
    raise Exception.Create ('Factura no asignada (Anadir)');

  if not Assigned(AListaAlbaranes) then
    raise Exception.Create ('Albaranes no asignados (Anadir)');

  if not AFacturas.DataTable.Active then
    AFacturas.DataTable.Active := True;

  if not AListaAlbaranes.DataTable.Active then
    AListaAlbaranes.DataTable.Active := True;

  ShowHourglassCursor;
  Application.ProcessMessages;

  AAlbaranesController := TAlbaranesClienteController.Create;
  try
    // Ordenar por fecha de albaran
    AListaAlbaranes.DataTable.Sort([fld_AlbaranesClienteFECHA_ALBARAN], [uDADataTable.sdAscending]);
    AListaAlbaranes.First;
    for I := 0 to AListaAlbaranes.DataTable.RecordCount - 1 do
    begin
      AListaAlbaranes._Cliente := NIL;
      AFacturaActual := NIL;

      // Busco si hay alguna factura ya hecha de ese cliente
      AFacturas.DataTable.First;
      if AFacturas.DataTable.Locate(fld_FacturasClienteID_CLIENTE, AListaAlbaranes.ID_CLIENTE, []) then
      begin
        AFacturaActual := AFacturas;
        RecuperarCliente(AFacturaActual);
        AFacturaActual.Cliente.DataTable.Active := True;
      end
      else begin
        // No hay factura de ese cliente. Creo una nueva
        Anadir(AFacturas);
        AAlbaranesController.RecuperarCliente(AListaAlbaranes);
        AListaAlbaranes.Cliente.DataTable.Active := True;
        AFacturas.Cliente := AListaAlbaranes.Cliente;

        //Si el albaran es de tipo devolución hacemos la factura de tipo abono
        if (AListaAlbaranes.TIPO = CTE_TIPO_ALBARAN_DEV) then
        begin
          bEnEdicion := (AFacturas.DataTable.State in dsEditModes);
          if not bEnEdicion then
             AFacturas.Edit;
          AFacturas.TIPO := CTE_TIPO_ABONO;
          AFacturas.Post;
          if bEnEdicion then
             AFacturas.Edit;
        end;

        AFacturaActual := AFacturas;
      end;

      // Ya tengo la factura. Le añado los conceptos del albarán
      AFacturaActual.Detalles.DataTable.Last;
      // Añado el título
      Self.DetallesController.Add(AFacturaActual.Detalles, TIPO_DETALLE_TITULO);
      with AFacturaActual.Detalles do
      begin
        Edit;
        CONCEPTO := 'Albarán ' + AListaAlbaranes.REFERENCIA + ' del ' + DateToStr(AListaAlbaranes.FECHA_ALBARAN);
        Post;
      end;
      // Añado el contenido del albarán
      CopiarArticulosAlbaran(AListaAlbaranes.Detalles, AFacturaActual.Detalles);

      Self.DetallesController.Add(AFacturaActual.Detalles, TIPO_DETALLE_CONCEPTO);
      with AFacturaActual.Detalles do
      begin
        Edit;
        CONCEPTO := 'Porte del albarán';
        CANTIDAD := 1;
        IMPORTE_UNIDAD := AListaAlbaranes.IMPORTE_PORTE;
        Post;
      end;

      // Añado el resumen
      Self.DetallesController.Add(AFacturaActual.Detalles, TIPO_DETALLE_SUBTOTAL);
      with AFacturaActual.Detalles do
      begin
        Edit;
        CONCEPTO := 'Total del albarán ' + AListaAlbaranes.REFERENCIA;
        Post;
      end;

      // Añado una línea en blanco
      Self.DetallesController.Add(AFacturaActual.Detalles, TIPO_DETALLE_CONCEPTO);
      with AFacturaActual.Detalles do
      begin
        Edit;
        CONCEPTO := '';
        Post;
      end;

      // Guardo la factura que acabo de generar o editar
      AFacturaActual.CalcularImporteTotal;
      Self.Guardar(AFacturaActual);
      // Asocio la factura con el albarán
      AListaAlbaranes.Edit;
      AListaAlbaranes.ID_FACTURA := AFacturaActual.ID;
      AListaAlbaranes.Post;
      AAlbaranesController.Guardar(AListaAlbaranes);

      AListaAlbaranes.Next;
    end;
    Result := True;
  finally
    AAlbaranesController := NIL;
    HideHourglassCursor
  end;
end;

{function TFacturasClienteController.Anadir(AFactura: IBizFacturaCliente;
  const IDPedido: Integer): Boolean;
var
  APedidosController : IPedidosClienteController;
  APedido : IBizPedidoCliente;
begin
  Result := False;
  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignada (Anadir)');

  if (IDPedido < 0) or (IDPedido = 0) then
    raise Exception.Create (Format('ID de pedido (%d) incorrecto (Anadir)', [IDPedido]));

  if not AFactura.DataTable.Active then
    AFactura.DataTable.Active := True;

  ShowHourglassCursor;
  Application.ProcessMessages;

  APedido := NIL;
  APedidosController := TPedidosClienteController.Create;
  try
    APedido := APedidosController.Buscar(IDPedido);

    if not Assigned(APedido) then
      raise Exception.Create (Format('No se ha encontrado un pedido de cliente con ID %d (Anadir)', [IDPedido]));

    APedido.DataTable.Active := True;
    APedidosController.RecuperarCliente(APedido);
    Self.Anadir(AFactura);
    AFactura.Cliente := APedido.Cliente;

    CopiarArticulosPedido(APedido.Detalles, AFactura.Detalles);

    // Guardo la factura que acabo de generar
    AFactura.CalcularImporteTotal;
    Self.Guardar(AFactura);

    Result := True;
  finally
    APedido := NIL;
    APedidosController := NIL;
    HideHourglassCursor;
    Application.ProcessMessages;
  end;
end;}


function TFacturasClienteController.Buscar(const ID: Integer): IBizFacturaCliente;
begin
  Result := (FDataModule as IDataModuleFacturasCliente).GetItem(ID);
  FiltrarEmpresa(Result);  
end;

function TFacturasClienteController.BuscarTodasPendientesComision(IdAgente:Integer; IdComision: Integer; IdFacturasAsociadas: String): IBizFacturaCliente;
var
  Condicion: TDAWhereExpression;
  Condicion1: TDAWhereExpression;
  Condicion2: TDAWhereExpression;
  Condicion3: TDAWhereExpression;
  Condicion4: TDAWhereExpression;

  Cadena : TStringList;
  i: Integer;

begin
  ShowHourglassCursor;
  try
    Result := BuscarTodos;

    with Result.DataTable.DynamicWhere do
    begin
      //Todas las facturas que sean comisionables
      Condicion := NewBinaryExpression(NewField('', fld_FacturasClienteSIN_COMISION), NewConstant(0, datInteger), dboEqual);

      if IsEmpty then
         Expression := Condicion
      else
         Expression := NewBinaryExpression(Expression, Condicion, dboAnd);
    end;


    with Result.DataTable.DynamicWhere do
    begin

      //Todas las facturas de esta liquidación
      Condicion1 := NewBinaryExpression(NewField('', fld_FacturasClienteID_COMISION_LIQUIDADA), NewConstant(IdComision, datInteger), dboEqual);
      Condicion2 := nil;
      //Todas aquellas facturas que ya esten asociadas a la liquidación actual no deben salir
      if (Length(IdFacturasAsociadas) > 0) then
      begin
        Cadena := TStringList.Create;
        Cadena.CommaText := IdFacturasAsociadas;
        //Vamos generando todas las where de cada uno de los ID recibidos por parametro
        for i := 0 to Cadena.Count - 1 do
            with Result.DataTable.DynamicWhere do
            begin
              Condicion4 := NewBinaryExpression(NewField('', fld_FacturasClienteID), NewConstant(StrToInt(Cadena.Strings[i]), datInteger), dboNotEqual);
              if Condicion2 = nil then
              Condicion2 := Condicion4
              else
              Condicion2 := NewBinaryExpression(Condicion2, Condicion4, dboAnd);
            end;

        Cadena.Free;
      end;
      //Si no hay facturas en el editor no las tenemos en cuenta
      if (Condicion2 <> nil) then
         Condicion1 := NewBinaryExpression(Condicion1, Condicion2, dboAnd);

      //Todas aquellas que no esten asociadas a ninguna liquidación, y que tampoco esten en el editor, ya que en cache estan
      //asociadas a la liquidadcion pero en datos me viene como nula
      Condicion3 := NewBinaryExpression(NewField('', fld_FacturasClienteID_COMISION_LIQUIDADA), NewNull(), dboEqual);
      Condicion2 := nil;      
     //Todas aquellas facturas que ya esten asociadas a la liquidación actual no deben salir
      if (Length(IdFacturasAsociadas) > 0) then
      begin
        Cadena := TStringList.Create;
        Cadena.CommaText := IdFacturasAsociadas;
        //Vamos generando todas las where de cada uno de los ID recibidos por parametro
        for i := 0 to Cadena.Count - 1 do
            with Result.DataTable.DynamicWhere do
            begin
              Condicion4 := NewBinaryExpression(NewField('', fld_FacturasClienteID), NewConstant(StrToInt(Cadena.Strings[i]), datInteger), dboNotEqual);
              if Condicion2 = nil then
              Condicion2 := Condicion4
              else
              Condicion2 := NewBinaryExpression(Condicion2, Condicion4, dboAnd);
            end;

        Cadena.Free;
      end;
      //Si no hay facturas en el editor no las tenemos en cuenta
      if (Condicion2 <> nil) then
         Condicion3 := NewBinaryExpression(Condicion3, Condicion2, dboAnd);

      // Todas aquellas facturas asociadas a la comisión actual y que no estén asociadas al editor (IdFacturasAsociadas)
      //esto es porque se puede agregar y quitar facturas y todos los cambios estan en cache
      // y por lo tanto al pedir las facturas a seleccionar debemos tener en cuenta que en el
      //almacén dichos cambios cache todavía no se han producido
      Condicion := NewBinaryExpression(Condicion1, Condicion3, dboOr);

      if IsEmpty then
         Expression := Condicion
      else
         Expression := NewBinaryExpression(Expression, Condicion, dboAnd);
    end;

  finally
    HideHourglassCursor;
  end;
end;

function TFacturasClienteController.BuscarTodos: IBizFacturaCliente;
begin
  Result := FDataModule.GetItems;
  FiltrarEmpresa(Result);
end;

function TFacturasClienteController.CambiarSituacion(AFactura: IBizFacturaCliente; AllItems: Boolean; ASituacion: String;
  AListaCausas: TStringList): Boolean;
//Solo devolverá true en el caso de cambiar de situación a todas las facturas
var
  bCambioOK: Boolean;
  ACausa: Variant;
  bEnEdicion: Boolean;
  ARecibosClienteController : IRecibosClienteController;
  ARecibos: IBizRecibosCliente;

begin
  bCambioOK := True;
  ARecibosClienteController := TRecibosClienteController.Create;

  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignada');

  ShowHourglassCursor;
  try
    if not AFactura.DataTable.Active then
       AFactura.DataTable.Active := True;

    if (AFactura.State in dsEditModes) then
       AFactura.Cancel;

    if not AllItems then
    begin
       if not EsModificable(AFactura, ACausa) then
       begin
         if Assigned(AListaCausas) then
            AListaCausas.Add(Format('%s=%s', [AFactura.REFERENCIA, ACausa]));
         bCambioOK := False;
      end
      else
      begin
        //Buscamos si tiene recibos la factura si es así no podrá cambiar de situación ya que son los recibos los que la definen
        ARecibos := ARecibosClienteController.BuscarRecibosFactura(AFactura.ID);
        ARecibos.open;
        if (ARecibos.RecordCount > 0) then
        begin
           if Assigned(AListaCausas) then
              AListaCausas.Add(Format('%s=%s', [AFactura.REFERENCIA, 'La factura tiene recibos asociados que definen su situación']));
           bCambioOK := False;
           ARecibos.Close;
           ARecibos := Nil;
        end
        else
        begin
           bEnEdicion := (AFactura.DataTable.State in dsEditModes);
           if not bEnEdicion then
              AFactura.DataTable.Edit;

           AFactura.SITUACION := ASituacion;

           AFactura.DataTable.Post;
           if bEnEdicion then
              AFactura.DataTable.Edit;
        end;
      end
    end
    else //En el caso de querer modificar todos los items del objeto AFactura
    begin
       with AFactura.DataTable do
       begin
         First;
         while not EOF do
         begin
           if not EsEliminable(AFactura, ACausa) then
           begin
             if Assigned(AListaCausas) then
                AListaCausas.Add(Format('%s=%s', [AFactura.REFERENCIA, ACausa]));
             bCambioOK := False;
           end
           else
           begin
            //Buscamos si tiene recibos la factura si es así no podrá cambiar de situación ya que son los recibos los que la definen
            ARecibos := ARecibosClienteController.BuscarRecibosFactura(AFactura.ID);
            ARecibos.open;
            if (ARecibos.RecordCount > 0) then
            begin
               if Assigned(AListaCausas) then
                  AListaCausas.Add(Format('%s=%s', [AFactura.REFERENCIA, 'La factura tiene recibos asociados que definen su situación']));
               bCambioOK := False;
               ARecibos.Close;
               ARecibos := Nil;
            end
            else
            begin
              bEnEdicion := (State in dsEditModes);
              if not bEnEdicion then
                 Edit;

              AFactura.SITUACION := ASituacion;

              Post;
              if bEnEdicion then
                 Edit;
            end;
           end;
           Next;
         end;
       end;
    end;

    try
      AFactura.DataTable.ApplyUpdates;
      Result := bCambioOK;
    except
      AFactura.DataTable.CancelUpdates;
      Result := False;
    end;

  finally
    ARecibosClienteController:= Nil;
    HideHourglassCursor;
  end;
end;

procedure TFacturasClienteController.CopiarDireccion(
  const ADireccionEnvio: IBizDireccionesContacto; AFactura: IBizFacturaCliente);
var
  bEnEdicion : Boolean;
begin
  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignada (CopiarDireccion)');

  if not Assigned(ADireccionEnvio) then
    raise Exception.Create ('No se ha indicado la dirección (CopiarDireccion)');

  if AFactura.DataTable.Active then
    AFactura.DataTable.Active := True;

  if ADireccionEnvio.DataTable.Active then
    ADireccionEnvio.DataTable.Active := True;

  bEnEdicion := (AFactura.DataTable.State in dsEditModes);
  if not bEnEdicion then
    AFactura.Edit;

  ShowHourglassCursor;
  AFactura.Edit;
  try
    AFactura.CALLE := ADireccionEnvio.CALLE;
    AFactura.POBLACION := ADireccionEnvio.POBLACION;
    AFactura.CODIGO_POSTAL := ADireccionEnvio.CODIGO_POSTAL;
    AFactura.PROVINCIA := ADireccionEnvio.PROVINCIA;
    AFactura.NIF_CIF := ADireccionEnvio.NIF_CIF;
    AFactura.NOMBRE := ADireccionEnvio.NOMBRE;

    if not bEnEdicion then
      AFactura.Post;
  finally
    HideHourglassCursor;
  end;
end;

constructor TFacturasClienteController.Create;
begin
  inherited;
  FDataModule := TDataModuleFacturasCliente.Create(Nil);
  FClienteController := TClientesController.Create;  
  FDetallesController := TDetallesFacturaClienteController.Create;
  FDetallesController.addObservador(Self);
end;

function TFacturasClienteController.CreateEditor(const AName: String;
  const IID: TGUID; out Intf): Boolean;
begin
  Result := Supports(EditorRegistry.CreateEditor(AName), IID, Intf);
end;

function TFacturasClienteController.DarListaAnosFacturas: TStringList;
begin
  Result := FDataModule.GetAnosItems;
end;

procedure TFacturasClienteController.DescartarCambios(AFactura: IBizFacturaCliente);
begin
  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignada');

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

    AFactura.DataTable.CancelUpdates;
  finally
    HideHourglassCursor;
  end;
end;

destructor TFacturasClienteController.Destroy;
begin
  FDataModule := Nil;
  FClienteController := Nil;
  FDetallesController := Nil;
  inherited;
end;

function TFacturasClienteController.Duplicar(AFactura: IBizFacturaCliente): IBizFacturaCliente;
begin
  Result := Self._Vacio;
  ShowHourglassCursor;
  try
    DuplicarRegistros(AFactura.DataTable, Result.DataTable, mdrActual);
    DuplicarRegistros(AFactura.Detalles.DataTable, Result.Detalles.DataTable, mdrTodos);

    // Hay que dejar algunos campos como si fuera una factura nueva
    Result.Edit;
    with Result do
    begin
      ID_EMPRESA := AppFactuGES.EmpresaActiva.ID;
      USUARIO := AppFactuGES.UsuarioActivo.UserName;
      ID_TIENDA := AppFactuGES.TiendaActiva.ID;
      TIENDA := AppFactuGES.TiendaActiva.NOMBRE;
      REFERENCIA := ''; //Para que se asigne una nueva
      SITUACION := CTE_PENDIENTE; //Al ser una nueva debe de estar pendiente
      FECHA_FACTURA := DateOf(Now);
      ID_COMISION_LIQUIDADA := 0;

      //CONTABILIDAD
      ID_EJERCICIO := AppFactuGES.EjercicioActivo.ID;
      ESTADO_EJERCICIO := AppFactuGES.EjercicioActivo.ESTADO;
      ASIENTO_PUNTEADO := -1;
      //En el caso de que el ejercicio este cerrado dejamos duplicar factura sin asociar a contabilidad.
      if ESTADO_EJERCICIO = CTE_CERRADO then
      begin
        IGNORAR_CONTABILIDAD := 1;
        ESTADO_EJERCICIO := '';
      end;
    end;

    Result.Post;
  finally
    HideHourglassCursor;
  end;
end;

function TFacturasClienteController.ValidarFactura(AFactura: IBizFacturaCliente): Boolean;
var
  AFormaPago: IBizFormaPago;

begin
  Result := False;

  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignada');

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

  //Tambien hacemos post de sus tablas hija
  if (AFactura.Detalles.DataTable.State in dsEditModes) then
      AFactura.Detalles.DataTable.Post;

  if (AFactura.ID_CLIENTE < 0) or (AFactura.ID_CLIENTE = 0) then
  // No comprobar el objeto Cliente por que puede fallar la validación
  // cuando se generan facturas automáticamente.
{     (not Assigned(AFactura.Cliente)) or
     (AFactura.Cliente.IsEmpty) then}
    raise Exception.Create('Debe indicar el cliente de esta factura');

  if (EsFechaVacia(AFactura.FECHA_FACTURA)) then
    raise Exception.Create('Debe indicar la fecha de esta factura');

  if (AFactura.Detalles.DataTable.RecordCount = 0) then
    raise Exception.Create('La factura debe tener al menos un concepto en su contenido');

  if (AFactura.ID_FORMA_PAGO = 0) then
    raise Exception.Create('Debe indicar una forma de pago para esta factura');

  //De esta forma obligaremos siempre a tener un recibo asociado a la factura,
  //porque si la forma de pago no tiene plazos es obligatorio la fecha de vencimiento
  //OJO-----En facturas de cliente para acana no es obligatorio porque deben de poder generar facturas sin recibos asociados
{  with TFormasPagoController.Create do
  begin
    try
      AFormaPago := Buscar(AFactura.ID_FORMA_PAGO);
      AFormaPago.DataTable.Active := True;
      if (AFormaPago.Plazos.RecordCount = 0)
      and (EsFechaVacia(AFactura.FECHA_VENCIMIENTO)) then
          raise Exception.Create('Debe indicar una fecha de vencimiento para esta factura');
    finally
      AFormaPago := NIL;
      Free;
    end;
  end;
}

  if (AFactura.IGNORAR_CONTABILIDAD = 0) then
  begin
    if (AppFactuGES.EjercicioActivo.ESTADO = CTE_CERRADO) then
       raise Exception.Create('Para que la factura pueda pasar a la parte contable el ejercicio activo debe esta ABIERTO');

    if ((AFactura.Cliente.SubCuentas.ID < 1) or (AFactura.Cliente.SubCuentas.ID_EJERCICIO <> AppFactuGES.EjercicioActivo.ID)) then
      raise Exception.Create('Para que la factura pueda pasar la parte contable es necesario que el cliente tenga asignada una subcuenta');
  end;

  //En caso de ser un Abono no podra tener un importe total positivo
  if (AFactura.TIPO = CTE_TIPO_ABONO) then
    if (AFactura.IMPORTE_TOTAL > 0) then
      raise Exception.Create('Un abono nunca no puede tener un importe positivo, asegurese que las cantidades sean negativas');

  { Esta validación puede saltar cuando se generan facturas automáticamente
    por albaranes o pedidos y el cliente no tiene Tipo de IVA puesto, se cambia,
    para signar automáticamente por defecto a la factura la primera forma de pago,
    e iva disponible (CONTADO, IVA16), en la clase de negocio }
{  if (AFactura.ID_TIPO_IVA = 0) then
    raise Exception.Create('Debe indicar un tipo de IVA para esta factura');}

  { Asegurarse de valores en campos "automáticos" tanto
    en MODIFICACIÓN como en INSERCIÓN. }
  AFactura.Edit;
  try
    AFactura.USUARIO := AppFactuGES.UsuarioActivo.UserName;

    if Assigned(AFactura.Cliente)
    and (AFactura.ID_CLIENTE <> AFactura.Cliente.ID) then
      AFactura.ID_CLIENTE := AFactura.Cliente.ID;

    Result := True;
  finally
    AFactura.Post;
  end;
end;

procedure TFacturasClienteController.Ver(AFactura: IBizFacturaCliente);
var
  AEditor : IEditorFacturaCliente;
  AComentario : Variant;
begin
  AEditor := NIL;

  RecuperarCliente(AFactura);

  CreateEditor('EditorFacturaCliente', IEditorFacturaCliente, AEditor);
  if Assigned(AEditor) then
    try
      AEditor.Controller := Self; //OJO ORDEN MUY IMPORTANTE
      AEditor.Factura := AFactura;

      //MODO CONSULTAR
      if not EsModificable(AFactura, AComentario) then
      begin
         SetDataTableReadOnly(AFactura.DataTable, True);
         AEditor.ReadOnly := True;
      end;

      if (AComentario <> null) then
         AEditor.Comentario := AComentario;

      AEditor.ShowModal;

      //MODO CONSULTAR (Se deja la tabla como estaba)
      if AEditor.ReadOnly then
         SetDataTableReadOnly(AFactura.DataTable, False);
    finally
      AEditor.Release;
      AEditor := NIL;
    end;
end;

procedure TFacturasClienteController.VerTodos(AFacturas: IBizFacturaCliente);
var
  AEditor : IEditorFacturasCliente;
begin
  AEditor := NIL;

  CreateEditor('EditorFacturasCliente', IEditorFacturasCliente, AEditor);
  if Assigned(AEditor) then
    with AEditor do
    begin
      Controller := Self; //OJO ORDEN MUY IMPORTANTE
      Facturas := AFacturas;
      MultiSelect := True;
      ShowEmbedded;
    end;
end;

function TFacturasClienteController._Vacio: IBizFacturaCliente;
begin
  Result := Buscar(ID_NULO);
end;

function TFacturasClienteController.Eliminar(const ID: Integer): Boolean;
var
  AFactura : IBizFacturaCliente;
begin
  AFactura := Buscar(ID);

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

  Result := Eliminar(AFactura);
  AFactura := NIL;
end;

function TFacturasClienteController.ElegirFacturas(AFacturas: IBizFacturaCliente; AMensaje: String;
  AMultiSelect: Boolean): IBizFacturaCliente;
var
  AEditor : IEditorElegirFacturasCliente;
begin
  Result := NIL;

  CreateEditor('EditorElegirFacturasCliente', IEditorElegirFacturasCliente, AEditor);
  if Assigned(AEditor) then
    try
       AEditor.Controller := Self;
       AEditor.Facturas := AFacturas;
       AEditor.MultiSelect := AMultiSelect;
       AEditor.Mensaje := AMensaje;
       if IsPositiveResult(AEditor.ShowModal) then
          Result := AEditor.FacturasClienteSeleccionados;
    finally
       AEditor.Release;
       AEditor := NIL;
    end;
end;

function TFacturasClienteController.Eliminar(AFactura: IBizFacturaCliente; AllItems: Boolean = false; AListaCausas: TStringList = nil): Boolean;
//Solo devolverá true en el caso de eliminar todos y cada uno de los elementos a eliminar
var
  bEliminado: Boolean;
  ACausa: Variant;

begin
  bEliminado := True;

  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignada');

  ShowHourglassCursor;
  try
    if not AFactura.DataTable.Active then
      AFactura.DataTable.Active := True;

    if (AFactura.State in dsEditModes) then
      AFactura.Cancel;

    //Siempre eliminaremos el seleccionado tanto si es una lista de facturas a eliminar como si es la propia lista del editor.
    if not EsEliminable(AFactura, ACausa) then
    begin
      if Assigned(AListaCausas) then
         AListaCausas.Add(Format('%d=%s', [AFactura.ID, ACausa]));
      bEliminado := False;
    end
    else
      AFactura.Delete;

    //En el caso de querer eliminar todos los items del objeto AAlbaran
    if AllItems then
    begin
       with AFactura.DataTable do
       begin
         First;
         while not EOF do
         begin
           if not EsEliminable(AFactura, ACausa) then
           begin
             if Assigned(AListaCausas) then
                AListaCausas.Add(Format('%d=%s', [AFactura.ID, ACausa]));
             bEliminado := False;
             Next;
           end
           else
             AFactura.Delete;
         end;
       end;
    end;

    try
      AFactura.DataTable.ApplyUpdates;
      Result := bEliminado;
    except
      //En el caso de una factura que tiene recibos con devoluciones hechas no se puede borrar aunque la factura este en situacion de pendiente
      AFactura.DataTable.CancelUpdates;
      Result := False;
    end;

  finally
    HideHourglassCursor;
  end;
end;

procedure TFacturasClienteController.EnviarFacturaPorEMail(AFactura: IBizFacturaCliente);
var
  AReportController : IFacturasClienteReportController;
  AFicheroTMP : TFileName;
  ATituloEnvio : String;
  AEMail : String;
  AListaEmail : TStringList;
begin
  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignado (EnviarFacturaPorEMail)');

  if AFactura.DataTable.Active then
    AFactura.DataTable.Active := True;

  AListaEmail := TStringList.Create;
  try
    RecuperarCliente(AFactura);
    AFactura.Cliente.DataTable.Active := True;

    if not AFactura.Cliente.EMAIL_1IsNull then
      AListaEmail.Add(AFactura.Cliente.EMAIL_1);
    if not AFactura.Cliente.EMAIL_2IsNull then
      AListaEmail.Add(AFactura.Cliente.EMAIL_2);

    if not ElegirEMail(AListaEmail, AEMail) then
      Exit;
  finally
    FreeANDNIL(AListaEmail);
  end;

  ShowHourglassCursor;
  ATituloEnvio := 'Factura ' + AFactura.REFERENCIA + ' - ' + AFactura.Cliente.NOMBRE;
  AFicheroTMP := DarFicheroPDFTemporal(EscapeIllegalChars(ATituloEnvio));
  AReportController := TFacturasClienteReportController.Create;
  try
    AReportController.ExportToPDF(AFactura.ID, AFicheroTMP);
    SendMailMAPI(ATituloEnvio, '', AFicheroTMP, '', '', AFactura.Cliente.NOMBRE, AEMail);
  finally
    DeleteFile(AFicheroTMP);
    AReportController := NIL;
    HideHourglassCursor;
  end;
end;

function TFacturasClienteController.EsEliminable(AFactura: IBizFacturaCliente; var AComentario: Variant): Boolean;
begin
  Result := True;

  if not Assigned(AFactura) then
     raise Exception.Create ('Factura no asignado: EsEliminable');

  if (AFactura.SITUACION <> CTE_PENDIENTE) then
  begin
    Result := False;
    AComentario := 'La factura esta parcial o totalmente pagada, por lo que no puede ser eliminada';
  end
  else if (AFactura.ESTADO_EJERCICIO = CTE_CERRADO) then
  begin
    Result := False;
    AComentario := 'La factura tiene un asiento asociado en la parte contable cuyo ejercicio esta cerrado, por lo que no puede ser eliminada';
  end
  else if (AFactura.ASIENTO_PUNTEADO > 0) then
  begin
    Result := False;
    AComentario := 'La factura tiene un asiento asociado en la parte contable y está punteado, por lo que no puede ser eliminada';
  end;
end;

function TFacturasClienteController.EsModificable(AFactura: IBizFacturaCliente; var AComentario: Variant): Boolean;
begin
  Result := True;
  AComentario := Null;

  if not Assigned(AFactura) then
     raise Exception.Create ('Factura no asignado: EsModificable');


  if (AFactura.SITUACION <> CTE_PENDIENTE) then
  begin
    if (AppFactuGES.UsuarioActivo.ID_PERFIL = CTE_PERFIL_ADMINISTRADOR)
    or (AppFactuGES.UsuarioActivo.ID_PERFIL = CTE_PREFIL_GERENCIA) then
      AComentario := 'ATENCIÓN - La factura esta parcial o totalmente pagada, los recibos se verán modificados si cambia los importes'
    else
    begin
      Result := False;
      AComentario := 'SOLO LECTURA - La factura esta parcial o totalmente pagada, por lo que no puede ser modificada';
    end
  end
  else
    if (AFactura.ESTADO_EJERCICIO = CTE_CERRADO) then
    begin
      Result := False;
      AComentario := 'SOLO LECTURA - La factura tiene un asiento en caja o banco que está cerrado, por lo que no puede ser modificada';
    end
    else if (AFactura.ASIENTO_PUNTEADO > 0) then
    begin
      Result := False;
      AComentario := 'SOLO LECTURA - La factura tiene un asiento en caja o banco que está cerrado, por lo que no puede ser modificada';
    end;
end;

procedure TFacturasClienteController.RecalcularImportes(
  AFactura: IBizFacturaCliente);
var
  bEnEdicion : Boolean;
  ADetallePosAct : Integer;
begin
  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignado (RecalcularImportes)');

  if AFactura.DataTable.Active then
    AFactura.DataTable.Active := True;

  { Hay que guardar la posición en la que estamos en los detalles por que
    la asignación de valores a los campos IMPORTE_NETO e IMPORTE_PORTE
    (ver más adelante) colocan el puntero en la tabla detalle al principio.
    No he encontrado la razón por la que mueve el puntero. }

  ADetallePosAct := AFactura.Detalles.POSICION;

  bEnEdicion := (AFactura.DataTable.State in dsEditModes);
  if not bEnEdicion then
    AFactura.Edit;

  ShowHourglassCursor;
  try
    AFactura.IMPORTE_NETO := FDetallesController.DarTotalImporteTotal(AFactura.Detalles);
    AFactura.IMPORTE_PORTE := FDetallesController.DarTotalPorteTotal(AFactura.Detalles);

    if not bEnEdicion then
      AFactura.Post;
  finally
    HideHourglassCursor;
    // Restaurar la posición que teníamos en los detalles.
    FDetallesController.LocalizarPosicion(AFactura.Detalles, ADetallePosAct);
  end;
end;

procedure TFacturasClienteController.RecibirAviso(ASujeto: ISujeto; ADataTable: IDAStronglyTypedDataTable);
var
  AFactura : IBizFacturaCliente;
  ADetalles : IBizDetallesFacturaCliente;
begin
  inherited;

  if Supports(ADataTable, IBizDetallesFacturaCliente, ADetalles) and
     Supports(ADetalles.DataTable.MasterSource.DataTable, IBizFacturaCliente, AFactura) then
  begin
    RecalcularImportes(AFactura);
  end;
end;

procedure TFacturasClienteController.RecuperarCliente(
  AFactura: IBizFacturaCliente);
begin
  AFactura._Cliente := (FClienteController.Buscar(AFactura.ID_CLIENTE)  as IBizCliente);
end;

function TFacturasClienteController.Existe(const ID: Integer): Boolean;
var
  AFactura : IBizFacturaCliente;
begin
  try
    AFactura := Buscar(ID);
    Result := Assigned(AFactura) and (AFactura.ID = ID);
  finally
    AFactura := NIL;
  end;
end;

function TFacturasClienteController.ExtraerSeleccionados(AFacturasCliente: IBizFacturaCliente): IBizFacturaCliente;
var
  ASeleccionados : IBizFacturaCliente;
begin
  ASeleccionados := (Self.Buscar(ID_NULO) as IBizFacturaCliente);
  CopyDataTableDA5(AFacturasCliente.DataTable, ASeleccionados.DataTable, True);
  Result := ASeleccionados;
end;

procedure TFacturasClienteController.FiltrarAno(AFactura: IBizFacturaCliente; ADynWhereDataTable: WideString; const Ano: String);
var
  Condicion: TDAWhereExpression;
  FechaIni: String;
  FechaFin: String;

begin
  AFactura.DataTable.DynamicWhere.Clear;
  AFactura.DataTable.DynamicWhere.Xml := ADynWhereDataTable;

  if (Ano <> 'Todos') then
  begin
    // Filtrar las facturas actuales por empresa
    FechaIni := '01/01/' + Ano;
    FechaFin := '31/12/' + Ano;
    with AFactura.DataTable.DynamicWhere do
    begin
      // (FECHA_INICIO between FECHA_FIN)
      Condicion := NewBinaryExpression(NewField('', fld_FacturasClienteFECHA_FACTURA), NewConstant(FechaIni, datString), dboGreaterOrEqual);
      Condicion := NewBinaryExpression(NewBinaryExpression(NewField('', fld_FacturasClienteFECHA_FACTURA), NewConstant(FechaFin, datString), dboLessOrEqual), Condicion, dboAnd);

      if IsEmpty then
         Expression := Condicion
      else
         Expression := NewBinaryExpression(Condicion, Expression, dboAnd);
    end;
  end;
end;

procedure TFacturasClienteController.FiltrarEmpresa(AFactura: IBizFacturaCliente);
var
  Condicion: TDAWhereExpression;
begin
  if AFactura.DataTable.Active then
    AFactura.DataTable.Active := False;

  // Filtrar las facturas actuales por empresa
  with AFactura.DataTable.DynamicWhere do
  begin
    // (ID_EMPRESA >= ID)
    Condicion := NewBinaryExpression(NewField('', fld_FacturasClienteID_EMPRESA), NewConstant(AppFactuGES.EmpresaActiva.ID, datInteger), dboEqual);

    if IsEmpty then
       Expression := Condicion
    else
       Expression := NewBinaryExpression(Expression, Condicion, dboAnd);
  end;
end;

procedure TFacturasClienteController.SetClienteController(const Value: IClientesController);
begin
  FClienteController := Value;
end;

procedure TFacturasClienteController.SetDetallesController(const Value: IDetallesFacturaClienteController);
begin
  FDetallesController := Value;
end;

procedure TFacturasClienteController.SetIgnorarContabilidad(AFactura: IBizFacturaCliente; const Ignorar: Integer);
var
  EnEdicion: Boolean;
begin
  if Assigned(AFactura) then
  begin
    EnEdicion := AFactura.DataTable.Editing;
    if not AFactura.DataTable.Editing then
       AFactura.DataTable.Edit;

    AFactura.IGNORAR_CONTABILIDAD := Ignorar;
    AFactura.DataTable.Post;

    if EnEdicion then
       AFactura.DataTable.Edit;
  end;
end;

function TFacturasClienteController.Guardar(AFactura: IBizFacturaCliente): Boolean;
var
  IDNuevo : Integer;
  EsModificacion: Boolean;
  HayCambio: TDADeltaChange;
  regenerarRecibos : Boolean;
begin
  Result := False;
  regenerarRecibos := True;

  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignada');

  if not Assigned(FDetallesController) then
    raise Exception.Create ('Controller detalles no asignado');

  EsModificacion := (AFactura.ID > 0);

  if ValidarFactura(AFactura) then
  begin
    ShowHourglassCursor;

    // Asegurarnos de que todos los importes están bien.
    RecalcularImportes(AFactura);

    if EsModificacion then
    begin
      //Al modificar la factura, si no cambia el importe no tocamos sus recibos.
      regenerarRecibos := False;
      //Comprobamos si ha cambiado el importe total, para avisar de la regeneración de los recibos
      HayCambio := AFactura.DataTable.Delta.FindChange(AFactura.RecNo);
      if (Assigned(HayCambio))
      and (HayCambio.OldValueByName[fld_FacturasClienteIMPORTE_TOTAL] <> 0) then
      begin
//      showmessage(currtostr(RoundCurrency(AFactura.DataTable.FieldByName('IMPORTE_TOTAL').asVariant)));
//      showmessage(currtostr(RoundCurrency(HayCambio.OldValueByName[fld_FacturasClienteIMPORTE_TOTAL])));
      if (RoundCurrency(AFactura.DataTable.FieldByName('IMPORTE_TOTAL').asVariant) <> RoundCurrency(HayCambio.OldValueByName[fld_FacturasClienteIMPORTE_TOTAL])) then
        if (Application.MessageBox('Ha cambiado el importe total, por lo que se regenerarán los recibos según forma de pago asignada, ¿desea continuar?', 'Atención', MB_YESNO) = IDYES) then
          regenerarRecibos := True
        else
          exit;
      end;
    end;

    try
      AFactura.DataTable.ApplyUpdates;

      //Se generan los recibos automáticamente a partir de la forma de pago
      if regenerarRecibos then
         GenerarRecibos(AFactura);

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

function TFacturasClienteController.Nuevo (WithInsert: Boolean = True): IBizFacturaCliente;
var
  AFactura : IBizFacturaCliente;
begin
  AFactura := FDataModule.NewItem;
  FiltrarEmpresa(AFactura);
  AFactura.DataTable.Active := True;
  if WithInsert then
     AFactura.Insert;
  Result := AFactura;
end;

procedure TFacturasClienteController.Preview(AFactura: IBizFacturaCliente; AllItems: Boolean = false; AVerDatosCliente: Boolean = true);
var
  AReportController : IFacturasClienteReportController;
  ID_Facturas: TIntegerList;

begin
  AReportController := TFacturasClienteReportController.Create;
  ID_Facturas := TIntegerList.Create;

  try
   //Si deseamos previsualizar todos los items del objeto albaran
   if AllItems then
   begin
     with AFactura.DataTable do
     begin
       First;
       while not EOF do
       begin
         ID_Facturas.Add(AFactura.ID);
         Next;
       end;
     end;
   end
   //Solo previsualizamos el item seleccionado
   else
     ID_Facturas.Add(AFactura.ID);

    AReportController.Preview(ID_Facturas, AVerDatosCliente);

  finally
    AReportController := NIL;
    FreeANDNIL(ID_Facturas);
  end;
end;

procedure TFacturasClienteController.Print(AFactura: IBizFacturaCliente; AllItems: Boolean = false; AVerDatosCliente: Boolean = true);
var
  AReportController : IFacturasClienteReportController;
  ID_Facturas: TIntegerList;

begin
  AReportController := TFacturasClienteReportController.Create;
  ID_Facturas := TIntegerList.Create;

  try
   //Si deseamos previsualizar todos los items del objeto albaran
   if AllItems then
   begin
     with AFactura.DataTable do
     begin
       First;
       while not EOF do
       begin
         ID_Facturas.Add(AFactura.ID);
         Next;
       end;
     end;
   end
   //Solo previsualizamos el item seleccionado
   else
     ID_Facturas.Add(AFactura.ID);

    AReportController.Print(ID_Facturas, AVerDatosCliente);

  finally
    AReportController := NIL;
    FreeANDNIL(ID_Facturas);
  end;
end;

function TFacturasClienteController.GenerarAbono(AFactura: IBizFacturaCliente): IBizFacturaCliente;
begin
  ShowHourglassCursor;
  try
    Result := Duplicar(AFactura);
    //Añade un concepto con los datos de la factura asociada al abono
    FDetallesController.AnadirDetalleFacturaAsociadaAbono(Result.Detalles, AFactura.REFERENCIA, DateToStr(AFactura.FECHA_FACTURA));
    //Convierte todos los articulos de la factura a negativos por se un abono
    FDetallesController.CambiarSignoDetalles(Result.Detalles);

    // Hay que dejar algunos campos como si fuera una factura nueva
    Result.Edit;
    Result.TIPO := CTE_TIPO_ABONO;
    Result.Post;
    
  finally
    HideHourglassCursor;
  end;
end;

procedure TFacturasClienteController.GenerarPDF(AFactura: IBizFacturaCliente; AllItems, AFirmado: Boolean);
var
  AReportController : IFacturasClienteReportController;
  AFile : String;
  bEliminado: Boolean;
  ACausa: Variant;

begin
  if not Assigned(AFactura) then
    raise Exception.Create ('Factura no asignado (GenerarPDF)');

  if AFactura.DataTable.Active then
    AFactura.DataTable.Active := True;

  //En el caso de querer eliminar todos los items del objeto AAlbaran
  if AllItems then
  begin
    with AFactura.DataTable do
    begin
      First;
      while not EOF do
      begin
        AFile := EscapeIllegalChars(AFactura.REFERENCIA + ' - ' + AFactura.NOMBRE_CLIENTE);
        if (not PreguntarFicheroPDFExportar(AFile)) then
          Next
        else
        begin
          ShowHourglassCursor;
          try
            AReportController := TFacturasClienteReportController.Create;
            AReportController.ExportToPDF(AFactura.ID, AFile, AFirmado);
          finally
            AReportController := NIL;
            HideHourglassCursor;
          end;
          Next;          
        end;
      end;
    end;
  end
  else
  begin
    AFile := EscapeIllegalChars(AFactura.REFERENCIA + ' - ' + AFactura.NOMBRE_CLIENTE);
    if (not PreguntarFicheroPDFExportar(AFile)) then
      Exit;

    ShowHourglassCursor;
    try
      AReportController := TFacturasClienteReportController.Create;
      AReportController.ExportToPDF(AFactura.ID, AFile, AFirmado);
    finally
      AReportController := NIL;
      HideHourglassCursor;
    end;
  end;
end;

procedure TFacturasClienteController.GenerarRecibos(AFactura: IBizFacturaCliente);
var
  AFormasPagoController : IFormasPagoController;
  AFormaPago: IBizFormaPago;
  ARecibosClienteController: IRecibosClienteController;
  ARecibos: IBizRecibosCliente;
  ARecibosACuenta: IBizRecibosCliente;
  AFechaVencimiento: TDateTime;
  ADiaVencimiento: Integer;
  ADiasMas: Integer;
  BSemaforo: Boolean;
  AImporteTotal: Currency;
  i: Integer;

begin
  if not Assigned(AFactura) then
     Exit;

  i := 1;     
  AImporteTotal := AFactura.IMPORTE_TOTAL;

  AFormasPagoController := TFormasPagoController.Create;
  AFormaPago := AFormasPagoController.Buscar(AFactura.ID_FORMA_PAGO);
  AFormaPago.DataTable.Active := True;
  if AFormaPago.DataTable.RecordCount <> 1 then
     Exit; //No hay forma de pago en la factura y por lo tanto no se generan recibos
     //raise Exception.Create('No existe la forma de pago de la factura');

  ARecibosClienteController := TRecibosClienteController.Create;
  //Eliminamos todos los recibos que tuviera la factura porque sabemos que todos
  //estarán pendientes (solo permitiremos modificar y eliminar facturas pendientes,
  //parcialmente pagadas o pagadas no
  ARecibos := ARecibosClienteController.BuscarRecibosFactura(AFactura.ID);
  ARecibosClienteController.EliminarTodo(ARecibos);


  //Miramos si existen recibos a cuenta para ese contrato, si es así los tendremos en cuenta para las facturas
  ARecibosACuenta := ARecibosClienteController.BuscarRecibosContrato(AFactura.ID_CONTRATO);
  ARecibosACuenta.DataTable.Active := True;
  if ARecibosACuenta.DataTable.RecordCount > 0 then
  begin
      if (ShowConfirmMessage('Generar factura a cliente', 'Hay un/os recibo/s a cuenta creados para el presupuesto asociado a la factura, desea incluirlo/s') = IDYES) then
      begin
        ARecibosACuenta.DataTable.First;
        while not ARecibosACuenta.DataTable.EOF do
        begin
          AImporteTotal := AImporteTotal - ARecibosAcuenta.IMPORTE_TOTAL;
          ARecibosACuenta.DataTable.Edit;
          ARecibosAcuenta.REFERENCIA := AFactura.REFERENCIA + ' - ' + IntToStr(i);
          ARecibosAcuenta.ID_FACTURA := AFactura.ID;
          ARecibosAcuenta.ID_EMPRESA := AFactura.ID_EMPRESA;
          ARecibosAcuenta.ID_CLIENTE := AFactura.ID_CLIENTE;
          ARecibosAcuenta.ID_TIENDA := AFactura.ID_TIENDA;
          ARecibosAcuenta.NOMBRE := AFactura.NOMBRE; //Es la razon social de la factura
          ARecibosAcuenta.CALLE := AFactura.CALLE;
          ARecibosAcuenta.NIF_CIF := AFactura.NIF_CIF;
          ARecibosAcuenta.POBLACION := AFactura.POBLACION;
          ARecibosAcuenta.PROVINCIA := AFactura.PROVINCIA;
          ARecibosAcuenta.CODIGO_POSTAL := AFactura.CODIGO_POSTAL;
          ARecibosACuenta.DataTable.Post;
          Inc(i);
          ARecibosACuenta.DataTable.Next;
        end;

        ARecibosACuenta.DataTable.ApplyUpdates;
      end;
  end;

  //Se cambia la lógica a peticion de acana, en el caso de meter una fecha de vencimiento,
  //los plazos de la forma de pago no tendrán efecto, se generará un recibo con el 100% y fecha de vencimiento
  //de la factura. (Se permitirá para acana poder dar de alta facturas sin recibos asociados)
  With AFormaPago.Plazos.DataTable do
  begin
    First;
    repeat
      //SOLO PARA ACANA (Leer comentario anterior
      if (AFormaPago.Plazos.RecordCount > 0)
      or (AFactura.FECHA_VENCIMIENTO <> 0) then
      begin
        ARecibos := ARecibosClienteController.Nuevo;
        ARecibos.Edit;
        ARecibos.ID_FACTURA := AFactura.ID;
        ARecibos.ID_EMPRESA := AFactura.ID_EMPRESA;
        ARecibos.ID_CLIENTE := AFactura.ID_CLIENTE;
        ARecibos.ID_TIENDA := AFactura.ID_TIENDA;
        ARecibos.REFERENCIA := AFactura.REFERENCIA + ' - ' + IntToStr(i);
        ARecibos.NOMBRE := AFactura.NOMBRE; //Es la razon social de la factura
        ARecibos.CALLE := AFactura.CALLE;
        ARecibos.NIF_CIF := AFactura.NIF_CIF;
        ARecibos.POBLACION := AFactura.POBLACION;
        ARecibos.PROVINCIA := AFactura.PROVINCIA;
        ARecibos.CODIGO_POSTAL := AFactura.CODIGO_POSTAL;
        ARecibos.FECHA_EMISION := AFactura.FECHA_FACTURA;

        if AFormaPago.Plazos.RecordCount < 1 then
        begin
          ARecibos.FECHA_VENCIMIENTO := AFactura.FECHA_VENCIMIENTO;
          ARecibos.IMPORTE := AImporteTotal;
        end
        else
        begin
          AFechaVencimiento := AFactura.FECHA_FACTURA + AFormaPago.Plazos.NUM_DIAS;
          ADiasMas := 0;
          BSemaforo := False;
          if (AFactura.Cliente.VENCIMIENTO_FACTURAS_1 <> 0)
          or (AFactura.Cliente.VENCIMIENTO_FACTURAS_2 <> 0)
          or (AFactura.Cliente.VENCIMIENTO_FACTURAS_3 <> 0) then
          begin
             ADiaVencimiento := DayOf(AFechaVencimiento);
             while (ADiaVencimiento <> AFactura.Cliente.VENCIMIENTO_FACTURAS_1)
             and (ADiaVencimiento <> AFactura.Cliente.VENCIMIENTO_FACTURAS_2)
             and (ADiaVencimiento <> AFactura.Cliente.VENCIMIENTO_FACTURAS_3) do
             begin
                if ADiaVencimiento = DaysInMonth(AFechaVencimiento) then
                begin
                  ADiaVencimiento := 1;

                  //Controlamos con una variable semaforo que no nos quedemos en un bucle infinito
                  //intentando encontrar el dia de pago establecido para el cliente (es el caso de tener
                  //asignado el dia de pago 30 y llegar febrero en el que no se encuentra dicho dia, o
                  //tener como dia de pago los días 31 y en el caso de meses de 30 dias no encontrarlo.
                  if BSemaforo then
                  begin
                    ADiasMas := 0;
                    Break
                  end
                  else
                    BSemaforo := True;
                end
                else
                   Inc(ADiaVencimiento);
                Inc(ADiasMas);
             end;
          end;

          AFechaVencimiento := IncDay(AFechaVencimiento, ADiasMas);
          ARecibos.FECHA_VENCIMIENTO := AFechaVencimiento;
          ARecibos.IMPORTE := AImporteTotal * (AFormaPago.Plazos.PORCENTAJE / 100);
        end;

        ARecibos.DESCRIPCION := 'RECIBO ' + ARecibos.REFERENCIA + ' - ' + CifraToLetras(ARecibos.IMPORTE);
        ARecibosClienteController.Guardar(ARecibos);
      end;

      Inc(i);
      Next;
    until (eof);
  end;

  //Liberamos
  AFormasPagoController := Nil;
  AFormaPago := Nil;
  ARecibosClienteController := Nil;
  ARecibos := Nil;
  ARecibosACuenta := Nil;
end;

function TFacturasClienteController.GetClienteController: IClientesController;
begin
  Result := FClienteController;
end;

function TFacturasClienteController.GetDetallesController: IDetallesFacturaClienteController;
begin
  Result := FDetallesController;
end;

end.
