// mg1400.cpp : Chiusura/Apertura esercizi di magazzino
#include <applicat.h>
#include <currency.h>
#include <mask.h>
#include <modaut.h>
#include <progind.h>
#include <tabutil.h>

#include "mglib.h"
#include "../cg/cglib01.h"
#include "../ve/veconf.h"

#include "mg1400.h"
#include "movmag.h"

#define MAX_ROWS 500

enum action 
{
  provvisoria,
  definitiva
};

class TApp_openclose;

class TMask_openclose: public TMask 
{
  bool                _ordaut;
  TEsercizi_contabili _esercizi;
  
  bool _gestione_listini;
  bool _gestione_condv;
  
protected:  
  static bool handle_close(TMask_field &, KEY);
  static bool handle_open(TMask_field &, KEY);
  static void common_handler(TMask_field &);
  static bool handle_what(TMask_field &, KEY);
  static bool handle_val(TMask_field &, KEY);

public:
  TEsercizi_contabili& exercise() { return (TEsercizi_contabili&)_esercizi; }
  TMask_openclose(const bool ord) ;
  virtual ~TMask_openclose() {}
};

TMask_openclose::TMask_openclose(const bool ord)
  : TMask("mg1400")
{
  _ordaut = ord;
  if (_ordaut)
    disable(-1);
  set_handler(F_DATECLOSE, handle_close);
  set_handler(F_DATEOPEN,  handle_open);
  set_handler(F_WHAT,      handle_what);
  set_handler(F_VALORIZZAZIONE, handle_val);
  
  TConfig cfg(CONFIG_DITTA, "ve");
  _gestione_listini = cfg.get_bool("GES", NULL, A_LISTINI);
  _gestione_condv = _gestione_listini && cfg.get_bool("GESLISCV");
}

bool TMask_openclose::handle_close(TMask_field &fld, KEY k)
{
  TMask_openclose& mask = (TMask_openclose&)fld.mask();
  if (k == K_ENTER || fld.to_check(k))
  {
    TEsercizi_contabili& ex = mask.exercise();
    TDate d(fld.get());
    
    const int es_close = ex.date2esc(d);
    mask.set(F_ESTOCLOSE, es_close);
    if (es_close == 0)
      return fld.error_box(TR("La data indicata non appartiene a nessun esercizio."));
      
    if (k == K_ENTER && ex.esercizio(es_close).chiusura_mag() != TDate(NULLDATE))
      return fld.error_box(FR("L'esercizio %d risulta gia' chiuso. Selezionare"
                           " un altro esercizio o togliere la data di chiusura."), es_close);
    else
    {
      int pred = ex.pred(es_close);
      if (k == K_ENTER && pred != 0 && ex.esercizio(pred).chiusura_mag() == TDate(NULLDATE))
        return fld.error_box(TR("L'esercizio precedente a quello indicato non e' ancora stato chiuso."));
    }
  }
  return TRUE;
}

bool TMask_openclose::handle_open(TMask_field &fld, KEY k)
{
  TMask_openclose& mask = (TMask_openclose&)fld.mask();
  if (k == K_ENTER || fld.to_check(k))
  {
    TDate in(mask.get(F_DATEOPEN));
    TDate fi(mask.get(F_DATECLOSE)); 
    if (k == K_ENTER && in <= fi )
      return fld.warning_box (TR("La data di apertura del nuovo esercizio deve essere superiore alla data di chiusura dell'esercizio precedente"));
    const int es_open = mask.exercise().date2esc(in);
    
    mask.set(F_ESTOOPEN, es_open);
    if (es_open == 0)
      return fld.error_box(TR("La data indicata non appartiene a nessun esercizio"));
    
    const int es_close = mask.exercise().pred(es_open);
    if (es_close == 0)
      return fld.error_box(TR("Non esiste l'esercizio precedente per la data indicata."));
    
    // Setta data ed esercizio di chiusura  
    mask.set(F_ESTOCLOSE,es_close);
    mask.set(F_DATECLOSE, mask.exercise().esercizio(es_close).fine());
  }
  return TRUE;
}

void TMask_openclose::common_handler(TMask_field &fld)
{
  TMask_openclose& mask = (TMask_openclose&)fld.mask();
  const action w = (action) mask.get_int(F_WHAT);
  const TTipo_valorizz t = (TTipo_valorizz) mask.get_int(F_VALORIZZAZIONE);
  const bool eb = w == definitiva && t >= valorizz_FIFOa;
  if (w == provvisoria) // Se oper. provv. disabilita tutte le causali
  {
    mask.disable(-6);
    mask.reset(-6);
  }
  else
  {
    mask.enable(-6);
    mask.enable(-5, eb);
    mask.enable(-4, !eb);
    // Le causali degli ordini sono abilitate solo se NON c'e' il modulo ordini
    if (mask._ordaut)
      mask.disable(-1);
    if (eb)
      mask.reset(-4);
    else
      mask.reset(-5);
  }
  if (mask.field(F_LIST).shown())
  {                                         
    mask.enable(F_LIST, mask._gestione_listini);
    mask.enable(F_CATVEN, mask._gestione_condv);
  }
}

bool TMask_openclose::handle_what(TMask_field &fld, KEY k)
{
  if (k == K_SPACE)
    common_handler(fld);
  return TRUE;
}

bool TMask_openclose::handle_val(TMask_field &fld, KEY k)
{
  if (fld.to_check(k))
    common_handler(fld);
  return TRUE;
}

// Corrispondenza elementi di _movimenti:
#define CAUS_A_RIMIN    0 // movimento con causale di rimanenze iniziali
#define CAUS_A_INCL     1 //  "              "      "  In conto lav.
#define CAUS_A_ACL      2 //  "              "      "  A  conto lav.
#define CAUS_A_INPRF    3 //  "              "      "  In prod. fin.
#define CAUS_A_INPRC    4 //  "              "      "  In prod. comp.
#define CAUS_A_ORDF     5 //  "              "      "  Ordinato fornitori
#define CAUS_A_ORDC     6 //  "              "      "  Ordinato clienti
#define CAUS_A_QTA      7 //  "              "      "  Movimentazione solo quantit�
#define CAUS_A_VAL      8 //  "              "      "  Movimentazione solo valore
// Elemento 9 e successivi: tutte le righe in eccesso a MAX_ROWS
// CAUS_A_QTA e CAUS_A_VAL vengono utilizzate solo se la chiusura � definitiva e la 
// valorizzazione impostata � FIFO/LIFO




class TApp_openclose : public TSkeleton_application
{
  TMask_openclose  *_msk;
  TArray           _movimenti; // Array di movimenti di TMov_mag
  TString_array    _causali;   // Array delle causali per i movimenti d'apertura (7)
  TString          _es_to_close;
  TString          _es_to_open;
  TString          _catven;
  TString          _codlis;
  TDate            _date_to_close;
  TDate            _date_to_open;
  action           _what;   
  bool             _valorizza_comp;   
  TTipo_valorizz   _calctype;
  
protected:
  virtual bool create();
  virtual bool destroy();
  virtual void main_loop();
  //void  scrivi_movimenti(TCursor&);
  //void  aggiorna_saldi();
  void compila_movimento(TMov_mag& currmov, TArticolo_giacenza& art, TRectype& rec, const int caus);
  void chiudi_esercizio();
  
public:
  TApp_openclose() {};
  virtual ~TApp_openclose() {};
};


bool TApp_openclose::create()
{                               
   TConfig c(CONFIG_DITTA);
	 open_files(LF_ANAMAG, LF_UMART, LF_MAG, LF_MOVMAG, LF_RMOVMAG, LF_STOMAG, 0);
  _msk = new TMask_openclose(!c.get_bool("RIPORD"));
  return TSkeleton_application::create();
}


bool TApp_openclose::destroy()
{
  delete _msk;
  return TSkeleton_application::destroy();
}


void TApp_openclose::compila_movimento(TMov_mag& currmov, TArticolo_giacenza& art, TRectype& rec, const int caus)
{
  // Aggiorna la testata se il movimento e' nuovo (non ha righe)
  if (currmov.rows() == 0)
  {
    TRectype& head = currmov.head();
    head.put(MOVMAG_ANNOES, _es_to_open);
    head.put(MOVMAG_DATAREG, _date_to_open);
    head.put(MOVMAG_DATACOMP, _date_to_open);
    head.put(MOVMAG_CODCAUS, _causali.row(caus));
  } 
  
  // Currency delle mie brame...
  TCurrency currency;
  
  // Aggiunge una nuova riga al movimento corrente
  TString codmagdep(rec.get(MAG_CODMAG));
  TString codmag(codmagdep);codmag.cut(3);
  TString codart(rec.get(MAG_CODART));
  TString livello(rec.get(MAG_LIVELLO));

  real qta, prezzo, val;
  // switch sul tipo di causale per settare quantita' e prezzo:
  switch (caus)
  {
    case CAUS_A_RIMIN: // Rimanenze iniziali
    case CAUS_A_QTA: // Movimentazione solo quantit�
    case CAUS_A_VAL: // Movimentazione solo valore
      {
        qta = rec.get_real(MAG_GIAC) + rec.get_real(MAG_ACL) - rec.get_real(MAG_INCL); // ??corretta da AcL, IncL e InProdF ??
        if (!_valorizza_comp)
          qta += rec.get_real(MAG_PRODFIN)-rec.get_real(MAG_PRODCOMP);
        if (caus != CAUS_A_QTA)
        {
          switch (_calctype)
          {
            case valorizz_costmediopond:
              val = art.costo_mediopond(_es_to_close, codmag, livello);
              break;
            case valorizz_costmedio:
              val = art.costo_medio(_es_to_close, codmag, livello);
              break;
            case valorizz_ultcos: 
              val = art.ultimo_costo(_es_to_close);
              break;
            case valorizz_mediacos:
              val = art.media_costi(_es_to_close);
              break;
            case valorizz_przlist:
              val = art.prezzo_listino(_es_to_close, _catven, _codlis);
              break;
            case valorizz_coststd:
              val = art.costo_standard(_es_to_close);
              break;
            case valorizz_LIFOa:
              val = art.LIFO_annuale(_es_to_close, codmag, livello);
              break;
            case valorizz_FIFOa:
              val = art.FIFO_annuale(_es_to_close, codmag, livello);
              break;
            case valorizz_LIFO:
              val = art.LIFO(_es_to_close, codmag, livello);
              break;
            case valorizz_FIFO:
              val = art.FIFO(_es_to_close, codmag, livello);
              break;
            case valorizz_LIFOr:
              val = art.LIFO_ragionieristico(_es_to_close, codmag, livello);
              break;
            case valorizz_FIFOr:
              val = art.FIFO_ragionieristico(_es_to_close, codmag, livello);
              break;
            default:
              break;
          }
        }
        if (caus == CAUS_A_RIMIN)
          prezzo = val;
        else
          if (caus == CAUS_A_VAL)
          {
            prezzo = val * qta;
            qta = ZERO;
          }// Per CAUS_A_QTA prezzo rimane a ZERO
      }
      break;
    case CAUS_A_INCL: // In conto lav.
      qta = rec.get_real(MAG_INCL);
      break;
    case CAUS_A_ACL: // A conto lav.
      qta = rec.get_real(MAG_ACL);
      break;
    case CAUS_A_INPRF: // In prod. fin.
      qta = rec.get_real(MAG_PRODFIN);
      break;
    case CAUS_A_INPRC: // In prod. com.
      qta = rec.get_real(MAG_PRODCOMP);
      break;
    case CAUS_A_ORDF: // Ord. forn.
      qta = rec.get_real(MAG_ORDF);
      val = rec.get_real(MAG_VALORDF);
      prezzo = val/qta;
      break;
    case CAUS_A_ORDC: // Ord. cli.
      qta = rec.get_real(MAG_ORDC);
      val = rec.get_real(MAG_VALORDC);
      prezzo = val/qta;
      break;
    default:
      break;
  }
  if (qta != 0.0 || (caus == CAUS_A_VAL && prezzo != ZERO))
  {
    TRectype& riga = currmov.new_row();
    TRecord_array& ums = art.um();
    const int r = ums.first_row();
    if (r > 0)
      riga.put(RMOVMAG_UM, (ums[r]).get(UMART_UM));
    riga.put(RMOVMAG_CODMAG, codmagdep);
    riga.put(RMOVMAG_CODART, codart);
    riga.put(RMOVMAG_LIVGIAC, livello);
    // La causale di riga va messa solo se e' diversa da quella di testata!
    riga.put(RMOVMAG_QUANT, qta);
    
    // Setta il prezzo al nr di decimali impostati per la valuta corrente
    currency.set_price(caus == CAUS_A_VAL ? FALSE : TRUE); // Se mov. solo valore (FIFO/LIFO) i decimali devono essere per gl'importi
    currency.set_num(prezzo);
    riga.put(RMOVMAG_PREZZO, currency);
  }
}


// Chiude l'esercizio selezionato (in base al flag lo fa provvisoriamente o definitivamente)
// L'unica differenza sta nella creazione dei movimenti d'apertura nel caso di chiusura definitiva
void TApp_openclose::chiudi_esercizio()
{
  TRelation rel(LF_ANAMAG);
  TCursor   cur(&rel);
  rel.lfile().set_curr(new TArticolo_giacenza()); // setta il Multirec come current della relazione

  TString descr1, descr2, codmag;
  int err = NOERR;
  TTable esc("ESC");
  TTable magazzini("MAG");
  
//  rebuild_balances(_es_to_close);  // Cosi' ci assicuriamo che i saldi dell'esercizio vecchio siano a posto
  descr1.format(_what == definitiva ? FR("Apertura esercizio %s") : FR("Aggiornamento saldi esercizio %s"), (const char*) _es_to_open);
  descr2 << descr1 << TR(" in corso...");
  
  // cose specifiche per chiusura definitiva...
  // Reperisce l'ultimo movimento di magazzino per il numero di registrazione
	TLocalisamfile movmag(LF_MOVMAG);

  movmag.last();
  long numreg = movmag.get_long(MOVMAG_NUMREG) +1;
  // Indici per i movimenti. Crea i movimenti di base (uno per ogni causale)
  int indici[9], last = 8;
  if (_what == definitiva)
  {
    for (int j = 0; j < 9; j++)
    {
      _movimenti.add(new TMov_mag);
      TRectype& h = ((TMov_mag&) _movimenti[j]).head();
      h.put(MOVMAG_NUMREG, numreg++);
      h.put(MOVMAG_DESCR,  descr1);
      indici[j] = j;
    }

    const long max_art = cur.items();
    cur.freeze();
    TProgind *prog = new TProgind(max_art, descr2, FALSE, TRUE);
    // Ciclo per i fottuti articoli
    for (cur = 0L; cur.pos() < max_art; ++cur)
    {
      // Scorre i saldi del vecchio esercizio per questo TArticolo_giacenza
      // compilando un array di almeno 9 TMov_mag
      // Almeno 9 perche' 9 sono le causali; fissiamo il limite di righe per movimento a 500 (MAXROWS)
      // quindi i movimenti possono essere di piu'.
      // Caso particolare da notare: nel caso di valorizzazione LIFO/FIFO in realt� i movimenti
      // Sono sempre e solo 2, uno movimenta solo la qta ed uno solo il valore, in modo da far
      // tornare i cosiddetti conti
      
      // I saldi per questo articolo
      TArticolo_giacenza& art = (TArticolo_giacenza&)cur.curr();
      TRecord_array& rec_arr = art.mag(_es_to_close);
      // Scorriamo le righe...
      const int lastrow = rec_arr.last_row();
      for (int r = lastrow; r > 0; r = rec_arr.pred_row(r))
      {
        TRectype& rec = rec_arr[r];
        for (int i = 0; i < 9; i++)
        {
          if (_causali.row(i).empty())
            continue; // Salta eventuali causali vuote
          // se il numero di righe di questo movimento eccede le 500 ne crea uno nuovo e 
          // setta il nuovo indice
          const int rows = ((TMov_mag&) _movimenti[indici[i]]).rows() ;
          if (rows > MAX_ROWS)
          {
            _movimenti.add(new TMov_mag);
            last++;
            indici[i] = last;
            TRectype& h = ((TMov_mag&) _movimenti[last]).head();
            h.put(MOVMAG_NUMREG, numreg++); // Aggiorna il nr. reg per il prossimo movimento
            h.put(MOVMAG_DESCR,  descr1);
          }
          TMov_mag& currmov = (TMov_mag&) _movimenti[indici[i]];
          compila_movimento(currmov, art, rec, i);
        }
      }

      // Aggiornamento storico
      
      // Zappa eventuali record gi� presenti.
      art.storico(_es_to_open).destroy_rows();
      
      // Ricostruisce lo storico per ogni magazzino possibile
      for (magazzini.first(); !magazzini.eof(); magazzini.next())
      {
        codmag = magazzini.get("CODTAB");
        if (codmag.len() > 3)
          continue; // Salta i depositi
          
        switch (_calctype)
        {
          case valorizz_LIFO:
            art.agg_storicoLIFO(_es_to_close,  codmag, TRUE, _valorizza_comp); break;
          case valorizz_FIFO:
            art.agg_storicoFIFO(_es_to_close,  codmag, TRUE, _valorizza_comp); break;
          default: 
          {
            real val;
            switch (_calctype)
            {
              case valorizz_costmediopond:
                val = art.costo_mediopond(_es_to_close, codmag, "");
                break;
              case valorizz_costmedio:
                val = art.costo_medio(_es_to_close, codmag, "");
                break;
              case valorizz_ultcos: 
                val = art.ultimo_costo(_es_to_close);
                break;
              case valorizz_mediacos:
                val = art.media_costi(_es_to_close);
                break;
              case valorizz_przlist:
                val = art.prezzo_listino(_es_to_close, _catven, _codlis);
                break;
              case valorizz_coststd:
                val = art.costo_standard(_es_to_close);
                break;
              case valorizz_LIFOa:
                val = art.LIFO_annuale(_es_to_close, codmag, "");
                break;
              case valorizz_FIFOa:
                val = art.FIFO_annuale(_es_to_close, codmag, "");
                break;
              default: break;
            }
            if (val != ZERO)
              art.agg_storico(_es_to_close, codmag, TRUE, _valorizza_comp, val); 
            break;
          }
        }
      }
      
      prog->addstatus(1L);
    }
    delete prog;    
    // **************
    // Effettua la scrittura dei movimenti di apertura generati
    const int max_mov = _movimenti.items();
    prog = new TProgind(max_mov, TR("Scrittura movimenti di apertura in corso..."),FALSE,TRUE);
    for (int i=0; err == NOERR && i<max_mov;i++)
    {
      prog->addstatus(1L);
      TMov_mag& mov = (TMov_mag&) _movimenti[i];
      if (mov.rows() == 0)
        continue; // Salta eventuali movimenti senza righe (OrdC e OrdF)
      // Effettua la rinumerazione del movimento se esso esiste gia'?
      err = mov.write(movmag); 
    }
    delete prog;
    
    // **************
    // chiude l'esercizio
    if (err == NOERR)
    {
      // Marca l'esercizio come chiuso
      esc.put("CODTAB", _es_to_close);
      err = esc.read();
      if (err == NOERR)
      {
        esc.put("D4", _date_to_close);
        err = esc.rewrite();
        if (err != NOERR)
          error_box(FR("Errore %d in fase di chiusura esercizio %s."), err, (const char*) _es_to_close);
      }
      else
        error_box(FR("Errore %d in fase di lettura esercizio %s."), err, (const char*) _es_to_close);
    }
    else
      error_box(FR("Errore %d durante la scrittura dei movimenti."),err);
    _movimenti.destroy();
  }
  
  // A questo punto l'esercizio vecchio e' marcato come chiuso in base al tipo di chiusura 
  // (definitiva o provvisoria); La ricostruzione dei saldi e' comune in entrambi i casi:
  // la rebuild_balances() deve sapere da sola se azzerare o meno le giacenze
  // a seconda che l'esercizio precedente sia chiuso (AZZERA) o no (NON AZZERARE).
  rebuild_balances(_es_to_open, _calctype, _catven, _codlis); 
}


void TApp_openclose::main_loop()
{
  //Preimposta valori di apertura e chiusura esercizio
  TDate d(TODAY);
  TEsercizi_contabili& ex = _msk->exercise();
  const int es_open  = ex.date2esc(d);
  if (es_open != 0)
  {
    _msk->set(F_DATEOPEN,ex.esercizio(es_open).inizio());
    _msk->set(F_ESTOOPEN,es_open);
  }
  const int es_close = ex.pred(es_open);
  if (es_close != 0)
  {
    _msk->set(F_DATECLOSE,ex.esercizio(es_close).fine());
    _msk->set(F_ESTOCLOSE,es_close);
  }
  
  while (_msk->run() == K_ENTER)
  {
    // Here is
    _causali.destroy();
    _date_to_close = _msk->get_date(F_DATECLOSE);
    _date_to_open = _msk->get_date(F_DATEOPEN);
    _es_to_close   = _msk->get(F_ESTOCLOSE);
    _es_to_open    = _msk->get(F_ESTOOPEN);
    _calctype      = (TTipo_valorizz) _msk->get_int(F_VALORIZZAZIONE);
    _what          = (action) _msk->get_int(F_WHAT); //Provvisoria o definitiva?
    _valorizza_comp= _msk->get(F_VALCOMP)=="C"; //Valorizza i componenti o i finiti
    _codlis = _msk->get(F_LIST);
    _catven = _msk->get(F_CATVEN);
    for (short xx = F_CAURIM; xx <= F_CAUVAL; xx++) // legge le causali impostate
      _causali.add(_msk->get(xx));
    chiudi_esercizio(); // Chiusura esercizio
  }
}


int mg1400(int argc, char* argv[])
{
  TApp_openclose a;

  a.run(argc, argv, TR("Apertura/Chiusura esercizio"));
  return 0;
}