Back to blog

Real World Cases: CRM System in Laravel in ~10 Minutes

Feb 05, 2024
.
Samuel Trstenský

Welcome to the Real World Cases series where we are crafting custom applications from real world cases using Laravel framework.

In this example, we are going to build a simple CRM system using Craftable PRO. The prerequisite is that:

  1. you have installed an empty Laravel project
  2. you have installed Craftable PRO
  3. you are logged into Craftable PRO admin interface

For easier walkthrough, this article is also captured as a video screen recording that is available at the very bottom of this article.

Database structure

When building any web app, it’s always good to start thinking about the data entities that our app will work with. Let's assume our CRM system will have Contacts, Companies, Opportunities, and Activities. Contacts will represent people that can be connected to Companies. This simple example will omit the usual Leads table, instead, there will be a status for opportunity named "Lead". 

After some deeper thinking about the attributes for each table, we create several migration files that might look like this (feel free to adjust to your needs):

// create_companies_table

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCompaniesTable extends Migration
{
    public function up()
    {
        Schema::create('companies', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('address')->nullable();
            $table->string('industry')->nullable();
            $table->string('website')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('companies');
    }
}
// create_contacts_table

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCompaniesTable extends Migration
{
    public function up()
    {
        Schema::create('companies', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('address')->nullable();
            $table->string('industry')->nullable();
            $table->string('website')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('companies');
    }
}
// create_opportunities_table

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCompaniesTable extends Migration
{
    public function up()
    {
        Schema::create('companies', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('address')->nullable();
            $table->string('industry')->nullable();
            $table->string('website')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('companies');
    }
}
// create_activities_table

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCompaniesTable extends Migration
{
    public function up()
    {
        Schema::create('companies', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('address')->nullable();
            $table->string('industry')->nullable();
            $table->string('website')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('companies');
    }
}

Let’s not forget to actually apply the migrations:

php artisan migrate

Generating the CRUD

Perfect. Now when our database structure is ready we can start building the CRUD for our tables.

Companies

Let's begin by creating the CRUD functionalities for the companies entity. To initiate this, we'll use the following command:

php artisan craftable-pro:generate-crud companies --listing-columns=id,name,industry --sortable-columns=id,name --media-collections=logo --image-collections=logo

This command utilizes specific options to tailor our CRUD interface. By using --listing-columns=id,name,industry we specify that our listing page will display the company's ID, name, and industry. With the --sortable-columns=id,name option we designate these columns as sortable. The --media-collections option is included to enable media uploads. Additionally, the --image-collections option is used to specifically limit the uploaded media to images only.

For those who prefer a more guided approach, the wizard mode is an excellent alternative. It offers an interactive process, leading you step-by-step through the CRUD generation. Activate it using:

php artisan craftable-pro:generate-crud companies -w

For a detailed understanding of these command options and their functionalities, please refer to our documentation.

Contacts

Let's proceed to the 'contacts' table, where the same principles apply. Notice the additional option --add-relation, which establishes the relationship between Contacts and Companies. This enables Craftable PRO to generate a select box with the appropriate options and display the correct company name in the listing based on the name attribute.

php artisan craftable-pro:generate-crud contacts --listing-columns=id,first_name,last_name,email,phone,company_id --sortable-columns=id,first_name,last_name --add-relation="belongsTo(Company->name,company_id,id)" --media-collections=image --image-collections=image

Opportunities

Now let’s add the opportunities CRUD.

php artisan craftable-pro:generate-crud opportunities --listing-columns=id,name,company_id,value,status,estimated_close_date --sortable-columns=id,name,status --add-relation="belongsTo(Company->name,company_id,id)"

Activities

And finally, the activities CRUD.

php artisan craftable-pro:generate-crud activities --listing-columns=id,type,subject,date,contact_id,opportunity_id --sortable-columns=id,date,type --add-relation="belongsTo(Contact->email,contact_id,id)" --add-relation="belongsTo(Opportunity->name,opportunity_id,id)" --media-collections=attachments

Building assets

In order to actually test our changes we need to build the frontend assets of the Craftable PRO, so we need to run:

npm run craftable-pro:build

And that’s it. It is that simple!

Customization

Our CRM system has one small flaw. Wouldn’t it be better if users had the possibility to select an opportunity status from a dropdown box instead of manually typing it into a text input?

Fortunately, since all the code is generated, making changes is quite simple. All you need to do is go to resources/js/craftable-pro/Pages/Opportunity/Form.vue and replace the TextInput component:

<TextInput v-model="form.status" name="status" :label="$t('craftable-pro', 'Status')" />

For Multiselect component:

<Multiselect
  v-model="form.status"
  name="status"
  :label="$t('craftable-pro', 'Status')"
  :options="[
      {value: 'lead', label: $t('craftable-pro', 'Lead')},
      {value: 'open', label: $t('craftable-pro', 'Open')},
      {value: 'won', label: $t('craftable-pro', 'Won')},
      {value: 'lost', label: $t('craftable-pro', 'Lost')},
  ]"
  mode="single"
/>

This is an example of how easily you can modify generated code. Craftable Pro comes with a wide array of prepared components that you can freely use for customizing your admin panel to ensure it meets your needs. Thanks to the code generation principle of Craftable PRO, you have endless possibilities for customization.

See it in Action (video)

We have captured the entire process in a video. At the end, you'll be able to see the fully functioning admin panel in action.

The method for integrating the multiselect feature is demonstrated in the following video:

As you can see, it's possible to create a functional CRM system with a custom database structure under 10 minutes. Moreover, Craftable PRO offers complete flexibility; your hands aren't tied to a predefined system structure. You have the freedom to modify everything directly in the code. So go ahead and create your own CRM system or any other type of admin panel!