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.

task domain

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.

CreateSimpleTask.groovy
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.

FindSummerTasks.groovy
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.

SetTaskStatus.groovy
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.

MyTimeSheet.groovy
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.

MyAlarmService.groovy
@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.

AddAttendersToEvent.groovy
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.