mirror of
https://github.com/pcvolkmer/mv64e-onkostar-data.git
synced 2025-07-02 02:22:54 +00:00
feat: add initial support for recommendations
This commit is contained in:
@ -30,11 +30,11 @@ public class ResultSet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the procedure id
|
||||
* Get the id
|
||||
*
|
||||
* @return The procedure id if any
|
||||
* @return The procedures id
|
||||
*/
|
||||
public Integer getProcedureId() {
|
||||
public Integer getId() {
|
||||
var procedureId = this.getInteger("id");
|
||||
if (procedureId == null) {
|
||||
throw new DataAccessException("No procedure id found");
|
||||
@ -42,19 +42,6 @@ public class ResultSet {
|
||||
return procedureId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the disease id
|
||||
*
|
||||
* @return The procedure id if any
|
||||
*/
|
||||
public Integer getDiseaseId() {
|
||||
var diseaseId = this.getInteger("id");
|
||||
if (diseaseId == null) {
|
||||
throw new DataAccessException("No disease id found");
|
||||
}
|
||||
return diseaseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column value as String and cast value if possible
|
||||
*
|
||||
|
@ -69,6 +69,8 @@ public class DataCatalogueFactory {
|
||||
return VorbefundeCatalogue.create(jdbcTemplate);
|
||||
} else if (c == TherapieplanCatalogue.class) {
|
||||
return TherapieplanCatalogue.create(jdbcTemplate);
|
||||
} else if (c == EinzelempfehlungCatalogue.class) {
|
||||
return EinzelempfehlungCatalogue.create(jdbcTemplate);
|
||||
}
|
||||
throw new DataCatalogueCreationException(clazz);
|
||||
});
|
||||
|
@ -0,0 +1,26 @@
|
||||
package dev.pcvolkmer.onco.datamapper.datacatalogues;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
/**
|
||||
* Load raw result sets from database table 'dk_dnpm_uf_einzelempfehlung'
|
||||
*
|
||||
* @author Paul-Christian Volkmer
|
||||
* @since 0.1
|
||||
*/
|
||||
public class EinzelempfehlungCatalogue extends AbstractSubformDataCatalogue {
|
||||
|
||||
private EinzelempfehlungCatalogue(JdbcTemplate jdbcTemplate) {
|
||||
super(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableName() {
|
||||
return "dk_dnpm_uf_einzelempfehlung";
|
||||
}
|
||||
|
||||
public static EinzelempfehlungCatalogue create(JdbcTemplate jdbcTemplate) {
|
||||
return new EinzelempfehlungCatalogue(jdbcTemplate);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package dev.pcvolkmer.onco.datamapper.mapper;
|
||||
|
||||
import dev.pcvolkmer.mv64e.mtb.*;
|
||||
import dev.pcvolkmer.onco.datamapper.ResultSet;
|
||||
import dev.pcvolkmer.onco.datamapper.datacatalogues.EinzelempfehlungCatalogue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static dev.pcvolkmer.onco.datamapper.mapper.MapperUtils.getPatientReference;
|
||||
|
||||
/**
|
||||
* Mapper class to load and map diagnosis data from database table 'dk_dnpm_einzelempfehlung'
|
||||
*
|
||||
* @author Paul-Christian Volkmer
|
||||
* @since 0.1
|
||||
*/
|
||||
public class EinzelempfehlungProzedurDataMapper extends AbstractSubformDataMapper<ProcedureRecommendation> {
|
||||
|
||||
public EinzelempfehlungProzedurDataMapper(EinzelempfehlungCatalogue einzelempfehlungCatalogue) {
|
||||
super(einzelempfehlungCatalogue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProcedureRecommendation map(ResultSet resultSet) {
|
||||
return ProcedureRecommendation.builder()
|
||||
.id(resultSet.getString("id"))
|
||||
.patient(getPatientReference(resultSet.getString("patient_id")))
|
||||
// TODO Fix id?
|
||||
.reason(Reference.builder().id(resultSet.getString("id")).build())
|
||||
.issuedOn(resultSet.getDate("datum"))
|
||||
.priority(
|
||||
getRecommendationPriorityCoding(
|
||||
resultSet.getString("evidenzlevel"),
|
||||
resultSet.getInteger("evidenzlevel_propcat_version")
|
||||
)
|
||||
)
|
||||
.code(
|
||||
getMtbProcedureRecommendationCategoryCoding(
|
||||
resultSet.getString("art_der_therapie"),
|
||||
resultSet.getInteger("art_der_therapie_propcat_version")
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcedureRecommendation getById(int id) {
|
||||
return this.map(this.catalogue.getById(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProcedureRecommendation> getByParentId(final int parentId) {
|
||||
return catalogue.getAllByParentId(parentId)
|
||||
.stream()
|
||||
// Filter Prozedurempfehlung (Weitere Empfehlungen)
|
||||
.filter(it -> it.getString("art_der_therapie") != null && !it.getString("art_der_therapie").isBlank() )
|
||||
.map(this::map)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private RecommendationPriorityCoding getRecommendationPriorityCoding(String code, int version) {
|
||||
if (code == null || !Arrays.stream(RecommendationPriorityCodingCode.values()).map(RecommendationPriorityCodingCode::toValue).collect(Collectors.toSet()).contains(code)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var resultBuilder = RecommendationPriorityCoding.builder()
|
||||
.system("dnpm-dip/recommendation/priority");
|
||||
|
||||
try {
|
||||
resultBuilder
|
||||
.code(RecommendationPriorityCodingCode.forValue(code))
|
||||
.display(code);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resultBuilder.build();
|
||||
}
|
||||
|
||||
private MtbProcedureRecommendationCategoryCoding getMtbProcedureRecommendationCategoryCoding(String code, int version) {
|
||||
if (code == null || !Arrays.stream(MtbProcedureRecommendationCategoryCodingCode.values()).map(MtbProcedureRecommendationCategoryCodingCode::toValue).collect(Collectors.toSet()).contains(code)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var resultBuilder = MtbProcedureRecommendationCategoryCoding.builder()
|
||||
.system("dnpm-dip/mtb/recommendation/procedure/category");
|
||||
|
||||
try {
|
||||
resultBuilder
|
||||
.code(MtbProcedureRecommendationCategoryCodingCode.forValue(code))
|
||||
.display(code);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resultBuilder.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package dev.pcvolkmer.onco.datamapper.mapper;
|
||||
|
||||
import dev.pcvolkmer.mv64e.mtb.MtbMedicationRecommendation;
|
||||
import dev.pcvolkmer.mv64e.mtb.RecommendationPriorityCoding;
|
||||
import dev.pcvolkmer.mv64e.mtb.RecommendationPriorityCodingCode;
|
||||
import dev.pcvolkmer.mv64e.mtb.Reference;
|
||||
import dev.pcvolkmer.onco.datamapper.ResultSet;
|
||||
import dev.pcvolkmer.onco.datamapper.datacatalogues.EinzelempfehlungCatalogue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static dev.pcvolkmer.onco.datamapper.mapper.MapperUtils.getPatientReference;
|
||||
|
||||
/**
|
||||
* Mapper class to load and map diagnosis data from database table 'dk_dnpm_einzelempfehlung'
|
||||
*
|
||||
* @author Paul-Christian Volkmer
|
||||
* @since 0.1
|
||||
*/
|
||||
public class EinzelempfehlungWirkstoffDataMapper extends AbstractSubformDataMapper<MtbMedicationRecommendation> {
|
||||
|
||||
public EinzelempfehlungWirkstoffDataMapper(EinzelempfehlungCatalogue einzelempfehlungCatalogue) {
|
||||
super(einzelempfehlungCatalogue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MtbMedicationRecommendation map(ResultSet resultSet) {
|
||||
return MtbMedicationRecommendation.builder()
|
||||
.id(resultSet.getString("id"))
|
||||
.patient(getPatientReference(resultSet.getString("patient_id")))
|
||||
// TODO Fix id?
|
||||
.reason(Reference.builder().id(resultSet.getString("id")).build())
|
||||
.issuedOn(resultSet.getDate("datum"))
|
||||
.priority(
|
||||
getRecommendationPriorityCoding(
|
||||
resultSet.getString("evidenzlevel"),
|
||||
resultSet.getInteger("evidenzlevel_propcat_version")
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MtbMedicationRecommendation getById(int id) {
|
||||
return this.map(this.catalogue.getById(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MtbMedicationRecommendation> getByParentId(final int parentId) {
|
||||
return catalogue.getAllByParentId(parentId)
|
||||
.stream()
|
||||
// Filter Wirkstoffempfehlung (Systemische Therapie)
|
||||
.filter(it -> it.getString("art_der_therapie") == null || it.getString("art_der_therapie").isBlank() )
|
||||
.map(this::map)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private RecommendationPriorityCoding getRecommendationPriorityCoding(String code, int version) {
|
||||
if (code == null || !Arrays.stream(RecommendationPriorityCodingCode.values()).map(RecommendationPriorityCodingCode::toValue).collect(Collectors.toSet()).contains(code)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var resultBuilder = RecommendationPriorityCoding.builder()
|
||||
.system("dnpm-dip/recommendation/priority");
|
||||
|
||||
try {
|
||||
resultBuilder
|
||||
.code(RecommendationPriorityCodingCode.forValue(code))
|
||||
.display(code);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resultBuilder.build();
|
||||
}
|
||||
|
||||
}
|
@ -51,7 +51,7 @@ public class KpaEcogDataMapper extends AbstractSubformDataMapper<PerformanceStat
|
||||
protected PerformanceStatus map(final ResultSet resultSet) {
|
||||
var builder = PerformanceStatus.builder();
|
||||
builder
|
||||
.id(resultSet.getProcedureId().toString())
|
||||
.id(resultSet.getId().toString())
|
||||
.patient(getPatientReference(resultSet.getString("patient_id")))
|
||||
.effectiveDate(resultSet.getDate("datum"))
|
||||
.value(getEcogCoding(resultSet.getString("ecog")))
|
||||
|
@ -35,17 +35,17 @@ public class KpaProzedurDataMapper extends AbstractKpaTherapieverlaufDataMapper<
|
||||
|
||||
@Override
|
||||
protected OncoProcedure map(final ResultSet resultSet) {
|
||||
var diseases = catalogue.getDiseases(resultSet.getProcedureId());
|
||||
var diseases = catalogue.getDiseases(resultSet.getId());
|
||||
|
||||
if (diseases.size() != 1) {
|
||||
throw new IllegalStateException(String.format("No unique disease for procedure %s", resultSet.getProcedureId()));
|
||||
throw new IllegalStateException(String.format("No unique disease for procedure %s", resultSet.getId()));
|
||||
}
|
||||
|
||||
var builder = OncoProcedure.builder();
|
||||
builder
|
||||
.id(resultSet.getString("id"))
|
||||
.patient(getPatientReference(resultSet.getString("patient_id")))
|
||||
.basedOn(Reference.builder().id(diseases.get(0).getDiseaseId().toString()).build())
|
||||
.basedOn(Reference.builder().id(diseases.get(0).getString("id")).build())
|
||||
.recordedOn(resultSet.getDate("erfassungsdatum"))
|
||||
.therapyLine(resultSet.getLong("therapielinie"))
|
||||
.intent(
|
||||
|
@ -35,17 +35,17 @@ public class KpaTherapielinieDataMapper extends AbstractKpaTherapieverlaufDataMa
|
||||
|
||||
@Override
|
||||
protected MtbSystemicTherapy map(final ResultSet resultSet) {
|
||||
var diseases = catalogue.getDiseases(resultSet.getProcedureId());
|
||||
var diseases = catalogue.getDiseases(resultSet.getId());
|
||||
|
||||
if (diseases.size() != 1) {
|
||||
throw new IllegalStateException(String.format("No unique disease for procedure %s", resultSet.getProcedureId()));
|
||||
throw new IllegalStateException(String.format("No unique disease for procedure %s", resultSet.getId()));
|
||||
}
|
||||
|
||||
var builder = MtbSystemicTherapy.builder();
|
||||
builder
|
||||
.id(resultSet.getString("id"))
|
||||
.patient(getPatientReference(resultSet.getString("patient_id")))
|
||||
.basedOn(Reference.builder().id(diseases.get(0).getDiseaseId().toString()).build())
|
||||
.basedOn(Reference.builder().id(diseases.get(0).getString("id")).build())
|
||||
.recordedOn(resultSet.getDate("erfassungsdatum"))
|
||||
.therapyLine(resultSet.getLong("therapielinie"))
|
||||
.intent(
|
||||
|
@ -80,7 +80,11 @@ public class MtbDataMapper implements DataMapper<Mtb> {
|
||||
);
|
||||
|
||||
var therapieplanCatalogue = catalogueFactory.catalogue(TherapieplanCatalogue.class);
|
||||
var therapieplanDataMapper = new TherapieplanDataMapper(therapieplanCatalogue, propertyCatalogue);
|
||||
var therapieplanDataMapper = new TherapieplanDataMapper(
|
||||
therapieplanCatalogue,
|
||||
catalogueFactory.catalogue(EinzelempfehlungCatalogue.class),
|
||||
propertyCatalogue
|
||||
);
|
||||
|
||||
var resultBuilder = Mtb.builder();
|
||||
|
||||
|
@ -2,6 +2,7 @@ package dev.pcvolkmer.onco.datamapper.mapper;
|
||||
|
||||
import dev.pcvolkmer.mv64e.mtb.MtbCarePlan;
|
||||
import dev.pcvolkmer.onco.datamapper.PropertyCatalogue;
|
||||
import dev.pcvolkmer.onco.datamapper.datacatalogues.EinzelempfehlungCatalogue;
|
||||
import dev.pcvolkmer.onco.datamapper.datacatalogues.TherapieplanCatalogue;
|
||||
|
||||
import static dev.pcvolkmer.onco.datamapper.mapper.MapperUtils.getPatientReference;
|
||||
@ -17,12 +18,19 @@ public class TherapieplanDataMapper implements DataMapper<MtbCarePlan> {
|
||||
private final TherapieplanCatalogue therapieplanCatalogue;
|
||||
private final PropertyCatalogue propertyCatalogue;
|
||||
|
||||
private final EinzelempfehlungProzedurDataMapper einzelempfehlungProzedurDataMapper;
|
||||
private final EinzelempfehlungWirkstoffDataMapper einzelempfehlungWirkstoffDataMapper;
|
||||
|
||||
public TherapieplanDataMapper(
|
||||
final TherapieplanCatalogue therapieplanCatalogue,
|
||||
final EinzelempfehlungCatalogue einzelempfehlungCatalogue,
|
||||
final PropertyCatalogue propertyCatalogue
|
||||
) {
|
||||
this.therapieplanCatalogue = therapieplanCatalogue;
|
||||
this.propertyCatalogue = propertyCatalogue;
|
||||
|
||||
this.einzelempfehlungProzedurDataMapper = new EinzelempfehlungProzedurDataMapper(einzelempfehlungCatalogue);
|
||||
this.einzelempfehlungWirkstoffDataMapper = new EinzelempfehlungWirkstoffDataMapper(einzelempfehlungCatalogue);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,6 +48,8 @@ public class TherapieplanDataMapper implements DataMapper<MtbCarePlan> {
|
||||
.id(therapieplanData.getString("id"))
|
||||
.patient(getPatientReference(therapieplanData.getString("patient_id")))
|
||||
.issuedOn(therapieplanData.getDate("datum"))
|
||||
.medicationRecommendations(einzelempfehlungWirkstoffDataMapper.getByParentId(id))
|
||||
.procedureRecommendations(einzelempfehlungProzedurDataMapper.getByParentId(id))
|
||||
;
|
||||
return builder.build();
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
package dev.pcvolkmer.onco.datamapper.datacatalogues;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class EinzelempfehlungCatalogueTest {
|
||||
|
||||
JdbcTemplate jdbcTemplate;
|
||||
EinzelempfehlungCatalogue catalogue;
|
||||
|
||||
@BeforeEach
|
||||
void setUp(@Mock JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.catalogue = EinzelempfehlungCatalogue.create(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCorrectQuery(@Mock Map<String, Object> resultSet) {
|
||||
doAnswer(invocationOnMock -> List.of(resultSet))
|
||||
.when(jdbcTemplate)
|
||||
.queryForList(anyString(), anyInt());
|
||||
|
||||
this.catalogue.getById(1);
|
||||
|
||||
var captor = ArgumentCaptor.forClass(String.class);
|
||||
verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt());
|
||||
|
||||
assertThat(captor.getValue())
|
||||
.isEqualTo("SELECT * FROM dk_dnpm_uf_einzelempfehlung JOIN prozedur ON (prozedur.id = dk_dnpm_uf_einzelempfehlung.id) WHERE geloescht = 0 AND prozedur.id = ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCorrectSubformQuery(@Mock Map<String, Object> resultSet) {
|
||||
doAnswer(invocationOnMock -> List.of(resultSet))
|
||||
.when(jdbcTemplate)
|
||||
.queryForList(anyString(), anyInt());
|
||||
|
||||
this.catalogue.getAllByParentId(1);
|
||||
|
||||
var captor = ArgumentCaptor.forClass(String.class);
|
||||
verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt());
|
||||
|
||||
assertThat(captor.getValue())
|
||||
.isEqualTo("SELECT * FROM dk_dnpm_uf_einzelempfehlung JOIN prozedur ON (prozedur.id = dk_dnpm_uf_einzelempfehlung.id) WHERE geloescht = 0 AND hauptprozedur_id = ?");
|
||||
}
|
||||
|
||||
}
|
@ -55,7 +55,7 @@ class KpaEcogDataMapperTest {
|
||||
return testData.get(columnName);
|
||||
}).when(resultSet).getDate(anyString());
|
||||
|
||||
when(resultSet.getProcedureId()).thenReturn(1);
|
||||
when(resultSet.getId()).thenReturn(1);
|
||||
|
||||
doAnswer(invocationOnMock -> List.of(resultSet))
|
||||
.when(catalogue)
|
||||
|
@ -69,8 +69,8 @@ class KpaProzedurDataMapperTest {
|
||||
return testData.get(columnName);
|
||||
}).when(resultSet).getDate(anyString());
|
||||
|
||||
when(resultSet.getDiseaseId()).thenReturn(1);
|
||||
when(resultSet.getProcedureId()).thenReturn(1);
|
||||
when(resultSet.getInteger(anyString())).thenReturn(1);
|
||||
when(resultSet.getId()).thenReturn(1);
|
||||
|
||||
doAnswer(invocationOnMock -> List.of(resultSet))
|
||||
.when(catalogue)
|
||||
|
@ -68,8 +68,8 @@ class KpaTherapielinieDataMapperTest {
|
||||
return testData.get(columnName);
|
||||
}).when(resultSet).getDate(anyString());
|
||||
|
||||
when(resultSet.getDiseaseId()).thenReturn(1);
|
||||
when(resultSet.getProcedureId()).thenReturn(1);
|
||||
when(resultSet.getInteger(anyString())).thenReturn(1);
|
||||
when(resultSet.getId()).thenReturn(1);
|
||||
|
||||
doAnswer(invocationOnMock -> List.of(resultSet))
|
||||
.when(catalogue)
|
||||
|
@ -4,6 +4,7 @@ import dev.pcvolkmer.mv64e.mtb.MtbCarePlan;
|
||||
import dev.pcvolkmer.mv64e.mtb.Reference;
|
||||
import dev.pcvolkmer.onco.datamapper.PropertyCatalogue;
|
||||
import dev.pcvolkmer.onco.datamapper.ResultSet;
|
||||
import dev.pcvolkmer.onco.datamapper.datacatalogues.EinzelempfehlungCatalogue;
|
||||
import dev.pcvolkmer.onco.datamapper.datacatalogues.TherapieplanCatalogue;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -22,6 +23,7 @@ import static org.mockito.Mockito.doAnswer;
|
||||
class TherapieplanDataMapperTest {
|
||||
|
||||
TherapieplanCatalogue therapieplanCatalogue;
|
||||
EinzelempfehlungCatalogue einzelempfehlungCatalogue;
|
||||
PropertyCatalogue propertyCatalogue;
|
||||
|
||||
TherapieplanDataMapper dataMapper;
|
||||
@ -29,11 +31,13 @@ class TherapieplanDataMapperTest {
|
||||
@BeforeEach
|
||||
void setUp(
|
||||
@Mock TherapieplanCatalogue therapieplanCatalogue,
|
||||
@Mock EinzelempfehlungCatalogue einzelempfehlungCatalogue,
|
||||
@Mock PropertyCatalogue propertyCatalogue
|
||||
) {
|
||||
this.therapieplanCatalogue = therapieplanCatalogue;
|
||||
this.einzelempfehlungCatalogue = einzelempfehlungCatalogue;
|
||||
this.propertyCatalogue = propertyCatalogue;
|
||||
this.dataMapper = new TherapieplanDataMapper(therapieplanCatalogue, propertyCatalogue);
|
||||
this.dataMapper = new TherapieplanDataMapper(therapieplanCatalogue, einzelempfehlungCatalogue, propertyCatalogue);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Reference in New Issue
Block a user