Unified contacts model: Rentman-style type field (company/person), inline addresses, ContactPerson 1:N, all Rentman fields except rental-specific

This commit is contained in:
Agent Zero
2026-07-19 21:12:49 +02:00
parent d80feb2a3a
commit cf75680583
14 changed files with 1411 additions and 869 deletions
+270 -30
View File
@@ -1,52 +1,291 @@
"""Contact schemas — create, update, read, list."""
"""Unified contact schemas — create, update, read, list, contactpersons."""
from __future__ import annotations
from pydantic import BaseModel, Field
class ContactCreate(BaseModel):
first_name: str = Field(..., min_length=1, max_length=100)
last_name: str = Field(..., min_length=1, max_length=100)
# ── 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)
phone: str | None = Field(None, max_length=30)
mobile: str | None = Field(None, max_length=30)
position: str | None = Field(None, max_length=100)
department: str | None = Field(None, max_length=100)
linkedin_url: str | None = Field(None, max_length=500)
notes: str | None = None
company_ids: list[str] | None = None
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
# Contact persons (optional inline create)
contact_persons: list[ContactPersonCreate] | None = None
class ContactUpdate(BaseModel):
first_name: str | None = Field(None, min_length=1, max_length=100)
last_name: str | None = Field(None, min_length=1, max_length=100)
email: str | None = Field(None, max_length=255)
phone: str | None = Field(None, max_length=30)
mobile: str | None = Field(None, max_length=30)
position: str | None = Field(None, max_length=100)
department: str | None = Field(None, max_length=100)
linkedin_url: str | None = Field(None, max_length=500)
notes: str | None = None
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
default_person_id: str | None = None
admin_contactperson_id: str | None = None
class ContactResponse(BaseModel):
id: str
first_name: str
last_name: str
email: str | None = None
phone: str | None = None
mobile: str | None = None
position: str | None = None
department: str | None = None
linkedin_url: str | None = None
notes: str | None = None
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
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):
companies: list[dict] = Field(default_factory=list)
contact_persons: list[ContactPersonResponse] = Field(default_factory=list)
class ContactListResponse(BaseModel):
@@ -56,6 +295,7 @@ class ContactListResponse(BaseModel):
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