Implemented bulk import of persons
All checks were successful
Build, Push and Deploy / build-and-deploy (push) Successful in 1m43s

This commit is contained in:
Murat Özkorkmaz
2025-11-04 13:40:33 +01:00
parent d85406f0c7
commit 62c13ff0b1
9 changed files with 497 additions and 5 deletions

View File

@@ -1,7 +1,7 @@
meta { meta {
name: Auth Bruno name: Auth Bruno
type: http type: http
seq: 2 seq: 1
} }
post { post {

View File

@@ -0,0 +1,39 @@
meta {
name: Upload Organizations
type: http
seq: 3
}
post {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/data-import/organizations
body: multipartForm
auth: bearer
}
auth:bearer {
token: {{BEARER_TOKEN}}
}
body:json {
{
"name": "Bungalow",
"street": "Hebbelstraße",
"houseNumber": "30",
"zipCode": "55127",
"city": "Mainz",
"country": "DE",
"notes": "Lorem ipsum"
}
}
body:multipart-form {
file: @file(/Users/muratoezkorkmaz/projects/misc/skamp/init_data/organizations-17911945-23.xlsx)
}
body:file {
file: @file(/Users/murat/Pictures/IMG_0229.jpeg) @contentType(image/jpeg)
}
settings {
encodeUrl: true
}

View File

@@ -1,7 +1,7 @@
meta { meta {
name: Upload files name: Upload Persons
type: http type: http
seq: 1 seq: 2
} }
post { post {

Binary file not shown.

View File

@@ -1,5 +1,6 @@
package de.iwomm.propify_api.controller; package de.iwomm.propify_api.controller;
import de.iwomm.propify_api.service.OrganizationImportService;
import de.iwomm.propify_api.service.PersonImportService; import de.iwomm.propify_api.service.PersonImportService;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -13,9 +14,12 @@ import java.io.IOException;
public class DataImportController { public class DataImportController {
private final PersonImportService personImportService; private final PersonImportService personImportService;
private final OrganizationImportService organizationImportService;
public DataImportController(PersonImportService personImportService) { public DataImportController(PersonImportService personImportService,
OrganizationImportService organizationImportService) {
this.personImportService = personImportService; this.personImportService = personImportService;
this.organizationImportService = organizationImportService;
} }
@PostMapping("/persons") @PostMapping("/persons")
@@ -39,4 +43,26 @@ public class DataImportController {
.body("Unexpected error: " + e.getMessage()); .body("Unexpected error: " + e.getMessage());
} }
} }
@PostMapping("/organizations")
public ResponseEntity<?> importOrganizations(@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 {
OrganizationImportService.ImportResult result = organizationImportService.importOrganizationsFromExcel(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());
}
}
} }

View File

@@ -0,0 +1,84 @@
package de.iwomm.propify_api.dto;
import com.alibaba.excel.annotation.ExcelProperty;
public class OrganizationImportDTO {
@ExcelProperty(index = 0, value = "Organisation - Name")
private String name;
@ExcelProperty(index = 1, value = "Organisation - Branche Intern")
private String industry;
@ExcelProperty(index = 2, value = "Organisation - Label")
private String labels;
@ExcelProperty(index = 3, value = "Organisation - Abgeschlossene Deals")
private String completedDeals;
@ExcelProperty(index = 4, value = "Organisation - Offene Deals")
private String openDeals;
@ExcelProperty(index = 5, value = "Organisation - Datum nächste Aktivität")
private String nextActivityDate;
@ExcelProperty(index = 6, value = "Organisation - Besitzer")
private String owner;
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public String getLabels() {
return labels;
}
public void setLabels(String labels) {
this.labels = labels;
}
public String getCompletedDeals() {
return completedDeals;
}
public void setCompletedDeals(String completedDeals) {
this.completedDeals = completedDeals;
}
public String getOpenDeals() {
return openDeals;
}
public void setOpenDeals(String openDeals) {
this.openDeals = openDeals;
}
public String getNextActivityDate() {
return nextActivityDate;
}
public void setNextActivityDate(String nextActivityDate) {
this.nextActivityDate = nextActivityDate;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}

View File

@@ -18,6 +18,9 @@ public class Organization {
@JoinColumn(name = "industry_id") @JoinColumn(name = "industry_id")
private Industry industry; private Industry industry;
@Column
private String owner;
@Column(nullable = false) @Column(nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
@@ -50,6 +53,14 @@ public class Organization {
this.industry = industry; this.industry = industry;
} }
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public LocalDateTime getCreatedAt() { public LocalDateTime getCreatedAt() {
return createdAt; return createdAt;
} }

View File

@@ -1,4 +1,199 @@
package de.iwomm.propify_api.entity; package de.iwomm.propify_api.entity;
import jakarta.persistence.Entity; import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(nullable = false)
private UUID id;
@Column
private String firstName;
@Column
private String lastName;
@Column
private String emailOffice;
@Column
private String emailPrivate;
@Column
private String emailOther;
@Column
private String phoneOfficeCountryCode;
@Column
private String phoneOfficeAreaCode;
@Column
private String phoneOfficeNumber;
@Column
private String phonePrivateCountryCode;
@Column
private String phonePrivateAreaCode;
@Column
private String phonePrivateNumber;
@Column
private String phoneMobile;
@Column(unique = true)
private String keycloakId;
@Column(nullable = false)
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
this.createdAt = LocalDateTime.now();
}
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(
name = "person_labels",
joinColumns = @JoinColumn(name = "person_id"),
inverseJoinColumns = @JoinColumn(name = "label_id"),
foreignKey = @ForeignKey(name = "fk_person_labels_person", foreignKeyDefinition = "FOREIGN KEY (person_id) REFERENCES person(id) ON DELETE CASCADE")
)
private Set<PersonLabel> labels = new HashSet<>();
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
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 getPhoneOfficeCountryCode() {
return phoneOfficeCountryCode;
}
public void setPhoneOfficeCountryCode(String phoneOfficeCountryCode) {
this.phoneOfficeCountryCode = phoneOfficeCountryCode;
}
public String getPhoneOfficeAreaCode() {
return phoneOfficeAreaCode;
}
public void setPhoneOfficeAreaCode(String phoneOfficeAreaCode) {
this.phoneOfficeAreaCode = phoneOfficeAreaCode;
}
public String getPhoneOfficeNumber() {
return phoneOfficeNumber;
}
public void setPhoneOfficeNumber(String phoneOfficeNumber) {
this.phoneOfficeNumber = phoneOfficeNumber;
}
public String getPhonePrivateCountryCode() {
return phonePrivateCountryCode;
}
public void setPhonePrivateCountryCode(String phonePrivateCountryCode) {
this.phonePrivateCountryCode = phonePrivateCountryCode;
}
public String getPhonePrivateAreaCode() {
return phonePrivateAreaCode;
}
public void setPhonePrivateAreaCode(String phonePrivateAreaCode) {
this.phonePrivateAreaCode = phonePrivateAreaCode;
}
public String getPhonePrivateNumber() {
return phonePrivateNumber;
}
public void setPhonePrivateNumber(String phonePrivateNumber) {
this.phonePrivateNumber = phonePrivateNumber;
}
public String getPhoneMobile() {
return phoneMobile;
}
public void setPhoneMobile(String phoneMobile) {
this.phoneMobile = phoneMobile;
}
public String getKeycloakId() {
return keycloakId;
}
public void setKeycloakId(String keycloakId) {
this.keycloakId = keycloakId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public Set<PersonLabel> getLabels() {
return labels;
}
public void setLabels(Set<PersonLabel> labels) {
this.labels = labels;
}
}

View File

@@ -0,0 +1,137 @@
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.OrganizationImportDTO;
import de.iwomm.propify_api.entity.Industry;
import de.iwomm.propify_api.entity.Organization;
import de.iwomm.propify_api.repository.IndustryRepository;
import de.iwomm.propify_api.repository.OrganizationRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*;
@Service
public class OrganizationImportService {
private final OrganizationRepository organizationRepository;
private final IndustryRepository industryRepository;
public OrganizationImportService(OrganizationRepository organizationRepository,
IndustryRepository industryRepository) {
this.organizationRepository = organizationRepository;
this.industryRepository = industryRepository;
}
@Transactional
public ImportResult importOrganizationsFromExcel(MultipartFile file) throws IOException {
ImportResult result = new ImportResult();
EasyExcel.read(file.getInputStream(), OrganizationImportDTO.class, new ReadListener<OrganizationImportDTO>() {
private int currentRow = 1; // Start at 1 to account for header
@Override
public void invoke(OrganizationImportDTO data, AnalysisContext context) {
currentRow++;
try {
Organization organization = convertToOrganization(data);
organizationRepository.save(organization);
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 Organization convertToOrganization(OrganizationImportDTO dto) {
Organization organization = new Organization();
// Name
if (dto.getName() != null && !dto.getName().trim().isEmpty()) {
organization.setName(dto.getName().trim());
} else {
throw new IllegalArgumentException("Name is required");
}
// Industry (Branche) - create or lookup
if (dto.getIndustry() != null && !dto.getIndustry().trim().isEmpty()) {
String industryName = dto.getIndustry().trim();
Industry industry = industryRepository.findByName(industryName)
.orElseGet(() -> {
Industry newIndustry = new Industry();
newIndustry.setName(industryName);
return industryRepository.save(newIndustry);
});
organization.setIndustry(industry);
}
// Owner
organization.setOwner(trimOrNull(dto.getOwner()));
return organization;
}
private String trimOrNull(String value) {
if (value == null || value.trim().isEmpty()) {
return null;
}
return value.trim();
}
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;
}
}
}