fega-schmitt-client 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. fega_schmitt_client-0.0.2/.github/workflows/pypi-publish.yml +202 -0
  2. fega_schmitt_client-0.0.2/.github/workflows/test.yml +28 -0
  3. fega_schmitt_client-0.0.2/.gitignore +13 -0
  4. fega_schmitt_client-0.0.2/.pre-commit-config.yaml +25 -0
  5. fega_schmitt_client-0.0.2/LICENSE +21 -0
  6. fega_schmitt_client-0.0.2/PKG-INFO +150 -0
  7. fega_schmitt_client-0.0.2/README.md +127 -0
  8. fega_schmitt_client-0.0.2/docs/architecture.md +190 -0
  9. fega_schmitt_client-0.0.2/pyproject.toml +54 -0
  10. fega_schmitt_client-0.0.2/renovate.json +20 -0
  11. fega_schmitt_client-0.0.2/src/fega_schmitt_client/__init__.py +15 -0
  12. fega_schmitt_client-0.0.2/src/fega_schmitt_client/_soap.py +181 -0
  13. fega_schmitt_client-0.0.2/src/fega_schmitt_client/cli.py +141 -0
  14. fega_schmitt_client-0.0.2/src/fega_schmitt_client/exceptions.py +21 -0
  15. fega_schmitt_client-0.0.2/src/fega_schmitt_client/ids/__init__.py +34 -0
  16. fega_schmitt_client-0.0.2/src/fega_schmitt_client/ids/_xml.py +331 -0
  17. fega_schmitt_client-0.0.2/src/fega_schmitt_client/ids/cart.py +68 -0
  18. fega_schmitt_client-0.0.2/src/fega_schmitt_client/ids/models.py +154 -0
  19. fega_schmitt_client-0.0.2/src/fega_schmitt_client/models.py +56 -0
  20. fega_schmitt_client-0.0.2/src/fega_schmitt_client/price_avail.py +110 -0
  21. fega_schmitt_client-0.0.2/src/fega_schmitt_client/py.typed +0 -0
  22. fega_schmitt_client-0.0.2/tests/fixtures.py +84 -0
  23. fega_schmitt_client-0.0.2/tests/test_cli.py +81 -0
  24. fega_schmitt_client-0.0.2/tests/test_client.py +90 -0
  25. fega_schmitt_client-0.0.2/tests/test_ids_cart.py +68 -0
  26. fega_schmitt_client-0.0.2/tests/test_ids_xml.py +118 -0
  27. fega_schmitt_client-0.0.2/tests/test_models.py +24 -0
  28. fega_schmitt_client-0.0.2/tests/test_soap.py +115 -0
  29. fega_schmitt_client-0.0.2/uv.lock +574 -0
@@ -0,0 +1,202 @@
1
+ name: Create Release and Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - 'src/**'
9
+ - 'pyproject.toml'
10
+ - '.github/workflows/pypi-publish.yml'
11
+ workflow_dispatch:
12
+ inputs:
13
+ publish_target:
14
+ description: 'Publish target'
15
+ required: true
16
+ default: 'all'
17
+ type: choice
18
+ options:
19
+ - all
20
+ - pypi-only
21
+ - none
22
+
23
+ jobs:
24
+ version:
25
+ name: Calculate Version
26
+ runs-on: ubuntu-latest
27
+ outputs:
28
+ version: ${{ steps.semver.outputs.version }}
29
+ version_tag: ${{ steps.semver.outputs.version_tag }}
30
+
31
+ steps:
32
+ - uses: actions/checkout@v7
33
+ with:
34
+ fetch-depth: 0
35
+
36
+ - name: Calculate semantic version
37
+ id: semver
38
+ uses: paulhatch/semantic-version@v6.0.3
39
+ with:
40
+ tag_prefix: "v"
41
+ major_pattern: "/(feat|fix|refactor)!:/"
42
+ minor_pattern: "/^feat:/"
43
+ version_format: "${major}.${minor}.${patch}"
44
+ bump_each_commit: true
45
+ search_commit_body: true
46
+
47
+ - name: Show version
48
+ run: |
49
+ echo "Version: ${{ steps.semver.outputs.version }}"
50
+ echo "Version Tag: ${{ steps.semver.outputs.version_tag }}"
51
+ cat >> $GITHUB_STEP_SUMMARY <<EOF
52
+ ## 📦 Neue Version berechnet
53
+
54
+ **Version:** \`${{ steps.semver.outputs.version }}\`
55
+ **Git-Tag:** \`${{ steps.semver.outputs.version_tag }}\`
56
+ EOF
57
+
58
+ create-release:
59
+ name: Create GitHub Release
60
+ needs: [version]
61
+ runs-on: ubuntu-latest
62
+ permissions:
63
+ contents: write
64
+
65
+ steps:
66
+ - uses: actions/checkout@v7
67
+
68
+ - name: Create Release
69
+ env:
70
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71
+ run: |
72
+ gh release create ${{ needs.version.outputs.version_tag }} \
73
+ --title "fega-schmitt-client ${{ needs.version.outputs.version }}" \
74
+ --notes "## fega-schmitt-client ${{ needs.version.outputs.version }}
75
+
76
+ Python-Client fĂĽr die B2B-Schnittstellen von FEGA & Schmitt ElektrogroĂźhandel.
77
+
78
+ ### Installation
79
+
80
+ \`\`\`bash
81
+ pip install fega-schmitt-client==${{ needs.version.outputs.version }}
82
+ # oder als CLI-Tool:
83
+ uv tool install fega-schmitt-client==${{ needs.version.outputs.version }}
84
+ \`\`\`
85
+ " \
86
+ --latest
87
+
88
+ cat >> $GITHUB_STEP_SUMMARY <<EOF
89
+ ## 🏷️ GitHub Release erstellt
90
+
91
+ **Release:** [${{ needs.version.outputs.version_tag }}](https://github.com/${{ github.repository }}/releases/tag/${{ needs.version.outputs.version_tag }})
92
+ EOF
93
+
94
+ build:
95
+ name: Build fega-schmitt-client distribution
96
+ needs: [version]
97
+ if: |
98
+ (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
99
+ (github.event_name == 'workflow_dispatch' &&
100
+ (github.event.inputs.publish_target == 'all' || github.event.inputs.publish_target == 'pypi-only'))
101
+ runs-on: ubuntu-latest
102
+
103
+ steps:
104
+ - uses: actions/checkout@v7
105
+
106
+ - name: Set up Python
107
+ uses: actions/setup-python@v7
108
+ with:
109
+ python-version: "3.11"
110
+
111
+ - name: Install build dependencies
112
+ run: |
113
+ python -m pip install --upgrade pip
114
+ pip install build twine
115
+
116
+ - name: Set package version
117
+ # GOTCHA: pyproject.toml has a plain static `version = "..."` under
118
+ # [project] (no hatch-vcs/dynamic versioning) - `hatch version <x>`
119
+ # refuses to touch it, so the version line is rewritten directly.
120
+ run: |
121
+ python3 -c "
122
+ import re, pathlib
123
+ path = pathlib.Path('pyproject.toml')
124
+ text = path.read_text()
125
+ new_text, count = re.subn(r'(?m)^version = \"[^\"]*\"', 'version = \"${{ needs.version.outputs.version }}\"', text, count=1)
126
+ assert count == 1, 'expected exactly one version = \"...\" line in [project]'
127
+ path.write_text(new_text)
128
+ "
129
+ grep '^version = ' pyproject.toml
130
+
131
+ - name: Build package
132
+ run: python -m build
133
+
134
+ - name: Check distribution
135
+ run: |
136
+ twine check dist/*
137
+ cat >> $GITHUB_STEP_SUMMARY <<EOF
138
+ ## 🔨 fega-schmitt-client Build
139
+
140
+ **Version:** \`${{ needs.version.outputs.version }}\`
141
+ \`\`\`
142
+ $(ls -lh dist/)
143
+ \`\`\`
144
+ EOF
145
+
146
+ - name: Upload artifacts
147
+ uses: actions/upload-artifact@v7
148
+ with:
149
+ name: fega-schmitt-client-dist
150
+ path: dist/
151
+
152
+ publish-to-pypi:
153
+ name: Publish fega-schmitt-client to PyPI
154
+ needs: [version, build]
155
+ runs-on: ubuntu-latest
156
+
157
+ environment:
158
+ name: pypi
159
+ url: https://pypi.org/p/fega-schmitt-client
160
+
161
+ permissions:
162
+ id-token: write # required for OIDC Trusted Publishing - no API token/secret needed
163
+
164
+ steps:
165
+ - name: Download distributions
166
+ uses: actions/download-artifact@v8
167
+ with:
168
+ name: fega-schmitt-client-dist
169
+ path: dist/
170
+
171
+ - name: Publish to PyPI
172
+ uses: pypa/gh-action-pypi-publish@release/v1
173
+
174
+ - name: Add PyPI Summary
175
+ run: |
176
+ cat >> $GITHUB_STEP_SUMMARY <<EOF
177
+ ## 🚀 fega-schmitt-client PyPI Veröffentlichung
178
+
179
+ **Package:** [fega-schmitt-client](https://pypi.org/project/fega-schmitt-client/)
180
+ **Version:** \`${{ needs.version.outputs.version }}\`
181
+ EOF
182
+
183
+ summary:
184
+ name: Release Summary
185
+ needs: [version, create-release, build, publish-to-pypi]
186
+ if: always()
187
+ runs-on: ubuntu-latest
188
+
189
+ steps:
190
+ - name: Create Summary
191
+ run: |
192
+ cat >> $GITHUB_STEP_SUMMARY <<EOF
193
+ ## 🎉 Release Workflow Abgeschlossen
194
+
195
+ **Version:** \`${{ needs.version.outputs.version }}\`
196
+ **Git Tag:** \`${{ needs.version.outputs.version_tag }}\`
197
+
198
+ ### Status
199
+
200
+ - GitHub Release: ${{ needs.create-release.result == 'success' && '✅' || '❌' }}
201
+ - PyPI Publish: ${{ needs.publish-to-pypi.result == 'success' && '✅' || needs.publish-to-pypi.result == 'skipped' && '⏭️' || '❌' }}
202
+ EOF
@@ -0,0 +1,28 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+ - uses: astral-sh/setup-uv@v7
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - run: uv sync --all-groups
20
+ - run: uv run pytest --cov --cov-report=term-missing --cov-report=xml
21
+ - name: Coverage summary
22
+ run: |
23
+ cat >> $GITHUB_STEP_SUMMARY <<EOF
24
+ ## đź§Ş Test coverage (Python ${{ matrix.python-version }})
25
+ \`\`\`
26
+ $(uv run coverage report)
27
+ \`\`\`
28
+ EOF
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .env
5
+ .DS_Store
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .coverage
11
+ coverage.xml
12
+ htmlcov/
13
+ /docs/specs/
@@ -0,0 +1,25 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-json
10
+ - id: check-added-large-files
11
+ - id: check-merge-conflict
12
+ - id: debug-statements
13
+ - id: check-ast
14
+
15
+ - repo: https://github.com/python-jsonschema/check-jsonschema
16
+ rev: 0.38.0
17
+ hooks:
18
+ - id: check-github-workflows
19
+
20
+ - repo: https://github.com/astral-sh/ruff-pre-commit
21
+ rev: v0.16.2
22
+ hooks:
23
+ - id: ruff
24
+ args: [--fix]
25
+ - id: ruff-format
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Glaser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: fega-schmitt-client
3
+ Version: 0.0.2
4
+ Summary: Python-Client fĂĽr die B2B-Schnittstellen von FEGA & Schmitt ElektrogroĂźhandel (SOAP-Preis-/VerfĂĽgbarkeitsservice)
5
+ Project-URL: Homepage, https://github.com/the78mole/fega-schmitt-client
6
+ Project-URL: Repository, https://github.com/the78mole/fega-schmitt-client
7
+ Project-URL: Issues, https://github.com/the78mole/fega-schmitt-client/issues
8
+ Author-email: Daniel Glaser <me@the78mole.de>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: click>=8.1.0
21
+ Requires-Dist: httpx>=0.27
22
+ Description-Content-Type: text/markdown
23
+
24
+ # fega-schmitt-client
25
+
26
+ [![Tests](https://github.com/the78mole/fega-schmitt-client/actions/workflows/test.yml/badge.svg)](https://github.com/the78mole/fega-schmitt-client/actions/workflows/test.yml)
27
+ [![Release](https://github.com/the78mole/fega-schmitt-client/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/the78mole/fega-schmitt-client/actions/workflows/pypi-publish.yml)
28
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
29
+ [![Renovate](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com)
30
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
31
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
32
+
33
+ > **Status: v1 implementiert (SOAP-Preis-/Verfügbarkeitsservice) + erste IDS-Erweiterung (Warenkorb senden/empfangen), noch nicht auf PyPI veröffentlicht.** UGL4 sowie IDS-Artikeldeeplink/-suche/Heatinglabel sind bewusst noch nicht umgesetzt, siehe [Architektur](docs/architecture.md#7-bewusst-nicht-in-v1-abgedeckt). Der SOAP-Preis-/Verfügbarkeitsservice ist sowohl gegen die Spezifikation/einen Mock-Server (automatisierte Tests) als auch **live gegen den echten FEGA & Schmitt-Server** verifiziert (siehe "Offene Punkte" für Details zum manuellen Testaufruf).
34
+
35
+ Eine reine Python-Library für den Zugriff auf die B2B-Schnittstellen von **FEGA & Schmitt Elektrogroßhandel** — allen voran den Preis-/Verfügbarkeitsservice. Kein MCP-, kein KI-spezifischer Code: diese Library ist eigenständig nutzbar (Skripte, andere Services, Warenwirtschaftssysteme) und wird zusätzlich vom Schwester-Projekt `fega-schmitt-mcp` (separates Repository unter `GIT/MCP/`) als MCP-Interface verpackt.
36
+
37
+ > **Suchst du das MCP-Interface fĂĽr KI-Assistenten?**
38
+ > Siehe das Repository `fega-schmitt-mcp` — der dünne MCP-Wrapper um diese Library.
39
+
40
+ ## Ăśber FEGA & Schmitt
41
+
42
+ FEGA & Schmitt ist ein deutscher Elektrogroßhändler. Kunden erhalten Zugriff auf mehrere, technisch sehr unterschiedliche B2B-Schnittstellen zum Datenaustausch mit ihrer Warenwirtschaft/Handwerkersoftware. Details siehe [docs/architecture.md](docs/architecture.md).
43
+
44
+ ## Schnittstellen im Ăśberblick
45
+
46
+ | Schnittstelle | Typ | Zweck | Eignung fĂĽr Library v1 |
47
+ |---|---|---|---|
48
+ | **SOAP Preis-/Verfügbarkeitsservice** | Synchrones SOAP/XML über HTTPS | Preis & Verfügbarkeit für bis zu 1000 Artikel je Anfrage | ✅ Basis für v1 — einzige echte Request/Response-API |
49
+ | **IDS-Schnittstelle** (BVBS/ITEK-Branchenstandard, v2.5) | Browser-Redirect + Hook-URL, halbautomatisch | Warenkorb-Austausch, Artikelsuche, Artikel-Deeplinks, Heizungslabel (ErP) | ⚠️ Später — passt nicht zu einer reinen Funktionsaufruf-API, siehe [Architektur](docs/architecture.md#7-bewusst-nicht-in-v1-abgedeckt) |
50
+ | **UGL Version 4** (SHK-Branchenstandard) | Datei-basiert (FTP/Portal), ASCII Fixed-Length | Anfragen, Abrufaufträge, Auftragsbestätigungen als Batch-Dateien | ⚠️ Später — Batch/Polling statt Live-Antwort |
51
+
52
+ Die vollständigen Original-Spezifikationen liegen als PDF in [docs/specs/](docs/specs/):
53
+
54
+ - `Schnittstellenbeschreibung_SOAP.pdf` — Preis-/Verfügbarkeits-Webservice (FEGA & Schmitt, Stand März 2016)
55
+ - `IDS_Schnittstelle_2_5_final_NEU.pdf` — IDS-Schnittstelle für Warenkorb/Artikelsuche/Heizlabel (BVBS/ITEK, Version 2.5, Stand 02.11.2020)
56
+ - `ugl4neutral.pdf` — UGL Version 4, Datenaustausch Handwerk ↔ SHK-Großhandel (GC-Gruppe, Stand 16.06.2006)
57
+
58
+ ## Ă–ffentliche API (v1)
59
+
60
+ ```python
61
+ from fega_schmitt_client import FegaSchmittClient, PriceAvailRequestItem
62
+
63
+ client = FegaSchmittClient(
64
+ partner_purchaser="9920", # Kundennummer bei FEGA & Schmitt
65
+ legitimation_id="...", # Shop-Kennwort
66
+ )
67
+
68
+ results = client.get_price_availability([
69
+ PriceAvailRequestItem(material_number="0815", quantity=200, unit="MTR"),
70
+ PriceAvailRequestItem(material_number="4711", quantity=1),
71
+ ])
72
+
73
+ for r in results:
74
+ print(r.material_number, r.availability_status, r.net_amount)
75
+ ```
76
+
77
+ Details zu Datenmodell, Fehlerbehandlung und Architektur: siehe [docs/architecture.md](docs/architecture.md).
78
+
79
+ ## IDS-Erweiterung: Warenkorb senden/empfangen
80
+
81
+ `fega_schmitt_client.ids` implementiert den "Warenkorb senden"-Teil der IDS-Schnittstelle (BVBS/ITEK v2.5, halbautomatisch — Browserfenster + manuelle Nutzerinteraktion im FEGA-Shop, siehe [`docs/specs/IDS_Schnittstelle_2_5_final_NEU.pdf`](docs/specs/IDS_Schnittstelle_2_5_final_NEU.pdf), Abschnitt 5.2). Die Library baut nur die Daten — **kein** Browser-Handling, **kein** Webhook-Server; das übernimmt der Aufrufer (z. B. `fega-schmitt-mcp`):
82
+
83
+ ```python
84
+ from decimal import Decimal
85
+ from fega_schmitt_client.ids import Cart, CartItem, build_cart_request, parse_cart_callback
86
+
87
+ cart = Cart(items=[
88
+ CartItem(article_number="0815", quantity=Decimal(200), unit="MTR"),
89
+ ])
90
+
91
+ request = build_cart_request(
92
+ cart,
93
+ shop_url="https://shop.fega.de/...", # noch nicht bekannt, siehe "Offene Punkte"
94
+ customer_number="9920",
95
+ hook_url="https://mein-server.example/fega-hook", # optional
96
+ )
97
+ # request.fields als multipart/form-data-POST an request.shop_url schicken
98
+ # (bzw. als auto-submitting HTML-Formular im Browser des Nutzers öffnen)
99
+
100
+ # Später, wenn der Shop das Ergebnis an die hook_url zurückschickt:
101
+ updated_cart = parse_cart_callback(received_xml)
102
+ ```
103
+
104
+ Ohne `hook_url` funktioniert das Senden weiterhin — der Nutzer schließt den Vorgang dann manuell im Browser ab, es gibt nur keinen automatischen Rücklauf.
105
+
106
+ ## CLI
107
+
108
+ Als Kommandozeilen-Tool `fega` installierbar, ohne dass ein eigenes venv angelegt werden muss:
109
+
110
+ ```bash
111
+ uv tool install fega-schmitt-client
112
+ fega --customer-number 9920 --shop-password "..." price-avail 0815:200:MTR 4711
113
+ ```
114
+
115
+ Alternativ per Umgebungsvariablen (`FEGA_CUSTOMER_NUMBER`, `FEGA_SHOP_PASSWORD`) statt Optionen. `-j`/`--json` liefert JSON statt Tabellenausgabe. `fega --help` bzw. `fega price-avail --help` zeigen alle Optionen.
116
+
117
+ ## Installation
118
+
119
+ ```bash
120
+ pip install fega-schmitt-client
121
+ # oder
122
+ uv add fega-schmitt-client
123
+ ```
124
+
125
+ (Noch nicht auf [PyPI](https://pypi.org/) veröffentlicht — bis dahin lokal installierbar via `uv pip install .` bzw. `uv tool install .` aus dem geklonten Repo.)
126
+
127
+ ## Entwicklung
128
+
129
+ ```bash
130
+ uv sync --all-groups # Dependencies + Dev-Tools installieren
131
+ uv run pytest # Tests (gegen einen respx-Mock-Server, kein echter FEGA-Zugang nötig)
132
+ uv run pre-commit install # Linting/Formatting-Hooks aktivieren
133
+ uv run pre-commit run --all-files
134
+ ```
135
+
136
+ ## Offene Punkte
137
+
138
+ - ~~Test-/Produktivzugangsdaten für den SOAP-Service~~ — **erledigt**: `get_price_availability` wurde live gegen `https://soap.fega.de/priceavail.php` verifiziert (echte Kundennummer/Shop-Kennwort, Artikel 121350 → `I720`, korrekte Preis-, Verfügbarkeits- und Kupferzuschlag-Daten). Zugangsdaten liegen als Secret in OpenBao, nicht im Repo.
139
+ - Wie FEGA & Schmitt eine komplett abgelehnte Anmeldung tatsächlich signalisiert (HTTP-Status, Fault oder ein bestimmter Returncode), bleibt weiterhin offen — der Live-Test lief mit gültigen Zugangsdaten, absichtlich falsche wurden nicht getestet. Beobachtet wurde allerdings: eine unbekannte Artikelnummer liefert `E999`/Fehler 216 mit dem missverständlichen Text "Bitte pruefen Sie Ihre Anmeldedaten" (HTTP 200) — nicht mit einem Auth-spezifischen HTTP-Status. `FegaAuthError` (aktuell nur bei HTTP 401/403) ist also weiterhin eine ungetestete Annahme, könnte in der Praxis aber nie greifen, wenn FEGA Ablehnungen generell per Item-Returncode statt HTTP-Status meldet
140
+ - Die tatsächliche IDS-Shop-URL von FEGA & Schmitt (`shop_url` in `build_cart_request`) sowie die referenzierten XSD-Schemas (`Warenkorb_senden.xsd`, `Warenkorb_empfangen_2-5.xsd` — in der vorliegenden PDF nicht enthalten, nur die tabellarische Feldbeschreibung); die Implementierung von `fega_schmitt_client.ids` ist entsprechend nur gegen die Spezifikation getestet, nicht gegen den echten Shop
141
+ - Das exakte Formularfeld, unter dem der Shop den Warenkorb an die Hook-URL zurückschickt — die Spec dokumentiert nur den Feldnamen `warenkorb` für die ausgehende Richtung, für den Rücklauf wird das per Analogieschluss angenommen
142
+ - Artikeldeeplink, Artikelsuche, Login-Informationen, Schnittstellenversion und Heatinglabel (restliche IDS-Aktionen) sind noch nicht umgesetzt
143
+ - **DATANORM v5 + Offline-Zugang (FTP)**: bei FEGA & Schmitt beantragt, aber Zugangsdaten und Format-Spezifikation liegen noch nicht vor — siehe [docs/architecture.md](docs/architecture.md#73-datanorm-v5-artikel-stammdatenpreislisten). DATANORM ist die von der SOAP-Spec selbst referenzierte Quelle der Artikelnummern für `get_price_availability`, daher relevanter als "nur" ein weiteres Zusatzformat
144
+ - Ob UGL4 fĂĽr dieses Projekt relevant ist, und falls ja: FTP-Zugangsdaten/Verzeichnis
145
+ - EndgĂĽltiger PyPI-/Modulname (`fega-schmitt-client` ist ein Arbeitstitel)
146
+ - PyPI Trusted Publishing muss einmalig manuell auf pypi.org eingerichtet werden (Workflow-Datei `pypi-publish.yml`, Environment `pypi`), bevor der Release-Workflow tatsächlich veröffentlichen kann
147
+
148
+ ## Lizenz
149
+
150
+ MIT, siehe [LICENSE](LICENSE).
@@ -0,0 +1,127 @@
1
+ # fega-schmitt-client
2
+
3
+ [![Tests](https://github.com/the78mole/fega-schmitt-client/actions/workflows/test.yml/badge.svg)](https://github.com/the78mole/fega-schmitt-client/actions/workflows/test.yml)
4
+ [![Release](https://github.com/the78mole/fega-schmitt-client/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/the78mole/fega-schmitt-client/actions/workflows/pypi-publish.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
+ [![Renovate](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com)
7
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
8
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
9
+
10
+ > **Status: v1 implementiert (SOAP-Preis-/Verfügbarkeitsservice) + erste IDS-Erweiterung (Warenkorb senden/empfangen), noch nicht auf PyPI veröffentlicht.** UGL4 sowie IDS-Artikeldeeplink/-suche/Heatinglabel sind bewusst noch nicht umgesetzt, siehe [Architektur](docs/architecture.md#7-bewusst-nicht-in-v1-abgedeckt). Der SOAP-Preis-/Verfügbarkeitsservice ist sowohl gegen die Spezifikation/einen Mock-Server (automatisierte Tests) als auch **live gegen den echten FEGA & Schmitt-Server** verifiziert (siehe "Offene Punkte" für Details zum manuellen Testaufruf).
11
+
12
+ Eine reine Python-Library für den Zugriff auf die B2B-Schnittstellen von **FEGA & Schmitt Elektrogroßhandel** — allen voran den Preis-/Verfügbarkeitsservice. Kein MCP-, kein KI-spezifischer Code: diese Library ist eigenständig nutzbar (Skripte, andere Services, Warenwirtschaftssysteme) und wird zusätzlich vom Schwester-Projekt `fega-schmitt-mcp` (separates Repository unter `GIT/MCP/`) als MCP-Interface verpackt.
13
+
14
+ > **Suchst du das MCP-Interface fĂĽr KI-Assistenten?**
15
+ > Siehe das Repository `fega-schmitt-mcp` — der dünne MCP-Wrapper um diese Library.
16
+
17
+ ## Ăśber FEGA & Schmitt
18
+
19
+ FEGA & Schmitt ist ein deutscher Elektrogroßhändler. Kunden erhalten Zugriff auf mehrere, technisch sehr unterschiedliche B2B-Schnittstellen zum Datenaustausch mit ihrer Warenwirtschaft/Handwerkersoftware. Details siehe [docs/architecture.md](docs/architecture.md).
20
+
21
+ ## Schnittstellen im Ăśberblick
22
+
23
+ | Schnittstelle | Typ | Zweck | Eignung fĂĽr Library v1 |
24
+ |---|---|---|---|
25
+ | **SOAP Preis-/Verfügbarkeitsservice** | Synchrones SOAP/XML über HTTPS | Preis & Verfügbarkeit für bis zu 1000 Artikel je Anfrage | ✅ Basis für v1 — einzige echte Request/Response-API |
26
+ | **IDS-Schnittstelle** (BVBS/ITEK-Branchenstandard, v2.5) | Browser-Redirect + Hook-URL, halbautomatisch | Warenkorb-Austausch, Artikelsuche, Artikel-Deeplinks, Heizungslabel (ErP) | ⚠️ Später — passt nicht zu einer reinen Funktionsaufruf-API, siehe [Architektur](docs/architecture.md#7-bewusst-nicht-in-v1-abgedeckt) |
27
+ | **UGL Version 4** (SHK-Branchenstandard) | Datei-basiert (FTP/Portal), ASCII Fixed-Length | Anfragen, Abrufaufträge, Auftragsbestätigungen als Batch-Dateien | ⚠️ Später — Batch/Polling statt Live-Antwort |
28
+
29
+ Die vollständigen Original-Spezifikationen liegen als PDF in [docs/specs/](docs/specs/):
30
+
31
+ - `Schnittstellenbeschreibung_SOAP.pdf` — Preis-/Verfügbarkeits-Webservice (FEGA & Schmitt, Stand März 2016)
32
+ - `IDS_Schnittstelle_2_5_final_NEU.pdf` — IDS-Schnittstelle für Warenkorb/Artikelsuche/Heizlabel (BVBS/ITEK, Version 2.5, Stand 02.11.2020)
33
+ - `ugl4neutral.pdf` — UGL Version 4, Datenaustausch Handwerk ↔ SHK-Großhandel (GC-Gruppe, Stand 16.06.2006)
34
+
35
+ ## Ă–ffentliche API (v1)
36
+
37
+ ```python
38
+ from fega_schmitt_client import FegaSchmittClient, PriceAvailRequestItem
39
+
40
+ client = FegaSchmittClient(
41
+ partner_purchaser="9920", # Kundennummer bei FEGA & Schmitt
42
+ legitimation_id="...", # Shop-Kennwort
43
+ )
44
+
45
+ results = client.get_price_availability([
46
+ PriceAvailRequestItem(material_number="0815", quantity=200, unit="MTR"),
47
+ PriceAvailRequestItem(material_number="4711", quantity=1),
48
+ ])
49
+
50
+ for r in results:
51
+ print(r.material_number, r.availability_status, r.net_amount)
52
+ ```
53
+
54
+ Details zu Datenmodell, Fehlerbehandlung und Architektur: siehe [docs/architecture.md](docs/architecture.md).
55
+
56
+ ## IDS-Erweiterung: Warenkorb senden/empfangen
57
+
58
+ `fega_schmitt_client.ids` implementiert den "Warenkorb senden"-Teil der IDS-Schnittstelle (BVBS/ITEK v2.5, halbautomatisch — Browserfenster + manuelle Nutzerinteraktion im FEGA-Shop, siehe [`docs/specs/IDS_Schnittstelle_2_5_final_NEU.pdf`](docs/specs/IDS_Schnittstelle_2_5_final_NEU.pdf), Abschnitt 5.2). Die Library baut nur die Daten — **kein** Browser-Handling, **kein** Webhook-Server; das übernimmt der Aufrufer (z. B. `fega-schmitt-mcp`):
59
+
60
+ ```python
61
+ from decimal import Decimal
62
+ from fega_schmitt_client.ids import Cart, CartItem, build_cart_request, parse_cart_callback
63
+
64
+ cart = Cart(items=[
65
+ CartItem(article_number="0815", quantity=Decimal(200), unit="MTR"),
66
+ ])
67
+
68
+ request = build_cart_request(
69
+ cart,
70
+ shop_url="https://shop.fega.de/...", # noch nicht bekannt, siehe "Offene Punkte"
71
+ customer_number="9920",
72
+ hook_url="https://mein-server.example/fega-hook", # optional
73
+ )
74
+ # request.fields als multipart/form-data-POST an request.shop_url schicken
75
+ # (bzw. als auto-submitting HTML-Formular im Browser des Nutzers öffnen)
76
+
77
+ # Später, wenn der Shop das Ergebnis an die hook_url zurückschickt:
78
+ updated_cart = parse_cart_callback(received_xml)
79
+ ```
80
+
81
+ Ohne `hook_url` funktioniert das Senden weiterhin — der Nutzer schließt den Vorgang dann manuell im Browser ab, es gibt nur keinen automatischen Rücklauf.
82
+
83
+ ## CLI
84
+
85
+ Als Kommandozeilen-Tool `fega` installierbar, ohne dass ein eigenes venv angelegt werden muss:
86
+
87
+ ```bash
88
+ uv tool install fega-schmitt-client
89
+ fega --customer-number 9920 --shop-password "..." price-avail 0815:200:MTR 4711
90
+ ```
91
+
92
+ Alternativ per Umgebungsvariablen (`FEGA_CUSTOMER_NUMBER`, `FEGA_SHOP_PASSWORD`) statt Optionen. `-j`/`--json` liefert JSON statt Tabellenausgabe. `fega --help` bzw. `fega price-avail --help` zeigen alle Optionen.
93
+
94
+ ## Installation
95
+
96
+ ```bash
97
+ pip install fega-schmitt-client
98
+ # oder
99
+ uv add fega-schmitt-client
100
+ ```
101
+
102
+ (Noch nicht auf [PyPI](https://pypi.org/) veröffentlicht — bis dahin lokal installierbar via `uv pip install .` bzw. `uv tool install .` aus dem geklonten Repo.)
103
+
104
+ ## Entwicklung
105
+
106
+ ```bash
107
+ uv sync --all-groups # Dependencies + Dev-Tools installieren
108
+ uv run pytest # Tests (gegen einen respx-Mock-Server, kein echter FEGA-Zugang nötig)
109
+ uv run pre-commit install # Linting/Formatting-Hooks aktivieren
110
+ uv run pre-commit run --all-files
111
+ ```
112
+
113
+ ## Offene Punkte
114
+
115
+ - ~~Test-/Produktivzugangsdaten für den SOAP-Service~~ — **erledigt**: `get_price_availability` wurde live gegen `https://soap.fega.de/priceavail.php` verifiziert (echte Kundennummer/Shop-Kennwort, Artikel 121350 → `I720`, korrekte Preis-, Verfügbarkeits- und Kupferzuschlag-Daten). Zugangsdaten liegen als Secret in OpenBao, nicht im Repo.
116
+ - Wie FEGA & Schmitt eine komplett abgelehnte Anmeldung tatsächlich signalisiert (HTTP-Status, Fault oder ein bestimmter Returncode), bleibt weiterhin offen — der Live-Test lief mit gültigen Zugangsdaten, absichtlich falsche wurden nicht getestet. Beobachtet wurde allerdings: eine unbekannte Artikelnummer liefert `E999`/Fehler 216 mit dem missverständlichen Text "Bitte pruefen Sie Ihre Anmeldedaten" (HTTP 200) — nicht mit einem Auth-spezifischen HTTP-Status. `FegaAuthError` (aktuell nur bei HTTP 401/403) ist also weiterhin eine ungetestete Annahme, könnte in der Praxis aber nie greifen, wenn FEGA Ablehnungen generell per Item-Returncode statt HTTP-Status meldet
117
+ - Die tatsächliche IDS-Shop-URL von FEGA & Schmitt (`shop_url` in `build_cart_request`) sowie die referenzierten XSD-Schemas (`Warenkorb_senden.xsd`, `Warenkorb_empfangen_2-5.xsd` — in der vorliegenden PDF nicht enthalten, nur die tabellarische Feldbeschreibung); die Implementierung von `fega_schmitt_client.ids` ist entsprechend nur gegen die Spezifikation getestet, nicht gegen den echten Shop
118
+ - Das exakte Formularfeld, unter dem der Shop den Warenkorb an die Hook-URL zurückschickt — die Spec dokumentiert nur den Feldnamen `warenkorb` für die ausgehende Richtung, für den Rücklauf wird das per Analogieschluss angenommen
119
+ - Artikeldeeplink, Artikelsuche, Login-Informationen, Schnittstellenversion und Heatinglabel (restliche IDS-Aktionen) sind noch nicht umgesetzt
120
+ - **DATANORM v5 + Offline-Zugang (FTP)**: bei FEGA & Schmitt beantragt, aber Zugangsdaten und Format-Spezifikation liegen noch nicht vor — siehe [docs/architecture.md](docs/architecture.md#73-datanorm-v5-artikel-stammdatenpreislisten). DATANORM ist die von der SOAP-Spec selbst referenzierte Quelle der Artikelnummern für `get_price_availability`, daher relevanter als "nur" ein weiteres Zusatzformat
121
+ - Ob UGL4 fĂĽr dieses Projekt relevant ist, und falls ja: FTP-Zugangsdaten/Verzeichnis
122
+ - EndgĂĽltiger PyPI-/Modulname (`fega-schmitt-client` ist ein Arbeitstitel)
123
+ - PyPI Trusted Publishing muss einmalig manuell auf pypi.org eingerichtet werden (Workflow-Datei `pypi-publish.yml`, Environment `pypi`), bevor der Release-Workflow tatsächlich veröffentlichen kann
124
+
125
+ ## Lizenz
126
+
127
+ MIT, siehe [LICENSE](LICENSE).