"""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