request->add(['entity_type' => 'leads']); } /** * Display a listing of the resource. */ public function index() { if (request()->ajax()) { return datagrid(LeadDataGrid::class)->process(); } if (request('pipeline_id')) { $pipeline = $this->pipelineRepository->find(request('pipeline_id')); } else { $pipeline = $this->pipelineRepository->getDefaultPipeline(); } return view('admin::leads.index', [ 'pipeline' => $pipeline, 'columns' => $this->getKanbanColumns(), ]); } /** * Returns a listing of the resource. */ public function get(): JsonResponse { if (request()->query('pipeline_id')) { $pipeline = $this->pipelineRepository->find(request()->query('pipeline_id')); } else { $pipeline = $this->pipelineRepository->getDefaultPipeline(); } if ($stageId = request()->query('pipeline_stage_id')) { $stages = $pipeline->stages->where('id', request()->query('pipeline_stage_id')); } else { $stages = $pipeline->stages; } foreach ($stages as $stage) { /** * We have to create a new instance of the lead repository every time, which is * why we're not using the injected one. */ $query = app(LeadRepository::class) ->pushCriteria(app(RequestCriteria::class)) ->where([ 'lead_pipeline_id' => $pipeline->id, 'lead_pipeline_stage_id' => $stage->id, ]); if ($userIds = bouncer()->getAuthorizedUserIds()) { $query->whereIn('leads.user_id', $userIds); } $stage->lead_value = (clone $query)->sum('lead_value'); $data[$stage->sort_order] = (new StageResource($stage))->jsonSerialize(); $data[$stage->sort_order]['leads'] = [ 'data' => LeadResource::collection($paginator = $query->with([ 'tags', 'type', 'source', 'user', 'person', 'person.organization', 'pipeline', 'pipeline.stages', 'stage', 'attribute_values', ])->paginate(10)), 'meta' => [ 'current_page' => $paginator->currentPage(), 'from' => $paginator->firstItem(), 'last_page' => $paginator->lastPage(), 'per_page' => $paginator->perPage(), 'to' => $paginator->lastItem(), 'total' => $paginator->total(), ], ]; } return response()->json($data); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::leads.create'); } /** * Store a newly created resource in storage. */ public function store(LeadForm $request): RedirectResponse|JsonResponse { Event::dispatch('lead.create.before'); $data = request()->all(); $data['status'] = 1; if (! empty($data['lead_pipeline_stage_id'])) { $stage = $this->stageRepository->findOrFail($data['lead_pipeline_stage_id']); $data['lead_pipeline_id'] = $stage->lead_pipeline_id; } else { if (empty($data['lead_pipeline_id'])) { $pipeline = $this->pipelineRepository->getDefaultPipeline(); $data['lead_pipeline_id'] = $pipeline->id; } else { $pipeline = $this->pipelineRepository->findOrFail($data['lead_pipeline_id']); } $stage = $pipeline->stages()->first(); $data['lead_pipeline_stage_id'] = $stage->id; } if (in_array($stage->code, ['won', 'lost'])) { $data['closed_at'] = Carbon::now(); } $lead = $this->leadRepository->create($data); if (request()->ajax()) { return response()->json([ 'message' => trans('admin::app.leads.create-success'), 'data' => new LeadResource($lead), ]); } Event::dispatch('lead.create.after', $lead); session()->flash('success', trans('admin::app.leads.create-success')); if (! empty($data['lead_pipeline_id'])) { $params['pipeline_id'] = $data['lead_pipeline_id']; } return redirect()->route('admin.leads.index', $params ?? []); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $lead = $this->leadRepository->findOrFail($id); return view('admin::leads.edit', compact('lead')); } /** * Display a resource. */ public function view(int $id) { $lead = $this->leadRepository->findOrFail($id); $userIds = bouncer()->getAuthorizedUserIds(); if ( $userIds && ! in_array($lead->user_id, $userIds) ) { return redirect()->route('admin.leads.index'); } return view('admin::leads.view', compact('lead')); } /** * Update the specified resource in storage. */ public function update(LeadForm $request, int $id): RedirectResponse|JsonResponse { Event::dispatch('lead.update.before', $id); $data = $request->all(); if (isset($data['lead_pipeline_stage_id'])) { $stage = $this->stageRepository->findOrFail($data['lead_pipeline_stage_id']); $data['lead_pipeline_id'] = $stage->lead_pipeline_id; } else { $pipeline = $this->pipelineRepository->getDefaultPipeline(); $stage = $pipeline->stages()->first(); $data['lead_pipeline_id'] = $pipeline->id; $data['lead_pipeline_stage_id'] = $stage->id; } $lead = $this->leadRepository->update($data, $id); Event::dispatch('lead.update.after', $lead); if (request()->ajax()) { return response()->json([ 'message' => trans('admin::app.leads.update-success'), ]); } session()->flash('success', trans('admin::app.leads.update-success')); if (request()->has('closed_at')) { return redirect()->back(); } else { return redirect()->route('admin.leads.index', $data['lead_pipeline_id']); } } /** * Update the lead attributes. */ public function updateAttributes(int $id) { $data = request()->all(); $attributes = $this->attributeRepository->findWhere([ 'entity_type' => 'leads', ['code', 'NOTIN', ['title', 'description']], ]); Event::dispatch('lead.update.before', $id); $lead = $this->leadRepository->update($data, $id, $attributes); Event::dispatch('lead.update.after', $lead); return response()->json([ 'message' => trans('admin::app.leads.update-success'), ]); } /** * Update the lead stage. */ public function updateStage(int $id) { $this->validate(request(), [ 'lead_pipeline_stage_id' => 'required', ]); $lead = $this->leadRepository->findOrFail($id); $stage = $lead->pipeline->stages() ->where('id', request()->input('lead_pipeline_stage_id')) ->firstOrFail(); Event::dispatch('lead.update.before', $id); $payload = request()->merge([ 'entity_type' => 'leads', 'lead_pipeline_stage_id' => $stage->id, ])->only([ 'closed_at', 'lost_reason', 'lead_pipeline_stage_id', 'entity_type', ]); $lead = $this->leadRepository->update($payload, $id, ['lead_pipeline_stage_id']); Event::dispatch('lead.update.after', $lead); return response()->json([ 'message' => trans('admin::app.leads.update-success'), ]); } /** * Search person results. */ public function search(): AnonymousResourceCollection { if ($userIds = bouncer()->getAuthorizedUserIds()) { $results = $this->leadRepository ->pushCriteria(app(RequestCriteria::class)) ->findWhereIn('user_id', $userIds); } else { $results = $this->leadRepository ->pushCriteria(app(RequestCriteria::class)) ->all(); } return LeadResource::collection($results); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $this->leadRepository->findOrFail($id); try { Event::dispatch('lead.delete.before', $id); $this->leadRepository->delete($id); Event::dispatch('lead.delete.after', $id); return response()->json([ 'message' => trans('admin::app.leads.destroy-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.leads.destroy-failed'), ], 400); } } /** * Mass update the specified resources. */ public function massUpdate(MassUpdateRequest $massUpdateRequest): JsonResponse { $leads = $this->leadRepository->findWhereIn('id', $massUpdateRequest->input('indices')); try { foreach ($leads as $lead) { Event::dispatch('lead.update.before', $lead->id); $lead = $this->leadRepository->find($lead->id); $lead?->update(['lead_pipeline_stage_id' => $massUpdateRequest->input('value')]); Event::dispatch('lead.update.before', $lead->id); } return response()->json([ 'message' => trans('admin::app.leads.update-success'), ]); } catch (\Exception $th) { return response()->json([ 'message' => trans('admin::app.leads.update-failed'), ], 400); } } /** * Mass delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $leads = $this->leadRepository->findWhereIn('id', $massDestroyRequest->input('indices')); try { foreach ($leads as $lead) { Event::dispatch('lead.delete.before', $lead->id); $this->leadRepository->delete($lead->id); Event::dispatch('lead.delete.after', $lead->id); } return response()->json([ 'message' => trans('admin::app.leads.destroy-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.leads.destroy-failed'), ]); } } /** * Attach product to lead. */ public function addProduct(int $leadId): JsonResponse { $product = $this->productRepository->updateOrCreate( [ 'lead_id' => $leadId, 'product_id' => request()->input('product_id'), ], array_merge( request()->all(), [ 'lead_id' => $leadId, 'amount' => request()->input('price') * request()->input('quantity'), ], ) ); return response()->json([ 'data' => $product, 'message' => trans('admin::app.leads.update-success'), ]); } /** * Remove product attached to lead. */ public function removeProduct(int $id): JsonResponse { try { Event::dispatch('lead.product.delete.before', $id); $this->productRepository->deleteWhere([ 'lead_id' => $id, 'product_id' => request()->input('product_id'), ]); Event::dispatch('lead.product.delete.after', $id); return response()->json([ 'message' => trans('admin::app.leads.destroy-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.leads.destroy-failed'), ]); } } /** * Kanban lookup. */ public function kanbanLookup() { $params = $this->validate(request(), [ 'column' => ['required'], 'search' => ['required', 'min:2'], ]); /** * Finding the first column from the collection. */ $column = collect($this->getKanbanColumns())->where('index', $params['column'])->firstOrFail(); /** * Fetching on the basis of column options. */ return app($column['filterable_options']['repository']) ->select([$column['filterable_options']['column']['label'].' as label', $column['filterable_options']['column']['value'].' as value']) ->where($column['filterable_options']['column']['label'], 'LIKE', '%'.$params['search'].'%') ->get() ->map ->only('label', 'value'); } /** * Get columns for the kanban view. */ private function getKanbanColumns(): array { return [ [ 'index' => 'id', 'label' => trans('admin::app.leads.index.kanban.columns.id'), 'type' => 'integer', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => null, 'filterable_options' => [], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'lead_value', 'label' => trans('admin::app.leads.index.kanban.columns.lead-value'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => null, 'filterable_options' => [], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'user_id', 'label' => trans('admin::app.leads.index.kanban.columns.sales-person'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => UserRepository::class, 'column' => [ 'label' => 'name', 'value' => 'id', ], ], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'person.id', 'label' => trans('admin::app.leads.index.kanban.columns.contact-person'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_options' => [], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => PersonRepository::class, 'column' => [ 'label' => 'name', 'value' => 'id', ], ], ], [ 'index' => 'lead_type_id', 'label' => trans('admin::app.leads.index.kanban.columns.lead-type'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => $this->typeRepository->all(['name as label', 'id as value'])->toArray(), 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'lead_source_id', 'label' => trans('admin::app.leads.index.kanban.columns.source'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => $this->sourceRepository->all(['name as label', 'id as value'])->toArray(), 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'tags.name', 'label' => trans('admin::app.leads.index.kanban.columns.tags'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_options' => [], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => TagRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], ], ]; } /** * Create lead with specified AI. */ public function createByAI() { $leadData = []; $errorMessages = []; foreach (request()->file('files') as $file) { $lead = $this->processFile($file); if ( isset($lead['status']) && $lead['status'] === 'error' ) { $errorMessages[] = $lead['message']; } else { $leadData[] = $lead; } } if (isset($errorMessages[0]['code'])) { return response()->json(MagicAI::errorHandler($errorMessages[0]['message'])); } if ( empty($leadData) && ! empty($errorMessages) ) { return response()->json(MagicAI::errorHandler(implode(', ', $errorMessages)), 400); } if (empty($leadData)) { return response()->json(MagicAI::errorHandler(trans('admin::app.leads.no-valid-files')), 400); } return response()->json([ 'message' => trans('admin::app.leads.create-success'), 'leads' => $this->createLeads($leadData), ]); } /** * Process file. * * @param mixed $file */ private function processFile($file) { $validator = Validator::make( ['file' => $file], ['file' => 'required|extensions:'.str_replace(' ', '', self::SUPPORTED_TYPES)] ); if ($validator->fails()) { return MagicAI::errorHandler($validator->errors()->first()); } $base64Pdf = base64_encode(file_get_contents($file->getRealPath())); $extractedData = MagicAIService::extractDataFromFile($base64Pdf); $lead = MagicAI::mapAIDataToLead($extractedData); return $lead; } /** * Create multiple leads. */ private function createLeads($rawLeads): array { $leads = []; foreach ($rawLeads as $rawLead) { Event::dispatch('lead.create.before'); foreach ($rawLead['person']['emails'] as $email) { $person = $this->personRepository ->whereJsonContains('emails', [['value' => $email['value']]]) ->first(); if ($person) { $rawLead['person']['id'] = $person->id; break; } } $pipeline = $this->pipelineRepository->getDefaultPipeline(); $stage = $pipeline->stages()->first(); $lead = $this->leadRepository->create(array_merge($rawLead, [ 'lead_pipeline_id' => $pipeline->id, 'lead_pipeline_stage_id' => $stage->id, ])); Event::dispatch('lead.create.after', $lead); $leads[] = $lead; } return $leads; } }