ERP5: Designing for Maximum Adaptability > Coding the ERP5 Project

21.5. Coding the ERP5 Project

The first thing we thought about in creating the Project BT was the main project class. Initially, instead of creating a new class, we decided to simply use Order itself, without change. But after some time, we realized that the business definitions for an order and a project are so different that we should simply create Project as a subclass of Order, without any new code in it, just for the sake of separating concerns. In this design, a project is an object that is described by a series of goals or milestones with one or more tasks associated with each of them.

Then, we had to decide how to implement task management since there are differences between a project and a trade operation. The first thing to consider is that tasks can occur outside projects—for instance, in production planning. Therefore, we consider a task as a composition of task lines, or smaller activities inside a task. In that way, we decouple tasks from projects, allowing them to be used in other situations, while keeping the relation with Project through source_project and destination_project base categories.

Tasks are implemented through configuration, as we did for Task Report Line in Figure 21-2, with the difference of using Order as a metaclass. The creation of a task associated with a project is shown here:

	# Add a task in task_module. Context represents the current project object.
	context_obj = context.getObject()
	# newContent is an ERP5 API method that creates a new content.
	task = context.task_module.newContent(portal_type = 'Task')
	# Set the source_project reference to the task.
	task.setSourceProjectValue(context_obj)
	# Redirect the user to Task GUI, so that the user can edit its properties.
	return context.REQUEST.RESPONSE.redirect(task.absolute_url() + '?portal_status_
	message=Created+Task.')


					    

Remember that for retrieving the tasks of a certain project, the programmer needs to use only the base category source_project. With this category, ERP5 RAD automatically generates signatures and algorithms of accessors. It is interesting to note that the same accessors will be created for both Task and Project. The programmer decides which ones to use through configuration, using the Actions tab shown in Figure 21-3. In that tab, the programmer can define a new GUI for using the following methods:

	### These accessors are used to navigate from task to project

	# This method returns the related Project reference
	getSourceProject()

	# This method sets the related Project reference
	setSourceProject()

	# This method returns the related Project object
	getSourceProjectValue()

	# This method sets the related Project object
	setSourceProjectValue()

	### These accessors are used to navigate from project to task

	# This method returns references to related tasks
	getSourceProjectRelated()

	# This method is not generated in order to avoid an encapsulation break
	# setSourceProjectRelated()

	# This method returns the related tasks objects
	getSourceProjectRelatedValue()

	# This method is not generated in order to avoid an encapsulation break
	# setSourceProjectRelatedValue()

You must be asking where the typical project domain attributes and behavior are. The answer for the attributes, in most cases, is that they are attributes of Movement and some other UBM classes, masked in the GUI with other names. In other cases, the attribute is implemented through a base category, with all the accessors automatically generated as expected.

One example of this is the task predecessors, a list of tasks that need to be executed before a given task—a very basic project management concept, not found on trade operations. This list is also implemented by a base category named predecessor, which links a task to its predecessor in a configurable way because the category takes care of all the code needed.

Workflows implement task behavior. Again, basic Movement and more specialized Order behavior is reused. These workflows manipulate the objects in a way that makes sense for project management, and include some scripts for doing so. Workflows make development easier because they are configurable, and the programmer needs to write scripts only for specific object manipulation.

Figure 21-5 shows the Task workflow. In each box, words in parentheses represent the state ID. Transitions with the _action suffix are trigged by GUI events; the others are internally trigged by workflow events. For each transition, it is possible to define pre-and post-scripts. These scripts are the ones that will manipulate the objects according to the business logic—in this case, the task execution logic. Task represents the planning view of the process, which essentially goes through planned, ordered, and confirmed states.

Figure 21-5. Task workflow


This workflow is the same as Order, but some scripts were changed according to project domain logic. As an example, here is the script order_validateData, which is called before every _action as follows:

	### This script check that necessary data exists on Task
	# gets the task object in use
	task = state_change.object
	error_message = ''
	message_list = []
	# checks if the task is attached to some project or not
	if task.getSource() is None:
	  message_list.append('No Source')
	# if the initial date is null, but there is a final date, makes
	# initialDate = finalDate
	if task.getStartDate() is None and task.getStopDate() is not None:
	  task.setStartDate(task.getStopDate())
	if task.getStartDate () is None:
	  message_list.append("No Date")
	if task.getDestination() is None:
	  message_list.append('No Destination')
	# for each contained object, filters the one that are movements.
	# A typical return would be something like
	#('Task Line', 'Sale Order Line', 'Purchase Order Line')
	for line in task.objectValues(portal_type=task.getPortalOrderMovementTypeList ()):
	  # checks if all movements have a associated resource
	  if line.getResourceValue() is None:
	    message_list.append("No Resource for line with id: %s" % line.getId())
	# if any error happened, raises a warning
	if len(message_list) > 0:
	  raise ValidationFailed, "Warning: " + " --- ".join(message_list)


					    

Figure 21-6 shows the Task Report workflow. It follows the same logic as the Delivery workflow, with some added scripts, such as taskReport_notifyAssignee, shown here.

Figure 21-6. Task Report workflow


	task_report = state_change.object
	# searches for the assigner for the task
	source_person = task_report.getSourceValue(portal_type="Person")
	# searches for the assignee
	destination_person = task_report.getDestinationValue(portal_type="Person")
	# get the assigner email
	if source_person is not None:
	  from_email = destination_person.getDefaultEmailText()
	  email = source_person.getDefaultEmailValue()
	  if email is not None:
	    msg = """
	# preformmated string with message plus task data goes here
	"""
	    email.activate().send(from_url = from_email,
	                         subject="New Task Assigned to You",
	                         msg = msg)