Files
jandl-taskmanager-app/be/app/Http/Controllers/SubTaskController.php
T

556 lines
21 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Http\Resources\BlockerResource;
use App\Http\Resources\SubTaskResource;
use App\Models\Attachment;
use App\Models\Blocker;
use App\Models\MasterTask;
use App\Models\SubTask;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class SubTaskController extends Controller
{
public function index(Request $request)
{
// get query parameter from request
$parameters = $request->query();
// unscheduled tasks
if (isset($parameters['type']) && $parameters['type'] == 'unscheduled') {
return $this->indexUnscheduled($request);
}
// ongoing tasks
if (isset($parameters['type']) && $parameters['type'] == 'ongoing') {
return $this->indexOngoing($request);
}
// draft tasks (koncepty)
if (isset($parameters['type']) && $parameters['type'] == 'draft') {
return $this->indexDrafts($request);
}
$parameters['user_id'] = ($parameters['employee_id'] ?? null);
$validator = Validator::make($request->all(), [
'start' => 'required',
'end' => 'required',
]);
$validator->after(function ($validator) use ($parameters) {
if (! isset($parameters['employee_id']) && ! isset($parameters['team_id'])) {
$validator->errors()->add(
'error', 'You must provide either employee_id or team_id.'
);
}
});
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
$start = Carbon::parse($parameters['start']);
$end = Carbon::parse($parameters['end'])->addDay();
if (isset($parameters['team_id'])) {
// ak je zadany team, tak najdem vsetkych userov, ktori su v tomto teame. potom najdem vsetky subtasky, ktore su priradene ludom v tomto teame. nevyhladavam na zaklade team_id v master_taskoch, pretoze toto je nepresne. Jandl chce vidiet tasky aj pre jednotlivcov v danom teame aj ked ten task nebol assignuty pod tymto teamom
$userIdArray = User::whereHas('teams', function ($query) use ($parameters) {
$query->where('team_id', $parameters['team_id']);
})->get()->pluck('id')->toArray();
$subtasks = SubTask::published()
->whereIn('user_id', $userIdArray)
->whereHas('timeblocks', function ($query) use ($start, $end) {
$query->where('start', '>=', $start);
$query->where('end', '<', $end);
})->get();
} elseif (isset($parameters['user_id'])) {
$subtasks = SubTask::published()
->where('user_id', $parameters['user_id'])->whereHas('timeblocks',
function ($query) use ($start, $end) {
$query->where('start', '>', $start);
$query->where('end', '<', $end);
})->get();
$userIdArray = [$parameters['user_id']];
}
$blockers1 = Blocker::whereIn('user_id', $userIdArray)->where('start', '>', $start)->get();
// vacation can last more than 7 days what is the date range we use in filter. we need to check also for vacations that started before 7 days and end after 7 days
$blockers2 = Blocker::whereIn('user_id', $userIdArray)->where('start', '<=', $start)->where('end', '>',
$start)->get();
$blockers = $blockers1->merge($blockers2);
return response()->json([
'subtasks' => SubTaskResource::collection($subtasks),
'blockers' => BlockerResource::collection($blockers),
], 200);
}
private function indexUnscheduled(Request $request)
{
// get query parameter from request
$parameters = $request->query();
$validator = Validator::make($parameters, [
'employee_id' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
$parameters['user_id'] = $parameters['employee_id'];
$subtasks = SubTask::published()
->where('user_id', $parameters['user_id'])
->where('is_finished', 0)
->whereDoesntHave('timeblocks')
->get();
return response()->json([
'subtasks' => SubTaskResource::collection($subtasks),
], 200);
}
private function indexOngoing(Request $request)
{
// get query parameter from request
$parameters = $request->query();
$validator = Validator::make($parameters, [
'employee_id' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
$parameters['user_id'] = $parameters['employee_id'];
$subtasks = SubTask::published()
->where('user_id', $parameters['user_id'])
->where('is_finished', 0)
->get();
return response()->json([
'subtasks' => SubTaskResource::collection($subtasks),
], 200);
}
/**
* Zoznam všetkých konceptov (draftov)
*/
private function indexDrafts(Request $request)
{
$parameters = $request->query();
$query = SubTask::draft()
->with(['mastertask', 'user', 'timeblocks', 'attachments'])
->orderBy('created_at', 'desc');
// ak je zadané employee_id, filtrovať podľa neho
if (isset($parameters['employee_id'])) {
$query->where('user_id', $parameters['employee_id']);
}
return response()->json([
'subtasks' => SubTaskResource::collection($query->get()),
], 200);
}
public function store($mastertask_id, Request $request)
{
// zistenie, či ide o draft
$isDraft = isset($request->all()['data']['is_draft']) && $request->all()['data']['is_draft'] == true;
$validator = Validator::make($request->all(), [
'data.employees' => 'required',
]);
// validacia, ci uz subtask pre tohto usera a mastertask neexistuje
// $validator->after(function ($validator) use ($mastertask_id, $request) {
// if (SubTask::where('master_task_id', $mastertask_id)->where('user_id', $request->all()['data']['employee_id'])->exists()) {
// $validator->errors()->add(
// 'data.employee_id', 'Sub task already exists for this employee and master task.'
// );
// }
// });
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
// validacia na max 50MB size pre attachmenty. neslo dat do klasickeho laravel validation, tak je to separe
if ($files = $request->file('data')) {
foreach ($files['files'] as $file) {
if ($file->getSize() > 52428800) {
return response()->json([
'files' => 'File size must be less than 50MB',
], 400);
}
}
}
$mastertask = MasterTask::find($mastertask_id);
$creatorId = User::getUserIdFromBearerToken($request->bearerToken());
// Pre drafty vytvoríme iba jeden záznam pod tvorcom s uloženými dátami všetkých zamestnancov
if ($isDraft) {
// Pripravíme dáta pre draft
$draftData = [
'user_id' => $creatorId,
'created_by' => $creatorId,
'title' => $request->all()['data']['title'] ?? null,
'description' => $request->all()['data']['description'] ?? null,
'deadline' => $request->all()['data']['deadline'] ?? null,
'estimated_time' => 0, // celkový odhadovaný čas sa vypočíta z employees
'is_draft' => true,
'draft_data' => $request->all()['data']['employees'], // uložíme všetky employees data
];
// Vypočítame celkový odhadovaný čas
foreach ($request->all()['data']['employees'] as $employee) {
$draftData['estimated_time'] += $employee['duration'] ?? 0;
}
$subtask = $mastertask->subtasks()->create($draftData);
// upload attachmentu
if ($request->file('data')) {
$attachments = Attachment::uploadAttachmentsToS3($request->file('data')['files'], $mastertask);
$subtask->attachments()->createMany($attachments);
}
return response()->json([
'message' => 'Draft created successfully',
'subtasks' => SubTaskResource::collection([$subtask]),
], 201);
}
// Pre normálne (nie draft) subtasky - pôvodná logika
// upratanie requestu, pretoze frontend pouziva inu nomenklaturu fieldov ako appka
$data['data'] = $request->all()['data'];
foreach ($request->all()['data']['employees'] as $key => $employee) {
$data['data']['employees'][$key]['user_id'] = $employee['id'];
$data['data']['employees'][$key]['title'] = isset($request->all()['data']['title']) ? $request->all()['data']['title'] : null;
$data['data']['employees'][$key]['estimated_time'] = $employee['duration'];
$data['data']['employees'][$key]['created_by'] = $creatorId;
$data['data']['employees'][$key]['description'] = $request->all()['data']['description'];
$data['data']['employees'][$key]['deadline'] = $request->all()['data']['deadline'];
$data['data']['employees'][$key]['is_draft'] = false;
}
$request->merge($data);
$subtasks = $mastertask->subtasks()->createMany($request->all()['data']['employees']);
// syncnutie timeblocks
foreach ($request->all()['data']['employees'] as $key => $employee) {
if (! isset($data['data']['employees'][$key]['timeblocks']) || $data['data']['employees'][$key]['timeblocks'] == null) {
continue;
}
if ($data['data']['employees'][$key]['user_id'] == $employee['id']) {
$subtasks[$key]->syncTimeblocks($data['data']['employees'][$key]['timeblocks']);
}
}
// upload attachmentu
if ($request->file('data')) {
$attachments = Attachment::uploadAttachmentsToS3($request->file('data')['files'], $mastertask);
}
foreach ($subtasks as $subtask) {
// upload attachmentu
if ($request->file('data')) {
$subtask->attachments()->createMany($attachments);
}
}
return response()->json([
'message' => 'Sub Task created successfully',
'subtasks' => SubTaskResource::collection($subtasks),
], 201);
}
// TODO zabezpecit, ze ked sa meni user, tak nech sa dvojmu nevlozia jeho vykazy
public function update($id, Request $request)
{
$subtask = SubTask::find($id);
if (! $subtask) {
return response()->json([
'message' => 'Subtask not found.',
], 404);
}
// ak je sub task v stave ukončený, nedovolime update. vynimkou je iba znovuotvorenie mastertasku
if ($subtask->is_finished == 1 && isset($request->all()['data']['is_finished']) && $request->all()['data']['is_finished'] == 1) {
return response()->json([
'message' => 'Cannot update subtask with is_finished 1.',
], 400);
}
// ak chcem ukoncit subtask, ktory nema natrackovany cas, tak to nedovolim
if (isset($request->all()['data']['is_finished']) && $request->all()['data']['is_finished'] == 1 && $subtask->timesheets->count() == 0) {
return response()->json([
'message' => 'Cannot finish subtask with untracked time.',
], 400);
}
// validácia pri publikovaní draftu (is_draft: false)
$isPublishing = $subtask->is_draft && isset($request->all()['data']['is_draft']) && $request->all()['data']['is_draft'] == false;
// Pri publikovaní draftu s draft_data (viacero zamestnancov) - vytvoríme subtasky pre každého
if ($isPublishing && $subtask->draft_data) {
return $this->publishDraftWithMultipleEmployees($subtask, $request);
}
if ($isPublishing) {
$publishValidator = Validator::make([
'master_task_id' => $subtask->master_task_id,
'user_id' => $request->all()['data']['employee_id'] ?? $subtask->user_id,
'estimated_time' => $request->all()['data']['duration'] ?? $subtask->estimated_time,
], [
'master_task_id' => 'required',
'user_id' => 'required',
'estimated_time' => 'required|numeric|min:0',
]);
if ($publishValidator->fails()) {
return response()->json([
'message' => 'Draft cannot be published. Missing required fields.',
'errors' => $publishValidator->errors(),
], 400);
}
}
// Pre drafty s employees dátami - aktualizujeme aj draft_data
if ($subtask->is_draft && isset($request->all()['data']['employees'])) {
$subtask->draft_data = $request->all()['data']['employees'];
$subtask->save();
}
// Pre drafty je validácia duration voliteľná
if (! $subtask->is_draft) {
$validator = Validator::make($request->all(), [
'data.duration' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
}
// validacia na max 50MB size pre attachmenty. neslo dat do klasickeho laravel validation, tak je to separe
if ($files = $request->file('data')) {
foreach ($files['files'] as $file) {
if ($file->getSize() > 52428800) {
return response()->json([
'files' => 'File size must be less than 50MB',
], 400);
}
}
}
// upratanie requestu, pretoze frontend pouziva inu nomenklaturu fieldov ako appka
$data['data'] = $request->all()['data'];
$data['data']['user_id'] = $request->all()['data']['employee_id'] ?? $subtask->user_id;
$data['data']['title'] = isset($request->all()['data']['title']) ? $request->all()['data']['title'] : null;
$data['data']['deadline'] = isset($request->all()['data']['deadline']) ? $request->all()['data']['deadline'] : null;
$data['data']['estimated_time'] = $request->all()['data']['duration'] ?? $subtask->estimated_time;
$data['data']['updated_by'] = User::getUserIdFromBearerToken($request->bearerToken());
// publikovanie draftu
if (isset($request->all()['data']['is_draft'])) {
$data['data']['is_draft'] = $request->all()['data']['is_draft'];
}
$request->merge($data);
$subtask->update($request->all()['data']);
// upload attachmentu
if ($request->file('data')) {
$attachments = Attachment::uploadAttachmentsToS3($request->file('data')['files'], $subtask->mastertask);
$subtask->attachments()->createMany($attachments);
}
// syncnutie vykazu
if (isset($data['data']['timesheets'])) {
$subtask->syncTimesheets($data['data']['timesheets']);
}
// syncnutie timeblocks
if (isset($data['data']['timeblocks'])) {
$subtask->syncTimeblocks($data['data']['timeblocks']);
}
$message = $isPublishing ? 'Draft published successfully' : 'Subtask updated successfully';
return response()->json([
'message' => $message,
'subtask' => new SubTaskResource($subtask),
]);
}
/**
* Publikovanie draftu s viacerými zamestnancami - rozdelí draft na jednotlivé subtasky
*/
private function publishDraftWithMultipleEmployees(SubTask $draft, Request $request)
{
$employees = $draft->draft_data;
// Ak je employees pole prázdne, zlyhaj
if (empty($employees)) {
return response()->json([
'message' => 'Draft cannot be published. No employees data found.',
], 400);
}
// Validácia a doplnenie duration z timeblocks
foreach ($employees as $key => $employee) {
if (! isset($employee['id'])) {
return response()->json([
'message' => 'Draft cannot be published. Each employee must have id.',
], 400);
}
// Ak duration nie je nastavený, vypočítaj ho z timeblocks
if (! isset($employee['duration']) || $employee['duration'] === null) {
if (isset($employee['timeblocks']) && ! empty($employee['timeblocks'])) {
$totalDuration = 0;
foreach ($employee['timeblocks'] as $timeblock) {
if (isset($timeblock['start']) && isset($timeblock['end'])) {
$start = Carbon::parse($timeblock['start']);
$end = Carbon::parse($timeblock['end']);
$totalDuration += $end->diffInMinutes($start);
}
}
$employees[$key]['duration'] = $totalDuration;
} else {
return response()->json([
'message' => 'Draft cannot be published. Each employee must have duration or timeblocks.',
], 400);
}
}
}
$mastertask = $draft->mastertask;
$createdSubtasks = [];
// Vytvoríme subtask pre každého zamestnanca
foreach ($employees as $employee) {
$subtaskData = [
'user_id' => $employee['id'],
'title' => $draft->title,
'description' => $draft->description,
'deadline' => $draft->deadline,
'estimated_time' => $employee['duration'],
'created_by' => $draft->created_by,
'is_draft' => false,
'draft_data' => null,
];
$newSubtask = $mastertask->subtasks()->create($subtaskData);
// Sync timeblocks ak existujú
if (isset($employee['timeblocks']) && ! empty($employee['timeblocks'])) {
$newSubtask->syncTimeblocks($employee['timeblocks']);
}
// Skopírujeme attachmenty z pôvodného draftu
foreach ($draft->attachments as $attachment) {
$newSubtask->attachments()->create([
'name' => $attachment->name,
'path' => $attachment->path,
'size' => $attachment->size,
'type' => $attachment->type,
]);
}
$createdSubtasks[] = $newSubtask;
}
// Zmažeme pôvodný draft
$draft->timeblocks()->delete();
$draft->attachments()->delete(); // attachmenty sú už skopírované
$draft->delete();
return response()->json([
'message' => 'Draft published successfully',
'subtasks' => SubTaskResource::collection($createdSubtasks),
], 201);
}
public function delete($id)
{
$subtask = SubTask::find($id);
// ak existuje timesheet k subtasku, nemoze sa zmazat
if ($subtask->timesheets()->exists()) {
return response()->json([
'message' => 'Cannot delete subtask with submitted timesheets.',
], 400);
}
$subtask->timeblocks()->delete();
$subtask->deleteAttachments();
$subtask->delete();
return response()->json([
'message' => 'Subtask deleted successfully.',
'subtask' => $subtask,
]);
}
public function submitTimesheet($id, Request $request)
{
$subtask = SubTask::find($id);
$validator = Validator::make($request->all(), [
'data.employee_id' => 'required',
'data.time' => 'required',
]);
// validacia, ci dany user moze submitovat timesheet - ci mu patri subtask
$validator->after(function ($validator) use ($subtask, $request) {
if ($subtask->user_id != $request->all()['data']['employee_id']) {
$validator->errors()->add(
'data.employee_id',
'This user cannot submit timesheet for the selected subtask. Subtask is assigned to user with id: '.$subtask->user_id.'.'
);
}
});
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
$subtask->timesheets()->insert([
'time' => $request->all()['data']['time'],
'user_id' => $request->all()['data']['employee_id'],
'sub_task_id' => $subtask->id,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
]);
return response()->json([
'message' => 'Subtask timesheet submitted successfully.',
'subtask' => $subtask,
], 201);
}
}