1. Introduction
GR8 CRM is a set of Grails Web Application Framework plugins that makes it easy to develop web applications with CRM functionality.
You can find more information about GR8 CRM on the main documentation site http://gr8crm.github.io.
1.1. Customer Relationship Management
Customer relationship management (CRM) is a system for managing a company’s interactions with current and future customers. It involves using technology to organize, automate and synchronize sales, marketing, customer service, and technical support. Wikipedia
The GR8 CRM "Ecosystem" currently contains over 40 Grails plugins. For a complete list of plugins see http://gr8crm.github.io.
Each GR8 CRM plugin defines a Bounded Context that focus on one specific domain, for example contact, project or document.
2. Task Management Plugin
A Task (a.k.a Activity) in GR8 CRM is a domain instance that represents a unit of work that has been done in the past or is scheduled to be done in the future. A task is very similar to a calendar item, it has a start time and a duration. Because tasks are so generic and flexible, GR8 CRM applications can use tasks in many ways. Here’s are a list of real-world use-cases for tasks.
-
Calendar items
-
Scheduled meetings, with list of attenders
-
Conference or training registration
-
Telemarketing call items
-
Alarms on important dates
-
Time reporting
-
Event logging
3. Domain Model
In the center of the domain model is the CrmTask domain class. It has a reference to CrmTaskType that specifies what type of task it is (phone call, meeting, training, work item, etc.) A task also has a many-relation to CrmTaskAttender. This collection is used when you want to specify attenders on the task. The CrmTaskAttender domain class has a reference to CrmContact that represents the attender. This is the main reason why this plugin have a compile-time dependency on the crm-contact plugin.
3.1. CrmTask
Property | Type | Description |
---|---|---|
number |
String |
The task’s natural ID |
startTime |
Date |
Start time |
endTime |
Date |
End time |
alarmTime |
Date |
Alarm/reminder time |
busy |
Boolean |
Show as busy in calendar |
hidden |
Boolean |
Hide from calendar |
displayDate |
String |
Custom date format, typically used when duration spans multiple days, i.e. "4-6 October" |
scope |
String |
Scope is typically used when duration spans multiple days, i.e. "3 days" |
username |
String |
User that "owns" this task |
name |
String |
Name/Title for the task |
description |
String |
A longer description of the task (max 2000 characters) |
location |
String |
Location can be the name of a conference room, venue or a street address |
priority |
Integer |
Task priority, 0-100 where 0 is lowest and 100 is highest priority |
complete |
Integer |
Task completion percent (0-100) |
type |
CrmTaskType |
Type of task |
ref |
String |
Reference identifier (entityName@id) if the task is connected to a domain instance |
address |
CrmEmbeddedAddress |
Extended address information, if location is not enough |
referenceProperty |
String |
Used by dynamic dates |
offsetType |
Integer |
Used by dynamic dates |
offset |
Integer |
Used by dynamic dates |
alarms |
Integer |
Number of alarms triggered for this task |
alarmType |
Integer |
Type of alarm set |
alarmOffset |
Integer |
Alarm offset in minutes from startTime |
isRecurring |
Boolean |
True if this task is recurring |
recurType |
String |
["daily", "weekly", "monthly", "yearly"] |
recurInterval |
Integer |
Example: If recurType is "weekly" and recurInterval is 1, task will repeat every week |
recurUntil |
java.sql.Date |
Task will repeat until this date |
recurCount |
Integer |
Task will repeat this many times |
sourceTask |
CrmTask |
Back link to original recurring task this task was created from |
4. CrmTaskService
CrmTaskService provides lots of useful methods for task management.
4.1. Create a task
To create a new task you use crmTaskService.createTask(Map values, boolean save). Mandatory properties in CrmTask are name, type, startTime and endTime. Name is the title of the task and is displayed in lists and reports. Type specifies the task type and must exist prior to creating a task. Start time and end time can be set in a few different ways, the most common way is to set them both to Date instances. Another option is to set startTime to a Date instance and duration in (int) minutes. Setting the transient property duration will set endTime automatically.
def type = crmTaskService.createTaskType(name: "Phone call", true)
def task = crmTaskService.createTask(number: 42, name: "Call Sam and schedule meeting", type: type, startTime: new Date() + 1, duration: 30, true)
4.2. Search for tasks
As usual in GR8 CRM plugins the main service has a list() method that performs a query.
The list() method is @Selectable which means you can use the selection plugin to query for tasks. |
def list(Map query, Map params)
To search for tasks you initialize the query map with query values. With the params map you can specify things like sort order and pagination. The following query keys can be used in the query map.
Key | Description | Type |
---|---|---|
number |
Task number |
String (wildcard supported) |
name |
Task name/title |
String (wildcard supported) |
location |
Task location |
String (wildcard supported) |
type |
Task type |
String (wildcard supported) |
username |
Task owner/user |
String |
priority |
Task priority |
Integer (0, 20, 40, 60, 80, 100) |
complete |
Task completion % |
Integer (0-100) |
fromDate |
Task start/end time |
Date or String (yyyy-MM-dd) |
toDate |
Task start/end time |
Date or String (yyyy-MM-dd) |
reference |
Task reference |
Domain instance or reference identifier |
referenceType |
Type of reference |
Domain class (property) name |
The following example will find all tasks that refer to contacts (crmContact), starts or ends during July 2014 and are not started. As you can see you can combine several query values when you search for domain instances.
def result = crmTaskService.list([referenceType: 'crmContact',
fromDate: '2014-07-01', toDate: '2014-07-31', complete: 0], [:])
println "Found ${result.size()} tasks scheduled for July"
4.3. Setting task status
A task keep track if it’s completed or not. The completed property is an Integer constrained to 0-100 which represents how many percent completed the task is. The CrmTask domain class has three constants that defines common/simple states of completion. These constants can be used when you don’t need fine grained control of how many percent is complete.
CrmTask.STATUS_PLANNED |
0 |
The task is not started (it’s zero percent complete) |
CrmTask.STATUS_ACTIVE |
50 |
The task is started but not yet completed (it’s 50 % complete) |
CrmTask.STATUS_COMPLETED |
100 |
The task is completed (it’s 100 % complete) |
To set status for a task you use one of the three setStatusXxxx() methods in CrmTaskService.
def task = crmTaskService.createTask(name: "Fix BUG-1234", type: bug, startTime: new Date(), duration: 60, true)
crmTaskService.setStatusPlanned(task) // Planned (0 %) is the default so this call does nothing
crmTaskService.setStatusActive(task) // Do some work
crmTaskService.setStatusCompleted(task) // We are finished!
To check if a task is completed you can call isCompleted() on the CrmTask instance.
4.4. Task duration
The time between a task’s startTime and endTime is the task’s duration. The transient property duration returns the task’s duration as a groovy.time.Duration instance. You can also set the duration property to a groovy.time.Duration instance or minutes as an Integer. One of startTime or endTime must be set before you can set the duration property, this is because setDuration() simply calculates and sets startTime or endTime for you.
If you want to calculate the total duration for a set of tasks you can call crmTaskService.getTotalDuration(Collection<CrmTask>). It will return a groovy.time.Duration instance that is the sum of all task durations.
def tasks = crmTaskService.list([username: 'me', reference: theProject], [:])
def duration = crmTaskService.getTotalDuration(tasks)
println "I spent $duration on the project"
5. Alarms
You can set an alarm for a task. A quartz job CrmTaskAlarmJob will monitor alarms and trigger an application event when the time is up. To do something useful (like sending an email or text message) when the alarm is triggered you must add an event listener in your application that listens for the crmTask.alarm event and take appropriate action.
To set an alarm for a task you just have to set two properties on the task instance. Set alarmTime to a Date instance and alarmType to an Integer. The CrmTask domain class defines Integer constants for common alarm types.
Constant | Value | Description |
---|---|---|
CrmTask.ALARM_NONE |
0 |
No alarm will be triggered |
CrmTask.ALARM_EMAIL |
1 |
Send an email to the user that owns the task |
CrmTask.ALARM_SMS |
2 |
Send a text message to the user that owns the task |
CrmTask.ALARM_RESERVED_1 |
3 |
Reserved for future use |
CrmTask.ALARM_RESERVED_2 |
4 |
Reserved for future use |
CrmTask.ALARM_RESERVED_3 |
5 |
Reserved for future use |
CrmTask.ALARM_CUSTOM_1 |
10 |
Application defined alarm type |
CrmTask.ALARM_CUSTOM_2 |
11 |
Application defined alarm type |
CrmTask.ALARM_CUSTOM_3 |
12 |
Application defined alarm type |
The following application code listens for the crmTask.alarm event and sends an email to the task owner. Email subject will be the task name and email body will be the task description.
@Listener(namespace = "crmTask", topic = "alarm")
def alarm(data) {
TenantUtils.withTenant(data.tenant) {
def task = crmTaskService.getTask(data.id)
def user = crmSecurityService.getUser(task.username)
if (task) {
sendMail {
to user.email
subject task.name
body task.description
}
} else {
log.error "Cannot find CrmTask with id [${data.id}]"
}
}
null
}
6. Recurring tasks
Recurring tasks are not implemented in the crm-task plugin yet. The domain class CrmTask have all required properties to support recurring tasks (I hope), but the logic to handle them is not implemented. My initial goal was to learn from Craig Burke and implement it like he describes in his blog post GOOGLE CALENDAR IN GRAILS. Contributions are welcome! |
7. Attenders
The crm-task plugin have extensive support for assigning attenders to a task. This can be used to schedule meetings with attenders, or conference registrations. The crm-task-ui plugin have back-office UI for managing events and attenders.
The CrmTask domain class contains information about a single event, with properties like start time, end time and location of the event. A CrmTask instance can have many CrmTaskBooking instances. A booking hold one or more attenders. For example, if one company register three employees for an event, then one CrmTaskBooking instances is created with three CrmTaskAttender instances. The booking is like an "order", typically for commercial events one invoice is created for each booking. That’s why CrmTaskBooking domain class contains customer and (invoice) address properties.
def conference = crmTaskService.createTaskType(name: "Conference", param: "conf", true) (1)
def gr8conf = crmTaskService.createTask(number: 'GR8CONF-EU-2015', name: "Gr8Conf EU 2015",
type: conference, location: "Copenhagen, Denmark",
startTime: date(2015, 6, 2, 9, 0), endTime: date(2015, 6, 4, 16, 0), displayDate: "2-4 june 2015",
true) (2)
def booking = crmTaskService.createBooking(task: gr8conf,
invoiceAddress: [address1: 'Technipelago AB', address2: 'Gransbergsvägen 18',
postalCode: '13973', city: 'Djurhamn', country: 'Sweden'],
true) (3)
def attender1 = crmContactService.createContactInformation(firstName: "Göran", lastName: "Ehrsson",
email: "goran@technipelago.se", company: "Technipelago AB") (4)
crmTaskService.addAttender(booking, attender1, 'created') (5)
def attender2 = crmContactService.createContactInformation(firstName: "Sven", lastName: "Andersson",
email: "sven@technipelago.se", company: "Technipelago AB") (6)
crmTaskService.addAttender(booking, attender2, 'created') (7)
1 | Task (event) type is mandatory so we create a type called "Conference". |
2 | We create an event for one of the best conferences in the Groovy ecosystem. |
3 | Create a booking that will hold all attendees from a company. |
4 | Create contact information for the first attender the company will send to the conference. |
5 | Add the first attender to the conference booking |
6 | Create contact information for the second attender. |
7 | Add the second attender to the conference booking. |
The above example is a typical workflow for registering two attendees from the same company on a conference. All of the above can be entered manually with forms in the crm-task-ui plugin.
Version 2.4.2 of crm-task plugin changed the domain hierarchy around attenders. Previously CrmTaskBooking was optional but since 2.4.2 CrmTaskBooking is mandatory when working with CrmTaskAttender. |
8. Changes
- 2.4.5
-
Fixed wrong booking association in CrmTaskService#listAttenders() and added missing permissions
- 2.4.4
-
Task attender improvements.
- 2.4.3
-
Increased CrmTaskAttender.source to 80 characters and renamed Task to Activity
- 2.4.2
-
Refactored domain hierarchy CrmTask → CrmTaskBooking → CrmTaskAttender (database migration!)
- 2.4.1
-
Tagging support for task attenders
- 2.4.0
-
First version compatible with Grails 2.4.4
- 2.0.0
-
First public release
9. License
This plugin is licensed with Apache License version 2.0
10. Source Code
The source code for this plugin is available at https://github.com/technipelago/grails-crm-task
11. Contributing
Please report issues or suggestions.
Want to improve the plugin: Fork the repository and send a pull request.