Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -384,71 +384,157 @@ def normalize_wholix_dropdown(val):
|
|
| 384 |
return {"keys": [s], "values": [s]}
|
| 385 |
return None
|
| 386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
def map_to_wholix_record(lead: dict, draft: dict, tag_text: str = "AI") -> dict:
|
| 388 |
-
"""
|
| 389 |
-
JS mapToWholixContactsPayload → Python
|
| 390 |
-
"""
|
| 391 |
p = (lead or {}).get("person") or {}
|
| 392 |
c = (lead or {}).get("company") or {}
|
| 393 |
m = (lead or {}).get("messages") or {}
|
| 394 |
ctx = (lead or {}).get("context") or {}
|
| 395 |
|
| 396 |
-
|
|
|
|
| 397 |
if not email:
|
| 398 |
e = ValueError("E-Mail-Adresse fehlt – Wholix benötigt 'email' als Pflichtfeld.")
|
| 399 |
e.name = "ValidationError"
|
| 400 |
raise e
|
| 401 |
|
|
|
|
| 402 |
depts_raw = p.get("departments")
|
| 403 |
if isinstance(depts_raw, list):
|
| 404 |
-
|
|
|
|
| 405 |
else:
|
| 406 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
)
|
| 413 |
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
|
| 424 |
payload = {
|
|
|
|
| 425 |
"firstname": p.get("first_name") or None,
|
| 426 |
"lastname": p.get("last_name") or None,
|
| 427 |
"email": email,
|
| 428 |
-
"adress": street,
|
| 429 |
-
"city":
|
| 430 |
-
"postcode":
|
| 431 |
"phonenumber": p.get("phone") or None,
|
| 432 |
"job_title": p.get("job_title") or None,
|
| 433 |
-
"departments":
|
| 434 |
"linkedin_url": p.get("linkedin_url") or None,
|
| 435 |
|
| 436 |
-
|
| 437 |
-
"
|
|
|
|
| 438 |
|
| 439 |
-
|
| 440 |
-
"
|
|
|
|
| 441 |
"message_followup1": followup1 or None,
|
| 442 |
"message_followup2": followup2 or None,
|
| 443 |
|
| 444 |
-
|
|
|
|
| 445 |
|
|
|
|
| 446 |
"status_field": { "keys": ["Kontakt aufgenommen"], "values": ["Kontakt aufgenommen"] },
|
| 447 |
-
"tags":
|
| 448 |
}
|
| 449 |
|
| 450 |
normalized = filter_wholix_contact_fields(payload)
|
| 451 |
|
|
|
|
| 452 |
if "status_field" in normalized:
|
| 453 |
fixed = normalize_wholix_dropdown(normalized["status_field"])
|
| 454 |
if fixed: normalized["status_field"] = fixed
|
|
@@ -461,6 +547,7 @@ def map_to_wholix_record(lead: dict, draft: dict, tag_text: str = "AI") -> dict:
|
|
| 461 |
|
| 462 |
return normalized
|
| 463 |
|
|
|
|
| 464 |
def wholix_store_contact(token: str, record: dict, module: str = "Contacts") -> dict:
|
| 465 |
"""
|
| 466 |
JS wholixStoreContact exakt nachgebildet (mit Fallbacks)
|
|
|
|
| 384 |
return {"keys": [s], "values": [s]}
|
| 385 |
return None
|
| 386 |
|
| 387 |
+
def _first_non_empty(*vals):
|
| 388 |
+
for v in vals:
|
| 389 |
+
if isinstance(v, str) and v.strip():
|
| 390 |
+
return v.strip()
|
| 391 |
+
if v not in (None, "", [], {}):
|
| 392 |
+
return v
|
| 393 |
+
return None
|
| 394 |
+
|
| 395 |
+
def _from_ci(d: dict, *keys, default=None):
|
| 396 |
+
if not isinstance(d, dict):
|
| 397 |
+
return default
|
| 398 |
+
for k in keys:
|
| 399 |
+
if k in d and str(d[k]).strip() != "":
|
| 400 |
+
return d[k]
|
| 401 |
+
for dk in d.keys():
|
| 402 |
+
if dk.lower() == k.lower() and str(d[dk]).strip() != "":
|
| 403 |
+
return d[dk]
|
| 404 |
+
return default
|
| 405 |
+
|
| 406 |
+
def _join_nonempty(parts, sep=" "):
|
| 407 |
+
return sep.join([str(x).strip() for x in parts if str(x or "").strip()])
|
| 408 |
+
|
| 409 |
def map_to_wholix_record(lead: dict, draft: dict, tag_text: str = "AI") -> dict:
|
|
|
|
|
|
|
|
|
|
| 410 |
p = (lead or {}).get("person") or {}
|
| 411 |
c = (lead or {}).get("company") or {}
|
| 412 |
m = (lead or {}).get("messages") or {}
|
| 413 |
ctx = (lead or {}).get("context") or {}
|
| 414 |
|
| 415 |
+
# --- Email (REQUIRED) ---
|
| 416 |
+
email = str(_first_non_empty(p.get("email"), _from_ci(p, "mail", "email_address")) or "").strip()
|
| 417 |
if not email:
|
| 418 |
e = ValueError("E-Mail-Adresse fehlt – Wholix benötigt 'email' als Pflichtfeld.")
|
| 419 |
e.name = "ValidationError"
|
| 420 |
raise e
|
| 421 |
|
| 422 |
+
# --- Departments as TEXT + we’ll mirror into tags dropdown later ---
|
| 423 |
depts_raw = p.get("departments")
|
| 424 |
if isinstance(depts_raw, list):
|
| 425 |
+
departments_txt = ", ".join([str(x).strip() for x in depts_raw if str(x).strip()]) or None
|
| 426 |
+
depts_list_for_tags = [str(x).strip() for x in depts_raw if str(x).strip()]
|
| 427 |
else:
|
| 428 |
+
departments_txt = str(depts_raw).strip() if depts_raw not in (None, "", []) else None
|
| 429 |
+
depts_list_for_tags = [departments_txt] if departments_txt else []
|
| 430 |
+
|
| 431 |
+
# --- Company URL with wide fallbacks ---
|
| 432 |
+
company_url = _first_non_empty(
|
| 433 |
+
c.get("url"),
|
| 434 |
+
c.get("website"),
|
| 435 |
+
c.get("domain"),
|
| 436 |
+
c.get("homepage_url"),
|
| 437 |
+
c.get("website_url"),
|
| 438 |
+
c.get("url_normalized"),
|
| 439 |
+
ctx.get("url"),
|
| 440 |
+
(lead or {}).get("homepage_url"),
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
# --- Message fields: PULL FROM 'draft' (fix) ---
|
| 444 |
+
draft = draft or {}
|
| 445 |
+
draft_email = draft.get("email") if isinstance(draft, dict) else {}
|
| 446 |
+
# tolerate shapes like {'email': {'subject','body','to'}} + followups at root
|
| 447 |
+
msg_subject = _first_non_empty(
|
| 448 |
+
_from_ci(draft_email, "subject", "email_subject"),
|
| 449 |
+
_from_ci(draft, "subject", "email_subject", "Betreff"),
|
| 450 |
+
_from_ci(m, "message_mail_subject"), # legacy
|
| 451 |
+
)
|
| 452 |
+
msg_body = _first_non_empty(
|
| 453 |
+
_from_ci(draft_email, "body", "text", "content"),
|
| 454 |
+
_from_ci(draft, "body", "Text", "content", "email_body"),
|
| 455 |
+
_from_ci(m, "message_mail"), # legacy
|
| 456 |
+
)
|
| 457 |
+
followup1 = _first_non_empty(
|
| 458 |
+
_from_ci(draft, "followup1", "FollowUp1", "LinkedIn", "linkedin", "li"),
|
| 459 |
+
_from_ci(m, "followup1", "message_followup1"),
|
| 460 |
+
)
|
| 461 |
+
followup2 = _first_non_empty(
|
| 462 |
+
_from_ci(draft, "followup2", "FollowUp2", "Facebook", "facebook", "fb"),
|
| 463 |
+
_from_ci(m, "followup2", "message_followup2"),
|
| 464 |
+
)
|
| 465 |
|
| 466 |
+
# --- Address with wide fallbacks ---
|
| 467 |
+
street = _first_non_empty(
|
| 468 |
+
_join_nonempty([c.get("street_name"), c.get("street_number")]),
|
| 469 |
+
c.get("address"),
|
| 470 |
+
c.get("address1"),
|
| 471 |
+
c.get("address_line1"),
|
| 472 |
+
c.get("street"),
|
| 473 |
+
c.get("street_address"),
|
| 474 |
)
|
| 475 |
|
| 476 |
+
city = _first_non_empty(
|
| 477 |
+
c.get("city"),
|
| 478 |
+
c.get("town"),
|
| 479 |
+
c.get("locality"),
|
| 480 |
+
)
|
| 481 |
|
| 482 |
+
postcode = _first_non_empty(
|
| 483 |
+
c.get("zip_code"),
|
| 484 |
+
c.get("postal_code"),
|
| 485 |
+
c.get("postcode"),
|
| 486 |
+
c.get("zip"),
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
# --- exclude_hash with fallbacks ---
|
| 490 |
+
exclude_hash = _first_non_empty(
|
| 491 |
+
lead.get("exclude_hash"),
|
| 492 |
+
c.get("exclude_hash"),
|
| 493 |
+
p.get("exclude_hash"),
|
| 494 |
+
lead.get("combined_id"),
|
| 495 |
+
)
|
| 496 |
+
|
| 497 |
+
# --- Build tags dropdown: keep existing tag_text and add departments (if any) ---
|
| 498 |
+
tag_items = [str(tag_text).strip("[]")] if str(tag_text).strip("[]") else []
|
| 499 |
+
for d in depts_list_for_tags:
|
| 500 |
+
if d and d not in tag_items:
|
| 501 |
+
tag_items.append(d)
|
| 502 |
+
tags_dropdown = {"keys": tag_items, "values": tag_items} if tag_items else None
|
| 503 |
|
| 504 |
payload = {
|
| 505 |
+
# Person
|
| 506 |
"firstname": p.get("first_name") or None,
|
| 507 |
"lastname": p.get("last_name") or None,
|
| 508 |
"email": email,
|
| 509 |
+
"adress": street or None, # (sic) exact key
|
| 510 |
+
"city": city or None,
|
| 511 |
+
"postcode": postcode or None,
|
| 512 |
"phonenumber": p.get("phone") or None,
|
| 513 |
"job_title": p.get("job_title") or None,
|
| 514 |
+
"departments": departments_txt, # text field, as before
|
| 515 |
"linkedin_url": p.get("linkedin_url") or None,
|
| 516 |
|
| 517 |
+
# Company
|
| 518 |
+
"company_name": c.get("name") or c.get("company_name") or None,
|
| 519 |
+
"company_url": company_url or None,
|
| 520 |
|
| 521 |
+
# Message (now from 'draft')
|
| 522 |
+
"message_mail": msg_body or None,
|
| 523 |
+
"message_mail_subject": msg_subject or None,
|
| 524 |
"message_followup1": followup1 or None,
|
| 525 |
"message_followup2": followup2 or None,
|
| 526 |
|
| 527 |
+
# Other
|
| 528 |
+
"exclude_hash": exclude_hash or None,
|
| 529 |
|
| 530 |
+
# Dropdowns
|
| 531 |
"status_field": { "keys": ["Kontakt aufgenommen"], "values": ["Kontakt aufgenommen"] },
|
| 532 |
+
"tags": tags_dropdown,
|
| 533 |
}
|
| 534 |
|
| 535 |
normalized = filter_wholix_contact_fields(payload)
|
| 536 |
|
| 537 |
+
# Normalize dropdowns (status/tags). Departments stays text.
|
| 538 |
if "status_field" in normalized:
|
| 539 |
fixed = normalize_wholix_dropdown(normalized["status_field"])
|
| 540 |
if fixed: normalized["status_field"] = fixed
|
|
|
|
| 547 |
|
| 548 |
return normalized
|
| 549 |
|
| 550 |
+
|
| 551 |
def wholix_store_contact(token: str, record: dict, module: str = "Contacts") -> dict:
|
| 552 |
"""
|
| 553 |
JS wholixStoreContact exakt nachgebildet (mit Fallbacks)
|