feat: extend farmer data model with complete address fields and customer number

Backend:
- Add customerNumber, companyName, contactPerson, street, houseNumber,
  postalCode, city, phoneNumber to Farmer domain model
- Update FarmerRow and FarmerMutation records with new fields
- Update repositories, services and controllers for new farmer structure
- Fix CORS configuration to allow credentials
- Remove unused authorizationService and imports
- Update data migration for new farmer schema

Frontend:
- Update FarmerRow and FarmerOption interfaces
- Extend AdministrationPage with new farmer form fields
- Update SampleRegistrationPage and SearchFarmerPage for new structure
This commit is contained in:
2026-03-18 21:13:29 +01:00
parent f9b83a166d
commit e7a18cd339
12 changed files with 464 additions and 128 deletions

View File

@@ -18,7 +18,8 @@ public class CorsConfig {
configuration.setAllowedOrigins(allowedOrigins);
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
configuration.setAllowCredentials(false);
configuration.setAllowCredentials(true);
configuration.setExposedHeaders(List.of("Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);

View File

@@ -10,8 +10,15 @@ public record Farmer(
@Id String id,
String accountId,
String businessKey,
String name,
String customerNumber,
String companyName,
String contactPerson,
String street,
String houseNumber,
String postalCode,
String city,
String email,
String phoneNumber,
boolean active,
String supersedesId,
LocalDateTime createdAt,

View File

@@ -6,11 +6,11 @@ import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
public interface FarmerRepository extends MongoRepository<Farmer, String> {
List<Farmer> findByActiveTrueOrderByNameAsc();
List<Farmer> findByActiveTrueOrderByCompanyNameAsc();
List<Farmer> findByAccountIdOrderByNameAsc(String accountId);
List<Farmer> findByAccountIdOrderByCompanyNameAsc(String accountId);
List<Farmer> findByAccountIdAndActiveTrueOrderByNameAsc(String accountId);
List<Farmer> findByAccountIdAndActiveTrueOrderByCompanyNameAsc(String accountId);
List<Farmer> findByNameContainingIgnoreCaseOrderByNameAsc(String name);
List<Farmer> findByCompanyNameContainingIgnoreCaseOrderByCompanyNameAsc(String companyName);
}

View File

@@ -14,7 +14,6 @@ import de.svencarstensen.muh.repository.FarmerRepository;
import de.svencarstensen.muh.repository.MedicationCatalogRepository;
import de.svencarstensen.muh.repository.PathogenCatalogRepository;
import de.svencarstensen.muh.security.AuthTokenService;
import de.svencarstensen.muh.security.AuthorizationService;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.data.mongodb.core.MongoTemplate;
@@ -42,7 +41,7 @@ public class CatalogService {
private static final Comparator<FarmerRow> FARMER_ROW_COMPARATOR = Comparator
.comparing(FarmerRow::active).reversed()
.thenComparing(FarmerRow::name, String.CASE_INSENSITIVE_ORDER)
.thenComparing(FarmerRow::companyName, String.CASE_INSENSITIVE_ORDER)
.thenComparing(FarmerRow::updatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
private static final Comparator<MedicationRow> MEDICATION_ROW_COMPARATOR = Comparator
@@ -67,7 +66,6 @@ public class CatalogService {
private final AppUserRepository appUserRepository;
private final MongoTemplate mongoTemplate;
private final AuthTokenService authTokenService;
private final AuthorizationService authorizationService;
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
public CatalogService(
@@ -77,8 +75,7 @@ public class CatalogService {
AntibioticCatalogRepository antibioticRepository,
AppUserRepository appUserRepository,
MongoTemplate mongoTemplate,
AuthTokenService authTokenService,
AuthorizationService authorizationService
AuthTokenService authTokenService
) {
this.farmerRepository = farmerRepository;
this.medicationRepository = medicationRepository;
@@ -87,7 +84,6 @@ public class CatalogService {
this.appUserRepository = appUserRepository;
this.mongoTemplate = mongoTemplate;
this.authTokenService = authTokenService;
this.authorizationService = authorizationService;
}
public ActiveCatalogSummary activeCatalogSummary(String actorId) {
@@ -113,7 +109,7 @@ public class CatalogService {
// Hilfsmethoden für Datenzugriff (immer nur eigene Daten des Hauptbenutzers)
private List<Farmer> listActiveFarmersForActor(AppUser actor) {
return farmerRepository.findByAccountIdAndActiveTrueOrderByNameAsc(resolveAccountId(actor));
return farmerRepository.findByAccountIdAndActiveTrueOrderByCompanyNameAsc(resolveAccountId(actor));
}
private List<MedicationCatalogItem> listActiveMedicationsForActor(AppUser actor) {
@@ -129,7 +125,7 @@ public class CatalogService {
}
private List<FarmerRow> listFarmerRowsForActor(AppUser actor) {
return farmerRepository.findByAccountIdOrderByNameAsc(resolveAccountId(actor)).stream()
return farmerRepository.findByAccountIdOrderByCompanyNameAsc(resolveAccountId(actor)).stream()
.map(this::toFarmerRow)
.sorted(FARMER_ROW_COMPARATOR)
.toList();
@@ -160,7 +156,7 @@ public class CatalogService {
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
String accountId = resolveAccountId(actor);
for (FarmerMutation mutation : mutations) {
if (isBlank(mutation.name())) {
if (isBlank(mutation.companyName())) {
continue;
}
LocalDateTime now = LocalDateTime.now();
@@ -169,8 +165,15 @@ public class CatalogService {
null,
accountId,
UUID.randomUUID().toString(),
mutation.name().trim(),
blankToNull(mutation.customerNumber()),
mutation.companyName().trim(),
blankToNull(mutation.contactPerson()),
blankToNull(mutation.street()),
blankToNull(mutation.houseNumber()),
blankToNull(mutation.postalCode()),
blankToNull(mutation.city()),
blankToNull(mutation.email()),
blankToNull(mutation.phoneNumber()),
mutation.active(),
null,
now,
@@ -185,15 +188,29 @@ public class CatalogService {
if (!accountId.equals(existing.accountId())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Nicht berechtigt");
}
boolean changed = !existing.name().equals(mutation.name().trim())
|| !safeEquals(existing.email(), blankToNull(mutation.email()));
boolean changed = !existing.companyName().equals(mutation.companyName().trim())
|| !safeEquals(existing.customerNumber(), blankToNull(mutation.customerNumber()))
|| !safeEquals(existing.contactPerson(), blankToNull(mutation.contactPerson()))
|| !safeEquals(existing.street(), blankToNull(mutation.street()))
|| !safeEquals(existing.houseNumber(), blankToNull(mutation.houseNumber()))
|| !safeEquals(existing.postalCode(), blankToNull(mutation.postalCode()))
|| !safeEquals(existing.city(), blankToNull(mutation.city()))
|| !safeEquals(existing.email(), blankToNull(mutation.email()))
|| !safeEquals(existing.phoneNumber(), blankToNull(mutation.phoneNumber()));
if (changed) {
farmerRepository.save(new Farmer(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.name(),
existing.customerNumber(),
existing.companyName(),
existing.contactPerson(),
existing.street(),
existing.houseNumber(),
existing.postalCode(),
existing.city(),
existing.email(),
existing.phoneNumber(),
false,
existing.supersedesId(),
existing.createdAt(),
@@ -203,8 +220,15 @@ public class CatalogService {
null,
existing.accountId(),
existing.businessKey(),
mutation.name().trim(),
blankToNull(mutation.customerNumber()),
mutation.companyName().trim(),
blankToNull(mutation.contactPerson()),
blankToNull(mutation.street()),
blankToNull(mutation.houseNumber()),
blankToNull(mutation.postalCode()),
blankToNull(mutation.city()),
blankToNull(mutation.email()),
blankToNull(mutation.phoneNumber()),
mutation.active(),
existing.id(),
now,
@@ -217,8 +241,15 @@ public class CatalogService {
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.name(),
existing.customerNumber(),
existing.companyName(),
existing.contactPerson(),
existing.street(),
existing.houseNumber(),
existing.postalCode(),
existing.city(),
existing.email(),
existing.phoneNumber(),
mutation.active(),
existing.supersedesId(),
existing.createdAt(),
@@ -746,8 +777,15 @@ public class CatalogService {
return new FarmerRow(
farmer.id(),
farmer.businessKey(),
farmer.name(),
farmer.customerNumber(),
farmer.companyName(),
farmer.contactPerson(),
farmer.street(),
farmer.houseNumber(),
farmer.postalCode(),
farmer.city(),
farmer.email(),
farmer.phoneNumber(),
farmer.active(),
farmer.updatedAt()
);
@@ -812,7 +850,7 @@ public class CatalogService {
}
private FarmerOption toFarmerOption(Farmer farmer) {
return new FarmerOption(farmer.businessKey(), farmer.name(), farmer.email());
return new FarmerOption(farmer.businessKey(), farmer.companyName(), farmer.contactPerson(), farmer.email());
}
private MedicationOption toMedicationOption(MedicationCatalogItem item) {
@@ -1077,20 +1115,31 @@ public class CatalogService {
LocalDateTime now = LocalDateTime.now();
// Migriere Farmers ohne accountId
// Migriere Farmers ohne accountId oder mit altem Schema
farmerRepository.findAll().stream()
.filter(farmer -> isBlank(farmer.accountId()))
.forEach(farmer -> farmerRepository.save(new Farmer(
farmer.id(),
defaultAccountId,
farmer.businessKey(),
farmer.name(),
farmer.email(),
farmer.active(),
farmer.supersedesId(),
farmer.createdAt(),
now
)));
.filter(farmer -> isBlank(farmer.accountId()) || isBlank(farmer.companyName()))
.forEach(farmer -> {
// Wenn companyName fehlt, nutze businessKey als Fallback
String companyName = isBlank(farmer.companyName()) ? farmer.businessKey() : farmer.companyName();
farmerRepository.save(new Farmer(
farmer.id(),
isBlank(farmer.accountId()) ? defaultAccountId : farmer.accountId(),
farmer.businessKey(),
null, // customerNumber
companyName,
null, // contactPerson
null, // street
null, // houseNumber
null, // postalCode
null, // city
farmer.email(),
null, // phoneNumber
farmer.active(),
farmer.supersedesId(),
farmer.createdAt(),
now
));
});
// Migriere Medications ohne accountId
medicationRepository.findAll().stream()
@@ -1213,7 +1262,7 @@ public class CatalogService {
) {
}
public record FarmerOption(String businessKey, String name, String email) {
public record FarmerOption(String businessKey, String companyName, String contactPerson, String email) {
}
public record MedicationOption(String businessKey, String name, MedicationCategory category) {
@@ -1249,14 +1298,33 @@ public class CatalogService {
public record FarmerRow(
String id,
String businessKey,
String name,
String customerNumber,
String companyName,
String contactPerson,
String street,
String houseNumber,
String postalCode,
String city,
String email,
String phoneNumber,
boolean active,
LocalDateTime updatedAt
) {
}
public record FarmerMutation(String id, String name, String email, boolean active) {
public record FarmerMutation(
String id,
String customerNumber,
String companyName,
String contactPerson,
String street,
String houseNumber,
String postalCode,
String city,
String email,
String phoneNumber,
boolean active
) {
}
public record MedicationRow(

View File

@@ -14,10 +14,8 @@ import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
@Service

View File

@@ -31,7 +31,7 @@ public class PortalService {
LocalDate date
) {
List<CatalogService.FarmerOption> matchingFarmers = catalogService.activeCatalogSummary(actorId).farmers().stream()
.filter(farmer -> farmerQuery == null || farmerQuery.isBlank() || farmer.name().toLowerCase(Locale.ROOT).contains(farmerQuery.toLowerCase(Locale.ROOT)))
.filter(farmer -> farmerQuery == null || farmerQuery.isBlank() || farmer.companyName().toLowerCase(Locale.ROOT).contains(farmerQuery.toLowerCase(Locale.ROOT)))
.toList();
List<PortalSampleRow> sampleRows;

View File

@@ -124,7 +124,7 @@ public class SampleService {
null,
sampleNumber,
farmer.businessKey(),
farmer.name(),
farmer.companyName(),
farmer.email(),
request.cowNumber().trim(),
blankToNull(request.cowName()),
@@ -178,7 +178,7 @@ public class SampleService {
existing.id(),
existing.sampleNumber(),
farmer.businessKey(),
farmer.name(),
farmer.companyName(),
farmer.email(),
request.cowNumber().trim(),
blankToNull(request.cowName()),