feat(sync): Anschrift je Kontaktquelle mitführen
Beleg-, Liefer-, Rechnungsadresse, Ansprechpartner und Kundenstamm tragen jetzt neben Namensblock und Kanälen auch ihre Anschrift (Straße, Nr., PLZ, Ort, Land aus dem Länderstamm, Adresszusatz), sofern im ERP gepflegt. Migration 0035 ergänzt die Spalten an delivery_contact_sources; ContactSource.address in der API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -86,15 +86,22 @@ const ADDRESS_CONTACT_COLUMNS: &[&str] = &[
|
||||
"Telefon3", "Telefon4", "Mobiltel", "Mobiltel2", "EMail", "EMail2", "EMail3", "InternetAdresse",
|
||||
];
|
||||
|
||||
/// Anschrift-Spalten je Adresse (`Land` = ERP-Freitext, praktisch Zusatz).
|
||||
const ADDRESS_POSTAL_COLUMNS: &[&str] = &["Strasse", "Hausnummer", "PLZ", "Ort", "Land"];
|
||||
|
||||
/// Erzeugt den SELECT-Block für alle 5 Adressen — eine Zeile pro Spalte mit
|
||||
/// `<alias>.<col> AS <präfix>_<col>`. Wird in beide UNION-Hälften identisch
|
||||
/// einkopiert.
|
||||
/// `<alias>.<col> AS <präfix>_<col>`, dazu das Land aus dem Länderstamm als
|
||||
/// `<präfix>_LandName`. Wird in beide UNION-Hälften identisch einkopiert.
|
||||
fn address_select_block() -> String {
|
||||
let mut out = String::new();
|
||||
for (alias, prefix) in ADDRESS_ALIASES {
|
||||
for col in ADDRESS_CONTACT_COLUMNS {
|
||||
for col in ADDRESS_CONTACT_COLUMNS.iter().chain(ADDRESS_POSTAL_COLUMNS) {
|
||||
out.push_str(&format!(" {alias}.{col} AS {prefix}_{col},\n"));
|
||||
}
|
||||
out.push_str(&format!(
|
||||
" (SELECT LTRIM(RTRIM(l.Land)) FROM Laender l WHERE l.ROW_ID = {alias}.LandID) \
|
||||
AS {prefix}_LandName,\n"
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
@ -348,8 +355,9 @@ fn boolc(row: &tiberius::Row, col: &str) -> bool {
|
||||
|
||||
/// Liest aus einem Tiberius-Row die Kontaktdaten **einer** Adress-Rolle aus
|
||||
/// (alle Spalten heißen `<präfix>_<spalte>`, siehe [`address_select_block`]).
|
||||
/// Gibt `None` zurück, wenn weder ein Namensfeld noch ein Kontaktkanal
|
||||
/// belegt ist — leere Adressen werden so vom Sync nicht mitgeschleppt.
|
||||
/// Gibt `None` zurück, wenn weder ein Namensfeld noch ein Kontaktkanal noch
|
||||
/// eine Anschrift belegt ist — leere Adressen werden so vom Sync nicht
|
||||
/// mitgeschleppt.
|
||||
fn read_contact_source(
|
||||
row: &tiberius::Row,
|
||||
prefix: &str,
|
||||
@ -391,6 +399,21 @@ fn read_contact_source(
|
||||
push(&mut channels, ContactKind::Email, 3, get("EMail3"));
|
||||
push(&mut channels, ContactKind::Web, 1, get("InternetAdresse"));
|
||||
|
||||
// Anschrift nur, wenn Straße oder Ort gepflegt sind.
|
||||
let street = get("Strasse");
|
||||
let city = get("Ort");
|
||||
let address = (street.is_some() || city.is_some()).then(|| {
|
||||
let country = get("LandName").unwrap_or_default();
|
||||
Address {
|
||||
street: street.unwrap_or_default(),
|
||||
house_number: get("Hausnummer").unwrap_or_default(),
|
||||
postal_code: get("PLZ").unwrap_or_default(),
|
||||
city: city.unwrap_or_default(),
|
||||
addition: address_addition(get("Land"), &country),
|
||||
country,
|
||||
}
|
||||
});
|
||||
|
||||
let has_name = anrede.is_some()
|
||||
|| titel.is_some()
|
||||
|| name1.is_some()
|
||||
@ -398,7 +421,7 @@ fn read_contact_source(
|
||||
|| name3.is_some()
|
||||
|| abteilung.is_some()
|
||||
|| funktion.is_some();
|
||||
if channels.is_empty() && !has_name {
|
||||
if channels.is_empty() && !has_name && address.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -412,6 +435,7 @@ fn read_contact_source(
|
||||
abteilung,
|
||||
funktion,
|
||||
channels,
|
||||
address,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -161,6 +161,12 @@ struct ContactSourceRow {
|
||||
name3: Option<String>,
|
||||
abteilung: Option<String>,
|
||||
funktion: Option<String>,
|
||||
street: Option<String>,
|
||||
house_number: Option<String>,
|
||||
postal_code: Option<String>,
|
||||
city: Option<String>,
|
||||
country: Option<String>,
|
||||
address_addition: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
@ -280,6 +286,19 @@ fn map_contact_source(row: ContactSourceRow) -> Result<ContactSource, Applicatio
|
||||
name3: row.name3,
|
||||
abteilung: row.abteilung,
|
||||
funktion: row.funktion,
|
||||
// Ohne Straße und Ort gilt die Quelle als anschriftslos.
|
||||
address: if row.street.is_some() || row.city.is_some() {
|
||||
Some(Address {
|
||||
street: row.street.unwrap_or_default(),
|
||||
house_number: row.house_number.unwrap_or_default(),
|
||||
postal_code: row.postal_code.unwrap_or_default(),
|
||||
city: row.city.unwrap_or_default(),
|
||||
country: row.country.unwrap_or_default(),
|
||||
addition: row.address_addition,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -746,7 +765,8 @@ impl TourRepository for PgTourRepository {
|
||||
let source_rows = sqlx::query_as::<_, ContactSourceRow>(
|
||||
r#"
|
||||
SELECT id, delivery_id, role,
|
||||
anrede, titel, name1, name2, name3, abteilung, funktion
|
||||
anrede, titel, name1, name2, name3, abteilung, funktion,
|
||||
street, house_number, postal_code, city, country, address_addition
|
||||
FROM delivery_contact_sources
|
||||
WHERE delivery_id = ANY($1)
|
||||
ORDER BY delivery_id, role
|
||||
@ -1319,8 +1339,9 @@ async fn replace_contact_sources(
|
||||
r#"
|
||||
INSERT INTO delivery_contact_sources (
|
||||
delivery_id, role, anrede, titel, name1, name2, name3,
|
||||
abteilung, funktion
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
abteilung, funktion,
|
||||
street, house_number, postal_code, city, country, address_addition
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
@ -1333,6 +1354,12 @@ async fn replace_contact_sources(
|
||||
.bind(src.name3.as_deref())
|
||||
.bind(src.abteilung.as_deref())
|
||||
.bind(src.funktion.as_deref())
|
||||
.bind(src.address.as_ref().map(|a| a.street.as_str()))
|
||||
.bind(src.address.as_ref().map(|a| a.house_number.as_str()))
|
||||
.bind(src.address.as_ref().map(|a| a.postal_code.as_str()))
|
||||
.bind(src.address.as_ref().map(|a| a.city.as_str()))
|
||||
.bind(src.address.as_ref().map(|a| a.country.as_str()))
|
||||
.bind(src.address.as_ref().and_then(|a| a.addition.as_deref()))
|
||||
.fetch_one(&mut **tx)
|
||||
.await
|
||||
.map_err(db)?;
|
||||
|
||||
Reference in New Issue
Block a user