Added excel export

This commit is contained in:
verboomp
2026-02-23 10:43:06 +01:00
parent 38be68397a
commit 888136e76b
22 changed files with 2809 additions and 81 deletions

View File

@@ -38,6 +38,18 @@
<dependencies> <dependencies>
<!-- EXCEL report -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
<!-- PDF report -->
<dependency> <dependency>
<groupId>org.apache.pdfbox</groupId> <groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId> <artifactId>pdfbox</artifactId>
@@ -356,6 +368,14 @@
<artifactId>org.jacoco.core</artifactId> <artifactId>org.jacoco.core</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Jakarta JSON Processing implementation for unit tests -->
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>parsson</artifactId>
<version>1.1.5</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId> <artifactId>slf4j-simple</artifactId>

View File

@@ -100,10 +100,10 @@ public class QuestionnaireCustomerService extends AbstractService {
return QuestionnaireCustomerValue.builder(customer); return QuestionnaireCustomerValue.builder(customer);
} }
public byte[] getExport(Long id, Long questionnaireId) { public Optional<byte[]> getExport(Long id, Long questionnaireId) {
QuestionnaireCustomer customer = entityManager.find(QuestionnaireCustomer.class, id); QuestionnaireCustomer customer = entityManager.find(QuestionnaireCustomer.class, id);
if (customer == null) { if (customer == null) {
return new byte[0]; return Optional.empty();
} }
List<Questionnaire> questionnaires = customer.getQuestionnaires().stream().sorted((x, y) -> x.getQuestionnaireDate().compareTo(y.getQuestionnaireDate())).toList(); List<Questionnaire> questionnaires = customer.getQuestionnaires().stream().sorted((x, y) -> x.getQuestionnaireDate().compareTo(y.getQuestionnaireDate())).toList();

View File

@@ -1,9 +1,16 @@
package marketing.heyday.hartmann.fotodocumentation.core.service; package marketing.heyday.hartmann.fotodocumentation.core.service;
import static marketing.heyday.hartmann.fotodocumentation.core.model.QuestionnaireCustomer.*;
import java.util.Optional;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import jakarta.ejb.LocalBean; import jakarta.ejb.LocalBean;
import jakarta.ejb.Stateless; import jakarta.ejb.Stateless;
import marketing.heyday.hartmann.fotodocumentation.rest.vo.QuestionnairePublishValue; import marketing.heyday.hartmann.fotodocumentation.core.model.Questionnaire;
import marketing.heyday.hartmann.fotodocumentation.core.model.QuestionnaireCustomer;
import marketing.heyday.hartmann.fotodocumentation.core.query.Param;
import marketing.heyday.hartmann.fotodocumentation.core.utils.QuestionnaireUploadJsonParser;
/** /**
* *
@@ -19,8 +26,32 @@ import marketing.heyday.hartmann.fotodocumentation.rest.vo.QuestionnairePublishV
@PermitAll @PermitAll
public class QuestionnairePublishService extends AbstractService { public class QuestionnairePublishService extends AbstractService {
public boolean publish(QuestionnairePublishValue value) { public boolean publish(String body) {
// FIXME: implement me var parserOpt = QuestionnaireUploadJsonParser.builder(body);
return false; if (parserOpt.isEmpty()) {
return false;
}
var parser = parserOpt.get();
Optional<QuestionnaireCustomer> customerOpt = queryService.callNamedQuerySingleResult(FIND_BY_NUMBER, new Param(PARAM_NUMBER, parser.customerNumber()));
var customer = customerOpt.orElseGet(() -> new QuestionnaireCustomer.Builder().customerNumber(parser.customerNumber()).name(parser.pharmacyName())
.city(parser.city()).zip(parser.zip())
.build());
customer = entityManager.merge(customer);
var questionnaire = new Questionnaire.Builder().customer(customer).username(parser.username())
.category("")// FIXME: remove category
.comment(parser.comment())
.customer(customer)
.questionnaireDate(parser.date())
.questions(parser.questionnair())
.username(parser.username())
.build();
customer.getQuestionnaires().add(questionnaire);
entityManager.persist(questionnaire);
entityManager.flush();
return true;
} }
} }

View File

@@ -1,12 +1,22 @@
package marketing.heyday.hartmann.fotodocumentation.core.utils; package marketing.heyday.hartmann.fotodocumentation.core.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import marketing.heyday.hartmann.fotodocumentation.core.model.Questionnaire; import marketing.heyday.hartmann.fotodocumentation.core.model.Questionnaire;
import marketing.heyday.hartmann.fotodocumentation.core.model.QuestionnaireCustomer; import marketing.heyday.hartmann.fotodocumentation.core.model.QuestionnaireCustomer;
import marketing.heyday.hartmann.fotodocumentation.core.utils.QuestionnaireJsonParser.QuestionJsonObj;
/** /**
* *
@@ -21,9 +31,164 @@ import marketing.heyday.hartmann.fotodocumentation.core.model.QuestionnaireCusto
public class ExcelUtils { public class ExcelUtils {
private static final Log LOG = LogFactory.getLog(ExcelUtils.class); private static final Log LOG = LogFactory.getLog(ExcelUtils.class);
public byte[] create(QuestionnaireCustomer customer, List<Questionnaire> questionnaires) { public Optional<byte[]> create(QuestionnaireCustomer customer, List<Questionnaire> questionnaires) {
// FIXME: implement excel export LOG.debug("Create excel file for customer " + customer);
return new byte[0]; // TODO: implement excel export
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); XSSFWorkbook workbook = new XSSFWorkbook()) {
for (var questionnaire : questionnaires) {
//TODO: set sheet name
writeSheet(workbook, customer, questionnaire);
}
workbook.write(bos);
return Optional.of(bos.toByteArray());
} catch (IOException e) {
LOG.debug("Failed to export countries", e);
return Optional.empty();
}
} }
private void writeSheet(XSSFWorkbook workbook, QuestionnaireCustomer customer, Questionnaire questionnaire) throws IOException {
XSSFSheet sheet = workbook.createSheet();
int rowIndex = writeCustomerData(workbook, sheet, customer, questionnaire);
writeQuestionData(sheet, questionnaire, rowIndex);
for (int i = 0; i < 10; i++) {
sheet.autoSizeColumn(i);
}
}
private int writeCustomerData(XSSFWorkbook workbook, XSSFSheet sheet, QuestionnaireCustomer customer, Questionnaire questionnaire) {
int rowIndex = 0;
XSSFRow row = sheet.createRow(rowIndex++);
row.createCell(0).setCellValue(customer.getName());
row = sheet.createRow(rowIndex++);
row.createCell(0).setCellValue(customer.getCustomerNumber());
row = sheet.createRow(rowIndex++);
row.createCell(0).setCellValue(customer.getCity());
row = sheet.createRow(rowIndex++);
row.createCell(0).setCellValue(customer.getZip());
row = sheet.createRow(rowIndex++);
CreationHelper createHelper = workbook.getCreationHelper();
short format = createHelper.createDataFormat().getFormat("dd.MM.yyyy h:mm");
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setDataFormat(format);
var dateCell = row.createCell(0);
dateCell.setCellStyle(cellStyle);
dateCell.setCellValue(questionnaire.getQuestionnaireDate());
rowIndex++;
rowIndex++;
return rowIndex;
}
private void writeQuestionData(XSSFSheet sheet, Questionnaire questionnaire, int rowIndex) throws IOException {
QuestionnaireJsonParser parser = new QuestionnaireJsonParser();
var success = parser.parse(questionnaire.getQuestions());
if (!success) {
throw new IOException("Failed to parse json");
}
var questions = parser.getQuestions();
for (var question : questions) {
XSSFRow row = sheet.createRow(rowIndex++);
row.createCell(0).setCellValue(question.title());
switch (question.type()) {
case "singleChoice":
writeSingle(row, question);
break;
case "multiplChoice":
int amount1 = writeMultiple(sheet, row, question);
rowIndex = rowIndex + amount1;
break;
case "number":
writeNumber(row, question);
break;
case "matrix":
int amount2 = writeMatrix(sheet, row, question);
rowIndex = rowIndex + amount2;
break;
case "freeText":
writeFreeText(row, question);
break;
default:
break;
}
rowIndex++;
}
}
private void writeSingle(XSSFRow row, QuestionJsonObj question) {
row.createCell(1).setCellValue(question.getSingleAnswer());
}
private int writeMultiple(XSSFSheet sheet, XSSFRow row, QuestionJsonObj question) {
var answers = question.getMultiAnswer();
int count = 0;
for (var answer : answers) {
if (count == 0) {
row.createCell(1).setCellValue(answer);
} else {
sheet.createRow(row.getRowNum() + count).createCell(1).setCellValue(answer);
}
count++;
}
return count - 1;
}
private void writeNumber(XSSFRow row, QuestionJsonObj question) {
var cell = row.createCell(1);
cell.setCellType(CellType.NUMERIC);
cell.setCellValue(question.getNumberAnswer());
}
private void writeFreeText(XSSFRow row, QuestionJsonObj question) {
row.createCell(1).setCellValue(question.getFreeText());
}
private int writeMatrix(XSSFSheet sheet, XSSFRow row, QuestionJsonObj question) {
var questions = question.getMatrixAnswer();
int count = 2;
var answerOpts = questions.getFirst().answers();
answerOpts.sort((i, j) -> i.answer().compareToIgnoreCase(j.answer()));
var headerRow = sheet.createRow(row.getRowNum() + count);
// create header
for (int i = 0; i < answerOpts.size(); i++) {
var answer = answerOpts.get(i);
headerRow.createCell(i + 1).setCellValue(answer.answer());
}
count++;
for (var subQuestion : questions) {
var questionRow = sheet.createRow(row.getRowNum() + count);
questionRow.createCell(0).setCellValue(subQuestion.title());
for (var answer : subQuestion.answers()) {
if (!answer.selected()) {
continue;
}
int index = 0;
for (int i = 0; i < answerOpts.size(); i++) {
if (answer.answer().equalsIgnoreCase(answerOpts.get(i).answer())) {
index = i;
}
}
var cell = questionRow.createCell(1 + index);
cell.setCellType(CellType.BOOLEAN);
cell.setCellValue(answer.selected());
}
count++;
}
return count;
}
} }

View File

@@ -0,0 +1,57 @@
package marketing.heyday.hartmann.fotodocumentation.core.utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.ValidationMessage;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 20 Feb 2026
*/
public class JsonSchemaValidator {
private static final Log LOG = LogFactory.getLog(JsonSchemaValidator.class);
public ValidationReply validate(String schemaPath, String jsonData) {
try {
JsonSchema schema = getJsonSchema(schemaPath);
JsonNode node = getJsonNode(jsonData);
Set<ValidationMessage> errors = schema.validate(node);
if (!errors.isEmpty()) {
LOG.error("Failed to validate json to schema " + schemaPath);
errors.forEach(LOG::error);
}
return new ValidationReply(errors.isEmpty(), errors);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
return new ValidationReply(false, new HashSet<>());
}
}
protected JsonSchema getJsonSchema(String name) throws IOException {
JsonSchemaFactory factory = new JsonSchemaFactory();
try (InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(name);) {
return factory.getSchema(input);
}
}
protected JsonNode getJsonNode(String content) throws IOException {
return new ObjectMapper().readTree(content);
}
}

View File

@@ -0,0 +1,170 @@
package marketing.heyday.hartmann.fotodocumentation.core.utils;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import jakarta.json.*;
import jakarta.json.stream.JsonParsingException;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 20 Feb 2026
*/
public class QuestionnaireJsonParser {
private static final Log LOG = LogFactory.getLog(QuestionnaireJsonParser.class);
private final List<QuestionJsonObj> questions = new ArrayList<>();
public List<QuestionJsonObj> getQuestions() {
return questions;
}
public boolean parse(String body) {
questions.clear();
if (body == null) {
return false;
}
try (var stringReader = new StringReader(body); JsonReader reader = Json.createReader(stringReader);) {
JsonArray root = reader.readArray();
for (var object : root) {
var question = object.asJsonObject();
String id = question.getString("id");
String title = question.getString("title");
int order = question.getInt("order");
String type = question.getString("type");
String data = toData(question.getJsonObject("data"));
questions.add(new QuestionJsonObj(id, title, order, type, data));
}
return true;
} catch (IOException | JsonParsingException | NullPointerException ioe) {
LOG.warn("Failed to parse json " + ioe.getMessage(), ioe);
return false;
}
}
private String toData(JsonObject obj) throws IOException {
try (StringWriter writer = new StringWriter(); JsonWriter jsonWriter = Json.createWriter(writer);) {
jsonWriter.writeObject(obj);
jsonWriter.close();
return writer.toString();
}
}
public record QuestionJsonObj(String id, String title, int order, String type, String data) {
public List<MatrixQuestion> getMatrixAnswer() {
return getValue("questions", () -> null, (questions) -> {
var retVal = new ArrayList<MatrixQuestion>();
for (var question : questions) {
var questionObj = question.asJsonObject();
var id = questionObj.getString("id");
var title = questionObj.getString("title");
var order = questionObj.getInt("order");
var answerList = new ArrayList<MatrixAnswer>();
var answers = questionObj.getJsonArray("answers");
for (var answer : answers) {
var answerObj = answer.asJsonObject();
var answerId = answerObj.getString("id");
var answerStr = answerObj.getString("answer");
var selected = answerObj.getBoolean("selected");
answerList.add(new MatrixAnswer(answerId, answerStr, selected));
}
retVal.add(new MatrixQuestion(id, title, order, answerList));
}
return retVal;
});
}
public Integer getNumberAnswer() {
return getValue(() -> null, (answers) -> {
for (var answer : answers) {
var answerObj = answer.asJsonObject();
if (answerObj.getBoolean("selected")) {
return answerObj.getInt("answer");
}
}
return null;
});
}
public String getSingleAnswer() {
return getValue(() -> "", (answers) -> {
for (var answer : answers) {
var answerObj = answer.asJsonObject();
if (answerObj.getBoolean("selected")) {
return answerObj.getString("answer");
}
}
return "";
});
}
public List<String> getMultiAnswer() {
return getValue(() -> List.of(), (answers) -> {
List<String> retVal = new ArrayList<>();
for (var answer : answers) {
var answerObj = answer.asJsonObject();
if (answerObj.getBoolean("selected")) {
retVal.add(answerObj.getString("answer"));
}
}
return retVal;
});
}
public String getFreeText() {
return getValue(() -> "", (answers) -> answers.getFirst().asJsonObject().getString("answer"));
}
private <T> T getValue(Supplier<T> emptyValue, Function<JsonArray, T> function) {
return getValue("answers", emptyValue, function);
}
private <T> T getValue(String element, Supplier<T> emptyValue, Function<JsonArray, T> function) {
try (var stringReader = new StringReader(data); JsonReader reader = Json.createReader(stringReader);) {
JsonObject root = reader.readObject();
JsonArray answers = root.getJsonArray(element);
if (answers.isEmpty()) {
return emptyValue.get();
}
return function.apply(answers);
} catch (JsonParsingException | NullPointerException ioe) {
LOG.warn("Failed to parse json " + ioe.getMessage(), ioe);
return emptyValue.get();
}
}
}
public record MatrixQuestion(String id, String title, int order, List<MatrixAnswer> answers) {
}
public record MatrixAnswer(String id, String answer, boolean selected) {
}
}

View File

@@ -0,0 +1,74 @@
package marketing.heyday.hartmann.fotodocumentation.core.utils;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import jakarta.json.*;
import jakarta.json.stream.JsonParsingException;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 20 Feb 2026
*/
public record QuestionnaireUploadJsonParser(
String username,
String pharmacyName,
String customerNumber,
Date date,
String comment,
String city,
String zip,
String questionnair) {
private static final Log LOG = LogFactory.getLog(QuestionnaireUploadJsonParser.class);
public static Optional<QuestionnaireUploadJsonParser> builder(String body) {
if (body == null) {
return Optional.empty();
}
try (var stringReader = new StringReader(body); JsonReader reader = Json.createReader(stringReader);) {
JsonObject root = reader.readObject();
String username = root.getString("username");
String pharmacyName = root.getString("pharmacyName");
String customerNumber = root.getString("customerNumber");
String dateStr = root.getString("date");
var dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date date = dateFormat.parse(dateStr);
String comment = root.getString("comment");
String city = root.getString("city");
String zip = root.getString("zip");
JsonArray questionnaireArr = root.getJsonArray("questionnaire");
String questionnaire = "";
try (StringWriter writer = new StringWriter(); JsonWriter jsonWriter = Json.createWriter(writer);) {
jsonWriter.writeArray(questionnaireArr);
jsonWriter.close();
questionnaire = writer.toString();
}
return Optional.of(new QuestionnaireUploadJsonParser(username, pharmacyName, customerNumber, date, comment, city, zip, questionnaire));
} catch (IOException | ParseException | JsonParsingException | NullPointerException ioe) {
LOG.warn("Failed to parse json " + ioe.getMessage(), ioe );
return Optional.empty();
}
}
}

View File

@@ -1,4 +1,4 @@
package marketing.heyday.hartmann.fotodocumentation.rest.jackson; package marketing.heyday.hartmann.fotodocumentation.core.utils;
import java.util.Set; import java.util.Set;

View File

@@ -3,6 +3,7 @@ package marketing.heyday.hartmann.fotodocumentation.rest;
import static marketing.heyday.hartmann.fotodocumentation.rest.jackson.ApplicationConfigApi.JSON_OUT; import static marketing.heyday.hartmann.fotodocumentation.rest.jackson.ApplicationConfigApi.JSON_OUT;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Optional;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@@ -73,15 +74,15 @@ public class QuestionnaireCustomerResource {
@ApiResponse(responseCode = "200", description = "Successfully retrieved export") @ApiResponse(responseCode = "200", description = "Successfully retrieved export")
public Response doExport(@PathParam("id") Long id, @QueryParam("questionnaire") Long questionnaireId) { public Response doExport(@PathParam("id") Long id, @QueryParam("questionnaire") Long questionnaireId) {
LOG.debug("Create export for customer " + id + " with optional param " + questionnaireId); LOG.debug("Create export for customer " + id + " with optional param " + questionnaireId);
byte[] pdf = questionnaireCustomerService.getExport(id, questionnaireId); Optional<byte[]> pdfOpt = questionnaireCustomerService.getExport(id, questionnaireId);
if (pdf.length == 0) { if (pdfOpt.isEmpty()) {
return Response.status(Status.NOT_FOUND).build(); return Response.status(Status.NOT_FOUND).build();
} }
StreamingOutput streamingOutput = (OutputStream output) -> { StreamingOutput streamingOutput = (OutputStream output) -> {
LOG.debug("Start writing content to OutputStream available bytes"); LOG.debug("Start writing content to OutputStream available bytes");
output.write(pdf); output.write(pdfOpt.get());
}; };
return Response.status(Status.OK).entity(streamingOutput).build(); return Response.status(Status.OK).entity(streamingOutput).build();
} }

View File

@@ -1,11 +1,19 @@
package marketing.heyday.hartmann.fotodocumentation.rest; package marketing.heyday.hartmann.fotodocumentation.rest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import org.jboss.resteasy.annotations.GZIP; import org.jboss.resteasy.annotations.GZIP;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ejb.EJB; import jakarta.ejb.EJB;
import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST; import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
@@ -13,8 +21,7 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.Response.Status;
import marketing.heyday.hartmann.fotodocumentation.core.service.QuestionnairePublishService; import marketing.heyday.hartmann.fotodocumentation.core.service.QuestionnairePublishService;
import marketing.heyday.hartmann.fotodocumentation.rest.jackson.JsonSchemaValidate; import marketing.heyday.hartmann.fotodocumentation.core.utils.JsonSchemaValidator;
import marketing.heyday.hartmann.fotodocumentation.rest.vo.QuestionnairePublishValue;
/** /**
* *
@@ -33,14 +40,28 @@ public class QuestionnairePublishResource {
@EJB @EJB
private QuestionnairePublishService questionnairePublishService; private QuestionnairePublishService questionnairePublishService;
@Inject
private JsonSchemaValidator jsonSchemaValidator;
@GZIP @GZIP
@POST @POST
@Path("") @Path("")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Add questionnaire to database") @Operation(summary = "Add questionnaire to database")
@ApiResponse(responseCode = "200", description = "Add successfull") @ApiResponse(responseCode = "200", description = "Add successfull")
public Response doAddQuestionnaire(@JsonSchemaValidate("schema/questionnaire_publish.json") QuestionnairePublishValue value) { public Response doAddQuestionnaire(InputStream inputStream) {
boolean success = questionnairePublishService.publish(value); try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
return success ? Response.ok().build() : Response.status(Status.BAD_REQUEST).build(); String body = reader.lines().collect(Collectors.joining("\n"));
var reply = jsonSchemaValidator.validate("schema/questionnaire_publish.json", body);
if (!reply.success()) {
return Response.status(Status.BAD_REQUEST).entity(reply.errors()).build();
}
questionnairePublishService.publish(body);
return Response.ok().build();
} catch (IOException e) {
return Response.status(Status.BAD_REQUEST).build();
}
} }
} }

View File

@@ -5,19 +5,10 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.ValidationMessage;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.WebApplicationException;
@@ -27,6 +18,8 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import marketing.heyday.hartmann.fotodocumentation.core.utils.JsonSchemaValidator;
import marketing.heyday.hartmann.fotodocumentation.core.utils.ValidationReply;
/** /**
* *
@@ -44,7 +37,6 @@ import jakarta.ws.rs.ext.Provider;
MediaType.APPLICATION_JSON, "application/json; charset=utf-8", MediaType.APPLICATION_JSON, "application/json; charset=utf-8",
}) })
public class ValidatedMessageBodyReader implements MessageBodyReader<SchemaValidated> { public class ValidatedMessageBodyReader implements MessageBodyReader<SchemaValidated> {
private static final Log LOG = LogFactory.getLog(ValidatedMessageBodyReader.class);
/* (non-Javadoc) /* (non-Javadoc)
* @see jakarta.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType) * @see jakarta.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType)
@@ -85,30 +77,7 @@ public class ValidatedMessageBodyReader implements MessageBodyReader<SchemaValid
*/ */
private ValidationReply validate(JsonSchemaValidate jsonSchema, String jsonData) { private ValidationReply validate(JsonSchemaValidate jsonSchema, String jsonData) {
String schemaPath = jsonSchema.value(); String schemaPath = jsonSchema.value();
try { return new JsonSchemaValidator().validate(schemaPath, jsonData);
JsonSchema schema = getJsonSchema(schemaPath);
JsonNode node = getJsonNode(jsonData);
Set<ValidationMessage> errors = schema.validate(node);
if (!errors.isEmpty()) {
LOG.error("Failed to validate json to schema " + schemaPath);
errors.forEach(LOG::error);
}
return new ValidationReply(errors.isEmpty(), errors);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
return new ValidationReply(false, new HashSet<>());
}
}
protected JsonSchema getJsonSchema(String name) throws IOException {
JsonSchemaFactory factory = new JsonSchemaFactory();
try (InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(name);) {
return factory.getSchema(input);
}
}
protected JsonNode getJsonNode(String content) throws IOException {
return new ObjectMapper().readTree(content);
} }
private String read(InputStream input) throws IOException { private String read(InputStream input) throws IOException {

View File

@@ -1,40 +1,44 @@
{ {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"title": "Add Customer Picture", "title": "Publish Questionnaire",
"description": "Add a Customer Picture to the system", "description": "Publish a completed questionnaire to the system",
"type": "object", "type": "object",
"properties": { "properties": {
"username": { "username": {
"description": "The username from the user who uploads the picture", "description": "The username from the user who submits the questionnaire",
"type": "string" "type": "string"
}, },
"pharmacyName": { "pharmacyName": {
"description": "The Name from the pharmacy customer ", "description": "The name of the pharmacy customer",
"type": "string" "type": "string"
}, },
"customerNumber": { "customerNumber": {
"description": "The unique number from the pharmacy customer ", "description": "The unique number of the pharmacy customer",
"type": "string" "type": "string"
}, },
"date": { "date": {
"description": "The date when the picture is taken ", "description": "The date when the questionnaire was filled in (ISO 8601)",
"type": "string" "type": "string"
}, },
"comment": { "comment": {
"description": "A free text comment field ", "description": "A free text comment field",
"type": "string"
},
"zip": {
"description": "The zip from the customer",
"type": "string" "type": "string"
}, },
"city": { "city": {
"description": "The city from the customer", "description": "The city of the customer",
"type": "string" "type": "string"
}, },
"base64String": { "zip": {
"description": "The Picture content as base64 ", "description": "The zip code of the customer",
"type": "string" "type": "string"
},
"questionnaire": {
"description": "The list of questions and answers",
"type": "array",
"items": {
"$ref": "#/definitions/question"
},
"minItems": 1
} }
}, },
"required": [ "required": [
@@ -42,7 +46,118 @@
"pharmacyName", "pharmacyName",
"customerNumber", "customerNumber",
"date", "date",
"comment", "questionnaire"
"base64String" ],
] "definitions": {
"answer": {
"type": "object",
"properties": {
"id": {
"description": "The unique identifier of the answer",
"type": "string"
},
"answer": {
"description": "The answer text",
"type": "string"
},
"selected": {
"description": "Whether this answer is selected",
"type": "boolean"
}
},
"required": [
"id",
"answer",
"selected"
]
},
"matrixSubQuestion": {
"type": "object",
"properties": {
"id": {
"description": "The unique identifier of the sub-question",
"type": "string"
},
"title": {
"description": "The title of the sub-question",
"type": "string"
},
"order": {
"description": "The display order of the sub-question",
"type": "integer"
},
"answers": {
"description": "The list of answers for the sub-question",
"type": "array",
"items": {
"$ref": "#/definitions/answer"
},
"minItems": 1
}
},
"required": [
"id",
"title",
"order",
"answers"
]
},
"questionData": {
"type": "object",
"properties": {
"answers": {
"description": "The list of answers (for singleChoice, multiplChoice, number, freeText)",
"type": "array",
"items": {
"$ref": "#/definitions/answer"
}
},
"questions": {
"description": "The list of sub-questions (for matrix type)",
"type": "array",
"items": {
"$ref": "#/definitions/matrixSubQuestion"
}
}
}
},
"question": {
"type": "object",
"properties": {
"id": {
"description": "The unique identifier of the question",
"type": "string"
},
"title": {
"description": "The title/text of the question",
"type": "string"
},
"order": {
"description": "The display order of the question",
"type": "integer"
},
"type": {
"description": "The type of question",
"type": "string",
"enum": [
"singleChoice",
"multiplChoice",
"number",
"freeText",
"matrix"
]
},
"data": {
"$ref": "#/definitions/questionData"
}
},
"required": [
"id",
"title",
"order",
"type",
"data"
]
}
}
} }

View File

@@ -0,0 +1,270 @@
package marketing.heyday.hartmann.fotodocumentation.core.utils;
import static org.junit.jupiter.api.Assertions.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import marketing.heyday.hartmann.fotodocumentation.core.model.Questionnaire;
import marketing.heyday.hartmann.fotodocumentation.core.model.QuestionnaireCustomer;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Feb 2026
*/
class ExcelUtilsTest {
private static final Log LOG = LogFactory.getLog(ExcelUtilsTest.class);
private ExcelUtils excelUtils;
@BeforeEach
void setUp() {
excelUtils = new ExcelUtils();
}
// --- create: basic output ---
@Test
void create_singleQuestionnaire_returnsPresent() {
QuestionnaireCustomer customer = createCustomer("Müller GmbH", "C-001", "Berlin", "10115");
Questionnaire questionnaire = createQuestionnaire(new Date(), "What is your rating?");
Optional<byte[]> result = excelUtils.create(customer, List.of(questionnaire));
assertTrue(result.isPresent());
}
@Test
void create_singleQuestionnaire_returnsValidXlsx() throws IOException {
QuestionnaireCustomer customer = createCustomer("Müller GmbH", "C-001", "Berlin", "10115");
Questionnaire questionnaire = createQuestionnaire(new Date(), "What is your rating?");
byte[] bytes = excelUtils.create(customer, List.of(questionnaire)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertNotNull(workbook);
}
}
@Test
void create_emptyQuestionnaires_returnsEmptyWorkbook() throws IOException {
QuestionnaireCustomer customer = createCustomer("Müller GmbH", "C-001", "Berlin", "10115");
byte[] bytes = excelUtils.create(customer, Collections.emptyList()).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertEquals(0, workbook.getNumberOfSheets());
}
}
// --- create: sheet count ---
@Test
void create_singleQuestionnaire_createsOneSheet() throws IOException {
QuestionnaireCustomer customer = createCustomer("Müller GmbH", "C-001", "Berlin", "10115");
Questionnaire questionnaire = createQuestionnaire(new Date(), "Q1");
byte[] bytes = excelUtils.create(customer, List.of(questionnaire)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertEquals(1, workbook.getNumberOfSheets());
}
}
@Test
void create_multipleQuestionnaires_createsSheetPerQuestionnaire() throws IOException {
QuestionnaireCustomer customer = createCustomer("Müller GmbH", "C-001", "Berlin", "10115");
Questionnaire q1 = createQuestionnaire(new Date(), "Q1");
Questionnaire q2 = createQuestionnaire(new Date(), "Q2");
Questionnaire q3 = createQuestionnaire(new Date(), "Q3");
byte[] bytes = excelUtils.create(customer, List.of(q1, q2, q3)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertEquals(3, workbook.getNumberOfSheets());
}
writeToFile(bytes, "create_multipleQuestionnaires_createsSheetPerQuestionnaire.xlsx");
}
// --- create: customer data (each field on its own row) ---
@Test
void create_writesCustomerNameInRow0() throws IOException {
QuestionnaireCustomer customer = createCustomer("Hartmann AG", "C-100", "München", "80331");
Questionnaire questionnaire = createQuestionnaire(new Date(), "Q1");
byte[] bytes = excelUtils.create(customer, List.of(questionnaire)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertEquals("Hartmann AG", workbook.getSheetAt(0).getRow(0).getCell(0).getStringCellValue());
}
}
@Test
void create_writesCustomerNumberInRow1() throws IOException {
QuestionnaireCustomer customer = createCustomer("Hartmann AG", "C-100", "München", "80331");
Questionnaire questionnaire = createQuestionnaire(new Date(), "Q1");
byte[] bytes = excelUtils.create(customer, List.of(questionnaire)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertEquals("C-100", workbook.getSheetAt(0).getRow(1).getCell(0).getStringCellValue());
}
}
@Test
void create_writesCustomerCityInRow2() throws IOException {
QuestionnaireCustomer customer = createCustomer("Hartmann AG", "C-100", "München", "80331");
Questionnaire questionnaire = createQuestionnaire(new Date(), "Q1");
byte[] bytes = excelUtils.create(customer, List.of(questionnaire)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertEquals("München", workbook.getSheetAt(0).getRow(2).getCell(0).getStringCellValue());
}
}
@Test
void create_writesCustomerZipInRow3() throws IOException {
QuestionnaireCustomer customer = createCustomer("Hartmann AG", "C-100", "München", "80331");
Questionnaire questionnaire = createQuestionnaire(new Date(), "Q1");
byte[] bytes = excelUtils.create(customer, List.of(questionnaire)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertEquals("80331", workbook.getSheetAt(0).getRow(3).getCell(0).getStringCellValue());
}
}
// --- create: questionnaire date in row 4 ---
@Test
void create_writesQuestionnaireDateInRow4() throws IOException {
QuestionnaireCustomer customer = createCustomer("Test", "C-001", "Berlin", "10115");
Date date = new Date();
Questionnaire questionnaire = createQuestionnaire(date, "Q1");
byte[] bytes = excelUtils.create(customer, List.of(questionnaire)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
XSSFRow row = workbook.getSheetAt(0).getRow(4);
assertEquals(date, row.getCell(0).getDateCellValue());
}
}
// --- create: question data in row 7 (after 5 customer rows + 2 blank rows) ---
@Test
void create_writesFirstQuestionTitleInRow7() throws IOException {
QuestionnaireCustomer customer = createCustomer("Test", "C-001", "Berlin", "10115");
Questionnaire questionnaire = createQuestionnaire(new Date(), null);
byte[] bytes = excelUtils.create(customer, List.of(questionnaire)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
XSSFSheet sheet = workbook.getSheetAt(0);
// Row 7 should contain the title of the first question from testJson1
XSSFRow row = sheet.getRow(7);
assertNotNull(row);
assertNotNull(row.getCell(0));
}
}
// --- create: null handling ---
@Test
void create_nullCustomerFields_writesNullsWithoutError() {
QuestionnaireCustomer customer = createCustomer(null, null, null, null);
Questionnaire questionnaire = createQuestionnaire(null, null);
Optional<byte[]> result = excelUtils.create(customer, List.of(questionnaire));
assertTrue(result.isPresent());
}
// --- create: each sheet gets same customer data ---
@Test
void create_multipleSheets_eachSheetHasCustomerData() throws IOException {
QuestionnaireCustomer customer = createCustomer("Hartmann AG", "C-100", "München", "80331");
Questionnaire q1 = createQuestionnaire(new Date(), "Q1");
Questionnaire q2 = createQuestionnaire(new Date(), "Q2");
byte[] bytes = excelUtils.create(customer, List.of(q1, q2)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
XSSFSheet sheet = workbook.getSheetAt(i);
assertEquals("Hartmann AG", sheet.getRow(0).getCell(0).getStringCellValue());
assertEquals("C-100", sheet.getRow(1).getCell(0).getStringCellValue());
}
}
}
@Test
void create_multipleSheets_eachSheetHasQuestionData() throws IOException {
QuestionnaireCustomer customer = createCustomer("Test", "C-001", "Berlin", "10115");
Questionnaire q1 = createQuestionnaire(new Date(), null);
Questionnaire q2 = createQuestionnaire(new Date(), null);
byte[] bytes = excelUtils.create(customer, List.of(q1, q2)).orElseThrow();
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
XSSFRow row = workbook.getSheetAt(i).getRow(7);
assertNotNull(row);
}
}
}
// --- helpers ---
private QuestionnaireCustomer createCustomer(String name, String number, String city, String zip) {
return new QuestionnaireCustomer.Builder()
.name(name)
.customerNumber(number)
.city(city)
.zip(zip)
.build();
}
private Questionnaire createQuestionnaire(Date date, String questions) {
return new Questionnaire.Builder()
.questionnaireDate(date)
.questions(QuestionnaireJsonParserTest.testJson1)
.build();
}
public void writeToFile(final byte[] content, final String fileName) {
File file = new File("target/test/output/");
file.mkdirs();
try (FileOutputStream out = new FileOutputStream(new File(file, fileName))) {
IOUtils.write(content, out);
} catch (Exception e) {
LOG.error("Error saveing pdf file", e);
}
}
}

View File

@@ -332,7 +332,7 @@ class ImageUtilTest {
@Test @Test
void getExifOrientation_invalidBytes_returns1() { void getExifOrientation_invalidBytes_returns1() {
byte[] garbage = new byte[]{0x00, 0x01, 0x02, 0x03}; byte[] garbage = new byte[] { 0x00, 0x01, 0x02, 0x03 };
int orientation = imageUtil.getExifOrientation(garbage); int orientation = imageUtil.getExifOrientation(garbage);
@@ -364,7 +364,7 @@ class ImageUtilTest {
// --- EXIF orientation with resize --- // --- EXIF orientation with resize ---
@Test @Test
void getImage_size2_rotatedImage_usesEffectiveWidthForResizeDecision() throws IOException { void getImage_size2_rotatedImage_usesEffectiveWidthForResizeDecision() {
// A tall image (600x800) with no EXIF won't be resized since width (600) < 1200 // A tall image (600x800) with no EXIF won't be resized since width (600) < 1200
String base64 = createTestImageBase64(600, 800); String base64 = createTestImageBase64(600, 800);
byte[] original = Base64.getDecoder().decode(base64); byte[] original = Base64.getDecoder().decode(base64);

View File

@@ -0,0 +1,349 @@
package marketing.heyday.hartmann.fotodocumentation.core.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 20 Feb 2026
*/
public class QuestionnaireJsonParserTest {
@Test
public void testJson1() {
var parser = new QuestionnaireJsonParser();
boolean retVal = parser.parse(testJson1);
assertTrue(retVal);
var questions = parser.getQuestions();
assertEquals(10, questions.size());
}
public static final String testJson1 = """
[
{
"id": "question1",
"title": "",
"order": 1,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Nicht-kaufender Kunde",
"selected": true
},
{
"id": "answer2",
"answer": "Bestandskunde",
"selected": false
}
]
}
},
{
"id": "question2",
"title": "Haben Sie Rezeptpatienten(GKV) für Inko?",
"order": 2,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer1",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question3",
"title": "Warum nicht?",
"order": 3,
"type": "multiplChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Lagergründe",
"selected": false
},
{
"id": "answer2",
"answer": "Wirtschaftlichkeitsgründe",
"selected": true
},
{
"id": "answer3",
"answer": "Administrativer Aufwand",
"selected": true
},
{
"id": "answer4",
"answer": "Personeller Aufwand",
"selected": false
}
]
}
},
{
"id": "question4",
"title": "Haben Sie Privatrezeptpatienten für inko?",
"order": 4,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer2",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question5",
"title": "Wie viele Patienten versorgen Sie regelmäßig? (Privat) un GKV",
"order": 5,
"type": "number",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": 47,
"selected": true
}
]
}
},
{
"id": "question6",
"title": "Mit welchem Herstellern arbeiten Sie zusammen?",
"order": 6,
"type": "multiplChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "TZMO",
"selected": true
},
{
"id": "answer3",
"answer": "Essity",
"selected": true
},
{
"id": "answer4",
"answer": "Ontex",
"selected": true
},
{
"id": "answer5",
"answer": "Param",
"selected": false
},
{
"id": "answer6",
"answer": "Andere",
"selected": false
}
]
}
},
{
"id": "question7",
"title": "Was sind Ihre Gründe für die Zusammenarbeit?",
"order": 7,
"type": "matrix",
"data":
{
"questions":
[
{
"id": "subq1",
"title": "Preis",
"order": 1,
"answers":
[
{
"id": "answer1",
"answer": "HARTMANN",
"selected": true
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq2",
"title": "Einkaufskondition",
"order": 2,
"answers":
[
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq3",
"title": "Qualität",
"order": 3,
"answers":
[
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": true
}
]
}
]
}
},
{
"id": "question8",
"title": "Beziehen Sie Produkte direkt oder über den Großhandel?",
"order": 8,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "primär direkt",
"selected": false
},
{
"id": "answer2",
"answer": "primär Großhandel",
"selected": true
},
{
"id": "answer3",
"answer": "ptimär teils. Großhandel",
"selected": true
},
{
"id": "answer4",
"answer": "unterschiedlich",
"selected": false
}
]
}
},
{
"id": "question9",
"title": "Gründe für Bezug?",
"order": 10,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Umsatzziel mit Händler",
"selected": false
},
{
"id": "answer2",
"answer": "Warenverfügbarkeit/ Liefergeschwindigkeit und Frequent",
"selected": true
},
{
"id": "answer3",
"answer": "Einkaufskondition",
"selected": true
}
]
}
},
{
"id": "question10",
"title": "Weiter/Kommentare Hinweise?",
"order": 11,
"type": "freeText",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Kommentar eintragen",
"selected": true
}
]
}
}
]
""";
}

View File

@@ -0,0 +1,270 @@
package marketing.heyday.hartmann.fotodocumentation.core.utils;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Optional;
import org.junit.jupiter.api.Test;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 20 Feb 2026
*/
class QuestionnaireUploadJsonParserTest {
// --- builder: successful parsing ---
@Test
void builder_validJson_returnsPresent() {
String json = createValidJson();
Optional<QuestionnaireUploadJsonParser> result = QuestionnaireUploadJsonParser.builder(json);
assertTrue(result.isPresent());
}
@Test
void builder_validJson_parsesUsername() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertEquals("verboomp", parser.username());
}
@Test
void builder_validJson_parsesPharmacyName() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertEquals("Müller Apotheke", parser.pharmacyName());
}
@Test
void builder_validJson_parsesCustomerNumber() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertEquals("1234", parser.customerNumber());
}
@Test
void builder_validJson_parsesDate() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertNotNull(parser.date());
}
@Test
void builder_validJson_parsesComment() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertEquals("Some long text", parser.comment());
}
@Test
void builder_validJson_parsesCity() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertEquals("Hannover", parser.city());
}
@Test
void builder_validJson_parsesZip() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertEquals("12345", parser.zip());
}
@Test
void builder_validJson_parsesQuestionnaireAsJsonString() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertNotNull(parser.questionnair());
assertTrue(parser.questionnair().contains("question1"));
assertTrue(parser.questionnair().contains("singleChoice"));
}
@Test
void builder_validJson_questionnaireContainsAnswers() {
String json = createValidJson();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertTrue(parser.questionnair().contains("answer1"));
assertTrue(parser.questionnair().contains("Ja"));
}
// --- builder: multiple questions ---
@Test
void builder_multipleQuestions_allQuestionsInOutput() {
String json = createJsonWithMultipleQuestions();
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertTrue(parser.questionnair().contains("question1"));
assertTrue(parser.questionnair().contains("question2"));
}
// --- builder: invalid input ---
@Test
void builder_invalidJson_returnsEmpty() {
String json = "not valid json";
Optional<QuestionnaireUploadJsonParser> result = QuestionnaireUploadJsonParser.builder(json);
assertTrue(result.isEmpty());
}
@Test
void builder_emptyObject_returnsEmpty() {
String json = "{}";
Optional<QuestionnaireUploadJsonParser> result = QuestionnaireUploadJsonParser.builder(json);
assertTrue(result.isEmpty());
}
@Test
void builder_missingRequiredField_returnsEmpty() {
String json = """
{
"username": "verboomp",
"pharmacyName": "Test"
}""";
Optional<QuestionnaireUploadJsonParser> result = QuestionnaireUploadJsonParser.builder(json);
assertTrue(result.isEmpty());
}
@Test
void builder_invalidDateFormat_returnsEmpty() {
String json = """
{
"username": "verboomp",
"pharmacyName": "Test",
"customerNumber": "1234",
"date": "not-a-date",
"comment": "test",
"city": "Berlin",
"zip": "10115",
"questionnaire": [{"id": "q1", "title": "Q", "order": 1, "type": "freeText", "data": {"answers": []}}]
}""";
Optional<QuestionnaireUploadJsonParser> result = QuestionnaireUploadJsonParser.builder(json);
assertTrue(result.isEmpty());
}
// --- builder: special characters ---
@Test
void builder_unicodeCharacters_parsedCorrectly() {
String json = createJsonWithValues("user1", "Löwen Apotheke", "5678", "Köln", "50667");
QuestionnaireUploadJsonParser parser = QuestionnaireUploadJsonParser.builder(json).orElseThrow();
assertEquals("Löwen Apotheke", parser.pharmacyName());
assertEquals("Köln", parser.city());
}
// --- helpers ---
private String createValidJson() {
return """
{
"username": "verboomp",
"pharmacyName": "Müller Apotheke",
"customerNumber": "1234",
"date": "2026-01-20T11:06:00+01:00",
"comment": "Some long text",
"city": "Hannover",
"zip": "12345",
"questionnaire": [
{
"id": "question1",
"title": "Kundentyp",
"order": 1,
"type": "singleChoice",
"data": {
"answers": [
{"id": "answer1", "answer": "Ja", "selected": true},
{"id": "answer2", "answer": "Nein", "selected": false}
]
}
}
]
}""";
}
private String createJsonWithMultipleQuestions() {
return """
{
"username": "verboomp",
"pharmacyName": "Test Apotheke",
"customerNumber": "9999",
"date": "2026-01-20T11:06:00+01:00",
"comment": "comment",
"city": "Berlin",
"zip": "10115",
"questionnaire": [
{
"id": "question1",
"title": "First",
"order": 1,
"type": "singleChoice",
"data": {"answers": [{"id": "a1", "answer": "Yes", "selected": true}]}
},
{
"id": "question2",
"title": "Second",
"order": 2,
"type": "freeText",
"data": {"answers": [{"id": "a1", "answer": "Some text", "selected": true}]}
}
]
}""";
}
private String createJsonWithValues(String username, String pharmacyName, String customerNumber, String city, String zip) {
return """
{
"username": "%s",
"pharmacyName": "%s",
"customerNumber": "%s",
"date": "2026-01-20T11:06:00+01:00",
"comment": "test",
"city": "%s",
"zip": "%s",
"questionnaire": [
{
"id": "q1",
"title": "Q",
"order": 1,
"type": "freeText",
"data": {"answers": [{"id": "a1", "answer": "text", "selected": true}]}
}
]
}""".formatted(username, pharmacyName, customerNumber, city, zip);
}
}

View File

@@ -35,7 +35,6 @@ public class QuestionnairePublishResourceTest extends AbstractQuestionnaireTest
@Test @Test
@Order(2) @Order(2)
@Disabled // FIXME: enable when implemented
public void doAddCustomerdoAddQuestionniare() throws IOException { public void doAddCustomerdoAddQuestionniare() throws IOException {
LOG.info("doAddCustomerdoAddQuestionniare"); LOG.info("doAddCustomerdoAddQuestionniare");
@@ -43,8 +42,8 @@ public class QuestionnairePublishResourceTest extends AbstractQuestionnaireTest
assertEquals(5, questionnaireCount()); assertEquals(5, questionnaireCount());
String path = deploymentURL + PATH; String path = deploymentURL + PATH;
Request request = Request.Post(path).addHeader("Accept", "application/json; charset=utf-8") Request request = Request.Post(path)//.addHeader("Accept", "application/json; charset=utf-8")
.bodyFile(new File(BASE_UPLOAD + "add.json"), ContentType.APPLICATION_JSON); .bodyFile(new File(BASE_UPLOAD + "questionnaire_add.json"), ContentType.APPLICATION_JSON);
HttpResponse httpResponse = executeRequest(request); HttpResponse httpResponse = executeRequest(request);
int code = httpResponse.getStatusLine().getStatusCode(); int code = httpResponse.getStatusLine().getStatusCode();
@@ -56,7 +55,6 @@ public class QuestionnairePublishResourceTest extends AbstractQuestionnaireTest
@Test @Test
@Order(3) @Order(3)
@Disabled // FIXME: enable when implemented
public void doAddCustomerWithQuestionnaire() throws IOException { public void doAddCustomerWithQuestionnaire() throws IOException {
LOG.info("doAddCustomerWithQuestionnaire"); LOG.info("doAddCustomerWithQuestionnaire");
@@ -65,7 +63,7 @@ public class QuestionnairePublishResourceTest extends AbstractQuestionnaireTest
String path = deploymentURL + PATH; String path = deploymentURL + PATH;
Request request = Request.Post(path).addHeader("Accept", "application/json; charset=utf-8") Request request = Request.Post(path).addHeader("Accept", "application/json; charset=utf-8")
.bodyFile(new File(BASE_UPLOAD + "addNewCustomer.json"), ContentType.APPLICATION_JSON); .bodyFile(new File(BASE_UPLOAD + "questionnaire_addNewCustomer.json"), ContentType.APPLICATION_JSON);
HttpResponse httpResponse = executeRequest(request); HttpResponse httpResponse = executeRequest(request);
int code = httpResponse.getStatusLine().getStatusCode(); int code = httpResponse.getStatusLine().getStatusCode();
@@ -77,13 +75,12 @@ public class QuestionnairePublishResourceTest extends AbstractQuestionnaireTest
@Test @Test
@Order(1) @Order(1)
@Disabled // FIXME: enable when implemented
public void doAddCustomerWithQuestionnaireWrongJson() throws IOException { public void doAddCustomerWithQuestionnaireWrongJson() throws IOException {
LOG.info("doAddCustomerWithQuestionnaireWrongJson"); LOG.info("doAddCustomerWithQuestionnaireWrongJson");
String path = deploymentURL + PATH; String path = deploymentURL + PATH;
Request request = Request.Post(path).addHeader("Accept", "application/json; charset=utf-8") Request request = Request.Post(path).addHeader("Accept", "application/json; charset=utf-8")
.bodyFile(new File(BASE_UPLOAD + "addWrong.json"), ContentType.APPLICATION_JSON); .bodyFile(new File(BASE_UPLOAD + "questionnaire_addWrong.json"), ContentType.APPLICATION_JSON);
HttpResponse httpResponse = executeRequest(request); HttpResponse httpResponse = executeRequest(request);
int code = httpResponse.getStatusLine().getStatusCode(); int code = httpResponse.getStatusLine().getStatusCode();

View File

@@ -0,0 +1,302 @@
{
"username": "verboomp",
"pharmacyName": "Müller Apotheke",
"customerNumber": "1234",
"date": "2026-01-20T11:06:00+01:00",
"comment": "Some long text for Müller Pharmacy",
"city": "Hannover",
"zip": "12345",
"questionnaire": [
{
"id": "question1",
"title": "",
"order": 1,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Nicht-kaufender Kunde",
"selected": true
},
{
"id": "answer2",
"answer": "Bestandskunde",
"selected": false
}
]
}
},
{
"id": "question2",
"title": "Haben Sie Rezeptpatienten(GKV) für Inko?",
"order": 2,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer1",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question3",
"title": "Warum nicht?",
"order": 3,
"type": "multiplChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Lagergründe",
"selected": false
},
{
"id": "answer2",
"answer": "Wirtschaftlichkeitsgründe",
"selected": true
},
{
"id": "answer3",
"answer": "Administrativer Aufwand",
"selected": true
},
{
"id": "answer4",
"answer": "Personeller Aufwand",
"selected": false
}
]
}
},
{
"id": "question4",
"title": "Haben Sie Privatrezeptpatienten für inko?",
"order": 4,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer2",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question5",
"title": "Wie viele Patienten versorgen Sie regelmäßig?\n(Privat) un GKV",
"order": 5,
"type": "number",
"data": {
"answers": [
{
"id": "answer1",
"answer": "47",
"selected": true
}
]
}
},
{
"id": "question6",
"title": "Mit welchem Herstellern arbeiten Sie zusammen?",
"order": 6,
"type": "multiplChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "TZMO",
"selected": true
},
{
"id": "answer3",
"answer": "Essity",
"selected": true
},
{
"id": "answer4",
"answer": "Ontex",
"selected": false
},
{
"id": "answer5",
"answer": "Param",
"selected": false
},
{
"id": "answer6",
"answer": "Andere",
"selected": false
}
]
}
},
{
"id": "question7",
"title": "Was sind Ihre Gründe für die Zusammenarbeit?",
"order": 7,
"type": "matrix",
"data": {
"questions": [
{
"id": "subq1",
"title": "Preis",
"order": 1,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq2",
"title": "Einkaufskondition",
"order": 2,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq3",
"title": "Qualität",
"order": 3,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
}
]
}
},
{
"id": "question8",
"title": "Beziehen Sie Produkte direkt oder über den Großhandel?",
"order": 8,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "primär direkt",
"selected": false
},
{
"id": "answer2",
"answer": "primär Großhandel",
"selected": true
},
{
"id": "answer3",
"answer": "ptimär teils. Großhandel",
"selected": true
},
{
"id": "answer4",
"answer": "unterschiedlich",
"selected": false
}
]
}
},
{
"id": "question9",
"title": "Gründe für Bezug?",
"order": 10,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Umsatzziel mit Händler",
"selected": false
},
{
"id": "answer2",
"answer": "Warenverfügbarkeit/ Liefergeschwindigkeit und Frequent",
"selected": true
},
{
"id": "answer3",
"answer": "Einkaufskondition",
"selected": true
}
]
}
},
{
"id": "question10",
"title": "Weiter/Kommentare Hinweise?",
"order": 11,
"type": "freeText",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Kommentar eintragen",
"selected": true
}
]
}
}
]
}

View File

@@ -0,0 +1,302 @@
{
"username": "verboomp",
"pharmacyName": "New Apotheke",
"customerNumber": "new_number",
"date": "2026-01-20T11:06:00+01:00",
"comment": "Some long text for New Pharmacy",
"city": "Hannover",
"zip": "12345",
"questionnaire": [
{
"id": "question1",
"title": "",
"order": 1,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Nicht-kaufender Kunde",
"selected": true
},
{
"id": "answer2",
"answer": "Bestandskunde",
"selected": false
}
]
}
},
{
"id": "question2",
"title": "Haben Sie Rezeptpatienten(GKV) für Inko?",
"order": 2,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer1",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question3",
"title": "Warum nicht?",
"order": 3,
"type": "multiplChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Lagergründe",
"selected": false
},
{
"id": "answer2",
"answer": "Wirtschaftlichkeitsgründe",
"selected": true
},
{
"id": "answer3",
"answer": "Administrativer Aufwand",
"selected": true
},
{
"id": "answer4",
"answer": "Personeller Aufwand",
"selected": false
}
]
}
},
{
"id": "question4",
"title": "Haben Sie Privatrezeptpatienten für inko?",
"order": 4,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer2",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question5",
"title": "Wie viele Patienten versorgen Sie regelmäßig?\n(Privat) un GKV",
"order": 5,
"type": "number",
"data": {
"answers": [
{
"id": "answer1",
"answer": "47",
"selected": true
}
]
}
},
{
"id": "question6",
"title": "Mit welchem Herstellern arbeiten Sie zusammen?",
"order": 6,
"type": "multiplChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "TZMO",
"selected": true
},
{
"id": "answer3",
"answer": "Essity",
"selected": true
},
{
"id": "answer4",
"answer": "Ontex",
"selected": false
},
{
"id": "answer5",
"answer": "Param",
"selected": false
},
{
"id": "answer6",
"answer": "Andere",
"selected": false
}
]
}
},
{
"id": "question7",
"title": "Was sind Ihre Gründe für die Zusammenarbeit?",
"order": 7,
"type": "matrix",
"data": {
"questions": [
{
"id": "subq1",
"title": "Preis",
"order": 1,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq2",
"title": "Einkaufskondition",
"order": 2,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq3",
"title": "Qualität",
"order": 3,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
}
]
}
},
{
"id": "question8",
"title": "Beziehen Sie Produkte direkt oder über den Großhandel?",
"order": 8,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "primär direkt",
"selected": false
},
{
"id": "answer2",
"answer": "primär Großhandel",
"selected": true
},
{
"id": "answer3",
"answer": "ptimär teils. Großhandel",
"selected": true
},
{
"id": "answer4",
"answer": "unterschiedlich",
"selected": false
}
]
}
},
{
"id": "question9",
"title": "Gründe für Bezug?",
"order": 10,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Umsatzziel mit Händler",
"selected": false
},
{
"id": "answer2",
"answer": "Warenverfügbarkeit/ Liefergeschwindigkeit und Frequent",
"selected": true
},
{
"id": "answer3",
"answer": "Einkaufskondition",
"selected": true
}
]
}
},
{
"id": "question10",
"title": "Weiter/Kommentare Hinweise?",
"order": 11,
"type": "freeText",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Kommentar eintragen",
"selected": true
}
]
}
}
]
}

View File

@@ -0,0 +1,298 @@
{
"username": "verboomp",
"date": "2026-01-20T11:06:00+01:00",
"comment": "Some long text for Müller Pharmacy",
"questionnaire": [
{
"id": "question1",
"title": "",
"order": 1,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Nicht-kaufender Kunde",
"selected": true
},
{
"id": "answer2",
"answer": "Bestandskunde",
"selected": false
}
]
}
},
{
"id": "question2",
"title": "Haben Sie Rezeptpatienten(GKV) für Inko?",
"order": 2,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer1",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question3",
"title": "Warum nicht?",
"order": 3,
"type": "multiplChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Lagergründe",
"selected": false
},
{
"id": "answer2",
"answer": "Wirtschaftlichkeitsgründe",
"selected": true
},
{
"id": "answer3",
"answer": "Administrativer Aufwand",
"selected": true
},
{
"id": "answer4",
"answer": "Personeller Aufwand",
"selected": false
}
]
}
},
{
"id": "question4",
"title": "Haben Sie Privatrezeptpatienten für inko?",
"order": 4,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer2",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question5",
"title": "Wie viele Patienten versorgen Sie regelmäßig?\n(Privat) un GKV",
"order": 5,
"type": "number",
"data": {
"answers": [
{
"id": "answer1",
"answer": "47",
"selected": true
}
]
}
},
{
"id": "question6",
"title": "Mit welchem Herstellern arbeiten Sie zusammen?",
"order": 6,
"type": "multiplChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "TZMO",
"selected": true
},
{
"id": "answer3",
"answer": "Essity",
"selected": true
},
{
"id": "answer4",
"answer": "Ontex",
"selected": false
},
{
"id": "answer5",
"answer": "Param",
"selected": false
},
{
"id": "answer6",
"answer": "Andere",
"selected": false
}
]
}
},
{
"id": "question7",
"title": "Was sind Ihre Gründe für die Zusammenarbeit?",
"order": 7,
"type": "matrix",
"data": {
"questions": [
{
"id": "subq1",
"title": "Preis",
"order": 1,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq2",
"title": "Einkaufskondition",
"order": 2,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq3",
"title": "Qualität",
"order": 3,
"answers": [
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
}
]
}
},
{
"id": "question8",
"title": "Beziehen Sie Produkte direkt oder über den Großhandel?",
"order": 8,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "primär direkt",
"selected": false
},
{
"id": "answer2",
"answer": "primär Großhandel",
"selected": true
},
{
"id": "answer3",
"answer": "ptimär teils. Großhandel",
"selected": true
},
{
"id": "answer4",
"answer": "unterschiedlich",
"selected": false
}
]
}
},
{
"id": "question9",
"title": "Gründe für Bezug?",
"order": 10,
"type": "singleChoice",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Umsatzziel mit Händler",
"selected": false
},
{
"id": "answer2",
"answer": "Warenverfügbarkeit/ Liefergeschwindigkeit und Frequent",
"selected": true
},
{
"id": "answer3",
"answer": "Einkaufskondition",
"selected": true
}
]
}
},
{
"id": "question10",
"title": "Weiter/Kommentare Hinweise?",
"order": 11,
"type": "freeText",
"data": {
"answers": [
{
"id": "answer1",
"answer": "Kommentar eintragen",
"selected": true
}
]
}
}
]
}

View File

@@ -290,12 +290,13 @@
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId> <artifactId>poi</artifactId>
<version>5.1.0</version> <version>5.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId> <artifactId>poi-ooxml</artifactId>
<version>5.1.0</version> <version>5.5.1</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>dom4j</groupId> <groupId>dom4j</groupId>

316
question_answer.json Normal file
View File

@@ -0,0 +1,316 @@
[
{
"id": "question1",
"title": "",
"order": 1,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Nicht-kaufender Kunde",
"selected": true
},
{
"id": "answer2",
"answer": "Bestandskunde",
"selected": false
}
]
}
},
{
"id": "question2",
"title": "Haben Sie Rezeptpatienten(GKV) für Inko?",
"order": 2,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer1",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question3",
"title": "Warum nicht?",
"order": 3,
"type": "multiplChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Lagergründe",
"selected": false
},
{
"id": "answer2",
"answer": "Wirtschaftlichkeitsgründe",
"selected": true
},
{
"id": "answer3",
"answer": "Administrativer Aufwand",
"selected": true
},
{
"id": "answer4",
"answer": "Personeller Aufwand",
"selected": false
}
]
}
},
{
"id": "question4",
"title": "Haben Sie Privatrezeptpatienten für inko?",
"order": 4,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Ja",
"selected": false
},
{
"id": "answer2",
"answer": "Nein",
"selected": true
}
]
}
},
{
"id": "question5",
"title": "Wie viele Patienten versorgen Sie regelmäßig?\n(Privat) un GKV",
"order": 5,
"type": "number",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "47",
"selected": true
}
]
}
},
{
"id": "question6",
"title": "Mit welchem Herstellern arbeiten Sie zusammen?",
"order": 6,
"type": "multiplChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "TZMO",
"selected": true
},
{
"id": "answer3",
"answer": "Essity",
"selected": true
},
{
"id": "answer4",
"answer": "Ontex",
"selected": false
},
{
"id": "answer5",
"answer": "Param",
"selected": false
},
{
"id": "answer6",
"answer": "Andere",
"selected": false
}
]
}
},
{
"id": "question7",
"title": "Was sind Ihre Gründe für die Zusammenarbeit?",
"order": 7,
"type": "matrix",
"data":
{
"questions":
[
{
"id": "subq1",
"title": "Preis",
"order": 1,
"answers":
[
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq2",
"title": "Einkaufskondition",
"order": 2,
"answers":
[
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
},
{
"id": "subq3",
"title": "Qualität",
"order": 3,
"answers":
[
{
"id": "answer1",
"answer": "HARTMANN",
"selected": false
},
{
"id": "answer2",
"answer": "Essity",
"selected": true
},
{
"id": "answer3",
"answer": "Ontex",
"selected": false
}
]
}
]
}
},
{
"id": "question8",
"title": "Beziehen Sie Produkte direkt oder über den Großhandel?",
"order": 8,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "primär direkt",
"selected": false
},
{
"id": "answer2",
"answer": "primär Großhandel",
"selected": true
},
{
"id": "answer3",
"answer": "ptimär teils. Großhandel",
"selected": true
},
{
"id": "answer4",
"answer": "unterschiedlich",
"selected": false
}
]
}
},
{
"id": "question9",
"title": "Gründe für Bezug?",
"order": 10,
"type": "singleChoice",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Umsatzziel mit Händler",
"selected": false
},
{
"id": "answer2",
"answer": "Warenverfügbarkeit/ Liefergeschwindigkeit und Frequent",
"selected": true
},
{
"id": "answer3",
"answer": "Einkaufskondition",
"selected": true
}
]
}
},
{
"id": "question10",
"title": "Weiter/Kommentare Hinweise?",
"order": 11,
"type": "freeText",
"data":
{
"answers":
[
{
"id": "answer1",
"answer": "Kommentar eintragen",
"selected": true
}
]
}
}
]