diff --git a/sql/91.plz-fix.sql b/sql/91.plz-fix.sql
index abc1b7d..9d9adc4 100644
--- a/sql/91.plz-fix.sql
+++ b/sql/91.plz-fix.sql
@@ -2,10 +2,16 @@
 INSERT INTO AT_plz_dest (plz, okz, dest)
 VALUES (2241,  3560, 'Schönkirchen-Reyersdorf'),
        (2165,  5013, 'Drasenhofen'),
-       (2134,  5115, 'Staatz-Kautendorf');
+       (2134,  5115, 'Staatz-Kautendorf'),
+       (2560,  3388, 'Grillenberg');
+
+DELETE FROM AT_plz_dest
+WHERE (plz, okz)
+IN ((2231,  5011),
+    (2561,  3388));
 
 UPDATE AT_ort SET name = 'Etzmannsdorf am Kamp' WHERE okz = 3938;
 UPDATE AT_ort SET kgnr = 18002 WHERE okz = 3779;
 UPDATE AT_ort SET kgnr = 5005 WHERE okz = 3818;
-
-DELETE FROM AT_plz_dest WHERE (plz, okz) = (2231,  5011);
+UPDATE AT_ort SET kgnr = 23351 WHERE okz = 5280;
+UPDATE AT_ort SET kgnr = 4311 WHERE okz = 3388;
diff --git a/sql/v01/99.schema_version.sql b/sql/v01/99.schema_version.sql
index 39c9913..c9d4eb6 100644
--- a/sql/v01/99.schema_version.sql
+++ b/sql/v01/99.schema_version.sql
@@ -1,3 +1,3 @@
 
 -- This value MUST NOT be changed while other connections are open!
-PRAGMA schema_version = 1500;
+PRAGMA schema_version = 1600;
diff --git a/wgmaster/migrate.py b/wgmaster/migrate.py
index c769f51..2dc4518 100755
--- a/wgmaster/migrate.py
+++ b/wgmaster/migrate.py
@@ -19,6 +19,8 @@ import utils
 class WG(Enum):
     MATZEN = 1
     WINZERKELLER = 2
+    WEINLAND = 3
+    BADEN = 4
 
     @classmethod
     def from_str(cls, name: str):
@@ -45,6 +47,7 @@ GROSSLAGE_KG_MAP: Optional[Dict[int, int]] = None
 DELIVERY_MAP: Optional[Dict[int, Tuple[int, int, int]]] = None
 MODIFIER_MAP: Optional[Dict[str, Dict]] = None
 SORT_MAP: Optional[Dict[str, str]] = None
+ATTRIBUTE_MAP: Optional[Dict[str, str]] = None
 PARAMETERS: Optional[Dict[str, str]] = None
 
 AUSTRIA = 40
@@ -112,6 +115,25 @@ ORT_NAMES: Dict[str, Optional[str]] = {
     'Obersdorf': None,
     'Sechshaus': None,
     'Rußbach': None,
+    'Pfaffstätten': 'Pfaffstättner',
+    'Berndorf': None,
+    'Teesdorf': None,
+    'Baden': 'Badner',
+    'Dornau': None,
+    'Pottendorf': None,
+    'Möllersdorf': None,
+    'Wienersdorf': None,
+    'Münchendorf': None,
+    'Hernstein': None,
+    'Großau': None,
+    'Oberwaltersdorf': None,
+    'Vöslau': None,
+    'Tribuswinkel': 'Tribuswinkler',
+    'Sollenau': None,
+    'Gutenbrunn': None,
+    'Kottingbrunn': None,
+    'Siebenhaus': None,
+    'Mariazell': None,
 }
 
 STREET_NAMES: Dict[str, str] = {
@@ -152,6 +174,40 @@ STREET_NAMES: Dict[str, str] = {
     'U. Weißgasse Straße': 'Untere Weißgerberstraße',
     'Dr. Josef-Levit-Straße': 'Dr.-Josef-Levit-Straße',
     'Karl Katschthaler-Straße': 'Karl-Katschthaler-Straße',
+    'Pfaffstättnerstraße': 'Paffstättner Straße',
+    'Badnerstraße': 'Badner Straße',
+    'Anton Krennstraße': 'Anton-Krenn-Straße',
+    'Fr.Jonasstraße': 'Franz-Jonas-Straße',
+    'Wr.Neustädterstraße': 'Wiener Neustädter Straße',
+    'Wr. Neustädterstraße': 'Wiener Neustädter Straße',
+    'Wr. Neustäderstraße': 'Wiener Neustädter Straße',
+    'Ob.Ödlitzerstraße': 'Obere Ödlitzer Straße',
+    'Obere Ödlitzerstraße': 'Obere Ödlitzer Straße',
+    'Triesterstraße': 'Triester Straße',
+    'Dr. Dolpstraße': 'Dr.-Dolp-Straße',
+    'Wienersd.Hauptstr.': 'Wienersdorfer Hauptstraße',
+    'Wienersd.Hauptstraße': 'Wienersdorfer Hauptstraße',
+    'Tr.Bundesstr.': 'Triester Bundesstraße',
+    'Tr.Bundesstraße': 'Triester Bundesstraße',
+    'J.Brunastraße': 'Josef-Bruna-Straße',
+    'J. Brunastraße': 'Josef-Bruna-Straße',
+    'Ferdinand Pichlergasse': 'Ferdinand-Pichler-Gasse',
+    'Dr. Figlstraße': 'Dr.-Figl-Straße',
+    'Franz Broschekplatz': 'Franz-Borschek-Platz',
+    'Tribuswinklerstraße': 'Tribuswinkler Straße',
+    'Rudolf Kaspargasse': 'Rudolf-Kaspar-Gasse',
+    'Traiskirchnerstraße': 'Traiskirchner Straße',
+    'Dr. Theodor Körnerstraße': 'Dr.-Theodor-Körner-Straße',
+    'Richard Klingerstraße': 'Richard-Klinger-Straße',
+    'Karl Langegasse': 'Karl-Lange-Gasse',
+    'Leopold Hörbingerstraße': 'Leopold-Hörbiger-Straße',
+    'Leopold Hörbinger Straße': 'Leopold-Hörbiger-Straße',
+    'Rudolf Zöllnergasse': 'Rudolf-Zöllner-Gasse',
+    'Anton Rauchstraße': 'Anton-Rauch-Straße',
+    'Isabellestraße': 'Erzherzogin-Isabelle-Straße',
+    'Erzherzogin Isabelle Straße': 'Erzherzogin-Isabelle-Straße',
+    'E. Penzig Franz Straße': 'Edgar-Penzing-Franz-Straße',
+    'Hernsteinerstr Straße': 'Hernsteiner Straße',
 }
 
 
@@ -249,16 +305,14 @@ def normalize_phone_nr(nr: Optional[str], ort: str = None) -> Optional[str]:
         nr = '+' + nr
     elif CLIENT == WG.WINZERKELLER and ort:
         ort = ort.upper().strip()
-        if ort in ('PILLICHSDORF', 'OBERSDORF', 'WOLKERSDORF', 'WOLFPASSING', 'PUTZING', 'GROSSENGERSDORF',
-                   'EIBESBRUNN'):
+        if ort in ('PILLICHSDORF', 'OBERSDORF', 'WOLKERSDORF', 'WOLFPASSING', 'PUTZING', 'GROSSENGERSDORF', 'EIBESBRUNN'):
             nr = f'+43 2245 {nr}'
         elif ort in ('ALBERNDORF', 'HAUGSDORF', 'AUGGENTHAL', 'HAUGSDORF'):
             nr = f'+43 2944 {nr}'
         elif ort in ('HADRES'):
             nr = f'+43 2943 {nr}'
         else:
-            print(nr, ort)
-            raise RuntimeError()
+            raise RuntimeError(f'Unable to find telephone number of "{ort}" ({nr})')
     if nr.startswith('+43'):
         if nr[4] == '6':
             nr = nr.replace(' ', '')
@@ -272,6 +326,22 @@ def normalize_phone_nr(nr: Optional[str], ort: str = None) -> Optional[str]:
     return nr.strip()
 
 
+def check_phone_nr(nr: str, mgnr: int, active: Optional[bool]) -> Tuple[Optional[str], Optional[str], Optional[str]]:
+    m = re.fullmatch(r'(.*?) ([A-Za-zäöüÄÖÜßẞ]+)$', nr)
+    if m is not None:
+        nr = m.group(1)
+        comment = m.group(2).strip()
+        if comment == 'Fi':
+            comment = 'Firma'
+    else:
+        comment = None
+    nnr = normalize_phone_nr(nr)
+    if len(nnr) <= 10 or nnr[0] != '+' or re.fullmatch(r'[+0-9 \-]+', nnr) is None:
+        invalid(mgnr, 'Tel.Nr.', nr, active)
+        return nnr, None, None
+    return nnr, 'mobile' if nnr[4] == '6' else 'landline', comment
+
+
 def fix_street_name(name: str) -> str:
     if name in STREET_NAMES:
         return STREET_NAMES[name]
@@ -322,6 +392,22 @@ def lookup_plz(plz: Optional[int], ort: Optional[str], address: Optional[str] =
         plz = 2023
     elif ort.upper() == 'NIEDERSCHLEINZ' and plz == 3721:
         plz = 3714
+    elif ort.upper() == 'OEYNHAUSEN' and plz == 2500:
+        plz = 2512
+    elif ort.upper() == 'MÖLLERSDORF' and plz == 2513:
+        plz = 2514
+    elif ort.upper() == 'SOOSS' and plz == 2500:
+        ort = 'SOOẞ'
+        plz = 2504
+    elif ort.upper() == 'ÖDLITZ' and plz == 2560:
+        ort = 'BERNDORF'
+    elif ort.upper() == 'ST.VEIT' and plz == 2562:
+        ort = 'BERNDORF'
+        plz = 2560
+    elif ort.upper() == 'SCHÖNAU/TRIESTING' and plz == 2525:
+        ort = 'SCHÖNAU AN DER TRIESTING'
+    elif ort.upper() == 'BAD FISCHAU - BRUNN' and plz == 2721:
+        ort = 'BAD FISCHAU-BRUNN'
 
     cur = DB_CNX.cursor()
     cur.execute("SELECT o.okz, p.dest, o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE plz = ?", (plz,))
@@ -347,6 +433,11 @@ def lookup_plz(plz: Optional[int], ort: Optional[str], address: Optional[str] =
         else:
             # Götzendorf
             return plz * 100000 + 3571
+    elif ort == 'BAD FISCHAU-BRUNN':
+        if 'viaduktstraße' in address.lower():
+            return plz * 100000 + 6560
+        elif 'teichplatz' in address.lower():
+            return plz * 100000 + 6560
 
     raise RuntimeError(f'PLZ not found ({plz} {ort}, {address})')
 
@@ -439,6 +530,38 @@ def lookup_gem_name(name: str) -> List[Tuple[int, int]]:
             gem_name = 'Poysdorf'
         elif name.lower() == 'nappersdorf-kammersdorf':
             return [(9008, 31028), (9026, 31028), (9032, 31028), (9037, 31028), (9051, 31028), (9067, 31028)]
+    elif CLIENT == WG.WEINLAND:
+        hkid = "'WLWV'"
+    elif CLIENT == WG.BADEN:
+        hkid = "'WLTH'"
+        if name.lower() == 'baden':
+            gem_name = 'Baden'
+        elif name.lower() in ('bad fischau-brunn', 'bad fischau - brunn'):
+            return [(23402, 32301), (23401, 32301)]
+        elif name.lower() == 'bad vöslau':
+            return [(4005, 30603), (4009, 30603), (4035, 30603)]
+        elif name.lower() == 'berndorf':
+            return [(4303, 30605), (4304, 30605), (4032, 30605), (4305, 30605)]
+        elif name.lower() in ('berndorf-ödlitz', 'ödlitz'):
+            return [(4304, 30605)]
+        elif name.lower() == 'eggendorf':
+            return [(23437, 32305), (23426, 32305)]
+        elif name.lower() == 'purkersdorf':
+            return []
+        elif name.lower() == 'schönau':
+            gem_name = 'Schönau an der Triesting'
+        elif name.lower() == 'siegersdorf':
+            gem_name = 'Pottendorf'
+        elif name.lower() == 'sooss':
+            name = 'Sooß'
+        elif name.lower() == 'st.veit':
+            return [(4303, 30605)]
+        elif name.lower() == 'wien':
+            return []
+        elif name.lower() == 'gramatneusiedl':
+            return []
+    else:
+        raise NotImplementedError(f'Gemeinde lookup for {CLIENT} not yet implemented')
 
     cur = DB_CNX.cursor()
     cur.execute("SELECT k.kgnr, k.name, g.gkz, g.name "
@@ -456,8 +579,7 @@ def lookup_gem_name(name: str) -> List[Tuple[int, int]]:
     if len(rows) == 1:
         return [(k, g) for k, _, g, _ in rows]
 
-    print(name, rows)
-    raise RuntimeError()
+    raise RuntimeError(f'Unable to find Gemeinde "{name}" ({rows})')
 
 
 def lookup_kg_name(kgnr: int) -> str:
@@ -477,8 +599,10 @@ def lookup_hkid(kgnr: Optional[int], qualid: str) -> str:
     if qualid in ('WEI', 'RSW'):
         return 'OEST'
     elif kgnr is None:
-        if CLIENT in (WG.MATZEN, WG.WINZERKELLER):
+        if CLIENT in (WG.MATZEN, WG.WINZERKELLER, WG.BADEN):
             hkid = 'WLNO'
+        else:
+            raise NotImplementedError(f'Default hkid for {CLIENT} not implemented yet')
     else:
         cur = DB_CNX.cursor()
         cur.execute("SELECT wb.hkid FROM AT_kg kg JOIN AT_gem g ON g.gkz = kg.gkz JOIN wb_gem wb ON wb.gkz = g.gkz "
@@ -605,6 +729,8 @@ def migrate_reeds(in_dir: str, out_dir: str) -> None:
 
 
 def migrate_attributes(in_dir: str, out_dir: str) -> None:
+    global ATTRIBUTE_MAP
+    ATTRIBUTE_MAP = {}
     with utils.csv_open(f'{out_dir}/wine_attribute.csv') as f:
         f.header('attrid', 'name', 'active', 'max_kg_per_ha', 'strict', 'fill_lower')
         for a in utils.csv_parse_dict(f'{in_dir}/TSortenAttribute.csv'):
@@ -613,10 +739,21 @@ def migrate_attributes(in_dir: str, out_dir: str) -> None:
             max_kg = int(a['KgProHa']) if a['KgProHa'] is not None else None
             if max_kg == 10_000:
                 max_kg = None
-            f.row(a['SANR'], a['Attribut'], True, max_kg, False, 0)
+            attrid = a['SANR']
+            if attrid == 'BIO':
+                attrid = 'B'
+            ATTRIBUTE_MAP[a['SANR']] = attrid
+            f.row(attrid, a['Attribut'], True, max_kg, False, 0)
         if CLIENT == WG.MATZEN:
             f.row('M', 'Matzen', False, None, False, 0)
             f.row('HU', 'Huber', False, None, False, 0)
+            ATTRIBUTE_MAP['M'] = 'M'
+            ATTRIBUTE_MAP['HU'] = 'HU'
+        elif CLIENT == WG.BADEN:
+            f.row('D', 'DAC', False, 7500, False, 0)
+            f.row('K', 'Kabinett', False, None, False, 0)
+            ATTRIBUTE_MAP['D'] = 'D'
+            ATTRIBUTE_MAP['K'] = 'K'
 
 
 def migrate_cultivations(in_dir: str, out_dir: str) -> None:
@@ -645,7 +782,8 @@ def migrate_area_commitment_types(in_dir: str, out_dir: str) -> None:
             if not sortid or sortid == 'SV':
                 continue
             menge = int(t['ErwarteteLiefermengeProHa'])
-            f.row(sortid + (t['SANR'] or ''), sortid[:2], t['SANR'] or sortid[2:] or None, None, menge,
+            attrid = ATTRIBUTE_MAP[t['SANR']] if t['SANR'] else None
+            f.row(sortid + (attrid or ''), sortid[:2], attrid or sortid[2:] or None, None, menge,
                   None, None, None)
         bio = []
         if CLIENT == WG.MATZEN:
@@ -671,6 +809,9 @@ def normalize_name(family_name: str, given_name: str) -> Tuple[Optional[str], Op
             return None, None, None, None, None, 'Landwirtschaftliche Fachschule Mistelbach'
         elif 'lagerhaus' in family_name.lower() and given_name == 'HOLLABRUNN-HORN':
             return None, None, None, None, None, 'Raiffeisen-Lagerhaus Hollabrunn-Horn eGen'
+    elif CLIENT == WG.BADEN:
+        if family_name in ('Marktgemeinde', 'Weinbauverein'):
+            return None, None, None, None, None, f'{family_name} {given_name}'
 
     if given_name.lower() not in ('kg', 'gesbr', 'gesnbr') and \
             len(family_name) > 0 and len(given_name) > 0 and is_alpha(family_name) and is_alpha(given_name):
@@ -689,7 +830,8 @@ def normalize_name(family_name: str, given_name: str) -> Tuple[Optional[str], Op
 
     given_name = given_name.replace('+', ' + ').replace('JOS ', 'JOS. ')
     given_name = re.sub(r' ?\((.+?)(, ?(.*?))?\)',
-                        lambda m: f' + {m.group(1)}{" + " + m.group(3) if m.group(2) else ""}', given_name)
+                        lambda m: m.group(0) if m.group(1) == 'FH' else
+                        f' + {m.group(1)}{" + " + m.group(3) if m.group(2) else ""}', given_name)
     given_name = re.sub(r' u\. ?| und ', ' + ', given_name, flags=re.IGNORECASE)
 
     titles = ''
@@ -703,35 +845,52 @@ def normalize_name(family_name: str, given_name: str) -> Tuple[Optional[str], Op
             case 'dr': titles += 'Dr. '
             case 'mag': titles += 'Mag. '
             case 'ing': titles += 'Ing. '
-            case 'dipling': titles += 'Dipl.-Ing. '
-            case 'di': titles += 'Dipl.-Ing. '
+            case 'di(fh)': titles += 'DI (FH) '
+            case 'dipling': titles += 'DI '
+            case 'dipli': titles += 'DI '
+            case 'di': titles += 'DI '
             case 'dkfm': titles += 'Dipl.-Kfm. '
             case 'ökrat': titles += 'ÖkR '
             case 'lkr': titles += 'ÖkR '
         return ' '
 
-    title_re = re.compile(r',?\b(dr|ing|mag|jun|sen|dkfm|dipl\. ?-?ing|di|ök\.rat|lkr)\b\.?', re.IGNORECASE)
+    title_re = re.compile(r',?((di ?\(fh\))|\b(dr|ing|mag|jun|sen|dkfm|dipl\. ?-?i(ng)?|di|ök\.rat|lkr)\b)\.?', re.IGNORECASE)
     given_name = utils.remove_spaces(re.sub(title_re, repl_title, given_name))
     family_name = utils.remove_spaces(re.sub(title_re, repl_title, family_name))
     if titles:
         prefix = titles.strip()
 
+    if given_name.lower() in ('weingut', 'weinbau'):
+        parts = family_name.split(' ')
+        return prefix, None, middle_names, ' '.join(parts[:-1]), suffix, given_name + ' ' + ' '.join(parts)
+    elif given_name.lower().startswith('weinbau ') or given_name.startswith('weingut '):
+        parts = given_name.split(' ')
+        return prefix, None, middle_names, family_name, suffix, ' '.join(parts[:-1]) + ' ' + family_name + ' ' + parts[-1]
+    elif family_name.lower() in ('weingut', 'weinbau'):
+        parts = given_name.split(' ')
+        return prefix, None, middle_names, ' '.join(parts[:-1]), suffix, family_name + ' ' + ' '.join(parts)
+
     family_parts = family_name.split(' ')
     last = family_parts[-1].lower()
-    if last in ('kg', 'keg.', 'gesbr', 'gnbr', 'gesnbr', 'gsbr'):
+    if last in ('kg', 'keg.', 'gesbr', 'gnbr', 'gesnbr', 'gsbr', 'og', 'gmbh'):
         family_name = ' '.join(family_parts[:-1])
         if ' ' not in family_name and len(family_name) > 4:
             family_name = family_name.title()
-        billing_name = family_name + ' ' + ('KG' if last == 'kg' else 'KEG' if last == 'keg.' else 'GesbR')
-        if is_alpha(given_name):
+        if family_name.startswith('Gem.'):
+            family_name = 'GeM ' + family_name[5:]
+        billing_name = family_name + ' ' + {'kg': 'KG', 'keg.': 'KEG', 'og': 'OG', 'gmbh': 'GmbH'}.get(last, 'GesbR')
+        if given_name.count(' ') == 1:
+            parts = given_name.split(' ')
+            return None, parts[0], None, parts[1], None, billing_name
+        elif is_alpha(given_name):
             return prefix, given_name.title(), middle_names, family_name, suffix, billing_name
 
     given_parts = given_name.split(' ')
     last = given_parts[-1].lower()
-    if last in ('kg', 'keg.', 'gesbr', 'gnbr', 'gesnbr', 'gsbr'):
+    if last in ('kg', 'keg.', 'gesbr', 'gnbr', 'gesnbr', 'gsbr', 'og', 'gmbh'):
         given_name = ' '.join(given_parts[:-1]).title()
         family_name = family_name.title()
-        billing_name = f'{family_name} {"KG" if last == "kg" else "KEG" if last == "keg." else "GesbR"}'
+        billing_name = family_name + ' ' + {'kg': 'KG', 'keg.': 'KEG', 'og': 'OG', 'gmbh': 'GmbH'}.get(last, 'GesbR')
         return prefix, given_name, middle_names, family_name, suffix, billing_name
 
     if ' ' in family_name or '.' in family_name:
@@ -767,11 +926,11 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
     mgnrs = [m['MGNR'] for m in members]
     fbs = parse_flaechenbindungen(in_dir)
 
-    with utils.csv_open(f'{out_dir}/member.csv') as f_m, \
+    with (utils.csv_open(f'{out_dir}/member.csv') as f_m, \
             utils.csv_open(f'{out_dir}/member_billing_address.csv') as f_mba, \
             utils.csv_open(f'{out_dir}/member_telephone_number.csv') as f_tel, \
             utils.csv_open(f'{out_dir}/member_email_address.csv') as f_email, \
-            utils.csv_open(f'{out_dir}/wb_kg.csv', 'a') as f_kg:
+            utils.csv_open(f'{out_dir}/wb_kg.csv', 'a') as f_kg):
         f_m.header(
             'mgnr', 'predecessor_mgnr', 'prefix', 'given_name', 'middle_names', 'family_name', 'suffix',
             'birthday', 'entry_date', 'exit_date', 'business_shares', 'accounting_nr', 'zwstid',
@@ -789,6 +948,10 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
 
             if family_name is None and given_name is None:
                 continue
+            elif m['Anmerkung'] == 'Musterbetrieb':
+                continue
+            elif CLIENT == WG.BADEN and family_name == 'Winzergenoss.':
+                continue
 
             given_name = given_name or ''
             if CLIENT == WG.MATZEN and given_name.startswith(' '):
@@ -861,6 +1024,14 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
                     bic = 'RLNWATWWAUE'
                 elif bic == 'RLNWATWMIB':
                     bic = 'RLNWATWWMIB'
+                elif bic == 'VBÖEATWW':
+                    bic = 'VBOEATWW'
+                elif bic == 'RLNWATBAD':
+                    bic = 'RLNWATWWBAD'
+                elif bic == 'SPBDATXXX':
+                    bic = 'SPBDAT21'
+                elif bic == 'IBAATWWXXX':
+                    bic = 'GIBAATWW'
                 if not BIC_RE.fullmatch(bic):
                     invalid(mgnr, 'BIC', bic, active)
                     bic = None
@@ -881,8 +1052,7 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
                     ort = ' '.join(parts[:-1])
                     new_address = parts[-1]
                 if address is not None and address != ' ' and address != new_address:
-                    print(address, new_address)
-                    raise RuntimeError()
+                    raise RuntimeError(f'Unable to rewrite address: "{address}" -> "{new_address}"')
                 address = parts[-1]
             if CLIENT == WG.WINZERKELLER and ort == 'JETZELDORF':
                 ort = 'JETZELSDORF'
@@ -898,9 +1068,9 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
                         address.startswith('Nr ') or \
                         (len(address) > 0 and address[0].isdigit()):
                     address = ort.title() + ' ' + address.split(' ')[-1]
-                address = address.replace('strasse', 'straße').replace('strassse', 'straße')\
-                    .replace('Strasse', 'Straße').replace('Str.', 'Straße').replace('stasse', 'straße')\
-                    .replace('str.', 'straße').replace('ster.', 'straße').replace('g. ', 'gasse ')\
+                address = address.replace('Pelz.', 'Pelzg.').replace('strasse', 'straße').replace('strassse', 'straße')\
+                    .replace('Strasse', 'Straße').replace('Str.', 'Straße').replace('stasse', 'straße').replace('st.', 'straße ')\
+                    .replace('str.', 'straße').replace('ster.', 'straße').replace('g.', 'gasse ').replace('pl.', 'platz ')\
                     .replace('Gross', 'Groß').replace('Bockfliess', 'Bockfließ').replace('Weiss', 'Weiß')\
                     .replace('Preussen', 'Preußen').replace('Schloss', 'Schloß').replace('luss', 'luß')\
                     .replace('Haupstraße', 'Hauptstraße').replace('Russ', 'Ruß').replace('Ross', 'Roß')
@@ -908,7 +1078,7 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
                 if address.startswith('Ob. '):
                     address = address.replace('Ob. ', 'Obere ', 1)
                 address = address.replace(' Nr. ', ' ')
-                address = re.sub(r'([^0-9]+?)( [0-9])', lambda a: fix_street_name(a.group(1)) + a.group(2), address)
+                address = re.sub(r'([^0-9]+?)( +[0-9])', lambda a: fix_street_name(a.group(1)) + a.group(2), address)
                 address = utils.remove_spaces(address)
                 if address_old != address:
                     convert(mgnr, 'Adresse', address_old, address)
@@ -916,7 +1086,7 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
             email: Optional[str] = m['EMail']
             emails = []
             if email is not None:
-                for email in email.split(' + '):
+                for email in (email.split('  ') if CLIENT == WG.BADEN else email.split(' + ')):
                     if email.isupper():
                         email = email.lower()
                     if not EMAIL_RE.fullmatch(email):
@@ -967,6 +1137,7 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
             phone_1: Optional[str] = m['Telefon']
             phone_2: Optional[str] = m['Telefax']
             phone_3: Optional[str] = m['Mobiltelefon']
+            phone_4: Optional[str] = m['EMail'] if m['EMail'] and '@' not in m['EMail'] else None
             numbers = []
 
             if CLIENT == WG.WINZERKELLER:
@@ -1063,35 +1234,27 @@ def migrate_members(in_dir: str, out_dir: str) -> None:
                         f_tel.row(mgnr, count, 'fax', nr, data['comment'])
             else:
                 if phone_1:
-                    phone_1 = normalize_phone_nr(phone_1)
-                    if len(phone_1) <= 10 or phone_1[0] != '+':
-                        invalid(mgnr, 'Tel.Nr.', m['Telefon'], active)
-                    else:
+                    phone_1, t, c = check_phone_nr(phone_1, mgnr, active)
+                    if t is not None:
                         numbers.append(phone_1)
-                        if phone_1[4] == '6':
-                            f_tel.row(mgnr, len(numbers), 'mobile', phone_1, None)
-                        else:
-                            f_tel.row(mgnr, len(numbers), 'landline', phone_1, None)
+                        f_tel.row(mgnr, len(numbers), t, phone_1, c)
                 if phone_2:
-                    phone_2 = normalize_phone_nr(phone_2)
-                    if len(phone_2) <= 8 or phone_2[0] != '+':
-                        invalid(mgnr, 'Fax.Nr.', m['Telefax'], active)
-                    else:
+                    phone_2, t, c = check_phone_nr(phone_2, mgnr, active)
+                    if t is not None:
                         numbers.append(phone_2)
-                        if phone_2[4] == '6':
-                            f_tel.row(mgnr, len(numbers), 'mobile', phone_2, None)
-                        else:
-                            f_tel.row(mgnr, len(numbers), 'fax', phone_2, None)
+                        f_tel.row(mgnr, len(numbers), 'fax' if t == 'landline' else t, phone_2, c)
                 if phone_3:
-                    phone_3 = normalize_phone_nr(phone_3)
-                    if len(phone_3) <= 10 or phone_3[0] != '+':
-                        invalid(mgnr, 'Tel.Nr.', m['Mobiltelefon'], active)
-                    elif phone_3 not in numbers:
+                    if phone_3.startswith('Handy'):
+                        phone_3 = phone_3[5:].strip(':').strip()
+                    phone_3, t, c = check_phone_nr(phone_3, mgnr, active)
+                    if t is not None and phone_3 not in numbers:
                         numbers.append(phone_3)
-                        if phone_3[4] == '6':
-                            f_tel.row(mgnr, len(numbers), 'mobile', phone_3, None)
-                        else:
-                            f_tel.row(mgnr, len(numbers), 'landline', phone_3, None)
+                        f_tel.row(mgnr, len(numbers), t, phone_3, c)
+                if phone_4:
+                    phone_4, t, c = check_phone_nr(phone_4, mgnr, active)
+                    if t is not None and phone_4 not in numbers:
+                        numbers.append(phone_4)
+                        f_tel.row(mgnr, len(numbers), t, phone_4, c)
 
             for i, email in enumerate(emails):
                 f_email.row(mgnr, i + 1, email, None)
@@ -1267,7 +1430,8 @@ def migrate_area_commitments(in_dir: str, out_dir: str) -> None:
                 convert(mgnr, f'GstNr. ({fbnr})', parz, gstnr)
 
             to = fb['Bis'] if fb['Bis'] and fb['Bis'] < 3000 else None
-            f_fb.row(fbnr, mgnr, fb['SNR'] + (fb['SANR'] or ''), CULTIVATION_MAP[fb['BANR'] or 1], area,
+            attrid = ATTRIBUTE_MAP[fb['SANR']] if fb['SANR'] else None
+            f_fb.row(fbnr, mgnr, fb['SNR'] + (attrid or ''), CULTIVATION_MAP[fb['BANR'] or 1], area,
                      kgnr, gstnr, rdnr, fb['Von'], to, comment)
 
 
@@ -1355,8 +1519,10 @@ def migrate_deliveries(in_dir: str, out_dir: str) -> None:
                 5: 'VT',  6: 'MV',  7: 'UP',  8: 'VL',
                 9: 'DN', 10: 'SA', 11: 'DA', 12: 'EG',
             }[nr]
+        elif CLIENT == WG.BADEN:
+            mod['id'] = name[-1] if name.startswith('Klasse') else 'TB' if name == 'Treuebonus' else 'UE'
         else:
-            raise NotImplementedError()
+            raise NotImplementedError(f'Modifier migration for {CLIENT} not yet implemented')
 
     deliveries = list(utils.csv_parse_dict(f'{in_dir}/TLieferungen.csv'))
     delivery_dict = {d['LINR']: d for d in deliveries}
@@ -1412,8 +1578,9 @@ def migrate_deliveries(in_dir: str, out_dir: str) -> None:
                 oe = d['OechsleOriginal'] or d['Oechsle']
                 kmw = GRADATION_MAP[oe]
                 sortid = d['SNR'].upper()
-                if d['SANR']:
-                    attributes.add(d['SANR'])
+                attrid = ATTRIBUTE_MAP[d['SANR']] if d['SANR'] else None
+                if attrid:
+                    attributes.add(attrid)
                 if len(sortid) != 2:
                     attributes.add(sortid[2:])
                     sortid = sortid[:2]
@@ -1437,10 +1604,13 @@ def migrate_deliveries(in_dir: str, out_dir: str) -> None:
                 elif CLIENT == WG.WINZERKELLER:
                     if 'F' in attributes:
                         attributes.remove('F')
+                elif CLIENT == WG.BADEN:
+                    if sortid == 'GO':
+                        sortid = 'SO'
 
                 if d['SNR'] != sortid:
-                    SORT_MAP[f'{d["SNR"]}/{d["SANR"] or ""}'] = f'{sortid}/{",".join(list(attributes)) or ""}'
-                    line = f'{d["SNR"]}/{d["SANR"]} -> {sortid}/{",".join(list(attributes)) or None}'
+                    SORT_MAP[f'{d["SNR"]}/{attrid or ""}'] = f'{sortid}/{",".join(list(attributes)) or ""}'
+                    line = f'{d["SNR"]}/{attrid} -> {sortid}/{",".join(list(attributes)) or None}'
                     if line not in updated_varieties:
                         updated_varieties[line] = 0
                     updated_varieties[line] += 1
@@ -1481,9 +1651,10 @@ def migrate_deliveries(in_dir: str, out_dir: str) -> None:
                 if waage:
                     # Waagenr: 1  ID: 19
                     # Waagennummer: 1  Speichernummer: 9166
+                    #    1
                     waage = re.split(r' +', waage)
-                    scale_id = waage[1]
-                    weighing_id = waage[3] if waage[2] == 'Speichernummer:' else f'{date}/{waage[3]}'
+                    scale_id = waage[1] if len(waage) > 2 else '1'
+                    weighing_id = waage[-1] if len(waage) > 2 and waage[2] == 'Speichernummer:' else f'{date}/{waage[-1]}'
                 elif len(glob_waage) == 0 and not handwiegung:
                     handwiegung = True
 
@@ -1639,17 +1810,20 @@ def migrate_payments(in_dir: str, out_dir: str) -> None:
                 'AuszahlungSortenQualitätsstufe': {},
             }
 
+            gb = data['Grundbetrag'] or 0
+            gbzs = data['GBZS']
             azs = data['AuszahlungSorten']
             for s in sort_map.get(p['AZNR'], []):
                 del s['AZNR']
                 del s['ID']
                 if s['Oechsle'] is None:
                     continue
-                key = SORT_MAP.get(f'{s["SNR"]}/{s["SANR"] or ""}', f'{s["SNR"].upper()}/{s["SANR"] or ""}')
+                attrid = ATTRIBUTE_MAP[s['SANR']] if s['SANR'] else None
+                key = SORT_MAP.get(f'{s["SNR"]}/{attrid or ""}', f'{s["SNR"].upper()}/{attrid or ""}')
                 if key is None or len(key) < 3:
                     continue
                 azs[key] = azs.get(key, {'Gebunden': {}, 'NichtGebunden': {}})
-                azs[key]['Gebunden' if s['gebunden'] else 'NichtGebunden'][s['Oechsle']] = s['Betrag']
+                azs[key]['Gebunden' if s['gebunden'] else 'NichtGebunden'][s['Oechsle']] = round(s['Betrag'] + gb, WGMASTER_PRECISION)
             curves = []
             curve_zero = False
             for key, d1 in azs.items():
@@ -1692,8 +1866,8 @@ def migrate_payments(in_dir: str, out_dir: str) -> None:
                 curves.insert(0, {
                     'id': 0,
                     'mode': 'oe',
-                    'data': data['Grundbetrag'],
-                    'geb': data['GBZS'] or 0
+                    'data': gb,
+                    'geb': gbzs or 0
                 })
             data['Kurven'] = curves
             azq = data['AuszahlungSortenQualitätsstufe']
@@ -1701,11 +1875,12 @@ def migrate_payments(in_dir: str, out_dir: str) -> None:
                 del q['AZNR']
                 del q['ID']
                 qualid = QUAL_MAP[q['QSNR']]
-                key = SORT_MAP.get(f'{q["SNR"]}/{q["SANR"] or ""}', f'{q["SNR"].upper()}/{q["SANR"] or ""}')
+                attrid = ATTRIBUTE_MAP[s['SANR']] if s['SANR'] else None
+                key = SORT_MAP.get(f'{q["SNR"]}/{attrid or ""}', f'{q["SNR"].upper()}/{attrid or ""}')
                 if key is None or len(key) < 3:
                     continue
                 azq[qualid] = azq.get(qualid, {})
-                azq[qualid][key] = q['Betrag'] or 0
+                azq[qualid][key] = round((q['Betrag'] or 0) + gb, WGMASTER_PRECISION)
             for qualid, d1 in azq.items():
                 azq[qualid] = collapse_data(d1)
 
@@ -1735,7 +1910,8 @@ def migrate_payments(in_dir: str, out_dir: str) -> None:
             b1 = gew - geb_gew
             b2 = geb_gew
             f_bucket.row(y, did, dpnr, 0, '_', b1)
-            f_bucket.row(y, did, dpnr, 1, p['SANR'] or '', b2)
+            attrid = ATTRIBUTE_MAP[p['SANR']] if p['SANR'] else None
+            f_bucket.row(y, did, dpnr, 1, attrid or '', b2)
             for aznr, avnr, tznr in variant_year_map[y]:
                 val = p[f'BTeilzahlung{tznr}' if tznr < 6 else 'BEndauszahlung']
                 val = round(val * pow(10, WGMASTER_PRECISION))
@@ -1752,7 +1928,9 @@ def migrate_parameters(in_dir: str, out_dir: str) -> None:
     }
     tokens = {
         WG.MATZEN: ('WGM', 'WG Matzen'),
-        WG.WINZERKELLER: ('WKW', 'Winzerkeller')
+        WG.WINZERKELLER: ('WKW', 'Winzerkeller'),
+        WG.WEINLAND: ('WGW', 'WG Weinland'),
+        WG.BADEN: ('WGB', 'WG Baden')
     }.get(CLIENT, (None, None))
 
     ort = PARAMETERS['MANDANTENORT'].title()