This commit is contained in:
JiriUhlir
2026-06-18 15:59:55 +02:00
parent a5795fe344
commit e3dccc46d3
3 changed files with 656 additions and 113 deletions
+360 -13
View File
@@ -1,7 +1,12 @@
"""Pydantic modely pro RAYNET endpointy.
Modely odpovídají RAYNET CRM API v2 (CompanyInsertDto). Slouží zároveň jako
zdroj pro Swagger/OpenAPI dokumentaci.
Modely odpovídají RAYNET CRM API v2 (Insert DTO jednotlivých entit) a slouží
zároveň jako zdroj pro Swagger/OpenAPI dokumentaci ve Swaggeru je tak vidět
přesná struktura objektů, povinná pole i tvar odpovědí.
POZN.: Názvy polí jsou v ``camelCase`` (tak je očekává RAYNET JSON tělo).
Nevyjmenovaná pole jsou povolená (``extra="allow"``), takže lze poslat i pole,
která zde nejsou explicitně uvedená.
"""
from enum import Enum
@@ -10,6 +15,9 @@ from typing import Optional
from pydantic import BaseModel, Field
# --------------------------------------------------------------------------- #
# Číselníkové enumy (company)
# --------------------------------------------------------------------------- #
class Rating(str, Enum):
A = "A"
B = "B"
@@ -35,6 +43,9 @@ class TaxPayer(str, Enum):
NO = "NO"
# --------------------------------------------------------------------------- #
# Sdílené pod-objekty
# --------------------------------------------------------------------------- #
class Address(BaseModel):
name: Optional[str] = Field(default=None, examples=["Sídlo klienta"])
street: Optional[str] = Field(default=None, examples=["Francouzská 6167/5"])
@@ -64,21 +75,21 @@ class CompanyAddress(BaseModel):
contactInfo: Optional[ContactInfo] = None
# --------------------------------------------------------------------------- #
# Entitní modely (request body pro create) CamelCase dle RAYNET API
# --------------------------------------------------------------------------- #
class CompanyData(BaseModel):
"""Tělo požadavku pro vytvoření firmy (CompanyInsertDto).
"""Firma / klient (RAYNET CompanyInsertDto). Povinná: name, rating, state, role."""
Povinná pole: ``name``, ``rating``, ``state``, ``role``.
"""
model_config = {"extra": "allow"}
# Povinná pole
name: str = Field(..., examples=["ACME s.r.o."])
rating: Rating = Field(..., examples=[Rating.A])
state: CompanyState = Field(..., examples=[CompanyState.A_POTENTIAL])
role: CompanyRole = Field(..., examples=[CompanyRole.A_SUBSCRIBER])
# Volitelná pole
regNumber: Optional[str] = Field(default=None, examples=["12345678"])
taxNumber: Optional[str] = Field(default=None, examples=["CZ12345678"])
regNumber: Optional[str] = Field(default=None, examples=["12345678"], description="IČO")
taxNumber: Optional[str] = Field(default=None, examples=["CZ12345678"], description="DIČ")
taxNumber2: Optional[str] = None
taxPayer: Optional[TaxPayer] = None
bankAccount: Optional[str] = None
@@ -95,11 +106,347 @@ class CompanyData(BaseModel):
tags: Optional[list[str]] = None
customFields: Optional[dict] = None
# Umožní i pole, která zde nejsou explicitně vyjmenovaná (RAYNET jich má víc).
class PersonData(BaseModel):
"""Kontaktní osoba (RAYNET PersonInsertDto). Povinné: lastName."""
model_config = {"extra": "allow"}
lastName: str = Field(..., description="Příjmení")
titleBefore: Optional[str] = None
firstName: Optional[str] = None
titleAfter: Optional[str] = None
securityLevel: Optional[int] = None
owner: Optional[int] = None
category: Optional[int] = None
personClassification1: Optional[int] = None
personClassification2: Optional[int] = None
personClassification3: Optional[int] = None
salutation: Optional[str] = None
birthday: Optional[str] = None
language: Optional[int] = None
maritalStatus: Optional[int] = None
gender: Optional[str] = None
contactInfo: Optional[dict] = None
socialNetworkContact: Optional[dict] = None
privateAddress: Optional[dict] = None
notice: Optional[str] = None
relationship: Optional[dict] = None
tags: Optional[list[str]] = None
keyman: Optional[bool] = None
originLead: Optional[int] = None
class CreateCompanyResponse(BaseModel):
id: Optional[int] = Field(default=None, description="ID nově vytvořené firmy")
class LeadData(BaseModel):
"""Lead / poptávka (RAYNET LeadInsertDto). Povinné: topic, priority."""
model_config = {"extra": "allow"}
topic: str = Field(..., description="Předmět")
priority: str = Field(..., description="Priorita (číselník)")
companyName: Optional[str] = None
regNumber: Optional[str] = None
firstName: Optional[str] = None
lastName: Optional[str] = None
titleBefore: Optional[str] = None
titleAfter: Optional[str] = None
securityLevel: Optional[int] = None
owner: Optional[int] = None
contactSource: Optional[int] = None
category: Optional[int] = None
notice: Optional[str] = None
leadPhase: Optional[int] = None
tags: Optional[str] = None
territory: Optional[int] = None
leadPerson: Optional[bool] = None
contactInfo: Optional[dict] = None
address: Optional[dict] = None
socialNetworkContact: Optional[dict] = None
customFields: Optional[dict] = None
notificationMessage: Optional[str] = None
notificationEmailAddresses: Optional[list[str]] = None
class BusinessCaseData(BaseModel):
"""Obchodní případ (RAYNET BusinessCaseInsertDto). Povinné: name, company."""
model_config = {"extra": "allow"}
name: str = Field(..., description="Předmět")
company: int = Field(..., description="ID klienta")
securityLevel: Optional[int] = None
owner: Optional[int] = None
person: Optional[int] = None
project: Optional[int] = None
totalAmount: Optional[float] = None
estimatedValue: Optional[float] = None
probability: Optional[int] = None
validFrom: Optional[str] = None
description: Optional[str] = None
currency: Optional[int] = None
exchangeRate: Optional[float] = None
category: Optional[int] = None
source: Optional[int] = None
businessCaseClassification1: Optional[int] = None
businessCaseClassification2: Optional[int] = None
businessCaseClassification3: Optional[int] = None
businessCasePhase: Optional[int] = None
originalLead: Optional[int] = None
tags: Optional[list[str]] = None
customFields: Optional[dict] = None
class TaskData(BaseModel):
"""Úkol = aktivita typu task (RAYNET TaskInsertDto, endpoint /task/).
Povinné: title, priority, owner, resolver, deadline.
"""
model_config = {"extra": "allow"}
title: str = Field(..., description="Předmět")
priority: str = Field(..., description="Priorita")
owner: int = Field(..., description="ID kontaktní osoby vlastník úkolu")
resolver: int = Field(..., description="ID kontaktní osoby řešitel úkolu")
deadline: str = Field(..., description="Deadline (datetime)")
category: Optional[int] = None
person: Optional[int] = None
company: Optional[int] = None
businessCase: Optional[int] = None
offer: Optional[int] = None
salesOrder: Optional[int] = None
project: Optional[int] = None
activity: Optional[int] = None
lead: Optional[int] = None
securityLevel: Optional[int] = None
scheduledFrom: Optional[str] = None
scheduledTill: Optional[str] = None
completed: Optional[str] = None
description: Optional[str] = None
solution: Optional[str] = None
tags: Optional[list[str]] = None
class ProductData(BaseModel):
"""Produkt (RAYNET ProductInsertDto). Povinné: code, name."""
model_config = {"extra": "allow"}
code: str = Field(..., description="Kód")
name: str = Field(..., description="Název")
unit: Optional[str] = None
description: Optional[str] = None
taxRate: Optional[float] = None
category: Optional[int] = Field(default=None, description="ID z číselníku ProductCategory")
productLine: Optional[int] = Field(default=None, description="ID z číselníku ProductLine")
cost: Optional[float] = None
price: Optional[float] = None
tags: Optional[list[str]] = None
class OfferData(BaseModel):
"""Nabídka (RAYNET OfferInsertDto). Povinné: name, company, businessCase."""
model_config = {"extra": "allow"}
name: str = Field(..., description="Předmět")
company: int = Field(..., description="ID klienta")
businessCase: int = Field(..., description="ID obchodního případu")
securityLevel: Optional[int] = None
owner: Optional[int] = None
person: Optional[int] = None
totalAmount: Optional[float] = None
estimatedValue: Optional[float] = None
validFrom: Optional[str] = None
validTill: Optional[str] = None
expirationDate: Optional[str] = None
description: Optional[str] = None
category: Optional[int] = None
offerStatus: Optional[int] = None
class SalesOrderData(BaseModel):
"""Objednávka (RAYNET SalesOrderInsertDto, endpoint /salesOrder/).
Povinné: name, company, businessCase.
"""
model_config = {"extra": "allow"}
name: str = Field(..., description="Předmět")
company: int = Field(..., description="ID klienta")
businessCase: int = Field(..., description="ID obchodního případu")
securityLevel: Optional[int] = None
owner: Optional[int] = None
person: Optional[int] = None
offer: Optional[int] = None
totalAmount: Optional[float] = None
estimatedValue: Optional[float] = None
validFrom: Optional[str] = None
validTill: Optional[str] = None
expirationDate: Optional[str] = None
requestDeliveryDate: Optional[str] = None
description: Optional[str] = None
category: Optional[int] = None
salesOrderStatus: Optional[int] = None
deliveryAddress: Optional[dict] = None
class InvoiceData(BaseModel):
"""Faktura (RAYNET InvoiceInsertDto, endpoint /invoice/).
Povinné: company, currency, dueDate, issueDate, invoiceType, paymentType,
taxableSupplyDate.
"""
model_config = {"extra": "allow"}
company: int = Field(..., description="ID klienta, kterému se fakturuje")
currency: int = Field(..., description="ID měny (číselník Currency)")
dueDate: str = Field(..., description="Datum splatnosti")
issueDate: str = Field(..., description="Datum vystavení")
invoiceType: str = Field(..., description="Typ faktury")
paymentType: str = Field(..., description="Způsob úhrady")
taxableSupplyDate: str = Field(..., description="Datum zdanitelného plnění")
securityLevel: Optional[int] = None
title: Optional[str] = None
constantSymbol: Optional[str] = None
specificSymbol: Optional[str] = None
paymentTermDays: Optional[int] = None
businessCase: Optional[int] = None
note: Optional[str] = None
deliveryNote: Optional[str] = None
privateNote: Optional[str] = None
class ProjectData(BaseModel):
"""Projekt (RAYNET ProjectInsertDto). Povinné: name, company."""
model_config = {"extra": "allow"}
name: str = Field(..., description="Předmět")
company: int = Field(..., description="ID klienta")
securityLevel: Optional[int] = None
owner: Optional[int] = None
person: Optional[int] = None
totalAmount: Optional[int] = None
avgValueTotalAmount: Optional[float] = None
minValueTotalAmount: Optional[float] = None
maxValueTotalAmount: Optional[float] = None
validFrom: Optional[str] = None
validTill: Optional[str] = None
scheduledEnd: Optional[str] = None
description: Optional[str] = None
category: Optional[int] = None
projectStatus: Optional[int] = None
tags: Optional[list[str]] = None
customFields: Optional[dict] = None
class PriceListData(BaseModel):
"""Ceník (RAYNET PriceListInsertDto). Povinné: name, code, currency, validFrom."""
model_config = {"extra": "allow"}
name: str = Field(..., description="Název ceníku")
code: str = Field(..., description="Kód ceníku")
currency: int = Field(..., description="ID měny (číselník Currency)")
validFrom: str = Field(..., description="Platnost od")
securityLevel: Optional[int] = None
owner: Optional[int] = None
category: Optional[int] = None
validTill: Optional[str] = None
description: Optional[str] = None
# --- Aktivity (sdílí strukturu: title, priority, owner povinné) ----------- #
class _ActivityBase(BaseModel):
"""Společná pole aktivit RAYNET (task/email/event/meeting/phoneCall/letter)."""
model_config = {"extra": "allow"}
title: str = Field(..., description="Předmět")
priority: str = Field(..., description="Priorita")
owner: int = Field(..., description="ID kontaktní osoby vlastník")
category: Optional[int] = None
person: Optional[int] = None
company: Optional[int] = None
businessCase: Optional[int] = None
offer: Optional[int] = None
salesOrder: Optional[int] = None
project: Optional[int] = None
activity: Optional[int] = None
lead: Optional[int] = None
securityLevel: Optional[int] = None
scheduledFrom: Optional[str] = None
scheduledTill: Optional[str] = None
completed: Optional[str] = None
description: Optional[str] = None
tags: Optional[list[str]] = None
class EmailData(_ActivityBase):
"""E-mailová aktivita (RAYNET EmailInsertDto)."""
class EventData(_ActivityBase):
"""Událost (RAYNET EventInsertDto)."""
class MeetingData(_ActivityBase):
"""Schůzka (RAYNET MeetingInsertDto)."""
solution: Optional[str] = None
class PhoneCallData(_ActivityBase):
"""Telefonát (RAYNET PhonecallInsertDto)."""
solution: Optional[str] = None
class LetterData(_ActivityBase):
"""Dopis (RAYNET LetterInsertDto)."""
class WebhookData(BaseModel):
"""Webhook (RAYNET WebhookInsertDto). Povinné: url, events."""
model_config = {"extra": "allow"}
url: str = Field(..., description="Cílová adresa (http/https)")
events: list[str] = Field(
...,
description="Typy událostí: record.created, record.updated, record.deleted",
)
secretToken: Optional[str] = Field(
default=None,
description="Token zaslaný v hlavičce X-RAYNETCRM-Token",
)
# --------------------------------------------------------------------------- #
# Obálky odpovědí (RAYNET vrací data zabalená do success/data/totalCount)
# --------------------------------------------------------------------------- #
class CreateResponse(BaseModel):
success: bool = True
id: Optional[int] = Field(default=None, description="ID nově vytvořeného záznamu")
data: Optional[dict] = Field(default=None, description="Surová data z RAYNET, pokud byla vrácena")
class DetailResponse(BaseModel):
success: bool = True
data: Optional[dict] = Field(
default=None,
description="Objekt záznamu vč. serverových polí (id, rowInfo, ...)",
)
class ListResponse(BaseModel):
success: bool = True
totalCount: Optional[int] = Field(default=None, description="Celkový počet záznamů")
data: list[dict] = Field(default_factory=list, description="Pole záznamů")
class SimpleResponse(BaseModel):
success: bool = True
raw: Optional[dict] = Field(default=None, description="Surová odpověď RAYNET API")