diff --git a/MDfile/RELEASE_NOTES_v2.8.6.md b/MDfile/RELEASE_NOTES_v2.8.6.md new file mode 100644 index 0000000..a4ca3ea --- /dev/null +++ b/MDfile/RELEASE_NOTES_v2.8.6.md @@ -0,0 +1,13 @@ +# BMC Hub v2.8.6 + +## e-conomic ordreeksport + +- Bruger kundens gyldige standardlayout fra e-conomic i stedet for et ugyldigt lokalt standardnummer. +- Kontrollerer produktnumre mod e-conomic inden eksport. +- Ukendte lokale produktnumre eksporteres som fritekstlinjer uden ugyldig produktreference. +- Bevarer produktreferencen, når produktet findes i e-conomic. + +## Verifikation + +- Fokuserede regressionstests består. +- Python-syntaks og diff-kontrol består. diff --git a/VERSION b/VERSION index 766d708..e43686a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.5 +2.8.6 diff --git a/app/modules/orders/backend/economic_export.py b/app/modules/orders/backend/economic_export.py index 9eaf0c4..9218028 100644 --- a/app/modules/orders/backend/economic_export.py +++ b/app/modules/orders/backend/economic_export.py @@ -2,6 +2,7 @@ import json import logging from datetime import date from typing import Any, Dict, List, Optional +from urllib.parse import quote import aiohttp from fastapi import HTTPException @@ -195,6 +196,43 @@ class OrdreEconomicExportService: detail="e-conomic-kunden mangler betalingsbetingelser eller momszone", ) + product_numbers = { + str(line.get("product", {}).get("productNumber") or "").strip() + for line in economic_lines + if line.get("product") + } + valid_product_numbers = set() + for product_number in product_numbers: + if not product_number: + continue + async with session.get( + f"{self.api_url}/products/{quote(product_number, safe='')}", + headers=self._headers(), + timeout=aiohttp.ClientTimeout(total=30), + ) as product_response: + if product_response.status == 200: + valid_product_numbers.add(product_number) + elif product_response.status == 404: + logger.warning( + "e-conomic product %s does not exist; exporting matching lines as free text", + product_number, + ) + else: + product_text = await product_response.text() + raise HTTPException( + status_code=502, + detail=_economic_error_message(product_response.status, product_text), + ) + + skipped_product_numbers = sorted(product_numbers - valid_product_numbers) + for line in economic_lines: + product_number = str(line.get("product", {}).get("productNumber") or "").strip() + if product_number and product_number not in valid_product_numbers: + line.pop("product", None) + + customer_layout = economic_customer.get("layout") or {} + resolved_layout_number = customer_layout.get("layoutNumber") or self.default_layout + payload: Dict[str, Any] = { "date": date.today().isoformat(), "currency": str(economic_customer.get("currency") or "DKK"), @@ -209,7 +247,7 @@ class OrdreEconomicExportService: "vatZone": vat_zone, }, "layout": { - "layoutNumber": int(layout_number or self.default_layout), + "layoutNumber": int(resolved_layout_number), }, "lines": economic_lines, } @@ -229,6 +267,7 @@ class OrdreEconomicExportService: "read_only": self.read_only, "dry_run": self.dry_run, "user_id": user_id, + "skipped_product_numbers": skipped_product_numbers, "payload": payload, }, } @@ -263,6 +302,7 @@ class OrdreEconomicExportService: "customer_name": customer.get("name"), "selected_line_count": len(selected_lines), "user_id": user_id, + "skipped_product_numbers": skipped_product_numbers, "economic_response": export_result, }, }