├── Plugin.php
├── README.md
├── controllers
├── Opportunities.php
└── opportunities
│ ├── _field_contacts.htm
│ ├── _field_notes.htm
│ ├── _field_offerings.htm
│ ├── _list_toolbar.htm
│ ├── config_form.yaml
│ ├── config_list.yaml
│ ├── config_relation.yaml
│ ├── create.htm
│ ├── index.htm
│ ├── preview.htm
│ └── update.htm
├── models
├── Contact.php
├── Note.php
├── Offering.php
├── Opportunity.php
├── OpportunityOfferingPivot.php
├── Status.php
├── contact
│ ├── columns.yaml
│ └── fields.yaml
├── note
│ ├── columns.yaml
│ └── fields.yaml
├── offering
│ ├── columns.yaml
│ └── fields.yaml
├── opportunity
│ ├── columns.yaml
│ └── fields.yaml
├── opportunityofferingpivot
│ ├── columns.yaml
│ └── fields.yaml
└── status
│ ├── columns.yaml
│ └── fields.yaml
└── updates
├── create_contacts_table.php
├── create_notes_table.php
├── create_offerings_table.php
├── create_opportunities_table.php
├── create_statuses_table.php
├── seed_all_tables.php
└── version.yaml
/Plugin.php:
--------------------------------------------------------------------------------
1 | 'CRM',
21 | 'description' => 'Customer Relationship Management',
22 | 'author' => 'Acme',
23 | 'icon' => 'icon-leaf'
24 | ];
25 | }
26 |
27 | public function registerNavigation()
28 | {
29 | return [
30 | 'crm' => [
31 | 'label' => 'CRM',
32 | 'url' => Backend::url('acme/crm/opportunities'),
33 | 'icon' => 'icon-trophy',
34 | 'permissions' => ['crm.*'],
35 | 'order' => 200,
36 | ]
37 | ];
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sample CRM Plugin
2 |
3 | A sample CRM plugin for OctoberCMS used in the presentation [Back-end Relations](https://vimeo.com/123489421).
4 |
5 | * [View the screencast presentation](https://vimeo.com/123489421)
6 |
7 | ## Using this plugin
8 |
9 | To install this plugin, extract this archive to `/plugins/acme/crm` and migrate the database by performing one of the following:
10 |
11 | 1. Log out and sign back in to the back-end; or
12 | 2. Run CLI command `php artisan october:up`
13 |
14 | To view the plugin click on **CRM** in the back-end area.
15 |
--------------------------------------------------------------------------------
/controllers/Opportunities.php:
--------------------------------------------------------------------------------
1 | relationRender('contacts') ?>
--------------------------------------------------------------------------------
/controllers/opportunities/_field_notes.htm:
--------------------------------------------------------------------------------
1 | = $this->relationRender('notes') ?>
--------------------------------------------------------------------------------
/controllers/opportunities/_field_offerings.htm:
--------------------------------------------------------------------------------
1 | = $this->relationRender('offerings') ?>
--------------------------------------------------------------------------------
/controllers/opportunities/_list_toolbar.htm:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/controllers/opportunities/config_form.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Form Behavior Config
3 | # ===================================
4 |
5 | # Record name
6 | name: Opportunity
7 |
8 | # Model Form Field configuration
9 | form: $/acme/crm/models/opportunity/fields.yaml
10 |
11 | # Model Class name
12 | modelClass: Acme\Crm\Models\Opportunity
13 |
14 | # Default redirect location
15 | defaultRedirect: acme/crm/opportunities
16 |
17 | # Create page
18 | create:
19 | title: Create Opportunity
20 | redirect: acme/crm/opportunities/update/:id
21 | redirectClose: acme/crm/opportunities
22 |
23 | # Update page
24 | update:
25 | title: Edit Opportunity
26 | redirect: acme/crm/opportunities
27 | redirectClose: acme/crm/opportunities
28 |
29 | # Preview page
30 | preview:
31 | title: Preview Opportunity
--------------------------------------------------------------------------------
/controllers/opportunities/config_list.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Behavior Config
3 | # ===================================
4 |
5 | # Model List Column configuration
6 | list: $/acme/crm/models/opportunity/columns.yaml
7 |
8 | # Model Class name
9 | modelClass: Acme\Crm\Models\Opportunity
10 |
11 | # List Title
12 | title: Manage Opportunities
13 |
14 | # Link URL for each record
15 | recordUrl: acme/crm/opportunities/update/:id
16 |
17 | # Message to display if the list is empty
18 | noRecordsMessage: backend::lang.list.no_records
19 |
20 | # Records to display per page
21 | recordsPerPage: 20
22 |
23 | # Displays the list column set up button
24 | showSetup: true
25 |
26 | # Displays the sorting link on each column
27 | showSorting: true
28 |
29 | # Default sorting column
30 | # defaultSort:
31 | # column: created_at
32 | # direction: desc
33 |
34 | # Display checkboxes next to each record
35 | # showCheckboxes: true
36 |
37 | # Toolbar widget configuration
38 | toolbar:
39 | # Partial for toolbar buttons
40 | buttons: list_toolbar
41 |
42 | # Search widget configuration
43 | search:
44 | prompt: backend::lang.list.search_prompt
45 |
--------------------------------------------------------------------------------
/controllers/opportunities/config_relation.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Relation Behavior Config
3 | # ===================================
4 |
5 | # offerings:
6 | # label: Offering
7 | # view:
8 | # list: $/acme/crm/models/offering/columns.yaml
9 | # toolbarButtons: add|remove
10 | # manage:
11 | # showSearch: true
12 | # recordsPerPage: 10
13 | # list: $/acme/crm/models/offering/columns.yaml
14 | # form: $/acme/crm/models/offering/fields.yaml
15 |
16 | offerings:
17 | label: Offering
18 | view:
19 | list: $/acme/crm/models/opportunityofferingpivot/columns.yaml
20 | toolbarButtons: add|remove
21 | manage:
22 | showSearch: true
23 | recordsPerPage: 10
24 | list: $/acme/crm/models/offering/columns.yaml
25 | pivot:
26 | form: $/acme/crm/models/opportunityofferingpivot/fields.yaml
27 |
28 | notes:
29 | label: Note
30 | view:
31 | list: $/acme/crm/models/note/columns.yaml
32 | toolbarButtons: create|delete
33 | manage:
34 | form: $/acme/crm/models/note/fields.yaml
35 |
36 | contacts:
37 | label: Contact
38 | view:
39 | list: $/acme/crm/models/contact/columns.yaml
40 | toolbarButtons: create|add|remove
41 | manage:
42 | showSearch: true
43 | recordsPerPage: 10
44 | list: $/acme/crm/models/contact/columns.yaml
45 | form: $/acme/crm/models/contact/fields.yaml
46 |
--------------------------------------------------------------------------------
/controllers/opportunities/create.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | fatalError): ?>
9 |
10 | = Form::open(['class' => 'layout']) ?>
11 |
12 |
13 | = $this->formRender() ?>
14 |
15 |
16 |
40 |
41 | = Form::close() ?>
42 |
43 |
44 |
45 | = e($this->fatalError) ?>
46 | Return to opportunities list
47 |
48 |
--------------------------------------------------------------------------------
/controllers/opportunities/index.htm:
--------------------------------------------------------------------------------
1 |
2 | = $this->listRender() ?>
3 |
--------------------------------------------------------------------------------
/controllers/opportunities/preview.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | fatalError): ?>
9 |
10 |
11 | = $this->formRenderPreview() ?>
12 |
13 |
14 |
15 |
16 | = e($this->fatalError) ?>
17 | Return to opportunities list
18 |
19 |
--------------------------------------------------------------------------------
/controllers/opportunities/update.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | fatalError): ?>
9 |
10 | = Form::open(['class' => 'layout']) ?>
11 |
12 |
13 | = $this->formRender() ?>
14 |
15 |
16 |
48 |
49 | = Form::close() ?>
50 |
51 |
52 |
53 | = e($this->fatalError) ?>
54 | Return to opportunities list
55 |
56 |
--------------------------------------------------------------------------------
/models/Contact.php:
--------------------------------------------------------------------------------
1 | ['Acme\Crm\Models\Opportunity'],
26 | 'owner' => ['Backend\Models\User'],
27 | ];
28 |
29 | }
--------------------------------------------------------------------------------
/models/Offering.php:
--------------------------------------------------------------------------------
1 | ['Acme\Crm\Models\Note'],
26 | ];
27 |
28 | public $belongsTo = [
29 | 'status' => ['Acme\Crm\Models\Status'],
30 | ];
31 |
32 | public $belongsToMany = [
33 | 'contacts' => [
34 | 'Acme\Crm\Models\Contact',
35 | 'table' => 'acme_crm_opportunities_contacts'
36 | ],
37 | // 'offerings' => [
38 | // 'Acme\Crm\Models\Offering',
39 | // 'table' => 'acme_crm_opportunities_offerings'
40 | // ],
41 | 'offerings' => [
42 | 'Acme\Crm\Models\Offering',
43 | 'table' => 'acme_crm_opportunities_offerings',
44 | 'pivot' => ['price', 'cost', 'owner_id'],
45 | 'pivotModel' => 'Acme\Crm\Models\OpportunityOfferingPivot'
46 | ],
47 | ];
48 |
49 | }
--------------------------------------------------------------------------------
/models/OpportunityOfferingPivot.php:
--------------------------------------------------------------------------------
1 | ['Backend\Models\User']
16 | ];
17 |
18 | }
--------------------------------------------------------------------------------
/models/Status.php:
--------------------------------------------------------------------------------
1 | engine = 'InnoDB';
14 | $table->increments('id');
15 | $table->string('first_name')->nullable();
16 | $table->string('last_name')->nullable();
17 | $table->string('email')->nullable();
18 | $table->string('phone')->nullable();
19 | $table->string('mobile')->nullable();
20 | $table->string('position')->nullable();
21 | $table->string('company')->nullable();
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | public function down()
27 | {
28 | Schema::dropIfExists('acme_crm_contacts');
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/updates/create_notes_table.php:
--------------------------------------------------------------------------------
1 | engine = 'InnoDB';
14 | $table->increments('id');
15 | $table->string('subject')->nullable();
16 | $table->text('content')->nullable();
17 | $table->integer('opportunity_id')->unsigned()->nullable()->index();
18 | $table->integer('owner_id')->unsigned()->nullable()->index();
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | public function down()
24 | {
25 | Schema::dropIfExists('acme_crm_notes');
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/updates/create_offerings_table.php:
--------------------------------------------------------------------------------
1 | engine = 'InnoDB';
14 | $table->increments('id');
15 | $table->string('name')->nullable();
16 | $table->text('description')->nullable();
17 | $table->decimal('price', 15)->default(0);
18 | $table->decimal('cost', 15)->default(0);
19 | $table->integer('owner_id')->unsigned()->nullable()->index();
20 | $table->timestamps();
21 | });
22 | }
23 |
24 | public function down()
25 | {
26 | Schema::dropIfExists('acme_crm_offerings');
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/updates/create_opportunities_table.php:
--------------------------------------------------------------------------------
1 | engine = 'InnoDB';
14 | $table->increments('id');
15 | $table->string('name')->nullable();
16 | $table->text('description')->nullable();
17 | $table->integer('status_id')->unsigned()->nullable()->index();
18 | $table->timestamp('closed_at')->nullable();
19 | $table->timestamps();
20 | });
21 |
22 | Schema::create('acme_crm_opportunities_contacts', function($table)
23 | {
24 | $table->engine = 'InnoDB';
25 | $table->integer('opportunity_id')->unsigned();
26 | $table->integer('contact_id')->unsigned();
27 | $table->primary(['opportunity_id', 'contact_id'], 'opportunity_contact');
28 | });
29 |
30 | Schema::create('acme_crm_opportunities_offerings', function($table)
31 | {
32 | $table->engine = 'InnoDB';
33 | $table->integer('opportunity_id')->unsigned();
34 | $table->integer('offering_id')->unsigned();
35 | $table->decimal('price', 15)->default(0);
36 | $table->decimal('cost', 15)->default(0);
37 | $table->integer('owner_id')->unsigned()->nullable()->index();
38 | $table->primary(['opportunity_id', 'offering_id'], 'opportunity_offering');
39 | });
40 | }
41 |
42 | public function down()
43 | {
44 | Schema::dropIfExists('acme_crm_opportunities');
45 | Schema::dropIfExists('acme_crm_opportunities_contacts');
46 | Schema::dropIfExists('acme_crm_opportunities_offerings');
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/updates/create_statuses_table.php:
--------------------------------------------------------------------------------
1 | engine = 'InnoDB';
14 | $table->increments('id');
15 | $table->string('name')->nullable();
16 | $table->string('probability')->nullable();
17 | $table->timestamps();
18 | });
19 | }
20 |
21 | public function down()
22 | {
23 | Schema::dropIfExists('acme_crm_statuses');
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/updates/seed_all_tables.php:
--------------------------------------------------------------------------------
1 | 'First contact', 'probability' => '5%']);
15 | Status::create(['name' => 'Proposal made', 'probability' => '20%']);
16 | Status::create(['name' => 'Under review', 'probability' => '40%']);
17 | Status::create(['name' => 'Positive response', 'probability' => '60%']);
18 | Status::create(['name' => 'Verbal agreement', 'probability' => '90%']);
19 | Status::create(['name' => 'Closed (Won)', 'probability' => '100%']);
20 | Status::create(['name' => 'Closed (Lost)', 'probability' => '0%']);
21 |
22 | Offering::create(['name' => 'Web application', 'cost' => 300, 'price' => 600]);
23 | Offering::create(['name' => 'Broschure website', 'cost' => 50, 'price' => 100]);
24 | Offering::create(['name' => 'Mobile application', 'cost' => 60, 'price' => 120]);
25 | Offering::create(['name' => 'Logo design', 'cost' => 40, 'price' => 80]);
26 | Offering::create(['name' => 'Copywriting', 'cost' => 20, 'price' => 40]);
27 |
28 | Contact::create(['first_name' => 'Jim', 'last_name' => 'Henson', 'position' => 'Operator', 'company' => "Jim's Mowing"]);
29 | Contact::create(['first_name' => 'Wes', 'last_name' => 'Anderson', 'position' => 'Director', 'company' => "Indian Paintbrush"]);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/updates/version.yaml:
--------------------------------------------------------------------------------
1 | 1.0.1: First version of CRM plugin
2 | 1.0.2:
3 | - Create base database tables
4 | - create_contacts_table.php
5 | - create_notes_table.php
6 | - create_offerings_table.php
7 | - create_opportunities_table.php
8 | - create_statuses_table.php
9 | 1.0.3:
10 | - Seeding all tables
11 | - seed_all_tables.php
12 |
--------------------------------------------------------------------------------