Files
leocrm/app/schemas/contact.py
T
Agent Zero 29202325a6 feat: Kontakt-Ordner mit Drag&Drop Baum (wie KI-Chat Sidebar)
- Backend: ContactFolder model (hierarchisch, parent_id, sort_order)
- Migration 0022: contact_folders Tabelle + folder_id FK auf contacts
- CRUD Routes: /api/v1/contact-folders (list, create, update, delete, reorder)
- Move Contact API: /api/v1/contact-folders/contacts/{id}/move
- folder_id Filter in contacts list API
- Frontend: ContactFolderTree mit Baum-Struktur, Drag&Drop, Kontext-Menü
- Kontakt-Personen-Icon statt Ordner-Symbol
- ContactList Items draggable (List/Table/Cards View)
- React Query Hooks für Folder CRUD + Move Contact
- Alle 266 Frontend-Tests passing
2026-07-20 01:33:44 +02:00

306 lines
12 KiB
Python

"""Unified contact schemas — create, update, read, list, contactpersons."""
from __future__ import annotations
from pydantic import BaseModel, Field
# ── ContactPerson ──
class ContactPersonCreate(BaseModel):
firstname: str | None = Field(None, max_length=100)
middle_name: str | None = Field(None, max_length=100)
lastname: str | None = Field(None, max_length=100)
function: str | None = Field(None, max_length=255)
phone: str | None = Field(None, max_length=50)
mobilephone: str | None = Field(None, max_length=50)
email: str | None = Field(None, max_length=255)
street: str | None = Field(None, max_length=255)
number: str | None = Field(None, max_length=20)
postalcode: str | None = Field(None, max_length=20)
city: str | None = Field(None, max_length=100)
state: str | None = Field(None, max_length=100)
country: str | None = Field(None, max_length=2)
tags: str | None = Field(None, max_length=500)
custom: dict | None = None
class ContactPersonUpdate(BaseModel):
firstname: str | None = Field(None, max_length=100)
middle_name: str | None = Field(None, max_length=100)
lastname: str | None = Field(None, max_length=100)
function: str | None = Field(None, max_length=255)
phone: str | None = Field(None, max_length=50)
mobilephone: str | None = Field(None, max_length=50)
email: str | None = Field(None, max_length=255)
street: str | None = Field(None, max_length=255)
number: str | None = Field(None, max_length=20)
postalcode: str | None = Field(None, max_length=20)
city: str | None = Field(None, max_length=100)
state: str | None = Field(None, max_length=100)
country: str | None = Field(None, max_length=2)
tags: str | None = Field(None, max_length=500)
custom: dict | None = None
class ContactPersonResponse(BaseModel):
id: str
contact_id: str
displayname: str
firstname: str | None = None
middle_name: str | None = None
lastname: str | None = None
function: str | None = None
phone: str | None = None
mobilephone: str | None = None
email: str | None = None
street: str | None = None
number: str | None = None
postalcode: str | None = None
city: str | None = None
state: str | None = None
country: str | None = None
tags: str | None = None
custom: dict | None = None
created_at: str | None = None
updated_at: str | None = None
# ── Contact ──
class ContactCreate(BaseModel):
type: str = Field("company", pattern="^(company|person)$")
name: str | None = Field(None, max_length=255)
firstname: str | None = Field(None, max_length=100)
surname: str | None = Field(None, max_length=100)
surfix: str | None = Field(None, max_length=50)
ext_name_line: str | None = Field(None, max_length=255)
gender: str | None = Field(None, max_length=20)
code: str | None = Field(None, max_length=100)
accounting_code: str | None = Field(None, max_length=100)
vendor_accounting_code: str | None = Field(None, max_length=100)
# Mailing
mailing_street: str | None = Field(None, max_length=255)
mailing_number: str | None = Field(None, max_length=20)
mailing_unit_number: str | None = Field(None, max_length=50)
mailing_district: str | None = Field(None, max_length=100)
mailing_extra_address_line: str | None = Field(None, max_length=255)
mailing_postalcode: str | None = Field(None, max_length=20)
mailing_city: str | None = Field(None, max_length=100)
mailing_state: str | None = Field(None, max_length=100)
mailing_country: str | None = Field(None, max_length=2)
# Visit
visit_street: str | None = Field(None, max_length=255)
visit_number: str | None = Field(None, max_length=20)
visit_unit_number: str | None = Field(None, max_length=50)
visit_district: str | None = Field(None, max_length=100)
visit_extra_address_line: str | None = Field(None, max_length=255)
visit_postalcode: str | None = Field(None, max_length=20)
visit_city: str | None = Field(None, max_length=100)
visit_state: str | None = Field(None, max_length=100)
# Invoice
invoice_street: str | None = Field(None, max_length=255)
invoice_number: str | None = Field(None, max_length=20)
invoice_unit_number: str | None = Field(None, max_length=50)
invoice_district: str | None = Field(None, max_length=100)
invoice_extra_address_line: str | None = Field(None, max_length=255)
invoice_postalcode: str | None = Field(None, max_length=20)
invoice_city: str | None = Field(None, max_length=100)
invoice_state: str | None = Field(None, max_length=100)
invoice_country: str | None = Field(None, max_length=2)
# General
country: str | None = Field(None, max_length=2)
# Communication
phone_1: str | None = Field(None, max_length=50)
phone_2: str | None = Field(None, max_length=50)
email_1: str | None = Field(None, max_length=255)
email_2: str | None = Field(None, max_length=255)
website: str | None = Field(None, max_length=500)
# Financial
vat_code: str | None = Field(None, max_length=50)
fiscal_code: str | None = Field(None, max_length=50)
commerce_code: str | None = Field(None, max_length=100)
purchase_number: str | None = Field(None, max_length=100)
bic: str | None = Field(None, max_length=50)
bank_account: str | None = Field(None, max_length=50)
# Discounts
discount_crew: float = 0
discount_transport: float = 0
discount_rental: float = 0
discount_sale: float = 0
discount_subrent: float = 0
discount_total: float = 0
# Geo
latitude: float | None = None
longitude: float | None = None
# Notes
projectnote: str | None = None
projectnote_title: str | None = Field(None, max_length=255)
contact_warning: str | None = None
tags: str | None = Field(None, max_length=500)
image: str | None = None
# Custom
custom: dict | None = None
# Folder assignment
folder_id: str | None = None
# Contact persons (optional inline create)
contact_persons: list[ContactPersonCreate] | None = None
class ContactUpdate(BaseModel):
type: str | None = Field(None, pattern="^(company|person)$")
name: str | None = Field(None, max_length=255)
firstname: str | None = Field(None, max_length=100)
surname: str | None = Field(None, max_length=100)
surfix: str | None = Field(None, max_length=50)
ext_name_line: str | None = Field(None, max_length=255)
gender: str | None = Field(None, max_length=20)
code: str | None = Field(None, max_length=100)
accounting_code: str | None = Field(None, max_length=100)
vendor_accounting_code: str | None = Field(None, max_length=100)
mailing_street: str | None = Field(None, max_length=255)
mailing_number: str | None = Field(None, max_length=20)
mailing_unit_number: str | None = Field(None, max_length=50)
mailing_district: str | None = Field(None, max_length=100)
mailing_extra_address_line: str | None = Field(None, max_length=255)
mailing_postalcode: str | None = Field(None, max_length=20)
mailing_city: str | None = Field(None, max_length=100)
mailing_state: str | None = Field(None, max_length=100)
mailing_country: str | None = Field(None, max_length=2)
visit_street: str | None = Field(None, max_length=255)
visit_number: str | None = Field(None, max_length=20)
visit_unit_number: str | None = Field(None, max_length=50)
visit_district: str | None = Field(None, max_length=100)
visit_extra_address_line: str | None = Field(None, max_length=255)
visit_postalcode: str | None = Field(None, max_length=20)
visit_city: str | None = Field(None, max_length=100)
visit_state: str | None = Field(None, max_length=100)
invoice_street: str | None = Field(None, max_length=255)
invoice_number: str | None = Field(None, max_length=20)
invoice_unit_number: str | None = Field(None, max_length=50)
invoice_district: str | None = Field(None, max_length=100)
invoice_extra_address_line: str | None = Field(None, max_length=255)
invoice_postalcode: str | None = Field(None, max_length=20)
invoice_city: str | None = Field(None, max_length=100)
invoice_state: str | None = Field(None, max_length=100)
invoice_country: str | None = Field(None, max_length=2)
country: str | None = Field(None, max_length=2)
phone_1: str | None = Field(None, max_length=50)
phone_2: str | None = Field(None, max_length=50)
email_1: str | None = Field(None, max_length=255)
email_2: str | None = Field(None, max_length=255)
website: str | None = Field(None, max_length=500)
vat_code: str | None = Field(None, max_length=50)
fiscal_code: str | None = Field(None, max_length=50)
commerce_code: str | None = Field(None, max_length=100)
purchase_number: str | None = Field(None, max_length=100)
bic: str | None = Field(None, max_length=50)
bank_account: str | None = Field(None, max_length=50)
discount_crew: float | None = None
discount_transport: float | None = None
discount_rental: float | None = None
discount_sale: float | None = None
discount_subrent: float | None = None
discount_total: float | None = None
latitude: float | None = None
longitude: float | None = None
projectnote: str | None = None
projectnote_title: str | None = Field(None, max_length=255)
contact_warning: str | None = None
tags: str | None = Field(None, max_length=500)
image: str | None = None
custom: dict | None = None
folder_id: str | None = None
default_person_id: str | None = None
admin_contactperson_id: str | None = None
class ContactResponse(BaseModel):
id: str
type: str
displayname: str
name: str | None = None
firstname: str | None = None
surname: str | None = None
surfix: str | None = None
ext_name_line: str | None = None
gender: str | None = None
code: str | None = None
accounting_code: str | None = None
vendor_accounting_code: str | None = None
# Addresses (flat)
mailing_street: str | None = None
mailing_number: str | None = None
mailing_unit_number: str | None = None
mailing_district: str | None = None
mailing_extra_address_line: str | None = None
mailing_postalcode: str | None = None
mailing_city: str | None = None
mailing_state: str | None = None
mailing_country: str | None = None
visit_street: str | None = None
visit_number: str | None = None
visit_unit_number: str | None = None
visit_district: str | None = None
visit_extra_address_line: str | None = None
visit_postalcode: str | None = None
visit_city: str | None = None
visit_state: str | None = None
invoice_street: str | None = None
invoice_number: str | None = None
invoice_unit_number: str | None = None
invoice_district: str | None = None
invoice_extra_address_line: str | None = None
invoice_postalcode: str | None = None
invoice_city: str | None = None
invoice_state: str | None = None
invoice_country: str | None = None
country: str | None = None
phone_1: str | None = None
phone_2: str | None = None
email_1: str | None = None
email_2: str | None = None
website: str | None = None
vat_code: str | None = None
fiscal_code: str | None = None
commerce_code: str | None = None
purchase_number: str | None = None
bic: str | None = None
bank_account: str | None = None
discount_crew: float = 0
discount_transport: float = 0
discount_rental: float = 0
discount_sale: float = 0
discount_subrent: float = 0
discount_total: float = 0
latitude: float | None = None
longitude: float | None = None
projectnote: str | None = None
projectnote_title: str | None = None
contact_warning: str | None = None
tags: str | None = None
image: str | None = None
custom: dict | None = None
folder_id: str | None = None
default_person_id: str | None = None
admin_contactperson_id: str | None = None
created_at: str | None = None
updated_at: str | None = None
class ContactDetailResponse(ContactResponse):
contact_persons: list[ContactPersonResponse] = Field(default_factory=list)
class ContactListResponse(BaseModel):
items: list[ContactResponse]
total: int
page: int
page_size: int
# Keep old names for compat
class CompanyLinkRequest(BaseModel):
role_at_company: str | None = Field(None, max_length=100)
is_primary: bool = False