ultimatepos/Modules/Connector/Http/Controllers/Api/V2/ProjectExtendedController.php

1032 lines
40 KiB
PHP

<?php
namespace Modules\Connector\Http\Controllers\Api\V2;
use App\BusinessLocation;
use App\Contact;
use App\DocumentAndNote;
use App\Events\TransactionPaymentAdded;
use App\InvoiceScheme;
use App\Media;
use App\Services\Api\BusinessContextService;
use App\TaxRate;
use App\TransactionPayment;
use App\User;
use App\Utils\ModuleUtil;
use App\Utils\TransactionUtil;
use App\Utils\Util;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Modules\Project\Entities\InvoiceLine;
use Modules\Project\Entities\Project;
use Modules\Project\Entities\ProjectCategory;
use Modules\Project\Entities\ProjectTask;
use Modules\Project\Entities\ProjectTaskComment;
use Modules\Project\Entities\ProjectTimeLog;
use Modules\Project\Entities\ProjectTransaction;
use Modules\Project\Entities\ProjectUser;
use Modules\Project\Utils\ProjectUtil;
class ProjectExtendedController extends BaseController
{
public function __construct(
protected BusinessContextService $contextService,
protected ModuleUtil $moduleUtil,
protected ProjectUtil $projectUtil,
protected Util $commonUtil,
protected TransactionUtil $transactionUtil
) {
}
protected function ensureProject(): int
{
if (! $this->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]);
}
}