============================================ Algoritmul de Distribuire a Suprasolicitarii ============================================ Aceasta pagina descrie in detaliu algoritmul executat de actiunea :doc:`Acceptare diferente ` pentru un task ramas in starea **EROARE** (``Q_DISPONIBIL < Q_NECESAR``), implementat in ``WlpServiceImpl.distribuieActivitateOverload()``. Acelasi algoritm are si o **varianta punctuala, per resursa**, descrisa la finalul paginii. Context ======= Un task in eroare **nu are nicio planificare generata** — Pasul 8 al :doc:`algoritmului de evaluare ` este sarit pentru taskurile cu eroare blocanta. La **Acceptare diferente**, sistemul trebuie sa genereze planificarea completa a taskului, tratand distinct: - orele care **se incadreaza** in capacitatea normal disponibila a fiecarei persoane; - orele care **depasesc** aceasta capacitate (suprasolicitare/overload), pe care organizatia le accepta explicit. Faza 1 — alocarea orelor acoperite normal ============================================ Se aplica exact algoritmul standard de alocare (``aplicaAlocarePlanificariPeTask``, aceeasi logica ca la Pasul 8 al evaluarii, sarita initial din cauza erorii): pentru fiecare persoana din ``WLP_CA_DETALIU``, in ordinea descrescatoare a capacitatii ei disponibile in perioada, se aloca ore pe zilele calendarului ei (``WLP_CALENDAR``) pana la limita capacitatii nete a zilei respective, fara a depasi niciodata necesarul cumulat al taskului. Rezultatul se scrie in ``WLP_CA_PLANIFICARI`` cu ``STARE = 0`` (alocare normala), iar cantitatile agregate (``Q_ALOCAT``) se resincronizeaza pe ``WLP_CA_DETALIU`` si ``WLP_CA``. Daca la finalul Fazei 1 tot necesarul este acoperit, algoritmul se opreste aici (Faza 2 nu se mai executa). Faza 2 — distribuirea suprasolicitarii ========================================== Se calculeaza diferenta ramasa neacoperita: .. code-block:: text ore_neacoperite = Q_NECESAR (task) - Q_DISPONIBIL (task) Se identifica **toate zilele lucratoare** din perioada taskului, pentru toate persoanele din detalierea lui (``WLP_CA_DETALIU`` join ``WLP_CALENDAR`` unde ``IS_WORKING_DAY = 1``), si se calculeaza numarul lor total: ``total_zile_lucratoare``. Se calculeaza **indicele de suprasolicitare per zi**: .. code-block:: text indice_brut = ore_neacoperite / total_zile_lucratoare Regulile de rotunjire si distributie sunt urmatoarele: .. list-table:: :header-rows: 1 :widths: 25 75 * - Conditie - Comportament * - ``indice_brut >= 2`` - Se rotunjeste la cel mai apropiat numar intreg (partea fractionara ``>= 0.5`` rotunjeste in sus, altfel in jos): ``increment_pe_zi = ROUND(indice_brut)``. Se aplica **tuturor** zilelor lucratoare gasite, cate ``increment_pe_zi`` ore fiecare, **cu exceptia ultimei zile procesate**, careia i se atribuie exact restul necesar pentru ca suma totala sa fie egala cu ``ore_neacoperite`` (corectie de rotunjire). * - ``indice_brut < 2`` si ``ore_neacoperite <= 2`` - Se aloca intreaga cantitate (``ore_neacoperite``) intr-o singura zi. * - ``indice_brut < 2`` si ``ore_neacoperite > 2`` - Se forteaza un minim de **2 ore/zi**, aplicat pe un numar de zile determinat prin ``CEIL(ore_neacoperite / 2)``, oprindu-se imediat ce suma orelor alocate atinge ``ore_neacoperite`` (ultima zi este corectata similar, la restul exact). Fiecare ora de suprasolicitare astfel calculata se insereaza ca un rand **nou si distinct** in ``WLP_CA_PLANIFICARI``, cu ``STARE = 3``, separat de eventualele randuri normale (``STARE = 0``) generate in Faza 1 pentru aceeasi zi si aceeasi persoana. .. important:: Suma tuturor randurilor cu ``STARE = 3`` generate in Faza 2 este **exact egala** cu ``ore_neacoperite`` — corectia aplicata pe ultima zi garanteaza acest lucru, indiferent de rotunjirile intermediare. Exemplu real, verificat ========================== Urmatorul exemplu a fost rulat si verificat direct in sistem (task ``MAI_01G``, ``ID_WLP_CA = 2400``, persoana cu marca 66, meserie ``TRAGERE``): .. list-table:: :header-rows: 1 :widths: 40 60 * - Marime - Valoare * - ``Q_NECESAR`` (task) - 80 ore * - ``Q_DISPONIBIL`` (task), inainte de acceptare - 0 ore (persoana nu avea deloc capacitate libera in perioada) * - ``total_zile_lucratoare`` - 15 zile * - ``ore_neacoperite`` - 80 - 0 = 80 ore * - ``indice_brut`` - 80 / 15 = 5.333... * - ``increment_pe_zi`` (rotunjit) - 5 ore/zi (0.333 < 0.5, rotunjire in jos) Rezultatul Fazei 1: niciun rand normal alocat (persoana nu avea nicio ora libera). Rezultatul Fazei 2, verificat in ``WLP_CA_PLANIFICARI``: .. list-table:: :header-rows: 1 :widths: 30 70 * - Zile - Alocare * - 14 zile - 5 ore/zi fiecare = 70 ore * - a 15-a zi (ultima) - corectata la 10 ore (restul exact, nu 5), astfel incat totalul sa fie 80 ore **Total verificat: 14 × 5 + 10 = 80 ore**, exact cat ``ore_neacoperite``, distribuit in 15 randuri distincte cu ``STARE = 3``. Dupa aplicarea celor doua faze, ``WLP_CA.STARE`` a taskului a trecut din **EROARE** in **VALIDATA**. Varianta punctuala — distribuirea overload-ului pe o singura resursa ============================================================================ Actiunea :doc:`Planificare resursa cu overload ` (``WlpCaDetaliuServiceImpl.planificaResursa`` cu ``tipPlanificare = 2``) executa **acelasi algoritm in doua faze**, dar restrans la un singur rand ``WLP_CA_DETALIU``: - **Faza 1** (``alocaDisponibilPeResursa``) aloca disponibilul persoanei selectate, limitat la necesarul **ramas** al taskului (``Q_NECESAR - Q_ALOCAT``), pentru ca apelurile repetate pe persoane diferite sa nu supra-aloce; - **Faza 2** (``distribuieOverloadPeResursa``) distribuie cantitatea de ore de overload introdusa de utilizator in dialog (nu neaparat intreaga diferenta — serverul o plafoneaza la ``Q_NECESAR - Q_DISPONIBIL`` al persoanei), doar peste zilele lucratoare ale persoanei selectate, tot ca randuri ``WLP_CA_PLANIFICARI`` cu ``STARE = 3``. Intern, ambele fluxuri folosesc aceeasi metoda ``distribuieOverloadPeTask``, cu un filtru optional pe ``ID_WLP_CA_DETALIU`` (``null`` = toate resursele, fluxul de la Acceptare diferente). Dupa distribuire, taskul este reevaluat *soft* (``reevaluareTaskFaraStergereAlocari``): se sterg doar mesajele din ``WLP_CA_LOG``, se recalculeaza disponibilul pe toate resursele taskului peste alocarile existente (orele ``STARE = 3`` conteaza ca acoperire), iar taskul se valideaza automat cand nu mai raman erori. Regula de distributie partajata ================================ Regula de calcul a orelor pe zi (indice brut, pragul de 2 ore/zi, rotunjirea si corectia pe ultima zi) este extrasa in metoda comuna ``WlpServiceImpl.calculeazaDistributieOrePeZile(totalOre, totalZile)`` si este refolosita de: - distribuirea suprasolicitarii pe task sau pe resursa (aceasta pagina); - actiunea **Ore suplimentare**, optiunea *Total ore distribuite automat pe zilele lucratoare* — cu diferenta ca acolo orele mareasc **capacitatea** zilelor (``WLP_CALENDAR.Q_OVERTIME`` + ``Q_CAPACITATE``), nu genereaza randuri de planificare ``STARE = 3``.