Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 89 |
| WF_template | |
0.00% |
0 / 1 |
|
0.00% |
0 / 12 |
1892.00 | |
0.00% |
0 / 89 |
| __construct | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 3 |
|||
| setup | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 11 |
|||
| sortByEst | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 5 |
|||
| validate | |
0.00% |
0 / 1 |
30.00 | |
0.00% |
0 / 10 |
|||
| set_next | |
0.00% |
0 / 1 |
30.00 | |
0.00% |
0 / 15 |
|||
| prevNextAll_deduce | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 4 |
|||
| prevAll_deduce | |
0.00% |
0 / 1 |
20.00 | |
0.00% |
0 / 5 |
|||
| nextAll_deduce | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 5 |
|||
| remove_fakeStart | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 4 |
|||
| cycles_detect | |
0.00% |
0 / 1 |
20.00 | |
0.00% |
0 / 5 |
|||
| earlyTimes_calculate | |
0.00% |
0 / 1 |
42.00 | |
0.00% |
0 / 9 |
|||
| lateTimes_calculate | |
0.00% |
0 / 1 |
30.00 | |
0.00% |
0 / 13 |
|||
| <?php | |
| namespace ia\Work\Workflow; | |
| use function array_combine; | |
| use function array_key_exists; | |
| use Exception; | |
| use function implode; | |
| use function uasort; | |
| /** | |
| * Workflow template: define tiempos estandard y sequencia de actividades. | |
| * | |
| * Valida y calcula agregando est,lst,eet,let, slack, next, prev activity, prevAll, nextAll. | |
| * Main usage: get user input for wf template: validate, acompleta, para guardar. | |
| * | |
| * est earliest start time, int nĂºmero de periodos: dias/horas | |
| * lst latest start time | |
| * eet earliest end time | |
| * let latest end time | |
| * slack holgura | |
| * | |
| * | |
| * plan=>[ | |
| * eet=>ya es fecha | |
| * | |
| * | |
| * ] | |
| * | |
| * rule invalid id "s\ttart" | |
| * | |
| * activityList = | |
| * [ | |
| * 'a'=>['duration'=>1, 'label'=>'Act: a', 'myData'=>1], | |
| * 'b'=>['duration'=>3], | |
| * 'c'=>['duration'=>4,'prev'=>['a','b'=>'b']], | |
| * 'd'=>['duration'=>5,'prev'=>['c']], | |
| * 'f'=>['duration'=>4,'prev'=>['b']], | |
| * 'e'=>['duration'=>5,'prev'=>['d']], | |
| * ]; | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * @version 0.0.1 | |
| */ | |
| class WF_template { | |
| use wfx; | |
| //https://www.youtube.com/watch?v=WHkf4bE5MrE | |
| // | |
| public const FAKE_START = "st\tart"; | |
| // sprotected $durationConstant; | |
| /** | |
| * WF_template constructor. | |
| * @param array $activityList | |
| * @throws Exception | |
| */ | |
| public function __construct(array $activityList) { //, $durationOffset = 0 | |
| $this->activityList = $activityList; | |
| //$this->durationConstant = $durationOffset; | |
| //$this->durationConstant = 0; | |
| $this->setup(); | |
| } | |
| /** | |
| * @throws Exception | |
| */ | |
| private function setup() { | |
| $this->validate(); | |
| $start = $this->set_next(); | |
| $this->prevNextAll_deduce(); | |
| $cycles = $this->cycles_detect(); | |
| if(!empty($cycles)) { | |
| throw new Exception("Cycles detected for activities: ". implode(", ", $cycles), self::$WF_ERROR_CYCLE); | |
| } | |
| $this->earlyTimes_calculate($start); | |
| $this->lateTimes_calculate($start); | |
| uasort($this->activityList, array($this, 'sortByEst') ); | |
| $this->remove_fakeStart(); | |
| } | |
| /** | |
| * @param array $a | |
| * @param array $b | |
| * @return int | |
| */ | |
| final protected function sortByEst($a, $b) { | |
| if($a['est'] === $b['est']) { | |
| if($a['slack'] === $b['slack']) { | |
| return $a['duration'] - $b['duration']; | |
| } | |
| return $a['slack'] - $b['slack']; | |
| } | |
| return $a['est'] - $b['est']; | |
| } | |
| // [ activity_id=>[prev=>['a','b'],label=>'activity name'] ] | |
| /** | |
| * @throws Exception | |
| */ | |
| private function validate() { | |
| foreach($this->activityList as $activity_id => &$act) { | |
| $act = array_merge( | |
| [ // fill if not in act | |
| 'id'=>$activity_id, | |
| 'label'=>$activity_id, | |
| 'prev' => [], // Inmediatly previous activitie(s) | |
| ], | |
| $act, | |
| [ // fill and replace if in act | |
| 'est'=>null, // Earliest start time | |
| 'lst'=>null, // Latest start time | |
| 'eet'=>null, // Earleist end time | |
| 'let'=>null, // Latest end time | |
| 'prevAll'=>[], // All activities before this one | |
| 'next'=>[], // Inmediatly following activitie(s) | |
| 'nextAll'=>[], // All activities that follow this one | |
| 'slack'=>0 // lag time, diff between eet and let | |
| ] | |
| ); | |
| if(!isset($act['duration']) || !is_numeric($act['duration']) || $act['duration'] < 0) { | |
| throw new Exception("Missing or invalid duration in activity $act[label]", self::$WF_ERROR_DURATION); | |
| } | |
| // reformat prev from ['a', 'b'] to ['a'=>'a', 'b'=>'b'] | |
| $act['prev'] = array_combine($act['prev'], $act['prev']); | |
| } | |
| } | |
| /** | |
| * @return array | |
| * @throws Exception | |
| */ | |
| private function set_next() { | |
| $start = [ | |
| 'id' => self::FAKE_START, | |
| 'duration' => 0, // How long the activity lasts | |
| 'est' => 0, // Earliest start time | |
| 'lst' => 0, // Latest start time | |
| 'eet' => 0, // Earliest end time | |
| 'let' => 0, // Latest end time | |
| 'prev' => [], // Inmediatly previous activitie(s) | |
| 'next' => [], // Inmediatly following activitie(s) | |
| ]; | |
| foreach($this->activityList as $activity_id => &$act) { | |
| if(empty($act['prev'])) { | |
| $act['prev'][self::FAKE_START] = self::FAKE_START; | |
| $start['next'][$activity_id] = $activity_id; | |
| } else { | |
| foreach($act['prev'] as $prev) { | |
| if(empty($this->activityList[$prev])) { | |
| throw new Exception("Unknown prev activity id: $prev in Activity: $act[label]", self::$WF_ERROR_ACTIVITY_NOT_FOUND); | |
| } | |
| $this->activityList[$prev]['next'][$activity_id] = $activity_id; | |
| } | |
| } | |
| } | |
| return $start; | |
| } | |
| /** | |
| * | |
| */ | |
| private function prevNextAll_deduce() { | |
| foreach($this->activityList as $activity_id => &$act) { | |
| $this->prevAll_deduce($act['prev'], $act['prevAll']); | |
| $this->nextAll_deduce($act['next'], $act['nextAll']); | |
| } | |
| } | |
| /** | |
| * @param array $prev | |
| * @param array $prevAll | |
| */ | |
| private function prevAll_deduce(array $prev, array &$prevAll) { | |
| foreach($prev as $prev_id) { | |
| if($prev_id !== self::FAKE_START && empty($prevAll[$prev_id])) { | |
| $prevAll[$prev_id] = $prev_id; | |
| $this->prevAll_deduce($this->activityList[$prev_id]['prev'], $prevAll); | |
| } | |
| } | |
| } | |
| /** | |
| * @param array $next | |
| * @param array $nextAll | |
| */ | |
| private function nextAll_deduce(array $next, array &$nextAll) { | |
| foreach($next as $next_id) { | |
| if(empty($nextAll[$next_id])) { | |
| $nextAll[$next_id] = $next_id; | |
| $this->nextAll_deduce($this->activityList[$next_id]['next'], $nextAll); | |
| } | |
| } | |
| } | |
| /** | |
| * | |
| */ | |
| private function remove_fakeStart() { | |
| foreach($this->activityList as &$act) { | |
| if(isset($act['prev'][self::FAKE_START])) { | |
| unset($act['prev'][self::FAKE_START]); | |
| } | |
| } | |
| } | |
| /** | |
| * @return array | |
| */ | |
| private function cycles_detect() { | |
| $cycles = []; | |
| foreach($this->activityList as $activity_id => $act) { | |
| if(array_key_exists($activity_id, $act['prevAll']) || array_key_exists($activity_id, $act['nextAll'])) { | |
| $cycles[] = $act['label']; | |
| } | |
| } | |
| return $cycles; | |
| } | |
| /** | |
| * @param array $fromActivity | |
| */ | |
| private function earlyTimes_calculate(array $fromActivity) { | |
| foreach($fromActivity['next'] as $activity_id) { | |
| $node = &$this->activityList[$activity_id] ; | |
| foreach($node['prev'] as $prev) { | |
| $activity = empty($this->activityList[$prev]) ? ['eet'=>0,] : $this->activityList[$prev]; // empty es start | |
| if($node['est'] === null || $node['est'] < $activity['eet']) { | |
| $this->activityList[$activity_id]['est'] = $activity['eet']; | |
| } | |
| $this->activityList[$activity_id]['eet'] = $this->activityList[$activity_id]['est'] + $node['duration']; // - $this->durationConstant; | |
| $this->earlyTimes_calculate($this->activityList[$activity_id]); | |
| } | |
| } | |
| } | |
| /** | |
| * @param array $fromActivity | |
| */ | |
| private function lateTimes_calculate(array &$fromActivity) { | |
| if(count($fromActivity['next']) === 0) { | |
| $fromActivity['let'] = $fromActivity['eet']; | |
| $fromActivity['lst'] = $fromActivity['let'] - $fromActivity['duration']; // + $this->durationConstant; | |
| $fromActivity['slack'] = $fromActivity['eet'] - $fromActivity['let']; | |
| return; | |
| } | |
| foreach($fromActivity['next'] as $next_id) { | |
| $this->lateTimes_calculate($this->activityList[$next_id]); | |
| $node = $this->activityList[$next_id]; | |
| if($fromActivity['let'] === null || $fromActivity['let'] > $node['lst']){ | |
| $fromActivity['let'] = $node['lst']; | |
| } | |
| $fromActivity['lst'] = $fromActivity['let'] - $fromActivity['duration']; // + $this->durationConstant; | |
| $fromActivity['slack'] = $fromActivity['let'] - $fromActivity['eet']; | |
| } | |
| } | |
| } |