#include #include #include #include #include #include #include "mrplib.h" #include "..\mg\mglib.h" #include "..\ve\velib.h" #include "..\ve\veconf.h" #include "mr2100.h" #include "mr2100a.h" /////////////////////////////////////////////////////////// // TMRP_record /////////////////////////////////////////////////////////// const real& TMRP_record::add_gross_req(const real & val) { _gross_requirement += val; return _gross_requirement; } const real& TMRP_record::add_sched_rec(const real & val) { _sched_receipts += val; return _sched_receipts; } const real& TMRP_record::add_net_req(const real & val) { _net_requirement += val; return _net_requirement; } TObject * TMRP_record::dup() const { TMRP_record *o= new TMRP_record(_time); o->TMRP_record::operator=(*this); return o; } TMRP_record & TMRP_record::operator=(const TMRP_record & a) { _gross_requirement=a._gross_requirement; _on_hand=a._on_hand; _sched_receipts=a._sched_receipts; _released_receipts=a._released_receipts; _net_requirement=a._net_requirement; _planned_orders=a._planned_orders; return *this; } TMRP_record::TMRP_record(const TMRP_time& t) : _time(t) { } /////////////////////////////////////////////////////////// // TMRP_line /////////////////////////////////////////////////////////// TArticolo_giacenza *TMRP_line::_articolo_giac=NULL; void TMRP_line::lotti_riordino(real & minimo, real & increm) const { _articolo_giac->read(_codart); const int i=_articolo_giac->find_mag("", _codmag); if (i>=0) { const TRectype & rec=_articolo_giac->mag("").row(i); minimo = rec.get_real(MAG_LOTTORIOR); increm = rec.get_real(MAG_LOTTOIRIOR); } else minimo=increm=0; if (minimo.is_zero()) minimo = _articolo_giac->get_real(MAG_LOTTORIOR); if (increm.is_zero()) increm = _articolo_giac->get_real(MAG_LOTTOIRIOR); } int TMRP_line::add_son(const real& qta, TMRP_line* son) { CHECK(son, "Can't add NULL TMRP_line son"); CHECK(son != this, "Hermafrodite TMRP_line"); _qta_sons.add(qta); return _sons.add(son); } bool TMRP_line::is_son(const TCodice_articolo& art) const { for (int i = sons()-1; i >= 0; i--) { const TMRP_line& r = son(i); if (r.codice() == art) break; } return i >= 0; } int TMRP_line::compare(const TSortable& s) const { const TMRP_line& line = (const TMRP_line&)s; const bool imfather = sons() > 0; const bool hesfather = line.sons() > 0; if (imfather && (!hesfather || is_son(line.codice()))) return +1; if (hesfather && (!imfather || line.is_son(codice()))) return -1; return 0; } TObject* TMRP_line::dup() const { TMRP_line * o=new TMRP_line(*this); return o; } TMRP_line & TMRP_line::operator=(const TMRP_line & a) { //key _codart=a._codart; _livgiac=a._livgiac; _codmag=a._codmag; _codimp=a._codimp; _codlin=a._codlin; _codcli=a._codcli; // contents _description=a._description; _sons=a._sons; _qta_sons=a._qta_sons; _req_per_bucket=a._req_per_bucket; _articolo_giac= new TArticolo_giacenza(a._articolo_giac->codice()); return *this; } TMRP_record& TMRP_line::record(int i) const { TMRP_record* rec = (TMRP_record*)_req_per_bucket.objptr(i); CHECKD(rec != NULL, "Invalid MRP record ", i); return *rec; } TMRP_record& TMRP_line::record(const TMRP_time& t) const { const int last = last_bucket(); for (int i = 0; i <= last; i++) { TMRP_record& rec = record(i); const int cmp = rec.time().compare(t); if (cmp == 0) return rec; if (cmp > 0) { ((TArray&)_req_per_bucket).insert(NULL, i); break; } } TMRP_record* rec = new TMRP_record(t); ((TArray&)_req_per_bucket).add(rec, i); return *rec; } // approssima in base al lotto minimo; prevede valori negativi real TMRP_line::sizeup_net_requirement(int i, const real &val) { real req; // Azzera net requirement if (val < ZERO) { req = -val; // Rende positiva la richiesta effettiva real lm,li; lotti_riordino(lm,li); int cazzo; if (!lm.is_zero() && !li.is_zero() ) cazzo=1; if (req > lm) // Se la richiesta supera il lotto minimo { if (li > ZERO) { // Calcola il numero di lotti incrementali real lotti = (req - lm) / li; lotti.ceil(); // La quantita' richiesta = minimo + lotti * dim.lotti req = lm + lotti * li; } } else req = lm; // Forza la richiesta almeno al lotto minimo } set_net_req(i, req); return req; } const real& TMRP_line::add_gross_req(const TMRP_time& t, const real& val) { return record(t).add_gross_req(val); } const real& TMRP_line::add_sched_rec(const TMRP_time& t, const real &val) { return record(t).add_sched_rec(val);} const real & TMRP_line::set_net_req(int i, const real &val) { if (val>ZERO) return record(i).set_net_req(val); else return record(i).set_net_req(ZERO); } const real & TMRP_line::add_net_req(const TMRP_time &t, const real &val) { return record(t).add_net_req(val); } const real & TMRP_line::set_on_hand(int i, const real &val) { return record(i).set_on_hand(val);} const real & TMRP_line::set_on_hand(const TMRP_time &t, const real &val) { return record(t).set_on_hand(val);} const TMRP_time& TMRP_line::lead_time(int i, TMRP_time& t) const { t = record(i).time(); TLocalisamfile dist(LF_DIST); dist.put("CODDIST", codice()); if (dist.read() == NOERR) { int days = dist.get_int("LEADTIME"); long hours = dist.get_long("LEADHOURS"); t.sub_time(days, hours); } else { TLocalisamfile anamag(LF_ANAMAG); anamag.put(ANAMAG_CODART, codice()); if (anamag.read() == NOERR) { int days = anamag.get_int("LEADTIME"); t.sub_time(days); } } return t; } real &TMRP_line::giacenza_attuale(real &g) const { TDate d(TODAY); return giacenza_attuale(g, d); } real &TMRP_line::giacenza_attuale(real &g, const TDate &d) const { _articolo_giac->read(codice()); g = _articolo_giac->giacenza_anno(codmag(),livgiac(),d.year()); return g; } real &TMRP_line::scorta_minima(real &g) const { TDate d(TODAY); return scorta_minima(g,d); } real &TMRP_line::scorta_minima(real &g, const TDate &d) const { _articolo_giac->read(codice()); g = _articolo_giac->scorta_minima(codmag(),livgiac(),d.year()); return g; } TMRP_line::TMRP_line(const char* art, const char* giac, const char* mag, const char* imp, const char* lin, long codcli) : _codart(art), _livgiac(giac), _codmag(mag), _codimp(imp), _codlin(lin), _codcli(codcli) { if (_articolo_giac==NULL) _articolo_giac= new TArticolo_giacenza(); } TMRP_line::TMRP_line(const TMRP_line&a) { TMRP_line::operator=(a); } /////////////////////////////////////////////////////////// // TMRP_lines /////////////////////////////////////////////////////////// TSortable* TMRP_lines::new_obj(const TToken_string& key) const { TCodice_articolo art; key.get(0, art); art.trim(); TString80 gia; key.get(1, gia); gia.trim(); TString16 mag; key.get(2, mag); mag.trim(); TString16 imp; key.get(3, imp); imp.trim(); TString16 lin; key.get(4, lin); lin.trim(); TString16 clifor ; key.get(5, clifor); return new TMRP_line(art, gia, mag, imp, lin, atol(clifor)); } TMRP_lines::TMRP_lines(const TMRP_lines & a) { TMRP_lines::operator=(a); } TObject* TMRP_lines::dup() const { return new TMRP_lines(*this);; } TMRP_lines & TMRP_lines::operator= (const TMRP_lines &a) { TMRP_array::operator= (a); _ignore_mag=a._ignore_mag; _ignore_imp=a._ignore_imp; _ignore_lin=a._ignore_lin; return *this; } TMRP_line* TMRP_lines::find(const TCodice_articolo& codart, const TString& gia, const TString& mag, const TString& imp, const TString& lin, long codcli, bool create) { _key = codart; _key.add(gia); if (_ignore_mag) _key.add(" ",2); else _key.add(mag,2); if (_ignore_imp) _key.add(" ",3); else _key.add(imp,3); if (_ignore_lin) _key.add(" ",4); else _key.add(lin,4); _key.add(codcli,5); TSortable* s = create ? add_obj(_key) : find_obj(_key); return (TMRP_line*)s; } TMRP_lines::TMRP_lines() : _ignore_mag(FALSE), _ignore_imp(FALSE), _ignore_lin(FALSE) { } TMRP_lines::~TMRP_lines() { destroy(); } /////////////////////////////////////////////////////////// // TRiga_ordine /////////////////////////////////////////////////////////// #define SORT_COMPLETE 13 // #define SORT_BY_DFA -1 // data documento + data di consegna + fornitore + articolo #define SORT_BY_DAF -2 // data documento + data di consegna + articolo + fornitore #define SORT_BY_FAD -3 // #define SORT_BY_FDA -4 // #define SORT_BY_AFD -5 // #define SORT_BY_ADF -6 // class TRiga_ordine : public TToken_string { public: int compare(const TToken_string& r, int level = SORT_COMPLETE) const; int compare_field(TString &str0, TString &str1,short field_no) const; TRiga_ordine& operator=(TToken_string& r); TRiga_ordine& operator+=(TRiga_ordine& r); TRiga_ordine() : TToken_string(128) { } TRiga_ordine(const TDate& datadoc, long forn, const TMRP_line& line, int bucket, TCodgiac_livelli& livelli, const real & price); virtual ~TRiga_ordine() { } }; int TRiga_ordine::compare(const TToken_string& riga, int level) const { TString16 str0, str1; int cmp = 0; if (level>=0) { for (int i = 2; i <= level && cmp == 0; i++) { get(i, str0); riga.get(i, str1); cmp=compare_field(str0,str1,short(i+FIRST_FIELD)); } } else { // ordinamenti non standard short f; short fields_DFA[] = {F_DATADOC, F_DATACONS, F_ORD_TYPE, F_FORNITORE, F_ARTICOLO}; short fields_DAF[] = {F_DATADOC, F_DATACONS, F_ARTICOLO, F_ORD_TYPE, F_FORNITORE}; short fields_AFD[] = {F_ARTICOLO, F_ORD_TYPE, F_FORNITORE, F_DATADOC, F_DATACONS}; short fields_ADF[] = {F_ARTICOLO, F_ORD_TYPE, F_DATADOC, F_DATACONS, F_FORNITORE}; short fields_FAD[] = {F_ORD_TYPE, F_FORNITORE, F_ARTICOLO, F_DATADOC, F_DATACONS}; short fields_FDA[] = {F_ORD_TYPE, F_FORNITORE, F_DATADOC, F_DATACONS, F_ARTICOLO}; for (int i = 0; i < 5 && cmp == 0; i++) { switch (level) { case SORT_BY_DFA: f=fields_DFA[i]; break; case SORT_BY_DAF: f=fields_DAF[i]; break; case SORT_BY_FAD: f=fields_FAD[i]; break; case SORT_BY_FDA: f=fields_FDA[i]; break; case SORT_BY_ADF: f=fields_ADF[i]; break; case SORT_BY_AFD: f=fields_AFD[i]; break; default: NFCHECK("Ordinamento sconosciuto"); break; } get(f-FIRST_FIELD, str0); riga.get(f-FIRST_FIELD, str1); cmp=compare_field(str0,str1,f); } } return cmp; } int TRiga_ordine::compare_field(TString &str0, TString &str1,short field_no) const { int cmp; switch (field_no) { case F_DATADOC: case F_DATACONS: { if (str0 != str1) { const TDate mine(str0); const TDate his(str1); cmp = mine > his ? +1 : -1; } else cmp = 0; } break; case F_FORNITORE: // data doc { const long f0 = atol(str0); const long f1 = atol(str1); cmp = f0 == f1 ? 0 : (f0 > f1 ? +1 : -1); } break; default: cmp = str0.compare(str1); break; } return cmp; } TRiga_ordine& TRiga_ordine::operator=(TToken_string& r) { this->TToken_string::operator=(r); return *this; } TRiga_ordine& TRiga_ordine::operator+=(TRiga_ordine& r) { CHECK(compare(r) == 0, "Can't add incompatible order line"); real qta = get(13); qta += real(r.get(13)); add(qta.string(), 13); return *this; } TRiga_ordine::TRiga_ordine(const TDate& datadoc, long forn, const TMRP_line& line, int bucket, TCodgiac_livelli& lg, const real & price) : TToken_string(128) { add(" ",F_SELECTED-FIRST_FIELD); add("F",F_ORD_TYPE-FIRST_FIELD); add(datadoc.string(),F_DATADOC-FIRST_FIELD); const TMRP_time& time = line.time(bucket); add(time.date().string(),F_DATACONS-FIRST_FIELD); add(forn,F_FORNITORE-FIRST_FIELD); add(line.codice(),F_ARTICOLO-FIRST_FIELD); const TString& liv = line.livgiac(); for (int l = 1; l <= 4; l++) { if (lg.enabled(l)) add(liv.mid(lg.code_start(l), lg.code_length(l)),F_LIV1+l-1-FIRST_FIELD); else add(" ",F_LIV1+l-1-FIRST_FIELD); } add(line.codmag().left(3),F_MAGAZZINO-FIRST_FIELD); add(line.codmag().mid(3),F_DEPOSITO-FIRST_FIELD); add(line.codimp(),F_CODIMP-FIRST_FIELD); add(line.codlin(),F_CODLIN-FIRST_FIELD); add(line.net_requirement(bucket).string(),F_QUANTITA-FIRST_FIELD); const TCodice_um um; const TQuantita qta(line.codice(), um, ZERO); add(qta.um(),F_UM-FIRST_FIELD); add(price.string(),F_PREZZO-FIRST_FIELD); add(line.description(),F_DESCART-FIRST_FIELD); } /////////////////////////////////////////////////////////// // TLav_finder Trova la prima lavorazione di un articolo /////////////////////////////////////////////////////////// class TLav_finder : public TAssoc_array { TDecoder _lnp; bool _keep_imp; public: void init(bool ki = TRUE); const TString& lin2imp(const TString& lin); TLavorazione& art2lav(const TCodice_articolo& art); void art2magimpline(const TCodice_articolo& art, TString& mag, TString& imp, TString& lin); TLav_finder(); virtual ~TLav_finder() { } }; void TLav_finder::init(bool ki) { _keep_imp = ki; TAssoc_array::destroy(); _lnp.destroy(); } TLavorazione& TLav_finder::art2lav(const TCodice_articolo& art) { TLavorazione* lav = (TLavorazione*)objptr(art); if (lav == NULL) { TDistinta_tree tree; TArray boom; tree.set_root(art); tree.explode(boom, FALSE, RAGGR_EXP_UMBASE, 1, "L"); TString16 codlav; if (boom.items() > 0) codlav = ((TRiga_esplosione&)boom[0]).articolo(); lav = new TLavorazione(codlav); add(art, lav); } return *lav; } void TLav_finder::art2magimpline(const TCodice_articolo& art, TString& mag, TString& imp, TString& lin) { TLavorazione& lav = art2lav(art); if (imp.blank() && !lin.blank()) imp = _lnp.decode(lin); lin.cut(0); const int ll = _keep_imp ? lav.linee() : lav.linee_standard(); for (int l = 0; l < ll; l++) { const TString& linea = lav.cod_linea(l); const bool goodimp = imp.empty() || _lnp.decode(linea) == imp; if (l == 0 || goodimp) { lin = linea; lin.trim(); } if (goodimp) break; } if (lin.not_empty()) { const TLinea_prod lnp(lin) ; imp = lnp.codimp(); mag = lnp.codmag(); } else { if (!_keep_imp) imp = mag = ""; } } TLav_finder::TLav_finder() : _lnp("LNP", "S6"), _keep_imp(TRUE) { } /////////////////////////////////////////////////////////// // TMatResPlanning /////////////////////////////////////////////////////////// class TMatResMask; class TMatResPlanning : public TSkeleton_application { TMRP_lines _articles; TLav_finder _artinfo; private: bool gross2net_logic(TMRP_line &curr_article, int bucket,bool lotsizing_f, bool lotsizing_p); bool build_gross_requirements(const TMatResMask& m); bool build_sched_receipts(const TMatResMask& m); bool explode_articles(const TMatResMask& m); bool test_codnum(const TCodice_numerazione& num, TString_array& a) const; bool test_status(const TRectype& doc, TString_array& a) const; protected: bool preprocess_cycle(const TMatResMask& m); // req iniziale dai docs bool net_requirement_cycle(const TMatResMask& m); bool build_orders(TMatResMask& m); public: virtual void main_loop(); virtual bool firm_change_enabled() const { return FALSE; } void compute(TMatResMask& m); bool emit_orders(TMatResMask& m); }; TMatResPlanning& app() { return (TMatResPlanning&)main_app(); } /////////////////////////////////////////////////////////// // TMatResMask /////////////////////////////////////////////////////////// class TMatResMask : public TCalendar_mask { TCodgiac_livelli _livelli; TCondizione_vendita *_condv; void clear_sheets(); protected: void round_field(TMask_field& fld, bool up) const; virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly); int find_pos(const TRiga_ordine& r, int& cmp) const; void sort_orders(); bool orders_selected(char type); void select_orders(char type); public: TCodgiac_livelli & livelli_giacenza() {return _livelli;} int round_date(TDate& date, bool up = FALSE) const; int add_order_line(const TDate& data, long forn, const TMRP_line& l, int bucket); TMatResMask(); virtual ~TMatResMask() { } }; int TMatResMask::round_date(TDate& date, bool up) const { // Dimensione del bucke in giorni int bucket_size = get_int(F_BUCKET) * 7; if (bucket_size < 7) bucket_size = 7; // Riporta la data al primo lunedi prima dell'inizio TDate inizio = get(F_DADATA); const int wday = inizio.wday(); if (wday > 1) inizio -= wday-1; // Calcola il bucket di appartenenza const int days = int(date - inizio); const int bucket = days / bucket_size; if (up) // Arrotonda alla fine del bucket date = inizio + long((bucket+1) * bucket_size - 1); else // Arrotonda all'inizio del bucket date = inizio + long(bucket * bucket_size); return bucket; } void TMatResMask::round_field(TMask_field& fld, bool up) const { TDate date = fld.get(); if (date.ok()) { round_date(date, up); fld.set(date); } } bool TMatResMask::on_field_event(TOperable_field& o, TField_event e, long jolly) { const char * confirm_msg="Le attuali %d righe di ordine verranno perse. Confermi ?"; switch (o.dlg()) { case F_DADATA: if (e == fe_modify) round_field(o, FALSE); break; case F_ADATA: if (e == fe_modify) round_field(o, TRUE); break; case F_BUCKET: if (e == fe_modify) { round_field(field(F_DADATA), FALSE); round_field(field(F_ADATA), TRUE); } break; case F_NUM_ORC: case F_TIPI_ORC: case F_NUM_ORF: case F_TIPI_ORF: if (e == fe_init) { TSheet_field& s = (TSheet_field&)o; if (s.items() == 0) { s.row(0); s.force_update(); } } if (e == fe_close) { const TSheet_field& s = (const TSheet_field&)o; FOR_EACH_SHEET_ROW_BACK(s, r, row) if (!row->empty_items()) return TRUE; return error_box("E' necessario inserire almeno una riga"); } break; case F_ORDINI: { TSheet_field& s = (TSheet_field&)o; switch(e) { case se_query_add: return FALSE; default: break; } } break; case F_YEAR: case F_IMPIANTO: case F_LINEA: if (e == fe_modify || (o.dlg() == F_YEAR && e == fe_init )) update_calendar(F_CALENDAR, F_YEAR, F_IMPIANTO, F_LINEA); break; case DLG_CANCEL: if (e == fe_button) if (jolly == 0L) { TSheet_field& s = sfield(F_ORDINI); const int it=s.items(); if (it==0 || yesno_box(confirm_msg, it)) { enable(-1); s.destroy(); s.force_update(); } else return FALSE; } break; case DLG_ELABORA: if (e == fe_button && check_fields()) { TSheet_field& s = sfield(F_ORDINI); const int it=s.items(); if (it==0 || yesno_box(confirm_msg, it)) { s.destroy(); app().compute(*this); enable(DLG_SAVEREC, s.items() > 0); enable(-1, s.items() == 0); sort_orders(); s.set_focus(); } } break; case F_SORT_ORDINI: if (e == fe_modify) sort_orders(); break; case F_SELECT_ORDF: if (e == fe_button) select_orders('F'); break; case F_SELECT_ORDP: if (e == fe_button) select_orders('P'); break; case DLG_SAVEREC: if (e == fe_button && check_fields()) { if (!app().emit_orders(*this)) message_box("Nessun ordine generato"); } break; case DLG_PROFILE: if (e == fe_modify) { clear_sheets(); enable(-1); } break; default: break; }; return TRUE; } void TMatResMask::clear_sheets() { TSheet_field &sa=sfield(F_ORDINI); if (sa.items()>0) { sa.destroy(); sa.force_update(); } } // Per ora scansione lineare, in futuro ricerca binaria int TMatResMask::find_pos(const TRiga_ordine& r, int& cmp) const { TSheet_field& s = sfield(F_ORDINI); TString_array& a = s.rows_array(); cmp = +1; for (int i = 0; i < a.items(); i++) { const TRiga_ordine& riga = (const TRiga_ordine&)a[i]; cmp = r.compare(riga); if (cmp <= 0) break; } return i; } int TMatResMask::add_order_line(const TDate& datadoc, long forn, const TMRP_line& line, int bucket) { TRiga_ordine* r = new TRiga_ordine(datadoc, forn, line, bucket, _livelli, ZERO); int cmp; const int pos = find_pos(*r, cmp); TSheet_field& s = sfield(F_ORDINI); TString_array& a = s.rows_array(); if (cmp == 0) { TRiga_ordine& riga = (TRiga_ordine&)a[pos]; riga += *r; delete r; } else { if (1 || cmp > 0) a.add(r); else a.insert(r, pos); } return pos; } static int order_compareDFA(TSheet_field &s,int i1,int i2) { const TRiga_ordine& r1 = (TRiga_ordine&)s.row(i1); const TRiga_ordine& r2 = (TRiga_ordine&)s.row(i2); return r1.compare(r2,SORT_BY_DFA); } static int order_compareDAF(TSheet_field &s,int i1,int i2) { const TRiga_ordine& r1 = (TRiga_ordine&)s.row(i1); const TRiga_ordine& r2 = (TRiga_ordine&)s.row(i2); return r1.compare(r2,SORT_BY_DAF); } static int order_compareAFD(TSheet_field &s,int i1,int i2) { const TRiga_ordine& r1 = (TRiga_ordine&)s.row(i1); const TRiga_ordine& r2 = (TRiga_ordine&)s.row(i2); return r1.compare(r2,SORT_BY_AFD); } static int order_compareADF(TSheet_field &s,int i1,int i2) { const TRiga_ordine& r1 = (TRiga_ordine&)s.row(i1); const TRiga_ordine& r2 = (TRiga_ordine&)s.row(i2); return r1.compare(r2,SORT_BY_ADF); } static int order_compareFAD(TSheet_field &s,int i1,int i2) { const TRiga_ordine& r1 = (TRiga_ordine&)s.row(i1); const TRiga_ordine& r2 = (TRiga_ordine&)s.row(i2); return r1.compare(r2,SORT_BY_FAD); } static int order_compareFDA(TSheet_field &s,int i1,int i2) { const TRiga_ordine& r1 = (TRiga_ordine&)s.row(i1); const TRiga_ordine& r2 = (TRiga_ordine&)s.row(i2); int cmp=r1.compare(r2,SORT_BY_FDA); return cmp; } void TMatResMask::sort_orders() { TSheet_field& s = sfield(F_ORDINI); TSheet_field& a = s; switch (-get_int(F_SORT_ORDINI)) { case SORT_BY_DAF: a.sort(order_compareDAF); break; case SORT_BY_FAD: a.sort(order_compareFAD); break; case SORT_BY_FDA: a.sort(order_compareFDA); break; case SORT_BY_AFD: a.sort(order_compareAFD); break; case SORT_BY_ADF: a.sort(order_compareADF); break; case SORT_BY_DFA: default: a.sort(order_compareDFA); break; } s.force_update(); } bool TMatResMask::orders_selected(char type) { TSheet_field& s = sfield(F_ORDINI); const int it=s.items(); for (int r=0; r< it; r++) { if ( *s.cell(r, s.cid2index(F_ORD_TYPE))==type && *s.cell(r, s.cid2index(F_SELECTED))=='X') return TRUE; } return FALSE; } void TMatResMask::select_orders(char type) { TSheet_field& s = sfield(F_ORDINI); bool on=!orders_selected(type); const int it=s.items(); for (int r=0; r< it; r++) { if ( *s.cell(r, s.cid2index(F_ORD_TYPE))==type ) { s.row(r).add(on ? "X" : " ", s.cid2index(F_SELECTED)); s.force_update(r); } } TString msg=format("Ordini %s %s",type=='F' ? "fornitore" : "di produzione" ,on ? "selezionati" :" de-selezionati"); xvt_statbar_set(msg); } TMatResMask::TMatResMask() : TCalendar_mask("mr2100a") { _condv = NULL; TSheet_field& sf = sfield(F_ORDINI); _livelli.set_sheet_columns(sf, F_LIV1); TConfig ini(CONFIG_DITTA, "mg"); if (!ini.get_bool("GESDEPOSITI", "mg")) { sf.delete_column(F_DEPOSITO); sf.sheet_mask().hide(F_DEPOSITO); } if (!ini.get_bool("GESTIMPIANTI", "mr")) { sf.delete_column(F_CODIMP); // Elimina colonna impianto sf.sheet_mask().hide(F_CODIMP); sf.sheet_mask().hide(F_DESCIMP); disable(F_NOIMP); // Forza l'ignoramento degli impianti set(F_NOIMP, "X"); } } /////////////////////////////////////////////////////////// // TMatResPlanning /////////////////////////////////////////////////////////// bool TMatResPlanning::test_codnum(const TCodice_numerazione& num, TString_array& a) const { bool yes = a.find(num.codice()) >= 0; return yes; } bool TMatResPlanning::test_status(const TRectype& doc, TString_array& a) const { const TString16 tipodoc = doc.get(DOC_TIPODOC); const int statodoc = doc.get_int(DOC_STATO); bool yes = FALSE; for (int i = a.items()-1; i >= 0 && !yes; i--) { TToken_string& riga = a.row(i); const char* t = riga.get(0); if (tipodoc == t) { const int state_fr = riga.get_int(F_DASTATO - FIRST_FIELD); const int state_to = riga.get_int(F_ASTATO - FIRST_FIELD); yes = statodoc >= state_fr && statodoc <= state_to; } } return yes; } static long table_items(const char* tab) { TRelation tabrel(tab); TCursor tabcur (&tabrel); long tot = tabcur.items(); return tot; } /////////// finished: 100% /////////// tested : 100% bool TMatResPlanning::build_gross_requirements(const TMatResMask& m) { TDate date_fr = m.get(F_DADATA); const int bucket_fr = m.round_date(date_fr, FALSE); TDate date_to = m.get(F_ADATA); const int bucket_to = m.round_date(date_to, TRUE); const int year_fr = date_fr.year(); const int year_to = date_to.year(); const bool master = m.get(F_ORC_MASTER)[0] == 'M'; // azzera l'array _requirements const bool nomag = m.get_bool(F_NOMAG); const bool noimp = m.get_bool(F_NOIMP); const bool nolin = m.get_bool(F_NOLIN); _articles.ignore(nomag, noimp, nolin); _articles.destroy(); TTable num("%NUM"); TCodice_numerazione cod; TRelation rel(LF_DOC); TCursor cur(&rel); const TRectype& curr_doc = cur.curr(); TRectype filter_fr(curr_doc), filter_to(curr_doc); TString_array& n = m.sfield(F_NUM_ORC).rows_array(); TString_array& a = m.sfield(F_TIPI_ORC).rows_array(); TProgind pi(table_items("%NUM"), "Fase 1: caricamento fabbisogni lordi...", TRUE, TRUE); // Scandisce tutte le numerazioni considerando solo quelle // contenenti i tipi documento specificati nella maschera for (int err = cod.first(num); err == NOERR; err = cod.next(num)) { pi.addstatus(1); if (pi.iscancelled()) return FALSE; if (test_codnum(cod, n)) { // Filtra il cursore in modo da limitarlo alla numerazione // corrente ed agli anni specificati dalle due date limite filter_fr.put(DOC_PROVV, "D"); filter_fr.put(DOC_ANNO, year_fr); filter_fr.put(DOC_CODNUM, cod.get("CODTAB")); filter_to.put(DOC_PROVV, "D"); filter_to.put(DOC_ANNO, year_to); filter_to.put(DOC_CODNUM, cod.get("CODTAB")); cur.setregion(filter_fr, filter_to); TString cfilter; cfilter << DOC_CODNUM << "==\"" << cod.get("CODTAB") << '"'; cur.setfilter(cfilter); const long items = cur.items(); cur.freeze(TRUE); // Scandisce i documenti inevasi e considera solo // quelli con uno stato nel range corretto for (cur = 0; cur.pos() < items; ++cur) { const bool evaso = curr_doc.get_bool(DOC_DOCEVASO); if (evaso) continue; TDate doc_cons = curr_doc.get(DOC_DATACONS); if (!doc_cons.ok()) doc_cons = curr_doc.get(DOC_DATADOC); if (test_status(curr_doc, a)) { // Scandisce le righe articolo e memorizza // le quantita' richieste TDocumento doc(cur.curr()); for (int r = doc.physical_rows(); r > 0; r--) { // Seleziona le righe articolo non ancora evase const TRiga_documento& riga = doc[r]; TDate datacons = riga.get(RDOC_DATACONS); if (!datacons.ok()) datacons = doc_cons; // Data consegna troppo avanti if (datacons > date_to) continue; if (riga.is_articolo()) { const real qta = riga.qtaresidua(); if (qta > ZERO) { const TCodice_articolo art = riga.get(RDOC_CODARTMAG); const TString16 liv = riga.get(RDOC_LIVELLO); const TString16 mag = nomag ? EMPTY_STRING : riga.get(RDOC_CODMAG); const TString16 imp = noimp ? EMPTY_STRING : riga.get(RDOC_IMPIANTO); const TString16 lin = nolin ? EMPTY_STRING : riga.get(RDOC_LINEA); const TCodice_um um = riga.get(RDOC_UMQTA); TQuantita q(art, um, qta); q.convert2umbase(); TMRP_line* line = _articles.find(art, liv, mag, imp, lin, 0L); if (line == NULL) { // nuova linea line = _articles.find(art, liv, mag, imp, lin, 0L, TRUE); line->set_description(riga.get(RDOC_DESCR)); } const TMRP_time t(datacons, 0, imp, lin); line->add_gross_req(t, q.val()); if (master) line->add_net_req(t, q.val()); } } } } } cur.freeze(FALSE); } } return _articles.items() > 0L; } /////////// finished: 100% /////////// tested : 100% bool TMatResPlanning::explode_articles(const TMatResMask& m) { TDistinta_tree distinta; // albero distinta TArray boom; // array per i figli int level = 1; TProgind* pi = NULL; // Inizializza la cache delle lavorazioni _artinfo.init(m.get_bool(F_KEEP_IMP)); // Scandisce gli articoli inseriti dal gross requirements ed // accoda tutti gli articoli risultanti dalla loro esplosione for (long a = 0; a < _articles.items(); a++) { if (pi && pi->isfinished()) { delete pi; pi = NULL; } if (pi == NULL) { TString80 msg; msg.format("Fase 2: esplosione articoli (livello %d)", level++); pi = new TProgind(_articles.items()-a, msg, TRUE, TRUE); } pi->addstatus(1); if (pi->iscancelled()) return FALSE; TMRP_line& line = _articles[a]; if (distinta.set_root(line.codice())) { distinta.set_global("_DISTINTA", line.codice()); distinta.set_global("_LIVELLO", line.livgiac()); distinta.set_global("_MAGAZZINO", line.codmag()); distinta.set_global("_IMPIANTO", line.codimp()); distinta.set_global("_LINEA", line.codlin()); distinta.explode(boom, FALSE, RAGGR_EXP_UMBASE, 1, "AV"); for (int i = 0; i < boom.items(); i++) { const TRiga_esplosione& riga = (const TRiga_esplosione&)boom[i]; const TCodice_articolo& art = riga.articolo(); const TRectype& articolo=cache().get(LF_ANAMAG,art); bool add=TRUE; if (!articolo.get(ANAMAG_CODART).blank()) { const char reorder_type=articolo.get_char(ANAMAG_RIORDINO); if (reorder_type!='F' && reorder_type!=' ' && reorder_type!='\0') // e' a riordino add=FALSE; } if (add) { // articolo gestito dall'MRP TString16 mag = line.codmag(); TString16 imp = line.codimp(); TString16 lin = line.codlin(); _artinfo.art2magimpline(art, mag, imp, lin); TMRP_line* son = _articles.find(art, riga.giacenza(),mag, imp, lin, 0L); if (son == NULL) { son = _articles.find(art, riga.giacenza(), mag, imp, lin, 0L, TRUE); son->set_description(distinta.describe(art)); } line.add_son(riga.val(), son); } } } } if (pi != NULL) delete pi; return _articles.items() > 0; } /////////// finished: 100% /////////// tested: bool TMatResPlanning ::build_sched_receipts(const TMatResMask& m) { TDate date_fr = m.get(F_DADATA); const int bucket_fr = m.round_date(date_fr, FALSE); TDate date_to = m.get(F_ADATA); const int bucket_to = m.round_date(date_to, TRUE); const int year_fr = date_fr.year(); const int year_to = date_to.year(); TTable num("%NUM"); TCodice_numerazione cod; TRelation rel(LF_DOC); TCursor cur(&rel); const TRectype& curr = cur.curr(); TRectype filter_fr(curr), filter_to(curr); TString_array& n = m.sfield(F_NUM_ORF).rows_array(); TString_array& a = m.sfield(F_TIPI_ORF).rows_array(); TProgind pi(table_items("%NUM"), "Fase 3: caricamento arrivi futuri...", TRUE, TRUE); // Scandisce tutte le numerazioni considerando solo quelle // contenenti i tipi documento specificati nella maschera for (int err = cod.first(num); err == NOERR; err = cod.next(num)) { pi.addstatus(1); if (pi.iscancelled()) return FALSE; if (test_codnum(cod, n)) { // Filtra il cursore in modo da limitarlo alla numerazione // corrente ed agli anni specificati dalle due date limite filter_fr.put(DOC_PROVV, "D"); filter_fr.put(DOC_CODNUM, cod.get("CODTAB")); filter_fr.put(DOC_ANNO, year_fr); filter_to.put(DOC_PROVV, "D"); filter_to.put(DOC_CODNUM, cod.get("CODTAB")); filter_to.put(DOC_ANNO, year_to); cur.setregion(filter_fr, filter_to); TString cfilter; cfilter << DOC_CODNUM << "==" << '"' << cod.get("CODTAB") << '"' ; cur.setfilter(cfilter); const long items = cur.items(); cur.freeze(TRUE); // Scandisce i documenti inevasi e considera solo // quelli con uno stato nel range corretto for (cur = 0; cur.pos() < items; ++cur) { const bool evaso = curr.get_bool(DOC_DOCEVASO); if (evaso) continue; if (!test_status(curr, a)) continue; // Data di consegna standard TDate doc_cons = curr.get(DOC_DATACONS); if (!doc_cons.ok()) doc_cons = curr.get(DOC_DATADOC); // Scandisce le righe articolo e memorizza // le quantita' richieste TDocumento doc(cur.curr()); for (int r = doc.physical_rows(); r > 0; r--) { // Seleziona le righe articolo non ancora evase const TRiga_documento& riga = doc[r]; TDate consegna = riga.get(RDOC_DATACONS); if (!consegna.ok()) consegna = doc_cons; // Data consegna troppo avanti if (consegna > date_to) continue; if (riga.is_articolo()) { const real qta = riga.qtaresidua(); if (qta > ZERO) { const TCodice_articolo art = riga.get(RDOC_CODARTMAG); const TString16 liv = riga.get(RDOC_LIVELLO); const TString16 mag = riga.get(RDOC_CODMAG); const TString16 imp = riga.get(RDOC_IMPIANTO); const TString16 lin = riga.get(RDOC_LINEA); TMRP_line* line = _articles.find(art, liv, mag, imp, lin, 0L, FALSE); if (line != NULL) { const TCodice_um um = riga.get(RDOC_UMQTA); TQuantita q(art, um, qta); q.convert2umbase(); const TMRP_time t(consegna, 0, imp, lin); line->add_sched_rec(t, q.val()); } } } } } cur.freeze(FALSE); } } return _articles.items() > 0L; } /////////// finished: 99% /////////// tested: 99% bool TMatResPlanning::preprocess_cycle(const TMatResMask& m) { // costruisce il gross requirement dai DOC di planning if (build_gross_requirements(m)) // esplode l'array mediante la DIBA if (explode_articles(m)) // costruisce gli sched rec degli elementi dai DOCS ordini fornitore emessi return build_sched_receipts(m); return FALSE; } /////////// finished: 99% /////////// tested: // implementa la logica che porta dal fabbisogno lordo a quello netto // per quel bucket e riporta il balance on hand sul bucket successivo bool TMatResPlanning::gross2net_logic(TMRP_line &curr_article, int bucket, bool lotsizing_f, bool lotsizing_p) { // Verifico se esiste gia' un fabbisogno netto real sm,tmpreal = -curr_article.net_requirement(bucket); if (tmpreal >= ZERO) // Devo calcolare il fabbisogno netto { tmpreal = curr_article.on_hand(bucket); tmpreal -= curr_article.gross_requirement(bucket); curr_article.scorta_minima(sm, curr_article.time(bucket).date()); tmpreal -= sm; } tmpreal += curr_article.sched_receipts(bucket); // adegua il quantitativo del fabbisogno netto in base al lot-sizing, se il valore risulta negativo const int n_figli = curr_article.sons(); real net_req; if ((lotsizing_p && n_figli!=0 )||(lotsizing_f && n_figli==0) ) net_req = curr_article.sizeup_net_requirement(bucket, tmpreal); else net_req = curr_article.set_net_req(bucket, -tmpreal); // calcola la giacenza del bucket successivo tmpreal += net_req; if (tmpreal > ZERO) { const int nb = curr_article.next_bucket(bucket); if (nb <= curr_article.last_bucket()) curr_article.set_on_hand(nb, tmpreal); } // trasforma i fabbisogni totali nel fabbisogno lordo dei figli net_req += curr_article.sched_receipts(bucket); if (net_req > ZERO) { TMRP_time lead_time; curr_article.lead_time(bucket, lead_time); for (int o = 0; o < n_figli; o++) { TMRP_line& article_son = curr_article.son(o); tmpreal = net_req * curr_article.qta_son(o); if (tmpreal > ZERO) article_son.add_gross_req(lead_time, tmpreal); } } return TRUE; } /////////// finished: 99% /////////// tested: bool TMatResPlanning::net_requirement_cycle(const TMatResMask& m) { bool ok = TRUE; bool lotsizing_p=m.get_bool(F_LOTSIZING_P); bool lotsizing_f=m.get_bool(F_LOTSIZING_F); // ordina gli articoli const long total = _articles.sort(); TProgind pi(total, "Fase 4: calcolo dei fabbisogni netti", FALSE, TRUE); // cicla iterativamente sugli elementi di _articles for (long a = 0; a < total; a++) { pi.addstatus(1); TMRP_line& curr_article = _articles[a]; const int last = curr_article.last_bucket(); if (last >= 0) { real curgiac; curr_article.set_on_hand(0, curr_article.giacenza_attuale(curgiac, m.get_date(F_DADATA))); for (int bucket = 0; ok && bucket <= last; bucket = curr_article.next_bucket(bucket)) { ok = gross2net_logic(curr_article, bucket, lotsizing_f, lotsizing_p); if (!ok) break; } } } return ok; } /////////// finished: 100% /////////// tested : 100% // genera le righe degli ordini di produzione o a fornitore bool TMatResPlanning::build_orders(TMatResMask& m) { TSheet_field& sf = m.sfield(F_ORDINI); sf.destroy(); // TArticolo articolo; const long tot = _articles.items(); if (tot > 0L) { const int leadtime = m.get_int(F_LEADTIME); TProgind pi(tot, "Fase 5: generazione righe ordini...", FALSE, TRUE); for (long a = 0; a < tot; a++) { pi.addstatus(1); const TMRP_line& line = _articles[a]; const TRectype& anarec = cache().get(LF_ANAMAG,line.codice()); const TRectype& distrec = cache().get(LF_DIST,line.codice()); const bool prod = distrec.get_bool("ARTPROD"); const long forn = prod ? 0 : anarec.get_long("CODFORN"); for (int b = line.last_bucket(); b >= 0; b--) { real qta = line.net_requirement(b); if (!qta.is_zero() && !qta.round(5).is_zero())// > real("0.00001")) { TMRP_time time = line.time(b); if (line.sons() == 0) // Sottrai alle foglie il bonus time.sub_time(leadtime); TDate date = time.date(); m.round_date(date); //articolo.read(line.codice()); //const char reorder_type=articolo.get_char(ANAMAG_RIORDINO); //if (prod || reorder_type=='F' || reorder_type==' ' || reorder_type=='\0') m.add_order_line(date, forn, line, b); } } } } const int orders=sf.items(); if (orders==0) message_box("Nessun ordine da emettere"); real price, qta; TCodice_articolo codart; FOR_EACH_SHEET_ROW(sf,n,row) { long codcli=atol(row->get(F_FORNITORE-FIRST_FIELD)); const TRectype& rec=cache().get(LF_DIST,row->get(F_ARTICOLO-FIRST_FIELD)); bool art_prod = rec.get_bool("ARTPROD"); bool art_acq = rec.get_bool("ARTACQ") || codcli!=0L; if (rec.empty()) // art_prod=!(art_acq=TRUE); TString16 tipoord=!art_acq ? "P":"F" ; row->add(tipoord, F_ORD_TYPE-FIRST_FIELD); sf.check_row(n); if (tipoord=="P") tipoord=" "; else tipoord=m.get(F_TIPOCF_CONDV); row->get(F_QUANTITA-FIRST_FIELD,qta); row->get(F_ARTICOLO-FIRST_FIELD,codart); find_price(m.get(F_TIPOCV),m.get(F_CODCONDV),m.get(F_CATVEN_CV), tipoord, codcli, codart, qta, price); row->add(price.string(), F_PREZZO-FIRST_FIELD); sf.enable_cell(n,F_ORD_TYPE-FIRST_FIELD,art_prod != art_acq); } return TRUE; } /////////// finished: 90% /////////// tested : 90% // genera gli ordini di produzione o a fornitore bool TMatResPlanning::emit_orders(TMatResMask& m) { TSheet_field& sf = m.sfield(F_ORDINI); TDate today(TODAY); int docf = 0, docp = 0; TAssoc_array docs; TToken_string key; FOR_EACH_SHEET_ROW(sf, r, row) { TToken_string& riga = *row; if (*riga.get(0) == 'X') { const TDate datadoc = riga.get(sf.cid2index(F_DATADOC)); const TDate datacon = riga.get(sf.cid2index(F_DATACONS)); const char type = riga.get_char(sf.cid2index(F_ORD_TYPE)); const bool prod = type == 'P'; const long forn = prod ? 0 : riga.get_long(sf.cid2index(F_FORNITORE)); const real qta(riga.get(sf.cid2index(F_QUANTITA))); riga.add(" ",sf.cid2index(F_OK)); if (qta.is_zero()) continue; if (datadoc < today) if (!noyes_box("Riga %d: data di documento inferiore a quella odierna. Confermi ?",r+1)) continue; if (datacon < datadoc) if (!noyes_box("Riga %d: data di consegna inferiore a quella del documento. Confermi ?",r+1)) continue; if (prod || forn!=0L) { key = datadoc.string(); key.add(datacon.string()); key.add(type); key.add(forn); TDocumento* doc = (TDocumento*)docs.objptr(key); if (doc == NULL) { const TString& codnum = m.get(prod ? F_NUM_PROD : F_NUM_FORN ); const TString& tipdoc = m.get(prod ? F_TIPO_PROD: F_TIPO_FORN ); doc = new TDocumento('D', datadoc.year(), codnum, 0); doc->set_tipo(tipdoc); TRectype& testata = doc->head(); testata.put(DOC_DATADOC, datadoc); testata.put(DOC_DATACONS, datacon); if (prod) docp++; else { testata.put(DOC_TIPOCF, "F"); testata.put(DOC_CODCF, forn); docf++; } docs.add(key, doc); } TRiga_documento& rdoc = doc->new_row(m.get(prod ? F_RIGA_PROD : F_RIGA_FORN)); rdoc.put(RDOC_CODART, riga.get(sf.cid2index(F_ARTICOLO))); TString80 str; TCodice_livelli & livelli = m.livelli_giacenza(); livelli.pack_grpcode(str, riga.get(sf.cid2index(F_LIV1)), 1); livelli.pack_grpcode(str, riga.get(sf.cid2index(F_LIV2)), 2); livelli.pack_grpcode(str, riga.get(sf.cid2index(F_LIV3)), 3); livelli.pack_grpcode(str, riga.get(sf.cid2index(F_LIV4)), 4); rdoc.put(RDOC_LIVELLO,str); add_magcode(str,riga.get(sf.cid2index(F_MAGAZZINO))); add_depcode(str,riga.get(sf.cid2index(F_DEPOSITO))); rdoc.put(RDOC_CODMAG, str); rdoc.put(RDOC_IMPIANTO, riga.get(sf.cid2index(F_CODIMP))); rdoc.put(RDOC_LINEA, riga.get(sf.cid2index(F_CODLIN))); rdoc.put(RDOC_QTA, qta); rdoc.put(RDOC_UMQTA, riga.get(sf.cid2index(F_UM))); rdoc.put(RDOC_PREZZO, riga.get(sf.cid2index(F_PREZZO))); TString ddd=riga.get(sf.cid2index(F_DESCART)); rdoc.put(RDOC_DESCR, riga.get(sf.cid2index(F_DESCART))); riga.add("X",sf.cid2index(F_OK)); } else error_box("Codice fornitore mancante alla riga %d", r+1); } } const int tot = int(docs.items()); if (tot > 0 && yesno_box("Confermare la generazione di\n" "%d ordini di produzione e di\n" "%d odini a fornitori?", docp, docf)) { TProgind pi(tot, "Generazione ordini", FALSE, TRUE); FOR_EACH_ASSOC_OBJECT(docs, hash, str, obj) { pi.addstatus(1); TDocumento* doc = (TDocumento*)obj; if (doc->write()!=NOERR) doc->read(); // unlock } FOR_EACH_SHEET_ROW_BACK(sf, r, row) { if (*row->get(sf.cid2index(F_SELECTED)) == 'X' && *row->get(sf.cid2index(F_OK))=='X') sf.destroy(r); } m.enable(DLG_SAVEREC, sf.items() > 0); } return tot > 0; } void TMatResPlanning::compute(TMatResMask& m) { if (preprocess_cycle(m)) // req iniziale dai docs if (net_requirement_cycle(m)) build_orders(m); } void TMatResPlanning::main_loop() { open_files(LF_TABCOM, LF_TAB, LF_DOC, LF_RIGHEDOC, 0); open_files(LF_CLIFO, LF_CFVEN, LF_OCCAS, LF_INDSP,LF_CONDV, 0); open_files(LF_ANAMAG, LF_DIST, LF_RDIST, 0); open_files(LF_MAG, 0); TMatResMask m; TConfig prassid(CONFIG_DITTA, "ve"); // apre il file di configurazione della ditta corrente if (prassid.get_bool("GES", NULL, A_LISTINI)) { m.enable(F_CATVEN_CV,prassid.get_bool("GESLISCV")); } else { m.disable(F_TIPOCV); m.disable(F_CATVEN_CV); } if (!prassid.get_bool("GES", NULL, A_CONTRATTI) || !prassid.get_bool("GESCONCC")) m.disable(F_TIPOCF_CONDV); while (m.run()!=K_QUIT); } int mr2100(int argc, char* argv[]) { TMatResPlanning a; a.run(argc, argv, "Material Requirements Planning"); return 0; }