campo-sirio/lv/lv2100.cpp

520 lines
19 KiB
C++
Executable File

#include <applicat.h>
#include <config.h>
#include <automask.h>
#include <progind.h>
#include "lvlib.h"
#include "lvcondv.h"
#include "lvpasplan.h"
#include "lvrconsplan.h"
#include "lv2100a.h"
/////////////////////////////////////
//// TGENERA_PLANNING_MASK ////
/////////////////////////////////////
//classe TGenera_plannin_mask
class TGenera_planning_mask : public TAutomask
{
public:
virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly);
TGenera_planning_mask (const char* name) : TAutomask(name) {}
};
//ON_FIELD_EVENT: metodo che gestisce gli eventi sui campi della maschera
bool TGenera_planning_mask::on_field_event(TOperable_field& o, TField_event e, long jolly)
{
switch (o.dlg())
{
case F_CODCF:
case F_CODITI:
if (e == fe_close)
if (!field(F_CODCF).empty() && !field(F_CODITI).empty())
return error_box(TR("Non è possibile specificare sia itinerario che cliente"));
break;
default: break;
}
return true;
}
/////////////////////////////////////
//// TGENERA_PLANNING_APP ////
/////////////////////////////////////
//classe TGenera_plannin_app
class TGenera_planning_app : public TSkeleton_application
{
TGenera_planning_mask* _msk;
TAssoc_array _codriga;
protected:
virtual bool create();
virtual bool destroy();
bool elimina_planning(const TDate& dadata, const TDate& adata, const bool elimina) const;
bool elimina_planning_itinerario(const TDate& dadata, const TDate& adata, const long& coditi, const bool elimina) const;
bool elimina_planning_cliente(const TDate& dadata, const TDate& adata, const long& codcf, const bool elimina) const;
bool kill_planning (TISAM_recordset& selrighe) const;
int prossimo_codriga(const TDate& data);
TDate cerca_ultima_consegna(long codcf, long codcont, const TDate& data) const;
int conta_consegne_mese(long codcf, long codcont, const TDate& adata, long coditi) const;
void arrotonda_al_giorno(TDate& data, const int ggcons) const;
void elabora_passaggio(const TDate& dadata, const TDate& adata, const TISAM_recordset& pplan, TFast_isamfile& rplan);
const TDate str2date(const TString& data, int year) const;
public:
bool transfer();
virtual void main_loop();
};
//CREATE: metodo costruttore
bool TGenera_planning_app::create()
{
_msk = new TGenera_planning_mask("lv2100a");
return TSkeleton_application::create();
}
//DESTROY: metodo distruttore
bool TGenera_planning_app::destroy()
{
delete _msk;
return TApplication::destroy();
}
////////////////////////////////////////////////////////////////
////// METODI PER LA CANCELLAZIONE DEI PLANNING //////
////////////////////////////////////////////////////////////////
//ELIMINA_PLANNING: metodo che estrae dalla tabella dei planning tutte le righe comprese tra le due date indicate
bool TGenera_planning_app::elimina_planning(const TDate& dadata, const TDate& adata, const bool elimina_manuali) const
{
//creo il recordset
TString query;
if (elimina_manuali)
query << "USE LVRCONSPLAN KEY 2\nFROM DTCONS=" << dadata << "\nTO DTCONS=" << adata;
else
query << "USE LVRCONSPLAN KEY 2 SELECT CONSSTD!=\"\"\nFROM DTCONS=" << dadata << "\nTO DTCONS=" << adata;
TISAM_recordset selrighe(query);
//richiamo la funzione che effettivamente fa la cancellazione delle righe interessate
kill_planning(selrighe);
return true;
}
//ELIMINA_PLANNING_ITINERARIO: metodo che estrae dalla tabella dei planning tutte le righe comprese tra...
//...le due date indicate relative ad un itinerario specifico
bool TGenera_planning_app::elimina_planning_itinerario(const TDate& dadata, const TDate& adata, const long& coditi, const bool elimina_manuali) const
{
TString4 itinerario;
itinerario.format("%03d",coditi);
//creo il recordset
TISAM_recordset selrighe("USE LVRCONSPLAN KEY 2\nSELECT CODITI=#CODITI\nFROM DTCONS=#DADATA\nTO DTCONS=#ADATA");
//setto le variabili
selrighe.set_var("#CODITI",coditi);
selrighe.set_var("#DADATA",dadata);
selrighe.set_var("#ADATA",adata);
//richiamo la funzione che effettivamente fa la cancellazione delle righe interessate
kill_planning(selrighe);
return true;
}
//ELIMINA_PLANNING_CLIENTE: metodo che estrae dalla tabella dei planning tutte le righe comprese tra
//...le due date indicate relative ad un cliente specifico
bool TGenera_planning_app::elimina_planning_cliente(const TDate& dadata, const TDate& adata, const long& codcf, const bool elimina_manuali) const
{
//creo il recordset
TISAM_recordset selrighe("USE LVRCONSPLAN KEY 2\nSELECT CODCF=#CODCF\nFROM DTCONS=#DADATA\nTO DTCONS=#ADATA");
//setto le variabili
selrighe.set_var("#CODCF",codcf);
selrighe.set_var("#DADATA",dadata);
selrighe.set_var("#ADATA",adata);
//richiamo la funzione che effettivamente fa la cancellazione delle righe interessate
kill_planning(selrighe);
return true;
}
//KILL_PLANNING: metodo che elimina un recordset generato precedentemente
bool TGenera_planning_app::kill_planning (TISAM_recordset& selrighe) const
{
const TRecnotype righe = selrighe.items();
if (righe > 0)
{
TProgress_monitor pi(righe, TR("Eliminazione planning precedenti in corso..."));
TLocalisamfile& rplan = selrighe.cursor()->file();
for (bool ok = selrighe.move_last(); ok; ok = selrighe.move_prev())
{
rplan.remove();
if (!pi.add_status())
break;
}
}
return true;
}
////////////////////////////////////////////////////////////////
////// METODI PER LA GENERAZIONE DEI PLANNING //////
////////////////////////////////////////////////////////////////
//PROSSIMO_CODRIGA: metodo che restituisce il prossimo codriga per il planning della data interessata
int TGenera_planning_app::prossimo_codriga(const TDate& data)
{
TString8 strcodplan;
strcodplan << data.date2ansi();
real *ptriga = (real*)_codriga.objptr(strcodplan);
if (ptriga == NULL)
{
//per ora memorizzo zero
ptriga = new real;
_codriga.add(strcodplan,ptriga);
//se esiste almeno una riga, memorizzo il codriga dell'ultima
TISAM_recordset rplan("USE LVRCONSPLAN\nFROM CODPLAN=#CODPLAN\nTO CODPLAN=#CODPLAN");
rplan.set_var("#CODPLAN",TVariant(strcodplan));
if (rplan.move_last())
*ptriga = rplan.get(LVRCONSPLAN_CODRIGA).as_real();
}
*ptriga += UNO; //incremento il codriga interessato
return ptriga->integer();
}
//CERCA_ULTIMA_CONSEGNA: questo metodo ricerca la data dell'ultima consegna fatta ad un certo cliente
TDate TGenera_planning_app::cerca_ultima_consegna(long codcf, long codcont, const TDate& data) const
{
//instanzia un recordset di LVRCONSPLAN prendendo tutte le consegne fatte per un cliente su un certo...
//...itinerario prima di una certa data
/* TISAM_recordset rplan("USE LVRCONSPLAN KEY 3\nFROM CODCF=#CODCF CODCONT=#CODCONT\nTO CODCF=#CODCF CODCONT=#CODCONT DTCONS=#DATA");
rplan.set_var("#CODCF",codcf);
rplan.set_var("#CODCONT",codcont);
rplan.set_var("#DATA",data);
if (rplan.move_last())
return rplan.get(LVRCONSPLAN_DTCONS).as_date(); // data dell'ultima consegna fatta
*/
TLocalisamfile rplan(LF_LVRCONSPLAN);
rplan.put(LVRCONSPLAN_CODCF, codcf);
rplan.put(LVRCONSPLAN_CODCONT, codcont);
rplan.put(LVRCONSPLAN_DTCONS, data);
rplan.put(LVRCONSPLAN_CODITI, 999);
int err = rplan.read(_isgteq);
if (err == NOERR)
err = rplan.prev();
if (err == NOERR)
return rplan.get_date(LVRCONSPLAN_DTCONS); // data dell'ultima consegna fatta
return data;
}
//CONTA_CONSEGNE_MESE: questo metodo conta quante consegne sono state fatte ad un certo cliente
//dal primo del mese fino ad una certa data
int TGenera_planning_app::conta_consegne_mese(long codcf, long codcont, const TDate& adata, long coditi) const
{
TDate dadata = adata;
dadata.set_day(1);
const int giorno = adata.wday();
//instanzia un recordset di LVRCONSPLAN prendendo tutte le consegne fatte per un cliente su un certo...
//...itinerario prima di una certa data (conta giorno per giorno)
TISAM_recordset rplan("USE LVRCONSPLAN KEY 3\nSELECT CODITI=#CODITI\nFROM CODCF=#CODCF CODCONT=#CODCONT DTCONS=#DADATA\nTO CODCF=#CODCF CODCONT=#CODCONT DTCONS=#ADATA");
rplan.set_var("#CODITI",coditi);
rplan.set_var("#CODCF",codcf);
rplan.set_var("#CODCONT",codcont);
rplan.set_var("#DADATA",dadata);
rplan.set_var("#ADATA",adata);
int contatore = 0;
for (bool ok = rplan.move_first(); ok; ok = rplan.move_next())
{
TDate data = rplan.get(LVRCONSPLAN_DTCONS).as_date();
if (giorno == data.wday())
contatore++;
}
return contatore;
}
//ARROTONDA_AL_GIORNO: questo metodo arrotonda per eccesso la data al giorno della settimana desiderato
//ggcons appartiene a [1..7] con 1 = lunedì -> 7 = domenica
void TGenera_planning_app::arrotonda_al_giorno(TDate& data, const int ggcons) const
{
int delta = ggcons - data.wday();
if (delta != 0)
{
if (delta < 0)
delta += 7;
data += delta;
}
}
//STR2DATE: metodo che converte una stringa in una data o non inizilizzata o sicuramente valida
const TDate TGenera_planning_app::str2date(const TString& data, int year) const
{
TDate date;
const int g = atoi(data.left(2));
const int m = atoi(data.mid(3,2));
const int a = data.len() < 10 ? year : atoi(data.right(4));
if (m >= 1 && m <= 12 && a > 2000 && g >= 1 && g <= date.last_day(m,a))
date = TDate(g,m,a);
return date;
}
//ELABORA_PASSAGGIO: questo metodo effettivamente genera il passaggio e lo salva nella tabella
void TGenera_planning_app::elabora_passaggio(const TDate& dadata, const TDate& adata,
const TISAM_recordset& pplan, TFast_isamfile& file_rplan)
{
//dati recuperati dalla tabella dei passaggi per contratto:
if (pplan.get(LVPASPLAN_NRIGA).as_int() <= 0)
return;
const long codcf = pplan.get(LVPASPLAN_CODCF).as_int(); //codice cliente
const long coditi = pplan.get(LVPASPLAN_CODITI).as_int(); //codice itinerario
const long codcont = pplan.get(LVPASPLAN_CODCONT).as_int(); //codice contratto
const bool flstag = pplan.get(LVPASPLAN_FLSTAG).as_bool(); //flag stagionalità
const int ggcons = pplan.get(LVPASPLAN_GGCONS).as_int(); //giorno di consegna (1 = lunedì -> 7 = domenica)
const int ordfer = pplan.get(LVPASPLAN_ORDFERM).as_int(); //ordine di fermata
const TString4 freq = pplan.get(LVPASPLAN_FREQ).as_string(); //codice della tabella di frequenze di consegna
char modpass = pplan.get(LVPASPLAN_MODPASS).as_string()[0]; //modalità di passaggio
//recupero la data di inizio e fine stagionalità
const TString16 strdastag = pplan.get(LVPASPLAN_DTSTAGIN).as_string();
const TString16 strastag = pplan.get(LVPASPLAN_DTSTAGSC).as_string();
//cache sulle testate dei contratti, selezionati per CODCF e CODCONT
TToken_string keycont;
keycont.add(codcf);
keycont.add(codcont);
const TRectype& contratto = cache().get(LF_LVCONDV,keycont);
//dati recuperati dalle testate dei contratti:
const TDate dadatacont = contratto.get_date(LVCONDV_DATAIN); //data di inizio validità del contratto
const TDate adatacont = contratto.get_date(LVCONDV_DATASC); //data di fine validità del contratto
// Controllo se il contratto e' valido nel periodo interessato
if (dadatacont > adata || (adatacont.ok() && adatacont < dadata))
return; // Inutile proseguire
//cache sulla tabella itinerari
TString4 keycoditi;
keycoditi.format("%03d", coditi);
const TRectype& iti = cache().get("&ITI",keycoditi);
//dati recuperati dalla tabella itinerari:
const TString8 codaut = iti.get("S1"); //codice autista
const TString8 codmez = iti.get("S2"); //codice mezzo
//recordset sulla tabella dei periodi di sospensione, limitati per CODCF e CODCONT
TISAM_recordset persosp("USE LVPERISOSP\nFROM CODCF=#CODCF CODCONT=#CODCONT\nTO CODCF=#CODCF CODCONT=#CODCONT");
persosp.set_var("#CODCF",codcf);
persosp.set_var("#CODCONT",codcont);
//cache sulla tabella frequenze consegne
const TRectype& frq = cache().get("&FRQ",freq);
//dati recuperati dalla tabella frequenze consegne:
const int maxcons = frq.get_int("I2"); //numero massimo di consegne per mese
TDate primogiorno = dadata; //primogiorno: primo giorno del periodo selezionato
if (frq.get_bool("B0")) //se devo usare "USA ULTIMA CONSEGNA", primogiorno diventa la data dell'ultima consegna
primogiorno = cerca_ultima_consegna(codcf,codcont,dadata);
const int ritardo = frq.get_int("I0"); //ritardo di consegna rispetto alla data di inizio calcolo
if (ritardo > 0) //evita anticipi senza senso
primogiorno += ritardo;
long frequenza = frq.get_long("I1"); //frequenza di consegna
if (frequenza <= 0 || frequenza > 28) //forza una frequenza valida
frequenza = 7;
TRectype& rplan = file_rplan.curr();
for (TDate d = primogiorno; d <= adata; d += frequenza)
{
arrotonda_al_giorno(d, ggcons);
if (d < dadata) // Puo' succedere se si utilizza la data di ultima consegna
continue;
if (d > adata) // Inutile generare date fuori range
break;
//controlla che d sia o meno in un periodo di validità contratto...
if (d < dadatacont)
continue; // Questa data non e' ancora in contratto: provo la prossima
if (adatacont.ok() && d > adatacont)
break; // Questa data e' oltre il contratto: inutile proseguire
//controlla se è una festività
if(lv_is_holiday(d))
continue;
//...e/o in un periodo distagionalità (se necessario)...
////la riga va elaborata se il flag di stagionalità è TRUE
////e se se la data del periodo di stagionalità è coerente
////e la data in considerazione è compresa tra le date della stagionalità
if (flstag && strdastag.full() && strastag.full())
{
const TDate dastag = str2date(strdastag,d.year());
const TDate astag = str2date(strastag,d.year());
if (!astag.ok() || d < dastag || d > astag)
continue;
}
//...e/o in un periodo di sospensione...
//...per cui c'è da verificare se è da saltare completamente o...
//...se c'è da elaborare ugualmente, ma con una modalità di passaggio differente
TString4 codpersosp;
for (bool ok = persosp.move_first(); ok; ok = persosp.move_next())
{
const TDate inisosp = persosp.get("DATAINI").as_date();
const TDate finsosp = persosp.get("DATAFIN").as_date();
if (d >= inisosp && d <= finsosp)
{
codpersosp = persosp.get("CODPER").as_string();
const TString& tpsosp = persosp.get("TPSOSP").as_string();
const TRectype& sosp = cache().get("&TSP",tpsosp);
modpass = sosp.get_char("S1");
break;
}
else
modpass = pplan.get(LVPASPLAN_MODPASS).as_string()[0]; //modalità di passaggio
}
//se la modalità di passaggio risulta vuota la riga non va elaborata
if (modpass <= ' ')
continue;
//se ho già raggiunto il numero massimo di consegne per mese...
//...la riga, anche se supera tutti i controlli, non va elaborata
if (maxcons > 0 && conta_consegne_mese(codcf, codcont, d, coditi) >= maxcons)
continue;
//scrivi la chiave
const long codplan = d.date2ansi(); //setta il codplan
int codriga = prossimo_codriga(d);
rplan.zero();
rplan.put(LVRCONSPLAN_CODPLAN, codplan); //setta il codplan
rplan.put(LVRCONSPLAN_CODRIGA, codriga); //setta il codriga
rplan.put(LVRCONSPLAN_DTCONS,d); //setta la data di consegna
rplan.put(LVRCONSPLAN_CODITI,coditi); //setta il codice itinerario
rplan.put(LVRCONSPLAN_ORDFER,ordfer); //setta l'ordine di fermata
rplan.put(LVRCONSPLAN_CODCF,codcf); //setta il codice cliente
rplan.put(LVRCONSPLAN_CODCONT,codcont); //setta il codice contratto
rplan.put(LVRCONSPLAN_GGCONS,ggcons); //setta il giorno di consegna
rplan.put(LVRCONSPLAN_MODPASS,modpass); //setta la modalità di passaggio
rplan.put(LVRCONSPLAN_CODAUT,codaut); //setta il codice autista
rplan.put(LVRCONSPLAN_CODMEZ,codmez); //setta il codice mezzo
rplan.put(LVRCONSPLAN_FREQ,freq); //setta la frequenza di consegna
rplan.put(LVRCONSPLAN_CONSSTD,true); //setta il flag di "consegna standard"
rplan.put(LVRCONSPLAN_PERSOSP,codpersosp); //setta il periodo di sospensione
lv_set_update_info(rplan); //setta utente, data e ora dell'ultimo aggiornamento
file_rplan.write();
}
}
//TRANSFER: questo metodo elimina i planning già esistenti in modo da evitare conflitti
//e chiama la funzione ELABORA_PASSAGGIO
bool TGenera_planning_app::transfer()
{
const TDate dadata = _msk->get(F_DADATA);
const TDate adata = _msk->get(F_ADATA);
TString query; query << "USE " << LF_LVPASPLAN;
//gli "if" sono stati commentati in data 11/02/2009 per evitare questo messaggio, in seguito alla segnalazione
//1162 in mantis; penso sia meglio commentarli perchè erano frutto di una segnalazione precedente, quindi
//li tengo in via precauzionale
//ATTENZIONE: prima esisteva la possibilità di riscrivere tutto completamente; adesso i giri che
//hanno avuto una modifica manuale rimangono sempre (possono essere cancelati solo a mano
//dalla gestione manuale dei giri)
if (_msk->get(F_CODCF).full())
{
const long codcf = _msk->get_long(F_CODCF);
/*if (yesno_box(TR("Si desidera cancellare i giri modificati manualmente?")))
elimina_planning_cliente(dadata,adata,codcf,true);
else*/
elimina_planning_cliente(dadata,adata,codcf,false);
query << " SELECT CODCF=" << codcf;
}
else if (_msk->get(F_CODITI).full())
{
const long coditi = _msk->get_long(F_CODITI);
TString4 itinerario;
itinerario.format("%03d",coditi);
/*if (yesno_box(TR("Si desidera cancellare i giri modificati manualmente?")))
elimina_planning_itinerario(dadata,adata,coditi,true);
else*/
elimina_planning_itinerario(dadata,adata,coditi,false);
query << " SELECT CODITI=" << itinerario;
}
else
/*if (yesno_box(TR("Si desidera cancellare i giri modificati manualmente?")))
elimina_planning(dadata,adata,true);
else*/
elimina_planning(dadata,adata,false);
_codriga.destroy(); //azzera il numero delle righe del planning già generati
TISAM_recordset pplan(query);
TProgress_monitor pi(pplan.items(), TR("Generazione giri in corso..."));
TFast_isamfile rplan(LF_LVRCONSPLAN);
for (bool ok = pplan.move_first(); ok; ok = pplan.move_next())
{
elabora_passaggio(dadata, adata, pplan, rplan);
if (!pi.add_status())
break;
}
return true;
}
void TGenera_planning_app::main_loop()
{
//generazione autonatica dei giri lanciata ogni volta che salvo i passaggi planning per contratto
if (argc() > 2)
{
TFilename ininame(argv(2));
ininame.ltrim(2);
if (ininame.exist())
{
TConfig ini(ininame, LF_LVRCONSPLAN);
const long codcf = ini.get_long("CODCF");
if (codcf > 0)
{
TLocalisamfile rplan(LF_LVRCONSPLAN);
rplan.last();
const TDate adata = rplan.curr().get_date(LVRCONSPLAN_DTCONS);
const TDate dadata(TODAY);
_msk->set(F_CODCF, codcf);
_msk->set(F_DADATA, dadata);
_msk->set(F_ADATA, adata);
transfer();
return;
}
}
}
while (_msk->run() == K_ENTER)
if (transfer())
message_box(TR("Generazione giri terminata"));
}
int lv2100(int argc, char* argv[])
{
TGenera_planning_app app;
app.run(argc, argv, TR("Generazione giri"));
return 0;
}