unit uFacturasClienteController;

interface


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

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; overload;
    function BuscarTodos(const AID_Cliente: Integer): IBizFacturaCliente; overload;
    function BuscarTodasPendientesComision(IdAgente: Integer; IdComision: Integer; IdFacturasAsociadas: String): IBizFacturaCliente;
    procedure Ver(AFactura : IBizFacturaCliente);
    procedure VerTodos(AFacturas: IBizFacturaCliente;
      const AVerModal : Boolean = False; const AWindowCaption: String = '';
      const AHeaderText: String = '');
    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 Anadir(AFactura : IBizFacturaCliente; const IDPedido : Integer): Boolean; overload;

    function Eliminar(const ID : Integer): Boolean; overload;
    function Eliminar(AFactura : IBizFacturaCliente; AllItems: Boolean = false): 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;
    const VerSello: Boolean = True; const VerCopia: Boolean = True);
    function Print(AFactura : IBizFacturaCliente; AllItems: Boolean = false;
    const VerSello: Boolean = True; const VerCopia: Boolean = True): Boolean;

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

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

    function DarListaAnosFacturas: TStringList;
    procedure FiltrarAno(AFactura: IBizFacturaCliente; ADynWhereDataTable: WideString; const Ano: String);
  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): 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 Anadir(AFactura : IBizFacturaCliente; const IDPedido : Integer): Boolean; overload;
    function Buscar(const ID: Integer): IBizFacturaCliente;
    function BuscarTodos: IBizFacturaCliente; overload;
    function BuscarTodos(const AID_Cliente: Integer): IBizFacturaCliente; overload;
    function BuscarTodasPendientesComision(IdAgente: Integer; IdComision: Integer; IdFacturasAsociadas: String): IBizFacturaCliente;
    function Nuevo (withInsert: Boolean = True) : IBizFacturaCliente;
    procedure Ver(AFactura : IBizFacturaCliente);
    procedure VerTodos(AFacturas: IBizFacturaCliente;
      const AVerModal : Boolean = False; const AWindowCaption: String = '';
      const AHeaderText: String = '');
    function Duplicar(AFactura: IBizFacturaCliente): IBizFacturaCliente;
    function GenerarAbono(AFactura: IBizFacturaCliente): IBizFacturaCliente;
    procedure RecuperarCliente(AFactura : IBizFacturaCliente);

    procedure Preview(AFactura : IBizFacturaCliente; AllItems: Boolean = false;
    const VerSello: Boolean = True; const VerCopia: Boolean = True);
    function Print(AFactura : IBizFacturaCliente; AllItems: Boolean = false;
    const VerSello: Boolean = True; const VerCopia: Boolean = True): Boolean;

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

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

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

implementation

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


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

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

  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.Anadir(AFacturas: IBizFacturaCliente;
  AListaAlbaranes: IBizAlbaranCliente): Boolean;
var
  AFacturaActual : IBizFacturaCliente;
  AAlbaranesController : IAlbaranesClienteController;
  I: Integer;
  bEnEdicion : Boolean;
begin
  // 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;
begin
  ShowHourglassCursor;
  try
    Result := BuscarTodos;
    with Result.DataTable.DynamicWhere do
    begin
      //Todas las facturas de un agente determinado
      Condicion := NewBinaryExpression(NewField('', fld_FacturasClienteID_AGENTE), NewConstant(IdAgente, datInteger), dboEqual);

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

    with Result.DataTable.DynamicWhere do
    begin
      //Todas aquellas que no esten asociadas a ninguna comisión o asociadas a la comisión,
      //pero no asociadas en el editor de la comision, esto es porque se puede agregar y quitar
      //facturas y todos los cambios estan el cache y por lo tanto al pedir las facturas a seleccionar
      //debemos tener en cuenta dichso cambios en cache
      Condicion := NewBinaryExpression(NewField('', fld_FacturasClienteID_COMISION_LIQUIDADA), NewNull(), dboEqual);

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

{REPASARRRRRRR
    //Quitamos aquellas que ya están asociadas
    if length(IdFacturasAsociadas) > 0 then
       with Result.DataTable.DynamicWhere do
       begin
         Condicion := NewBinaryExpression(NewField('', fld_FacturasClienteID), ' not in (' + IdFacturasAsociadas + ')');, dboEqual);

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

    with Result.DataTable.Where do
    begin

        CLOSEBraket;
        AddOperator(opOR);

        OpenBraket;
          //En caso de ser facturas ya asociadas lo limitamos a la comision actual
          //ya que no debemos poder elegir facturas asociadas a otras comisiones
          OpenBraket;
          AddText(fld_FacturasClienteID_COMISION_LIQUIDADA + ' = ' + IntToStr(IdComision));
          CloseBraket;
          //En el caso de que halla facturas asociadas
          if length(IdFacturasAsociadas) > 0 then
          begin
            AddOperator(opAND);
            OpenBraket;
            AddText(fld_FacturasClienteID + ' not in (' + IdFacturasAsociadas + ')');
            CloseBraket;
          end;
        CloseBraket;

      CloseBraket;
    end;
}    
  finally
    HideHourglassCursor;
  end;
end;

function TFacturasClienteController.BuscarTodos(
  const AID_Cliente: Integer): IBizFacturaCliente;
var
  Condicion: TDAWhereExpression;
begin
  ShowHourglassCursor;
  try
    Result := BuscarTodos;

    with Result.DataTable.DynamicWhere do
    begin
      // ID_CLIENTE
      Condicion := NewBinaryExpression(NewField('', fld_FacturasClienteID_CLIENTE), NewConstant(AID_Cliente, datInteger), dboEqual);

      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;

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;
      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;
    end;

    Result.Post;
  finally
    HideHourglassCursor;
  end;
end;

function TFacturasClienteController.ValidarFactura(AFactura: IBizFacturaCliente): Boolean;
var
  AFormaPago: IBizFormaPago;
  
begin
  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');

  //En el caso de asignar una retención a la factura será obligatorio meter la fecha de retención.
  if (AFactura.IMPORTE_RETENCION > 0) then
     if (EsFechaVacia(AFactura.FECHA_RETENCION)) then
        raise Exception.Create('Debe indicar la fecha de vencimiento de la retención, para poder asociarla al recibo que contendrá el importe de la retención.');

  if (AFactura.Detalles.DataTable.RecordCount = 0) then
    raise Exception.Create('La factura debe tener al menos un concepto en su contenido');
    
  { Esta validación puede saltar cuando se generan facturas automáticamente
    por albaranes o pedidos y el cliente no tiene Tipo de IVA puesto. }
{  if (AFactura.ID_TIPO_IVA = 0) then
    raise Exception.Create('Debe indicar un tipo de IVA 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
  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;

  //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');

  { 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;
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) then
        begin
           SetDataTableReadOnly(AFactura.DataTable, True);
           AEditor.ReadOnly := True;
        end;

        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;
  const AVerModal : Boolean = False; const AWindowCaption: String = '';
  const AHeaderText: String = '');
var
  AEditor : IEditorFacturasCliente;
begin
  AEditor := NIL;

  CreateEditor('EditorFacturasCliente', IEditorFacturasCliente, AEditor);
  if Assigned(AEditor) then
    try
      if not EsCadenaVacia(AWindowCaption) then
        AEditor.WindowCaption := AWindowCaption;

      if not EsCadenaVacia(AHeaderText) then
        AEditor.HeaderText := AHeaderText;

      AEditor.Controller := Self; //OJO ORDEN MUY IMPORTANTE
      AEditor.Facturas := AFacturas;
      AEditor.MultiSelect := True;
      if AVerModal then
        AEditor.ShowModal
      else
        AEditor.ShowEmbedded;
    finally
      if AVerModal then
        AEditor.Release;
      AEditor := Nil;
    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): Boolean;
//En el caso de eliminar almenos un elemento del conjunto se devuelve true
var
  bEliminado: Boolean;

begin
  bEliminado := False;

  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
    if EsEliminable(AFactura) then
    begin
      AFactura.Delete;
      bEliminado := True;
    end;

    //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 EsEliminable(AFactura) then
           begin
              AFactura.Delete;
              bEliminado := True
           end
           else Next;
         end;
       end;
    end;

    if bEliminado then
    begin
      try
      AFactura.DataTable.ApplyUpdates;
      Result := True
      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;
    end
    else
       Result := False;

  finally
    HideHourglassCursor;
  end;
end;

procedure TFacturasClienteController.EnviarFacturaPorEMail(
  AFactura: IBizFacturaCliente);
var
  AReportController : IFacturasClienteReportController;
  AFicheroTMP : TFileName;
  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;

  AFicheroTMP := DarFicheroPDFTemporal(EscapeIllegalChars(AFactura.REFERENCIA));

  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;
  AReportController := TFacturasClienteReportController.Create;
  try
    AReportController.ExportToPDF(AFactura.ID, AFicheroTMP);
    EnviarEMailMAPI('Factura ' + AFactura.REFERENCIA, '', AFicheroTMP, '', '', AFactura.Cliente.NOMBRE, AEMail);
  finally
    SysUtils.DeleteFile(AFicheroTMP);
    AReportController := NIL;
    HideHourglassCursor;
  end;
end;

function TFacturasClienteController.EsEliminable(AFactura: IBizFacturaCliente): Boolean;
begin
  Result := EsModificable(AFactura);
end;

function TFacturasClienteController.EsModificable(AFactura: IBizFacturaCliente): Boolean;
begin
  if not Assigned(AFactura) then
     raise Exception.Create ('Factura no asignado: EsModificable');

  Result := (AFactura.SITUACION = CTE_PENDIENTE);
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;

function TFacturasClienteController.Guardar(AFactura: IBizFacturaCliente): Boolean;
begin
  Result := False;

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

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

  if ValidarFactura(AFactura) then
  begin
    ShowHourglassCursor;

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

    try
      AFactura.DataTable.ApplyUpdates;

      //Se generan los recibos automáticamente a partir de la forma de pago
      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;
const VerSello: Boolean = True; const VerCopia: 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, VerSello, VerCopia);

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

function TFacturasClienteController.Print(AFactura: IBizFacturaCliente; AllItems: Boolean = false;
const VerSello: Boolean = True; const VerCopia: Boolean = True): Boolean;
var
  AReportController : IFacturasClienteReportController;
  ID_Facturas: TIntegerList;

begin
  Result := False;
  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);

    Result := AReportController.Print(ID_Facturas, VerSello, VerCopia);

  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.GenerarRecibos(AFactura: IBizFacturaCliente);
var
  AFormasPagoController : IFormasPagoController;
  AFormaPago: IBizFormaPago;
  ARecibosClienteController: IRecibosClienteController;
  ARecibos: IBizRecibosCliente;
  AFechaVencimiento: TDateTime;
  i: Integer;
  ADiaVencimiento: Integer;
  ADiasMas: Integer;
  BSemaforo: Boolean;
  AImporteTotal: Double;

begin
  if not Assigned(AFactura) then
     Exit;

  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);

  //Si hay retención, se generará un recibo por ese importe, y el resto se generarán recibos
  //según forma de pago
  AImporteTotal := AFactura.IMPORTE_TOTAL - AFactura.IMPORTE_RETENCION;

  //Vamos a generar todos los recibos necesarios para la factura
  With AFormaPago.Plazos.DataTable do
  begin
    i := 1;
    First;
    repeat
      ARecibos := ARecibosClienteController.Nuevo;
      ARecibos.Edit;
      ARecibos.ID_FACTURA := AFactura.ID;
      ARecibos.REFERENCIA := AFactura.REFERENCIA + ' - ' + IntToStr(i);
      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 := 'Pago de factura ' + AFactura.REFERENCIA + ': son ' + CifraToLetras(ARecibos.IMPORTE);
      ARecibosClienteController.Guardar(ARecibos);
      Inc(i);
      Next;
    until (eof);

    //Si hay retención generaremos un recibo para dicha retención
    if AFactura.IMPORTE_RETENCION > 0 then
    begin
      ARecibos := ARecibosClienteController.Nuevo;
      ARecibos.Edit;
      ARecibos.ID_FACTURA := AFactura.ID;
      ARecibos.REFERENCIA := AFactura.REFERENCIA + ' - ' + IntToStr(i);
      ARecibos.FECHA_EMISION := AFactura.FECHA_FACTURA;
      ARecibos.FECHA_VENCIMIENTO := AFactura.FECHA_RETENCION;
      ARecibos.IMPORTE := AFactura.IMPORTE_RETENCION;
      ARecibos.DESCRIPCION := 'Pago de factura de retención ' + AFactura.REFERENCIA + ': son ' + CifraToLetras(ARecibos.IMPORTE);
      ARecibosClienteController.Guardar(ARecibos);
    end;
  end;

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

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

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

end.
