Skip to content

Commit 4116904

Browse files
corbininseeSimonDmz
authored andcommitted
feat: Update tracking status for Individual surveys (Face-to-Face & T… (#208)
* feat: Update tracking status for Individual surveys (Face-to-Face & Telephone)
1 parent 68c4ab6 commit 4116904

File tree

15 files changed

+470
-420
lines changed

15 files changed

+470
-420
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,4 @@
290290
</plugins>
291291
</pluginManagement>
292292
</build>
293-
</project>
293+
</project>

src/main/java/fr/insee/pearljam/api/repository/SurveyUnitRepository.java

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -85,44 +85,43 @@ public interface SurveyUnitRepository extends JpaRepository<SurveyUnit, String>
8585
")")
8686
List<SurveyUnit> findClosableNoIdentSurveyUnitId(@Param("ids") List<String> ids);
8787

88-
@Query(value="SELECT su FROM SurveyUnit su left join fetch su.identification ident " +
89-
"WHERE su.id IN (:ids) " +
90-
"AND ( "+
91-
// First case : Contact outcome is IMP and identification not finished
92-
"( " +
93-
"EXISTS ( " +
94-
"SELECT 1 FROM ContactOutcomeDB co WHERE co.surveyUnit.id = su.id " +
95-
"AND co.type = 'IMP' " +
96-
") "+
97-
// and missing/incomplete identification
98-
"AND ( "+
99-
"(ident IS NULL ) " +
100-
"OR (ident.identification IS NULL )" +
101-
"OR (ident.identification = 'IDENTIFIED' AND ident.access IS NULL ) " +
102-
"OR (ident.identification = 'IDENTIFIED' AND ident.access IS NOT NULL AND ident.situation IS NULL ) " +
103-
"OR (ident.identification = 'IDENTIFIED' AND ident.access IS NOT NULL AND ident.situation = 'ORDINARY' AND ident.category IS NULL ) " +
104-
"OR (ident.identification = 'IDENTIFIED' AND ident.access IS NOT NULL AND ident.situation = 'ORDINARY' AND ident.category IN ('PRIMARY', 'OCCASIONAL', 'UNKNOWN') AND ident.occupant IS NULL ) " +
105-
") "+
106-
") " +
107-
"OR "+
108-
"( " +
109-
// Second case : contact outcome must be null or INA or NOA
110-
"NOT EXISTS ( "+
111-
"SELECT 1 FROM ContactOutcomeDB co WHERE co.surveyUnit.id = su.id " +
112-
"AND co.type NOT IN('INA','NOA')"+
113-
" ) " +
114-
// and identification is not started / incomplete / and doesn't lead to Out_of_scope (business rule here, not an enum)
115-
"AND ( "+
116-
"(ident IS NULL ) " +
117-
"OR (ident.identification IS NULL )" +
118-
"OR (ident.identification = 'IDENTIFIED' AND ident.access IS NULL ) " +
119-
"OR (ident.identification = 'IDENTIFIED' AND ident.access IS NOT NULL AND ident.situation IS NULL ) " +
120-
"OR (ident.identification = 'IDENTIFIED' AND ident.access IS NOT NULL AND ident.situation = 'ORDINARY' AND ident.category IS NULL ) " +
121-
"OR (ident.identification = 'IDENTIFIED' AND ident.access IS NOT NULL AND ident.situation = 'ORDINARY' AND ident.category NOT IN ('SECONDARY', 'VACANT') ) " +
122-
") " +
123-
") " +
124-
" )"
125-
)
88+
@Query(value = """
89+
SELECT su
90+
FROM SurveyUnit su
91+
LEFT JOIN FETCH su.identification ident
92+
WHERE su.id IN (:ids)
93+
AND (
94+
(
95+
EXISTS (
96+
SELECT 1
97+
FROM ContactOutcomeDB co
98+
WHERE co.surveyUnit.id = su.id
99+
AND co.type = 'IMP'
100+
)
101+
AND (
102+
ident IS NULL
103+
OR ident.identificationState = 'MISSING'
104+
OR ident.identificationState = 'ONGOING'
105+
)
106+
)
107+
OR
108+
(
109+
NOT EXISTS (
110+
SELECT 1
111+
FROM ContactOutcomeDB co
112+
WHERE co.surveyUnit.id = su.id
113+
AND co.type NOT IN ('INA','NOA')
114+
)
115+
AND (
116+
ident IS NULL
117+
OR ident.identificationState = 'MISSING'
118+
OR ident.identificationState = 'ONGOING'
119+
OR (ident.identificationState = 'FINISHED' AND ident.access IS NOT NULL AND ident.situation = 'ORDINARY' AND ident.category NOT IN ('SECONDARY', 'VACANT') )
120+
121+
)
122+
)
123+
)
124+
""")
126125
List<SurveyUnit> findClosableIascoSurveyUnitId(@Param("ids") List<String> ids);
127126

128127

Lines changed: 89 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
11
package fr.insee.pearljam.api.service.impl;
22

3-
import fr.insee.pearljam.api.domain.ContactOutcomeType;
4-
import fr.insee.pearljam.api.domain.IdentificationConfiguration;
5-
import fr.insee.pearljam.api.domain.SurveyUnit;
6-
import fr.insee.pearljam.api.service.SurveyUnitUpdateService;
3+
import fr.insee.pearljam.api.domain.*;
74
import fr.insee.pearljam.api.surveyunit.dto.CommentDto;
85
import fr.insee.pearljam.api.surveyunit.dto.CommunicationRequestCreateDto;
96
import fr.insee.pearljam.api.surveyunit.dto.ContactOutcomeDto;
107
import fr.insee.pearljam.api.surveyunit.dto.SurveyUnitUpdateDto;
118
import fr.insee.pearljam.api.surveyunit.dto.identification.IdentificationDto;
9+
import fr.insee.pearljam.domain.campaign.port.userside.DateService;
1210
import fr.insee.pearljam.domain.campaign.model.Visibility;
1311
import fr.insee.pearljam.domain.campaign.model.communication.CommunicationMedium;
1412
import fr.insee.pearljam.domain.campaign.model.communication.CommunicationTemplate;
1513
import fr.insee.pearljam.domain.campaign.port.serverside.CommunicationTemplateRepository;
16-
import fr.insee.pearljam.domain.campaign.port.userside.DateService;
1714
import fr.insee.pearljam.domain.campaign.port.userside.VisibilityService;
1815
import fr.insee.pearljam.domain.exception.CommunicationTemplateNotFoundException;
1916
import fr.insee.pearljam.domain.exception.VisibilityNotFoundException;
2017
import fr.insee.pearljam.domain.surveyunit.model.Comment;
2118
import fr.insee.pearljam.domain.surveyunit.model.ContactOutcome;
2219
import fr.insee.pearljam.domain.surveyunit.model.Identification;
2320
import fr.insee.pearljam.domain.surveyunit.model.communication.CommunicationRequest;
21+
import fr.insee.pearljam.api.service.SurveyUnitUpdateService;
2422
import fr.insee.pearljam.domain.surveyunit.port.serverside.CommunicationRequestRepository;
23+
import fr.insee.pearljam.infrastructure.surveyunit.entity.identification.IdentificationDB;
24+
import java.util.Optional;
2525
import lombok.RequiredArgsConstructor;
2626
import lombok.extern.slf4j.Slf4j;
2727
import org.springframework.stereotype.Service;
2828
import org.springframework.transaction.annotation.Transactional;
29-
3029
import java.util.List;
3130
import java.util.Set;
3231
import java.util.stream.Collectors;
@@ -36,98 +35,98 @@
3635
@Slf4j
3736
public class SurveyUnitUpdateServiceImpl implements SurveyUnitUpdateService {
3837

39-
private final CommunicationRequestRepository communicationRequestRepository;
40-
private final CommunicationTemplateRepository communicationTemplateRepository;
41-
private final VisibilityService visibilityService;
42-
private final DateService dateService;
38+
private final CommunicationRequestRepository communicationRequestRepository;
39+
private final CommunicationTemplateRepository communicationTemplateRepository;
40+
private final VisibilityService visibilityService;
41+
private final DateService dateService;
42+
43+
@Transactional
44+
@Override
45+
public void updateSurveyUnitInfos(SurveyUnit surveyUnit, SurveyUnitUpdateDto surveyUnitUpdateDto) {
46+
if(surveyUnitUpdateDto.comments() != null) {
47+
Set<Comment> commentsToUpdate = surveyUnitUpdateDto.comments().stream()
48+
.map(commentDto -> CommentDto.toModel(surveyUnit.getId(), commentDto))
49+
.collect(Collectors.toSet());
50+
51+
surveyUnit.updateComments(commentsToUpdate);
52+
}
53+
if(surveyUnitUpdateDto.communicationRequests() != null) {
54+
Long timestamp = dateService.getCurrentTimestamp();
55+
List<CommunicationRequest> communicationRequestsToCreate =
56+
surveyUnitUpdateDto.communicationRequests()
57+
.stream()
58+
.map(communicationRequestCreateDto -> getNewCommunicationRequest(communicationRequestCreateDto, surveyUnit, timestamp))
59+
.toList();
60+
communicationRequestRepository.addCommunicationRequests(surveyUnit, communicationRequestsToCreate);
61+
}
62+
IdentificationConfiguration identificationConfiguration = surveyUnit.getCampaign().getIdentificationConfiguration();
4363

44-
@Transactional
45-
@Override
46-
public void updateSurveyUnitInfos(SurveyUnit surveyUnit, SurveyUnitUpdateDto surveyUnitUpdateDto) {
47-
if (surveyUnitUpdateDto.comments() != null) {
48-
Set<Comment> commentsToUpdate = surveyUnitUpdateDto.comments().stream()
49-
.map(commentDto -> CommentDto.toModel(surveyUnit.getId(), commentDto))
50-
.collect(Collectors.toSet());
64+
Identification identification = Optional.ofNullable(surveyUnitUpdateDto.identification())
65+
.map(idDto -> IdentificationDto.toModel(idDto, identificationConfiguration))
66+
.orElseGet(() -> IdentificationDB.toModel(surveyUnit.getIdentification()));
5167

52-
surveyUnit.updateComments(commentsToUpdate);
53-
}
54-
if (surveyUnitUpdateDto.communicationRequests() != null) {
55-
Long timestamp = dateService.getCurrentTimestamp();
56-
List<CommunicationRequest> communicationRequestsToCreate =
57-
surveyUnitUpdateDto.communicationRequests()
58-
.stream()
59-
.map(communicationRequestCreateDto -> getNewCommunicationRequest(communicationRequestCreateDto, surveyUnit, timestamp))
60-
.toList();
61-
communicationRequestRepository.addCommunicationRequests(surveyUnit, communicationRequestsToCreate);
62-
}
63-
IdentificationConfiguration identificationConfiguration =
64-
surveyUnit.getCampaign().getIdentificationConfiguration();
65-
Identification identification = IdentificationDto.toModel(surveyUnitUpdateDto.identification(),
66-
identificationConfiguration);
67-
surveyUnit.updateIdentification(identification);
68+
surveyUnit.updateIdentification(identification);
6869

69-
//update ContactOutcome
70-
ContactOutcome contactOutcome = ContactOutcomeDto.toModel(surveyUnit.getId(),
71-
surveyUnitUpdateDto.contactOutcome());
72-
contactOutcome = convertDeprecatedContactOutcomeValue(contactOutcome);
73-
surveyUnit.updateContactOutcome(contactOutcome);
74-
}
70+
//update ContactOutcome
71+
ContactOutcome contactOutcome = ContactOutcomeDto.toModel(surveyUnit.getId(),
72+
surveyUnitUpdateDto.contactOutcome());
73+
contactOutcome = convertDeprecatedContactOutcomeValue(contactOutcome);
74+
surveyUnit.updateContactOutcome(contactOutcome);
75+
}
7576

76-
// when DCD and DUU values are not used anymore => to be removed
77-
private ContactOutcome convertDeprecatedContactOutcomeValue(ContactOutcome contactOutcome) {
78-
if (contactOutcome == null) {
79-
return null;
80-
}
81-
return switch (contactOutcome.type()) {
82-
case DCD -> new ContactOutcome(contactOutcome.id(), contactOutcome.date(), ContactOutcomeType.NOA,
83-
contactOutcome.totalNumberOfContactAttempts(), contactOutcome.surveyUnitId());
84-
case DUU -> new ContactOutcome(contactOutcome.id(), contactOutcome.date(), ContactOutcomeType.DUK,
85-
contactOutcome.totalNumberOfContactAttempts(), contactOutcome.surveyUnitId());
86-
case INA, REF, IMP, UCD, UTR, ALA, DUK, NUH, NOA -> contactOutcome;
87-
};
88-
}
77+
// when DCD and DUU values are not used anymore => to be removed
78+
private ContactOutcome convertDeprecatedContactOutcomeValue(ContactOutcome contactOutcome) {
79+
if (contactOutcome == null) {
80+
return null;
81+
}
82+
return switch (contactOutcome.type()) {
83+
case DCD -> new ContactOutcome(contactOutcome.id(), contactOutcome.date(), ContactOutcomeType.NOA,
84+
contactOutcome.totalNumberOfContactAttempts(), contactOutcome.surveyUnitId());
85+
case DUU -> new ContactOutcome(contactOutcome.id(), contactOutcome.date(), ContactOutcomeType.DUK,
86+
contactOutcome.totalNumberOfContactAttempts(), contactOutcome.surveyUnitId());
87+
case INA, REF, IMP, UCD, UTR, ALA, DUK, NUH, NOA -> contactOutcome;
88+
};
89+
}
8990

90-
/**
91-
* This method checks the validity of a communication request
92-
*
93-
* @param communicationRequestToCreate communication request to create
94-
* @param surveyUnit the survey unit to update
95-
* @return a new communication request
96-
*/
97-
private CommunicationRequest getNewCommunicationRequest(CommunicationRequestCreateDto communicationRequestToCreate
98-
, SurveyUnit surveyUnit, Long readyTimestamp) {
99-
String campaignId = surveyUnit.getCampaign().getId();
100-
CommunicationTemplate communicationTemplate = communicationTemplateRepository
101-
.findCommunicationTemplate(communicationRequestToCreate.communicationTemplateId(), campaignId)
102-
.orElseThrow(CommunicationTemplateNotFoundException::new);
91+
/**
92+
* This method checks the validity of a communication request
93+
* @param communicationRequestToCreate communication request to create
94+
* @param surveyUnit the survey unit to update
95+
* @return a new communication request
96+
*/
97+
private CommunicationRequest getNewCommunicationRequest(CommunicationRequestCreateDto communicationRequestToCreate, SurveyUnit surveyUnit, Long readyTimestamp) {
98+
String campaignId = surveyUnit.getCampaign().getId();
99+
CommunicationTemplate communicationTemplate = communicationTemplateRepository
100+
.findCommunicationTemplate(communicationRequestToCreate.communicationTemplateId(), campaignId)
101+
.orElseThrow(CommunicationTemplateNotFoundException::new);
103102

104-
if (!communicationTemplate.medium().equals(CommunicationMedium.LETTER)) {
105-
return CommunicationRequest.create(
106-
communicationRequestToCreate.communicationTemplateId(),
107-
communicationRequestToCreate.creationTimestamp(),
108-
readyTimestamp,
109-
communicationRequestToCreate.reason());
110-
}
103+
if(!communicationTemplate.medium().equals(CommunicationMedium.LETTER)) {
104+
return CommunicationRequest.create(
105+
communicationRequestToCreate.communicationTemplateId(),
106+
communicationRequestToCreate.creationTimestamp(),
107+
readyTimestamp,
108+
communicationRequestToCreate.reason());
109+
}
111110

112-
Visibility visibility = visibilityService
113-
.findVisibility(campaignId, surveyUnit.getOrganizationUnit().getId())
114-
.orElseThrow(VisibilityNotFoundException::new);
111+
Visibility visibility = visibilityService
112+
.findVisibility(campaignId, surveyUnit.getOrganizationUnit().getId())
113+
.orElseThrow(VisibilityNotFoundException::new);
115114

116115

117-
if (visibility.useLetterCommunication() != null && visibility.useLetterCommunication()) {
118-
return CommunicationRequest.create(
119-
communicationRequestToCreate.communicationTemplateId(),
120-
communicationRequestToCreate.creationTimestamp(),
121-
readyTimestamp,
122-
communicationRequestToCreate.reason());
123-
}
116+
if(visibility.useLetterCommunication() != null && visibility.useLetterCommunication()) {
117+
return CommunicationRequest.create(
118+
communicationRequestToCreate.communicationTemplateId(),
119+
communicationRequestToCreate.creationTimestamp(),
120+
readyTimestamp,
121+
communicationRequestToCreate.reason());
122+
}
124123

125-
// if the communication request is a letter communication request, but the visibility doesn't admit it,
126-
// create a cancelled communication request
127-
return CommunicationRequest.createCancelled(
128-
communicationRequestToCreate.communicationTemplateId(),
129-
communicationRequestToCreate.creationTimestamp(),
130-
dateService.getCurrentTimestamp(),
131-
communicationRequestToCreate.reason());
132-
}
124+
// if the communication request is a letter communication request, but the visibility doesn't admit it,
125+
// create a cancelled communication request
126+
return CommunicationRequest.createCancelled(
127+
communicationRequestToCreate.communicationTemplateId(),
128+
communicationRequestToCreate.creationTimestamp(),
129+
dateService.getCurrentTimestamp(),
130+
communicationRequestToCreate.reason());
131+
}
133132
}

src/main/java/fr/insee/pearljam/domain/surveyunit/model/IdentificationState.java

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import fr.insee.pearljam.api.domain.IdentificationConfiguration;
44
import fr.insee.pearljam.domain.surveyunit.model.question.CategoryQuestionValue;
55
import fr.insee.pearljam.domain.surveyunit.model.question.IdentificationQuestionValue;
6+
import fr.insee.pearljam.domain.surveyunit.model.question.IndividualStatusQuestionValue;
7+
import fr.insee.pearljam.domain.surveyunit.model.question.InterviewerCanProcessQuestionValue;
68
import fr.insee.pearljam.domain.surveyunit.model.question.SituationQuestionValue;
9+
import java.util.EnumSet;
710

811
public enum IdentificationState {
912
MISSING, FINISHED, ONGOING;
@@ -26,21 +29,69 @@ public static IdentificationState getState(Identification identification,
2629

2730
case IASCO, HOUSEF2F -> houseF2F(identification);
2831

32+
case INDF2F, INDF2FNOR -> individuF2F(identification);
33+
34+
case INDTEL, INDTELNOR -> individuTel(identification);
35+
2936
// additional rules coming soon
30-
case HOUSETEL, HOUSETELWSR, INDF2F, INDF2FNOR, INDTEL, INDTELNOR, SRCVREINT -> IdentificationState.MISSING;
37+
case HOUSETEL, HOUSETELWSR, SRCVREINT -> IdentificationState.MISSING;
3138

3239
};
3340

41+
}
42+
43+
private static IdentificationState individuTel(Identification identification) {
44+
if (identification.identification() == null) return IdentificationState.MISSING;
45+
46+
IndividualStatusQuestionValue status = identification.individualStatus();
47+
if (status == null) return IdentificationState.ONGOING;
48+
if (EnumSet.of(IndividualStatusQuestionValue.DCD, IndividualStatusQuestionValue.NOIDENT, IndividualStatusQuestionValue.NOFIELD)
49+
.contains(status))
50+
return IdentificationState.FINISHED;
3451

52+
SituationQuestionValue situation = identification.situation();
53+
if (EnumSet.of(IndividualStatusQuestionValue.SAME_ADDRESS, IndividualStatusQuestionValue.OTHER_ADDRESS).contains(status))
54+
return (situation == null) ? IdentificationState.ONGOING : IdentificationState.FINISHED;
55+
56+
return (situation != null) ? IdentificationState.ONGOING : null;
3557
}
3658

37-
private static IdentificationState houseF2F(Identification identification) {
38-
IdentificationQuestionValue identificationQuestionValue = identification.identification();
39-
if (identificationQuestionValue == null) {
40-
return IdentificationState.MISSING;
59+
60+
private static IdentificationState individuF2F(Identification identification) {
61+
if (identification.identification() == null) return IdentificationState.MISSING;
62+
63+
IndividualStatusQuestionValue status = identification.individualStatus();
64+
if (status == null) return IdentificationState.ONGOING;
65+
if (EnumSet.of(IndividualStatusQuestionValue.DCD, IndividualStatusQuestionValue.NOIDENT, IndividualStatusQuestionValue.NOFIELD)
66+
.contains(status))
67+
return IdentificationState.FINISHED;
68+
69+
SituationQuestionValue situation = identification.situation();
70+
if (status == IndividualStatusQuestionValue.SAME_ADDRESS)
71+
return (situation == null) ? IdentificationState.ONGOING : IdentificationState.FINISHED;
72+
73+
InterviewerCanProcessQuestionValue interviewer = identification.interviewerCanProcess();
74+
if (status == IndividualStatusQuestionValue.OTHER_ADDRESS) {
75+
if (interviewer == null) return IdentificationState.ONGOING;
76+
if (interviewer == InterviewerCanProcessQuestionValue.NO) return IdentificationState.FINISHED;
4177
}
4278

43-
if (identificationQuestionValue == IdentificationQuestionValue.DESTROY ||
79+
if (interviewer == InterviewerCanProcessQuestionValue.YES && situation == null) return IdentificationState.ONGOING;
80+
if (EnumSet.of(SituationQuestionValue.NOORDINARY, SituationQuestionValue.ABSORBED).contains(situation))
81+
return IdentificationState.FINISHED;
82+
83+
if (interviewer == InterviewerCanProcessQuestionValue.NO) return IdentificationState.ONGOING;
84+
85+
return null;
86+
}
87+
88+
private static IdentificationState houseF2F(Identification identification) {
89+
IdentificationQuestionValue identificationQuestionValue = identification.identification();
90+
if (identificationQuestionValue == null) {
91+
return IdentificationState.MISSING;
92+
}
93+
94+
if (identificationQuestionValue == IdentificationQuestionValue.DESTROY ||
4495
identificationQuestionValue == IdentificationQuestionValue.UNIDENTIFIED) {
4596
return IdentificationState.FINISHED;
4697
}

0 commit comments

Comments
 (0)