release: v2.8.8 fix economic permissions and order validation

This commit is contained in:
Christian 2026-09-13 09:33:36 +02:00
parent 3e8e5ba1cf
commit 87932f8e16
5 changed files with 72 additions and 12 deletions

View File

@ -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.

View File

@ -1 +1 @@
2.8.7
2.8.8

View File

@ -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,
},
}

View File

@ -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;

View File

@ -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"}) == []