// Programma di gestione provvigioni
#include <applicat.h>
#include <msksheet.h>

#include "../ve/velib.h"
#include "prlib.h"
#include "provv.h"
#include "pr0700a.h"
#include "pr0700b.h"
#include "pr0.h"

class TGestione_provv_app : public TSkeleton_application
{
  TMask                 *_msk;
  TProvvigioni_agente   *_prag;
  TAssoc_array           _vittime;
  bool                   _dirty;
  
protected:
  virtual bool create();
  virtual bool destroy();
  virtual void main_loop();

  void    kill_vittime();
  void    load_provvigioni(TMask* m);
  void    fill_sheet_doc();
  bool    check_totals();
  static  bool doc_sheet_notify(TSheet_field& ds, int r, KEY key);
  static  bool rate_sheet_notify(TSheet_field& rs, int r, KEY key);
  static  bool nrata_handler(TMask_field& f, KEY key);
  static  bool ndoc_handler(TMask_field& f, KEY key);
  static  bool calcola_rate_handler(TMask_field& f, KEY k);
  static  bool datascad_handler(TMask_field& f, KEY key);
  static  bool imprata_handler(TMask_field& f, KEY key);
  static  bool improvv_handler(TMask_field& f, KEY key);
  static  bool enable_handler(TMask_field& f, KEY k);
  static  void fill_rate_doc(TRate_doc& rd, TSheet_field& sf);
public:
};

static inline TGestione_provv_app& app() { return (TGestione_provv_app&) main_app(); }
static TString16 __current_key;

////////////////////////////////////////////////////////////////////////////////////////
// Da fare :
// - Quando si elimina l'ultima riga dello spreadsheet si posiziona sulla prima senza 
//   mandare un K_TAB per aggiornare le rate
// - Browse dei documenti in ricerca, quando si seleziona setta giustamente focusdirty() e manda
//   un K_TAB. Quando si tenta di uscire dal campo si ha ancora il campo focusdirty()
// - Cercare di implementare scarico/saldo righe provvigionali per agente in base ad una 
//   impostata
// - Ultimo ma non meno importante (da ritenersi visualmente valido) lo riempimento fisso
//   degli sheet (documenti e rate) in modo da evitare il fastidiosissimo sfarfallio in
//   inserimento righe con elementi vuoti
////////////////////////////////////////////////////////////////////////////////////////

bool TGestione_provv_app::calcola_rate_handler(TMask_field& f, KEY key)
{
  // - Impostazione importi e provvigioni (documento) in caso di selezione documento
  // - Calcolo righe di provvigioni in caso di selezione documento (integrato in TDocumento)
  // - Rata 0 : importo rata = 0; importo provvigione = provvigione all'atto della fattura (percentuale sugli agenti)
  //   la provvigione rimanente va suddivisa in rate a seconda del codice pagamento
  if (f.focusdirty() && key == K_TAB)
  {
    TMask& m = f.mask();
    TSheet_field & rs = (TSheet_field&)app()._msk->field(F_RATE_SHEET);// Resetta lo spreadsheet
    if (rs.items() > 0) // Resetta lo spreadsheet se vi sono delle righe
      rs.reset();
    TDocumento doc;
    const int anno = m.get_int(F_ANNO);
    const long numdoc = m.get_long(F_NDOC);
    const TString& codnum = m.get(F_CODNUM);
    const int err = doc.read('D', anno, (const char*) codnum, numdoc);

		if (err == NOERR)
    {
      m.set(F_DATADOC, doc.data());
      m.set(F_CLIENTE, doc.codcf());
      m.set(F_CODVAL,doc.valuta());
      m.set(F_CAMBIO,doc.cambio());
      m.set(F_DATACAMBIO,doc.get_date(DOC_DATACAMBIO));
      m.set(F_IMPDOC,doc.totale_doc());
      m.set(F_IMPPRDOC,doc.provvigione());
      m.set(F_IMPNETDOC, doc.totale_netto());
      // Adesso calcoliamo la provvigione all'atto della fatturazione da inserire
      // nella rata 0. Il residuo va ripartito nelle rate del documento
      TProvvigioni_agente& pra  = doc.calc_provvigioni(FALSE); // Non settare il flag di generata dal documento!
      
      // Adesso genera le righe da mettere nello spreadsheet. Quando si uscira' dalla riga
      // attuale verra' chiamata la doc_sheet_notify() con K_ENTER, che memorizza  il tutto
      // gestendo automaticamente la eventuale modifica di un documento gia' presente o meno.
        
      TRate_doc& rd = pra.rate(doc.anno(), m.get(F_CODNUM), doc.numero()); // Estrae le rate appena calcolate
      const int numrate = rd.items(); /* Numero rate per questo documento */
      for (int i = 0; i < numrate; i++)
      {
        TToken_string& ttt = rs.row(i);
        TRata& rt = rd[i];
        ttt.add(rt.rata());              // Numero di rata
        ttt.add(rt.datascad());          // Data scadenza della rata
        ttt.add(rt.tipopag());           // Tipo pag per questa rata
        ttt.add(rt.imprata().string());  // Importo della rata (occhio alla valuta)
        ttt.add(rt.impprovv().string()); // Importo della provvigione per questa rata
        ttt.add("");                     // Tutti gli altri valori sono a 0
        ttt.add("");  ttt.add("");   ttt.add(""); 
        ttt.add("");  ttt.add("");
        ttt.add(rt.codval());
      }
      rs.force_update();
      f.set_focusdirty(FALSE);
      f.set_dirty(FALSE);
    }
    else
      return f.error_box(TR("Errore %d tentando di leggere il documento specificato"),err);
  }
  return TRUE;
}

bool TGestione_provv_app::ndoc_handler(TMask_field& f, KEY key)
{
  if (key == K_ENTER) // Quando sta cambiando riga...
  { // Scorre tutte le righe dello spreadsheet esaminando ANNO+CODNUM+NDOC
    // nel caso trovi un'altra riga con la stessa chiave, visualizza un errore
    TSheet_field& s = *f.mask().get_sheet();
    const int selected = s.selected();
    const int items = s.items(); // Numero di righe dello spreadsheet
    TToken_string&  tt = s.row(selected);
    int     anno   = tt.get_int(0);
    TString codnum = tt.get(1);
    long    ndoc   = tt.get_long(2);
    TString key,other_key;

    key.format("%4d%4s%7ld",anno,(const char*)codnum,ndoc);
    for (int i = 0; i < items; i++) 
      if (i != selected)
      {
        TToken_string& ot = s.row(i);
        anno   = ot.get_int(0);
        codnum = ot.get(1);
        ndoc   = ot.get_long(2);
        other_key.format("%4d%4s%7ld",anno,(const char*)codnum,ndoc);
        if (key == other_key)
          return f.error_box(TR("Non e' possibile inserire due documenti con la stessa chiave"));
      }
  }
  return calcola_rate_handler(f, key);
}

bool TGestione_provv_app::nrata_handler(TMask_field& f, KEY key)
{
  if (key == K_ENTER)
  { // Scorre tutte le righe dello spreadsheet esaminando il numero di rata
    // nel caso trovi un'altra riga con lo stesso numero, visualizza un errore
    const int nrata = atoi(f.get());
    TSheet_field& s = *f.mask().get_sheet();
    const int selected = s.selected();
    const int items = s.items(); // Numero di righe dello spreadsheet
    for (int i = 0; i < items; i++) 
      if (i != selected)
      {
        int other_rata = s.row(i).get_int(0);
        if (nrata == other_rata)
          return f.error_box(TR("Non e' possibile inserire due rate con lo stesso numero"));
      }
  }
  return TRUE;
}

bool TGestione_provv_app::datascad_handler(TMask_field& f, KEY key)
{
  if (key == K_ENTER && __current_key.not_empty())
  {
    TProvvigioni_agente* pa = app()._prag;
    TRate_doc& rd = pa->rate(__current_key);
    TDate d(f.get());
    if (d < rd.datadoc())
      return f.error_box(TR("La data di scadenza deve essere maggiore o uguale della data documento"));
  }
  return TRUE;
}

// Abilita le colonne PAGATO e PROVVPAG per evntuale editing degli importi
bool TGestione_provv_app::enable_handler(TMask_field& f, KEY key)
{
  if (key == K_SPACE)
    f.mask().enable(-5);
  return TRUE;
}

bool TGestione_provv_app::imprata_handler(TMask_field& f, KEY key)
{
  if (key == K_ENTER && __current_key.not_empty())
  {
    TProvvigioni_agente* pa = app()._prag;
    TRate_doc& rd = pa->rate(__current_key);
    real r(f.get());
    r = abs(r);
    if (r > abs(rd.impdoc()))
      return f.error_box(TR("L'importo della rata non puo' essere maggiore dell'importo del documento"));
  }
  return TRUE;
}

bool TGestione_provv_app::improvv_handler(TMask_field& f, KEY key)
{
  if (key == K_ENTER && __current_key.not_empty())
  {
    TProvvigioni_agente* pa = app()._prag;
    TRate_doc& rd = pa->rate(__current_key);
    real r(f.get());
    r = abs(r);
    if (r > abs(rd.impprdoc()))
      return f.error_box(TR("L'importo della provvigione non puo' essere maggiore dell'importo provvigione del documento"));
  }
  return TRUE;
}

void TGestione_provv_app::fill_rate_doc(TRate_doc& rd, TSheet_field& sf)
{
  // Righe delle rate
  const int items = sf.items();
  rd.remove_rata(); // cancella tutte le rate
  for (int i = 0; i < items; i++) // Aggiunge le rate presenti nello sheet
  {
    TToken_string& tt = sf.row(i);
    TRata* rt = new TRata;
    rt->set(tt);                          
    rd.add_rata(rt);
  }
}

bool TGestione_provv_app::rate_sheet_notify(TSheet_field& ds, int r, KEY key)
{
  TProvvigioni_agente* pa = app()._prag;
  if (__current_key.empty() || pa->items() == 0)
    return TRUE;

  TRate_doc& rd = pa->rate(__current_key);

  switch (key)
  { 
    case K_INS:    // Inserimento di una nuova rata vuota
    {
      TRata* rt = new TRata;
      // Nuova rata: va segnalata come inserita (generata = blank)
      rd.add_rata(rt);
      app()._dirty  = TRUE;
    }
    break; 
    case K_ENTER:  // Notifica dell'avvenuta modifica di una rata
    {
      TRata& rt = rd[r]; // Sostituisce i valori della riga corrente
      rt.set(ds.row(r));
      app()._dirty  = TRUE;
    }  
    break;
    case K_DEL:    // Notifica della cancellazione di una riga
      rd.remove_rata(r); 
      rd.pack_rate();    // Effettua anche il pack degli elementi (corrispondenza 1 a 1 tra sheet ed array)
      app()._dirty  = TRUE;
    break;
    default:
    break;
  }
  return TRUE;
}

bool TGestione_provv_app::doc_sheet_notify(TSheet_field& ds, int r, KEY key)
{
  TProvvigioni_agente* pa = app()._prag;
  
  switch (key)
  { 
    case K_INS:   // Inserimento di un nuovo documento
      {
        TSheet_field& rs = ds.mask().sfield(F_RATE_SHEET);
        if (rs.items() > 0) // Resetta lo spreadsheet se vi sono delle righe
          rs.reset();
        app()._dirty  = TRUE;
      }
      break;
    case K_TAB:        // Posizionamento sulla riga r
      // Visualizza le rate relative sull'apposito sheet
      {
        TMask& m = ds.mask();
        if (!m.is_running())
          break;

        TToken_string&  tt = ds.row(r);
        int     anno   = tt.get_int(0);
        TString4 codnum = tt.get(1);
        long    ndoc   = tt.get_long(2);
        TString4 codval = tt.get(8);

        m.set(F_MANNO,anno);
        m.set(F_MCODNUM,codnum);
        m.set(F_MNDOC,ndoc);
        m.set(F_MCODVAL, codval);

        __current_key.format("%4d%4s%7ld",anno,(const char*)codnum,ndoc);
        TSheet_field&  rs = ds.mask().sfield(F_RATE_SHEET);
        if (rs.items() > 0) // Resetta lo spreadsheet se vi sono delle righe precedenti
          rs.reset();
        TRate_doc& rd = pa->rate(__current_key,TRUE);
        const int items = rd.items();
        for (int i = 0; i < items; i++)
        {
          TRata& rt = rd[i];
          TToken_string& ttt = rs.row(i);
          ttt.add(rt.rata());
          ttt.add(rt.datascad()); 
          ttt.add(rt.tipopag()); 
          ttt.add(rt.imprata().string());
          ttt.add(rt.impprovv().string()); 
          ttt.add(rt.pagmat().string());
          ttt.add(rt.provvmat().string()); 
          ttt.add(rt.pagato().string());
          ttt.add(rt.provvpag().string()); 
          ttt.add(rt.saldata() ? "X" : " ");
          ttt.add(rt.tipopagpr()); 
          ttt.add(rt.codval()); 
        }
        rs.force_update();
      }
      break;
    case K_ENTER: // Abbandono della riga r (modificata). Analogamente va fatto sullo sheet delle rate
      // Memorizza rate e documento
      // Confronta la chiave della riga attuale con quella ricavata quando ci si e' 
      // posizionati sopra.
      // Se __current_key non e' vuota viene rimosso il vecchio TRate_doc
      // e aggiunto quello con la chiave nuova
      {
        TToken_string&  tt = ds.row(r);
        int     anno   = tt.get_int(0);
        TString codnum = tt.get(1);
        long    ndoc   = tt.get_long(2);
        
        TString k;
        k.format("%4d%4s%7ld",anno,(const char*)codnum,ndoc);
        bool flag = FALSE;
        
        if (__current_key != k) // Se le chiavi sono diverse
        {
          TAssoc_array& vitt = app()._vittime;
          if (__current_key.not_empty())
          {
            pa->remove_rate(__current_key);    // Cancella il vecchio
            vitt.add(__current_key);
          }  
          __current_key = k;
          if (vitt.is_key(k))
            vitt.remove(k);
          flag = TRUE;
        }
        TRate_doc& rd = pa->rate(k,flag);  // Aggiunge il nuovo o prende lo stesso elemento
        // Schiaffa dentro rate e dati documento
        rd.set(tt);
        fill_rate_doc(rd, ds.mask().sfield(F_RATE_SHEET));
        app()._dirty  = TRUE;
      }
      break;
    case K_DEL:  // Rimozione della riga r
      // Rimuove il documento e le rate
      pa->remove_rate(__current_key);
      app()._vittime.add(__current_key);
      __current_key.cut(0);
      if (pa->items() == 0)
      {
        TSheet_field&  rs = (TSheet_field&) ds.mask().field(F_RATE_SHEET);
        if (rs.items() > 0) // Resetta lo spreadsheet se vi sono delle righe
          rs.reset();
      }
      app()._dirty  = TRUE;
      break;
    default:
      break;
  }
  return TRUE;
}

bool TGestione_provv_app::create()
{
  open_files(LF_PROVV, LF_CONDV, LF_RCONDV, LF_ANAMAG, LF_SCONTI, LF_UMART, 
						 LF_TABCOM, LF_CLIFO,LF_INDSP, LF_MOVMAG, LF_RMOVMAG, LF_DOC,
						 LF_RIGHEDOC, LF_CFVEN, LF_OCCAS, LF_TAB, 0);

  _msk   = new TMask("pr0700b") ;
  _prag  = new TProvvigioni_agente;
  TSheet_field & sf = (TSheet_field&)_msk->field(F_DOC_SHEET);
  sf.set_notify(doc_sheet_notify);
  sf.sheet_mask().set_handler(F_NDOC,ndoc_handler);
  TSheet_field & rs = (TSheet_field&)_msk->field(F_RATE_SHEET);
  rs.set_notify(rate_sheet_notify);
  TMask& sm = rs.sheet_mask();
  sm.set_handler(F_RATA,nrata_handler);
  sm.set_handler(F_DATASCAD,datascad_handler);
  sm.set_handler(F_IMPRATA,imprata_handler);
  sm.set_handler(F_IMPPROVV,improvv_handler);
  sm.set_handler(DLG_ENABLE,enable_handler);

  return TSkeleton_application::create();
}

bool TGestione_provv_app::destroy()
{
  delete _msk;
  delete _prag;
  return TRUE;
}

void TGestione_provv_app::fill_sheet_doc()
{
  TString_array kl;
  const int items = _prag->documenti(kl);
  kl.sort(); // Cosi' i documenti sono in ordine 
  TSheet_field & sf = (TSheet_field&)_msk->field(F_DOC_SHEET);
  TSheet_field & rs = (TSheet_field&)_msk->field(F_RATE_SHEET);
  
  __current_key = "";
  rs.reset();
  sf.reset();
  for (int i = 0; i < items; i++)
  {
    TRate_doc& rd      = _prag->rate(kl.row(i));
    TToken_string&  tt = sf.row(i);
    tt.add(rd.anno()); tt.add(rd.codnum());
    tt.add(rd.ndoc()); tt.add(rd.datadoc());
    tt.add(rd.impdoc().string());
    tt.add(rd.impprdoc().string());
    tt.add(rd.impnet().string());
    tt.add(rd.codcf());
    tt.add(rd.codval());
    tt.add(rd.cambio().string());
    tt.add(rd.datacam());
  }
}

bool TGestione_provv_app::check_totals()
// Controlla che la somma delle provvigioni per ogni rata non sia superiore alla provvigione
// totale del documento. Ritorna TRUE se tutti i documenti hanno le provvigioni ok
{
  TString_array kl;
  const int items = _prag->documenti(kl);
  bool rt = TRUE;

  for (int i = 0; rt && i < items ; i++)
    if (!_prag->rate(kl.row(i)).ok_provvigione())
    {
      rt = FALSE;
      error_box(FR("La somma delle provvigioni supera l'importo della provvigione totale per il documento %s"),
                 (const char*)kl.row(i));
    }
  return rt;
}

void TGestione_provv_app::kill_vittime()
{ 
  const TString16 agente = _msk->get(F_CODAGE);                     
  TProvvigioni_agente prov;
  FOR_EACH_ASSOC_OBJECT(_vittime, ass, key, doc)
  {                        
    const TFixed_string codice(key);
    const int anno = atoi(codice.left(4));
    const long ndoc = atol(codice.right(7));
    TString16 codnum = codice.mid(4, 4); codnum.trim();
    if (prov.read(agente, anno, codnum, ndoc) == NOERR)
      prov.remove();
  }  
}

void TGestione_provv_app::load_provvigioni(TMask* m)
{
  const TString16 agente = m->get(F_CODAGE);
  const int anno = m->get_int(F_FILTERANNO);
  
  _msk->set(F_CODAGE,agente);
  _msk->set(F_RAGSOC,m->get(F_RAGSOC));
  _msk->set(F_MANNO,"");
  _msk->set(F_MCODNUM,"");
  _msk->set(F_MNDOC,"");
  if (_prag->read(agente, anno) == _islocked)
  {
    message_box(FR("Dati agente %s in uso da un altro utente."), (const char*) agente);
    return;
  }
  
  KEY k;
  bool repeat;
  const char* msg = _prag->items() == 0 ? TR("Registrare i dati inseriti") : TR("Registrare le modifiche") ;
  fill_sheet_doc(); // Inizializza lo sheet dei documenti
  
  _dirty = FALSE;
  int err = NOERR;
  do
  {
    k = _msk->run();
    bool to_write = k == K_ENTER;
    repeat = FALSE;
    if (k == K_ESC && _dirty)
    {
      k = yesnocancel_box(msg);
      if (k == K_ESC)
        repeat = TRUE;
      else if (k == K_YES)
        to_write = TRUE;
    }
    if (to_write)
    {
      if (check_totals()) // Controlla i totali delle provvigioni per tutti i documenti
      {
        kill_vittime();   // Cancella eventuali documenti eliminati dallo sheet
        err = _prag->write();
      }  
      else
        repeat = TRUE;
    }    
  } while (repeat);
  if (err != NOERR)
    message_box(FR("Errore %d tentando di scrivere i dati provvigionali dell'agente %s."),err, (const char*) agente);
  _prag->unlock();
}

void TGestione_provv_app::main_loop()
{
  bool ok = TRUE;
  TMask* m = new TMask("pr0700a");
  while (ok)
  {      
    xvtil_statbar_set(TR("Ricerca"), TRUE);
    m->reset();
    ok = m->run() == K_ENTER;
    if (ok)
      load_provvigioni(m);
  }  
  delete m;
}

int pr0700(int argc, char** argv)
{
  TGestione_provv_app  a;
  a.run(argc,argv,TR("Gestione provvigioni"));
  return 0;
}