diff --git a/MDfile/RELEASE_NOTES_v2.8.8.md b/MDfile/RELEASE_NOTES_v2.8.8.md new file mode 100644 index 0000000..d165ce2 --- /dev/null +++ b/MDfile/RELEASE_NOTES_v2.8.8.md @@ -0,0 +1,17 @@ +# BMC Hub v2.8.8 + +## Rettelse af adgang til e-conomic-varekataloget + +- Tildeler de nye e-conomic-rettigheder til den eksisterende `Administrators`-gruppe. +- Retter fejlen `Missing required permission: economic.catalog.view` for almindelige administratorer. +- Andre grupper og brugere får ikke nye rettigheder automatisk. + +## Ordreeksport + +- Stopper eksporten før afsendelse, hvis et varenummer ikke findes i e-conomic. +- Viser de manglende varenumre og sender ikke længere prislinjer uden den produktreference, som e-conomic kræver. + +## Installation + +- Kør migration `244_economic_permissions_backfill.sql` efter migration 242 og 243. +- Migrationen er idempotent og ændrer kun gruppens koblinger til eksisterende rettigheder. diff --git a/VERSION b/VERSION index bcd0f91..80803fa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.7 +2.8.8 diff --git a/app/modules/orders/backend/economic_export.py b/app/modules/orders/backend/economic_export.py index 4070f24..187dfe2 100644 --- a/app/modules/orders/backend/economic_export.py +++ b/app/modules/orders/backend/economic_export.py @@ -44,6 +44,21 @@ def _economic_error_message(status: int, response_text: str) -> str: return f"e-conomic afviste ordren ({status})" + (f": {detail}" if detail else "") +def _require_valid_product_numbers(product_numbers: set[str], valid_product_numbers: set[str]) -> List[str]: + """Reject priced lines before POST when e-conomic cannot resolve their product.""" + missing = sorted(product_numbers - valid_product_numbers) + if missing: + raise HTTPException( + status_code=409, + detail=( + "Ordren blev ikke sendt. Følgende varenumre findes ikke i e-conomic: " + + ", ".join(missing) + + ". Knyt eller opret varerne under Varer og e-conomic, og prøv igen." + ), + ) + return missing + + class OrdreEconomicExportService: """e-conomic export service for global ordre page.""" @@ -224,10 +239,7 @@ class OrdreEconomicExportService: 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, - ) + logger.warning("e-conomic product %s does not exist", product_number) else: product_text = await product_response.text() raise HTTPException( @@ -235,11 +247,7 @@ class OrdreEconomicExportService: 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) + missing_product_numbers = _require_valid_product_numbers(product_numbers, valid_product_numbers) customer_layout = economic_customer.get("layout") or {} resolved_layout_number = customer_layout.get("layoutNumber") or self.default_layout @@ -278,7 +286,8 @@ class OrdreEconomicExportService: "read_only": self.read_only, "dry_run": self.dry_run, "user_id": user_id, - "skipped_product_numbers": skipped_product_numbers, + "skipped_product_numbers": missing_product_numbers, + "missing_product_numbers": missing_product_numbers, "payload": payload, }, } @@ -314,7 +323,8 @@ class OrdreEconomicExportService: "customer_name": customer.get("name"), "selected_line_count": len(selected_lines), "user_id": user_id, - "skipped_product_numbers": skipped_product_numbers, + "skipped_product_numbers": missing_product_numbers, + "missing_product_numbers": missing_product_numbers, "economic_response": export_result, }, } diff --git a/migrations/244_economic_permissions_backfill.sql b/migrations/244_economic_permissions_backfill.sql new file mode 100644 index 0000000..98524d2 --- /dev/null +++ b/migrations/244_economic_permissions_backfill.sql @@ -0,0 +1,14 @@ +-- Preserve administrator access after adding the e-conomic catalog permissions. +-- Migration 002 grants all permissions known at install time, so permissions +-- introduced later must also be assigned to the Administrators group. +BEGIN; + +INSERT INTO group_permissions (group_id, permission_id) +SELECT g.id, p.id +FROM groups g +CROSS JOIN permissions p +WHERE g.name = 'Administrators' + AND p.category = 'economic' +ON CONFLICT DO NOTHING; + +COMMIT; diff --git a/tests/test_order_economic_safety_config.py b/tests/test_order_economic_safety_config.py index b166086..df78617 100644 --- a/tests/test_order_economic_safety_config.py +++ b/tests/test_order_economic_safety_config.py @@ -47,3 +47,22 @@ def test_economic_validation_error_is_exposed_without_developer_metadata(): assert "paymentTerms" in message assert "Required" in message assert "internal" not in message + + +def test_order_export_blocks_priced_line_when_product_does_not_exist(): + from fastapi import HTTPException + from app.modules.orders.backend.economic_export import _require_valid_product_numbers + + try: + _require_valid_product_numbers({"1000", "UKENDT"}, {"1000"}) + assert False, "missing product must block the export before POST" + except HTTPException as exc: + assert exc.status_code == 409 + assert "UKENDT" in exc.detail + assert "Ordren blev ikke sendt" in exc.detail + + +def test_order_export_accepts_only_verified_products(): + from app.modules.orders.backend.economic_export import _require_valid_product_numbers + + assert _require_valid_product_numbers({"1000"}, {"1000"}) == []