Added excel export
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
if (parserOpt.isEmpty()) {
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"zip": {
|
|
||||||
"description": "The zip from the customer",
|
|
||||||
"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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
""";
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
316
question_answer.json
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user