#include #include #include #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); 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) { TProgind pi(righe, TR("Eliminazione planning precedenti in corso..."), true, true); TLocalisamfile& rplan = selrighe.cursor()->file(); for (bool ok = selrighe.move_last(); ok; ok = selrighe.move_prev()) { if (!pi.addstatus(1)) break; rplan.remove(); } } 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 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); //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\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); return rplan.items(); } //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) { //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; TLocalisamfile file_rplan(LF_LVRCONSPLAN); 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; } } //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 = "USE 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); TProgind pi(pplan.items(), TR("Generazione giri in corso..."), true, true); for (bool ok = pplan.move_first(); ok; ok = pplan.move_next()) { if (!pi.addstatus(1)) break; elabora_passaggio(dadata, adata, pplan); } 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,"167"); 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 = TDate(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("La generazione dei giri è stata completata\nin modo corretto")); } int lv2100(int argc, char* argv[]) { TGenera_planning_app app; app.run(argc, argv, TR("Generazione giri")); return 0; }