moduleUtil->isModuleInstalled('Project')) { abort(403, 'Unauthorized action.'); } return $this->contextService->getBusinessId(request()); } protected function isAdmin(int $business_id): bool { return $this->moduleUtil->is_admin(Auth::user(), $business_id); } public function resources(Request $request) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $users = User::forDropdown($business_id, false); $categories = ProjectCategory::where('business_id', $business_id)->pluck('name', 'id'); $customers = Contact::customersDropdown($business_id, false); $projects_query = Project::where('business_id', $business_id); if (! $is_admin) { $projects_query->whereHas('members', fn ($q) => $q->where('user_id', $user_id)); } $projects = $projects_query->pluck('name', 'id'); return $this->success([ 'users' => $users, 'customers' => $customers, 'categories' => $categories, 'projects' => $projects, 'project_statuses' => Project::statusDropdown(), 'task_statuses' => ProjectTask::taskStatuses(), 'priorities' => ProjectTask::prioritiesDropdown(), ]); } public function projectsKanban(Request $request) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $statuses = Project::statusDropdown(); $projects = Project::with('customer', 'members', 'lead', 'categories') ->where('business_id', $business_id); if (! $is_admin) { $projects->whereHas('members', fn ($q) => $q->where('user_id', $user_id)); } if ($request->filled('status')) { $projects->where('status', $request->input('status')); } if ($request->filled('end_date')) { $end_date = $request->input('end_date'); if ($end_date === 'overdue') { $projects->where('end_date', '<', Carbon::today())->where('status', '!=', 'completed'); } elseif ($end_date === 'today') { $projects->where('end_date', Carbon::today())->where('status', '!=', 'completed'); } elseif ($end_date === 'less_than_one_week') { $projects->whereBetween('end_date', [Carbon::today(), Carbon::today()->addWeek()]) ->where('status', '!=', 'completed'); } } if ($request->filled('category_id')) { $category_id = $request->input('category_id'); $projects->whereHas('categories', fn ($q) => $q->where('id', $category_id)); } $grouped = $projects->get()->groupBy('status'); $columns = []; foreach ($statuses as $key => $label) { $items = $grouped[$key] ?? collect(); $cards = []; foreach ($items as $project) { $assigned_to = []; foreach ($project->members as $member) { $assigned_to[] = [ 'name' => $member->user_full_name ?? '', 'avatar' => $member->media->display_url ?? null, ]; } $tags = $project->categories->pluck('name')->values()->all(); $cards[] = [ 'id' => $project->id, 'title' => $project->name, 'end_date' => $project->end_date, 'customer' => $project->customer->name ?? '', 'lead' => $project->lead->user_full_name ?? '', 'assigned_to' => $assigned_to, 'tags' => $tags, 'has_description' => ! empty($project->description), ]; } $columns[] = [ 'id' => $key, 'title' => $label, 'cards' => $cards, ]; } return $this->success(['columns' => $columns]); } public function tasksKanban(Request $request) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $statuses = ProjectTask::taskStatuses(); $query = ProjectTask::with(['members', 'project', 'comments']) ->where('business_id', $business_id); if (empty($request->input('project_id')) && ! $is_admin) { $query->whereHas('members', fn ($q) => $q->where('user_id', $user_id)); } if ($request->filled('project_id')) { $query->where('project_id', $request->input('project_id')); } if ($request->filled('user_id')) { $filter_user = $request->input('user_id'); $query->whereHas('members', fn ($q) => $q->where('user_id', $filter_user)); } if ($request->filled('status')) { $query->where('status', $request->input('status')); } if ($request->filled('priority')) { $query->where('priority', $request->input('priority')); } if ($request->filled('due_date')) { $due = $request->input('due_date'); if ($due === 'overdue') { $query->where('due_date', '<', Carbon::today())->where('status', '!=', 'completed'); } elseif ($due === 'today') { $query->where('due_date', Carbon::today())->where('status', '!=', 'completed'); } elseif ($due === 'less_than_one_week') { $query->whereBetween('due_date', [Carbon::today(), Carbon::today()->addWeek()]) ->where('status', '!=', 'completed'); } } $grouped = $query->get()->groupBy('status'); $columns = []; foreach ($statuses as $key => $label) { $items = $grouped[$key] ?? collect(); $cards = []; foreach ($items as $task) { $assigned_to = []; foreach ($task->members as $member) { $assigned_to[] = [ 'name' => $member->user_full_name ?? '', 'avatar' => $member->media->display_url ?? null, ]; } $cards[] = [ 'id' => $task->id, 'title' => $task->subject, 'subtitle' => $task->task_id, 'project_id' => $task->project_id, 'project' => $task->project->name ?? '', 'due_date' => $task->due_date, 'priority' => $task->priority, 'assigned_to' => $assigned_to, 'tags' => [$task->priority], 'has_description' => ! empty($task->description), 'has_comments' => $task->comments->count() > 0, ]; } $columns[] = [ 'id' => $key, 'title' => $label, 'cards' => $cards, ]; } return $this->success(['columns' => $columns]); } public function updateProjectStatus(Request $request, $id) { $business_id = $this->ensureProject(); $request->validate([ 'status' => 'required|in:not_started,in_progress,on_hold,cancelled,completed', ]); $project = Project::where('business_id', $business_id)->findOrFail($id); $project->status = $request->input('status'); $project->save(); return $this->success($project, __('lang_v1.success')); } public function updateTaskStatus(Request $request, $id) { $this->ensureProject(); $request->validate([ 'status' => 'required|in:not_started,in_progress,on_hold,cancelled,completed', 'project_id' => 'required|integer', ]); $task = ProjectTask::where('project_id', $request->input('project_id')) ->findOrFail($id); $task->status = $request->input('status'); $task->save(); return $this->success($task, __('lang_v1.success')); } protected function projectQuery(int $business_id, int $user_id, bool $is_admin) { $query = Project::with(['customer', 'members', 'lead', 'categories']) ->where('business_id', $business_id); if (! $is_admin) { $query->whereHas('members', fn ($q) => $q->where('user_id', $user_id)); } return $query; } public function showProject(Request $request, $id) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $project = $this->projectQuery($business_id, $user_id, $is_admin) ->withCount(['tasks as incomplete_task' => fn ($q) => $q->where('status', '!=', 'completed')]) ->findOrFail($id); $timelog = ProjectTimeLog::where('project_id', $id) ->select(DB::raw('SUM(TIMESTAMPDIFF(SECOND, start_datetime, end_datetime)) as total_seconds')) ->first(); $invoice_paid = ProjectTransaction::leftJoin('transaction_payments as TP', 'transactions.id', '=', 'TP.transaction_id') ->where('transactions.business_id', $business_id) ->where('pjt_project_id', $id) ->select(DB::raw('SUM(TP.amount) as paid')) ->value('paid'); $invoice_total = ProjectTransaction::where('business_id', $business_id) ->where('pjt_project_id', $id) ->sum('final_total'); $is_lead = $this->projectUtil->isProjectLead($user_id, $id); $is_lead_or_admin = $is_admin || $is_lead; return $this->success([ 'project' => $project, 'timelog_total_seconds' => (int) ($timelog->total_seconds ?? 0), 'invoice_paid' => (float) ($invoice_paid ?? 0), 'invoice_total' => (float) $invoice_total, 'is_lead_or_admin' => $is_lead_or_admin, 'can_pay_invoice' => $this->canPayProjectInvoice($business_id, $user_id, $is_admin, (int) $id), 'can_crud_task' => $this->projectUtil->canMemberCrudTask($business_id, $is_lead_or_admin ? null : $user_id, $id), 'can_crud_timelog' => $this->projectUtil->canMemberCrudTimelog($business_id, $user_id, $id), ]); } public function storeProject(Request $request) { $business_id = $this->ensureProject(); $request->validate([ 'name' => 'required|string|max:191', 'lead_id' => 'required|integer', 'status' => 'required|in:not_started,in_progress,on_hold,cancelled,completed', ]); $input = $request->only('name', 'description', 'contact_id', 'status', 'lead_id'); $input['start_date'] = $request->filled('start_date') ? $this->commonUtil->uf_date($request->input('start_date')) : null; $input['end_date'] = $request->filled('end_date') ? $this->commonUtil->uf_date($request->input('end_date')) : null; $input['business_id'] = $business_id; $input['created_by'] = Auth::id(); $input['settings'] = [ 'enable_timelog' => 1, 'enable_invoice' => 1, 'enable_notes_documents' => 1, 'members_crud_task' => 0, 'members_crud_note' => 0, 'members_crud_timelog' => 0, 'task_view' => 'list_view', 'task_id_prefix' => '#', ]; $members = $request->input('user_id', []); if (! is_array($members)) { $members = []; } $members[] = $request->input('lead_id'); DB::beginTransaction(); $project = Project::create($input); $project->members()->sync(array_unique($members)); if ($request->filled('category_id')) { $project->categories()->sync((array) $request->input('category_id')); } DB::commit(); return $this->success($project->load(['customer', 'members', 'lead', 'categories']), __('lang_v1.success'), 201); } public function updateProject(Request $request, $id) { $business_id = $this->ensureProject(); $project = Project::where('business_id', $business_id)->findOrFail($id); $input = $request->only('name', 'description', 'contact_id', 'status', 'lead_id'); $input['start_date'] = $request->filled('start_date') ? $this->commonUtil->uf_date($request->input('start_date')) : $project->start_date; $input['end_date'] = $request->filled('end_date') ? $this->commonUtil->uf_date($request->input('end_date')) : $project->end_date; $project->update($input); if ($request->has('user_id')) { $members = (array) $request->input('user_id', []); $members[] = $request->input('lead_id', $project->lead_id); $project->members()->sync(array_unique($members)); } if ($request->has('category_id')) { $project->categories()->sync((array) $request->input('category_id')); } return $this->success($project->fresh(['customer', 'members', 'lead', 'categories'])); } public function showTask(Request $request, $projectId, $id) { $this->ensureProject(); $task = ProjectTask::with([ 'members', 'createdBy', 'project', 'comments' => fn ($q) => $q->latest(), 'comments.media', 'comments.commentedBy', 'timeLogs', 'timeLogs.user', ]) ->where('project_id', $projectId) ->findOrFail($id); return $this->success($task); } public function storeTask(Request $request, $projectId) { $business_id = $this->ensureProject(); $request->validate([ 'subject' => 'required|string|max:191', 'priority' => 'required|in:low,medium,high,urgent', 'status' => 'required|in:not_started,in_progress,on_hold,cancelled,completed', ]); $input = $request->only('subject', 'description', 'priority', 'status', 'custom_field_1', 'custom_field_2', 'custom_field_3', 'custom_field_4'); $input['project_id'] = $projectId; $input['start_date'] = $request->filled('start_date') ? $this->commonUtil->uf_date($request->input('start_date')) : null; $input['due_date'] = $request->filled('due_date') ? $this->commonUtil->uf_date($request->input('due_date')) : null; $input['created_by'] = Auth::id(); $input['business_id'] = $business_id; $input['task_id'] = $this->projectUtil->generateTaskId($business_id, $projectId); $task = ProjectTask::create($input); if ($request->filled('user_id')) { $task->members()->sync((array) $request->input('user_id')); } return $this->success($task->load('members'), __('lang_v1.success'), 201); } public function updateTask(Request $request, $projectId, $id) { $this->ensureProject(); $task = ProjectTask::where('project_id', $projectId)->findOrFail($id); $input = $request->only('subject', 'description', 'priority', 'status', 'custom_field_1', 'custom_field_2', 'custom_field_3', 'custom_field_4'); $input['start_date'] = $request->filled('start_date') ? $this->commonUtil->uf_date($request->input('start_date')) : $task->start_date; $input['due_date'] = $request->filled('due_date') ? $this->commonUtil->uf_date($request->input('due_date')) : $task->due_date; $task->update($input); if ($request->has('user_id')) { $task->members()->sync((array) $request->input('user_id', [])); } return $this->success($task->fresh(['members', 'comments', 'timeLogs'])); } public function storeTaskComment(Request $request, $projectId, $taskId) { $business_id = $this->ensureProject(); $request->validate(['comment' => 'required|string']); $task = ProjectTask::where('project_id', $projectId)->findOrFail($taskId); $comment = $task->comments()->create([ 'comment' => $request->input('comment'), 'commented_by' => Auth::id(), ]); $file_names = $request->input('file_name', []); if (! empty($file_names)) { $names = is_array($file_names) ? $file_names : explode(',', $file_names); Media::attachMediaToModel($comment, $business_id, $names); } return $this->success($comment->load(['commentedBy', 'media']), __('lang_v1.success'), 201); } public function uploadCommentMedia(Request $request) { $this->ensureProject(); $request->validate(['file' => 'required|file|max:10240']); $file_name = Media::uploadFile($request->file('file')); return $this->success(['file_name' => $file_name]); } public function destroyTaskComment(Request $request, $projectId, $taskId, $commentId) { $this->ensureProject(); $comment = ProjectTaskComment::where('project_task_id', $taskId)->findOrFail($commentId); $comment->media()->delete(); $comment->delete(); return $this->success(null, __('lang_v1.success')); } public function timeLogs(Request $request, $projectId) { $this->ensureProject(); $logs = ProjectTimeLog::where('project_id', $projectId) ->with(['task', 'user']) ->orderByDesc('start_datetime') ->paginate($request->input('per_page', 25)); return $this->paginated($logs); } public function storeTimeLog(Request $request, $projectId) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $is_lead = $this->projectUtil->isProjectLead($user_id, $projectId); $request->validate([ 'start_datetime' => 'required', 'end_datetime' => 'required', ]); $input = $request->only('project_task_id', 'note'); $input['project_id'] = $projectId; $input['start_datetime'] = $this->commonUtil->uf_date($request->input('start_datetime'), true); $input['end_datetime'] = $this->commonUtil->uf_date($request->input('end_datetime'), true); $input['created_by'] = $user_id; $input['user_id'] = ($is_admin || $is_lead) ? $request->input('user_id', $user_id) : $user_id; $log = ProjectTimeLog::create($input); return $this->success($log->load(['task', 'user']), __('lang_v1.success'), 201); } public function destroyTimeLog(Request $request, $projectId, $id) { $this->ensureProject(); ProjectTimeLog::where('project_id', $projectId)->where('id', $id)->delete(); return $this->success(null, __('lang_v1.success')); } protected function ensureInvoiceAccess(int $business_id, int $user_id, bool $is_admin, int $projectId): void { if ($is_admin || $this->projectUtil->isProjectLead($user_id, $projectId)) { return; } abort(403, 'Unauthorized action.'); } protected function canPayProjectInvoice(int $business_id, int $user_id, bool $is_admin, int $projectId): bool { if ($is_admin || $this->projectUtil->isProjectLead($user_id, $projectId)) { return true; } $user = Auth::user(); if ($user->can('sell.payments') || $user->can('purchase.payments')) { return $this->projectUtil->isProjectMember($user_id, $projectId); } return false; } protected function ensureInvoicePaymentAccess(int $business_id, int $user_id, bool $is_admin, int $projectId): void { if (! $this->canPayProjectInvoice($business_id, $user_id, $is_admin, $projectId)) { abort(403, 'Unauthorized action.'); } } protected function documentPermissions(Project $project, int $user_id, bool $is_admin): array { if ($is_admin || $this->projectUtil->isProjectLead($user_id, $project->id)) { return ['view', 'create', 'delete']; } if ($this->projectUtil->isProjectMember($user_id, $project->id)) { if (! empty($project->settings['members_crud_note'])) { return ['view', 'create', 'delete']; } return ['view']; } return []; } public function invoiceResources(Request $request) { $business_id = $this->ensureProject(); $tax_dropdown = TaxRate::forBusinessDropdown($business_id, true, true); return $this->success([ 'invoice_schemes' => InvoiceScheme::forDropdown($business_id), 'default_scheme' => InvoiceScheme::getDefault($business_id), 'statuses' => ProjectTransaction::invoiceStatuses(), 'discount_types' => ProjectTransaction::discountTypes(), 'taxes' => $tax_dropdown['tax_rates'], 'tax_attributes' => $tax_dropdown['attributes'], 'locations' => BusinessLocation::forDropdown($business_id), 'customers' => Contact::customersDropdown($business_id, false), ]); } public function projectInvoices(Request $request, $projectId) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $this->ensureInvoiceAccess($business_id, $user_id, $is_admin, (int) $projectId); $invoices = ProjectTransaction::where('business_id', $business_id) ->where('pjt_project_id', $projectId) ->with('contact') ->orderByDesc('transaction_date') ->paginate($request->input('per_page', 25)); return $this->paginated($invoices); } public function showProjectInvoice(Request $request, $projectId, $id) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $this->ensureInvoiceAccess($business_id, $user_id, $is_admin, (int) $projectId); $invoice = ProjectTransaction::where('business_id', $business_id) ->where('pjt_project_id', $projectId) ->with(['contact', 'invoiceLines', 'invoiceLines.tax', 'project', 'payment_lines']) ->findOrFail($id); $data = $invoice->toArray(); $data['can_pay_invoice'] = $this->canPayProjectInvoice($business_id, $user_id, $is_admin, (int) $projectId); return $this->success($data); } public function storeProjectInvoice(Request $request, $projectId) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $this->ensureInvoiceAccess($business_id, $user_id, $is_admin, (int) $projectId); $request->validate([ 'pjt_title' => 'required|string|max:191', 'contact_id' => 'required|integer', 'transaction_date' => 'required', 'status' => 'required|in:final,draft', 'final_total' => 'required|numeric', 'lines' => 'required|array|min:1', 'lines.*.task' => 'required|string', 'lines.*.rate' => 'required|numeric', 'lines.*.quantity' => 'required|numeric', 'lines.*.total' => 'required|numeric', ]); DB::beginTransaction(); $input = $request->only('pjt_title', 'contact_id', 'pay_term_number', 'pay_term_type', 'status', 'discount_type', 'staff_note', 'additional_notes', 'location_id'); $input['business_id'] = $business_id; $input['created_by'] = $user_id; $input['pjt_project_id'] = $projectId; $input['transaction_date'] = $this->commonUtil->uf_date($request->input('transaction_date')); $input['invoice_no'] = $this->transactionUtil->getInvoiceNumber($business_id, $input['status'], null, $request->input('invoice_scheme_id')); $input['discount_amount'] = $this->commonUtil->num_uf($request->input('discount_amount', 0)); $input['total_before_tax'] = $this->commonUtil->num_uf($request->input('total_before_tax', $request->input('final_total'))); $input['final_total'] = $this->commonUtil->num_uf($request->input('final_total')); $input['type'] = 'sell'; $input['sub_type'] = 'project_invoice'; $input['payment_status'] = 'due'; $invoice_lines = []; foreach ($request->input('lines', []) as $line) { $invoice_lines[] = [ 'task' => $line['task'], 'rate' => $this->commonUtil->num_uf($line['rate']), 'tax_rate_id' => $line['tax_rate_id'] ?? null, 'quantity' => $this->commonUtil->num_uf($line['quantity']), 'total' => $this->commonUtil->num_uf($line['total']), 'description' => $line['description'] ?? null, ]; } $transaction = ProjectTransaction::create($input); $transaction->invoiceLines()->createMany($invoice_lines); DB::commit(); return $this->success($transaction->load(['contact', 'invoiceLines', 'invoiceLines.tax']), __('lang_v1.success'), 201); } public function updateProjectInvoice(Request $request, $projectId, $id) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $this->ensureInvoiceAccess($business_id, $user_id, $is_admin, (int) $projectId); $request->validate([ 'pjt_title' => 'required|string|max:191', 'contact_id' => 'required|integer', 'transaction_date' => 'required', 'status' => 'required|in:final,draft', 'final_total' => 'required|numeric', 'lines' => 'required|array|min:1', 'lines.*.task' => 'required|string', 'lines.*.rate' => 'required|numeric', 'lines.*.quantity' => 'required|numeric', 'lines.*.total' => 'required|numeric', ]); DB::beginTransaction(); $transaction = ProjectTransaction::where('business_id', $business_id) ->where('pjt_project_id', $projectId) ->findOrFail($id); $input = $request->only('pjt_title', 'contact_id', 'pay_term_number', 'pay_term_type', 'status', 'discount_type', 'staff_note', 'additional_notes', 'location_id'); $input['transaction_date'] = $this->commonUtil->uf_date($request->input('transaction_date')); $input['discount_amount'] = $this->commonUtil->num_uf($request->input('discount_amount', 0)); $input['total_before_tax'] = $this->commonUtil->num_uf($request->input('total_before_tax', $request->input('final_total'))); $input['final_total'] = $this->commonUtil->num_uf($request->input('final_total')); $transaction->update($input); $existing_ids = []; $new_lines = []; foreach ($request->input('lines', []) as $line) { if (! empty($line['id'])) { $invoice_line = InvoiceLine::where('transaction_id', $id)->findOrFail($line['id']); $invoice_line->update([ 'task' => $line['task'], 'tax_rate_id' => $line['tax_rate_id'] ?? null, 'description' => $line['description'] ?? null, 'rate' => $this->commonUtil->num_uf($line['rate']), 'quantity' => $this->commonUtil->num_uf($line['quantity']), 'total' => $this->commonUtil->num_uf($line['total']), ]); $existing_ids[] = $invoice_line->id; } else { $new_lines[] = [ 'task' => $line['task'], 'rate' => $this->commonUtil->num_uf($line['rate']), 'tax_rate_id' => $line['tax_rate_id'] ?? null, 'quantity' => $this->commonUtil->num_uf($line['quantity']), 'total' => $this->commonUtil->num_uf($line['total']), 'description' => $line['description'] ?? null, ]; } } InvoiceLine::where('transaction_id', $id)->whereNotIn('id', $existing_ids)->delete(); if (! empty($new_lines)) { $transaction->invoiceLines()->createMany($new_lines); } DB::commit(); return $this->success($transaction->fresh(['contact', 'invoiceLines', 'invoiceLines.tax', 'payment_lines'])); } public function storeProjectInvoicePayment(Request $request, $projectId, $id) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $this->ensureInvoicePaymentAccess($business_id, $user_id, $is_admin, (int) $projectId); $request->validate([ 'amount' => 'required|numeric|min:0.01', 'method' => 'required|string|max:191', ]); $transaction = ProjectTransaction::where('business_id', $business_id) ->where('pjt_project_id', $projectId) ->with('contact') ->findOrFail($id); if ($transaction->payment_status === 'paid') { return $this->error(__('purchase.amount_already_paid'), 422); } DB::beginTransaction(); $transaction_before = $transaction->replicate(); $inputs = $request->only(['method', 'note', 'card_number', 'card_holder_name', 'cheque_number', 'bank_account_number']); $inputs['paid_on'] = $this->commonUtil->uf_date($request->input('paid_on', now()), true); $inputs['transaction_id'] = $transaction->id; $inputs['amount'] = $this->commonUtil->num_uf($request->input('amount')); $inputs['created_by'] = $user_id; $inputs['payment_for'] = $transaction->contact_id; $inputs['business_id'] = $business_id; if (! empty($request->input('account_id')) && $inputs['method'] !== 'advance') { $inputs['account_id'] = $request->input('account_id'); } $ref_count = $this->transactionUtil->setAndGetReferenceCount('sell_payment'); $inputs['payment_ref_no'] = $this->transactionUtil->generateReferenceNumber('sell_payment', $ref_count); $tp = TransactionPayment::create($inputs); $inputs['transaction_type'] = $transaction->type; event(new TransactionPaymentAdded($tp, $inputs)); $transaction->payment_status = $this->transactionUtil->updatePaymentStatus($transaction->id, $transaction->final_total); $transaction->save(); $this->transactionUtil->activityLog($transaction, 'payment_edited', $transaction_before); DB::commit(); return $this->success($transaction->fresh(['contact', 'invoiceLines', 'payment_lines']), __('purchase.payment_added_success')); } public function destroyProjectInvoice(Request $request, $projectId, $id) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $this->ensureInvoiceAccess($business_id, $user_id, $is_admin, (int) $projectId); $transaction = ProjectTransaction::where('business_id', $business_id) ->where('pjt_project_id', $projectId) ->findOrFail($id); $transaction->invoiceLines()->delete(); $transaction->delete(); return $this->success(null, __('lang_v1.success')); } public function projectDocuments(Request $request, $projectId) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $project = $this->projectQuery($business_id, $user_id, $is_admin)->findOrFail($projectId); $permissions = $this->documentPermissions($project, $user_id, $is_admin); if (! in_array('view', $permissions)) { abort(403, 'Unauthorized action.'); } $docs = DocumentAndNote::where('business_id', $business_id) ->where('notable_id', $projectId) ->where('notable_type', Project::class) ->where(function ($query) use ($user_id) { $query->where('is_private', 0) ->orWhere(function ($q) use ($user_id) { $q->where('is_private', 1)->where('created_by', $user_id); }); }) ->with(['createdBy', 'media']) ->orderByDesc('created_at') ->paginate($request->input('per_page', 25)); return $this->paginated($docs); } public function storeProjectDocument(Request $request, $projectId) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $project = $this->projectQuery($business_id, $user_id, $is_admin)->findOrFail($projectId); $permissions = $this->documentPermissions($project, $user_id, $is_admin); if (! in_array('create', $permissions)) { abort(403, 'Unauthorized action.'); } $request->validate(['heading' => 'required|string|max:191']); DB::beginTransaction(); $input = $request->only('heading', 'description', 'is_private'); $input['business_id'] = $business_id; $input['created_by'] = $user_id; $input['is_private'] = ! empty($input['is_private']) ? 1 : 0; $doc = $project->documentsAndnote()->create($input); $file_names = $request->input('file_name', []); if (! empty($file_names)) { $names = is_array($file_names) ? $file_names : explode(',', $file_names); Media::attachMediaToModel($doc, $business_id, $names); } DB::commit(); return $this->success($doc->load(['createdBy', 'media']), __('lang_v1.success'), 201); } public function destroyProjectDocument(Request $request, $projectId, $id) { $business_id = $this->ensureProject(); $user_id = Auth::id(); $is_admin = $this->isAdmin($business_id); $project = $this->projectQuery($business_id, $user_id, $is_admin)->findOrFail($projectId); $permissions = $this->documentPermissions($project, $user_id, $is_admin); if (! in_array('delete', $permissions)) { abort(403, 'Unauthorized action.'); } $doc = DocumentAndNote::where('business_id', $business_id) ->where('notable_id', $projectId) ->where('notable_type', Project::class) ->findOrFail($id); $doc->media()->delete(); $doc->delete(); return $this->success(null, __('lang_v1.success')); } public function employeeTimeLogReport(Request $request) { $business_id = $this->ensureProject(); $user_ids = (array) $request->input('user_id', []); $project_ids = (array) $request->input('project_id', []); $start_date = $request->input('start_date'); $end_date = $request->input('end_date'); $query = ProjectUser::with(['projects' => function ($q) use ($project_ids) { if (! empty($project_ids)) { $q->whereIn('pjt_projects.id', $project_ids); } }, 'projects.timeLogs' => function ($q) use ($start_date, $end_date) { if (! empty($start_date) && ! empty($end_date)) { if ($start_date === $end_date) { $q->whereDate('start_datetime', $start_date); } else { $q->whereBetween('start_datetime', [$start_date, $end_date]); } } }, 'projects.timeLogs.task']) ->where('business_id', $business_id); if (! empty($user_ids)) { $query->whereIn('id', $user_ids); } $rows = []; foreach ($query->get() as $user) { $user_projects = []; $user_total = 0; foreach ($user->projects as $project) { $logs = []; $project_seconds = 0; foreach ($project->timeLogs as $timeLog) { if ($timeLog->user_id != $user->id) { continue; } $seconds = $timeLog->end_datetime && $timeLog->start_datetime ? Carbon::parse($timeLog->start_datetime)->diffInSeconds(Carbon::parse($timeLog->end_datetime)) : 0; $project_seconds += $seconds; $logs[] = [ 'id' => $timeLog->id, 'task' => $timeLog->task->subject ?? null, 'start_datetime' => $timeLog->start_datetime, 'end_datetime' => $timeLog->end_datetime, 'duration_seconds' => $seconds, 'note' => $timeLog->note, ]; } if (empty($logs)) { continue; } $user_total += $project_seconds; $user_projects[] = [ 'id' => $project->id, 'name' => $project->name, 'time_logs' => $logs, 'total_seconds' => $project_seconds, ]; } if (! empty($user_projects)) { $rows[] = [ 'id' => $user->id, 'name' => $user->user_full_name, 'projects' => $user_projects, 'total_seconds' => $user_total, ]; } } return $this->success(['users' => $rows]); } public function projectTimeLogReport(Request $request) { $business_id = $this->ensureProject(); $project_ids = (array) $request->input('project_id', []); $start_date = $request->input('start_date'); $end_date = $request->input('end_date'); $query = Project::with(['timeLogs' => function ($q) use ($start_date, $end_date) { if (! empty($start_date) && ! empty($end_date)) { if ($start_date === $end_date) { $q->whereDate('start_datetime', $start_date); } else { $q->whereBetween('start_datetime', [$start_date, $end_date]); } } }, 'timeLogs.task', 'timeLogs.user']) ->where('business_id', $business_id); if (! empty($project_ids)) { $query->whereIn('id', $project_ids); } $rows = []; foreach ($query->get() as $project) { $logs = []; $total_seconds = 0; foreach ($project->timeLogs as $timeLog) { $seconds = $timeLog->end_datetime && $timeLog->start_datetime ? Carbon::parse($timeLog->start_datetime)->diffInSeconds(Carbon::parse($timeLog->end_datetime)) : 0; $total_seconds += $seconds; $logs[] = [ 'id' => $timeLog->id, 'user' => $timeLog->user->user_full_name ?? null, 'task' => $timeLog->task->subject ?? null, 'start_datetime' => $timeLog->start_datetime, 'end_datetime' => $timeLog->end_datetime, 'duration_seconds' => $seconds, 'note' => $timeLog->note, ]; } if (! empty($logs)) { $rows[] = [ 'id' => $project->id, 'name' => $project->name, 'time_logs' => $logs, 'total_seconds' => $total_seconds, ]; } } return $this->success(['projects' => $rows]); } }