453 lines
16 KiB
Python
453 lines
16 KiB
Python
"""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
|