Files
raynet/app/models.py
T
JiriUhlir e3dccc46d3 3
2026-06-18 15:59:55 +02:00

453 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Pydantic modely pro RAYNET endpointy.
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
from typing import Optional
from pydantic import BaseModel, Field
# --------------------------------------------------------------------------- #
# Číselníkové enumy (company)
# --------------------------------------------------------------------------- #
class Rating(str, Enum):
A = "A"
B = "B"
C = "C"
class CompanyState(str, Enum):
A_POTENTIAL = "A_POTENTIAL"
B_ACTUAL = "B_ACTUAL"
C_DEFERRED = "C_DEFERRED"
D_UNATTRACTIVE = "D_UNATTRACTIVE"
class CompanyRole(str, Enum):
A_SUBSCRIBER = "A_SUBSCRIBER"
B_PARTNER = "B_PARTNER"
C_SUPPLIER = "C_SUPPLIER"
D_RIVAL = "D_RIVAL"
class TaxPayer(str, Enum):
YES = "YES"
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"])
city: Optional[str] = Field(default=None, examples=["Ostrava"])
province: Optional[str] = Field(default=None, examples=["Morava"])
zipCode: Optional[str] = Field(default=None, examples=["708 00"])
country: Optional[str] = Field(default=None, examples=["CZ"])
lat: Optional[float] = None
lng: Optional[float] = None
class ContactInfo(BaseModel):
email: Optional[str] = Field(default=None, examples=["info@raynet.cz"])
email2: Optional[str] = None
fax: Optional[str] = None
tel1: Optional[str] = Field(default=None, examples=["+420 553 401 520"])
tel1Type: Optional[str] = Field(default=None, examples=["recepce"])
tel2: Optional[str] = None
tel2Type: Optional[str] = None
www: Optional[str] = Field(default=None, examples=["www.raynet.cz"])
doNotSendMM: Optional[bool] = None
otherContact: Optional[str] = None
class CompanyAddress(BaseModel):
address: Optional[Address] = None
contactInfo: Optional[ContactInfo] = None
# --------------------------------------------------------------------------- #
# Entitní modely (request body pro create) CamelCase dle RAYNET API
# --------------------------------------------------------------------------- #
class CompanyData(BaseModel):
"""Firma / klient (RAYNET CompanyInsertDto). Povinná: name, rating, state, role."""
model_config = {"extra": "allow"}
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])
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
notice: Optional[str] = None
owner: Optional[int] = Field(default=None, description="ID vlastníka (uživatele)")
category: Optional[int] = None
contactSource: Optional[int] = None
employeesNumber: Optional[int] = None
legalForm: Optional[int] = None
paymentTerm: Optional[int] = None
turnover: Optional[int] = None
territory: Optional[int] = None
addresses: Optional[list[CompanyAddress]] = None
tags: Optional[list[str]] = None
customFields: Optional[dict] = None
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 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