diff --git a/app/Console/Commands/CalculateSCurve.php b/app/Console/Commands/CalculateSCurve.php new file mode 100644 index 0000000..72a2e37 --- /dev/null +++ b/app/Console/Commands/CalculateSCurve.php @@ -0,0 +1,31 @@ +argument('project_id'); + $project = Project::find($project_id); + + $project->calculation_status = true; + $project->save(); + + $data = MasterFunctionsHelper::CalculateSCurve($project_id); + + $project->scurve = json_encode($data); + $project->calculation_status = true; + $project->save(); + + return $data; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index a379d80..a7fceee 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -13,7 +13,8 @@ class Kernel extends ConsoleKernel * @var array */ protected $commands = [ - Commands\syncHumanResourceIntegration::class + Commands\syncHumanResourceIntegration::class, + Commands\CalculateSCurve::class ]; /** diff --git a/app/Helpers/MasterFunctionsHelper.php b/app/Helpers/MasterFunctionsHelper.php index 1a07007..b93588d 100644 --- a/app/Helpers/MasterFunctionsHelper.php +++ b/app/Helpers/MasterFunctionsHelper.php @@ -99,11 +99,8 @@ class MasterFunctionsHelper { $maxDate = Project::select('akhir_proyek')->where('id', $request->project_id)->first(); $begin = new \DateTime($minDate->mulai_proyek. ' Monday'); - $begin->modify('last Monday'); $end = new \DateTime($maxDate->akhir_proyek. ' Friday'); - $end->modify('next Friday'); - $end->modify('next Friday'); $interval = new \DateInterval('P7D'); @@ -118,13 +115,14 @@ class MasterFunctionsHelper { $dates = []; $tempProgress = 0; $tempPlanning = 0; - + array_push($progressData, round($tempProgress, 2)); + array_push($planningData, round($tempPlanning, 2)); foreach($period as $p){ - array_push($progressData, $tempProgress); - array_push($planningData, $tempPlanning); - array_push($dates, $p->format("Y-m-d")); $tempProgress += $avgProgress; $tempPlanning += $avgPlanning; + array_push($progressData, round($tempProgress, 2)); + array_push($planningData, round($tempPlanning, 2)); + array_push($dates, $p->format("Y-m-d")); } $dataResponse = array( @@ -444,6 +442,280 @@ class MasterFunctionsHelper { return $dataFinal; } + + public function calculateSCurve($projectId) + { + DB::enableQueryLog(); + + $dataFinal=[]; + $dataPayload = []; + $dataPayload['period'] = 'week'; + $totalACWP = 0; + $totalBCWP = 0; + $tempPercentage = []; + + $dataProject = Project::find($projectId); + $dataHeader = Activity::where('type_activity', 'header')->where("proyek_id", $projectId)->first(); + if(isset($dataPayload['end_date']) && $dataPayload['end_date'] > $dataProject->akhir_proyek){ + $dataPayload['end_date'] = $dataProject->akhir_proyek; + } + + if($dataHeader){ + $totalRencanaBudget = Activity::where('parent_id', $dataHeader->id)->where("proyek_id", $projectId)->sum("rencana_biaya"); + }else{ + $totalRencanaBudget = Activity::whereNull('parent_id')->where("proyek_id", $projectId)->sum("rencana_biaya"); + } + + $alreadyHasReport = DB::table('report_activity_material as a') + ->select('a.id') + ->join('m_activity as b', 'b.id', '=', 'a.activity_id') + ->exists(); + + $minDate = $dataProject->mulai_proyek; + + $begin = new \DateTime($minDate.' Monday'); + $begin->modify('last Monday'); + if(isset($dataPayload['end_date'])){ + $maxDate = $dataPayload['end_date']; + $end = new \DateTime($maxDate. ' Friday'); + $end->modify('next Friday'); + $end->modify('next Friday'); + /* $interval = \DateInterval::createFromDateString('1 day'); */ // should be using this but its bugged + $interval = new \DateInterval('P7D'); + } else { + // $maxDate = DB::table('assign_material_to_activity as ama') + // ->where("ama.proyek_id", $projectId) + // ->join('m_activity as a', 'a.id', '=', 'ama.activity_id') + // ->max("plan_date"); // plan date overlapped with assign_material_to_activity's, it should be m_activity's + $maxDate = $dataProject->akhir_proyek; + $end = new \DateTime($maxDate. ' Friday'); + $end->modify('next Friday'); + $end->modify('next Friday'); + $interval = new \DateInterval('P7D'); + } + $period = new \DatePeriod($begin, $interval, $end); + + $arr_ActualM = []; + $tempDate = []; + $tempPercentagePlan = []; + $tempPercentagePlanWhr = []; + $tempPercentageReal = []; + $tempTtlPercentPlan=0; + $tempTtlPercentActual=0; + + $currentACWP = 0; + $currentBCWP = 0; + + foreach ($period as $dt) { + $minSevenDays = new \Datetime($dt->format("Y-m-d")); + $minSevenDays = $minSevenDays->modify('-7 day')->format("Y-m-d"); + // $dataPlanM = DB::table('assign_material_to_activity as ama') + // ->select('ama.activity_id', 'ama.qty_planning', 'ama.plan_date', 'ama.start_activity', 'a.bobot_planning', 'a.biaya_actual', 'a.duration', 'a.persentase_progress') + // ->join('m_activity as a', 'a.id', '=', 'ama.activity_id') + // ->where('ama.proyek_id', '=', $keyGantt['proyek_id']) + // ->where('a.version_gantt_id', '=', $keyGantt['id']) + // ->whereDate('ama.plan_date', '<=',$dt->format("Y-m-d")) + // ->whereDate('ama.plan_date', '>', $minSevenDays) + // ->get(); + + $activities = DB::table('m_activity AS a') + ->join('assign_material_to_activity AS amta', 'amta.activity_id', '=', 'a.id') + ->where('a.type_activity', 'task') + ->where('a.bobot_planning', '>', 0) + ->whereDate('amta.plan_date', '<=',$dt->format("Y-m-d")) + ->whereDate('amta.plan_date', '>', $minSevenDays) + ->select('a.bobot_planning', 'a.biaya_actual', 'a.duration', 'a.persentase_progress', 'a.id'); + + $dataPlanM = DB::table('m_activity AS a') + ->join('assign_hr_to_activity AS ahta', 'ahta.activity_id', '=', 'a.id') + ->where('a.type_activity', 'task') + ->where('a.bobot_planning', '>', 0) + ->whereDate('a.start_date', '<=',$dt->format("Y-m-d")) + ->whereDate('a.start_date', '>', $minSevenDays) + ->select('a.bobot_planning', 'a.biaya_actual', 'a.duration', 'a.persentase_progress', 'a.id') + ->union($activities) + ->get(); + + $dataActualM = DB::table('report_activity_material as ram') + ->select('ram.activity_id', 'ram.qty', 'ram.report_date', 'a.bobot_planning', 'a.biaya_actual', 'a.duration', 'a.persentase_progress') + ->join('m_activity as a', 'a.id', '=', 'ram.activity_id') + ->where('a.proyek_id', '=', $projectId) + ->whereDate('ram.report_date', '<=',$dt->format("Y-m-d")) + ->whereDate('ram.report_date', '>',$minSevenDays) + ->get(); + $dataTempPlan = []; + $x = 0; + $sumPercentagePlan=0; + $totalACWP = isset($totalACWP) ? $totalACWP : 0; + $totalBCWP = isset($totalBCWP) ? $totalBCWP : 0; + + foreach ($dataPlanM as $keyPlanM) { + $sumVolPlan = DB::table(function ($query){ + $query->select('a.*') + ->from('m_activity AS a') + ->join('assign_material_to_activity as amta', 'amta.activity_id', '=', 'a.id') + ->where('a.type_activity', 'task') + ->where('a.bobot_planning', '>', 0) + ->unionAll(function ($query){ + $query->select('a.*') + ->from('m_activity AS a') + ->join('assign_hr_to_activity as ahta', 'ahta.activity_id', '=', 'a.id') + ->where('a.type_activity', 'task') + ->where('a.bobot_planning', '>', 0); + })->orderBy('id', 'asc'); + }, 'subquery') + ->sum('bobot_planning'); + $dataTempPlan [$x]['activity_id'] = $keyPlanM->id; + $dataTempPlan [$x]['bobot_planning'] = $keyPlanM->bobot_planning; + $dataTempPlan [$x]['ttl_plan'] = $sumVolPlan; + $dataTempPlan [$x]['biaya_actual'] = $keyPlanM->biaya_actual; + $dataTempPlan [$x]['duration'] = $keyPlanM->duration; + $dataTempPlan [$x]['persentase_progress'] = $keyPlanM->persentase_progress; + try { + $dataTempPlan [$x]['percentage'] = $keyPlanM->bobot_planning; + $sumPercentagePlan+= $keyPlanM->bobot_planning; + if(isset($keyPlanM->duration) && $keyPlanM->duration > 0) + $totalBCWP += (((($keyPlanM->persentase_progress*$keyPlanM->bobot_planning)/100)/$keyPlanM->duration)* $totalRencanaBudget)/100; + else + $totalBCWP = 0; + $dataTempPlan [$x]['totalBCWP'] = $totalBCWP; + } catch (\DivisionByZeroError $e) { + return response()->json(['message' => $e->getMessage()]); + } + $x++; + } + + $w = 0; + $dataTempReport = []; + $sumPercentageActual=0; + foreach ($dataActualM as $keyActualM) { + $sumVolActual = DB::table('assign_material_to_activity') + ->select('activity_id', DB::raw('SUM(qty_planning) as ttl_qty_plan')) + ->where('activity_id', '=', $keyActualM->activity_id) + ->groupBy('activity_id') + ->first(); + $sumReportActual = DB::table('report_activity_material') + ->where('activity_id', $keyActualM->activity_id) + ->sum('qty'); + $reportCount = DB::table('report_activity_material')->where('activity_id', '=', $keyActualM->activity_id)->count(); + $dataTempReport [$w]['activity_id'] = $keyActualM->activity_id; + $dataTempReport [$w]['qty'] = $keyActualM->qty; + $dataTempReport [$w]['report_date'] = $keyActualM->report_date; + $dataTempReport [$w]['bobot_planning'] = $keyActualM->bobot_planning; + $dataTempReport [$w]['ttl_plan'] = $sumVolActual->ttl_qty_plan; + $dataTempReport [$w]['biaya_actual'] = $keyActualM->biaya_actual; + $dataTempReport [$w]['duration'] = $keyActualM->duration; + $dataTempReport [$w]['persentase_progress'] = $keyActualM->persentase_progress; + try { + // assign_material_to_activity + $checkStatusActivity = DB::table('assign_material_to_activity') + ->select('activity_id', 'status_activity') + ->where('activity_id', '=', $keyActualM->activity_id) + ->orderBy('status_activity', 'ASC') + ->first(); + $dataTempReport [$w]['percentage'] = ($keyActualM->qty/$sumVolActual->ttl_qty_plan)*$keyActualM->bobot_planning; + // $sumPercentageActual+=($keyActualM->qty/$sumVolActual->ttl_qty_plan)*$keyActualM->bobot_planning; + // if($keyActualM->qty/$sumVolActual->ttl_qty_plan >= 1){ + if($checkStatusActivity->status_activity == 'done'){ + $sumPercentageActual+=$keyActualM->bobot_planning/$reportCount; + // $sumPercentageActual = $sumPercentageActual > $keyGantt['progress'] ? $keyGantt['progress'] : $sumPercentageActual; + }else{ + if($keyActualM->qty/$sumVolActual->ttl_qty_plan >= 1 || (int)$sumVolActual->ttl_qty_plan == (int)$sumReportActual){ + $sumPercentageActual+=(($keyActualM->qty/$sumVolActual->ttl_qty_plan)*$keyActualM->bobot_planning)*(95/100); + // $sumPercentageActual = $sumPercentageActual > $keyGantt['progress'] ? $keyGantt['progress'] : $sumPercentageActual; + }else{ + $sumPercentageActual+=($keyActualM->qty/$sumVolActual->ttl_qty_plan)*$keyActualM->bobot_planning; + // $sumPercentageActual = $sumPercentageActual > $keyGantt['progress'] ? $keyGantt['progress'] : $sumPercentageActual; + } + } + // }else { + // if($checkStatusActivity->status_activity == 'done'){ + // $sumPercentageActual+=($keyActualM->qty/$keyActualM->qty)*$keyActualM->bobot_planning; + // }else{ + // $sumPercentageActual+=($keyActualM->qty/$sumVolActual->ttl_qty_plan)*$keyActualM->bobot_planning; + // } + // } + + + $totalACWP += $keyActualM->biaya_actual/$keyActualM->duration; + } catch (\DivisionByZeroError $e) { + return response()->json(['message' => $e->getMessage()]); + } + $dataTempReport [$w]['totalacwp'] = $totalACWP; + $w++; + } + + $arr_ActualM[] = array( + 'date'=>$dt->format("Y-m-d"), + 'percentPlan'=>$sumPercentagePlan, + 'percentActual'=> $sumPercentageActual, + 'plan'=>$dataTempPlan, + 'actual'=>$dataTempReport, + ); + if(isset($dataPayload['period']) && $dataPayload['period'] == 'week'){ + $tempTtlPercentPlan+= $sumPercentagePlan; + $tempTtlPercentActual+= $sumPercentageActual; + + $currentACWP += $totalACWP; + $currentBCWP += $totalBCWP; + + $tempPercentage[] = array(round($tempTtlPercentPlan,2), round($tempTtlPercentActual,2)); + $tempPercentagePlan[] = round($tempTtlPercentPlan, 2); + $tempPercentagePlanWhr[] = ["weekly period", $tempPercentagePlan]; + $tempPercentageReal[] = round($tempTtlPercentActual, 2); + }else{ + $tempPercentage[] = array(round($sumPercentagePlan,2), round($sumPercentageActual,2)); + $tempPercentagePlan[] = round($sumPercentagePlan, 2); + $tempPercentageReal[] = round($sumPercentageActual, 2); + } + $tempDate[] = array($dt->format("Y-m-d")); + } + + try { + if(round($totalACWP,0) > $totalRencanaBudget){ + $estimatedCost = round($totalACWP,0)+0; + }else{ + $estimatedCost = ($totalRencanaBudget+0); + } + } catch (\DivisionByZeroError $e) { + return response()->json([ + 'message' => $e->getMessage(), + "line" => 566, + ]); + } + $estimatedCost = $totalACWP > $totalRencanaBudget ? $totalACWP : $totalRencanaBudget; + + $costDeviation = $totalRencanaBudget - $estimatedCost; + if($costDeviation > 0){ + $potential = "SAVING"; + } else { + $potential = $costDeviation == 0 ? "ON BUDGET" : "OVERRUN"; + } + + $dataResponse = array( + "date" =>$tempDate, + "percentage" =>$tempPercentage, + "percentagePlan" => $tempPercentagePlan, + "percentageReal" => $tempPercentageReal, + "data_details" =>$arr_ActualM, + "budget_control" =>array("current_budget"=> $totalRencanaBudget, + "acwp" => round($totalACWP,0), + "bcwp" => round($totalBCWP,0), + "rem_to_complete" => ($totalRencanaBudget - round($totalACWP,0)), + "add_cost_to_complete" => 0, + "estimated_at_completion" => $estimatedCost, + "cost_deviation" => $costDeviation, + "potential" => $potential, + ) + ); + + $dataFinal[] = array( + "proyek_name"=> $dataProject->nama, + "data"=>$dataResponse, + ); + + return $dataFinal; + } public function calculateProgressBasedOnSimple($keyGantt){ DB::enableQueryLog(); diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index ba90377..6e8ca66 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -29,6 +29,7 @@ use App\Models\OfficeHours; use Illuminate\Support\Facades\DB; use App\Helpers\MasterFunctionsHelper; use App\Models\ReportActivityMaterial; +use Illuminate\Support\Facades\Artisan; const API_GEOLOCATION = "https://nominatim.oslogdev.com/search/ADDR?format=json&addressdetails=1&limit=1"; @@ -332,6 +333,17 @@ class ProjectController extends Controller return response()->json(['status'=>'success','code'=>200, 'data' => $data], 200); } + public function calculateSCurve(Request $request){ + $sCurve = Project::select('scurve')->where('id', $request->project_id)->first(); + return response()->json(['status'=>'success','code'=>200, 'data' => json_decode($sCurve->scurve)], 200); + } + + public function sCurveCommand(Request $request){ + Artisan::call('calculate:scurve', [ + 'project_id' => $request->project_id + ]); + } + public function getLinearSCurve(Request $request){ $data = MasterFunctionsHelper::getLinearSCurve($request); return response()->json(['status'=>'success','code'=>200, 'data' => $data], 200); diff --git a/app/Models/Project.php b/app/Models/Project.php index 452dd62..880efec 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -42,6 +42,8 @@ class Project extends Model 'currency_name', 'budget_health', 'phase_id', + 'calculation_status', + 'scurve', 'created_at', 'created_by', 'updated_at', diff --git a/routes/web.php b/routes/web.php index c65786d..03f769c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -66,6 +66,8 @@ $router->group(['prefix'=>'api', 'middleware' => 'cors'], function () use ($rout $router->get('/project/manpower/assigned/{gantt_id}', 'ProjectController@getAssignedHR'); $router->post('/project/get-s-curve', 'ProjectController@getSCurve'); + $router->post('/project/calculate-s-curve', 'ProjectController@calculateSCurve'); + $router->post('/project/s-curve-command', 'ProjectController@sCurveCommand'); $router->post('/project/get-linear-s-curve', 'ProjectController@getLinearSCurve'); $router->post('/project/get-overdue-activities', 'ProjectController@getOverdueActivities'); $router->post('/project/get-integration-invoice', 'ProjectController@getInvoiceIntegration');