Implemented bulk import of persons
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package de.iwomm.propify_api.controller;
|
||||
|
||||
import de.iwomm.propify_api.service.PersonImportService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/data-import")
|
||||
public class DataImportController {
|
||||
|
||||
private final PersonImportService personImportService;
|
||||
|
||||
public DataImportController(PersonImportService personImportService) {
|
||||
this.personImportService = personImportService;
|
||||
}
|
||||
|
||||
@PostMapping("/persons")
|
||||
public ResponseEntity<?> importPersons(@RequestParam("file") MultipartFile file) {
|
||||
if (file.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body("Please upload a file");
|
||||
}
|
||||
|
||||
if (!file.getOriginalFilename().endsWith(".xlsx")) {
|
||||
return ResponseEntity.badRequest().body("Only .xlsx files are supported");
|
||||
}
|
||||
|
||||
try {
|
||||
PersonImportService.ImportResult result = personImportService.importPersonsFromExcel(file);
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (IOException e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Error processing file: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Unexpected error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/main/java/de/iwomm/propify_api/dto/PersonImportDTO.java
Normal file
117
src/main/java/de/iwomm/propify_api/dto/PersonImportDTO.java
Normal file
@@ -0,0 +1,117 @@
|
||||
package de.iwomm.propify_api.dto;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
|
||||
public class PersonImportDTO {
|
||||
|
||||
@ExcelProperty(index = 0, value = "Person - Name")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty(index = 1, value = "Person - Label")
|
||||
private String labels;
|
||||
|
||||
@ExcelProperty(index = 2, value = "Person - Organisation")
|
||||
private String organization;
|
||||
|
||||
@ExcelProperty(index = 3, value = "Person - E-Mail-Adresse - Büro")
|
||||
private String emailOffice;
|
||||
|
||||
@ExcelProperty(index = 4, value = "Person - E-Mail-Adresse - Privat")
|
||||
private String emailPrivate;
|
||||
|
||||
@ExcelProperty(index = 5, value = "Person - E-Mail-Adresse - Sonstiger")
|
||||
private String emailOther;
|
||||
|
||||
@ExcelProperty(index = 6, value = "Person - Telefon - Büro")
|
||||
private String phoneOffice;
|
||||
|
||||
@ExcelProperty(index = 7, value = "Person - Telefon - Privat")
|
||||
private String phonePrivate;
|
||||
|
||||
@ExcelProperty(index = 8, value = "Person - Telefon - Mobil")
|
||||
private String phoneMobile;
|
||||
|
||||
@ExcelProperty(index = 9, value = "Person - Telefon - Sonstiger")
|
||||
private String phoneOther;
|
||||
|
||||
// Getters and Setters
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLabels() {
|
||||
return labels;
|
||||
}
|
||||
|
||||
public void setLabels(String labels) {
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
public String getOrganization() {
|
||||
return organization;
|
||||
}
|
||||
|
||||
public void setOrganization(String organization) {
|
||||
this.organization = organization;
|
||||
}
|
||||
|
||||
public String getEmailOffice() {
|
||||
return emailOffice;
|
||||
}
|
||||
|
||||
public void setEmailOffice(String emailOffice) {
|
||||
this.emailOffice = emailOffice;
|
||||
}
|
||||
|
||||
public String getEmailPrivate() {
|
||||
return emailPrivate;
|
||||
}
|
||||
|
||||
public void setEmailPrivate(String emailPrivate) {
|
||||
this.emailPrivate = emailPrivate;
|
||||
}
|
||||
|
||||
public String getEmailOther() {
|
||||
return emailOther;
|
||||
}
|
||||
|
||||
public void setEmailOther(String emailOther) {
|
||||
this.emailOther = emailOther;
|
||||
}
|
||||
|
||||
public String getPhoneOffice() {
|
||||
return phoneOffice;
|
||||
}
|
||||
|
||||
public void setPhoneOffice(String phoneOffice) {
|
||||
this.phoneOffice = phoneOffice;
|
||||
}
|
||||
|
||||
public String getPhonePrivate() {
|
||||
return phonePrivate;
|
||||
}
|
||||
|
||||
public void setPhonePrivate(String phonePrivate) {
|
||||
this.phonePrivate = phonePrivate;
|
||||
}
|
||||
|
||||
public String getPhoneMobile() {
|
||||
return phoneMobile;
|
||||
}
|
||||
|
||||
public void setPhoneMobile(String phoneMobile) {
|
||||
this.phoneMobile = phoneMobile;
|
||||
}
|
||||
|
||||
public String getPhoneOther() {
|
||||
return phoneOther;
|
||||
}
|
||||
|
||||
public void setPhoneOther(String phoneOther) {
|
||||
this.phoneOther = phoneOther;
|
||||
}
|
||||
}
|
||||
31
src/main/java/de/iwomm/propify_api/entity/Industry.java
Normal file
31
src/main/java/de/iwomm/propify_api/entity/Industry.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package de.iwomm.propify_api.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
public class Industry {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column(nullable = false)
|
||||
private UUID id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
60
src/main/java/de/iwomm/propify_api/entity/Organization.java
Normal file
60
src/main/java/de/iwomm/propify_api/entity/Organization.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package de.iwomm.propify_api.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
public class Organization {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column(nullable = false)
|
||||
private UUID id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@ManyToOne()
|
||||
@JoinColumn(name = "industry_id")
|
||||
private Industry industry;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Industry getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(Industry industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
}
|
||||
31
src/main/java/de/iwomm/propify_api/entity/PersonLabel.java
Normal file
31
src/main/java/de/iwomm/propify_api/entity/PersonLabel.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package de.iwomm.propify_api.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
public class PersonLabel {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column(nullable = false)
|
||||
private UUID id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.iwomm.propify_api.repository;
|
||||
|
||||
import de.iwomm.propify_api.entity.Industry;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface IndustryRepository extends JpaRepository<Industry, UUID> {
|
||||
Optional<Industry> findByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.iwomm.propify_api.repository;
|
||||
|
||||
import de.iwomm.propify_api.entity.Organization;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface OrganizationRepository extends JpaRepository<Organization, UUID> {
|
||||
Optional<Organization> findByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.iwomm.propify_api.repository;
|
||||
|
||||
import de.iwomm.propify_api.entity.PersonLabel;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface PersonLabelRepository extends JpaRepository<PersonLabel, UUID> {
|
||||
Optional<PersonLabel> findByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.iwomm.propify_api.repository;
|
||||
|
||||
import de.iwomm.propify_api.entity.Person;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface PersonRepository extends JpaRepository<Person, UUID> {
|
||||
Optional<Person> findByKeycloakId(String keycloakId);
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package de.iwomm.propify_api.service;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.excel.read.listener.ReadListener;
|
||||
import de.iwomm.propify_api.dto.PersonImportDTO;
|
||||
import de.iwomm.propify_api.entity.Person;
|
||||
import de.iwomm.propify_api.entity.PersonLabel;
|
||||
import de.iwomm.propify_api.repository.OrganizationRepository;
|
||||
import de.iwomm.propify_api.repository.PersonLabelRepository;
|
||||
import de.iwomm.propify_api.repository.PersonRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
public class PersonImportService {
|
||||
|
||||
private final PersonRepository personRepository;
|
||||
private final PersonLabelRepository personLabelRepository;
|
||||
private final OrganizationRepository organizationRepository;
|
||||
|
||||
public PersonImportService(PersonRepository personRepository,
|
||||
PersonLabelRepository personLabelRepository,
|
||||
OrganizationRepository organizationRepository) {
|
||||
this.personRepository = personRepository;
|
||||
this.personLabelRepository = personLabelRepository;
|
||||
this.organizationRepository = organizationRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ImportResult importPersonsFromExcel(MultipartFile file) throws IOException {
|
||||
ImportResult result = new ImportResult();
|
||||
|
||||
EasyExcel.read(file.getInputStream(), PersonImportDTO.class, new ReadListener<PersonImportDTO>() {
|
||||
private int currentRow = 1; // Start at 1 to account for header
|
||||
|
||||
@Override
|
||||
public void invoke(PersonImportDTO data, AnalysisContext context) {
|
||||
currentRow++;
|
||||
try {
|
||||
Person person = convertToPerson(data);
|
||||
personRepository.save(person);
|
||||
result.incrementSuccess();
|
||||
} catch (Exception e) {
|
||||
result.addError(currentRow, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||
// Called after all data has been analyzed
|
||||
}
|
||||
}).sheet().doRead();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Person convertToPerson(PersonImportDTO dto) {
|
||||
Person person = new Person();
|
||||
|
||||
// Parse name (format: "firstName lastName")
|
||||
if (dto.getName() != null && !dto.getName().trim().isEmpty()) {
|
||||
String[] nameParts = dto.getName().trim().split("\\s+", 2);
|
||||
person.setFirstName(nameParts[0]);
|
||||
person.setLastName(nameParts.length > 1 ? nameParts[1] : null);
|
||||
} else {
|
||||
person.setFirstName(null);
|
||||
person.setLastName(null);
|
||||
}
|
||||
|
||||
// Parse labels (comma-separated)
|
||||
if (dto.getLabels() != null && !dto.getLabels().trim().isEmpty()) {
|
||||
Set<PersonLabel> labels = new HashSet<>();
|
||||
String[] labelNames = dto.getLabels().split(",");
|
||||
for (String labelName : labelNames) {
|
||||
String trimmedName = labelName.trim();
|
||||
if (!trimmedName.isEmpty()) {
|
||||
PersonLabel label = personLabelRepository.findByName(trimmedName)
|
||||
.orElseGet(() -> {
|
||||
PersonLabel newLabel = new PersonLabel();
|
||||
newLabel.setName(trimmedName);
|
||||
return personLabelRepository.save(newLabel);
|
||||
});
|
||||
labels.add(label);
|
||||
}
|
||||
}
|
||||
person.setLabels(labels);
|
||||
}
|
||||
|
||||
// Organization (not yet linked in Person entity)
|
||||
// String organization = dto.getOrganization();
|
||||
|
||||
// Email addresses
|
||||
person.setEmailOffice(trimOrNull(dto.getEmailOffice()));
|
||||
person.setEmailPrivate(trimOrNull(dto.getEmailPrivate()));
|
||||
person.setEmailOther(trimOrNull(dto.getEmailOther()));
|
||||
|
||||
// Office phone
|
||||
if (dto.getPhoneOffice() != null && !dto.getPhoneOffice().trim().isEmpty()) {
|
||||
PhoneNumber parsed = parsePhoneNumber(dto.getPhoneOffice());
|
||||
person.setPhoneOfficeCountryCode(parsed.countryCode);
|
||||
person.setPhoneOfficeAreaCode(parsed.areaCode);
|
||||
person.setPhoneOfficeNumber(parsed.number);
|
||||
}
|
||||
|
||||
// Private phone
|
||||
if (dto.getPhonePrivate() != null && !dto.getPhonePrivate().trim().isEmpty()) {
|
||||
PhoneNumber parsed = parsePhoneNumber(dto.getPhonePrivate());
|
||||
person.setPhonePrivateCountryCode(parsed.countryCode);
|
||||
person.setPhonePrivateAreaCode(parsed.areaCode);
|
||||
person.setPhonePrivateNumber(parsed.number);
|
||||
}
|
||||
|
||||
// Mobile phone (prefer phoneMobile, fallback to phoneOther)
|
||||
String mobile = dto.getPhoneMobile();
|
||||
String other = dto.getPhoneOther();
|
||||
|
||||
if (mobile != null && !mobile.trim().isEmpty()) {
|
||||
person.setPhoneMobile(mobile.trim());
|
||||
} else if (other != null && !other.trim().isEmpty()) {
|
||||
person.setPhoneMobile(other.trim());
|
||||
}
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
private String trimOrNull(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private PhoneNumber parsePhoneNumber(String phone) {
|
||||
PhoneNumber result = new PhoneNumber();
|
||||
|
||||
// Remove all whitespace and special characters except + and digits
|
||||
String cleaned = phone.replaceAll("[^+\\d]", "");
|
||||
|
||||
// Pattern to match: +CountryCode AreaCode Number
|
||||
// Examples: +493012345678, +4930123456
|
||||
Pattern pattern = Pattern.compile("^(\\+\\d{1,3})(\\d{2,5})(\\d+)$");
|
||||
Matcher matcher = pattern.matcher(cleaned);
|
||||
|
||||
if (matcher.matches()) {
|
||||
result.countryCode = matcher.group(1);
|
||||
result.areaCode = matcher.group(2);
|
||||
result.number = matcher.group(3);
|
||||
} else {
|
||||
// If pattern doesn't match, try simpler parsing
|
||||
if (cleaned.startsWith("+")) {
|
||||
// Assume format: +CC followed by rest
|
||||
int ccEnd = Math.min(4, cleaned.length()); // Max 3 digits + '+'
|
||||
result.countryCode = cleaned.substring(0, ccEnd);
|
||||
|
||||
String rest = cleaned.substring(ccEnd);
|
||||
if (rest.length() > 4) {
|
||||
// Split remaining into area code (2-3 digits) and number
|
||||
result.areaCode = rest.substring(0, Math.min(3, rest.length() - 4));
|
||||
result.number = rest.substring(Math.min(3, rest.length() - 4));
|
||||
} else {
|
||||
result.number = rest;
|
||||
}
|
||||
} else {
|
||||
// No country code, store as-is in number field
|
||||
result.number = cleaned;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class PhoneNumber {
|
||||
String countryCode;
|
||||
String areaCode;
|
||||
String number;
|
||||
}
|
||||
|
||||
public static class ImportResult {
|
||||
private int successCount = 0;
|
||||
private int errorCount = 0;
|
||||
private List<ImportError> errors = new ArrayList<>();
|
||||
|
||||
public void incrementSuccess() {
|
||||
successCount++;
|
||||
}
|
||||
|
||||
public void addError(int row, String message) {
|
||||
errorCount++;
|
||||
errors.add(new ImportError(row, message));
|
||||
}
|
||||
|
||||
public int getSuccessCount() {
|
||||
return successCount;
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
public List<ImportError> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImportError {
|
||||
private int row;
|
||||
private String message;
|
||||
|
||||
public ImportError(int row, String message) {
|
||||
this.row = row;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user