feat: consolidar desenvolvimentos do portal InvestMoz
This commit is contained in:
@@ -10,9 +10,14 @@ use App\Models\Lead;
|
||||
use App\Models\User;
|
||||
use App\Services\ConvertInterestToLead;
|
||||
use App\Services\CountryCatalogue;
|
||||
use App\Services\CrmNextAction;
|
||||
use App\Services\CrmTaskCreation;
|
||||
use App\Services\CrmTaskPreparations;
|
||||
use App\Services\DeclaredOriginStatistics;
|
||||
use App\Services\PortalAudit;
|
||||
use App\Services\VirtualDesk;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -23,6 +28,12 @@ use Inertia\Response;
|
||||
|
||||
class CrmController extends Controller
|
||||
{
|
||||
private const VERSION_MESSAGES = [
|
||||
'version.required' => 'Actualize os dados para recuperar a versão deste registo antes de gravar. As suas edições foram conservadas.',
|
||||
'version.integer' => 'A versão do registo não foi reconhecida. Recupere os dados actuais.',
|
||||
'version.min' => 'A versão do registo não foi reconhecida. Recupere os dados actuais.',
|
||||
];
|
||||
|
||||
public function investors(Request $request): Response
|
||||
{
|
||||
$filters = $request->validate(['q' => ['nullable', 'string', 'max:150']]);
|
||||
@@ -43,10 +54,12 @@ class CrmController extends Controller
|
||||
|
||||
public function saveInvestor(Request $request, Investor $investor): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['legal_name' => ['required', 'string', 'max:200'],
|
||||
$data = $request->validate(['version' => 'required|integer|min:1', 'legal_name' => ['required', 'string', 'max:200'],
|
||||
'investor_type' => ['required', Rule::in(['domestic', 'foreign', 'institutional', 'individual'])],
|
||||
'status' => ['required', Rule::in(['prospect', 'active', 'inactive'])]]);
|
||||
PortalAudit::save($investor, $data, $request->user()->id);
|
||||
'status' => ['required', Rule::in(['prospect', 'active', 'inactive'])]], self::VERSION_MESSAGES);
|
||||
$version = (int) $data['version'];
|
||||
unset($data['version']);
|
||||
PortalAudit::save($investor, $data, $request->user()->id, $version);
|
||||
|
||||
return back()->with('success', 'Investidor actualizado.');
|
||||
}
|
||||
@@ -54,15 +67,17 @@ class CrmController extends Controller
|
||||
public function saveContact(Request $request, Investor $investor, Contact $contact): RedirectResponse
|
||||
{
|
||||
abort_unless(DB::table('investor_contacts')->where('investor_id', $investor->id)->where('contact_id', $contact->id)->exists(), 404);
|
||||
$data = $request->validate(['full_name' => ['required', 'string', 'max:150'],
|
||||
'email' => ['required', 'email:rfc', 'max:150'], 'phone' => ['nullable', 'string', 'max:30']]);
|
||||
$data = $request->validate(['version' => 'required|integer|min:1', 'full_name' => ['required', 'string', 'max:150'],
|
||||
'email' => ['required', 'email:rfc', 'max:150'], 'phone' => ['nullable', 'string', 'max:30']], self::VERSION_MESSAGES);
|
||||
$data['email'] = strtolower(trim($data['email']));
|
||||
DB::transaction(function () use ($contact, $data, $request) {
|
||||
$version = (int) $data['version'];
|
||||
unset($data['version']);
|
||||
DB::transaction(function () use ($contact, $data, $request, $version) {
|
||||
DB::select('select pg_advisory_xact_lock(170925, hashtext(?))', [$data['email']]);
|
||||
if (Contact::where('id', '!=', $contact->id)->whereRaw('lower(trim(email)) = ?', [$data['email']])->exists()) {
|
||||
throw ValidationException::withMessages(['email' => 'Já existe outro contacto com este email. Reveja os registos antes de os fundir.']);
|
||||
}
|
||||
PortalAudit::save($contact, $data, $request->user()->id);
|
||||
PortalAudit::save($contact, $data, $request->user()->id, $version);
|
||||
});
|
||||
|
||||
return back()->with('success', 'Contacto actualizado.');
|
||||
@@ -82,7 +97,7 @@ class CrmController extends Controller
|
||||
'owner_user_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'origin' => ['nullable', Rule::in(['unknown', ...array_column(app(CountryCatalogue::class)->all(), 'code')])]]);
|
||||
$query = Lead::with(['investor:id,legal_name', 'owner:id,name'])
|
||||
->withCount(['tasks as overdue_tasks_count' => fn ($q) => $q->whereIn('status', ['open', 'in_progress'])->where('due_at', '<', now())]);
|
||||
->withCount(['tasks as overdue_tasks_count' => fn ($q) => $q->availableForWork()->where('due_at', '<', now())]);
|
||||
if ($term = $filters['q'] ?? null) {
|
||||
$query->where(fn ($q) => $q->whereLike('title', "%{$term}%")
|
||||
->orWhereHas('investor', fn ($investor) => $investor->whereLike('legal_name', "%{$term}%")));
|
||||
@@ -127,29 +142,37 @@ class CrmController extends Controller
|
||||
return redirect('/management/crm/leads/'.$lead->id)->with('success', 'Pedido convertido em lead com responsável e próxima acção.');
|
||||
}
|
||||
|
||||
public function show(Lead $lead): Response
|
||||
public function show(Request $request, Lead $lead): Response
|
||||
{
|
||||
$lead->load(['investor', 'owner:id,name', 'tasks' => fn ($q) => $q->orderBy('due_at')]);
|
||||
$caseIds = $lead->tasks->pluck('desk_case_id')->filter();
|
||||
$closedCases = $caseIds->isEmpty() ? [] : DB::table('desk_cases')->whereIn('id', $caseIds)->whereNotNull('closed_at')->pluck('id')->all();
|
||||
foreach ($lead->tasks as $task) {
|
||||
$task->setAttribute('can_update', ! in_array($task->getAttribute('desk_case_id'), $closedCases, true));
|
||||
}
|
||||
$contacts = DB::table('investor_contacts')->join('contacts', 'contacts.id', '=', 'investor_contacts.contact_id')
|
||||
->where('investor_id', $lead->investor_id)->whereNull('contacts.deleted_at')->get(['contacts.id', 'full_name', 'email', 'phone']);
|
||||
|
||||
return Inertia::render('crm/lead', ['lead' => $lead, 'contacts' => $contacts, 'owners' => $this->owners()]);
|
||||
return Inertia::render('crm/lead', ['lead' => $lead, 'contacts' => $contacts, 'owners' => $this->owners(), 'taskCreation' => app(CrmTaskCreation::class)->context($request->user(), $lead),
|
||||
'nextAction' => ['future_tasks' => app(CrmNextAction::class)->future($lead)->count()]]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Lead $lead): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['owner_user_id' => ['required', 'integer', Rule::in($this->owners()->pluck('id'))],
|
||||
$data = $request->validate(['version' => 'required|integer|min:1', 'owner_user_id' => ['required', 'integer', Rule::in($this->owners()->pluck('id'))],
|
||||
'status' => ['required', Rule::in(['new', 'qualified', 'disqualified'])],
|
||||
'qualification_reason' => ['required', 'string', 'min:10', 'max:2000']]);
|
||||
DB::transaction(function () use ($lead, $data, $request) {
|
||||
'qualification_reason' => ['required', 'string', 'min:10', 'max:2000']], self::VERSION_MESSAGES);
|
||||
$version = (int) $data['version'];
|
||||
unset($data['version']);
|
||||
DB::transaction(function () use ($lead, $data, $request, $version) {
|
||||
$lead = Lead::lockForUpdate()->findOrFail($lead->id);
|
||||
if ($lead->status === 'converted') {
|
||||
throw ValidationException::withMessages(['status' => 'O lead já foi convertido numa negociação.']);
|
||||
}
|
||||
if ($data['status'] !== 'disqualified' && ! $lead->tasks()->whereIn('status', ['open', 'in_progress'])->where('due_at', '>', now())->exists()) {
|
||||
if ($data['status'] !== 'disqualified' && ! app(CrmNextAction::class)->future($lead)->exists()) {
|
||||
throw ValidationException::withMessages(['status' => 'Registe uma próxima acção com prazo futuro antes de activar ou qualificar o lead.']);
|
||||
}
|
||||
PortalAudit::save($lead, $data, $request->user()->id);
|
||||
PortalAudit::save($lead, $data, $request->user()->id, $version);
|
||||
});
|
||||
|
||||
return back()->with('success', 'Lead actualizado.');
|
||||
@@ -157,24 +180,39 @@ class CrmController extends Controller
|
||||
|
||||
public function task(Request $request, Lead $lead): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['title' => ['required', 'string', 'max:200'], 'due_at' => ['required', 'date', 'after:now'],
|
||||
'assignee_user_id' => ['required', 'integer', Rule::in($this->owners()->pluck('id'))]]);
|
||||
PortalAudit::save(new CrmTask, [...$data, 'lead_id' => $lead->id, 'status' => 'open'], $request->user()->id);
|
||||
$result = app(CrmTaskCreation::class)->create($request->user(), $lead, $request->all(), $this->owners()->pluck('id')->all());
|
||||
|
||||
return back()->with('success', 'Tarefa criada.');
|
||||
return redirect('/management/crm/leads/'.$lead->id)->with('success', $result['replayed'] ? 'A tarefa já tinha sido registada. A criação não foi repetida.' : 'Tarefa criada.');
|
||||
}
|
||||
|
||||
public function taskPreparation(Request $request, Lead $lead): JsonResponse
|
||||
{
|
||||
// Preserve the user's partial text; business validation still occurs only on task creation.
|
||||
$input = json_decode($request->getContent(), true);
|
||||
if (! is_array($input)) {
|
||||
throw ValidationException::withMessages(['snapshot' => 'A preparação deve ser enviada em JSON.']);
|
||||
}
|
||||
|
||||
return response()->json(app(CrmTaskPreparations::class)->save($request->user(), $lead, $input));
|
||||
}
|
||||
|
||||
public function updateTask(Request $request, Lead $lead, CrmTask $task): RedirectResponse
|
||||
{
|
||||
abort_unless($task->lead_id === $lead->id, 404);
|
||||
$data = $request->validate(['status' => ['required', Rule::in(['open', 'in_progress', 'done', 'cancelled'])]]);
|
||||
DB::transaction(function () use ($lead, $task, $data, $request) {
|
||||
$lead = Lead::lockForUpdate()->findOrFail($lead->id);
|
||||
if (in_array($lead->status, ['new', 'qualified'], true) && in_array($data['status'], ['done', 'cancelled'], true)
|
||||
&& ! $lead->tasks()->where('id', '!=', $task->id)->whereIn('status', ['open', 'in_progress'])->where('due_at', '>', now())->exists()) {
|
||||
throw ValidationException::withMessages(['status' => 'Crie a próxima acção antes de encerrar a última tarefa de um lead activo.']);
|
||||
$data = $request->validate(['version' => 'required|integer|min:1', 'status' => ['required', Rule::in(['open', 'in_progress', 'done', 'cancelled'])]], self::VERSION_MESSAGES);
|
||||
$version = (int) $data['version'];
|
||||
unset($data['version']);
|
||||
DB::transaction(function () use ($lead, $task, $data, $request, $version) {
|
||||
if ($task->getAttribute('desk_case_id')) {
|
||||
$case = DB::table('desk_cases')->where('id', $task->getAttribute('desk_case_id'))->lockForUpdate()->first();
|
||||
abort_unless($case !== null, 404);
|
||||
app(VirtualDesk::class)->assertOpen($case);
|
||||
}
|
||||
PortalAudit::save($task, $data, $request->user()->id);
|
||||
$lead = Lead::lockForUpdate()->findOrFail($lead->id);
|
||||
$task = CrmTask::lockForUpdate()->findOrFail($task->id);
|
||||
abort_unless($task->lead_id === $lead->id, 404);
|
||||
app(CrmNextAction::class)->assertMayFinish($lead, $task, $data['status']);
|
||||
PortalAudit::save($task, $data, $request->user()->id, $version);
|
||||
});
|
||||
|
||||
return back()->with('success', 'Tarefa actualizada.');
|
||||
|
||||
Reference in New Issue
Block a user