├── 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 | relationRender('notes') ?> -------------------------------------------------------------------------------- /controllers/opportunities/_field_offerings.htm: -------------------------------------------------------------------------------- 1 | relationRender('offerings') ?> -------------------------------------------------------------------------------- /controllers/opportunities/_list_toolbar.htm: -------------------------------------------------------------------------------- 1 |
2 | 5 | New Opportunity 6 | 7 |
-------------------------------------------------------------------------------- /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 | 'layout']) ?> 11 | 12 |
13 | formRender() ?> 14 |
15 | 16 |
17 |
18 | 26 | 35 | 36 | or Cancel 37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 |

fatalError) ?>

46 |

Return to opportunities list

47 | 48 | -------------------------------------------------------------------------------- /controllers/opportunities/index.htm: -------------------------------------------------------------------------------- 1 | 2 | listRender() ?> 3 | -------------------------------------------------------------------------------- /controllers/opportunities/preview.htm: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | fatalError): ?> 9 | 10 |
11 | formRenderPreview() ?> 12 |
13 | 14 | 15 | 16 |

fatalError) ?>

17 |

Return to opportunities list

18 | 19 | -------------------------------------------------------------------------------- /controllers/opportunities/update.htm: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | fatalError): ?> 9 | 10 | 'layout']) ?> 11 | 12 |
13 | formRender() ?> 14 |
15 | 16 |
17 |
18 | 27 | 36 | 43 | 44 | or Cancel 45 | 46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 |

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 | --------------------------------------------------------------------------------