diff --git a/innungsapp/.env.example b/innungsapp/.env.example index 07ce7e4..e9fc4f5 100644 --- a/innungsapp/.env.example +++ b/innungsapp/.env.example @@ -1,7 +1,9 @@ # ============================================= -# DATABASE +# DATABASE (SQLite — kein externer DB-Server nötig) +# Dev: file:../../packages/shared/prisma/dev.db +# Prod: file:./prisma/prod.db # ============================================= -DATABASE_URL="postgresql://postgres:password@localhost:5432/innungsapp" +DATABASE_URL="file:../../packages/shared/prisma/dev.db" # ============================================= # BETTER-AUTH diff --git a/innungsapp/apps/admin/app/api/setup/route.ts b/innungsapp/apps/admin/app/api/setup/route.ts new file mode 100644 index 0000000..5a16412 --- /dev/null +++ b/innungsapp/apps/admin/app/api/setup/route.ts @@ -0,0 +1,60 @@ +/** + * DEV-ONLY: Sets a password for the demo admin user via better-auth. + * Call once after seeding: GET http://localhost:3032/api/setup + * Remove this file before going to production. + */ +import { NextResponse } from 'next/server' +import { auth } from '@/lib/auth' +import { prisma } from '@innungsapp/shared' + +export async function GET() { + if (process.env.NODE_ENV === 'production') { + return NextResponse.json({ error: 'Not available in production' }, { status: 403 }) + } + + // Delete the pre-seeded user so better-auth can create it fresh with a hashed password + await prisma.account.deleteMany({ where: { userId: 'demo-admin-user-id' } }) + await prisma.member.deleteMany({ where: { userId: 'demo-admin-user-id' } }) + await prisma.userRole.deleteMany({ where: { userId: 'demo-admin-user-id' } }) + await prisma.user.deleteMany({ where: { id: 'demo-admin-user-id' } }) + + // Re-create via better-auth so the password is properly hashed + const result = await auth.api.signUpEmail({ + body: { email: 'admin@demo.de', password: 'demo1234', name: 'Demo Admin' }, + }) + + if (!result?.user) { + return NextResponse.json({ error: 'signUp failed', result }, { status: 500 }) + } + + const newUserId = result.user.id + + // Restore org membership for the new user ID + const org = await prisma.organization.findFirst({ where: { slug: 'innung-elektro-stuttgart' } }) + if (org) { + await prisma.userRole.upsert({ + where: { orgId_userId: { orgId: org.id, userId: newUserId } }, + update: {}, + create: { orgId: org.id, userId: newUserId, role: 'admin' }, + }) + await prisma.member.upsert({ + where: { userId: newUserId }, + update: {}, + create: { + orgId: org.id, + userId: newUserId, + name: 'Demo Admin', + betrieb: 'Innungsgeschäftsstelle', + sparte: 'Elektrotechnik', + ort: 'Stuttgart', + email: 'admin@demo.de', + status: 'aktiv', + }, + }) + } + + return NextResponse.json({ + ok: true, + message: 'Setup complete. Login: admin@demo.de / demo1234', + }) +} diff --git a/innungsapp/apps/admin/app/login/page.tsx b/innungsapp/apps/admin/app/login/page.tsx index 50e345e..b53a4d0 100644 --- a/innungsapp/apps/admin/app/login/page.tsx +++ b/innungsapp/apps/admin/app/login/page.tsx @@ -3,14 +3,16 @@ import { useState } from 'react' import { createAuthClient } from 'better-auth/react' import { magicLinkClient } from 'better-auth/client/plugins' - const authClient = createAuthClient({ - baseURL: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000', plugins: [magicLinkClient()], }) +type Mode = 'password' | 'magic' + export default function LoginPage() { + const [mode, setMode] = useState('password') const [email, setEmail] = useState('') + const [password, setPassword] = useState('') const [sent, setSent] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState('') @@ -20,16 +22,29 @@ export default function LoginPage() { setLoading(true) setError('') - const result = await authClient.signIn.magicLink({ - email, - callbackURL: '/dashboard', - }) - - setLoading(false) - if (result.error) { - setError(result.error.message ?? 'Ein Fehler ist aufgetreten.') + if (mode === 'password') { + const result = await authClient.signIn.email({ + email, + password, + callbackURL: '/dashboard', + }) + setLoading(false) + if (result.error) { + setError(result.error.message ?? 'E-Mail oder Passwort falsch.') + } else { + window.location.href = '/dashboard' + } } else { - setSent(true) + const result = await authClient.signIn.magicLink({ + email, + callbackURL: '/dashboard', + }) + setLoading(false) + if (result.error) { + setError(result.error.message ?? 'Ein Fehler ist aufgetreten.') + } else { + setSent(true) + } } } @@ -53,9 +68,7 @@ export default function LoginPage() { -

- E-Mail gesendet! -

+

E-Mail gesendet!

Wir haben einen Login-Link an {email} gesendet. Bitte überprüfen Sie Ihr Postfach. @@ -69,15 +82,33 @@ export default function LoginPage() { ) : ( <> -

- Anmelden -

+

Anmelden

+ + {/* Mode toggle */} +
+ + +
+
-
+ {mode === 'password' && ( +
+ + setPassword(e.target.value)} + placeholder="••••••••" + className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + /> +
+ )} + {error && ( -

- {error} -

+

{error}

)}
-

- Kein Passwort nötig — Sie erhalten einen Link per E-Mail. -

+ {mode === 'password' && ( +

+ Demo: admin@demo.de / demo1234 +

+ )} )} diff --git a/innungsapp/apps/admin/lib/auth.ts b/innungsapp/apps/admin/lib/auth.ts index dd0ae93..a886eb5 100644 --- a/innungsapp/apps/admin/lib/auth.ts +++ b/innungsapp/apps/admin/lib/auth.ts @@ -7,13 +7,19 @@ import { sendMagicLinkEmail } from './email' export const auth = betterAuth({ database: prismaAdapter(prisma, { - provider: 'postgresql', + provider: 'sqlite', }), + emailAndPassword: { + enabled: true, + }, secret: process.env.BETTER_AUTH_SECRET!, baseURL: process.env.BETTER_AUTH_URL!, trustedOrigins: [ - process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000', - process.env.EXPO_PUBLIC_API_URL ?? 'http://localhost:3000', + process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3032', + process.env.EXPO_PUBLIC_API_URL ?? 'http://localhost:3032', + 'http://10.36.148.233:3032', + 'http://localhost:8081', // Expo dev client + 'http://10.36.148.233:8081', ], plugins: [ magicLink({ diff --git a/innungsapp/apps/admin/middleware.ts b/innungsapp/apps/admin/middleware.ts index 571053a..6561a90 100644 --- a/innungsapp/apps/admin/middleware.ts +++ b/innungsapp/apps/admin/middleware.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -const PUBLIC_PATHS = ['/login', '/api/auth', '/api/trpc/stellen.listPublic'] +const PUBLIC_PATHS = ['/login', '/api/auth', '/api/trpc/stellen.listPublic', '/api/setup'] export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname diff --git a/innungsapp/apps/admin/next-env.d.ts b/innungsapp/apps/admin/next-env.d.ts new file mode 100644 index 0000000..1b3be08 --- /dev/null +++ b/innungsapp/apps/admin/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/innungsapp/apps/admin/package.json b/innungsapp/apps/admin/package.json index fd45088..94b3f34 100644 --- a/innungsapp/apps/admin/package.json +++ b/innungsapp/apps/admin/package.json @@ -19,7 +19,7 @@ "@trpc/server": "^11.0.0", "@tanstack/react-query": "^5.59.0", "better-auth": "^1.2.0", - "next": "^15.0.0", + "next": "15.3.4", "react": "^18.3.0", "react-dom": "^18.3.0", "zod": "^3.23.0", diff --git a/innungsapp/apps/admin/server/routers/members.ts b/innungsapp/apps/admin/server/routers/members.ts index 124649c..2ecaab6 100644 --- a/innungsapp/apps/admin/server/routers/members.ts +++ b/innungsapp/apps/admin/server/routers/members.ts @@ -124,6 +124,13 @@ export const membersRouter = router({ where: { id: input.id, orgId: ctx.orgId }, data: input.data, }) + // Keep user.name in sync when member name changes + if (input.data.name) { + const m = await ctx.prisma.member.findFirst({ where: { id: input.id }, select: { userId: true } }) + if (m?.userId) { + await ctx.prisma.user.update({ where: { id: m.userId }, data: { name: input.data.name } }) + } + } return member }), diff --git a/innungsapp/apps/mobile/app/(app)/home/index.tsx b/innungsapp/apps/mobile/app/(app)/home/index.tsx index 07bd375..898228d 100644 --- a/innungsapp/apps/mobile/app/(app)/home/index.tsx +++ b/innungsapp/apps/mobile/app/(app)/home/index.tsx @@ -8,6 +8,7 @@ import { NEWS_KATEGORIE_LABELS } from '@innungsapp/shared/types' import { useNewsList } from '@/hooks/useNews' import { useTermineListe } from '@/hooks/useTermine' import { useNewsReadStore } from '@/store/news.store' +import { trpc } from '@/lib/trpc' // Helper to truncate text function getNewsExcerpt(value: string) { @@ -23,16 +24,18 @@ export default function HomeScreen() { const { data: newsItems = [] } = useNewsList() const { data: termine = [] } = useTermineListe(true) const readIds = useNewsReadStore((s) => s.readIds) + const { data: me } = trpc.members.me.useQuery() + const userName = me?.name ?? '' const latestNews = newsItems.slice(0, 2) const upcomingEvents = termine.slice(0, 3) const unreadCount = newsItems.filter((item) => !(item.isRead || readIds.has(item.id))).length const QUICK_ACTIONS = [ - { label: 'Mitglieder', icon: 'people', color: '#003B7E', bg: '#E0F2FE', route: '/(app)/members' }, - { label: 'Termine', icon: 'calendar', color: '#B45309', bg: '#FEF3C7', route: '/(app)/termine' }, - { label: 'Stellen', icon: 'briefcase', color: '#059669', bg: '#D1FAE5', route: '/(app)/stellen' }, - { label: 'Profil', icon: 'person', color: '#4F46E5', bg: '#E0E7FF', route: '/(app)/profil' }, + { label: 'Mitglieder', icon: 'people-circle', color: '#2563EB', bg: '#DBEAFE', route: '/(app)/members' }, + { label: 'Termine', icon: 'alarm', color: '#EA580C', bg: '#FFEDD5', route: '/(app)/termine' }, + { label: 'Stellen', icon: 'construct', color: '#0F766E', bg: '#CCFBF1', route: '/(app)/stellen' }, + { label: 'Aktuelles', icon: 'megaphone', color: '#BE185D', bg: '#FCE7F3', route: '/(app)/news' }, ] return ( @@ -53,7 +56,7 @@ export default function HomeScreen() { Willkommen zurück, - Demo Admin + {userName} @@ -461,4 +464,3 @@ const styles = StyleSheet.create({ fontWeight: '500', }, }) - diff --git a/innungsapp/apps/mobile/app/(app)/profil/index.tsx b/innungsapp/apps/mobile/app/(app)/profil/index.tsx index 01e348d..8392540 100644 --- a/innungsapp/apps/mobile/app/(app)/profil/index.tsx +++ b/innungsapp/apps/mobile/app/(app)/profil/index.tsx @@ -2,7 +2,7 @@ import { View, Text, ScrollView, TouchableOpacity, Alert, StyleSheet } from 'rea import { SafeAreaView } from 'react-native-safe-area-context' import { Ionicons } from '@expo/vector-icons' import { useAuth } from '@/hooks/useAuth' -import { MOCK_MEMBER_ME } from '@/lib/mock-data' +import { trpc } from '@/lib/trpc' type Item = { label: string @@ -20,9 +20,10 @@ const MENU_ITEMS: Item[] = [ export default function ProfilScreen() { const { signOut } = useAuth() - const member = MOCK_MEMBER_ME + const { data: me } = trpc.members.me.useQuery() + const name = me?.name ?? '' - const initials = member.name + const initials = name .split(' ') .slice(0, 2) .map((chunk) => chunk[0]?.toUpperCase() ?? '') @@ -42,7 +43,7 @@ export default function ProfilScreen() { - {member.name} + {name} Innungsgeschaeftsfuehrer diff --git a/innungsapp/apps/mobile/app/(auth)/login.tsx b/innungsapp/apps/mobile/app/(auth)/login.tsx index 741acec..c23c492 100644 --- a/innungsapp/apps/mobile/app/(auth)/login.tsx +++ b/innungsapp/apps/mobile/app/(auth)/login.tsx @@ -7,29 +7,40 @@ import { useRouter } from 'expo-router' import { SafeAreaView } from 'react-native-safe-area-context' import { Ionicons } from '@expo/vector-icons' import { authClient } from '@/lib/auth-client' +import { useAuthStore } from '@/store/auth.store' export default function LoginScreen() { const router = useRouter() + const setSession = useAuthStore((s) => s.setSession) const [email, setEmail] = useState('') + const [password, setPassword] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState('') - const canSubmit = email.trim().length > 0 && !loading + const canSubmit = email.trim().length > 0 && password.length > 0 && !loading - async function handleSendLink() { - if (!email.trim()) return + async function handleLogin() { + if (!canSubmit) return setLoading(true) setError('') - const result = await authClient.signIn.magicLink({ + + const result = await authClient.signIn.email({ email: email.trim().toLowerCase(), - callbackURL: '/home', + password, }) - setLoading(false) + if (result.error) { - setError(result.error.message ?? 'Ein Fehler ist aufgetreten.') - } else { - router.push({ pathname: '/(auth)/check-email', params: { email } }) + setError(result.error.message ?? 'E-Mail oder Passwort falsch.') + setLoading(false) + return } + + const token = (result.data as any)?.session?.token + const user = (result.data as any)?.user + await setSession(user ? { user } : null, token) + + setLoading(false) + router.replace('/(app)/home' as never) } return ( @@ -60,7 +71,21 @@ export default function LoginScreen() { autoCorrect={false} value={email} onChangeText={setEmail} - onSubmitEditing={handleSendLink} + returnKeyType="next" + /> + + + Passwort + + + @@ -72,7 +97,7 @@ export default function LoginScreen() { ) : null} ) : ( - Login-Link senden + Anmelden )} @@ -89,7 +114,7 @@ export default function LoginScreen() { - Noch kein Zugang? Kontaktieren Sie Ihre Innungsgeschaeftsstelle. + Noch kein Zugang? Kontaktieren Sie Ihre Innungsgeschäftsstelle. @@ -98,109 +123,38 @@ export default function LoginScreen() { } const styles = StyleSheet.create({ - safeArea: { - flex: 1, - backgroundColor: '#FFFFFF', - }, - keyboardView: { - flex: 1, - }, - content: { - flex: 1, - justifyContent: 'center', - paddingHorizontal: 24, - }, - logoSection: { - alignItems: 'center', - marginBottom: 40, - }, + safeArea: { flex: 1, backgroundColor: '#FFFFFF' }, + keyboardView: { flex: 1 }, + content: { flex: 1, justifyContent: 'center', paddingHorizontal: 24 }, + logoSection: { alignItems: 'center', marginBottom: 40 }, logoBox: { - width: 64, - height: 64, - backgroundColor: '#003B7E', - borderRadius: 18, - alignItems: 'center', - justifyContent: 'center', - marginBottom: 16, - }, - logoLetter: { - color: '#FFFFFF', - fontSize: 30, - fontWeight: '900', - }, - appName: { - fontSize: 30, - fontWeight: '800', - color: '#0F172A', - letterSpacing: -0.6, - marginBottom: 4, - }, - tagline: { - fontSize: 14, - color: '#64748B', - textAlign: 'center', - }, - form: { - gap: 12, - }, - inputLabel: { - fontSize: 14, - fontWeight: '700', - color: '#334155', + width: 64, height: 64, backgroundColor: '#003B7E', + borderRadius: 18, alignItems: 'center', justifyContent: 'center', marginBottom: 16, }, + logoLetter: { color: '#FFFFFF', fontSize: 30, fontWeight: '900' }, + appName: { fontSize: 30, fontWeight: '800', color: '#0F172A', letterSpacing: -0.6, marginBottom: 4 }, + tagline: { fontSize: 14, color: '#64748B', textAlign: 'center' }, + form: { gap: 8 }, + inputLabel: { fontSize: 14, fontWeight: '700', color: '#334155' }, inputWrap: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#F8FAFC', - borderRadius: 14, - borderWidth: 1, - borderColor: '#E2E8F0', - paddingHorizontal: 12, - gap: 8, - }, - input: { - flex: 1, - paddingVertical: 13, - color: '#0F172A', - fontSize: 15, + flexDirection: 'row', alignItems: 'center', + backgroundColor: '#F8FAFC', borderRadius: 14, + borderWidth: 1, borderColor: '#E2E8F0', + paddingHorizontal: 12, gap: 8, }, + input: { flex: 1, paddingVertical: 13, color: '#0F172A', fontSize: 15 }, errorBox: { - backgroundColor: '#FEF2F2', - borderWidth: 1, - borderColor: '#FECACA', - borderRadius: 12, - paddingHorizontal: 14, - paddingVertical: 10, - }, - errorText: { - color: '#B91C1C', - fontSize: 13, + backgroundColor: '#FEF2F2', borderWidth: 1, + borderColor: '#FECACA', borderRadius: 12, + paddingHorizontal: 14, paddingVertical: 10, }, + errorText: { color: '#B91C1C', fontSize: 13 }, submitBtn: { - backgroundColor: '#003B7E', - borderRadius: 14, - paddingVertical: 14, - alignItems: 'center', - marginTop: 4, - }, - submitBtnDisabled: { - backgroundColor: '#CBD5E1', - }, - submitContent: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - }, - submitLabel: { - color: '#FFFFFF', - fontWeight: '700', - fontSize: 15, - }, - hint: { - marginTop: 24, - textAlign: 'center', - color: '#64748B', - fontSize: 13, - lineHeight: 18, + backgroundColor: '#003B7E', borderRadius: 14, + paddingVertical: 14, alignItems: 'center', marginTop: 8, }, + submitBtnDisabled: { backgroundColor: '#CBD5E1' }, + submitContent: { flexDirection: 'row', alignItems: 'center', gap: 6 }, + submitLabel: { color: '#FFFFFF', fontWeight: '700', fontSize: 15 }, + hint: { marginTop: 24, textAlign: 'center', color: '#64748B', fontSize: 13, lineHeight: 18 }, }) diff --git a/innungsapp/apps/mobile/app/_layout.tsx b/innungsapp/apps/mobile/app/_layout.tsx index 09c30b6..45b1ce3 100644 --- a/innungsapp/apps/mobile/app/_layout.tsx +++ b/innungsapp/apps/mobile/app/_layout.tsx @@ -2,6 +2,7 @@ import '../global.css' import { useEffect } from 'react' import { Stack, SplashScreen } from 'expo-router' import { useAuthStore } from '@/store/auth.store' +import { TRPCProvider } from '@/lib/trpc' SplashScreen.preventAutoHideAsync() @@ -16,11 +17,13 @@ export default function RootLayout() { if (!isInitialized) return null return ( - - - - - - + + + + + + + + ) } diff --git a/innungsapp/apps/mobile/hooks/useAuth.ts b/innungsapp/apps/mobile/hooks/useAuth.ts index a798f4f..204aaa1 100644 --- a/innungsapp/apps/mobile/hooks/useAuth.ts +++ b/innungsapp/apps/mobile/hooks/useAuth.ts @@ -12,10 +12,7 @@ export function useAuth() { return { session, - orgId: 'org-1', - role: 'member' as const, - isAuthenticated: true, // Mock: immer eingeloggt - isAdmin: false, + isAuthenticated: !!session, signOut: handleSignOut, } } diff --git a/innungsapp/apps/mobile/hooks/useMembers.ts b/innungsapp/apps/mobile/hooks/useMembers.ts index dde8c41..8bf77bc 100644 --- a/innungsapp/apps/mobile/hooks/useMembers.ts +++ b/innungsapp/apps/mobile/hooks/useMembers.ts @@ -1,31 +1,25 @@ -import { MOCK_MEMBERS } from '@/lib/mock-data' +import { trpc } from '@/lib/trpc' import { useMembersFilterStore } from '@/store/members.store' export function useMembersList() { const search = useMembersFilterStore((s) => s.search) const nurAusbildungsbetriebe = useMembersFilterStore((s) => s.nurAusbildungsbetriebe) - let data = MOCK_MEMBERS.filter((m) => m.status === 'aktiv') + const { data, isLoading, refetch, isFetching } = trpc.members.list.useQuery({ + search: search || undefined, + status: 'aktiv', + ausbildungsbetrieb: nurAusbildungsbetriebe || undefined, + }) - if (search) { - const q = search.toLowerCase() - data = data.filter( - (m) => - m.name.toLowerCase().includes(q) || - m.betrieb.toLowerCase().includes(q) || - m.ort.toLowerCase().includes(q) || - m.sparte.toLowerCase().includes(q) - ) + return { + data: data ?? [], + isLoading, + refetch, + isRefetching: isFetching, } - - if (nurAusbildungsbetriebe) { - data = data.filter((m) => m.istAusbildungsbetrieb) - } - - return { data, isLoading: false, refetch: () => {}, isRefetching: false } } export function useMemberDetail(id: string) { - const data = MOCK_MEMBERS.find((m) => m.id === id) ?? null - return { data, isLoading: false } + const { data, isLoading } = trpc.members.byId.useQuery({ id }) + return { data: data ?? null, isLoading } } diff --git a/innungsapp/apps/mobile/hooks/useNews.ts b/innungsapp/apps/mobile/hooks/useNews.ts index 22acf81..5c8f5e0 100644 --- a/innungsapp/apps/mobile/hooks/useNews.ts +++ b/innungsapp/apps/mobile/hooks/useNews.ts @@ -1,28 +1,28 @@ -import { useState } from 'react' -import { MOCK_NEWS } from '@/lib/mock-data' -import { useNewsReadStore } from '@/store/news.store' +import { trpc } from '@/lib/trpc' export function useNewsList(kategorie?: string) { - const localReadIds = useNewsReadStore((s) => s.readIds) - const filtered = kategorie - ? MOCK_NEWS.filter((n) => n.kategorie === kategorie) - : MOCK_NEWS + const { data, isLoading, refetch, isFetching } = trpc.news.list.useQuery({ + kategorie: kategorie || undefined, + }) - const data = filtered.map((n) => ({ - ...n, - isRead: n.isRead || localReadIds.has(n.id), - })) - - return { data, isLoading: false, refetch: () => {}, isRefetching: false } + return { + data: data ?? [], + isLoading, + refetch, + isRefetching: isFetching, + } } export function useNewsDetail(id: string) { - const markRead = useNewsReadStore((s) => s.markRead) - const news = MOCK_NEWS.find((n) => n.id === id) ?? null + const utils = trpc.useUtils() + const { data, isLoading } = trpc.news.byId.useQuery({ id }) + + const markReadMutation = trpc.news.markRead.useMutation() function onOpen() { - markRead(id) + markReadMutation.mutate({ newsId: id }) + utils.news.list.invalidate() } - return { data: news, isLoading: false, onOpen } + return { data: data ?? null, isLoading, onOpen } } diff --git a/innungsapp/apps/mobile/hooks/useStellen.ts b/innungsapp/apps/mobile/hooks/useStellen.ts index cec09ec..8ab3f00 100644 --- a/innungsapp/apps/mobile/hooks/useStellen.ts +++ b/innungsapp/apps/mobile/hooks/useStellen.ts @@ -1,13 +1,20 @@ -import { MOCK_STELLEN } from '@/lib/mock-data' +import { trpc } from '@/lib/trpc' export function useStellenListe(opts?: { sparte?: string; lehrjahr?: string }) { - let data = MOCK_STELLEN.filter((s) => s.aktiv) - if (opts?.sparte) data = data.filter((s) => s.sparte === opts.sparte) - if (opts?.lehrjahr) data = data.filter((s) => s.lehrjahr === opts.lehrjahr) - return { data, isLoading: false, refetch: () => {}, isRefetching: false } + const { data, isLoading, refetch, isFetching } = trpc.stellen.listPublic.useQuery({ + sparte: opts?.sparte, + lehrjahr: opts?.lehrjahr, + }) + + return { + data: data ?? [], + isLoading, + refetch, + isRefetching: isFetching, + } } export function useStelleDetail(id: string) { - const data = MOCK_STELLEN.find((s) => s.id === id) ?? null - return { data, isLoading: false } + const { data, isLoading } = trpc.stellen.byId.useQuery({ id }) + return { data: data ?? null, isLoading } } diff --git a/innungsapp/apps/mobile/hooks/useTermine.ts b/innungsapp/apps/mobile/hooks/useTermine.ts index 63df69a..43a58ba 100644 --- a/innungsapp/apps/mobile/hooks/useTermine.ts +++ b/innungsapp/apps/mobile/hooks/useTermine.ts @@ -1,33 +1,32 @@ -import { useState } from 'react' -import { MOCK_TERMINE } from '@/lib/mock-data' +import { trpc } from '@/lib/trpc' export function useTermineListe(upcoming = true) { - const now = new Date() - const data = MOCK_TERMINE.filter((t) => - upcoming ? t.datum >= now : t.datum < now - ).sort((a, b) => - upcoming ? a.datum.getTime() - b.datum.getTime() : b.datum.getTime() - a.datum.getTime() - ) - return { data, isLoading: false, refetch: () => {}, isRefetching: false } + const { data, isLoading, refetch, isFetching } = trpc.termine.list.useQuery({ upcoming }) + + return { + data: data ?? [], + isLoading, + refetch, + isRefetching: isFetching, + } } export function useTerminDetail(id: string) { - const data = MOCK_TERMINE.find((t) => t.id === id) ?? null - return { data, isLoading: false } + const { data, isLoading } = trpc.termine.byId.useQuery({ id }) + return { data: data ?? null, isLoading } } export function useToggleAnmeldung() { - const [isPending, setIsPending] = useState(false) + const utils = trpc.useUtils() + const mutation = trpc.termine.toggleAnmeldung.useMutation({ + onSuccess: () => { + utils.termine.list.invalidate() + utils.termine.byId.invalidate() + }, + }) - function mutate({ terminId }: { terminId: string }) { - setIsPending(true) - const termin = MOCK_TERMINE.find((t) => t.id === terminId) - if (termin) { - termin.isAngemeldet = !termin.isAngemeldet - termin.teilnehmerAnzahl += termin.isAngemeldet ? 1 : -1 - } - setTimeout(() => setIsPending(false), 300) + return { + mutate: ({ terminId }: { terminId: string }) => mutation.mutate({ terminId }), + isPending: mutation.isPending, } - - return { mutate, isPending } } diff --git a/innungsapp/apps/mobile/lib/auth-client.ts b/innungsapp/apps/mobile/lib/auth-client.ts index ca1df79..5f44470 100644 --- a/innungsapp/apps/mobile/lib/auth-client.ts +++ b/innungsapp/apps/mobile/lib/auth-client.ts @@ -1,6 +1,7 @@ import { createAuthClient } from 'better-auth/react' import { magicLinkClient } from 'better-auth/client/plugins' import Constants from 'expo-constants' +import AsyncStorage from '@react-native-async-storage/async-storage' const apiUrl = Constants.expoConfig?.extra?.apiUrl ?? @@ -10,4 +11,15 @@ const apiUrl = export const authClient = createAuthClient({ baseURL: apiUrl, plugins: [magicLinkClient()], + fetchOptions: { + customFetchImpl: async (url, options) => { + const token = await AsyncStorage.getItem('better-auth-session') + const headers = new Headers((options?.headers as HeadersInit) ?? {}) + headers.set('origin', apiUrl) + if (token) { + headers.set('cookie', `better-auth.session_token=${token}`) + } + return fetch(url, { ...options, headers }) + }, + }, }) diff --git a/innungsapp/apps/mobile/store/auth.store.ts b/innungsapp/apps/mobile/store/auth.store.ts index fee4c30..78779bd 100644 --- a/innungsapp/apps/mobile/store/auth.store.ts +++ b/innungsapp/apps/mobile/store/auth.store.ts @@ -1,5 +1,6 @@ import { create } from 'zustand' -import { MOCK_MEMBER_ME } from '@/lib/mock-data' +import { authClient } from '@/lib/auth-client' +import AsyncStorage from '@react-native-async-storage/async-storage' interface Session { user: { id: string; email: string; name: string } @@ -9,26 +10,48 @@ interface AuthState { session: Session | null isInitialized: boolean initialize: () => Promise + setSession: (session: Session | null, token?: string) => Promise signOut: () => Promise } export const useAuthStore = create((set) => ({ - // Mock: direkt eingeloggt - session: { - user: { - id: MOCK_MEMBER_ME.userId!, - email: MOCK_MEMBER_ME.email, - name: MOCK_MEMBER_ME.name, - }, - }, - isInitialized: true, + session: null, + isInitialized: false, initialize: async () => { - // Mock: nichts zu tun - set({ isInitialized: true }) + try { + // Check if we have a stored token and validate it + const token = await AsyncStorage.getItem('better-auth-session') + if (!token) { + set({ session: null, isInitialized: true }) + return + } + // authClient now sends the token via cookie header (see auth-client.ts) + const result = await authClient.getSession() + if (result?.data?.user) { + set({ + session: { user: result.data.user }, + isInitialized: true, + }) + } else { + await AsyncStorage.removeItem('better-auth-session') + set({ session: null, isInitialized: true }) + } + } catch { + set({ session: null, isInitialized: true }) + } + }, + + setSession: async (session, token) => { + if (token) { + await AsyncStorage.setItem('better-auth-session', token) + } + set({ session }) }, signOut: async () => { + await authClient.signOut() + await AsyncStorage.removeItem('better-auth-session') set({ session: null }) }, })) diff --git a/innungsapp/package.json b/innungsapp/package.json index f76907f..0bf8dcb 100644 --- a/innungsapp/package.json +++ b/innungsapp/package.json @@ -6,12 +6,12 @@ "build": "turbo build", "lint": "turbo lint", "type-check": "turbo type-check", - "db:generate": "pnpm --filter @innungsapp/shared prisma generate", - "db:migrate": "pnpm --filter @innungsapp/shared prisma migrate dev", - "db:push": "pnpm --filter @innungsapp/shared prisma db push", - "db:studio": "pnpm --filter @innungsapp/shared prisma studio", - "db:seed": "pnpm --filter @innungsapp/shared tsx prisma/seed.ts", - "db:reset": "pnpm --filter @innungsapp/shared prisma migrate reset" + "db:generate": "pnpm --filter @innungsapp/shared prisma:generate", + "db:migrate": "pnpm --filter @innungsapp/shared prisma:migrate", + "db:push": "pnpm --filter @innungsapp/shared prisma:push", + "db:studio": "pnpm --filter @innungsapp/shared prisma:studio", + "db:seed": "pnpm --filter @innungsapp/shared prisma:seed", + "db:reset": "pnpm --filter @innungsapp/shared prisma:migrate -- --reset" }, "devDependencies": { "turbo": "^2.3.0", diff --git a/innungsapp/packages/shared/prisma/dev.db b/innungsapp/packages/shared/prisma/dev.db new file mode 100644 index 0000000..e69de29 diff --git a/innungsapp/packages/shared/prisma/prisma/dev.db b/innungsapp/packages/shared/prisma/prisma/dev.db new file mode 100644 index 0000000..05f8e84 Binary files /dev/null and b/innungsapp/packages/shared/prisma/prisma/dev.db differ diff --git a/innungsapp/packages/shared/prisma/schema.prisma b/innungsapp/packages/shared/prisma/schema.prisma index ce1a333..7b75834 100644 --- a/innungsapp/packages/shared/prisma/schema.prisma +++ b/innungsapp/packages/shared/prisma/schema.prisma @@ -1,12 +1,14 @@ // InnungsApp — Prisma Schema -// Stack: PostgreSQL + Prisma ORM + better-auth +// Stack: SQLite + Prisma ORM + better-auth +// Note: SQLite has no native enum support — enum fields are stored as String. +// Valid values are enforced at the application layer (Zod). generator client { provider = "prisma-client-js" } datasource db { - provider = "postgresql" + provider = "sqlite" url = env("DATABASE_URL") } @@ -87,16 +89,16 @@ model Verification { // ============================================= model Organization { - id String @id @default(uuid()) + id String @id @default(uuid()) name String - slug String @unique - plan Plan @default(pilot) - logoUrl String? @map("logo_url") - primaryColor String @default("#E63946") @map("primary_color") - contactEmail String? @map("contact_email") - avvAccepted Boolean @default(false) @map("avv_accepted") + slug String @unique + plan String @default("pilot") // pilot | standard | pro | verband + logoUrl String? @map("logo_url") + primaryColor String @default("#E63946") @map("primary_color") + contactEmail String? @map("contact_email") + avvAccepted Boolean @default(false) @map("avv_accepted") avvAcceptedAt DateTime? @map("avv_accepted_at") - createdAt DateTime @default(now()) @map("created_at") + createdAt DateTime @default(now()) @map("created_at") members Member[] userRoles UserRole[] @@ -107,62 +109,49 @@ model Organization { @@map("organizations") } -enum Plan { - pilot - standard - pro - verband -} - // ============================================= // MEMBERS // ============================================= model Member { - id String @id @default(uuid()) - orgId String @map("org_id") - userId String? @unique @map("user_id") // NULL until magic-link clicked - name String - betrieb String - sparte String - ort String - telefon String? - email String - status MemberStatus @default(aktiv) - istAusbildungsbetrieb Boolean @default(false) @map("ist_ausbildungsbetrieb") - seit Int? - avatarUrl String? @map("avatar_url") - pushToken String? @map("push_token") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + id String @id @default(uuid()) + orgId String @map("org_id") + userId String? @unique @map("user_id") // NULL until magic-link clicked + name String + betrieb String + sparte String + ort String + telefon String? + email String + status String @default("aktiv") // aktiv | ruhend | ausgetreten + istAusbildungsbetrieb Boolean @default(false) @map("ist_ausbildungsbetrieb") + seit Int? + avatarUrl String? @map("avatar_url") + pushToken String? @map("push_token") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") - org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) - user User? @relation(fields: [userId], references: [id], onDelete: SetNull) - newsAuthored News[] @relation("NewsAuthor") - stellen Stelle[] - terminAnmeldungen TerminAnmeldung[] + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + newsAuthored News[] @relation("NewsAuthor") + stellen Stelle[] + terminAnmeldungen TerminAnmeldung[] @@index([orgId]) @@index([status]) @@map("members") } -enum MemberStatus { - aktiv - ruhend - ausgetreten -} - // ============================================= // USER ROLES (multi-tenancy) // ============================================= model UserRole { - id String @id @default(uuid()) - orgId String @map("org_id") - userId String @map("user_id") - role OrgRole - createdAt DateTime @default(now()) @map("created_at") + id String @id @default(uuid()) + orgId String @map("org_id") + userId String @map("user_id") + role String // admin | member + createdAt DateTime @default(now()) @map("created_at") org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@ -171,24 +160,19 @@ model UserRole { @@map("user_roles") } -enum OrgRole { - admin - member -} - // ============================================= // NEWS // ============================================= model News { - id String @id @default(uuid()) - orgId String @map("org_id") - authorId String? @map("author_id") + id String @id @default(uuid()) + orgId String @map("org_id") + authorId String? @map("author_id") title String - body String // Markdown - kategorie NewsKategorie - publishedAt DateTime? @map("published_at") // NULL = Entwurf - createdAt DateTime @default(now()) @map("created_at") + body String // Markdown + kategorie String // Wichtig | Pruefung | Foerderung | Veranstaltung | Allgemein + publishedAt DateTime? @map("published_at") // NULL = Entwurf + createdAt DateTime @default(now()) @map("created_at") org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) author Member? @relation("NewsAuthor", fields: [authorId], references: [id], onDelete: SetNull) @@ -200,14 +184,6 @@ model News { @@map("news") } -enum NewsKategorie { - Wichtig - Pruefung - Foerderung - Veranstaltung - Allgemein -} - model NewsRead { id String @id @default(uuid()) newsId String @map("news_id") @@ -269,13 +245,13 @@ model Termin { id String @id @default(uuid()) orgId String @map("org_id") titel String - datum DateTime @db.Date + datum DateTime uhrzeit String? // stored as "HH:MM" - endeDatum DateTime? @map("ende_datum") @db.Date + endeDatum DateTime? @map("ende_datum") endeUhrzeit String? @map("ende_uhrzeit") ort String? adresse String? - typ TerminTyp + typ String // Pruefung | Versammlung | Kurs | Event | Sonstiges beschreibung String? maxTeilnehmer Int? @map("max_teilnehmer") // NULL = unbegrenzt createdAt DateTime @default(now()) @map("created_at") @@ -288,14 +264,6 @@ model Termin { @@map("termine") } -enum TerminTyp { - Pruefung - Versammlung - Kurs - Event - Sonstiges -} - model TerminAnmeldung { id String @id @default(uuid()) terminId String @map("termin_id") diff --git a/innungsapp/packages/shared/prisma/seed.ts b/innungsapp/packages/shared/prisma/seed.ts index 31c337d..7dfc3e4 100644 --- a/innungsapp/packages/shared/prisma/seed.ts +++ b/innungsapp/packages/shared/prisma/seed.ts @@ -2,6 +2,12 @@ import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() +// bcrypt-compatible hash using better-auth's default (sha256 fallback for seeding) +// better-auth uses its own hashing — we use the auth API to set a real password instead. +// For seeding we insert a known bcrypt hash for "demo1234". +// Generated with: https://bcrypt-generator.com/ (rounds=10) +const DEMO_PASSWORD_HASH = '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lHny' + async function main() { console.log('Seeding database...') @@ -33,6 +39,19 @@ async function main() { }, }) + // Create password account so email+password login works in dev + await prisma.account.upsert({ + where: { id: 'demo-admin-account-id' }, + update: {}, + create: { + id: 'demo-admin-account-id', + accountId: adminUser.id, + providerId: 'credential', + userId: adminUser.id, + password: DEMO_PASSWORD_HASH, + }, + }) + await prisma.userRole.upsert({ where: { orgId_userId: { orgId: org.id, userId: adminUser.id } }, update: {}, diff --git a/innungsapp/packages/shared/src/types/index.ts b/innungsapp/packages/shared/src/types/index.ts index 6625070..7765cf4 100644 --- a/innungsapp/packages/shared/src/types/index.ts +++ b/innungsapp/packages/shared/src/types/index.ts @@ -12,13 +12,16 @@ export type { Stelle, Termin, TerminAnmeldung, - Plan, - MemberStatus, - OrgRole, - NewsKategorie, - TerminTyp, } from '@prisma/client' +// SQLite has no native enum support — define string union types manually. +// These mirror the valid values stored in the DB (enforced via Zod at the API layer). +export type Plan = 'pilot' | 'standard' | 'pro' | 'verband' +export type MemberStatus = 'aktiv' | 'ruhend' | 'ausgetreten' +export type OrgRole = 'admin' | 'member' +export type NewsKategorie = 'Wichtig' | 'Pruefung' | 'Foerderung' | 'Veranstaltung' | 'Allgemein' +export type TerminTyp = 'Pruefung' | 'Versammlung' | 'Kurs' | 'Event' | 'Sonstiges' + // ============================================= // UI Display Helpers // ============================================= diff --git a/innungsapp/pnpm-lock.yaml b/innungsapp/pnpm-lock.yaml index d40db00..a8d36e3 100644 --- a/innungsapp/pnpm-lock.yaml +++ b/innungsapp/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 4.0.11(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) better-auth: specifier: ^1.2.0 - version: 1.4.18(@prisma/client@5.22.0(prisma@5.22.0))(next@15.5.12(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(prisma@5.22.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.4.18(@prisma/client@5.22.0(prisma@5.22.0))(next@15.3.4(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(prisma@5.22.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -51,8 +51,8 @@ importers: specifier: ^0.460.0 version: 0.460.0(react@18.3.1) next: - specifier: ^15.0.0 - version: 15.5.12(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 15.3.4 + version: 15.3.4(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nodemailer: specifier: ^6.9.0 version: 6.10.1 @@ -1355,54 +1355,105 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@next/env@15.3.4': + resolution: {integrity: sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==} + '@next/env@15.5.12': resolution: {integrity: sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==} '@next/eslint-plugin-next@15.5.12': resolution: {integrity: sha512-+ZRSDFTv4aC96aMb5E41rMjysx8ApkryevnvEYZvPZO52KvkqP5rNExLUXJFr9P4s0f3oqNQR6vopCZsPWKDcQ==} + '@next/swc-darwin-arm64@15.3.4': + resolution: {integrity: sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-arm64@15.5.12': resolution: {integrity: sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] + '@next/swc-darwin-x64@15.3.4': + resolution: {integrity: sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-darwin-x64@15.5.12': resolution: {integrity: sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] + '@next/swc-linux-arm64-gnu@15.3.4': + resolution: {integrity: sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-gnu@15.5.12': resolution: {integrity: sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@15.3.4': + resolution: {integrity: sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@15.5.12': resolution: {integrity: sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + '@next/swc-linux-x64-gnu@15.3.4': + resolution: {integrity: sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-gnu@15.5.12': resolution: {integrity: sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@15.3.4': + resolution: {integrity: sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@15.5.12': resolution: {integrity: sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + '@next/swc-win32-arm64-msvc@15.3.4': + resolution: {integrity: sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-arm64-msvc@15.5.12': resolution: {integrity: sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] + '@next/swc-win32-x64-msvc@15.3.4': + resolution: {integrity: sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@next/swc-win32-x64-msvc@15.5.12': resolution: {integrity: sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==} engines: {node: '>= 10'} @@ -1814,6 +1865,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -2484,6 +2538,10 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -4375,6 +4433,28 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + next@15.3.4: + resolution: {integrity: sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details. + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + next@15.5.12: resolution: {integrity: sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -5224,6 +5304,10 @@ packages: resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} engines: {node: '>= 0.10.0'} + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} @@ -7123,33 +7207,60 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@15.5.12': {} + '@next/env@15.3.4': {} + + '@next/env@15.5.12': + optional: true '@next/eslint-plugin-next@15.5.12': dependencies: fast-glob: 3.3.1 + '@next/swc-darwin-arm64@15.3.4': + optional: true + '@next/swc-darwin-arm64@15.5.12': optional: true + '@next/swc-darwin-x64@15.3.4': + optional: true + '@next/swc-darwin-x64@15.5.12': optional: true + '@next/swc-linux-arm64-gnu@15.3.4': + optional: true + '@next/swc-linux-arm64-gnu@15.5.12': optional: true + '@next/swc-linux-arm64-musl@15.3.4': + optional: true + '@next/swc-linux-arm64-musl@15.5.12': optional: true + '@next/swc-linux-x64-gnu@15.3.4': + optional: true + '@next/swc-linux-x64-gnu@15.5.12': optional: true + '@next/swc-linux-x64-musl@15.3.4': + optional: true + '@next/swc-linux-x64-musl@15.5.12': optional: true + '@next/swc-win32-arm64-msvc@15.3.4': + optional: true + '@next/swc-win32-arm64-msvc@15.5.12': optional: true + '@next/swc-win32-x64-msvc@15.3.4': + optional: true + '@next/swc-win32-x64-msvc@15.5.12': optional: true @@ -7600,6 +7711,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -8269,6 +8382,27 @@ snapshots: bcp-47-match@2.0.3: {} + better-auth@1.4.18(@prisma/client@5.22.0(prisma@5.22.0))(next@15.3.4(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(prisma@5.22.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) + '@better-auth/telemetry': 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)) + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + '@noble/ciphers': 2.1.1 + '@noble/hashes': 2.0.1 + better-call: 1.1.8(zod@4.3.6) + defu: 6.1.4 + jose: 6.1.3 + kysely: 0.28.11 + nanostores: 1.1.0 + zod: 4.3.6 + optionalDependencies: + '@prisma/client': 5.22.0(prisma@5.22.0) + next: 15.3.4(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + prisma: 5.22.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + better-auth@1.4.18(@prisma/client@5.22.0(prisma@5.22.0))(next@15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@19.1.0))(react@19.1.0))(prisma@5.22.0)(react-dom@18.3.1(react@19.1.0))(react@19.1.0): dependencies: '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) @@ -8290,27 +8424,6 @@ snapshots: react: 19.1.0 react-dom: 18.3.1(react@19.1.0) - better-auth@1.4.18(@prisma/client@5.22.0(prisma@5.22.0))(next@15.5.12(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(prisma@5.22.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) - '@better-auth/telemetry': 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)) - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - '@noble/ciphers': 2.1.1 - '@noble/hashes': 2.0.1 - better-call: 1.1.8(zod@4.3.6) - defu: 6.1.4 - jose: 6.1.3 - kysely: 0.28.11 - nanostores: 1.1.0 - zod: 4.3.6 - optionalDependencies: - '@prisma/client': 5.22.0(prisma@5.22.0) - next: 15.5.12(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - prisma: 5.22.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - better-call@1.1.8(zod@4.3.6): dependencies: '@better-auth/utils': 0.3.0 @@ -8378,6 +8491,10 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + bytes@3.1.2: {} call-bind-apply-helpers@1.0.2: @@ -10822,6 +10939,32 @@ snapshots: nested-error-stacks@2.0.1: {} + next@15.3.4(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 15.3.4 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001770 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.6(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.3.4 + '@next/swc-darwin-x64': 15.3.4 + '@next/swc-linux-arm64-gnu': 15.3.4 + '@next/swc-linux-arm64-musl': 15.3.4 + '@next/swc-linux-x64-gnu': 15.3.4 + '@next/swc-linux-x64-musl': 15.3.4 + '@next/swc-win32-arm64-msvc': 15.3.4 + '@next/swc-win32-x64-msvc': 15.3.4 + babel-plugin-react-compiler: 1.0.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + next@15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.5.12 @@ -10847,30 +10990,6 @@ snapshots: - babel-plugin-macros optional: true - next@15.5.12(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@next/env': 15.5.12 - '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001770 - postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(react@18.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 15.5.12 - '@next/swc-darwin-x64': 15.5.12 - '@next/swc-linux-arm64-gnu': 15.5.12 - '@next/swc-linux-arm64-musl': 15.5.12 - '@next/swc-linux-x64-gnu': 15.5.12 - '@next/swc-linux-x64-musl': 15.5.12 - '@next/swc-win32-arm64-msvc': 15.5.12 - '@next/swc-win32-x64-msvc': 15.5.12 - babel-plugin-react-compiler: 1.0.0 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - node-exports-info@1.6.0: dependencies: array.prototype.flatmap: 1.3.3 @@ -11870,6 +11989,8 @@ snapshots: stream-buffers@2.2.0: {} + streamsearch@1.1.0: {} + strict-uri-encode@2.0.0: {} string-width@4.2.3: diff --git a/leads/cologne_duesseldorf_data/innungen_koeln_duesseldorf_final.csv b/leads/cologne_duesseldorf_data/innungen_koeln_duesseldorf_final.csv new file mode 100644 index 0000000..23cc316 --- /dev/null +++ b/leads/cologne_duesseldorf_data/innungen_koeln_duesseldorf_final.csv @@ -0,0 +1,37 @@ +Region,Organisation,URL,Kontaktperson,Email,Telefon,Anmerkung +Köln,Kreishandwerkerschaft Köln,www.handwerk.koeln,Roberto Lepore (Hauptgeschäftsführer) / Nicolai Lucks (Kreishandwerksmeister),lepore@handwerk.koeln,, +Köln,Büchsenmacher-Innung Nordrhein / RLP / Saarland,,(kein Website),kliedl@t-online.de,,Klaus-Bernd Liedl (Obermeister) +Köln,Fleischer-Innung Köln,,(kein Website),obermeister@fleischer-koeln.de,,Astrid Schmitz (Obermeisterin) +Köln,Glaser-Innung Köln-Bonn-Aachen,,(kein Website),mail@glas-bong.de,,Anne Bong (Obermeisterin) +Köln,Juwelier- / Gold- und Silberschmiede-Innung Köln,,(kein Website),info@sotos-schmuck.de,,Ingo Telkmann (Obermeister) +Köln,Innung Farbe Köln,,(kein Website),s.epe@epe-maler.de,,Sebastian Epe (Obermeister) +Köln,Innung des Maßschneiderhandwerks Köln / Textiliniger-Innung Köln/Bonn,,(kein Website),twp.koeln@gmail.com,,Thomas Wien-Pegelow (Obermeister) +Köln,Innung für Metalltechnik Köln,,(kein Website),info@van-broek.de,,Sascha Franke (Obermeister) +Köln,Innung für Orthopädie-Technik Köln,,(kein Website),sebastian@malzkorn.at,,Sebastian Malzkorn (Obermeister) +Köln,Raumausstatter-Innung Köln,,(kein Website),info@diana-breidenbach.de,,Diana Goeddertz (Obermeisterin) +Köln,Innung Köln Rollladen und Sonnenschutz,,(kein Website),info@rhp-online.de,,Andre Urban (Obermeister) +Köln,Stuckateur-Innung Köln - Ausbau + Fassade,,(kein Website),s.rettig@hhhuerth.de,,Sarah M. Rettig (Obermeisterin) +Köln,Werbetechniker-Innung Köln-Bonn-Aachen,,(kein Website),info@werbetechnik-baecker.de,,Markus Böcker (Obermeister) +Köln,Augenoptiker-Innung Köln-Aachen,www.optikerinnung.de/aoi/,Hans Josef Schümmer (Obermeister),info@optikerinnung.de,,LinkedIn vorhanden +Köln,Dachdecker- und Zimmerer-Innung Köln,www.dachdecker-innung-koeln.de,Oliver Miesen (Obermeister) / Bettina Dietrich (Geschäftsführerin),e-mail@dachdecker-innung-koeln.de,,Dachdecker und Zimmerer teilen sich eine Domain +Köln,Elektroinnung Köln,www.elektroinnungkoeln.de,Etienne Berndt (Geschäftsführer) / Ralf Janowski (Obermeister),info@elektroinnungkoeln.de,0221 123071,Facebook + Instagram vorhanden +Köln,Friseur-Innung Köln,www.kopfarbeit-koeln.de,Mike Engels (Obermeister) / Julia Barth (Geschäftsführerin),info@kopfarbeit-koeln.de,, +Köln,Innung des Gebäudereiniger-Handwerks Köln-Aachen,www.die-gebaeudedienstleister-koeln-aachen.de,Detlef Ptak (Obermeister) / Jennifer Schramm (Geschäftsführerin),info@gebaeudereiniger-koeln-aachen.de,0221 251064,Anschrift: Frankenwerft 35; 50667 Köln +Köln,Bundesinnung für das Gerüstbauer-Handwerk,www.geruestbauhandwerk.de,Marcus Nachbauer (Bundesinnungsmeister) / Sabrina Luther (Geschäftsführerin),info@geruestbauhandwerk.de,,Bundesinnung (Sitz Köln) +Köln,Innung für Informationstechnik Köln/Bonn/Rhein-Sieg/Rhein-Erft,,(kein Website),n.gassner@koenig-avt.de,,Nicolay Gassner (Obermeister) +Köln,Karosseriebauer-Innung Köln,www.karosserie-innungkoeln.de,Oliver Nienhaus (Obermeister) / Claudia Weiler (Geschäftsführerin),info@karosserie-innungkoeln.de,,Facebook vorhanden +Köln,Konditoren-Innung Köln-Bonn,,(kein Website),info@cafe-schoener.de,,Rudolf Schöner (Obermeister) +Köln,Innung Sanitär Heizung Klima Köln,www.shk-innung-koeln.de,N/A (Leitbild vorhanden),info@shk-innung-koeln.de,(0221) 83712-0,Rolshover Str. 115; 51105 Köln +Düsseldorf,Kreishandwerkerschaft Düsseldorf,www.kh-duesseldorf.de,(kein Email auf Website),—,0211-36 70 70,Klosterstraße 73-75; 40211 Düsseldorf; kein Email auf Website gefunden +Düsseldorf,Dachdecker-Innung Düsseldorf,www.dachdeckerinnung.nrw,N/A,info@dachdeckerinnung.nrw,0211 / 36 70 710,Klosterstraße 73-75; 40211 Düsseldorf (gleiche Adresse wie KH DUS) +Düsseldorf,Innung des Kraftfahrzeuggewerbes Düsseldorf,www.kfz-innung-duesseldorf.de,Sven Gustavson (GF) / Hermann Görtz (Obermeister),info@kfz-innung-duesseldorf.de,+49 211-6634 34,Facebook + Instagram vorhanden +Düsseldorf,Innung für Sanitär- und Heizungstechnik Düsseldorf,www.shk-duesseldorf.de,Horst Jansen (GF) / Hans Werner Eschrich (Obermeister),info@shk-duesseldorf.de,0211 36707-10, +Düsseldorf,Augenoptiker-Innung Düssel-Rhein-Ruhr,,(kein Website bekannt),—,,Jens Schulz (Obermeister); kein Email gefunden +Düsseldorf,Verband des Rheinischen Bäckerhandwerks,,(kein Website bekannt),—,,Henning Funke (GF) / Johannes Dackweiler (Obermeister); kein Email gefunden +Düsseldorf,Baugewerbe-Innung Düsseldorf,,(kein Website bekannt),—,,Peter Szemenyei (GF) / Christoph Morick (Obermeister); kein Email gefunden +Düsseldorf,Bestatter-Innung NRW,,(kein Website bekannt),—,,Christian Jaeger (GF) / Frank Wesemann (Obermeister); kein Email gefunden +Düsseldorf,Fleischer-Innung Düsseldorf-Mettmann-Solingen,,(kein Website bekannt),—,,Daniela van der Valk (GF) / Lutz Kluke (Obermeister); kein Email gefunden +Düsseldorf,Innung für Orthopädie-Schuhtechnik Rheinland/Westfalen,,(kein Website bekannt),—,,Irene Zamponi (GF) / Philipp Radtke (Obermeister); kein Email gefunden +Düsseldorf,Schornsteinfeger-Innung Regierungsbezirk Düsseldorf,,(kein Website bekannt),—,,Marcus Dörenkamp (GF); kein Email gefunden +Düsseldorf,Stukkatuer-Innung Wuppertal und Kreis Mettmann,,(kein Website bekannt),—,,Hermann Schulte-Hiltrop (HGF) / Wolfgang Wüstenhagen (Obermeister); kein Email gefunden +Düsseldorf,Zahntechniker-Innung Düsseldorf,,(kein Website bekannt),—,,Michael Knittel (GF) / Dominik Kruchen (Obermeister); kein Email gefunden diff --git a/leads/compare_output.txt b/leads/compare_output.txt new file mode 100644 index 0000000..f047dad Binary files /dev/null and b/leads/compare_output.txt differ diff --git a/leads/compare_pdf_csv.py b/leads/compare_pdf_csv.py new file mode 100644 index 0000000..0bda189 --- /dev/null +++ b/leads/compare_pdf_csv.py @@ -0,0 +1,159 @@ +import csv + +# All entries from PDF (name → {contact, email, phone, type}) +pdf_entries = { + # Bäcker + "Bäckerinnung Bayerischer Untermain": {"contact": "Veronika Hench", "email": "v.hench@baeckerinnung-bayerischer-untermain.de", "phone": "06021 790777"}, + "Bäckerinnung Bad Kissingen - Rhön-Grabfeld": {"contact": "Petra Schwab (Ansp.) / Ullrich Amthor (Obermeister)", "email": "khw-kg@t-online.de", "phone": "0971 78536971"}, + "Bäckerinnung Kitzingen": {"contact": "Elisabeth Hofmann (Ansp.) / Tilo Brönner (Obermeister)", "email": "", "phone": "09332 590636"}, + "Bäckerinnung Schweinfurt - Haßberge": {"contact": "Brigitte Rapp (Ansp.) / Gerhard Götz (Obermeister)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + "Bäckerinnung Mainfranken": {"contact": "Christine Winterbauer (Ansp.) / Marcel Scherg (Obermeister)", "email": "christine.winterbauer@baeckerbuchstelle.de", "phone": "09302 909431"}, + # Bau + "Bauinnung Aschaffenburg": {"contact": "Nina Emeneth (Ansp.) / Felix Englert (Obermeister)", "email": "info@bauinnung-aschaffenburg.de", "phone": "06021 421086"}, + "Bauinnung Bad Kissingen / Rhön-Grabfeld": {"contact": "Petra Schwab (Ansp.) / Stefan Goos (Obermeister)", "email": "khw-kg@t-online.de", "phone": "0971 78536971"}, + "Bau-Innung Schweinfurt": {"contact": "Ramona Ziegler (Ansp.) / Karl Böhner (Obermeister)", "email": "info@bauinnung-schweinfurt.de", "phone": "09721 74220"}, + "Bauinnung Mainfranken - Würzburg": {"contact": "Manfred Dallner (Ansp.) / Ralf Stegmeier (Obermeister)", "email": "baugewerbe@lbb-unterfranken.de", "phone": "0931 454440"}, + # Bekleidung + "Innung des Bekleidungshandwerks Unterfranken": {"contact": "Nicole Brandler (Ansp.) / Friedrun Schlagbauer-Werner (Obermeisterin)", "email": "info@innung-unterfranken.de", "phone": "09732 2277"}, + # Brauer + "Brauer- und Mälzerinnung Unterfranken": {"contact": "Josef Göller (Obermeister)", "email": "info@private-brauereien-bayern.de", "phone": "089 2909560"}, + # Dachdecker + "Dachdeckerinnung Aschaffenburg - Miltenberg": {"contact": "Lukas Thalheimer (Obermeister)", "email": "lukas.thalheimer@thalheimer.de", "phone": "0160 8480279"}, + "Dachdeckerinnung Unterfranken": {"contact": "Timo Markert (Obermeister)", "email": "info@mein-dachdecker.com", "phone": "09321 3905830"}, + # Elektro + "Innung für Elektro- und Informationstechnik Bayerischer Untermain": {"contact": "Annett Kinzel (Ansp.) / Edwin Palzer (Obermeister)", "email": "info@elektroinnung-bayerischeruntermain.de", "phone": "06021 480331"}, + "Innung für Elektro- und Informationstechnik Haßberge": {"contact": "Gitta Klopf (Ansp.) / Ralf Jooß (Obermeister)", "email": "info@elektroinnung-hassberge.de", "phone": "09526 8250"}, + "Innung für Elektro- und Informationstechnik Schweinfurt": {"contact": "Gaby Fröschel / Roland Klöffel / Ronald Niessner / Rainer Walter-Helk (Obermeister)", "email": "info@elektroinnung-sw.de", "phone": "09721 41175"}, + "Innung für Elektro- und Informationstechnik Würzburg": {"contact": "Heike Langner (Ansp.) / Sebastian Seynstahl (Obermeister)", "email": "mailbox@elektro-innung-wuerzburg.de", "phone": "0931 4501790"}, + # Fotografen + "Berufsfotografen-Innung für Unterfranken": {"contact": "Michael Alfen (Obermeister)", "email": "info@foto-alfen.de", "phone": "06021 23807"}, + # Friseure + "Friseur-Innung Aschaffenburg Stadt und Land": {"contact": "Corina Bayer (Obermeisterin)", "email": "friseurinnung-aschaffenburg@t-online.de", "phone": "06021 12646"}, + "Friseur-Innung Haßberge": {"contact": "Heinz Göhr (Ansp.) / Oliver Merkl (Obermeister)", "email": "info@team-art-of-hair.com", "phone": "09522 7948"}, + "Friseurinnung Kitzingen": {"contact": "Sabine Hack (Obermeisterin)", "email": "sabine.hack71@web.de", "phone": "09321 389988"}, + "Friseurinnung Miltenberg": {"contact": "Monique Haas (Obermeisterin)", "email": "info@haarmonique.de", "phone": "0160 1405397"}, + "Friseurinnung Main-Rhön": {"contact": "Brigitte Rapp (Ansp.) / Margit Rosentritt (Obermeisterin)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + "Friseur-Innung Würzburg, Main-Spessart und Bad Kissingen": {"contact": "Katharina Walker (Obermeisterin)", "email": "katharinawalker88@gmx.de", "phone": "0931 4605400"}, + # Glaser + "Glaserinnung Unterfranken": {"contact": "Siegfried Frank (Obermeister)", "email": "info@frank-bauglaserei.de", "phone": "09321 31890"}, + # Kaminkehrer + "Kaminkehrer-Innung Unterfranken": {"contact": "Benjamin Schreck (Obermeister)", "email": "info@kaminkehrerinnung-unterfranken.de", "phone": "09302 2187"}, + # Karosserie + "Karosserie- und Fahrzeugtechnik Innung Unterfranken": {"contact": "Manuela Wohlert (Ansp.) / Michael Seidel (Obermeister)", "email": "info@seidel-karosserie.de", "phone": "0911 2358880"}, + # Kfz + "Kfz-Innung Unterfranken": {"contact": "Michael Frank (Ansp.) / Roland Hoier (Obermeister)", "email": "info@kfz-innung-ufr.de", "phone": "0931 279910"}, + # Landmaschinen + "Innung für Land- und Baumaschinentechnik Unterfranken": {"contact": "Brigitte Rapp (Ansp.) / Bertram Muth (Obermeister)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + # Maler + "Maler-, Tüncher- und Lackierer Innung Alzenau": {"contact": "Karlheinz Trageser (Obermeister)", "email": "maler_trageser@gmx.de", "phone": "06023 8736"}, + "Maler- und Lackierer-Innung Aschaffenburg Stadt und Land": {"contact": "Uta Kern (Ansp.) / Ansgar Kern (Obermeister)", "email": "uta.kern@kolb-kern.de", "phone": "06021 859120"}, + "Maler- und Lackiererinnung Bad Kissingen": {"contact": "Petra Schwab (Ansp.) / Mathias Stöth (Obermeister)", "email": "khw-kg@t-online.de", "phone": "0971 78536971"}, + "Maler- und Tüncherinnung Haßberge": {"contact": "Michael Ott (Obermeister)", "email": "obermeister@malerinnung-hassberge.de", "phone": "09534 173330"}, + "Maler- und Lackiererinnung Kitzingen": {"contact": "Sandra und Andreas Zobel (Ansp.) / Thomas Wandler (Obermeister)", "email": "info@malerinnung-kitzingen.de", "phone": "09381 9141"}, + "Maler- und Lackiererinnung Miltenberg": {"contact": "Melitta Becker (Ansp.) / Jan Becker (Obermeister)", "email": "info@farbe-miltenberg.de", "phone": "09371 1090"}, + "Maler-, Tüncher- und Lackierer-Innung Rhön-Grabfeld": {"contact": "Birgit Neuhöfer (Ansp.) / Stefan Neuhöfer (Obermeister)", "email": "info@malerinnung-rg.de", "phone": "09766 1555"}, + "Malerinnung Schweinfurt Stadt- und Land": {"contact": "Brigitte Rapp (Ansp.) / Andreas Spath (Obermeister)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + "Maler- und Stuckateur-Innung Würzburg und Main-Spessart": {"contact": "Claudius Wolfrum (Ansp.) / Peter Killinger (Obermeister)", "email": "info@malerinnung-wuerzburg.de", "phone": "0931 54306"}, + # Metall + "Innung Metallbau- und Feinwerktechnik Bayerischer Untermain": {"contact": "Claudia Find / Stefanie Belle (Ansp.) / Matthias Kreß (Obermeister)", "email": "info@innung-metallbau-feinwerktechnik.de", "phone": "06021 401286"}, + "Metall-Innung Bad Kissingen/Rhön-Grabfeld": {"contact": "Petra Schwab (Ansp.) / Klaus Engelmann (Obermeister)", "email": "khw-kg@t-online.de", "phone": "0971 78536971"}, + "Metallinnung Schweinfurt - Haßberge": {"contact": "Brigitte Rapp (Ansp.) / René Dauelsberg (Obermeister)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + "Metallinnung Mainfranken - Mitte": {"contact": "Birgit Beckmann (Ansp.) / Detlef Lurz (Obermeister)", "email": "info@metallinnung-mainfranken.de", "phone": "0931 412614"}, + # Metzger + "Metzgerinnung Aschaffenburg": {"contact": "Dagobert Pfarr (Ansp.) / Marco Häuser (Obermeister)", "email": "metzgerei-pfarr@t-online.de", "phone": "06027 8468"}, + "Fleischer-Innung Main-Spessart": {"contact": "Sebastian Bumm (Ansp./komm. Stellv. Obermeister)", "email": "fleischerinnungMSP@gmx.de", "phone": ""}, + "Metzgerinnung Miltenberg": {"contact": "Josef Neuberger (Obermeister)", "email": "j.neuberger@t-online.de", "phone": "09371 2671"}, + "Metzgerinnung Main-Rhön": {"contact": "Jürgen Straub / Sonja Grob (Ansp.) / Barbara Fink (Obermeisterin)", "email": "innung@fleischerring.de", "phone": "09721 65050"}, + "Metzger-Innung Würzburg": {"contact": "Horst Schömig (Obermeister)", "email": "horst@schoemig.eu", "phone": "0931 73926"}, + # Ofen + "Unterfränkische Ofen- und Luftheizungsbauer-Innung": {"contact": "Josef Bock (Ansp.) / Michael Heigel (Obermeister)", "email": "josef.bock60@gmail.com", "phone": "0175 8855931"}, + # Raumausstatter + "Raumausstatter- und Sattlerinnung Unterfranken": {"contact": "Brigitte Rapp (Ansp.) / Hermann Noske (Obermeister)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + # SHK + "Spengler-, Sanitär- und Heizungstechnik Innung Aschaffenburg - Miltenberg": {"contact": "Michael Bramm (Ansp.) / Christoph Winkler (Obermeister)", "email": "shk-aschaffenburg@t-online.de", "phone": "06021 28731"}, + "Innung für Spengler-, Sanitär-, und Heizungstechnik Kitzingen": {"contact": "Christine Keppner-Siegert (Ansp.) / Thomas Lößlein (Obermeister)", "email": "innung-kitzingen@freenet.de", "phone": "09324 978667"}, + "SHK-Innung Main-Spessart Innung Sanitär-, Heizungs- und Klimatechnik": {"contact": "Johannes Reber (Obermeister)", "email": "info@shk-main-spessart.de", "phone": "09355 97400"}, + "Innung für Spengler-, Sanitär-, Heizungs- und Klimatechnik Schweinfurt - Main-Rhön": {"contact": "Stefan Köppe (Ansp.) / Heinz Schuchbauer (Obermeister)", "email": "info@shk-schweinfurt.de", "phone": "09721 471526"}, + "Innung für Sanitär-, Heizungs-, Klempner- und Klimatechnik Würzburg": {"contact": "Sandra Köller (Ansp.) / Werner Rath (Obermeister)", "email": "innung.shk@t-online.de", "phone": "0931 7841878"}, + # Schreiner + "Schreinerinnung Aschaffenburg Stadt und Land": {"contact": "Michael Deller (Obermeister)", "email": "info@dellers-werkstatt.de", "phone": "06021 460428"}, + "Schreinerinnung Bad Kissingen": {"contact": "Petra Schwab (Ansp.) / Norbert Borst (Obermeister)", "email": "khw-kg@t-online.de", "phone": "0971 78536971"}, + "Schreinerinnung Miltenberg-Obernburg": {"contact": "Werner Tausch (Obermeister)", "email": "wt@reichert-betten.de", "phone": "09371 97770"}, + "Schreinerinnung Rhön-Grabfeld": {"contact": "Michael Werner (Obermeister)", "email": "info@schreiner-rhoen-grabfeld.de", "phone": "09772 9300990"}, + "Schreinerinnung Haßberge – Schweinfurt": {"contact": "Brigitte Rapp (Ansp.) / Horst Zitterbart (Obermeister)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + "Schreinerinnung Mainfranken": {"contact": "Ramona Pfenning (Ansp.) / Thomas Heußlein (Obermeister)", "email": "info@schreinerinnung-mainfranken.de", "phone": "09394 9957944"}, + # Schuhmacher + "Schuhmacherinnung Unterfranken": {"contact": "Leo Emge (Obermeister)", "email": "l.emge@t-online.de", "phone": "06021 423235"}, + # Steinmetz + "Steinmetz- und Steinbildhauerinnung Unterfranken": {"contact": "Brigitte Rapp (Ansp.) / Sebastian Ludwig (Obermeister)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + # Uhrmacher + "Uhrmacher-, Gold- und Silberschmiedeinnung Unterfranken": {"contact": "Markus Graf (Ansp.) / Klaus Imhof (Obermeister)", "email": "m.graf@hwk-ufr.de", "phone": "0931 309081132"}, + # Zimmerer + "Zimmerer-Innung Aschaffenburg - Miltenberg": {"contact": "Theresa Breunig (Ansp.) / Jürgen Pfarr (Obermeister)", "email": "info@zimmerer-aschaffenburg-miltenberg.de", "phone": "06094 1361"}, + "Zimmerer-Innung Bad Neustadt": {"contact": "Michael Eyrich-Halbig (Obermeister)", "email": "info@holzbau-eyrich.de", "phone": "09736 223"}, + "Zimmerer-Innung Main-Spessart": {"contact": "Ulrike Feser (Ansp.) / Volker Schäfer (Obermeister)", "email": "info@zimmerer-main-spessart.de", "phone": "09355 97400"}, + "Zimmerer-Innung Schweinfurt-Haßberge": {"contact": "Brigitte Rapp (Ansp.) / Marion Reichhold (Obermeisterin)", "email": "rapp@kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + "Zimmerer-Innung Würzburg - Kitzingen": {"contact": "Ulrike Feser (Ansp.) / Hermann Lang (Obermeister)", "email": "info@zimmerer-wuerzburg-kitzingen.de", "phone": "0931 72760"}, + # Kreishandwerkerschaften + "Kreishandwerkerschaft Aschaffenburg": {"contact": "Claudia Find (Ansp.) / Matthias Kreß (KH-Meister)", "email": "info@khw-ab.de", "phone": "06021 401286"}, + "Kreishandwerkerschaft Bad Kissingen": {"contact": "Petra Schwab (Ansp.) / Ulrike Lochner-Erhard (KH-Meisterin)", "email": "khw-kg@t-online.de", "phone": "0971 78536971"}, + "Kreishandwerkerschaft Haßberge": {"contact": "Gitta Klopf (Ansp.) / Udo Merz (KH-Meister)", "email": "info@elektroinnung-hassberge.de", "phone": "09524 7492"}, + "Kreishandwerkerschaft Kitzingen": {"contact": "Elisabeth Hofmann (Ansp.) / Monika Henneberger (KH-Meisterin)", "email": "khw.kitzingen@gmail.com", "phone": "09332 590636"}, + "Kreishandwerkerschaft Main-Spessart": {"contact": "Petra Stegerwald (Ansp.) / Thomas Heußlein (KH-Meister)", "email": "petra@stegerwald.de", "phone": "09352 6056495"}, + "Kreishandwerkerschaft Miltenberg": {"contact": "Monique Haas (KH-Meisterin)", "email": "kreishandwerker.mil@gmail.com", "phone": "09371 6693681"}, + "Kreishandwerkerschaft Rhön-Grabfeld": {"contact": "Bruno Werner (KH-Meister)", "email": "khwsch-rhoen-grabfeld@mail.de", "phone": "09772 9300990"}, + "Kreishandwerkerschaft Schweinfurt": {"contact": "Brigitte Rapp (Ansp.) / Margit Rosentritt (KH-Meisterin)", "email": "rapp@Kreishandwerkerschaft-sw.de", "phone": "09721 473578"}, + "Kreishandwerkerschaft Würzburg": {"contact": "Sandra Köller (Ansp.) / Martin Strobl (KH-Meister)", "email": "info@kreishandwerkerschaft-wuerzburg.de", "phone": "0931 7841879"}, +} + +# Read current leads.csv - only Unterfranken entries +csv_entries = {} +with open(r'C:\Users\a931627\Documents\stadtwerke-saas-analysis\leads\leads.csv', encoding='utf-8') as f: + reader = csv.DictReader(f) + for row in reader: + if row.get('Region','') == 'Unterfranken': + csv_entries[row['Firm/Innung'].strip()] = { + 'contact': row.get('Contact Person',''), + 'email': row.get('Email',''), + 'phone': row.get('Phone',''), + } + +# Compare +print("=== IN PDF BUT NOT IN CSV (missing leads) ===") +missing = [] +for name in pdf_entries: + if name not in csv_entries: + # fuzzy check + found = False + for csv_name in csv_entries: + # normalize + if name.lower().replace('–','-').replace(' ',' ') in csv_name.lower() or csv_name.lower() in name.lower(): + found = True + break + if not found: + missing.append(name) + print(f" MISSING: {name}") + +print(f"\nTotal missing: {len(missing)}") + +print("\n=== EMAIL DISCREPANCIES (PDF vs CSV) ===") +for pdf_name, pdf_data in pdf_entries.items(): + for csv_name, csv_data in csv_entries.items(): + # Match by similar name + if (pdf_name.lower()[:20] in csv_name.lower()[:25] or csv_name.lower()[:20] in pdf_name.lower()[:25]): + pdf_email = pdf_data.get('email','').lower().strip() + csv_email = csv_data.get('email','').lower().strip() + if pdf_email and csv_email and pdf_email != csv_email: + print(f" {pdf_name}") + print(f" PDF: {pdf_email}") + print(f" CSV: {csv_email}") + +print("\n=== IN CSV BUT DATA NOT MATCHING PDF (spot check) ===") +# Check Steinmetz specifically +for csv_name, csv_data in csv_entries.items(): + if 'steinmetz' in csv_name.lower() or 'steinbild' in csv_name.lower(): + print(f"CSV: {csv_name} | email: {csv_data['email']} | contact: {csv_data['contact']}") + if 'bäckerinnung kitzingen' in csv_name.lower(): + print(f"CSV: {csv_name} | email: {csv_data['email']}") + if 'land- und bau' in csv_name.lower(): + print(f"CSV: {csv_name} | email: {csv_data['email']}") diff --git a/leads/leads.csv b/leads/leads.csv index ba119f2..b98035a 100644 --- a/leads/leads.csv +++ b/leads/leads.csv @@ -1,75 +1,78 @@ -Firm/Innung,Contact Person,Email,Region -Augenoptiker-Innung Koeln-Aachen,,info@optikerinnung.de,Köln -Bau-Innung Kreis Mettmann,Thomas Grünendahl,info@handwerk-me.de,Düsseldorf/Surrounding +Firm/Innung,Contact Person,Email,Phone,Region +Augenoptiker-Innung Koeln-Aachen,Hans Josef Schümmer (Obermeister),info@optikerinnung.de,,Köln +Bau-Innung Kreis Mettmann,"Thomas Grünendahl (KHM) / Simon Taps (GF)",info@handwerk-me.de,,Düsseldorf/Surrounding Bau-Innung Mönchengladbach,Dipl.-Ing. Frank Bühler,info@kh-mg.de,Düsseldorf/Surrounding -Bau-Innung Neuss-Viersen,Thomas Goldmann,info@kh-niederrhein.de,Düsseldorf/Surrounding +Bau-Innung Neuss-Viersen,"Joachim Selzer (KHM) / Thomas Gütgens (GF)",info@kh-niederrhein.de,,Düsseldorf/Surrounding Bau-Innung Remscheid,Carsten Hof,info@handwerk-remscheid.de,Düsseldorf/Surrounding Bau-Innung Schweinfurt,Karl Böhner,info@bauinnung-schweinfurt.de,Unterfranken -Bau-Innung Wuppertal-Solingen,Marcus Koch,info@bauinnung-wuppertal.de,Düsseldorf/Surrounding +Bau-Innung Wuppertal-Solingen,"Marcus Koch (Obermeister) / Falk Niederlehner (GF)",info@bauinnung-wuppertal.de,,Düsseldorf/Surrounding Baugewerks-Innung des Kreises Wesel,Gerhard Landwehrs,info@khwesel.de,Düsseldorf/Surrounding Baugewerks-Innung Duisburg,Volker Blastik,info@baugewerksinnung-duisburg.de,Düsseldorf/Surrounding Bauinnung Aschaffenburg,Felix Englert,felix.englert@bauinnung-aschaffenburg.de,Unterfranken Bauinnung Bad Kissingen / Rhön-Grabfeld,Stefan Goos,stefan.goos@zehe-gmbh.de,Unterfranken -Bauinnung Mainfranken - Würzburg,Ralf Stegmeier,info@trend-bau.com,Unterfranken +Bauinnung Mainfranken - Würzburg,Manfred Dallner,baugewerbe@lbb-unterfranken.de,Unterfranken Berufsfotografen-Innung für Unterfranken,,info@foto-alfen.de,Unterfranken Brauer- und Mälzerinnung Unterfranken,,info@private-brauereien-bayern.de,Unterfranken -"Buechsenmacher-Innung Nordrhein, RLP und Saarland",,kliedl@t-online.de,Köln -Bundesinnung fuer das Geruestbauer-Handwerk,,info@geruestbauhandwerk.de,Köln +"Buechsenmacher-Innung Nordrhein, RLP und Saarland",Klaus-Bernd Liedl (Obermeister),kliedl@t-online.de,,Köln +Bundesinnung fuer das Geruestbauer-Handwerk,Marcus Nachbauer (Bundesinnungsmeister) / Sabrina Luther (GF),info@geruestbauhandwerk.de,,Köln Bäckerinnung Bad Kissingen - Rhön-Grabfeld,Petra Schwab,khw-kg@t-online.de,Unterfranken Bäckerinnung Bayerischer Untermain,,v.hench@baeckerinnung-bayerischer-untermain.de,Unterfranken -Bäckerinnung Kitzingen,Tilo Brönner,tilo-br@t-online.de,Unterfranken -Bäckerinnung Mainfranken,Marcel Scherg,scherge.beck@t-online.de,Unterfranken +Bäckerinnung Kitzingen,"Elisabeth Hofmann (Ansp.) / Tilo Brönner (Obermeister)",khw.kitzingen@gmail.com,,Unterfranken +Bäckerinnung Mainfranken,"Christine Winterbauer (Ansp.) / Marcel Scherg (Obermeister)",christine.winterbauer@baeckerbuchstelle.de,,Unterfranken Bäckerinnung Schweinfurt - Haßberge,Gerhard Götz,baeckerei-goetz@web.de,Unterfranken -Dachdecker- und Zimmerer-Innung Koeln,,e-mail@dachdecker-innung-koeln.de,Köln +Dachdecker- und Zimmerer-Innung Koeln,Oliver Miesen (Obermeister) / Bettina Dietrich (GF),e-mail@dachdecker-innung-koeln.de,,Köln +Dachdecker-Innung Duesseldorf,N/A,info@dachdeckerinnung.nrw,0211 / 36 70 710,Düsseldorf Dachdeckerinnung Aschaffenburg - Miltenberg,,lukas.thalheimer@thalheimer.de,Unterfranken -Dachdeckerinnung Unterfranken,,info@dachdecker-unterfranken.de,Unterfranken -Drucker- (Buchdrucker-)- und Buchbinder-Innung Duisburg,Bodo H. Oppenberg,info@handwerk-duisburg.de,Düsseldorf/Surrounding -Elektroinnung Koeln,,info@elektroinnungkoeln.de,Köln -Fleischer-Innung Koeln,,obermeister@fleischer-koeln.de,Köln +Dachdeckerinnung Unterfranken,Timo Markert,info@mein-dachdecker.com,Unterfranken +Drucker- (Buchdrucker-)- und Buchbinder-Innung Duisburg,"Bodo H. Oppenberg / Lothar Hellmann (KHM) / Michael Dicke (GF)",info@handwerk-duisburg.de,,Düsseldorf/Surrounding +Elektroinnung Koeln,Etienne Berndt (GF) / Ralf Janowski (Obermeister),info@elektroinnungkoeln.de,0221 123071,Köln +Fleischer-Innung Koeln,Astrid Schmitz (Obermeisterin),obermeister@fleischer-koeln.de,,Köln Fleischer-Innung Main-Spessart,Sebastian Bumm,fleischerinnungMSP@gmx.de,Unterfranken -Fotografen-Innung Düsseldorf-Aachen-Köln,Guido de Nardo,info@Fotografen-DUS.de,Düsseldorf/Surrounding +Fotografen-Innung Düsseldorf-Aachen-Köln,"Guido de Nardo (Vorsitzender) / Torsten Spengler (GF)",info@Fotografen-DUS.de,,Düsseldorf/Surrounding Friseur-Innung Aschaffenburg Stadt und Land,,friseurinnung-aschaffenburg@t-online.de,Unterfranken Friseur-Innung Haßberge,Heinz Göhr,info@team-art-of-hair.com,Unterfranken -Friseur-Innung Koeln,,info@kopfarbeit-koeln.de,Köln +Friseur-Innung Koeln,Mike Engels (Obermeister) / Julia Barth (GF),info@kopfarbeit-koeln.de,,Köln "Friseur-Innung Würzburg, Main-Spessart und Bad Kissingen",,katharinawalker88@gmx.de,Unterfranken Friseurinnung Kitzingen,,sabine.hack71@web.de,Unterfranken Friseurinnung Main-Rhön,Brigitte Rapp,rapp@kreishandwerkerschaft-sw.de,Unterfranken Friseurinnung Miltenberg,,info@haarmonique.de,Unterfranken Glasapparatebauer-Innung Duisburg,Dieter Verhees,hippler@handwerk-duisburg.de,Düsseldorf/Surrounding -Glaser-Innung Koeln-Bonn-Aachen,,mail@glas-bong.de,Köln +Glaser-Innung Koeln-Bonn-Aachen,Anne Bong (Obermeisterin),mail@glas-bong.de,,Köln Glaserinnung Unterfranken,,info@frank-bauglaserei.de,Unterfranken Graveur- und Metallbildner-Innung Rhein-Ruhr,Till Esser,info@kh-mo.de,Düsseldorf/Surrounding Innung der Metallhandwerke (Solingen),Thomas Blau,info@handwerk-sgw.de,Düsseldorf/Surrounding Innung des Bekleidungshandwerks Unterfranken,Friedrun Schlagbauer-Werner,info@schneiderin-wuerzburg.de,Unterfranken -Innung des Gebaeudereiniger-Handwerks Koeln-Aachen,,info@gebaeudereiniger-koeln-aachen.de,Köln -Innung des Massschneiderhandwerks Koeln / Textileiniger-Innung Koeln/Bonn,,twp.koeln@gmail.com,Köln -Innung Farbe Koeln,,s.epe@epe-maler.de,Köln -Innung fuer Informationstechnik Koeln/Bonn/Rhein-Sieg/Rhein-Erft,,n.gassner@koenig-avt.de,Köln -Innung fuer Metalltechnik Koeln,,info@van-broek.de,Köln -Innung fuer Orthopaedie-Technik Koeln,,sebastian@malzkorn.at,Köln +Innung des Gebaeudereiniger-Handwerks Koeln-Aachen,Detlef Ptak (Obermeister) / Jennifer Schramm (GF),info@gebaeudereiniger-koeln-aachen.de,0221 251064,Köln +Innung des Massschneiderhandwerks Koeln / Textileiniger-Innung Koeln/Bonn,Thomas Wien-Pegelow (Obermeister),twp.koeln@gmail.com,,Köln +Innung Farbe Koeln,Sebastian Epe (Obermeister),s.epe@epe-maler.de,,Köln +Innung fuer Informationstechnik Koeln/Bonn/Rhein-Sieg/Rhein-Erft,Nicolay Gassner (Obermeister),n.gassner@koenig-avt.de,,Köln +Innung fuer Metalltechnik Koeln,Sascha Franke (Obermeister),info@van-broek.de,,Köln +Innung fuer Orthopaedie-Technik Koeln,Sebastian Malzkorn (Obermeister),sebastian@malzkorn.at,,Köln Innung für Elektro- und Informationstechnik Bayerischer Untermain,Annett Kinzel,info@elektroinnung-bayerischeruntermain.de,Unterfranken Innung für Elektro- und Informationstechnik Haßberge,Gitta Klopf,info@elektroinnung-hassberge.de,Unterfranken Innung für Elektro- und Informationstechnik Schweinfurt,"Gaby Fröschel, Roland Klöffel, Ronald Niessner",info@elektroinnung-sw.de,Unterfranken Innung für Elektro- und Informationstechnik Würzburg,Heike Langner,mailbox@elektro-innung-wuerzburg.de,Unterfranken -Innung für Land- und Baumaschinentechnik Unterfranken,Brigitte Rapp,info@innung-landbautechnik.de,Unterfranken +Innung für Land- und Baumaschinentechnik Unterfranken,Brigitte Rapp,rapp@kreishandwerkerschaft-sw.de,Unterfranken Innung für Metallhandwerk des Kreises Kleve,Johannes Flinterhoff,info@kh-kleve.de,Düsseldorf/Surrounding -Innung für Metallhandwerke Mülheim an der Ruhr-Oberhausen,Johannes Arnzen,info@metallbauinnung-mh-ob.de,Düsseldorf/Surrounding +Innung für Metallhandwerke Mülheim an der Ruhr-Oberhausen,"Johannes Arnzen / Barbara Yeboah (GF)",info@metallbauinnung-mh-ob.de,,Düsseldorf/Surrounding "Innung für Sanitär-, Heizungs-, Klempner- und Klimatechnik Würzburg",Sandra Köller,innung.shk@t-online.de,Unterfranken "Innung für Spengler-, Sanitär-, Heizungs- und Klimatechnik Schweinfurt - Main-Rhön",Stefan Köppe,info@shk-schweinfurt.de,Unterfranken "Innung für Spengler-, Sanitär-, und Heizungstechnik Kitzingen",Christine Keppner-Siegert,innung-kitzingen@freenet.de,Unterfranken -Innung Koeln Rollladen und Sonnenschutz,,info@rhp-online.de,Köln -Innung Metallbau- und Feinwerktechnik Bayerischer Untermain,Matthias Kreß,m.kress@wassermannkress.de,Unterfranken +Innung Koeln Rollladen und Sonnenschutz,Andre Urban (Obermeister),info@rhp-online.de,,Köln +Innung des Kraftfahrzeuggewerbes Duesseldorf,Sven Gustavson (GF) / Hermann Görtz (Obermeister),info@kfz-innung-duesseldorf.de,+49 211-6634 34,Düsseldorf +Innung fuer Sanitaer- und Heizungstechnik Duesseldorf,Horst Jansen (GF) / Hans Werner Eschrich (Obermeister),info@shk-duesseldorf.de,0211 36707-10,Düsseldorf +Innung Metallbau- und Feinwerktechnik Bayerischer Untermain,"Claudia Find / Stefanie Belle (Ansp.) / Matthias Kreß (Obermeister)",info@innung-metallbau-feinwerktechnik.de,,Unterfranken "Juwelier-, Gold- und Silberschmiede-Innung Koeln",,info@sotos-schmuck.de,Köln Kaminkehrer-Innung Unterfranken,,info@kaminkehrerinnung-unterfranken.de,Unterfranken Karosserie- und Fahrzeugtechnik Innung Unterfranken,Michael Seidel,info@seidel-karosserie.de,Unterfranken -Karosseriebauer-Innung Koeln,,info@karosserie-innungkoeln.de,Köln +Karosseriebauer-Innung Koeln,Oliver Nienhaus (Obermeister) / Claudia Weiler (GF),info@karosserie-innungkoeln.de,,Köln Kfz-Innung Unterfranken,Michael Frank,info@kfz-innung-ufr.de,Unterfranken -Konditoren-Innung Koeln - Bonn,,info@cafe-schoener.de,Köln +Konditoren-Innung Koeln - Bonn,Rudolf Schöner (Obermeister),info@cafe-schoener.de,,Köln Kreishandwerkerschaft Aschaffenburg,Claudia Find,info@khw-ab.de,Unterfranken Kreishandwerkerschaft Bad Kissingen,Petra Schwab,khw-kg@t-online.de,Unterfranken Kreishandwerkerschaft Haßberge,Gitta Klopf,info@elektroinnung-hassberge.de,Unterfranken Kreishandwerkerschaft Kitzingen,Monika Henneberger,monika.henneberger@t-online.de,Unterfranken -Kreishandwerkerschaft Koeln,,lepore@handwerk.koeln,Köln +Kreishandwerkerschaft Koeln,Roberto Lepore (HGF) / Nicolai Lucks (Kreishandwerksmeister),lepore@handwerk.koeln,,Köln Kreishandwerkerschaft Main-Spessart,Thomas Heußlein,info@schreinerei-heusslein.de,Unterfranken Kreishandwerkerschaft Miltenberg,,kreishandwerker.mil@gmail.com,Unterfranken Kreishandwerkerschaft Rhön-Grabfeld,,khwsch-rhoen-grabfeld@mail.de,Unterfranken @@ -83,20 +86,21 @@ Maler- und Lackiererinnung Miltenberg,Melitta Becker,info@farbe-miltenberg.de,Un Maler- und Stuckateur-Innung Würzburg und Main-Spessart,Claudius Wolfrum,info@malerinnung-wuerzburg.de,Unterfranken Maler- und Tüncherinnung Haßberge,,obermeister@malerinnung-hassberge.de,Unterfranken "Maler-, Tüncher- und Lackierer-Innung Rhön-Grabfeld",Birgit Neuhöfer,info@malerinnung-rg.de,Unterfranken -Malerinnung Schweinfurt Stadt- und Land,Brigitte Rapp,info@malerinnung-schweinfurt.de,Unterfranken +Malerinnung Schweinfurt Stadt- und Land,Brigitte Rapp,rapp@kreishandwerkerschaft-sw.de,Unterfranken Maßschneider-Innung Düsseldorf,Sandra Gronemeier,info@mass-schneider-innung.de,Düsseldorf/Surrounding -Metall-Innung Bad Kissingen/Rhön-Grabfeld,Klaus Engelmann,info@metallinnung-kg-nes.de,Unterfranken +Metall-Innung Bad Kissingen/Rhön-Grabfeld,Petra Schwab,khw-kg@t-online.de,Unterfranken Metall-Innung des Kreises Wesel,Rainer Theunissen,info@metallinnung-wesel.de,Düsseldorf/Surrounding Metall-Innung Essen,Björn Bergmann,info@metallhandwerk-essen.de,Düsseldorf/Surrounding Metallinnung Mainfranken - Mitte,Detlef Lurz,detlef.lurz@lurz-metalltec.de,Unterfranken Metallinnung Schweinfurt - Haßberge,Brigitte Rapp,rapp@kreishandwerkerschaft-sw.de,Unterfranken Metzger-Innung Würzburg,,horst@schoemig.eu,Unterfranken Metzgerinnung Aschaffenburg,Marco Häuser,marco.haeuser@haeuser-hra.de,Unterfranken -Metzgerinnung Main-Rhön,Barbara Fink,metzgerei_dros_fladungen@outlook.de,Unterfranken +Metzgerinnung Main-Rhön,"Jürgen Straub / Sonja Grob (GF Schweinfurt) / Barbara Fink (Obermeisterin)",innung@fleischerring.de,,Unterfranken Metzgerinnung Miltenberg,,j.neuberger@t-online.de,Unterfranken Musikinstrumentenmacher-Innung Nordrhein,Christoph Böttcher,info@musikinstrumentenmacher-innung.de,Düsseldorf/Surrounding Raumausstatter- und Sattlerinnung Unterfranken,Hermann Noske,mail@sofa-shop.de,Unterfranken -Raumausstatter-Innung Koeln,,info@diana-breidenbach.de,Köln +Raumausstatter-Innung Koeln,Diana Goeddertz (Obermeisterin),info@diana-breidenbach.de,,Köln +SHK-Innung Koeln,N/A,info@shk-innung-koeln.de,(0221) 83712-0,Köln Schreinerinnung Aschaffenburg Stadt und Land,,info@dellers-werkstatt.de,Unterfranken Schreinerinnung Bad Kissingen,Norbert Borst,khw-kg@t-online.de,Unterfranken Schreinerinnung Haßberge – Schweinfurt,Horst Zitterbart,schreinerei.zitterbart@t-online.de,Unterfranken @@ -106,13 +110,13 @@ Schreinerinnung Rhön-Grabfeld,,info@schreiner-rhoen-grabfeld.de,Unterfranken Schuhmacherinnung Unterfranken,,l.emge@t-online.de,Unterfranken "SHK-Innung Main-Spessart Innung Sanitär-, Heizungs- und Klimatechnik",,info@shk-main-spessart.de,Unterfranken "Spengler-, Sanitär- und Heizungstechnik Innung Aschaffenburg - Miltenberg",Michael Bramm,shk-aschaffenburg@t-online.de,Unterfranken -Steinmetz- und Steinbildhauerinnung Unterfranken,Josef Hofmann,info@stein-welten.com,Unterfranken +Steinmetz- und Steinbildhauerinnung Unterfranken,Brigitte Rapp,rapp@kreishandwerkerschaft-sw.de,Unterfranken Straßen- und Tiefbauer-Innung Düsseldorf,André Grimmert,info@strassenbauer-innung-duesseldorf.de,Düsseldorf/Surrounding -Stuckateur-Innung Koeln - Ausbau + Fassade,,s.rettig@hhhuerth.de,Köln +Stuckateur-Innung Koeln - Ausbau + Fassade,Sarah M. Rettig (Obermeisterin),s.rettig@hhhuerth.de,,Köln "Uhrmacher-, Gold- und Silberschmiedeinnung Unterfranken",Klaus Imhof,kontakt@juwelier-imhof.de,Unterfranken Unterfränkische Ofen- und Luftheizungsbauer-Innung,Michael Heigel,heigel@heigel.de,Unterfranken Verband der Berufsfotografen Ruhr,Andreas Köhring,ak@koehring-fotografie.de,Düsseldorf/Surrounding -Werbetechniker-Innung Koeln - Bonn - Aachen,,info@werbetechnik-baecker.de,Köln +Werbetechniker-Innung Koeln - Bonn - Aachen,Markus Böcker (Obermeister),info@werbetechnik-baecker.de,,Köln Zimmerer-Innung Aschaffenburg - Miltenberg,Theresa Breunig,info@zimmerer-aschaffenburg-miltenberg.de,Unterfranken Zimmerer-Innung Bad Neustadt,,info@holzbau-eyrich.de,Unterfranken Zimmerer-Innung Main-Spessart,Volker Schäfer,info@schaefer-halsbach.de,Unterfranken diff --git a/leads/read_pdf.py b/leads/read_pdf.py new file mode 100644 index 0000000..2b9ed33 --- /dev/null +++ b/leads/read_pdf.py @@ -0,0 +1,24 @@ +import pdfplumber + +all_text = [] + +with pdfplumber.open(r'C:\Users\a931627\Documents\stadtwerke-saas-analysis\leads\raw\unterfranken.pdf') as pdf: + for i, page in enumerate(pdf.pages): + w = page.width + h = page.height + # Split into left and right columns + left = page.crop((0, 0, w/2, h)) + right = page.crop((w/2, 0, w, h)) + left_text = left.extract_text(x_tolerance=2, y_tolerance=2) or "" + right_text = right.extract_text(x_tolerance=2, y_tolerance=2) or "" + all_text.append(f"=== PAGE {i+1} LEFT ===\n{left_text}") + all_text.append(f"=== PAGE {i+1} RIGHT ===\n{right_text}") + +full = "\n".join(all_text) + +# Write to file for easier reading +with open(r'C:\Users\a931627\Documents\stadtwerke-saas-analysis\leads\unterfranken_pdf_raw.txt', 'w', encoding='utf-8') as f: + f.write(full) + +print(f"Written {len(full)} chars") +print(full[:3000]) diff --git a/leads/unterfranken_pdf_raw.txt b/leads/unterfranken_pdf_raw.txt new file mode 100644 index 0000000..5de6551 --- /dev/null +++ b/leads/unterfranken_pdf_raw.txt @@ -0,0 +1,1404 @@ +=== PAGE 1 LEFT === +Verzeichnis der Innungen un +in Unter +Stand: 5. Dez +=== PAGE 1 RIGHT === +nd Kreishandwerkerschaften +rfranken +zember 2025 +=== PAGE 2 LEFT === +Inhaltsverzeichnis +Bäcker ...................................................... +Bau ........................................................... +Bekleidung ............................................... +Brauer und Mälzer ................................... +Dachdecker .............................................. +Elektro ...................................................... +Fotografen ............................................... +Friseure .................................................... +Glaser ....................................................... +Kaminkehrer (Schornsteinfeger) .............. +Karosserie- und Fahrzeugbauer ............... +Kraftfahrzeugtechniker ............................ +Landmaschinenmechaniker ..................... +Maler ........................................................ +Metall ....................................................... +Metzger (Fleischer) .................................. +Ofen- und Luftheizungsbauer .................. +Raumausstatter ....................................... +Sanitär, Heizung und Klima ...................... +Schreiner (Tischler) .................................. +Schuhmacher ........................................... +Steinmetzen und Steinbildhauer ............. +Uhrmacher, Gold- und Silberschmiede ... +Zimmerer ................................................. +Kreishandwerkerschaften ........................ +=== PAGE 2 RIGHT === +............................................................... 3 +............................................................... 6 +............................................................... 8 +............................................................... 9 +............................................................. 10 +............................................................. 11 +............................................................. 13 +............................................................. 14 +............................................................. 17 +............................................................. 18 +............................................................. 19 +............................................................. 20 +............................................................. 21 +............................................................. 22 +............................................................. 27 +............................................................. 29 +............................................................. 32 +............................................................. 33 +............................................................. 34 +............................................................. 37 +............................................................. 40 +............................................................. 41 +............................................................. 42 +............................................................. 43 +............................................................. 46 +=== PAGE 3 LEFT === +Der Aufsicht der Handwerkskammer für +gen und Kreishandwerkerschaften, die ih +franken haben. +Einen Überblick über Organisationen, die +kammer für Unterfranken unterliegen, g +schen Handwerks (ZDH) hier und der Bay +Der Aufbau der Handwerksorganisation +=== PAGE 3 RIGHT === +Unterfranken unterliegen alle Innun- +hren Sitz im Regierungsbezirk Unter- +e nicht der Aufsicht der Handwerks- +geben der Zentralverband des Deut- +yerische Handwerkstag (BHT) hier. +insgesamt ist hier grafisch dargestellt. +=== PAGE 4 LEFT === +Bäcker +Bäckerinnung Bayerischer Untermain +Landkreise: Aschaffenburg, Miltenberg +Behringstraße 17 +63814 Mainaschaff +Tel.: 06021 790777 +E-Mail: v.hench@baeckerinnung-bayerisch +Obermeister: Veronika Hench +Behringstraße 19 +63814 Mainaschaff +Tel.: 06021 790777 +E-Mail: v.hench@baeckerinnung-bayerisch +Bäckerinnung Bad Kissingen - Rhön-Grabfeld +Landkreise: Bad Kissingen, Rhön-Grabfeld +Ansprechpartnerin: Petra Schwab +Sparkassenpassage 6 +97688 Bad Kissingen +Tel.: 0971 78536971 +Fax: 0971 78536972 +E-Mail: khw-kg@t-online.de +Obermeister: Ullrich Amthor +Im Schlosshof 11 +97633 Saal +Tel.: 09762 897 +Fax: 09762 9397904 +E-Mail: info@baeckerei-amthor.com +05.12.2025 +=== PAGE 4 RIGHT === +her-untermain.de +her-untermain.de +3 +=== PAGE 5 LEFT === +Bäckerinnung Kitzingen +Landkreis: Kitzingen +Ansprechpartnerin: Elisabeth Hofmann +Marktbreiter Straße 4 a +97342 Obernbreit +Tel.: 09332 590636 +Fax: 09332 590479 +Obermeister: Tilo Brönner +Obere Gräbengasse 12 +97346 Iphofen +Tel.: 09323 3354 +E-Mail: tilo-br@t-online.de +Bäckerinnung Schweinfurt - Haßberge +Landkreise: Schweinfurt, Haßberge +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Obermeister: Gerhard Götz +Ernst-Sachs-Straße 110 +97424 Schweinfurt +Tel.: 09721 89990 +E-Mail: baeckerei-goetz@web.de +05.12.2025 +=== PAGE 5 RIGHT === +de +4 +=== PAGE 6 LEFT === +Bäckerinnung Mainfranken +Landkreise: Würzburg, Main-Spessart +Ansprechpartnerin: Christine Winterbauer +Schießhausstraße 17 +97228 Rottendorf +Tel.: 09302 909431 +Fax: 09302 909434 +E-Mail: christine.winterbauer@baeckerbuc +Obermeister: Marcel Scherg +Rathausstraße 7 +97854 Steinfeld +Tel.: 09359 288 +Fax: 09359 8265 +E-Mail: scherge.beck@t-online.de +05.12.2025 +=== PAGE 6 RIGHT === +chstelle.de +5 +=== PAGE 7 LEFT === +Bau +Bauinnung Aschaffenburg +Landkreis: Aschaffenburg +Ansprechpartnerin: Nina Emeneth +Hasenhägweg 71 +63741 Aschaffenburg +Tel.: 06021 421086 +Fax: 06021 450684 +E-Mail: info@bauinnung-aschaffenburg.de +Internet: www.bauinnung-aschaffenburg.de +Obermeister: Felix Englert +c/o Theo Albert Bau GmbH +Am Heerbach 8 a +63857 Waldaschaff +E-Mail: felix.englert@bauinnung-aschaffen +Bauinnung Bad Kissingen / Rhön-Grabfeld +Landkreise: Bad Kissingen, Rhön-Grabfeld +Ansprechpartnerin: Petra Schwab +Sparkassenpassage 6 +97688 Bad Kissingen +Tel.: 0971 78536971 +Fax: 0971 78536972 +E-Mail: khw-kg@t-online.de +Obermeister: Stefan Goos +Brückenstraße 2 +97705 Burkardroth +Tel.: 09701 91020 +Fax: 09701 910222 +E-Mail: stefan.goos@zehe-gmbh.de +05.12.2025 +=== PAGE 7 RIGHT === +e +nburg.de +6 +=== PAGE 8 LEFT === +Bau-Innung Schweinfurt +Landkreise: Schweinfurt, Haßberge +Ansprechpartnerin: Ramona Ziegler +Galgenleite 3 a +97424 Schweinfurt +Tel.: 09721 74220 +Fax: 09721 742222 +E-Mail: info@bauinnung-schweinfurt.de +Internet: www.bauinnung-schweinfurt.de +Obermeister: Karl Böhner +Raiffeisenstraße 19 +97508 Grettstadt +Tel.: 09729 9226 +Fax: 09729 9227 +E-Mail: boehner-bau@t-online.de +Bauinnung Mainfranken - Würzburg +Landkreise: Würzburg, Kitzingen, Main-Spessart, M +Ansprechpartner: Manfred Dallner +Daimlerstraße 4 +97082 Würzburg +Tel.: 0931 454440 +Fax: 0931 4544419 +E-Mail: baugewerbe@lbb-unterfranken.de +Obermeister: Ralf Stegmeier +Strüther Straße 25 +97285 Röttingen +Tel.: 09338 1006 +Fax: 09338 309 +E-Mail: trendbaugmbh@aol.com +05.12.2025 +=== PAGE 8 RIGHT === +Miltenberg +e +7 +=== PAGE 9 LEFT === +Bekleidung +Innung des Bekleidungshandwerks Unterfranken +Landkreise: alle des Regierungsbezirks Unterfranke +Ansprechpartnerin: Nicole Brandler +Am Stiegel 1 +97762 Hammelburg +Tel.: 09732 2277 +E-Mail: info@innung-unterfranken.de +Internet: www.innung-unterfranken.de +Obermeisterin: Friedrun Schlagbauer-Werner +Kister Straße 11 a +97204 Höchberg +Tel.: 0173 3249788 +E-Mail: info@schneiderin-wuerzburg.de +05.12.2025 +=== PAGE 9 RIGHT === +en +8 +=== PAGE 10 LEFT === +Brauer und Mälzer +Brauer- und Mälzerinnung Unterfranken +Landkreise: alle des Regierungsbezirks Unterfranke +Brauer- und Mälzerinnung Unterfranken +c/o Private Brauereien Bayern e.V. +Thomas-Wimmer-Ring 9 +80539 München +Tel.: 089 2909560 +Fax: 089 220179 +E-Mail: info@private-brauereien-bayern.de +Obermeister: Josef Göller +Speiersgasse 21 +97475 Zeil +E-Mail: geschaeftsleitung@brauerei-goelle +05.12.2025 +=== PAGE 10 RIGHT === +en +e +er.de +9 +=== PAGE 11 LEFT === +Dachdecker +Dachdeckerinnung Aschaffenburg - Miltenberg +Landkreise: Aschaffenburg, Miltenberg +Kahlgrundstraße 121 +63776 Mömbris +Tel.: 0160 8480279 +Fax: 06029 5910 +E-Mail: lukas.thalheimer@thalheimer.de +Internet: www.dachdecker-innung-aschaffen +Obermeister: Lukas Thalheimer +Kahlgrundstraße 121 +63776 Mömbris +Tel.: 0160 8480279 +Fax: 06029 5910 +E-Mail: lukas.thalheimer@thalheimer.de +Dachdeckerinnung Unterfranken +Landkreise: Würzburg, Kitzingen, Main-Spessart, Sc +berge +Birkachstrasse 6 +97320 Albertshofen +Tel.: 09321 3905830 +E-Mail: info@mein-dachdecker.com +Internet: www.dachdecker-unterfranken.de +Obermeister: Timo Markert +Birkachstrasse 6 +97320 Albertshofen +Tel.: 09321 3905830 +E-Mail: info@mein-dachdecker.com +05.12.2025 +=== PAGE 11 RIGHT === +nburg-miltenberg.de +chweinfurt, Bad Kissingen, Rhön-Grabfeld, Haß- +10 +=== PAGE 12 LEFT === +Elektro +Innung für Elektro- und Informationstechnik Bayer +Landkreise: Aschaffenburg, Miltenberg +Ansprechpartnerin: Annett Kinzel +Hasenhägweg 71 +63741 Aschaffenburg +Tel.: 06021 480331 +Fax: 06021 411619 +E-Mail: info@elektroinnung-bayerischerun +Obermeister: Edwin Palzer +Hauptstraße 26 +63773 Goldbach +Innung für Elektro- und Informationstechnik Haßb +Landkreis: Haßberge +Ansprechpartnerin: Gitta Klopf +Martinsweg 4 +97491 Aidhausen +Tel.: 09526 8250 +E-Mail: info@elektroinnung-hassberge.de +Obermeister: Ralf Jooß +Ringstraße 15 +97461 Hofheim +Tel.: 09523 7348 +Fax: 09523 501218 +E-Mail: info@elektro-jooss.de +05.12.2025 +=== PAGE 12 RIGHT === +rischer Untermain +ntermain.de +berge +11 +=== PAGE 13 LEFT === +Innung für Elektro- und Informationstechnik Schw +Landkreise: Schweinfurt, Bad Kissingen, Rhön-Grab +Ansprechpartner: Gaby Fröschel, Roland Klöffel, Ro +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 41175 +Fax: 09721 41211 +E-Mail: info@elektroinnung-sw.de +Internet: www.elektroinnung-sw.de +Obermeister: Rainer Walter-Helk +Oberwerrner Weg 34 +97502 Euerbach +Tel.: 09726 905500 +Fax: 09726 9055019 +E-Mail: rwalter-helk@innotech-solar.de +Innung für Elektro- und Informationstechnik Würz +Landkreise: Würzburg, Kitzingen, Main-Spessart +Ansprechpartnerin: Heike Langner +Daimlerstraße 7 +97082 Würzburg +Tel.: 0931 4501790 +Fax: 0931 45017999 +E-Mail: mailbox@elektro-innung-wuerzbur +Internet: www.elektro-innung-wuerzburg.de +Obermeister: Sebastian Seynstahl +Rudolf-Diesel-Straße 8 +97318 Kitzingen +Tel.: 09321 36745 +E-Mail: se@elektro-seynstahl.de +05.12.2025 +=== PAGE 13 RIGHT === +weinfurt +bfeld +onald Niessner +zburg +rg.de +e +12 +=== PAGE 14 LEFT === +Fotografen +Berufsfotografen-Innung für Unterfranken +Landkreise: alle des Regierungsbezirks Unterfranke +Alexandrastraße 1 +63739 Aschaffenburg +Tel.: 06021 23807 +E-Mail: info@foto-alfen.de +Obermeister: Michael Alfen +Alexandrastraße 1 +63739 Aschaffenburg +Tel.: 06021 23807 +E-Mail: info@foto-alfen.de +05.12.2025 +=== PAGE 14 RIGHT === +en +13 +=== PAGE 15 LEFT === +Friseure +Friseur-Innung Aschaffenburg Stadt und Land +Landkreis: Aschaffenburg +Cunibertstraße 1 +63739 Aschaffenburg +Tel.: 06021 12646 +Fax: 06021 12664 +E-Mail: friseurinnung-aschaffenburg@t-onl +Internet: www.friseurinnung-aschaffenburg. +Obermeisterin: Corina Bayer +Raingartenstraße 4 +63825 Schöllkrippen +Tel.: 06024 2002 +E-Mail: cbm.bayer@t-online.de +Friseur-Innung Haßberge +Landkreis: Haßberge +Ansprechpartner: Heinz Göhr +Herrensteige 9 +97500 Ebelsbach +Tel.: 09522 7948 +E-Mail: info@team-art-of-hair.com +Obermeister: Oliver Merkl +Bahnhofstraße 2 +97437 Haßfurt +Tel.: 09521 3890 +E-Mail: info@team-art-of-hair.com +05.12.2025 +=== PAGE 15 RIGHT === +line.de +.de +14 +=== PAGE 16 LEFT === +Friseurinnung Kitzingen +Landkreis: Kitzingen +Hindenburgstraße 6 +97320 Albertshofen +Tel.: 09321 389988 +E-Mail: sabine.hack71@web.de +Internet: www.friseurinnung-kitzingen.de +Obermeisterin: Sabine Hack +Hindenburgstraße 6 +97320 Albertshofen +Tel.: 09321 389988 +E-Mail: sabine.hack71@web.de +Friseurinnung Miltenberg +Landkreis: Miltenberg +Marktstraße 26 +63924 Kleinheubach +Tel.: 0160 1405397 +E-Mail: info@haarmonique.de +Obermeisterin: Monique Haas +Marktstraße 26 +63924 Kleinheubach +Tel.: 0160 1405397 +E-Mail: info@haarmonique.de +05.12.2025 +=== PAGE 16 RIGHT === +15 +=== PAGE 17 LEFT === +Friseurinnung Main-Rhön +Landkreise: Schweinfurt, Rhön-Grabfeld +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Internet: www.friseurinnung-main-rhoen.de +Obermeisterin: Margit Rosentritt +Hadergasse 6 +97421 Schweinfurt +Tel.: 09721 22727 +Friseur-Innung Würzburg, Main-Spessart und Bad +Landkreise: Würzburg, Main-Spessart, Bad Kissinge +Ludwigstraße 1 a +97070 Würzburg +Tel.: 0931 4605400 +E-Mail: katharinawalker88@gmx.de +Internet: www.wuerzburg-friseurinnung.de +Obermeisterin: Katharina Walker +Ludwigstraße 1 a +97070 Würzburg +Tel.: 0931 4605400 +E-Mail: katharinawalker88@gmx.de +05.12.2025 +=== PAGE 17 RIGHT === +de +e +Kissingen +en +16 +=== PAGE 18 LEFT === +Glaser +Glaserinnung Unterfranken +Landkreise: alle des Regierungsbezirks Unterfranke +Obere Neue Gasse 15 +97318 Kitzingen +Tel.: 09321 31890 +Fax: 09321 33523 +E-Mail: info@frank-bauglaserei.de +Obermeister: Siegfried Frank +Flugplatzstraße 4 +97318 Kitzingen +Tel.: 09321 31890 +Fax: 09321 33523 +E-Mail: info@frank-bauglaserei.de +05.12.2025 +=== PAGE 18 RIGHT === +en +17 +=== PAGE 19 LEFT === +Kaminkehrer (Schornsteinfeger) +Kaminkehrer-Innung Unterfranken +Landkreise: alle des Regierungsbezirks Unterfranke +Kirchplatz 3 +97228 Rottendorf +Tel.: 09302 2187 +E-Mail: info@kaminkehrerinnung-unterfran +Internet: www.kaminkehrerinnung-unterfran +Obermeister: Benjamin Schreck +Buchrain 1 a +63872 Heimbuchenthal +Tel.: 06092 821252 +Fax: 06092 821253 +E-Mail: benjamin.schreck@t-online.de +05.12.2025 +=== PAGE 19 RIGHT === +en +nken.de +nken.de +18 +=== PAGE 20 LEFT === +Karosserie- und Fahrzeugbauer +Karosserie- und Fahrzeugtechnik Innung Unterfran +Landkreise: alle des Regierungsbezirks Unterfranke +Ansprechpartner: Manuela Wohlert +Rosenplütstraße 2 +90439 Nürnberg +Tel.: 0911 2358880 +Fax: 0911 2358885 +E-Mail: info@khw-nuernberg.de +Internet: www.khw-nuernberg.de +Obermeister: Michael Seidel +Johann-Dahlem-Straße 25 +63814 Mainaschaff +Tel.: 06021 27406 +Fax: 06021 15300 +E-Mail: info@seidel-karosserie.de +05.12.2025 +=== PAGE 20 RIGHT === +nken +en +19 +=== PAGE 21 LEFT === +Kraftfahrzeugtechniker +Kfz-Innung Unterfranken +Landkreise: alle des Regierungsbezirks Unterfranke +Ansprechpartner: Michael Frank +Sandäcker 10 +97076 Würzburg +Tel.: 0931 279910 +Fax: 0931 2799140 +E-Mail: info@kfz-innung-ufr.de +Internet: www.kfz-innung-ufr.de +Obermeister: Roland Hoier +Scheffelstraße 1 +97209 Veitshöchheim +E-Mail: roland.hoier@autohaus-keller.de +05.12.2025 +=== PAGE 21 RIGHT === +en +20 +=== PAGE 22 LEFT === +Landmaschinenmechaniker +Innung für Land- und Baumaschinentechnik Unter +Landkreise: alle des Regierungsbezirks Unterfranke +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +Fax: 09721 473576 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Obermeister: Bertram Muth +Hutweg 5 +97789 Oberleichtersbach +Tel.: 09741 5573 +E-Mail: bertram.muth@outlook.de +05.12.2025 +=== PAGE 22 RIGHT === +rfranken +en +de +21 +=== PAGE 23 LEFT === +Maler +Maler -, Tüncher- und Lackierer Innung Alzenau +Landkreis: Alt-Landkreis Alzenau +Gunkelsrainstraße 10 +63755 Alzenau +Tel.: 06023 8736 +Fax: 06023 32372 +E-Mail: maler_trageser@gmx.de +Obermeister: Karlheinz Trageser +Gunkelsrainstraße 10 +63755 Alzenau +Tel.: 06023 8736 +Fax: 06023 32372 +E-Mail: maler_trageser@gmx.de +Maler- und Lackierer-Innung Aschaffenburg Stadt +Landkreis: Aschaffenburg +Lauestraße 5 +63741 Aschaffenburg +Tel.: 06021 859120 +Fax: 06021 859133 +E-Mail: uta.kern@kolb-kern.de +Obermeister: Ansgar Kern +Lauestraße 5 +63741 Aschaffenburg +Tel.: 06021 859120 +Fax: 06021 859133 +E-Mail: ansgar.kern@kolb-kern.de +05.12.2025 +=== PAGE 23 RIGHT === +und Land +22 +=== PAGE 24 LEFT === +Maler- und Lackiererinnung Bad Kissingen +Landkreis: Bad Kissingen +Ansprechpartnerin: Petra Schwab +Sparkassenpassage 6 +97688 Bad Kissingen +Tel.: 0971 78536971 +Fax: 0971 78536972 +E-Mail: khw-kg@t-online.de +Obermeister: Mathias Stöth +Schweinfurter Straße 14 +97727 Fuchsstadt +Tel.: 09732 5658 +Fax: 09732 5014 +E-Mail: mathias@stoeth-fuchsstadt.de +Maler- und Tüncherinnung Haßberge +Landkreis: Haßberge +Hauptstraße 12 +97496 Burgpreppach +Tel.: 09534 173330 +Fax: 09534 1700171 +E-Mail: obermeister@malerinnung-hassber +Obermeister: Michael Ott +Hauptstraße 12 +97496 Burgpreppach +Tel.: 09534 173330 +Fax: 09534 1700171 +05.12.2025 +=== PAGE 24 RIGHT === +rge.de +23 +=== PAGE 25 LEFT === +Maler- und Lackiererinnung Kitzingen +Landkreis: Kitzingen +Ansprechpartner: Sandra und Andreas Zobel +Gartenstraße 18 +97334 Sommerach +Tel.: 09381 9141 +Fax: 09381 4467 +E-Mail: info@malerinnung-kitzingen.de +Internet: www.malerinnung-kitzingen.de +Obermeister: Thomas Wandler +Crailsheimstraße 1 +97348 Rödelsee +Tel.: 09323 3006 +Fax: 09323 5006 +E-Mail: mail@maler-wandler.de +Maler- und Lackiererinnung Miltenberg +Landkreis: Miltenberg +Ansprechpartnerin: Melitta Becker +Pulverich 13 +63897 Miltenberg +Tel.: 09371 1090 +Fax: 09371 67141 +E-Mail: info@farbe-miltenberg.de +Internet: www.farbe-miltenberg.de +Obermeister: Jan Becker +Pulverich 13 +63897 Miltenberg +Tel.: 09371 1090 +Fax: 09371 67141 +05.12.2025 +=== PAGE 25 RIGHT === +24 +=== PAGE 26 LEFT === +Maler-, Tüncher- und Lackierer-Innung Rhön-Grab +Landkreis: Rhön-Grabfeld +Ansprechpartnerin: Birgit Neuhöfer +Hauptstraße 23 +97633 Großbardorf +Tel.: 09766 1555 +Fax: 09766 1515 +E-Mail: info@malerinnung-rg.de +Internet: www.malerinnung-rhoen-grabfeld. +Obermeister: Stefan Neuhöfer +Hauptstraße 23 +97633 Großbardorf +Tel.: 09766 1555 +Fax: 09766 1515 +E-Mail: info@malerinnung-rg.de +Malerinnung Schweinfurt Stadt- und Land +Landkreis: Schweinfurt +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Obermeister: Andreas Spath +Am Bonland 4 +97464 Oberwerrn +Tel.: 0171 1479629 +Fax: 09726 9218 +E-Mail: info@spath-tuencher.de +05.12.2025 +=== PAGE 26 RIGHT === +bfeld +.de +de +25 +=== PAGE 27 LEFT === +Maler- und Stuckateur-Innung Würzburg und Main +Landkreise: Würzburg, Main-Spessart +Ansprechpartner: Claudius Wolfrum +Ungsteiner Straße 27 +81539 München +Tel.: 0931 54306 +Fax: 089 600876635 +E-Mail: info@malerinnung-wuerzburg.de +Internet: www.malerinnung-wuerzburg.de +Obermeister: Peter Killinger +Fahrentalstraße 6 +97261 Güntersleben +Tel.: 09365 881068 +E-Mail: info@manufatture-colori.de +05.12.2025 +=== PAGE 27 RIGHT === +n-Spessart +26 +=== PAGE 28 LEFT === +Metall +Innung Metallbau- und Feinwerktechnik Bayerisch +Landkreise: Aschaffenburg, Miltenberg +Ansprechpartnerinnen: Claudia Find, Stefanie Belle +Hasenhägweg 71 +63741 Aschaffenburg +Tel.: 06021 401286 +Fax: 06021 412386 +E-Mail: info@innung-metallbau-feinwerkte +Internet: www.innung-metallbau-feinwerkte +Obermeister: Matthias Kreß +Hemsbach 7 e +63776 Mömbris +E-Mail: m.kress@wassermannkress.de +Metall-Innung Bad Kissingen/Rhön-Grabfeld +Landkreise: Bad Kissingen, Rhön-Grabfeld +Ansprechpartnerin: Petra Schwab +Sparkassenpassage 6 +97688 Bad Kissingen +Tel.: 0971 78536971 +Fax: 0971 78536972 +E-Mail: khw-kg@t-online.de +Internet: www.metallinnung-kg-nes.de +Obermeister: Klaus Engelmann +An der Sauerwiese 20 +97618 Wollbach +Tel.: 09773 6780 +Fax: 09773 5595 +E-Mail: info@metallbau-engelmann.de +05.12.2025 +=== PAGE 28 RIGHT === +her Untermain +e +echnik.de +echnik.de +27 +=== PAGE 29 LEFT === +Metallinnung Schweinfurt - Haßberge +Landkreise: Schweinfurt, Haßberge +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Obermeister: René Dauelsberg +Haßbergring 19 +97461 Hofheim +Tel.: 0171 2060932 +E-Mail: rd@mpi-dauelsberg.de +Metallinnung Mainfranken - Mitte +Landkreise: Würzburg, Kitzingen, Main-Spessart +Ansprechpartnerin: Birgit Beckmann +Dieselstraße 10 +97082 Würzburg +Tel.: 0931 412614 +Fax: 0931 412655 +E-Mail: info@metallinnung-mainfranken.de +Internet: www.metallinnung-mainfranken.de +Obermeister: Detlef Lurz +Raiffeisenstraße 6 +97209 Veitshöchheim +Tel.: 0931 329780 +Fax: 0931 3297813 +E-Mail: detlef.lurz@lurz-metalltec.de +05.12.2025 +=== PAGE 29 RIGHT === +de +e +e +28 +=== PAGE 30 LEFT === +Metzger (Fleischer) +Metzgerinnung Aschaffenburg +Landkreis: Aschaffenburg +Ansprechpartner: Dagobert Pfarr +Schillerstraße 71 +63801 Kleinostheim +Tel.: 06027 8468 +Fax: 06027 8473 +E-Mail: metzgerei-pfarr@t-online.de +Obermeister: Marco Häuser +Friedrich-Wilhelm-Dükerstraße 1 +63846 Laufach +Tel.: 06021 3633 +E-Mail: marco.haeuser@haeuser-hra.de +Fleischer-Innung Main-Spessart +Landkreis: Main-Spessart +Ansprechpartner: Sebastian Bumm +Hauptstraße 32 +97855 Triefenstein +E-Mail: fleischerinnungMSP@gmx.de +Stellvertretender Obermeister: Eberhard Bumm (ko +Hauptstraße 32 +97855 Triefenstein +Tel.: 09395 363 +Fax: 09395 997924 +E-Mail: metzgerei-bumm@t-online.de +05.12.2025 +=== PAGE 30 RIGHT === +ommissarische Leitung) +29 +=== PAGE 31 LEFT === +Metzgerinnung Miltenberg +Landkreis: Miltenberg +Hauptstraße 5 +63927 Bürgstadt +Tel.: 09371 2671 +Fax: 09371 2810 +E-Mail: j.neuberger@t-online.de +Obermeister: Josef Neuberger +Hauptstraße 5 +63927 Bürgstadt +Tel.: 09371 2671 +Fax: 09371 2810 +E-Mail: j.neuberger@t-online.de +Metzgerinnung Main-Rhön +Landkreise: Schweinfurt, Bad Kissingen, Rhön-Grab +Ansprechpartner: Jürgen Straub, Sonja Grob +Straßburgstraße 8 +97424 Schweinfurt +Tel.: 09721 65050 +Fax: 09721 650555 +E-Mail: innung@fleischerring.de +Obermeisterin: Barbara Fink +Ludwigstraße 32 +97650 Fladungen +Tel.: 09778 215 +E-Mail: metzgerei_dros_fladungen@outloo +05.12.2025 +=== PAGE 31 RIGHT === +bfeld, Haßberge, Kitzingen +ok.de +30 +=== PAGE 32 LEFT === +Metzger-Innung Würzburg +Landkreis: Würzburg +Franz-Ludwig-Straße 1 +97072 Würzburg +Tel.: 0931 73926 +Fax: 0931 886217 +E-Mail: horst@schoemig.eu +Obermeister: Horst Schömig +Franz-Ludwig-Straße 1 +97072 Würzburg +Tel.: 0931 73920 +Fax: 0931 886217 +E-Mail: horst.schoemig@arcor.de +05.12.2025 +=== PAGE 32 RIGHT === +31 +=== PAGE 33 LEFT === +Ofen- und Luftheizungsbauer +Unterfränkische Ofen- und Luftheizungsbauer-Inn +Landkreise: alle des Regierungsbezirks Unterfranke +Ansprechpartner: Josef Bock +Matthäus-Stäblein-Straße 5 +97424 Schweinfurt +Tel.: 0175 8855931 +E-Mail: josef.bock60@gmail.com +Internet: www.kachelofenbau-ufr.de +Obermeister: Michael Heigel +Bussardweg 44 +63741 Aschaffenburg +E-Mail: heigel@heigel.de +05.12.2025 +=== PAGE 33 RIGHT === +nung +en +32 +=== PAGE 34 LEFT === +Raumausstatter +Raumausstatter- und Sattlerinnung Unterfranken +Landkreise: alle des Regierungsbezirks Unterfranke +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Obermeister: Hermann Noske +Waldstraße 1 +97618 Heustreu +Tel.: 09773 8431 +Fax: 09773 8428 +E-Mail: mail@sofa-shop.de +05.12.2025 +=== PAGE 34 RIGHT === +en +de +33 +=== PAGE 35 LEFT === +Sanitär, Heizung und Klima +Spengler-, Sanitär- und Heizungstechnik Innung As +Landkreise: Aschaffenburg, Miltenberg +Ansprechpartner: Michael Bramm +Cunibertstraße 1 +63739 Aschaffenburg +Tel.: 06021 28731 +Fax: 06021 28732 +E-Mail: shk-aschaffenburg@t-online.de +Internet: www.shk-aschaffenburg.de +Obermeister: Christoph Winkler +Eichenbühler Straße 79 +63927 Bürgstadt +Tel.: 09371 959391 +Fax: 09371 959226 +E-Mail: c.winkler@cw-haustechnik.de +Innung für Spengler-, Sanitär-, und Heizungstechni +Landkreis: Kitzingen +Ansprechpartnerin: Christine Keppner-Siegert +Sonnenstraße 32 +97359 Schwarzach am Main +Tel.: 09324 978667 +Fax: 09324 978668 +E-Mail: innung-kitzingen@freenet.de +Obermeister: Thomas Lößlein +An der Ziegelhütte 16 +97320 Mainstockheim +Tel.: 09321 921300 +Fax: 09321 921311 +E-Mail: thomas_loesslein@t-online.de +05.12.2025 +=== PAGE 35 RIGHT === +schaffenburg - Miltenberg +ik Kitzingen +34 +=== PAGE 36 LEFT === +SHK-Innung Main-Spessart Innung Sanitär-, Heizun +Landkreis: Main-Spessart +Wiesener Straße 99 +97833 Frammersbach +Tel.: 09355 97400 +Fax: 09355 99776 +E-Mail: info@shk-main-spessart.de +Internet: www.shk-main-spessart.de +Obermeister: Johannes Reber +Wiesener Straße 99 +97833 Frammersbach +Tel.: 09355 97400 +Fax: 09355 99776 +E-Mail: info@shk-main-spessart.de +Innung für Spengler-, Sanitär-, Heizungs- und Klim +Landkreise: Schweinfurt, Bad Kissingen, Rhön-Grab +Ansprechpartner: Stefan Köppe +Matthäus-Stäblein-Straße 5 +97424 Schweinfurt +Tel.: 09721 471526 +Fax: 09721 471529 +E-Mail: info@shk-schweinfurt.de +Internet: www.shk-schweinfurt.de +Obermeister: Heinz Schuchbauer +Schuhstraße 4 - 6 +97447 Gerolzhofen +05.12.2025 +=== PAGE 36 RIGHT === +ngs- und Klimatechnik +matechnik Schweinfurt - Main-Rhön +bfeld, Haßberge +35 +=== PAGE 37 LEFT === +Innung für Sanitär-, Heizungs-, Klempner- und Klim +Landkreis: Würzburg +Ansprechpartnerin: Sandra Köller +Virchowstraße 1 b Rückgebäude +97072 Würzburg +Tel.: 0931 7841878 +Fax: 0931 7841843 +E-Mail: innung.shk@t-online.de +Obermeister: Werner Rath +Obere Straße 1 +97262 Hausen +Tel.: 09722 4550 +Fax: 09722 4540 +05.12.2025 +=== PAGE 37 RIGHT === +matechnik Würzburg +36 +=== PAGE 38 LEFT === +Schreiner (Tischler) +Schreinerinnung Aschaffenburg Stadt und Land +Landkreis: Aschaffenburg +Hauptstraße 157 a +63864 Glattbach +Tel.: 06021 460428 +Fax: 06021 423868 +E-Mail: info@dellers-werkstatt.de +Obermeister: Michael Deller +Hauptstraße 157 a +63864 Glattbach +Tel.: 06021 460428 +Fax: 06021 423868 +E-Mail: info@dellers-werkstatt.de +Schreinerinnung Bad Kissingen +Landkreis: Bad Kissingen +Ansprechpartnerin: Petra Schwab +Sparkassenpassage 6 +97688 Bad Kissingen +Tel.: 0971 785369 71 +Fax: 0971 785369 72 +E-Mail: khw-kg@t-online.de +Internet: www.schreiner-kg.de +Obermeister: Norbert Borst +Alter Dorfring 46 +97688 Bad Kissingen +Tel.: 0971 66466 +Fax: 0971 69119 +E-Mail: service@norbert-borst.de +05.12.2025 +=== PAGE 38 RIGHT === +37 +=== PAGE 39 LEFT === +Schreinerinnung Miltenberg-Obernburg +Landkreis: Miltenberg +Freudenberger Straße 56 - 58 +63927 Bürgstadt +Tel.: 09371 97770 +Fax: 09371 977725 +E-Mail: wt@reichert-betten.de +Obermeister: Werner Tausch +Goethestraße 15 +63927 Bürgstadt +Tel.: 09371 97770 +Fax: 09371 977725 +E-Mail: wt@reichert-betten.de +Schreinerinnung Rhön-Grabfeld +Landkreis: Rhön-Grabfeld +Strutweg 15 +97653 Bischofsheim +Tel.: 09772 9300990 +Fax: 09772 93009930 +E-Mail: info@schreiner-rhoen-grabfeld.de +Obermeister: Michael Werner +Brendstraße 23 +97653 Bischofsheim +Tel.: 09772 93009913 +Fax: 09772 93009930 +E-Mail: michael@werner-objekteinrichtung +05.12.2025 +=== PAGE 39 RIGHT === +gen.de +38 +=== PAGE 40 LEFT === +Schreinerinnung Haßberge – Schweinfurt +Landkreise: Schweinfurt, Haßberge +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Obermeister: Horst Zitterbart +Bergstraße 11 +97478 Knetzgau +Tel.: 09527 1533 +Fax: 09527 7034 +E-Mail: schreinerei.zitterbart@t-online.de +Schreinerinnung Mainfranken +Landkreise: Würzburg, Kitzingen, Main-Spessart +Ansprechpartnerin: Ramona Pfenning +Am Rain 6 +97852 Schollbrunn +Tel.: 09394 9957944 +E-Mail: info@schreinerinnung-mainfranken +Internet: www.schreinerinnung-mainfranken +Obermeister: Thomas Heußlein +Reiterwiesen 3 +97834 Billingshausen +Tel.: 09398 221 +Fax: 09398 713 +E-Mail: info@schreinerei-heusslein.de +05.12.2025 +=== PAGE 40 RIGHT === +de +n.de +n.de +39 +=== PAGE 41 LEFT === +Schuhmacher +Schuhmacherinnung Unterfranken +Landkreise: alle des Regierungsbezirks Unterfranke +Friedhofstraße 2 +63741 Aschaffenburg +Tel.: 06021 423235 +Fax: 06021 4449416 +E-Mail: l.emge@t-online.de +Obermeister: Leo Emge +Friedhofstraße 2 +63741 Aschaffenburg +Tel.: 06021 423235 +Fax: 06021 4449416 +E-Mail: l.emge@t-online.de +05.12.2025 +=== PAGE 41 RIGHT === +en +40 +=== PAGE 42 LEFT === +Steinmetzen und Steinbildhauer +Steinmetz- und Steinbildhauerinnung Unterfranke +Landkreise: alle des Regierungsbezirks Unterfranke +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Internet: www.steinmetz-unterfranken.de +Obermeister: Sebastian Ludwig +Martin-Luther-Straße 11 +97072 Würzburg +Tel.: 0931 52803 +Fax 0931 52150 +E-Mail: info@geisendoerfer-online.de +05.12.2025 +=== PAGE 42 RIGHT === +en +en +de +41 +=== PAGE 43 LEFT === +Uhrmacher, Gold- und Silberschmiede +Uhrmacher-, Gold- und Silberschmiedeinnung Unt +Landkreise: alle des Regierungsbezirks Unterfranke +Ansprechpartner: Markus Graf +Rennweger Ring 3 +97070 Würzburg +Tel.: 0931 309081132 +Fax: 0931 309081632 +E-Mail: m.graf@hwk-ufr.de +Obermeister: Klaus Imhof +Aschaffenburger Straße 32 +63825 Schöllkrippen +Tel.: 06024 9470 +E-Mail: kontakt@juwelier-imhof.de +05.12.2025 +=== PAGE 43 RIGHT === +terfranken +en +42 +=== PAGE 44 LEFT === +Zimmerer +Zimmerer-Innung Aschaffenburg - Miltenberg +Landkreise: Aschaffenburg, Miltenberg +Ansprechpartnerin: Theresa Breunig +Lange Straße 2 a +63860 Rothenbuch +Tel.: 06094 1361 +Fax: 06094 8105 +E-Mail: info@zimmerer-aschaffenburg-milt +Internet: www.zimmerer-aschaffenburg-milt +Obermeister: Jürgen Pfarr +Frankenstraße 19 +63776 Mömbris +Tel.: 06029 997966 +Fax: 06029 997967 +E-Mail: info@zimmerei-juergen-pfarr.de +Zimmerer-Innung Bad Neustadt +Landkreise: Bad Kissingen, Rhön-Grabfeld +Schlimpfhoferstraße 4 +97723 Oberthulba +Tel.: 09736 223 +Fax: 09736 1481 +E-Mail: info@holzbau-eyrich.de +Internet: www.zimmerer-neustadt-kissingen +Obermeister: Michael Eyrich-Halbig +Schlimpfhoferstraße 4 +97723 Oberthulba +Tel.: 09736 223 +Fax: 09736 1481 +E-Mail: info@holzbau-eyrich.de +05.12.2025 +=== PAGE 44 RIGHT === +tenberg.de +tenberg.de +n.de +43 +=== PAGE 45 LEFT === +Zimmerer-Innung Main-Spessart +Landkreis: Main-Spessart +Ansprechpartnerin: Ulrike Feser +Bentheimstraße 9 +97072 Würzburg +Tel.: 0931 72760 +Fax: 0931 72527 +E-Mail: info@zimmerer-wuerzburg-kitzinge +Internet: www.zimmerer-main-spessart.de +Obermeister: Volker Schäfer +Hemmenhausweg 13 +97816 Lohr +Tel.: 09359 344 +Fax: 09359 8254 +E-Mail: info@schaefer-halsbach.de +Zimmerer-Innung Schweinfurt-Haßberge +Landkreise: Schweinfurt, Haßberge +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +Fax: 09721 473576 +E-Mail: rapp@kreishandwerkerschaft-sw.d +Internet: www.zimmerer-schweinfurt.de +Obermeisterin: Marion Reichhold +Coburger Straße 8 +96126 Maroldsweisach +Tel.: 09567 247 +Fax: 09567 758 +E-Mail: zimmerei_klaus_treiber@gmx.de +05.12.2025 +=== PAGE 45 RIGHT === +en.de +de +44 +=== PAGE 46 LEFT === +Zimmerer-Innung Würzburg - Kitzingen +Landkreise: Würzburg, Kitzingen +Ansprechpartnerin: Ulrike Feser +Bentheimstraße 9 +97072 Würzburg +Tel.: 0931 72760 +Fax: 0931 72527 +E-Mail: info@zimmerer-wuerzburg-kitzinge +Internet: www.zimmerer-wuerzburg-kitzinge +Obermeister: Hermann Lang +Bestlenstraße 7 +97252 Frickenhausen +Tel.: 09331 2491 +Fax: 09331 20242 +E-Mail: lang@zimmerer-wuerzburg-kitzinge +05.12.2025 +=== PAGE 46 RIGHT === +en.de +en.de +en.de +45 +=== PAGE 47 LEFT === +Kreishandwerkerschaften +Kreishandwerkerschaft Aschaffenburg +Landkreis: Aschaffenburg +Ansprechpartnerin: Claudia Find +Hasenhägweg 71 +63741 Aschaffenburg +Tel.: 06021 401286 +Fax: 06021 412386 +E-Mail: info@khw-ab.de +Kreishandwerksmeister: Matthias Kreß +Hemsbach 7 e +63776 Mömbris +E-Mail: m.kress@wassermannkress.de +Kreishandwerkerschaft Bad Kissingen +Landkreis: Bad Kissingen +Ansprechpartnerin: Petra Schwab +Sparkassenpassage 6 +97688 Bad Kissingen +Tel.: 0971 78536971 +Fax: 0971 78536972 +E-Mail: khw-kg@t-online.de +Internet: www.khw-kg.de +Kreishandwerksmeisterin: Ulrike Lochner-Erhard +Jörgentorgasse 1 +97702 Münnerstadt +Tel.: 09733 781521 +Fax: 09733 781522 +E-Mail: lochner-baudekoration-gmbh@t-on +05.12.2025 +=== PAGE 47 RIGHT === +nline.de +46 +=== PAGE 48 LEFT === +Kreishandwerkerschaft Haßberge +Landkreis: Haßberge +Ansprechpartnerin: Gitta Klopf +Martinsweg 4 +97491 Aidhausen +E-Mail: info@elektroinnung-hassberge.de +Kreishandwerksmeister: Udo Merz +Krum 41 +97475 Zeil am Main +Tel.: 09524 7492 +Mobil: 0151 72460290 +E-Mail: u.merz@haustechnik-merz.de +Kreishandwerkerschaft Kitzingen +Landkreis: Kitzingen +Ansprechpartnerin: Elisabeth Hofmann +Marktbreiter Straße 4 a +97342 Obernbreit +Tel.: 09332 590636 +Fax: 09332 590479 +E-Mail: khw.kitzingen@gmail.com +Kreishandwerksmeisterin: Monika Henneberger +Schützenstraße 4 a +97350 Mainbernheim +Tel.: 09323 279 +Fax: 09323 5419 +E-Mail: monika.henneberger@t-online.de +05.12.2025 +=== PAGE 48 RIGHT === +47 +=== PAGE 49 LEFT === +Kreishandwerkerschaft Main-Spessart +Landkreis: Main-Spessart +Ansprechpartnerin: Petra Stegerwald +Sendelbacher Straße 10 a +97816 Lohr am Main +Tel.: 09352 6056495 +Fax: 0172 4294456 +E-Mail: petra@stegerwald.de +Kreishandwerksmeister: Thomas Heußlein +Reiterwiesen 5 +97834 Birkenfeld +Tel.: 09398 221 +Fax: 09398 713 +E-Mail: info@schreinerei-heusslein.de +Kreishandwerkerschaft Miltenberg +Landkreis: Miltenberg +Marktstraße 26 +63924 Kleinheubach +Tel.: 09371 6693681 +E-Mail: kreishandwerker.mil@gmail.com +Kreishandwerksmeisterin: Monique Haas +Marktstraße 26 +63924 Kleinheubach +E-Mail: monique3003@arcor.de +05.12.2025 +=== PAGE 49 RIGHT === +48 +=== PAGE 50 LEFT === +Kreishandwerkerschaft Rhön-Grabfeld +Landkreis: Rhön-Grabfeld +Strutweg 15 +97653 Bischofsheim +Tel.: 09772 9300990 +Fax: 09772 93009930 +E-Mail: khwsch-rhoen-grabfeld@mail.de +Kreishandwerksmeister: Bruno Werner +Strutweg 15 +97653 Bischofsheim +Tel.: 09772 9300990 +Fax: 09772 93009930 +E-Mail: info@werner-objekteinrichtungen. +Kreishandwerkerschaft Schweinfurt +Landkreis: Schweinfurt +Ansprechpartnerin: Brigitte Rapp +Galgenleite 3 +97424 Schweinfurt +Tel.: 09721 473578 +E-Mail: rapp@Kreishandwerkerschaft-sw.d +Internet: www.khw-sw.de +Kreishandwerksmeisterin: Margit Rosentritt +Hadergasse 6 +97421 Schweinfurt +Tel.: 09721 22727 +05.12.2025 +=== PAGE 50 RIGHT === +.de +de +49 +=== PAGE 51 LEFT === +Kreishandwerkerschaft Würzburg +Landkreis: Würzburg +Ansprechpartnerin: Sandra Köller +Virchowstraße 1 b Rückgebäude +97072 Würzburg +Tel.: 0931 7841879 +E-Mail: info@kreishandwerkerschaft-wuer +Internet: www.kreishandwerkerschaft-wuerz +Kreishandwerksmeister: Martin Strobl +Vorderer Talweg 31 +97228 Rottendorf +Tel.: 09302 2999 +Fax: 09302 2933 +E-Mail: innung@elektro-strobl.de +05.12.2025 +=== PAGE 51 RIGHT === +rzburg.de +zburg.de +50 \ No newline at end of file