mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 17:39:57 +01:00
feat(shared): Länderauswahl mit ISO 3166-1 Mapping und CountryPicker
Backend: Country-Record (Shared Kernel), InMemoryCountryRepository mit ~249 Ländern und DACH-Priorisierung, ListCountries-UseCase, GET /api/countries?q= Endpoint. Frontend: CountryPicker-Komponente mit Fuzzy-Suche, DACH-Favoriten bei leerem Query. SupplierCreate-, CustomerCreate- und AddDeliveryAddress- Screens verwenden jetzt den CountryPicker statt Freitext. Detail-Screens zeigen den Ländercode in der Adressanzeige. Closes #71
This commit is contained in:
parent
2811836039
commit
a77f0ec5df
20 changed files with 1136 additions and 63 deletions
|
|
@ -0,0 +1,22 @@
|
|||
package de.effigenix.application.shared;
|
||||
|
||||
import de.effigenix.shared.common.Country;
|
||||
import de.effigenix.shared.common.CountryRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ListCountries {
|
||||
|
||||
private final CountryRepository countryRepository;
|
||||
|
||||
public ListCountries(CountryRepository countryRepository) {
|
||||
this.countryRepository = countryRepository;
|
||||
}
|
||||
|
||||
public List<Country> execute(String query) {
|
||||
if (query == null || query.isBlank()) {
|
||||
return countryRepository.findAll();
|
||||
}
|
||||
return countryRepository.search(query);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package de.effigenix.infrastructure.config;
|
||||
|
||||
import de.effigenix.application.shared.ListCountries;
|
||||
import de.effigenix.infrastructure.shared.InMemoryCountryRepository;
|
||||
import de.effigenix.shared.common.CountryRepository;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class SharedUseCaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public CountryRepository countryRepository() {
|
||||
return new InMemoryCountryRepository();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListCountries listCountries(CountryRepository countryRepository) {
|
||||
return new ListCountries(countryRepository);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
package de.effigenix.infrastructure.shared;
|
||||
|
||||
import de.effigenix.shared.common.Country;
|
||||
import de.effigenix.shared.common.CountryRepository;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class InMemoryCountryRepository implements CountryRepository {
|
||||
|
||||
private static final Set<String> DACH_CODES = Set.of("DE", "AT", "CH");
|
||||
|
||||
private final Map<String, Country> byCode;
|
||||
private final List<Country> allSorted;
|
||||
|
||||
public InMemoryCountryRepository() {
|
||||
var countries = List.of(
|
||||
new Country("AF", "Afghanistan"),
|
||||
new Country("EG", "Ägypten"),
|
||||
new Country("AX", "Åland-Inseln"),
|
||||
new Country("AL", "Albanien"),
|
||||
new Country("DZ", "Algerien"),
|
||||
new Country("AS", "Amerikanisch-Samoa"),
|
||||
new Country("VI", "Amerikanische Jungferninseln"),
|
||||
new Country("AD", "Andorra"),
|
||||
new Country("AO", "Angola"),
|
||||
new Country("AI", "Anguilla"),
|
||||
new Country("AQ", "Antarktis"),
|
||||
new Country("AG", "Antigua und Barbuda"),
|
||||
new Country("GQ", "Äquatorialguinea"),
|
||||
new Country("AR", "Argentinien"),
|
||||
new Country("AM", "Armenien"),
|
||||
new Country("AW", "Aruba"),
|
||||
new Country("AZ", "Aserbaidschan"),
|
||||
new Country("ET", "Äthiopien"),
|
||||
new Country("AU", "Australien"),
|
||||
new Country("BS", "Bahamas"),
|
||||
new Country("BH", "Bahrain"),
|
||||
new Country("BD", "Bangladesch"),
|
||||
new Country("BB", "Barbados"),
|
||||
new Country("BY", "Belarus"),
|
||||
new Country("BE", "Belgien"),
|
||||
new Country("BZ", "Belize"),
|
||||
new Country("BJ", "Benin"),
|
||||
new Country("BM", "Bermuda"),
|
||||
new Country("BT", "Bhutan"),
|
||||
new Country("BO", "Bolivien"),
|
||||
new Country("BQ", "Bonaire, Sint Eustatius und Saba"),
|
||||
new Country("BA", "Bosnien und Herzegowina"),
|
||||
new Country("BW", "Botsuana"),
|
||||
new Country("BV", "Bouvetinsel"),
|
||||
new Country("BR", "Brasilien"),
|
||||
new Country("VG", "Britische Jungferninseln"),
|
||||
new Country("IO", "Britisches Territorium im Indischen Ozean"),
|
||||
new Country("BN", "Brunei Darussalam"),
|
||||
new Country("BG", "Bulgarien"),
|
||||
new Country("BF", "Burkina Faso"),
|
||||
new Country("BI", "Burundi"),
|
||||
new Country("CV", "Cabo Verde"),
|
||||
new Country("CL", "Chile"),
|
||||
new Country("CN", "China"),
|
||||
new Country("CK", "Cookinseln"),
|
||||
new Country("CR", "Costa Rica"),
|
||||
new Country("CI", "Côte d'Ivoire"),
|
||||
new Country("CW", "Curaçao"),
|
||||
new Country("DK", "Dänemark"),
|
||||
new Country("DE", "Deutschland"),
|
||||
new Country("DM", "Dominica"),
|
||||
new Country("DO", "Dominikanische Republik"),
|
||||
new Country("DJ", "Dschibuti"),
|
||||
new Country("EC", "Ecuador"),
|
||||
new Country("SV", "El Salvador"),
|
||||
new Country("ER", "Eritrea"),
|
||||
new Country("EE", "Estland"),
|
||||
new Country("SZ", "Eswatini"),
|
||||
new Country("FK", "Falklandinseln"),
|
||||
new Country("FO", "Färöer"),
|
||||
new Country("FJ", "Fidschi"),
|
||||
new Country("FI", "Finnland"),
|
||||
new Country("FR", "Frankreich"),
|
||||
new Country("GF", "Französisch-Guayana"),
|
||||
new Country("PF", "Französisch-Polynesien"),
|
||||
new Country("TF", "Französische Süd- und Antarktisgebiete"),
|
||||
new Country("GA", "Gabun"),
|
||||
new Country("GM", "Gambia"),
|
||||
new Country("GE", "Georgien"),
|
||||
new Country("GH", "Ghana"),
|
||||
new Country("GI", "Gibraltar"),
|
||||
new Country("GD", "Grenada"),
|
||||
new Country("GR", "Griechenland"),
|
||||
new Country("GL", "Grönland"),
|
||||
new Country("GP", "Guadeloupe"),
|
||||
new Country("GU", "Guam"),
|
||||
new Country("GT", "Guatemala"),
|
||||
new Country("GG", "Guernsey"),
|
||||
new Country("GN", "Guinea"),
|
||||
new Country("GW", "Guinea-Bissau"),
|
||||
new Country("GY", "Guyana"),
|
||||
new Country("HT", "Haiti"),
|
||||
new Country("HM", "Heard und McDonaldinseln"),
|
||||
new Country("HN", "Honduras"),
|
||||
new Country("HK", "Hongkong"),
|
||||
new Country("IN", "Indien"),
|
||||
new Country("ID", "Indonesien"),
|
||||
new Country("IQ", "Irak"),
|
||||
new Country("IR", "Iran"),
|
||||
new Country("IE", "Irland"),
|
||||
new Country("IS", "Island"),
|
||||
new Country("IM", "Isle of Man"),
|
||||
new Country("IL", "Israel"),
|
||||
new Country("IT", "Italien"),
|
||||
new Country("JM", "Jamaika"),
|
||||
new Country("JP", "Japan"),
|
||||
new Country("YE", "Jemen"),
|
||||
new Country("JE", "Jersey"),
|
||||
new Country("JO", "Jordanien"),
|
||||
new Country("KY", "Kaimaninseln"),
|
||||
new Country("KH", "Kambodscha"),
|
||||
new Country("CM", "Kamerun"),
|
||||
new Country("CA", "Kanada"),
|
||||
new Country("KZ", "Kasachstan"),
|
||||
new Country("QA", "Katar"),
|
||||
new Country("KE", "Kenia"),
|
||||
new Country("KG", "Kirgisistan"),
|
||||
new Country("KI", "Kiribati"),
|
||||
new Country("CC", "Kokosinseln"),
|
||||
new Country("CO", "Kolumbien"),
|
||||
new Country("KM", "Komoren"),
|
||||
new Country("CD", "Kongo, Demokratische Republik"),
|
||||
new Country("CG", "Kongo, Republik"),
|
||||
new Country("KP", "Korea, Demokratische Volksrepublik"),
|
||||
new Country("KR", "Korea, Republik"),
|
||||
new Country("HR", "Kroatien"),
|
||||
new Country("CU", "Kuba"),
|
||||
new Country("KW", "Kuwait"),
|
||||
new Country("LA", "Laos"),
|
||||
new Country("LS", "Lesotho"),
|
||||
new Country("LV", "Lettland"),
|
||||
new Country("LB", "Libanon"),
|
||||
new Country("LR", "Liberia"),
|
||||
new Country("LY", "Libyen"),
|
||||
new Country("LI", "Liechtenstein"),
|
||||
new Country("LT", "Litauen"),
|
||||
new Country("LU", "Luxemburg"),
|
||||
new Country("MO", "Macao"),
|
||||
new Country("MG", "Madagaskar"),
|
||||
new Country("MW", "Malawi"),
|
||||
new Country("MY", "Malaysia"),
|
||||
new Country("MV", "Malediven"),
|
||||
new Country("ML", "Mali"),
|
||||
new Country("MT", "Malta"),
|
||||
new Country("MA", "Marokko"),
|
||||
new Country("MH", "Marshallinseln"),
|
||||
new Country("MQ", "Martinique"),
|
||||
new Country("MR", "Mauretanien"),
|
||||
new Country("MU", "Mauritius"),
|
||||
new Country("YT", "Mayotte"),
|
||||
new Country("MX", "Mexiko"),
|
||||
new Country("FM", "Mikronesien"),
|
||||
new Country("MD", "Moldau"),
|
||||
new Country("MC", "Monaco"),
|
||||
new Country("MN", "Mongolei"),
|
||||
new Country("ME", "Montenegro"),
|
||||
new Country("MS", "Montserrat"),
|
||||
new Country("MZ", "Mosambik"),
|
||||
new Country("MM", "Myanmar"),
|
||||
new Country("NA", "Namibia"),
|
||||
new Country("NR", "Nauru"),
|
||||
new Country("NP", "Nepal"),
|
||||
new Country("NC", "Neukaledonien"),
|
||||
new Country("NZ", "Neuseeland"),
|
||||
new Country("NI", "Nicaragua"),
|
||||
new Country("NL", "Niederlande"),
|
||||
new Country("NE", "Niger"),
|
||||
new Country("NG", "Nigeria"),
|
||||
new Country("NU", "Niue"),
|
||||
new Country("MK", "Nordmazedonien"),
|
||||
new Country("MP", "Nördliche Marianen"),
|
||||
new Country("NF", "Norfolkinsel"),
|
||||
new Country("NO", "Norwegen"),
|
||||
new Country("OM", "Oman"),
|
||||
new Country("AT", "Österreich"),
|
||||
new Country("TL", "Osttimor"),
|
||||
new Country("PK", "Pakistan"),
|
||||
new Country("PW", "Palau"),
|
||||
new Country("PS", "Palästina"),
|
||||
new Country("PA", "Panama"),
|
||||
new Country("PG", "Papua-Neuguinea"),
|
||||
new Country("PY", "Paraguay"),
|
||||
new Country("PE", "Peru"),
|
||||
new Country("PH", "Philippinen"),
|
||||
new Country("PN", "Pitcairninseln"),
|
||||
new Country("PL", "Polen"),
|
||||
new Country("PT", "Portugal"),
|
||||
new Country("PR", "Puerto Rico"),
|
||||
new Country("RE", "Réunion"),
|
||||
new Country("RW", "Ruanda"),
|
||||
new Country("RO", "Rumänien"),
|
||||
new Country("RU", "Russland"),
|
||||
new Country("SB", "Salomonen"),
|
||||
new Country("ZM", "Sambia"),
|
||||
new Country("WS", "Samoa"),
|
||||
new Country("SM", "San Marino"),
|
||||
new Country("ST", "São Tomé und Príncipe"),
|
||||
new Country("SA", "Saudi-Arabien"),
|
||||
new Country("SE", "Schweden"),
|
||||
new Country("CH", "Schweiz"),
|
||||
new Country("SN", "Senegal"),
|
||||
new Country("RS", "Serbien"),
|
||||
new Country("SC", "Seychellen"),
|
||||
new Country("SL", "Sierra Leone"),
|
||||
new Country("ZW", "Simbabwe"),
|
||||
new Country("SG", "Singapur"),
|
||||
new Country("SX", "Sint Maarten"),
|
||||
new Country("SK", "Slowakei"),
|
||||
new Country("SI", "Slowenien"),
|
||||
new Country("SO", "Somalia"),
|
||||
new Country("ES", "Spanien"),
|
||||
new Country("LK", "Sri Lanka"),
|
||||
new Country("BL", "St. Barthélemy"),
|
||||
new Country("SH", "St. Helena"),
|
||||
new Country("KN", "St. Kitts und Nevis"),
|
||||
new Country("LC", "St. Lucia"),
|
||||
new Country("MF", "St. Martin"),
|
||||
new Country("PM", "St. Pierre und Miquelon"),
|
||||
new Country("VC", "St. Vincent und die Grenadinen"),
|
||||
new Country("ZA", "Südafrika"),
|
||||
new Country("SD", "Sudan"),
|
||||
new Country("GS", "Südgeorgien und die Südlichen Sandwichinseln"),
|
||||
new Country("SS", "Südsudan"),
|
||||
new Country("SR", "Suriname"),
|
||||
new Country("SJ", "Svalbard und Jan Mayen"),
|
||||
new Country("SY", "Syrien"),
|
||||
new Country("TJ", "Tadschikistan"),
|
||||
new Country("TW", "Taiwan"),
|
||||
new Country("TZ", "Tansania"),
|
||||
new Country("TH", "Thailand"),
|
||||
new Country("TG", "Togo"),
|
||||
new Country("TK", "Tokelau"),
|
||||
new Country("TO", "Tonga"),
|
||||
new Country("TT", "Trinidad und Tobago"),
|
||||
new Country("TD", "Tschad"),
|
||||
new Country("CZ", "Tschechien"),
|
||||
new Country("TN", "Tunesien"),
|
||||
new Country("TR", "Türkei"),
|
||||
new Country("TM", "Turkmenistan"),
|
||||
new Country("TC", "Turks- und Caicosinseln"),
|
||||
new Country("TV", "Tuvalu"),
|
||||
new Country("UG", "Uganda"),
|
||||
new Country("UA", "Ukraine"),
|
||||
new Country("HU", "Ungarn"),
|
||||
new Country("UM", "United States Minor Outlying Islands"),
|
||||
new Country("UY", "Uruguay"),
|
||||
new Country("UZ", "Usbekistan"),
|
||||
new Country("VU", "Vanuatu"),
|
||||
new Country("VA", "Vatikanstadt"),
|
||||
new Country("VE", "Venezuela"),
|
||||
new Country("AE", "Vereinigte Arabische Emirate"),
|
||||
new Country("US", "Vereinigte Staaten"),
|
||||
new Country("GB", "Vereinigtes Königreich"),
|
||||
new Country("VN", "Vietnam"),
|
||||
new Country("WF", "Wallis und Futuna"),
|
||||
new Country("CX", "Weihnachtsinsel"),
|
||||
new Country("EH", "Westsahara"),
|
||||
new Country("CF", "Zentralafrikanische Republik"),
|
||||
new Country("CY", "Zypern")
|
||||
);
|
||||
|
||||
this.byCode = countries.stream().collect(Collectors.toMap(Country::code, c -> c));
|
||||
|
||||
// Sort: DACH first (DE, AT, CH), then rest alphabetically by name
|
||||
var dach = countries.stream()
|
||||
.filter(c -> DACH_CODES.contains(c.code()))
|
||||
.sorted(Comparator.comparing(c -> List.of("DE", "AT", "CH").indexOf(c.code())))
|
||||
.toList();
|
||||
var rest = countries.stream()
|
||||
.filter(c -> !DACH_CODES.contains(c.code()))
|
||||
.sorted(Comparator.comparing(Country::name))
|
||||
.toList();
|
||||
this.allSorted = Stream.concat(dach.stream(), rest.stream()).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Country> findAll() {
|
||||
return allSorted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Country> search(String query) {
|
||||
if (query == null || query.isBlank()) {
|
||||
return findAll();
|
||||
}
|
||||
var q = query.toLowerCase();
|
||||
var matches = allSorted.stream()
|
||||
.filter(c -> c.name().toLowerCase().contains(q) || c.code().toLowerCase().contains(q))
|
||||
.toList();
|
||||
// DACH countries first in results too
|
||||
var dach = matches.stream().filter(c -> DACH_CODES.contains(c.code())).toList();
|
||||
var rest = matches.stream().filter(c -> !DACH_CODES.contains(c.code())).toList();
|
||||
return Stream.concat(dach.stream(), rest.stream()).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Country> findByCode(String code) {
|
||||
return Optional.ofNullable(byCode.get(code));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package de.effigenix.infrastructure.shared.web;
|
||||
|
||||
import de.effigenix.application.shared.ListCountries;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/countries")
|
||||
@SecurityRequirement(name = "Bearer Authentication")
|
||||
@Tag(name = "Countries", description = "ISO 3166-1 country reference data")
|
||||
public class CountryController {
|
||||
|
||||
private final ListCountries listCountries;
|
||||
|
||||
public CountryController(ListCountries listCountries) {
|
||||
this.listCountries = listCountries;
|
||||
}
|
||||
|
||||
public record CountryResponse(String code, String name) {}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<CountryResponse>> search(
|
||||
@RequestParam(name = "q", required = false, defaultValue = "") String query) {
|
||||
var countries = listCountries.execute(query);
|
||||
var response = countries.stream()
|
||||
.map(c -> new CountryResponse(c.code(), c.name()))
|
||||
.toList();
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.effigenix.shared.common;
|
||||
|
||||
public record Country(String code, String name) {
|
||||
|
||||
public Country {
|
||||
if (code == null || !code.matches("^[A-Z]{2}$")) {
|
||||
throw new IllegalArgumentException("Country code must be exactly 2 uppercase letters, got: " + code);
|
||||
}
|
||||
if (name == null || name.isBlank()) {
|
||||
throw new IllegalArgumentException("Country name must not be blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package de.effigenix.shared.common;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CountryRepository {
|
||||
List<Country> findAll();
|
||||
List<Country> search(String query);
|
||||
Optional<Country> findByCode(String code);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue