class SpendProfileServiceImpl extends BaseTransactionalService implements SpendProfileService {

    @Override
    @Transactional
    public ServiceResult<Void> generateSpendProfile(Long projectId) {
        return getProject(projectId)
                .andOnSuccess(project -> canSpendProfileCanBeGenerated(project)
                        .andOnSuccess(() -> partnerOrganisationService.getProjectPartnerOrganisations(projectId)
                                .andOnSuccess(partnerOrganisationResources -> {
                                    List<Long> organisationIds = removeDuplicates(simpleMap(partnerOrganisationResources, PartnerOrganisationResource::getOrganisation));
                                    return generateSpendProfileForPartnerOrganisations(project, organisationIds);
                                }))
                        .andOnSuccess(() ->
                                getCurrentlyLoggedInUser().andOnSuccess(user -> {
                                    if (spendProfileWorkflowHandler.spendProfileGenerated(project, user)) {
                                        return serviceSuccess(project);
                                    } else {
                                        LOG.error(String.format(SPEND_PROFILE_STATE_ERROR, project.getId()));
                                        return serviceFailure(GENERAL_UNEXPECTED_ERROR);
                                    }
                                })
                        )
                )
                .andOnSuccess(project -> {
                    if (!competitionHasSpendProfileStage(project)) {
                        List<ServiceResult<Void>> markAsCompleteResults = project.getPartnerOrganisations()
                                .stream()
                                .map(po -> markSpendProfileComplete(ProjectOrganisationCompositeId.id(project.getId(), po.getOrganisation().getId())))
                                .collect(toList());
                        return aggregate(markAsCompleteResults)
                                .andOnSuccess(() -> completeSpendProfilesReview(projectId))
                                .andOnSuccess(() -> approveOrRejectSpendProfile(projectId, APPROVED));
                    }
                    return serviceSuccess();
                });
    }

}