#include <config.h>
#include <mask.h>
#include <tabutil.h>

#include "saldacon.h"

#include <mov.h>
#include <scadenze.h>
#include <pagsca.h>

///////////////////////////////////////////////////////////
// TTree_rectype
///////////////////////////////////////////////////////////

TTree_rectype::TTree_rectype(const TRectype& testa, const TRectype& riga, const char* num)
: TRectype(testa), _recarr(riga, num)
{
}

TTree_rectype::TTree_rectype(int testa, int riga, const char* num)
: TRectype(testa), _recarr(riga, num)
{
}

TTree_rectype::TTree_rectype(const TTree_rectype& t)
: TRectype(t), _recarr(t._recarr)
{
}

TObject* TTree_rectype::dup() const
{
  TTree_rectype* r = new TTree_rectype(*this);
  return r;
}

void TTree_rectype::renum_key(const char* field, const char* val)
{                   
  TRectype::renum_key(field, val);
  _recarr.renum_key(field, val);
}

void TTree_rectype::copy_key_to_row(TRectype& row) const
{   
  RecDes* recd = rec_des();  // Descrizione del record della testata
  row.zero();                              
  const KeyDes& kd = recd->Ky[0];   // Memento! Gli indici delle chiavi partono da zero!  
  for (int i = kd.NkFields-1; i >= 0; i--)
  {                        
    const int nf = kd.FieldSeq[i] % MaxFields;
    const RecFieldDes& rf = recd->Fd[nf];  
    const char* name = rf.Name;
    const TString& val = get(name);
    row.renum_key(name, val);
  }
}  


TRectype& TTree_rectype::new_row(int r) 
{ 
  if (r <= 0) 
    r = last()+1;                     
  CHECKD(!exist(r), "Ue' pirla!, esiste gia' la riga ", r);
  TRectype& row = rows_array().row(r, TRUE); 
  return row;
}


int TTree_rectype::fill_array()
{
  TRectype* row = (TRectype*)_recarr.key().dup();
  copy_key_to_row(*row);
  const int err = _recarr.read(row);
  return err;
}


int TTree_rectype::read(TBaseisamfile& f, word op)
{
  int err = TRectype::read(f, op);
  if (err == NOERR)
    fill_array();
  else 
    _recarr.destroy_rows();
  return err; 
}

int TTree_rectype::next(TBaseisamfile& f)
{
  int err = TRectype::next(f);
  if (err == NOERR)
    fill_array();
  else 
    _recarr.destroy_rows();
  return err; 
}

int TTree_rectype::write(TBaseisamfile& f) const
{
  int err = TRectype::write(f);
  if (err == NOERR)
    err = _recarr.write();
  return err; 
}

int TTree_rectype::rewrite(TBaseisamfile& f) const
{
  int err = TRectype::rewrite(f);
  if (err == NOERR)
    err = _recarr.rewrite();
  return err; 
}

int TTree_rectype::remove(TBaseisamfile& f) const
{
  int err = TRectype::remove(f);
  if (err == NOERR)
    err = _recarr.remove();
  return err; 
}

///////////////////////////////////////////////////////////
// Valuta
///////////////////////////////////////////////////////////

TValuta::TValuta(const char* cod, const TDate& dat, const real& cam)
: _cod(cod), _dat(dat), _cam(cam)
{
  adjust();
}

TValuta::TValuta() : _cod(""), _dat(TODAY), _cam(1.0)
{}

void TValuta::adjust()
{ 
  _cod.upper();
  if (_cod.empty() || _cod == "LIT" || _cam.is_zero()) 
    _cam = 1.0; 
}

int TValuta::compare(const TSortable& s) const
{
  const TValuta& v = (const TValuta&)s;
  real c(_cam); c -= v._cam;
  return c.sign();
}

void TValuta::get(const TRectype& rec)
{
  _cod = rec.get("CODVAL");
  _dat = rec.get("DATACAM");
  _cam = rec.get_real("CAMBIO");
  adjust();
}

void TValuta::put(TRectype& rec) const
{
  if (in_lire())            
  {  
    rec.zero("CODVAL");
    rec.zero("DATACAM");
    rec.zero("CAMBIO");
  }
  else
  {
    rec.put("CODVAL", _cod);
    rec.put("DATACAM", _dat);
    rec.put("CAMBIO", _cam);
  }  
}

void TValuta::set(TMask& m, short v, short d, short c) const
{
  if (in_lire())            
  {
    m.reset(v);
    m.reset(d);
    m.reset(c);
  }  
  else
  {
    m.set(v, _cod);
    m.set(d, _dat.string());
    m.set(c, _cam.string());
  }  
}

void TValuta::get(const TMask& m, short v, short d, short c)
{
  _cod = m.get(v);
  _dat = m.get(d);
  _cam = real(m.get(c));
  adjust();
}

real TValuta::lit2val(const real& lit) const
{
  real val(lit);
  val /= _cam;
  val.round(2);
  return val;
}

real TValuta::val2lit(const real& val) const
{   
  real lit(val);
  lit *= _cam;
  lit.round();
  return lit;
}

void TValuta::val2lit(real& val) const
{   
  val *= _cam;
  val.round();
}

void TValuta::val2lit(TImporto& imp) const
{           
  val2lit(imp.valore());
}

///////////////////////////////////////////////////////////
// TDecoder
///////////////////////////////////////////////////////////

TDecoder::TDecoder(int num, const char* inf, const char* outf)
        : _file(new TLocalisamfile(num)), _if(inf), _of(outf)
{ }        
 
TDecoder::TDecoder(const char* tab)
        : _file(new TTable(tab)), _if("CODTAB"), _of("S0")
{ }        

TDecoder::~TDecoder()
{
  delete _file;
}

const TString& TDecoder::decode(const char* code)
{
  TObject* obj = objptr(code);
  if (obj == NULL)
  {
    _file->setkey(1);
    _file->put(_if, code);
    const int err = _file->read();
    CHECKS(err == NOERR, "Can't decode ", code);
    obj = new TString(_file->get(_of));
    add(code, obj);
  }
  const TString& s = (const TString&)*obj;
  return s;
}

///////////////////////////////////////////////////////////
// TRiga_scadenze
///////////////////////////////////////////////////////////

TRiga_scadenze::TRiga_scadenze(TRiga_partite* r)
: TTree_rectype(LF_SCADENZE, LF_PAGSCA, "NRIGP"), _riga(r)
{
  CHECK(_riga, "Riga nulla");
  r->copy_key_to_row(*this);
}  

TRiga_scadenze::TRiga_scadenze(const TRiga_scadenze& s)
: TTree_rectype(s), _riga(s._riga)
{} 

TObject* TRiga_scadenze::dup() const
{
  TRiga_scadenze* r = new TRiga_scadenze(*this);
  return r;
}

TPartita& TRiga_scadenze::partita() const
{
  return riga().partita();
}

// Controlla se la fattura della rata e' in valuta
bool TRiga_scadenze::in_valuta() const
{
  return riga().in_valuta();
}

// Controlla se la rata e' stata completamente pagata
bool TRiga_scadenze::chiusa(bool update) const
{                                      
  bool chiusa;
  if (update)
  {          
    TImporto imp(importo(TRUE));
    imp += importo_pagato(TRUE);
    chiusa = imp.is_zero();
    ((TRectype*)this)->put(SCAD_PAGATA, chiusa ? "X" : "");
  }
  else
    chiusa = get_bool(SCAD_PAGATA);
  
  return chiusa;  
}

// Calcola il totale dei pagamenti (eventualmente in valuta)
TImporto TRiga_scadenze::importo_pagato(bool val, int mode) const
{                                
  CHECKD(mode > 0x0 && mode <= 0xF, "Bad importo_pagato mode ", mode);
  const TPartita& game = partita();
  const bool in_val = in_valuta();   
  const char* imp_field = (val && in_val) ? PAGSCA_IMPORTOVAL : PAGSCA_IMPORTO;
  
  TImporto totale;
  for (int p = last(); p > 0; p = pred(p))
  {
    const TRectype& pag = row(p);                         // Riga pagamento
    const TRiga_partite& sum = game.riga(p);              // Riga partite
    const char sez = sum.sezione();
    
    if (mode & 0x1)              
      totale += TImporto(sez, pag.get_real(imp_field));
    if (!in_val && (mode & 0x8))     // Le ritenute non esistono nei pagamenti in valuta
      totale += TImporto(sez, pag.get_real(PAGSCA_RITENUTE));
    
    if (mode & 0x2)                   // Voglio anche gli abbuoni
    {
      real abb(pag.get_real(PAGSCA_ABBUONI));
      if (in_val && !val)             // Gli abbuoni sono sempre in valuta e quindi
      {                               // devono essere convertiti opportunamente
        abb *= sum.get_real(PART_CAMBIO);
        abb.round();
      }
      totale += TImporto(sez, abb);
    }
    
    if (in_val && !val && (mode & 0x4))   // Voglio anche le differenze cambio
    {                                     // Esse esistono solo nei pagamenti in valuta
      const TImporto diffcam(sez, pag.get_real(PAGSCA_DIFFCAM));
      totale += diffcam;
    }
  }  
  
  return totale.normalize();
}

// Calcola l'importo da pagare (eventualmente in valuta)
TImporto TRiga_scadenze::importo(bool val) const
{                                       
  const TRiga_partite& fatt = riga();              // Riga fattura
  const char* const imp = val && in_valuta() ? SCAD_IMPORTOVAL : SCAD_IMPORTO;
  const TImporto i(fatt.sezione(), get_real(imp));  
  return i;
}

// Calcola l'abbuono in valuta della rata e ritorna il suo tipo:
// 'A' abbuono attivo; 'P' abbuono passivo
char TRiga_scadenze::calcola_abbuono(int p, TImporto& abbuono, bool update)
{
  char ap = ' ';
  TRectype& pag = row(p);

  const char sez = partita().riga(p).sezione();
  
  if (update)
  {
    if (pag.get_char(PAGSCA_ACCSAL) == 'S')
    {                                
      abbuono = importo(TRUE);
      pag.zero(PAGSCA_ABBUONI);
      abbuono += importo_pagato(TRUE, 0x3);          // Conta anche altri abbuoni
      abbuono.swap_section();

      const int sign = abbuono.valore().sign();
      if (sign != 0)
      {
        if (sign > 0)
          ap = abbuono.sezione() == 'D' ? 'A' : 'P';
        else  
          ap = abbuono.sezione() == 'D' ? 'P' : 'A';
      }                       
    }
    else 
      abbuono.valore() = ZERO;
    
    if (ap > ' ')  
    {                         
      abbuono.normalize(sez);
      pag.put(PAGSCA_ABBUONI, abbuono.valore());
      pag.put(PAGSCA_PASSATT, ap);
    }  
    else
    {
      pag.zero(PAGSCA_ABBUONI);
      pag.zero(PAGSCA_PASSATT);
    }  
  }
  else
  {
    abbuono.set(sez, pag.get_real(PAGSCA_ABBUONI));
    ap = pag.get_char(PAGSCA_PASSATT);
    if (ap < ' ') ap = ' ';
  }
  
  return ap;
}

// Calcola la differenza cambi con la sezione da mettere nella riga contabile corrispondente
TImporto TRiga_scadenze::calcola_differenza_cambio(int p, bool update)
{ 
  TImporto diffcam;      

  TRectype& pag = row(p);
  if (pag.get_char(PAGSCA_ACCSAL) == 'S' && in_valuta())
  {
    const char sez = partita().riga(p).sezione();
    
    if (update)
    {
      diffcam = importo(FALSE);
      pag.zero(PAGSCA_DIFFCAM);
      diffcam += importo_pagato(FALSE);      // Conta anche gli abbuoni e le altre diffcam
      diffcam.swap_section();
      diffcam.normalize(sez);
      pag.put(PAGSCA_DIFFCAM, diffcam.valore());
    }
    else
    {                                            
      diffcam.set(sez, pag.get_real(PAGSCA_DIFFCAM));
    }  
  } 
  else
  {
    if (update)
      pag.zero(PAGSCA_DIFFCAM);
  }

  return diffcam;
}

TImporto TRiga_scadenze::residuo(bool val, int mode) const
{                         
  TImporto residuo(importo(val));
  residuo += importo_pagato(val, mode);            // Somma con sezione opposta 
  residuo.normalize();
  return residuo;
}


bool TRiga_scadenze::modifica_pagamento(const TRectype& new_pag, const TValuta& valuta,
                                        char& old_ap, TImporto& old_abb, TImporto& old_diffcam,  
                                        char& new_ap, TImporto& new_abb, TImporto& new_diffcam,
                                        bool update)
{       
  const bool in_val = in_valuta();
  const int nrigp = new_pag.get_int(PAGSCA_NRIGP);
  const TRectype old_pag(row(nrigp));
  TRiga_partite& sum = partita().riga(nrigp);
  
  TImporto old_abbuono;
  old_ap = calcola_abbuono(nrigp, old_abbuono, FALSE);   // Vecchio abbuono in valuta
  old_abb = old_abbuono;                                 // Vecchio abbuono in lire
  if (in_val)
  {
    old_abb.valore() *= sum.get_real(PART_CAMBIO);
    old_abb.valore().round();
  }
  
  old_diffcam = calcola_differenza_cambio(nrigp, FALSE);
  
  if (in_val && !valuta.in_lire())
    valuta.put(sum);
  
  row(nrigp) = new_pag;
  
  TImporto new_abbuono;
  new_ap = calcola_abbuono(nrigp, new_abbuono, update); // Calcolo abbuono in valuta
  
  new_abb = new_abbuono;                                // Calcola nuovo abbuono in lire
  if (in_valuta())
  {
    new_abb.valore() *= sum.get_real(PART_CAMBIO);
    new_abb.valore().round();
  }  

  new_diffcam = calcola_differenza_cambio(nrigp, update);
  
  sum.update(old_abbuono, new_abbuono, PART_SEZABB, PART_ABBUONI);
  sum.update(old_diffcam, new_diffcam, PART_SEZDIFCAM, PART_DIFFCAM);
  sum.update(old_pag, new_pag, PART_IMPORTO);
  sum.update(old_pag, new_pag, PART_IMPORTOVAL);
  sum.update(old_pag, new_pag, PART_RITENUTE);
  
  // Aggiorna flags di chiusura          
  chiusa(TRUE);          
  partita().chiusa(TRUE);             
  
  const bool empty = new_pag.get_char(PAGSCA_ACCSAL) != 'S' && 
                     new_pag.get_real(PAGSCA_IMPORTO).is_zero() &&
                     new_pag.get_real(PAGSCA_RITENUTE).is_zero();
  if (empty)
    rows_array().destroy_row(nrigp);
  
  return empty;
}

bool TRiga_scadenze::elimina_pagamento(int p)
{
  TRectype old_pag(row(p));
  old_pag.put(PAGSCA_ACCSAL, "A");
  old_pag.zero(PAGSCA_IMPORTO); 
  old_pag.zero(PAGSCA_IMPORTOVAL);                
  old_pag.zero(PAGSCA_RITENUTE);                
  TValuta val;
  return partita().modifica_pagamento(old_pag, val, TRUE);
}

///////////////////////////////////////////////////////////
// TRiga_partite
///////////////////////////////////////////////////////////

TRiga_partite::TRiga_partite(TPartita* game)
: TTree_rectype(LF_PARTITE, LF_SCADENZE, SCAD_NRATA), _partita(game)
{
  CHECK(_partita, "Partita nulla");
  TRiga_scadenze* scad = new TRiga_scadenze(this);
  copy_key_to_row(*this);
  rows_array().set_key(scad);               // Altrimenti le righe sarebbero dei TRectype!
}  

TRiga_partite::TRiga_partite(const TRiga_partite& r)
: TTree_rectype(r), _partita(r._partita)
{
  update_rigaptr();
}  

void TRiga_partite::update_rigaptr()
{
  for (int i = rate(); i >= 0; i--)
  {
    TRiga_scadenze& scad = i == 0 ? (TRiga_scadenze&)rows_array().key() : rata(i);
    scad._riga = this;
  }  
}

TRiga_scadenze& TRiga_partite::new_row(int r)
{               
  TRiga_scadenze& scad = (TRiga_scadenze&)TTree_rectype::new_row(r); 
  scad._riga = this;
  return scad;
}   

void TRiga_partite::elimina_rata(int r)
{
  const int from = r <= 0 ? 1 : r;
  const int to   = r <= 0 ? rate() : r;
  for (r = from; r <= to; r++)
  {
    TRiga_scadenze& scad = rata(r);
    for (int p = scad.last(); p > 0; p = scad.pred(p))
      scad.elimina_pagamento(p);
    rows_array().destroy_row(r);  
  }           
}

int TRiga_partite::ultima_ratapagata() const
{
  for (int r = rate(); r > 0; r--)
    if (rata(r).chiusa()) break;
  return r;
}

tipo_movimento TRiga_partite::tipo() const 
{ 
  const tipo_movimento tm = (tipo_movimento)get_int(PART_TIPOMOV); 
  CHECKD(tm != tm_nessuno, "Riga partite con un tipo losco ", tm);
  return tm;
}

int TRiga_partite::read(TBaseisamfile& f, word op)
{                  
  int err = TRectype::read(f, op);
  if (err == NOERR && is_fattura())
  {
    TRiga_scadenze* s = new TRiga_scadenze(this);             
    err = rows_array().read(s);                // Deve esistere almento una scadenza
    update_rigaptr();
  }  
  else 
    rows_array().destroy_rows();
  return err; 
}

int TRiga_partite::write(TBaseisamfile& f) const
{
  const int err = is_fattura() ?  TTree_rectype::write(f) : TRectype::write(f);
  return err;
}

int TRiga_partite::rewrite(TBaseisamfile& f) const
{
  const int err = is_fattura() ?  TTree_rectype::rewrite(f) : TRectype::rewrite(f);
  return err;
}

int TRiga_partite::remove(TBaseisamfile& f) const
{
  const int err = is_fattura() ?  TTree_rectype::remove(f) : TRectype::remove(f);
  return err;
}

int TRiga_partite::ultimo_pagamento(int r) const
{           
  const TRiga_scadenze& s = rata(r);
  return s.last();
}

bool TRiga_partite::update(const TRectype& vec, const TRectype& nuo, const char* field)
{
  real totale(get(field));
  totale -= vec.get_real(field);
  totale += nuo.get_real(field);
  put(field, totale);
  return totale.is_zero();
}

bool TRiga_partite::update(const TImporto& vec, const TImporto& nuo, 
                           const char* sez, const char* val)
{  
  bool zero = FALSE;                  
  TImporto growth(nuo); growth -= vec;     // Variazione al totale

  if (!growth.is_zero())
  {
    TImporto totale(get_char(sez), get_real(val));
    totale += growth;                      // incrementa il totale
    totale.normalize();
    put(sez, totale.sezione());            // Aggiorna il totale sul record
    put(val, totale.valore());
    zero = totale.is_zero();
  }
  else
    zero = get_real(val).is_zero();
  
  return zero;
}

int TRiga_partite::rata_con_abbuoni_diffcam() const
{         
  int found = 0;
  for (int s = rate(); s > 0 && found == 0; s--)
  {
    const TRiga_scadenze& scad = rata(s);
    for (int p = scad.last(); p > 0; p = scad.pred(p))
    {
      const TRectype& pag = scad.row(p);
      if (!pag.get_real(PART_ABBUONI).is_zero() || !pag.get_real(PART_DIFFCAM).is_zero())
      {
        found = s;
        break;
      }  
    }    
  }           
  return found;
}

// Calcola l'importo in lire o in valuta su di una riga di partita
// E' possibile considerare o meno IMPORTO, ABBUONI e DIFFCAM
TImporto TRiga_partite::importo(bool valuta, int mode) const
{      
  CHECKD(mode > 0 && mode < 16, "Bad importo mode ", mode);
  const bool in_lire = !in_valuta();
  if (valuta && in_lire)
    valuta = FALSE;
            
  TImporto i;          
  if (mode & 0x1)          
  {
    const char* const field = valuta ? PART_IMPORTOVAL : PART_IMPORTO;
    i.set(sezione(), get_real(field));
  }

  if (!is_fattura())
  {
    TImporto abbuoni(get_char(PART_SEZABB), get_real(PART_ABBUONI));
    if (valuta)
    {
      i += abbuoni;
    }
    else
    {
      if (in_lire && (mode & 0x8))
        i.valore() += get_real(PART_RITENUTE);
      
      if ((mode & 0x2) && !abbuoni.is_zero())
      {   
        if (!in_lire)
        {
          const real cambio(get(PART_CAMBIO));
          abbuoni.valore() *= cambio; 
          abbuoni.valore().round();
        }  
        i += abbuoni;
      }
      
      if (!in_lire && (mode & 0x4))
      {
        const TImporto diffcam(get_char(PART_SEZDIFCAM), get_real(PART_DIFFCAM));
        i += diffcam;
      }  
    }  
  }

  return i;
}

///////////////////////////////////////////////////////////
// TPartita
///////////////////////////////////////////////////////////

char TPartita::_cli_align = ' ';
char TPartita::_for_align = ' ';

TPartita::TPartita(const TBill& clifo, int anno, const char* num) 
: _part(LF_PARTITE, PART_NRIGA), _unassigned(LF_PAGSCA, "NRIGP"),
  _align(' ')
{ 
  read(clifo, anno, num); 
}                    

TPartita::TPartita(const TRectype& r)
: _part(LF_PARTITE, PART_NRIGA), _unassigned(LF_PAGSCA, "NRIGP"),
  _align(' ')
{
  TBill clifo; clifo.get(r);
  if (clifo.gruppo() == 0)
  {
    clifo.set(r.get_int(PART_GRUPPOCL), r.get_int(PART_CONTOCL), 
              clifo.sottoconto(), clifo.tipo());
  }

  const int anno = r.get_int(PART_ANNO);
  const char* num = r.get_str(PART_NUMPART);
  read(clifo, anno, num); 
}

const char* TPartita::build_key(TString& key) const
{
  if (_conto.tipo() > ' ')
    key.format("%c%3d%3d%6ld%4d%*s", _conto.tipo(), 0, 0, _conto.sottoconto(), 
               _anno, NUMLEN, (const char*)_num);
  else
    key.format("%c%3d%3d%6ld%4d%*s", 
               _conto.tipo(), _conto.gruppo(), _conto.conto(), _conto.sottoconto(), 
               _anno, NUMLEN, (const char*)_num);
  return key;               
}

int TPartita::compare(const TSortable& s) const
{
  TString80 key; build_key(key);
  const TPartita& p = (const TPartita&)s;             
  TString80 par; p.build_key(par);
  return strcmp(key, par);
}

void TPartita::carica_allineamento()
{
  TConfig cd(CONFIG_DITTA, "cg");
  _cli_align = cd.get_bool("NrCliDx") ? 'R' : 'L';
  _for_align = cd.get_bool("NrForDx") ? 'R' : 'L';
}

char TPartita::allineamento_richiesto() const
{
  char all;
  if (_cli_align <= ' ' || _for_align <= ' ')
  {            
#ifdef DBG  
    warning_box("Questa applicazione usa le partite ma ignora i "
                "parametri ditta relativi al loro allineamento: "
                "continuiamo pure cosi', facciamoci del male!");
#endif    
    carica_allineamento();
  }  
  switch (conto().tipo())
  {          
    case 'C': all = _cli_align; break;
    case 'F': all = _for_align; break;
    default : all = ' '; break;
  }
  return all;
}


// Ritorna l'allineamento corrente del numero partita
// L = Sinistra, R = Destra, ' ' = indeterminato
char TPartita::allineamento_corrente() const
{
  const TString& n = numero();
  
  char all = n[0] == ' ' ? 'R' : 'L';
  if (all == 'L' && n.len() == NUMLEN && n[6] != ' ')
    all = ' ';
  return all;  
}

void TPartita::allinea(char all)
{                       
  if (all <= ' ')
    all = allineamento_richiesto();

  if (all == 'R')
    _num.right_just(NUMLEN);
  else
    _num.trim();
       
  if (ok())
  {  
    _part.renum_key(PART_NUMPART, _num);
    _unassigned.renum_key(PAGSCA_NUMPART, _num);
  }  
}


// Costruisce le righe della partita
bool TPartita::read(const TBill& clifo, int year, const char* num)
{
  _conto = clifo;
  _anno  = year;
  _num   = num;
  
  if (_conto.tipo() > ' ' && allineamento_corrente() > ' ')
  {   
    // Prepara la chiave per verificare l'esistenza della partita specificata
    TLocalisamfile part(LF_PARTITE);   
    part.zero();
    part.put(PART_TIPOCF, _conto.tipo());
    part.put(PART_SOTTOCONTO, _conto.codclifo());
    part.put(PART_ANNO, _anno);
    part.put(PART_NUMPART, _num);
    TRectype filter(part.curr());              
    
    // Cerca la partita usando l'allineamento richiesto dall'utente
    if (part.read(_isgteq) == NOERR && part.curr() == filter)
    {
      // Trovata!
    }
    else
    {                     
      // Se non la trova inverte l'allineamento
      if (allineamento_corrente() == 'R')
        _num.trim();
      else  
        _num.right_just(NUMLEN);
      
      // Riprova a cercarla col nuovo allineamento
      filter.put(PART_NUMPART, _num);
      if (part.read(_isgteq) == NOERR && part.curr() == filter)
      {
                                                  // Esiste: memorizzo l'allineamento
      }                                            
      else
      {
        allinea();                                // Non esite: e' una nuova partita!
      }  
    }
  }
  else
    _num.trim();
  _align = allineamento_corrente();
  
  TRiga_partite* partita = new TRiga_partite(this);        // Record campione della partita
  TString16 str;
  str << clifo.tipo();
  partita->renum_key(PART_TIPOCF, str);                    // Tipo clifo
  if (clifo.tipo() <= ' ')
  {                                                        
    str.cut(0); str << clifo.gruppo();
    partita->renum_key(PART_GRUPPO, str);                  // Scrivi gruppo e conto solamente
    str.cut(0); str << clifo.conto();                      // nei conti normali (no clifo)
    partita->renum_key(PART_CONTO, str);                   
  }                                   
  str.cut(0); str << clifo.sottoconto();
  partita->renum_key(PART_SOTTOCONTO, str);                // Sottoconto o codice clifo
  str.cut(0); str << _anno;
  partita->renum_key(PART_ANNO, str);                      // Anno partita
  partita->renum_key(PART_NUMPART, _num);                  // Numero partita
  _part.read(partita);

  TRectype* unas = new TRectype(LF_PAGSCA);                // Record pagamenti non assegnati
  partita->copy_key_to_row(*unas);
  unas->put(PART_NRIGA, (int)UNASSIGNED);                           
  unas->put(SCAD_NRATA, (int)UNASSIGNED);
  _unassigned.read(unas);
  
  return ok();
}

bool TPartita::reread()
{       
  return read(conto(), anno(), numero());
}

bool TPartita::write(bool re) const
{                   
  if (ok() && conto().tipo() > ' ')
  {
    for (int r = last(); r > 0; r = pred(r))
    {
      TRiga_partite& row = riga(r);
      if (row.get(PART_GRUPPOCL).empty())
      {
        row.put(PART_GRUPPOCL, conto().gruppo());
        row.put(PART_CONTOCL, conto().conto());
      }
    }                           
    
    const char cur_align = allineamento_corrente();
    const char req_align = allineamento_richiesto();
    if (cur_align > ' ' && cur_align != req_align)
    {
      if (yesno_box("La partita %d '%s' non rispetta l'allineamento "
                    "specificato nei parametri ditta: si desidera correggerlo?", 
                    anno(), (const char*)numero()))
      {   
        // Riallinea tutti i numeri partita nelle righe. rate e pagamenti
        ((TPartita*)this)->allinea(req_align);
      }  
    }  
  }
  
  int err = _part.write(re);
  if (err == NOERR)
    err = _unassigned.write(re);
  
  // Se l'allineamento e' cambiato cancello la vecchia partita
  if (err == NOERR && allineamento_corrente() != allineamento_iniziale())
  {                   
    // Costruisce il vecchio numero partita
    TString16 num = numero();
    if (allineamento_iniziale() == 'R')
      num.right_just(NUMLEN);
    else  
      num.trim();
    
    // Cancella vecchia partita
    TPartita canc(conto(), anno(), num);
    canc.remove();
  }
    
  return err == NOERR;
} 

// Cancella tutta una partita
bool TPartita::remove() const
{ 
  const int err = _part.remove();  
  if (err == NOERR)
    _unassigned.remove();
  return err == NOERR;
} 


// Crea un  nuova riga partite
TRiga_partite& TPartita::new_row(int r) 
{                        
  if (r <= 0)                   
    for (r = 1; esiste(r); r++);
  CHECKD(!esiste(r), "Ue' pirla!, esiste gia' la riga ", r);
  TRiga_partite& nuova = (TRiga_partite&)_part.row(r, TRUE); 
  nuova.put(PART_GRUPPOCL, conto().gruppo());
  nuova.put(PART_CONTOCL, conto().conto());
  return nuova;
}

TRiga_scadenze& TPartita::rata(int nriga, int nrata) const
{
  //  if (nriga <= 0) nriga = prima_fattura();
  const TRiga_partite& r = riga(nriga);
  return r.rata(nrata);
}

bool TPartita::rata_chiusa(int nriga, int nrata) const
{        
  bool pag = FALSE;
  if (nriga != UNASSIGNED)
  {
    const TRiga_scadenze& r = rata(nriga, nrata);
    pag = r.chiusa();
  }
  return pag;
}

bool TPartita::esistono_abbuoni_diffcam(long nreg) const
{
  int nrata = 0;
  const int nriga = prima_fattura(nreg);
  if (_part.exist(nriga))
    nrata = riga(nriga).rata_con_abbuoni_diffcam();
  return nrata > 0;
}

TRectype& TPartita::pagamento(int nriga, int nrata, int nrigp)
{
  if (nriga == UNASSIGNED)
    return _unassigned.row(nrigp, TRUE);
  TRiga_scadenze& r = rata(nriga, nrata);
  return r.row(nrigp);
}

bool TPartita::esiste(int nriga, int nrata, int nrigp) const
{
  if (nriga == UNASSIGNED)
    return _unassigned.exist(nrigp);
  
  if (nrata <= 0)
    return _part.exist(nriga);
  
  if (nrigp <= 0)
  {
    const TRiga_partite& r = riga(nriga);
    return r.exist(nrata);
  }  
  
  const TRiga_scadenze& r = rata(nriga, nrata);
  return r.exist(nrigp);
}

// Ritorna l'importo in lire speso su di una riga contabile
TImporto TPartita::importo_speso(long nreg, int numrig) const
{           
  TImporto imp;
  
  for (int r = last(); r > 0; r = pred(r))
  {
    const TRiga_partite& row = riga(r);
    if (nreg == row.get_long(PART_NREG) && numrig == row.get_int(PART_NUMRIG))
      imp += row.importo(FALSE, 0x7);  // Importo in lire
  }
  
  return imp;
}


void TPartita::update_reg(long nreg, const TRectype& mov)
{                         
  for (int r = last(); r > 0; r = pred(r))
  {
    TRiga_partite& rig = riga(r);
    if (rig.get_long(PART_NREG) == nreg)
    {
      rig.put(PART_NREG,    mov.get(MOV_NUMREG));
      rig.put(PART_DATAREG, mov.get(MOV_DATAREG));
      rig.put(PART_DATADOC, mov.get(MOV_DATADOC));
      rig.put(PART_NUMDOC,  mov.get(MOV_NUMDOC));
      rig.put(PART_REG,     mov.get(MOV_REG));
      rig.put(PART_PROTIVA, mov.get(MOV_PROTIVA));
      rig.put(PART_CODCAUS, mov.get(MOV_CODCAUS));
    }    
  }
}

// Calcola la riga di movimento relativa a una riga partita
int TPartita::rig2mov(int rp) const
{
  const TRiga_partite& r = riga(rp);
  return r.get_int(PART_NUMRIG);
}

// Calcola la riga di partita relativa a una riga movimento
int TPartita::mov2rig(long numreg, int rm) const
{ 
  for (int r = last(); r > 0; r = pred(r))
  {
    const TRiga_partite& row = riga(r);
    if (row.get_long(PART_NREG) == numreg)
    {
      if (rm <= 0 || row.get_int(PART_NUMRIG) == rm)
        return r;
    }  
  }
  return 0;
} 

// Trova la prima riga della partita contenente una fattura
int TPartita::prima_fattura(long nreg) const
{  
  const int lastrow = last();
  for (int r = first(); r <= lastrow; r = succ(r))
  {
    const TRiga_partite& row = riga(r);
    const int tipomov = row.get_int(PART_TIPOMOV);
    if (tipomov == 1 || tipomov == 2)
      if (nreg <= 0 || nreg == row.get_long(PART_NREG))
        return r;
  }
  return -1;
}

// Trova la prima riga della partita contenente una fattura
int TPartita::primo_pagamento(long nreg) const
{  
  const int lastrow = last();
  for (int r = first(); r <= lastrow; r = succ(r))
  {
    const TRiga_partite& row = riga(r);
    const int tipomov = row.get_int(PART_TIPOMOV);
    if (tipomov != 1)   
      if (nreg <= 0 || nreg == row.get_long(PART_NREG))
        return r;
  }
  return -1;
}

void TPartita::calcola_saldo(TImporto& saldo, TImporto& doc, TImporto& pag, TImporto& imp) const
{   
  doc = pag = imp = TImporto('D', ZERO);

  for (int r = last(); r > 0; r = pred(r))
  {
    const TRiga_partite& row = riga(r);
    const TImporto i(row.importo(FALSE, 0x9));

    const tipo_movimento tipo = row.tipo();
    switch (tipo)
    {
    case tm_fattura:
    case tm_nota_credito:
      doc += i;              // documenti
      break;
    case tm_pagamento: 
      pag += i;              // pagamenti
      break;
    default:
      imp += i;              // altri importi
      break;
    }    
    
    if (tipo >= tm_pagamento)
    {
      const TImporto abbuoni(row.importo(FALSE, 0x2));
      imp += abbuoni;
      
      const TImporto diffcam(row.importo(FALSE, 0x4));
      imp += diffcam;
    }  
  }

  saldo = doc;
  saldo += pag;
  saldo += imp;
  
  saldo.normalize();
  doc.normalize();
  pag.normalize();
  imp.normalize();
}

TImporto TPartita::calcola_saldo(bool valuta) const
{
  TImporto saldo;
  for (int r = last(); r > 0; r = pred(r))
  {
    const TRiga_partite& row = riga(r);
    saldo += row.importo(valuta);
  }  
  return saldo;
}

TImporto TPartita::calcola_saldo_al(bool valuta,const TDate& al, const TDate& data_rischio) const
{
  TImporto saldo;
  const bool calcolo_rischio = data_rischio.ok();
  
  for (int r = last(); r > 0; r = pred(r))
  {
    const TRiga_partite& row = riga(r);
    if (row.is_fattura())
    {
      for (int s = row.rate(); s > 0 ;s--)
      {
        const TRiga_scadenze & rata = row.rata(s);
        const TDate data(rata.get(SCAD_DATASCAD));
        
        if (data < al)
         saldo += rata.importo(valuta);
      }  
    }
    else
    {
      const TDate data = row.get(PART_DATAPAG); 
      if (calcolo_rischio)
      {
        const int tipopag = row.get_int(PART_TIPOPAG);
        if (tipopag < 2 || tipopag > 7 || data <= data_rischio)
          saldo += row.importo(valuta);
        else
          if (data < al)
            saldo += row.importo(valuta);
      }    
      if (data < al)
        saldo += row.importo(valuta);
        
    }   
        
  }  
  return saldo;
}


// Controlla se esistono pagamenti riferiti alla riga nrigp
bool TPartita::utilizzata(int nrigp) const
{           
  for (int p = last(); p > 0; p = pred(p))
  {
    const TRiga_partite& fatt = riga(p);
    for (int r = fatt.rate(); r > 0; r--)
    {
      const TRiga_scadenze& scad = fatt.rata(r);
      if (scad.rows_array().exist(nrigp))
        return TRUE;
    }    
  }           
  return _unassigned.exist(nrigp);
}       

bool TPartita::modifica_pagamento(const TRectype& new_pag, const TValuta& valuta, 
                                  char& old_ap, TImporto& old_abb, TImporto& old_diffcam,  
                                  char& new_ap, TImporto& new_abb, TImporto& new_diffcam,
                                  bool update)
{                    
  const int nriga = new_pag.get_int(PAGSCA_NRIGA);
  const int nrata = new_pag.get_int(PAGSCA_NRATA);
  const int nrigp = new_pag.get_int(PAGSCA_NRIGP);

  bool empty = FALSE;
  
  if (nriga != UNASSIGNED)    
  {                       
    TRiga_scadenze& scaden = rata(nriga, nrata);
    empty = scaden.modifica_pagamento(new_pag, valuta,
                                      old_ap, old_abb, old_diffcam,  
                                      new_ap, new_abb, new_diffcam, update);
  }                            
  else
  {                                    
    const TRectype& old_pag = pagamento(nriga, nrata, nrigp);
    
    TRiga_partite& sum = riga(nrigp);
    empty = sum.update(old_pag, new_pag, PART_IMPORTO);
    sum.update(old_pag, new_pag, PART_IMPORTOVAL);
    sum.update(old_pag, new_pag, PART_RITENUTE);
    
    if (new_pag.get_real(PAGSCA_IMPORTO).is_zero())        
      _unassigned.destroy_row(nrigp);
    else    
      _unassigned.row(nrigp, FALSE) = new_pag;
    
    // Non ci possono essere abbuoni o differenze cambio di sorta!
    old_ap = new_ap = ' ';       
    old_abb = new_abb = old_diffcam = new_diffcam = TImporto('D', ZERO);
  }
  
  if (empty && !utilizzata(nrigp))
    rimuovi_riga(nrigp);

  return empty;    
} 


bool TPartita::modifica_pagamento(const TRectype& new_pag, const TValuta& valuta, bool update)
{
  char old_ap, new_ap;
  TImporto old_abbuono, new_abbuono, old_diffcam, new_diffcam;
  return modifica_pagamento(new_pag, valuta,
                            old_ap, old_abbuono, old_diffcam, 
                            new_ap, new_abbuono, new_diffcam, update);
}


bool TPartita::chiusa(bool update)
{ 
  bool chiusa = FALSE;          

  const int ultima = last();
  if (ultima > 0)
  {
    const TRiga_partite& row = riga(ultima);
    chiusa = row.get_bool(PART_CHIUSA);
  }
  
  if (update)
  {
    const TImporto saldo = calcola_saldo(TRUE);  // Saldo in valuta
    const bool ora_chiusa = saldo.is_zero();
    if (chiusa != ora_chiusa)
    {
      chiusa = ora_chiusa;
      for (int p = last(); p > 0; p = pred(p))
      {
        TRiga_partite& part = riga(p);
        part.put(PART_CHIUSA, chiusa);
      }
    }
  }  

  return chiusa;
}

void TPartita::rimuovi_riga(int r) 
{                
  TRiga_partite& row = riga(r);
  if (row.rate() > 0)
    row.elimina_rata(-1);
  else
    CHECKD(!utilizzata(r), "Can't remove still referenced row ", r);
  _part.destroy_row(r); 
  chiusa(TRUE);
}


static void somma(const TRectype& vec, TRectype& nuo, const char* field)
{
  real totale(vec.get(field));
  totale += nuo.get_real(field);
  nuo.put(field, totale);
}


void TPartita::sposta_riga(int r, TPartita& part)
{
  const TRiga_partite& row = riga(r);
  const long nreg = row.get_long(PART_NREG);
  const int prima = part.prima_fattura(nreg);
  TRiga_partite& nrw = part.riga(prima);
  
  const int address_size = last()+1;
  int* address = new int[address_size];
  memset(address, 0, address_size*sizeof(int));

  TToken_string knames(64);
  knames.add(PART_TIPOCF);
  knames.add(PART_GRUPPO);
  knames.add(PART_CONTO);
  knames.add(PART_SOTTOCONTO);
  knames.add(PART_ANNO);
  knames.add(PART_NUMPART);
  knames.add(PART_NRIGA);
  knames.add(SCAD_NRATA);
  knames.add(PAGSCA_NRIGP);
  
  for (int i = 1; i <= row.rate(); i++)
  {                         
    TRiga_scadenze& scd = row.rata(i);
    for (int j = scd.last(); j > 0; j = scd.pred(j))
    { 
      const TRectype& pag = scd.row(j);
      
      CHECK(j < address_size, "Bad nrigp address");
      if (address[j] == 0)
      {                                       
        const TRiga_partite& vecchia = riga(j);
        TRiga_partite& nuova = part.new_row(part.esiste(j) ? 0 :j);
        for (int f = 0; f < vecchia.items(); f++)
        {
          const char* fnam = vecchia.fieldname(f);
          if (knames.get_pos(fnam) < 0)
            nuova.put(fnam, vecchia.get(fnam));
        }
        nuova.zero(PART_IMPORTO);
        nuova.zero(PART_IMPORTOVAL);
        nuova.zero(PART_ABBUONI);
        nuova.zero(PART_DIFFCAM);
        address[j] = nuova.get_int(PART_NRIGA);
      }        
      
      const int nrigp = address[j];
      TRectype npg(LF_PAGSCA);
      if (part.esiste(r, i, nrigp))
      {  
        npg = part.pagamento(r, i, nrigp);
        CHECK(npg.get_char(PAGSCA_ACCSAL) != 'S', "Can't add to soldered payment");
        somma(pag, npg, PART_IMPORTO);
        somma(pag, npg, PART_IMPORTOVAL); 
        somma(pag, npg, PART_RITENUTE);
      }
      else
      {
        if (nrw.exist(i))
          npg = nrw.rata(i).new_row(nrigp);
        else
          npg = part.unassigned().row(nrigp, TRUE);
        for (int f = 0; f < pag.items(); f++)
        {
          const char* fnam = pag.fieldname(f);
          if (knames.get_pos(fnam) < 0)
            npg.put(fnam, pag.get(fnam));
        }    
      }                               
      const TValuta val;
      part.modifica_pagamento(npg, val, FALSE);
      scd.elimina_pagamento(j);
    }
  }  
  
  delete address;
  rimuovi_riga(r);        
}

// Sposta i pagamenti della riga r, dalla rata s compresa in poi, sui non assegnati
void TPartita::scollega_pagamenti(int r, int s)
{                          
  CHECKD(s > 0, "Impossibile scollegare la rata ", s);
  
  const TRiga_partite& row = riga(r);
  for (int i = row.rate(); i >= s; i--)
  {                         
    TRiga_scadenze& sc = row.rata(i);
    for (int j = sc.last(); j > 0; j = sc.pred(j))
    { 
      TRectype& pag = sc.row(j);                              
      CHECK(pag.get_real(PAGSCA_ABBUONI).is_zero(), "Can't detach payment with ABBUONI");
      CHECK(pag.get_real(PAGSCA_DIFFCAM).is_zero(), "Can't detach payment with DIFFCAM");
      pag.put(PAGSCA_NRIGA, (int)TPartita::UNASSIGNED);
      pag.put(PAGSCA_NRATA, (int)TPartita::UNASSIGNED);  
      pag.put(PAGSCA_ACCSAL, 'A');
      if (_unassigned.exist(j))
      { 
        TRectype& unas = _unassigned.row(j, FALSE);
        somma(pag, unas, PAGSCA_IMPORTO);
        somma(pag, unas, PAGSCA_IMPORTOVAL);
      }
      else
        _unassigned.add_row(pag);
      sc.rows_array().destroy_row(j);
    }
  }
}


///////////////////////////////////////////////////////////
// TPartite_array
///////////////////////////////////////////////////////////

// Certified 99%              
const TString& TPartite_array::key(const TBill& clifo, int anno, const char* num)
{                  
  if (clifo.tipo() > ' ')
    _key.format("%c%3d%3d%6ld%4d%*s", clifo.tipo(), 0, 0, clifo.sottoconto(), 
                                      anno, TPartita::NUMLEN, num);
  else
    _key.format("%c%3d%3d%6ld%4d%*s", 
                clifo.tipo(), clifo.gruppo(), clifo.conto(), clifo.sottoconto(), 
                anno, TPartita::NUMLEN, num);
  return _key;
}

// Certified 99%              
TPartita* TPartite_array::find(const TBill& clifo, int anno, const char* num, bool create)
{
  const TString& k = key(clifo, anno, num);
  TPartita* p = (TPartita*)objptr(k);
  if (p == NULL && create)
  {
    p = new TPartita(clifo, anno, num);
    add(k, p);
  }
  return p;
}

TPartita* TPartite_array::find(const TRectype& r, bool create)
{                           
  TBill zio; zio.get(r);
  if (zio.gruppo() == 0)
  {
    zio.set(r.get_int(PART_GRUPPOCL), r.get_int(PART_CONTOCL), 
            zio.codclifo(), zio.tipo());
  }
  const int anno = r.get_int(PART_ANNO);
  const char* num = r.get_str(PART_NUMPART);
  return find(zio, anno, num, create);
}

TPartita& TPartite_array::partita(const TBill& clifo, int anno, const char* num)
{                           
  TPartita* game = find(clifo, anno, num, TRUE);
  CHECK(game, "Partita errata");
  return *game;
}

TPartita& TPartite_array::partita(const TRectype& r)
{                           
  TPartita* game = find(r, TRUE);
  CHECK(game, "Partita errata");
  return *game;
}

bool TPartite_array::destroy(const TBill& clifo, int anno, const char* num)
{
  const TString& k = key(clifo, anno, num);
  return remove(k);
}

bool TPartite_array::write(bool re)
{       
  bool ok = TRUE;
  
  TPartita* game;
  restart();
  while ((game = (TPartita*)get()) != NULL)
  {
    ok = game->write(re);
    if (!ok)    // L'errore viene gia' segnalato dalla partita
      break;
  }  
  
  return ok;
}

void TPartite_array::destroy() 
{ 
  TAssoc_array::destroy(); 
  _numreg = 0;
}


// Aggiunge all'array tutte le partite che si riferiscono alla registrazione nreg
int TPartite_array::add_numreg(long nreg)
{                                       
  if (nreg != _numreg)
  {
    TRelation rel(LF_PARTITE);
    TRectype& part = rel.curr(); 
    
    // Costruzione cursore filtrato
    part.zero();
    part.put(PART_NREG, nreg);
    const TRectype filter(part);    
    TCursor cur(&rel, "", 2, &filter, &filter);
    
    for (cur = 0; cur.ok(); ++cur)
      partita(part);                // Aggiungi partita se non esiste gia'

    _numreg = nreg;
  }
  
  return items();
}

bool TPartite_array::utilizzata(long numreg, int numrig)
{
  bool ok = FALSE;
  for (TPartita* game = first(); game; game = next())
  {
    ok = game->mov2rig(numreg, numrig) > 0;
    if (ok) break;
  }  
  return ok;
}

TImporto TPartite_array::importo_speso(long nreg, int numrig)
{
  TImporto imp;
  add_numreg(nreg);
  for (TPartita* game = first(); game; game = next())
    imp += game->importo_speso(nreg, numrig);
  imp.normalize();
  return imp;
}

void TPartite_array::update_reg(const TRectype& mov, long old_nreg)
{
  if (old_nreg <= 0)
    old_nreg = mov.get_long(MOV_NUMREG);
  add_numreg(old_nreg);      
  for (TPartita* game = first(); game; game = next())
    game->update_reg(old_nreg, mov);
}

