feat(customers): add controlled CSV and XLSX customer import
This commit is contained in:
parent
613ffdc8b7
commit
82e03877b3
566 changed files with 2752 additions and 991 deletions
|
|
@ -6,7 +6,7 @@ from datetime import date
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy import create_engine, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.base import Base
|
||||
|
|
@ -32,6 +32,7 @@ from app.modules.orion.result import validation_result_box, validation_result_pr
|
|||
from app.modules.orion.template_service import REPORT_SECTIONS, ReportTemplateService
|
||||
from app.modules.helios.service import HeliosImportService
|
||||
from app.services.report_settings import OrionReportSettingsService
|
||||
from app.services.customer_import import CustomerImportService
|
||||
|
||||
|
||||
def session() -> Session:
|
||||
|
|
@ -1564,3 +1565,113 @@ def test_orion_report_settings_affect_html_without_removing_summary_result(tmp_p
|
|||
assert '<header class="report-header"' not in html
|
||||
assert '<footer class="report-footer"' not in html
|
||||
assert html.count("result-box") >= 1
|
||||
|
||||
|
||||
def xlsx_bytes(headers: list[str], rows: list[list[str]]) -> bytes:
|
||||
from zipfile import ZIP_DEFLATED, ZipFile
|
||||
import io
|
||||
|
||||
def cell_ref(index: int, row_number: int) -> str:
|
||||
return f"{chr(65 + index)}{row_number}"
|
||||
|
||||
sheet_rows = []
|
||||
for row_number, row in enumerate([headers, *rows], start=1):
|
||||
cells = "".join(
|
||||
f'<c r="{cell_ref(index, row_number)}" t="inlineStr"><is><t>{value}</t></is></c>'
|
||||
for index, value in enumerate(row)
|
||||
)
|
||||
sheet_rows.append(f'<row r="{row_number}">{cells}</row>')
|
||||
buffer = io.BytesIO()
|
||||
with ZipFile(buffer, "w", ZIP_DEFLATED) as archive:
|
||||
archive.writestr("[Content_Types].xml", '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="xml" ContentType="application/xml"/></Types>')
|
||||
archive.writestr("xl/workbook.xml", '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheets><sheet name="Tabelle1" sheetId="1" r:id="rId1" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/></sheets></workbook>')
|
||||
archive.writestr("xl/worksheets/sheet1.xml", f'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>{"".join(sheet_rows)}</sheetData></worksheet>')
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
def test_customer_import_csv_semicolon_preview_and_confirm():
|
||||
db = session()
|
||||
admin = seed_admin(db)
|
||||
content = "Kundennr;Firma;PLZ;Ort;E-Mail;Ansprechpartner\nD4-1;Praxis Müller;10115;Berlin;info@praxis.de;Max Mustermann\n".encode("utf-8-sig")
|
||||
|
||||
service = CustomerImportService(db)
|
||||
preview = service.preview("desk4.csv", content)
|
||||
|
||||
assert preview["columns"] == ["Kundennr", "Firma", "PLZ", "Ort", "E-Mail", "Ansprechpartner"]
|
||||
assert preview["mapping"]["Kundennr"] == "customer_number"
|
||||
assert preview["rows"][0]["action"] == "NEU_ANLEGEN"
|
||||
assert db.scalar(select(func.count()).select_from(Customer)) == 0
|
||||
|
||||
result = service.confirm("desk4.csv", content, preview["mapping"], {}, True, admin)
|
||||
|
||||
assert result["summary"]["created_customers"] == 1
|
||||
assert result["summary"]["created_locations"] == 1
|
||||
assert result["summary"]["created_contacts"] == 1
|
||||
customer = db.scalar(select(Customer).where(Customer.external_id == "D4-1"))
|
||||
assert customer is not None
|
||||
assert customer.source_system == "desk4"
|
||||
assert customer.name == "Praxis Müller"
|
||||
|
||||
|
||||
def test_customer_import_csv_comma_existing_customer_and_ignore_empty_values():
|
||||
db = session()
|
||||
admin = seed_admin(db)
|
||||
existing = Customer(
|
||||
customer_type=CustomerType.practice,
|
||||
source_system="desk4",
|
||||
external_id="D4-2",
|
||||
name="Alt",
|
||||
email="alt@example.de",
|
||||
)
|
||||
db.add(existing)
|
||||
db.commit()
|
||||
content = "Kundennr,Firma,E-Mail\nD4-2,Neu,\n".encode()
|
||||
service = CustomerImportService(db)
|
||||
preview = service.preview("desk4.csv", content)
|
||||
|
||||
assert preview["rows"][0]["action"] == "BESTEHENDEN_AKTUALISIEREN"
|
||||
service.confirm("desk4.csv", content, preview["mapping"], {}, True, admin)
|
||||
db.refresh(existing)
|
||||
|
||||
assert existing.name == "Neu"
|
||||
assert existing.email == "alt@example.de"
|
||||
|
||||
|
||||
def test_customer_import_detects_possible_duplicate_and_invalid_email():
|
||||
db = session()
|
||||
db.add(Customer(customer_type=CustomerType.practice, name="Praxis A", postal_code="12345"))
|
||||
db.commit()
|
||||
service = CustomerImportService(db)
|
||||
|
||||
duplicate = service.preview("desk4.csv", "Firma;PLZ\nPraxis A;12345\n".encode())
|
||||
invalid = service.preview("desk4.csv", "Firma;E-Mail\nPraxis B;nicht-mail\n".encode())
|
||||
|
||||
assert duplicate["rows"][0]["action"] == "UEBERSPRINGEN"
|
||||
assert duplicate["summary"]["duplicates"] == 1
|
||||
assert invalid["rows"][0]["action"] == "ERROR"
|
||||
assert "ungültig" in invalid["rows"][0]["errors"][0]
|
||||
|
||||
|
||||
def test_customer_import_xlsx_and_invalid_files():
|
||||
db = session()
|
||||
service = CustomerImportService(db)
|
||||
preview = service.preview(
|
||||
"desk4.xlsx",
|
||||
xlsx_bytes(["Kundennr", "Firma", "Ort"], [["X-1", "Praxis XLSX", "Hamburg"]]),
|
||||
)
|
||||
|
||||
assert preview["rows"][0]["recognized"]["customer"]["name"] == "Praxis XLSX"
|
||||
with pytest.raises(ValueError):
|
||||
service.preview("desk4.txt", b"Firma\nTest")
|
||||
with pytest.raises(ValueError):
|
||||
service.preview("desk4.csv", b"")
|
||||
|
||||
|
||||
def test_customer_import_missing_required_mapping_is_error():
|
||||
db = session()
|
||||
service = CustomerImportService(db)
|
||||
content = "Telefon\n123\n".encode()
|
||||
preview = service.preview("desk4.csv", content, {"Telefon": "customer_phone"})
|
||||
|
||||
assert preview["rows"][0]["action"] == "ERROR"
|
||||
assert "Kundennummer oder Firmenname fehlt." in preview["rows"][0]["errors"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue