Blogs

Gary's Blog

XAF – Project Management Application #8

     

Welcome to part 8 of a series of blog posts illustrating the building of our project management application. In this post we’ll continue our work with Project statuses.

Currently a user can change from one status to another without any consideration as to whether that change makes sense or not. This behaviour is not ideal, so we want to be able to specify which changes are valid and, as we already have the ProjectTaskStatuses enumeration, we’ll be using that to specify the order of the changes. The first thing we are going to do is to add the NextStatus property to the TaskStatus class:

[Persistent]
public TaskStatus NextStatus
{
    get { return GetPropertyValue<TaskStatus>("NextStatus"); }
    protected set { SetPropertyValue<TaskStatus>("NextStatus", value); }
}

Then we’ll modify the constructor so that it takes the next valid Status as a parameter:

public TaskStatus(Session session, TaskStatusSet owner, TaskStatus nextStatus, string name)
    : base(session)
{
    Guard.ArgumentNotNull(owner, "owner");
    Guard.ArgumentNotNullOrEmpty(name, "name");
    Owner = owner;
    Name = name;
    NextStatus = nextStatus;
}

To reflect these changes, we’ll also have to change the ProjectTask’s UpdateDatebase method:

public static void UpdateDatabase(Session session)
{
    TaskStatusSet statusSet = FindTaskStatusSet(session);
    if (statusSet == null)
    {
        string[] statusNames = Enum.GetNames(typeof(ProjectTaskStatuses));
        TaskStatus[] statusList = new TaskStatus[statusNames.Length];
        statusSet = new TaskStatusSet(session, StatusSetName);

        TaskStatus nextStatus = null;
        for (int j = statusNames.Length - 1; j >= 0; j--)
        {
            string statusName = statusNames[j];
            statusList[j] = new TaskStatus(session, statusSet, nextStatus, statusName);
            nextStatus = statusList[j];
        }
        statusSet.FirstStatus = statusList[0];
        statusSet.LastStatus = statusList[statusList.Length - 1];
        statusSet.Save();
    }
}

And, being good programmers, we’ll provide a test for this functionality:

[Test]
public void UpdateDatabase_CorrectStatusSequence()
{
    TaskStatusSet statusSet = ProjectTask.FindTaskStatusSet(Session);
    Assert.IsNotNull(statusSet);

    TaskStatus currentStatus = statusSet.FirstStatus;
    Assert.AreEqual(ProjectTaskStatuses.Draft.ToString(), currentStatus.Name);

    currentStatus = currentStatus.NextStatus;
    Assert.AreEqual(ProjectTaskStatuses.NotStarted.ToString(), currentStatus.Name);

    currentStatus = currentStatus.NextStatus;
    Assert.AreEqual(ProjectTaskStatuses.InProgress.ToString(), currentStatus.Name);

    currentStatus = currentStatus.NextStatus;
    Assert.AreEqual(ProjectTaskStatuses.Paused.ToString(), currentStatus.Name);

    currentStatus = currentStatus.NextStatus;
    Assert.AreEqual(ProjectTaskStatuses.Completed.ToString(), currentStatus.Name);

    Assert.AreEqual(null, currentStatus.NextStatus);
}

We also need to write a functional test, in EasyTest, to ensure that users can’t set an incorrect status:

#DropDB XProjectEasyTest

#Application XProjectWin
#Application XProjectWeb

*Action Navigation(Project Tasks)
*Action New

*CheckFieldValues
 Status = Draft
 
!FillForm
 Status = Completed

When we run this test it fails, of course, as we’ve not yet specified how we are going to limit the user’s choice of Status. Now, there are two ways of going about this:

  1. We can limit the available Statuses in the combobox.
  2. We can make the combobox read only and apply the Status via an Action which would hold all the required logic for specifying the legal next status.

I think we’ll favour the second option as the cleaner solution, so let’s mark the ProjectTask’s Status property with the Custom attribute, to make it read-only:

[DataSourceProperty("StatusSet.Items"), Custom("AllowEdit", "false")]
public TaskStatus Status {
    get { return GetPropertyValue<TaskStatus>("Status"); }
    protected set { SetPropertyValue<TaskStatus>("Status", value); }
}

This makes the Status field read-only on the UI, but we still have to implement the status changing functionality. To do this we’ll implement a new View Controller:

public class ChangeTaskStatusController : ViewController
{
    SimpleAction changeTaskStatusAction;
    public ChangeTaskStatusController()
    {
        changeTaskStatusAction = new SimpleAction(this, "ChangeStatus", PredefinedCategory.RecordEdit);
        changeTaskStatusAction.Execute += new SimpleActionExecuteEventHandler(action_Execute);
        changeTaskStatusAction.SelectionDependencyType = SelectionDependencyType.RequireSingleObject;
        TargetObjectType = typeof(ProjectTask);
    }
private void action_Execute(object sender, SimpleActionExecuteEventArgs e)
{
    ((ProjectTask)e.CurrentObject).Status = ((ProjectTask)e.CurrentObject).Status.NextStatus;
}
}

As you can see from the constructor declaration, we didn’t use the Controller Designer. Instead we subscribe to the events and create an Action manually, adding the following functionality to our application:

2

 1

Well okay, this works but putting the business logic in the controller is not best practice as it’s hard to test and you get no reuse from your code, it also breaks the “separation of concerns rule”. So, to fix this, we’ll add the SetNextStatus method to the ProjectTask class:

public void SetNextStatus()
{
    if (this.Status == this.StatusSet.LastStatus)
    {
        throw new InvalidOperationException();
    }
    this.Status = this.Status.NextStatus;
}

And we’ll modify the controller’s execute event handler:

private void action_Execute(object sender, SimpleActionExecuteEventArgs e)
{
    ProjectTask currentTask = (ProjectTask)e.CurrentObject;

    currentTask.SetNextStatus();
    if (currentTask.Status == currentTask.StatusSet.LastStatus)
    {
        changeTaskStatusAction.Enabled.SetItemValue(string.Empty, false);
    }
}

And, just for good measure, we’ll write a test to ensure that an exception is thrown if a wrong status is set:

[Test]
public void IncorrectStatusSet()
{
    ProjectTask task = new ProjectTask(Session);

    for (int i = 0; i < task.StatusSet.Items.Count - 1; i++)
    {
        task.SetNextStatus();
    }

    try
    {
        task.SetNextStatus();
        Assert.Fail();
    }
    catch (InvalidOperationException) { }
}

Since a task’s status is now changed by a dedicated method, we’ll make the ProjectTask’s Status field read-only, by making the setter protected, clearing all the old attributes and applying the Persistent attribute:

[Persistent]
public TaskStatus Status {
    get { return GetPropertyValue<TaskStatus>("Status"); }
    protected set { SetPropertyValue<TaskStatus>("Status", value); }
}

To ensure that the ChangeStatus Action is activated/deactivated correctly, we’ll write a functional test. The test requires a couple of test ProjectTasks in the test database. So, we’ll extend the Updater’s UpdateDatabaseAfterUpdateSchema method of the Xproject.Module project:

public class Updater : ModuleUpdater {
   ...
    public override void UpdateDatabaseAfterUpdateSchema() {
        base.UpdateDatabaseAfterUpdateSchema();

        ProjectTask.UpdateDatabase(Session);

#if EASYTEST
        ProjectTask task = new ProjectTask(Session);
        task.Name = "DraftTask";
        task.Save();

        task = new ProjectTask(Session);
        task.Name = "CompletedTask";
        task.SetNextStatus();
        task.SetNextStatus();
        task.SetNextStatus();
        task.SetNextStatus();
        task.Save();
#endif

    }
}

And here’s the EasyTest test:

#DropDB XProjectEasyTest

#Application XProjectWin
#Application XProjectWeb

*Action Navigation(Project Tasks)

*Action Filter(All Tasks)

*SelectRecords
 Columns = Name
 Row = DraftTask
 
*ActionAvailable Change Status

*ClearSelection

*SelectRecords
 Columns = Name
 Row = CompletedTask
 
!ActionAvailable Change Status

*ClearSelection

*SelectRecords
 Columns = Name
 Row = DraftTask
 
*ActionAvailable Change Status

Umm… Which doesn’t work. If you run it, it fails with: “Error in test: Action: 'Change Status' is available, line 22”. It looks like, once disabled, the button stays disabled even for other project tasks. To correct this, we’ll modify the controller – update the action_Execute event handler and add another couple of event handler to update the Action’s status based on the currently selected task:

public class ChangeTaskStatusController : ViewController {
    …
    protected override void OnActivated() {
        base.OnActivated();

        View.CurrentObjectChanged += new EventHandler(View_CurrentObjectChanged);
    }
    void View_CurrentObjectChanged(object sender, EventArgs e) {
        UpdateChangeStatusActionState();
    }
    private void UpdateChangeStatusActionState() {
        if(View.CurrentObject != null)
            changeTaskStatusAction.Enabled["ChangeTaskStatusController"] = ((ProjectTask)View.CurrentObject).CanChangeStatus;
    }
    private void action_Execute(object sender, SimpleActionExecuteEventArgs e) {
        ProjectTask currentTask = (ProjectTask)e.CurrentObject;
        currentTask.SetNextStatus();
        UpdateChangeStatusActionState();
    }

}

For this updated Controller to work, we’ll add a new CanChangeStatus property to the ProjectTask class:

[Browsable(false)]
public bool CanChangeStatus
{
    get { return this.Status != this.StatusSet.LastStatus; }
}

Now, if we run the test again we see that it passes – Phew!

Well, I think we’ll leave it there for this post, the code is available if you want to play along at home. In the next post we’ll add a new kind of task with a new set of statuses, I hope you’ll join me for that. Until then, happy XAFing!

Published Sep 02 2009, 02:29 PM by Gary Short (DevExpress)
Filed under:
Technorati tags: XAF
Bookmark and Share

Comments

 

Steve Sharkey said:

That's a very sequential status - how about status changes with iterations?

September 2, 2009 10:51 AM
 

Valuable Internet Information » XAF ??? Project Management Application #8 - Gary's Blog said:

Pingback from  Valuable Internet Information » XAF ??? Project Management Application #8 - Gary's Blog

September 2, 2009 2:39 PM
 

XAF ??? Project Management Application #8 - Gary's Blog « Management said:

Pingback from  XAF ??? Project Management Application #8 - Gary's Blog «  Management

September 2, 2009 7:37 PM
 

Luc DEBRUN said:

Nice workflow process where tasks never fail to move forward. In my world, we often have to re-open tasks or send them back to a specific prior status.

September 2, 2009 11:27 PM
 

XAF ??? Project Management Application Index - Gary's Blog said:

Pingback from  XAF ??? Project Management Application Index - Gary's Blog

September 15, 2009 6:03 AM
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.