diff --git a/Modules/Client/Entities/Client.php b/Modules/Client/Entities/Client.php index 597f5482d2..8e40bb6a9c 100644 --- a/Modules/Client/Entities/Client.php +++ b/Modules/Client/Entities/Client.php @@ -13,6 +13,7 @@ use Modules\Invoice\Entities\LedgerAccount; use Modules\Invoice\Services\InvoiceService; use Modules\Project\Entities\Project; +use Modules\Prospect\Entities\Prospect; use Modules\User\Entities\User; class Client extends Model @@ -396,6 +397,16 @@ public function hasCustomInvoiceTemplate() return false; } + public function prospect() + { + return $this->belongsTo(Prospect::class); + } + + public function getClientsAttribute() + { + return $this->query()->orderBy('name')->get(); + } + protected static function booted() { static::addGlobalScope(new ClientGlobalScope); diff --git a/Modules/Prospect/Config/config.php b/Modules/Prospect/Config/config.php index 5b307ee26c..5ff0f7a413 100644 --- a/Modules/Prospect/Config/config.php +++ b/Modules/Prospect/Config/config.php @@ -7,4 +7,13 @@ 'existing' => 'Existing', 'dormant' => 'Dormant', ], + 'status' => [ + 'pending' => 'Pending', + 'proposal-sent' => 'Proposal Sent', + 'discussions-ongoing' => 'Discussions Ongoing', + 'converted' => 'Converted', + 'rejected' => 'Rejected', + 'client-unresponsive' => 'Client Unresponsive', + 'final-decision-pending' => 'Final Decision Pending', + ], ]; diff --git a/Modules/Prospect/Database/Migrations/2024_10_25_220553_prospect-insights.php b/Modules/Prospect/Database/Migrations/2024_10_25_220553_prospect-insights.php new file mode 100644 index 0000000000..71bb8ec8d6 --- /dev/null +++ b/Modules/Prospect/Database/Migrations/2024_10_25_220553_prospect-insights.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->unsignedBigInteger('prospect_id'); + $table->integer('user_id')->unsigned(); + $table->longText('insight_learning'); + $table->timestamps(); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('prospect_id')->references('id')->on('prospects')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('prospect_insights'); + } +} diff --git a/Modules/Prospect/Database/Migrations/2024_10_28_221259_added-client-id-project-name.php b/Modules/Prospect/Database/Migrations/2024_10_28_221259_added-client-id-project-name.php new file mode 100644 index 0000000000..62320a32a9 --- /dev/null +++ b/Modules/Prospect/Database/Migrations/2024_10_28_221259_added-client-id-project-name.php @@ -0,0 +1,39 @@ +unsignedBigInteger('client_id')->nullable(); + $table->string('project_name')->nullable(); + $table->string('organization_name')->nullable()->change(); + + $table->foreign('client_id')->references('id')->on('clients'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('prospects', function (Blueprint $table) { + $table->dropForeign(['client_id']); + $table->dropColumn('client_id'); + $table->dropColumn('project_name'); + $table->string('organization_name')->nullable(false)->change(); + }); + } +} diff --git a/Modules/Prospect/Entities/Prospect.php b/Modules/Prospect/Entities/Prospect.php index 85d850d0d2..918deda0c1 100644 --- a/Modules/Prospect/Entities/Prospect.php +++ b/Modules/Prospect/Entities/Prospect.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Modules\Client\Entities\Client; use Modules\User\Entities\User; class Prospect extends Model @@ -26,4 +27,27 @@ public function getFormattedDate($date) return $date ? Carbon::parse($date)->format('M d, Y') : '-'; } + + public function client() + { + return $this->belongsTo(Client::class, 'client_id'); + } + + public function getProspectDisplayName() + { + return $this->organization_name ?? optional($this->client)->name ?? 'N/A'; + } + + public function formattedAmount($amount) + { + $formattedAmount = preg_replace('/\B(?=(\d{2})+(?!\d))/', ',', substr($amount, 0, -3)) . + ',' . substr($amount, -3); + + return $formattedAmount; + } + + public function insights() + { + return $this->hasMany(ProspectInsight::class); + } } diff --git a/Modules/Prospect/Entities/ProspectComment.php b/Modules/Prospect/Entities/ProspectComment.php index 2dd59ea4e3..21dbcda366 100644 --- a/Modules/Prospect/Entities/ProspectComment.php +++ b/Modules/Prospect/Entities/ProspectComment.php @@ -18,6 +18,6 @@ public function prospect() public function user() { - return $this->belongsTo(User::class); + return $this->belongsTo(User::class, 'user_id'); } } diff --git a/Modules/Prospect/Entities/ProspectInsight.php b/Modules/Prospect/Entities/ProspectInsight.php new file mode 100644 index 0000000000..c6fd114f15 --- /dev/null +++ b/Modules/Prospect/Entities/ProspectInsight.php @@ -0,0 +1,23 @@ +belongsTo(Prospect::class); + } + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/Modules/Prospect/Http/Controllers/ProspectController.php b/Modules/Prospect/Http/Controllers/ProspectController.php index 9a7a08b8c1..92b4b9b9e4 100644 --- a/Modules/Prospect/Http/Controllers/ProspectController.php +++ b/Modules/Prospect/Http/Controllers/ProspectController.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; +use Modules\Client\Entities\Client; use Modules\Client\Entities\Country; use Modules\Prospect\Entities\Prospect; use Modules\Prospect\Http\Requests\ProspectRequest; @@ -26,14 +27,10 @@ public function __construct(ProspectService $service) */ public function index() { - $prospects = Prospect::with('pocUser')->get(); - $countries = Country::all(); - $currencySymbols = $countries->pluck('currency_symbol', 'currency'); + $requestData = request()->all(); + $data = $this->service->index($requestData); - return view('prospect::index', [ - 'prospects' => $prospects, - 'currencySymbols' => $currencySymbols, - ]); + return view('prospect::index', $data); } /** @@ -44,11 +41,13 @@ public function create() { $countries = Country::all(); $user = new User(); + $client = new Client(); $activeUsers = $user->active_users; return view('prospect::create', [ 'users' => $activeUsers, 'countries' => $countries, + 'clients' => $client->clients, ]); } @@ -59,9 +58,9 @@ public function create() public function store(ProspectRequest $request) { $validated = $request->validated(); - $data = $this->service->store($validated); + $this->service->store($validated); - return $data; + return redirect()->route('prospect.index')->with('status', 'Prospect created successfully!'); } /** @@ -70,7 +69,7 @@ public function store(ProspectRequest $request) */ public function show($id) { - $prospect = Prospect::with(['pocUser', 'comments', 'comments.user'])->find($id); + $prospect = Prospect::with(['comments', 'insights'])->find($id); $countries = Country::all(); $currencySymbols = $countries->pluck('currency_symbol', 'currency'); @@ -87,26 +86,28 @@ public function show($id) */ public function edit($id) { - $prospect = Prospect::with(['pocUser', 'comments'])->find($id); + $prospect = Prospect::with(['comments'])->find($id); $countries = Country::all(); $user = new User(); $activeUsers = $user->active_users; + $client = new Client(); return view('prospect::edit', [ 'prospect' => $prospect, 'users' => $activeUsers, 'countries' => $countries, + 'clients' => $client->clients, ]); } /** * Update the specified resource in storage. * @param Request $request - * @param int $id + * @param Prospect $prospect */ - public function update(Request $request, $id) + public function update(Request $request, Prospect $prospect) { - $data = $this->service->update($request, $id); + $data = $this->service->update($request, $prospect); return $data; } @@ -124,4 +125,14 @@ public function commentUpdate(Request $request, $id) return redirect()->route('prospect.show', $id)->with('status', 'Comment updated successfully!'); } + + public function insightsUpdate(Request $request, $id) + { + $validated = $request->validate([ + 'insight_learning' => 'required', + ]); + $this->service->insightsUpdate($validated, $id); + + return redirect()->route('prospect.show', $id)->with('status', 'Prospect Insights updated successfully!'); + } } diff --git a/Modules/Prospect/Http/Requests/ProspectRequest.php b/Modules/Prospect/Http/Requests/ProspectRequest.php index fe1ff644d3..6d5a5ac442 100644 --- a/Modules/Prospect/Http/Requests/ProspectRequest.php +++ b/Modules/Prospect/Http/Requests/ProspectRequest.php @@ -14,7 +14,7 @@ class ProspectRequest extends FormRequest public function rules() { return [ - 'org_name' => 'required', + 'org_name' => 'nullable', 'poc_user_id' => 'required', 'proposal_sent_date' => 'nullable|date', 'domain' => 'nullable', @@ -26,9 +26,30 @@ public function rules() 'rfp_link' => 'nullable|url', 'proposal_link' => 'nullable|url', 'currency' => 'nullable', + 'client_id' => 'nullable|exists:clients,id', + 'project_name' => 'nullable', ]; } + /** + * Configure the validator instance. + * + * @param \Illuminate\Validation\Validator $validator + * @return void + */ + public function withValidator($validator) + { + if ($this->customer_type === 'new') { + $validator->addRules([ + 'org_name' => 'required', + ]); + } elseif ($this->customer_type === 'existing') { + $validator->addRules([ + 'client_id' => 'required', + ]); + } + } + /** * Determine if the user is authorized to make this request. * @@ -47,7 +68,8 @@ public function authorize() public function messages() { return [ - 'org_name.required' => 'Organization name is required', + 'org_name.required' => 'Organization name is required when customer type is new.', + 'client_id.required' => 'Please select an organization when customer type is existing.', 'poc_user_id.required' => 'Point of contact user ID is required', ]; } diff --git a/Modules/Prospect/Resources/assets/js/app.js b/Modules/Prospect/Resources/assets/js/app.js index e69de29bb2..2c48291b48 100644 --- a/Modules/Prospect/Resources/assets/js/app.js +++ b/Modules/Prospect/Resources/assets/js/app.js @@ -0,0 +1,38 @@ +const CUSTOMER_TYPES = { + NEW: 'new', + EXISTING: 'existing', + DORMANT: 'dormant' +}; +document.addEventListener('DOMContentLoaded', function () { + const customerTypeField = document.getElementById('customer_type'); + const orgNameTextField = document.getElementById('org_name_text_field'); + const orgNameSelectField = document.getElementById('org_name_select_field'); + const orgNameTextInput = document.getElementById('org_name'); + const orgNameSelectInput = document.getElementById('org_name_select'); + + function toggleOrgNameField() { + if (customerTypeField.value === CUSTOMER_TYPES.NEW) { + orgNameTextField.classList.remove('d-none'); + orgNameSelectField.classList.add('d-none'); + orgNameTextInput.required = true; + orgNameSelectInput.value = ''; + orgNameSelectInput.required = false; + } else if (customerTypeField.value === CUSTOMER_TYPES.EXISTING) { + orgNameTextField.classList.add('d-none'); + orgNameSelectField.classList.remove('d-none'); + orgNameSelectInput.required = true; + orgNameTextInput.required = false; + orgNameTextInput.value = null; + } else { + orgNameTextField.classList.remove('d-none'); + orgNameSelectField.classList.add('d-none'); + orgNameTextInput.required = true; + orgNameSelectInput.value = ''; + orgNameSelectInput.required = false; + } + } + + toggleOrgNameField(); + + customerTypeField.addEventListener('change', toggleOrgNameField); +}); diff --git a/Modules/Prospect/Resources/views/create.blade.php b/Modules/Prospect/Resources/views/create.blade.php index 9a57422a9d..17716d2e50 100644 --- a/Modules/Prospect/Resources/views/create.blade.php +++ b/Modules/Prospect/Resources/views/create.blade.php @@ -16,12 +16,34 @@