Кодогенератор (Codegen)
======================

Обзор
-----

Кастомные поля AmoCRM имеют числовые ID и произвольные названия, часто на русском языке.
Кодогенератор автоматически создаёт типизированные подклассы DTO (``MyLead``, ``MyContact``,
``MyCompany``) с named-свойствами для каждого кастомного поля, что позволяет обращаться к ним
как к обычным атрибутам Python вместо ``get_cf_value(123)``.

Имена свойств автоматически транслитерируются из кириллицы в латиницу и приводятся к
snake_case: ``Источник трафика`` → ``istochnik_trafika``.

Быстрый старт
-------------

.. code-block:: python

   from amocrm import OAuthConfig, DjangoTokenStorage
   from amocrm.codegen import generate_custom_fields_dto

   oauth = OAuthConfig(
       client_id="...",
       client_secret="...",
       redirect_uri="https://yourapp.com/oauth/callback",
       storage=DjangoTokenStorage(my_settings_obj),
   )
   generate_custom_fields_dto("mycompany", oauth, output_file="my_models.py")

Функция подключится к AmoCRM, загрузит определения кастомных полей и запишет
готовый Python-код в файл ``my_models.py``.

Пример сгенерированного файла
------------------------------

Для каждого кастомного поля генерируются **и геттер, и сеттер** — можно читать
и записывать значение через стандартный property-синтаксис:

.. code-block:: python

   # Generated by amocrm-sdk codegen. DO NOT EDIT.
   # Regenerate: from amocrm.codegen import generate_custom_fields_dto

   from __future__ import annotations

   from amocrm import Lead, Contact, Company


   class MyLead(Lead):
       """Типизированные кастомные поля для сделок."""

       @property
       def istochnik_trafika(self) -> str | None:
           """Источник трафика (text, id=101)"""
           return self.get_cf_value(101)

       @istochnik_trafika.setter
       def istochnik_trafika(self, value: str | None) -> None:
           self.set_cf_value(101, value)

       @property
       def byudzhet(self) -> float | None:
           """Бюджет (numeric, id=102)"""
           return self.get_cf_value(102)

       @byudzhet.setter
       def byudzhet(self, value: float | None) -> None:
           self.set_cf_value(102, value)

   ...

Подключение к клиенту через ``configure_dto``
----------------------------------------------

После генерации подключите классы к клиенту. Все методы ``get``, ``list``,
``create``, ``update`` начнут возвращать экземпляры вашего подкласса:

.. code-block:: python

   from amocrm.manager import get_client
   from my_models import MyLead, MyContact, MyCompany

   client = get_client(subdomain="mycompany", oauth=oauth)
   client.configure_dto(leads=MyLead, contacts=MyContact, companies=MyCompany)

   lead = client.leads.get(123)          # → MyLead
   print(lead.istochnik_trafika)         # → str | None  (геттер)
   lead.istochnik_trafika = "SEO"        # сеттер — записывает в custom_fields_values
   client.leads.update([lead])           # сохраняет изменения в AmoCRM

   for lead in client.leads.list():      # → Iterator[MyLead]
       print(lead.byudzhet)

Параметры ``configure_dto`` — все необязательны, можно подменить только часть ресурсов:

.. code-block:: python

   client.configure_dto(leads=MyLead)    # contacts и companies остаются стандартными

.. note::

   Вложенные объекты внутри ``Lead`` (``lead.contacts``, ``lead.company``) остаются
   стандартными классами ``Contact`` / ``Company``. Чтобы и они были вашими подклассами,
   переопределите ``from_dict`` в ``MyLead``.

Транслитерация имён
-------------------

Названия кастомных полей на кириллице автоматически транслитерируются перед
преобразованием в snake_case. Это позволяет использовать имена свойств как
стандартные Python-атрибуты.

Примеры транслитерации:

.. list-table::
   :header-rows: 1

   * - Оригинал
     - Результат
   * - ``Источник``
     - ``istochnik``
   * - ``Источник трафика``
     - ``istochnik_trafika``
   * - ``пользовательское_соглашение``
     - ``polzovatelskoe_soglashenie``
   * - ``предыдущая_сделка_1``
     - ``predydushchaya_sdelka_1``

Таблица транслитерации (кириллица → латиница):

.. list-table::
   :header-rows: 1

   * - Символ
     - Транслит
   * - а, б, в, г, д
     - a, b, v, g, d
   * - е, ё, ж, з, и
     - e, yo, zh, z, i
   * - й, к, л, м, н
     - j, k, l, m, n
   * - о, п, р, с, т
     - o, p, r, s, t
   * - у, ф, х, ц, ч
     - u, f, kh, ts, ch
   * - ш, щ, ъ, ы, ь
     - sh, shch, (пусто), y, (пусто)
   * - э, ю, я
     - e, yu, ya

Запись значений через сеттеры
------------------------------

Каждое сгенерированное property имеет сеттер. Тип аргумента совпадает с типом
геттера:

.. code-block:: python

   lead = client.leads.get(123)

   # Обычные скалярные поля (text, numeric, integer, …)
   lead.email = "new@example.com"
   lead.byudzhet = 150000.0

   # Multiselect — передаётся список значений
   lead.uslugi = ["SEO", "Контекст"]

   # Сброс значения поля
   lead.email = None

   client.leads.update([lead])

Под капотом сеттер вызывает методы ``CustomFieldsMixin``:

.. list-table::
   :header-rows: 1

   * - Тип поля AmoCRM
     - Сеттер в миксине
   * - text, numeric, integer, select, phone, email, url, checkbox, bool, date, …
     - ``set_cf_value(field_id, value)``
   * - multiselect
     - ``set_cf_values(field_id, values)``
   * - smart_address
     - ``set_cf_raw(field_id, values)``

Конфигурация
------------

``generate_custom_fields_dto`` принимает следующие параметры:

.. list-table::
   :header-rows: 1

   * - Параметр
     - По умолчанию
     - Описание
   * - ``subdomain``
     - —
     - Поддомен аккаунта AmoCRM (например, ``"mycompany"``)
   * - ``oauth``
     - —
     - Объект :class:`~amocrm.OAuthConfig` с реквизитами и хранилищем токенов
   * - ``entities``
     - ``["leads", "contacts", "companies"]``
     - Список сущностей для генерации
   * - ``base_module``
     - ``"amocrm"``
     - Модуль, из которого импортируются базовые классы
   * - ``output_file``
     - ``None``
     - Путь к выходному файлу; если ``None`` — вывод в stdout

Использование без записи в файл
--------------------------------

Если ``output_file`` не передан, код выводится в stdout:

.. code-block:: python

   generate_custom_fields_dto("mycompany", oauth)

Это удобно для просмотра результата перед сохранением или для использования
вывода в пайплайне.

Генерация только для части сущностей:

.. code-block:: python

   generate_custom_fields_dto(
       "mycompany", oauth,
       entities=["leads"],
       output_file="lead_models.py",
   )

API-справочник
--------------

.. autofunction:: amocrm.codegen.generate_custom_fields_dto

.. autofunction:: amocrm.codegen.generate_and_print

.. autofunction:: amocrm.codegen.fetch_custom_fields

.. autofunction:: amocrm.codegen.generate_custom_models
