Implemented bulk import of persons
All checks were successful
Build, Push and Deploy / build-and-deploy (push) Successful in 1m43s
All checks were successful
Build, Push and Deploy / build-and-deploy (push) Successful in 1m43s
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: Auth Bruno
|
name: Auth Bruno
|
||||||
type: http
|
type: http
|
||||||
seq: 2
|
seq: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
|||||||
39
bruno/propify/DataImport/Upload Organizations.bru
Normal file
39
bruno/propify/DataImport/Upload Organizations.bru
Normal 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
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: Upload files
|
name: Upload Persons
|
||||||
type: http
|
type: http
|
||||||
seq: 1
|
seq: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
BIN
init_data/organizations-17911945-23.xlsx
Normal file
BIN
init_data/organizations-17911945-23.xlsx
Normal file
Binary file not shown.
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user