/**
*
* Copyright
* 2009-2015 Jayway Products AB
* 2016-2017 Föreningen Sambruk
*
* Licensed under AGPL, Version 3.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/agpl.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package se.streamsource.streamflow.web.context.workspace.cases;
import static org.qi4j.api.util.Iterables.matchesAny;
import static se.streamsource.dci.api.RoleMap.role;
import static se.streamsource.streamflow.api.workspace.cases.CaseStates.CLOSED;
import static se.streamsource.streamflow.api.workspace.cases.CaseStates.DRAFT;
import static se.streamsource.streamflow.api.workspace.cases.CaseStates.ON_HOLD;
import static se.streamsource.streamflow.api.workspace.cases.CaseStates.OPEN;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.qi4j.api.concern.Concerns;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.mixin.Mixins;
import org.qi4j.api.specification.Specification;
import org.qi4j.api.structure.Module;
import org.qi4j.api.value.ValueBuilder;
import se.streamsource.dci.api.Context;
import se.streamsource.dci.api.DeleteContext;
import se.streamsource.dci.api.RoleMap;
import se.streamsource.dci.value.EntityValue;
import se.streamsource.dci.value.StringValue;
import se.streamsource.dci.value.link.LinksValue;
import se.streamsource.streamflow.api.workspace.cases.CaseOutputConfigDTO;
import se.streamsource.streamflow.api.workspace.cases.CaseStates;
import se.streamsource.streamflow.web.application.pdf.PdfGeneratorService;
import se.streamsource.streamflow.web.context.LinksBuilder;
import se.streamsource.streamflow.web.context.RequiresPermission;
import se.streamsource.streamflow.web.domain.Describable;
import se.streamsource.streamflow.web.domain.Removable;
import se.streamsource.streamflow.web.domain.entity.RequiresRemoved;
import se.streamsource.streamflow.web.domain.entity.caze.CaseEntity;
import se.streamsource.streamflow.web.domain.entity.caze.CaseTypeQueries;
import se.streamsource.streamflow.web.domain.entity.organization.OrganizationsEntity;
import se.streamsource.streamflow.web.domain.entity.project.ProjectMembersQueries;
import se.streamsource.streamflow.web.domain.interaction.gtd.Actor;
import se.streamsource.streamflow.web.domain.interaction.gtd.Assignable;
import se.streamsource.streamflow.web.domain.interaction.gtd.Assignee;
import se.streamsource.streamflow.web.domain.interaction.gtd.Ownable;
import se.streamsource.streamflow.web.domain.interaction.gtd.Owner;
import se.streamsource.streamflow.web.domain.interaction.gtd.RequiresAssigned;
import se.streamsource.streamflow.web.domain.interaction.gtd.RequiresOwner;
import se.streamsource.streamflow.web.domain.interaction.gtd.RequiresStatus;
import se.streamsource.streamflow.web.domain.interaction.gtd.RequiresUnread;
import se.streamsource.streamflow.web.domain.interaction.gtd.Status;
import se.streamsource.streamflow.web.domain.interaction.security.CaseAccess;
import se.streamsource.streamflow.web.domain.interaction.security.CaseAccessDefaults;
import se.streamsource.streamflow.web.domain.interaction.security.CaseAccessRestriction;
import se.streamsource.streamflow.web.domain.interaction.security.CaseAccessType;
import se.streamsource.streamflow.web.domain.interaction.security.PermissionType;
import se.streamsource.streamflow.web.domain.interaction.security.RequiresRestricted;
import se.streamsource.streamflow.web.domain.interaction.security.RequiresUnrestricted;
import se.streamsource.streamflow.web.domain.structure.casetype.CaseType;
import se.streamsource.streamflow.web.domain.structure.casetype.FormOnClose;
import se.streamsource.streamflow.web.domain.structure.casetype.Resolution;
import se.streamsource.streamflow.web.domain.structure.casetype.Resolvable;
import se.streamsource.streamflow.web.domain.structure.casetype.TypedCase;
import se.streamsource.streamflow.web.domain.structure.caze.Case;
import se.streamsource.streamflow.web.domain.structure.conversation.Conversation;
import se.streamsource.streamflow.web.domain.structure.conversation.Conversations;
import se.streamsource.streamflow.web.domain.structure.form.Form;
import se.streamsource.streamflow.web.domain.structure.form.SubmittedFormValue;
import se.streamsource.streamflow.web.domain.structure.form.SubmittedForms;
import se.streamsource.streamflow.web.domain.structure.organization.FormOnRemove;
import se.streamsource.streamflow.web.domain.structure.organization.Organization;
import se.streamsource.streamflow.web.domain.structure.organization.Organizations;
import se.streamsource.streamflow.web.domain.structure.organization.OwningOrganizationalUnit;
import se.streamsource.streamflow.web.domain.structure.project.Project;
/**
* JAVADOC
*/
@Concerns(UpdateCaseCountCacheConcern.class)
@Mixins(CaseCommandsContext.Mixin.class)
@RequiresPermission(PermissionType.write)
public interface CaseCommandsContext
extends DeleteContext, Context
{
// List possible actions
public LinksValue possiblesendto();
public LinksValue possibleresolutions();
@RequiresStatus( OPEN )
@RequiresAssigned(false)
@RequiresRemoved(false)
public LinksValue possibleassignees();
/**
* Assign the case to the user invoking the method
*/
@RequiresStatus( OPEN )
@RequiresAssigned(false)
@RequiresRemoved(false)
public void assign();
/**
* Assign the case to the given user
*/
@RequiresStatus( OPEN )
@RequiresAssigned(false)
@RequiresRemoved(false)
public void assignto( EntityValue assignee);
/**
* Mark the draft case as open
*/
@RequiresStatus(DRAFT)
@RequiresOwner
@RequiresRemoved(false)
public void open();
/**
* Mark the case as closed
*/
@RequiresStatus( OPEN )
@RequiresCaseType(false)
@HasResolutions(false)
@HasFormOnClose(false)
@SubCasesAreClosed
@RequiresRemoved(false)
public void close();
/**
* Mark the case as resolved and closed
*/
@RequiresStatus( OPEN )
@HasResolutions(true)
@RequiresCaseType(false)
@HasFormOnClose(false)
@RequiresRemoved(false)
@SubCasesAreClosed
public void resolve( EntityValue resolution );
/**
* Mark the case for submission of a certain form on close
* and close the case when submitted.
*/
@RequiresStatus( OPEN )
@HasFormOnClose(true)
@RequiresCaseType(false)
@RequiresRemoved(false)
@SubCasesAreClosed
public void formonclose();
@RequiresStatus({OPEN, DRAFT})
@HasFormOnDelete(true)
@RequiresRemoved(false)
public void formondelete();
@RequiresStatus({OPEN, DRAFT})
@HasFormOnDelete(true)
@RequiresRemoved(false)
public StringValue formondeletename();
@RequiresStatus( OPEN )
@RequiresCaseType(true)
@SubCasesAreClosed
@RequiresRemoved(false)
public void requirecasetype();
/**
* Mark the case as on-hold
* Removed as of SF-773
*/
/*
@RequiresAssigned
@RequiresStatus(OPEN)
@RequiresRemoved(false)
public void onhold();
*/
@RequiresStatus({DRAFT,OPEN})
@RequiresRemoved(false)
public void sendto( EntityValue entity );
@RequiresStatus(CLOSED)
@RequiresRemoved(false)
public void reopen();
@RequiresStatus(ON_HOLD)
@RequiresRemoved(false)
public void resume();
@RequiresAssigned()
@RequiresStatus(OPEN)
@RequiresRemoved(false)
public void unassign();
@RequiresStatus({OPEN, DRAFT})
@HasFormOnDelete(false)
@RequiresRemoved(false)
public void delete();
@RequiresRemoved()
@RequiresPermission(PermissionType.administrator)
public void reinstate();
@RequiresStatus( OPEN )
@RequiresUnrestricted()
@RequiresRemoved(false)
public void restrict();
@RequiresStatus( OPEN )
@RequiresRestricted()
@RequiresRemoved(false)
public void unrestrict();
@RequiresRemoved(false)
public PDDocument exportpdf( CaseOutputConfigDTO config ) throws Throwable;
@RequiresStatus( OPEN )
@RequiresUnread(false)
@RequiresRemoved(false)
public void markunread();
@RequiresStatus( OPEN )
@RequiresUnread(true)
@RequiresRemoved(false)
public void markread();
@RequiresStatus( OPEN )
@RequiresUnread(true)
@RequiresRemoved(false)
public void read();
abstract class Mixin
implements CaseCommandsContext
{
@Structure
Module module;
@Service
PdfGeneratorService pdfGenerator;
// List possible actions
public LinksValue possiblesendto()
{
LinksBuilder builder = new LinksBuilder( module.valueBuilderFactory() ).command( "sendto" );
List<Project> projects = RoleMap.role( CaseTypeQueries.class ).possibleProjects();
Ownable ownable = RoleMap.role( Ownable.class );
CaseType caseType = RoleMap.role( TypedCase.Data.class ).caseType().get();
for (Project project : projects)
{
if (!ownable.isOwnedBy( project ))
{
if (caseType == null || project.hasSelectedCaseType( caseType ))
builder.addDescribable( project, ((OwningOrganizationalUnit.Data) project).organizationalUnit().get() );
}
}
return builder.newLinks();
}
public LinksValue possibleresolutions()
{
LinksBuilder builder = new LinksBuilder( module.valueBuilderFactory() ).command( "resolve" );
CaseType type = RoleMap.role( TypedCase.Data.class ).caseType().get();
if (type != null)
{
Iterable<Resolution> resolutions = type.getSelectedResolutions();
builder.addDescribables( resolutions );
}
return builder.newLinks();
}
public LinksValue possibleassignees()
{
LinksBuilder builder = new LinksBuilder( module.valueBuilderFactory() ).command( "assignto" );
Owner owner = RoleMap.role( Ownable.Data.class ).owner().get();
for (Assignee assignee : ((ProjectMembersQueries)owner).possibleAssignees())
{
builder.addDescribable( (Describable)assignee );
}
return builder.newLinks();
}
// Commands
public void assign()
{
Assignable assignable = RoleMap.role( Assignable.class );
Assignee assignee = RoleMap.role( Actor.class );
assignable.assignTo( assignee );
}
public void assignto( EntityValue assigneeDTO)
{
Assignee assignee = module.unitOfWorkFactory().currentUnitOfWork().get( Actor.class, assigneeDTO.entity().get() );
Assignable assignable = RoleMap.role( Assignable.class );
Owner owner = RoleMap.role( Ownable.Data.class ).owner().get();
if (((ProjectMembersQueries)owner).possibleAssignees().contains( assignee ))
{
assignable.assignTo( assignee );
}
}
public void open()
{
Status aCase = RoleMap.role( Status.class );
aCase.open();
}
public void close()
{
CaseEntity aCase = RoleMap.role( CaseEntity.class );
Actor actor = RoleMap.role( Actor.class );
if (!aCase.isAssigned())
{
aCase.assignTo( actor );
}
aCase.close();
}
public void resolve( EntityValue resolutionDTO )
{
Resolution resolution = module.unitOfWorkFactory().currentUnitOfWork().get( Resolution.class, resolutionDTO.entity().get() );
Assignable assignable = RoleMap.role( Assignable.class );
Resolvable resolvable = RoleMap.role( Resolvable.class );
Status status = RoleMap.role( Status.class );
Actor actor = RoleMap.role( Actor.class );
if (!assignable.isAssigned())
{
assignable.assignTo( actor );
}
resolvable.resolve( resolution );
status.close();
}
public void formonclose()
{
CaseType caseType = RoleMap.role( TypedCase.Data.class ).caseType().get();
final Form formOnClose = (( FormOnClose.Data )caseType).formOnClose().get();
List<SubmittedFormValue> submittedForms = RoleMap.role( SubmittedForms.Data.class ).submittedForms().get();
boolean formOnCloseExists = matchesAny( new Specification<SubmittedFormValue>()
{
public boolean satisfiedBy( SubmittedFormValue item )
{
if (item.form().get().identity().equals( formOnClose.toString() ))
return true;
return false;
}
}, submittedForms );
if ( formOnCloseExists )
{
close();
} else
{
throw new RuntimeException( "No form on close submission." );
}
}
public void formondelete()
{
final Form form = getFormOnDelete();
List<SubmittedFormValue> submittedForms = RoleMap.role( SubmittedForms.Data.class ).submittedForms().get();
boolean formOnCloseExists = matchesAny( new Specification<SubmittedFormValue>()
{
public boolean satisfiedBy( SubmittedFormValue item )
{
if (item.form().get().identity().equals( form.toString() ))
return true;
return false;
}
}, submittedForms );
if ( formOnCloseExists )
{
delete();
} else
{
throw new RuntimeException( "No form on remove submission." );
}
}
public StringValue formondeletename()
{
final Form form = getFormOnDelete();
ValueBuilder<StringValue> builder = module.valueBuilderFactory().newValueBuilder( StringValue.class );
builder.prototype().string().set( form.getDescription() );
return builder.newInstance();
}
private Form getFormOnDelete()
{
Organizations.Data orgs = module.unitOfWorkFactory().currentUnitOfWork().get( OrganizationsEntity.class, OrganizationsEntity.ORGANIZATIONS_ID );
FormOnRemove.Data data = (FormOnRemove.Data) orgs.organization().get();
return data.formOnRemove().get();
}
public void onhold()
{
RoleMap.role( Status.class ).onHold();
}
public void sendto( EntityValue entity )
{
CaseEntity aCase = RoleMap.role( CaseEntity.class );
Owner toOwner = module.unitOfWorkFactory().currentUnitOfWork().get( Owner.class, entity.entity().get() );
if (aCase.isAssigned())
aCase.unassign();
aCase.changeOwner( toOwner );
}
public void reopen()
{
// Reopen the case, take away resolution, and assign to user who did the reopen
Status caze = RoleMap.role( Status.class );
caze.reopen();
Resolvable resolvable = RoleMap.role( Resolvable.class );
resolvable.unresolve();
Assignable assignable = RoleMap.role( Assignable.class );
Assignee assignee = RoleMap.role( Assignee.class );
assignable.assignTo( assignee );
}
public void resume()
{
RoleMap.role( Status.class ).resume();
}
public void unassign()
{
Assignable caze = RoleMap.role( Assignable.class );
caze.unassign();
}
public void delete()
{
Removable caze = RoleMap.role( Removable.class );
if( (CaseStates.DRAFT.equals( ((Status.Data) caze ).status().get() )) )
{
caze.deleteEntity();
} else
{
// just mark the case as removed
caze.removeEntity();
}
}
public void reinstate()
{
Removable caze = RoleMap.role( Removable.class );
caze.reinstate();
}
public void restrict()
{
CaseAccessRestriction secrecy = RoleMap.role( CaseAccessRestriction.class );
secrecy.restrict();
Organizations.Data orgs = module.unitOfWorkFactory().currentUnitOfWork().get( OrganizationsEntity.class, OrganizationsEntity.ORGANIZATIONS_ID );
Organization org = orgs.organization().get();
CaseAccessDefaults.Data defaults = (CaseAccessDefaults.Data) org;
CaseAccess access = RoleMap.role( CaseAccess.class );
for (Map.Entry<PermissionType, CaseAccessType> entry : defaults.accessPermissionDefaults().get().entrySet())
{
access.changeAccess( entry.getKey(), entry.getValue() );
}
}
/**
* This is not a perfect "undo". We cannot
* go back to the previous settings before the
* secrecy was enabled. Instead we force the
* settings for the project and the case type
*/
public void unrestrict()
{
CaseAccessRestriction secrecy = RoleMap.role( CaseAccessRestriction.class );
secrecy.unrestrict();
Ownable.Data owner = RoleMap.role( Ownable.Data.class );
// force set secrecy setting of the project
CaseAccessDefaults.Data defaults = (CaseAccessDefaults.Data) owner.owner().get();
CaseAccess access = RoleMap.role( CaseAccess.class );
access.clearAccess();
for (Map.Entry<PermissionType, CaseAccessType> entry : defaults.accessPermissionDefaults().get().entrySet())
{
access.changeAccess( entry.getKey(), entry.getValue() );
}
// apply the case type setting
TypedCase.Data data = RoleMap.role( TypedCase.Data.class );
defaults = (CaseAccessDefaults.Data) data.caseType().get();
if( defaults != null )
{
for (Map.Entry<PermissionType, CaseAccessType> entry : defaults.accessPermissionDefaults().get().entrySet())
{
access.changeAccess( entry.getKey(), entry.getValue() );
}
}
}
/**
* Do nothing - just a marker method to tell the client
* that there needs to be a case type set before closing this case.
*/
public void requirecasetype()
{
}
public PDDocument exportpdf( CaseOutputConfigDTO config ) throws Throwable
{
return pdfGenerator.generateCasePdf( role( CaseEntity.class ), config, role( Locale.class ) );
}
public void read()
{
RoleMap.role( Case.class ).setUnread( false );
}
public void markunread()
{
RoleMap.role( Case.class ).setUnread( true );
}
public void markread()
{
RoleMap.role( Case.class ).setUnread( false );
for ( Conversation conversation : RoleMap.role( Conversations.Data.class ).conversations() )
{
if( conversation.hasUnreadMessage() )
{
conversation.markRead();
}
}
int index = 0;
SubmittedForms submittedForms = RoleMap.role( SubmittedForms.class );
SubmittedForms.Data submittedFormsData = RoleMap.role( SubmittedForms.Data.class );
for( SubmittedFormValue submittedForm : submittedFormsData.submittedForms().get() )
{
if( submittedForm.unread().get() )
{
submittedForms.read( index );
}
index++;
}
}
}
}