Skip to content

Instantly share code, notes, and snippets.

@litnimax
Created August 3, 2026 07:37
Show Gist options
  • Select an option

  • Save litnimax/b2f5f1b69ceb7efd1973d90dcd77b0de to your computer and use it in GitHub Desktop.

Select an option

Save litnimax/b2f5f1b69ceb7efd1973d90dcd77b0de to your computer and use it in GitHub Desktop.
Odoo 19 core architecture, addons/web audit, and Odoo-to-Angee/arpee comparison (RU)

Внутренняя архитектура Odoo 19.0: карта ядра

Срез исходников: ветка 19.0, commit 7d3a7fac27332f440404383f41cc113bf3face53 от 14 марта 2026 года.
Первоначальная область анализа: Python-пакет odoo/ без odoo/addons/**; odoo/addons/base рассматривается только как опорный системный модуль. После уточнения область дополнена отдельным полным аудитом обязательного модуля /Users/max/Dev/odoo_19/addons/web; см. раздел 27 и связанный подробный отчёт.

1. Что именно здесь исследовано

В ядре вне odoo/addons/** находится 207 файлов и 63 542 строки:

Область Файлов Строк Назначение
корень odoo/ 9 4 762 bootstrap, HTTP, БД, исключения, логирование
_monkeypatches/ 20 2 620 ранняя нормализация Python и сторонних библиотек
api/, fields/, models/ 3 74 стабильные публичные фасады
cli/ с шаблонами 46 2 862 команды, конфигурация запуска и scaffold
modules/ 8 2 091 обнаружение, граф, загрузка и миграции модулей
orm/ 23 20 018 модельная система, поля, домены, registry, cache
osv/ 2 477 совместимость старого API выражений
service/ 6 2 540 RPC, серверные процессы, DB administration
tests/ 13 5 342 собственный test runtime Odoo
tools/ 67 20 730 SQL/XML/i18n/media/security/diagnostics utilities
upgrade/, upgrade_code/ 10 2 027 namespace миграций и переписывание кода addons

Каждый из этих файлов включён в инвентаризацию; Python-файлы разобраны по определениям и импортам, а ключевые исполняемые пути — по телам функций и точкам вызова. Приложение в конце даёт назначение каждого файла, чтобы редкая утилита или compatibility shim не потерялись за крупными слоями.

Не включены:

  • соседний каталог репозитория addons/;
  • подробная бизнес-логика res.partner, валют, стран, компаний и других моделей base;
  • web-клиент в первоначальном основном проходе (теперь он разобран в разделе 27 и отдельном отчёте) и прикладные addons;
  • Enterprise-код, инфраструктура сборки и внешние сервисы, которых нет в данном дереве.

2. Главная ментальная модель

Odoo — не просто MVC-приложение и не просто ORM. Ядро одновременно выполняет три роли:

  1. Компилятор расширяемой модели. Оно читает manifests, строит граф addons, импортирует Python-классы-определения, сливает _inherit/_inherits, строит новый набор классов для конкретной базы и синхронизирует его с PostgreSQL.
  2. Транзакционный runtime. Оно представляет данные recordset-объектами, связывает их с Environment и курсором, ведёт field cache, prefetch, dirty values, dependency graph вычисляемых полей и превращает домены в SQL.
  3. Сервер приложений. Оно выбирает базу, восстанавливает сессию, аутентифицирует запрос, сопоставляет маршрут, вызывает ORM/RPC, повторяет конфликтующие транзакции, коммитит изменения и синхронизирует кэши между процессами.

Ключевая единица изоляции — база данных. Для каждой базы существует собственный Registry; в каждом процессе registry содержит собственные динамические классы моделей и кэши. Консистентность между процессами обеспечивается PostgreSQL-сигналами, а не общей памятью.

flowchart TB
    P["Процесс Odoo"] --> C["CLI + config"]
    P --> S["HTTP/cron server"]
    S --> R1["Registry базы A"]
    S --> R2["Registry базы B"]
    R1 --> M1["Динамические классы моделей A"]
    R2 --> M2["Динамические классы моделей B"]
    M1 --> E1["Environment + Transaction + Cursor"]
    M2 --> E2["Environment + Transaction + Cursor"]
    E1 --> DB1[("PostgreSQL A")]
    E2 --> DB2[("PostgreSQL B")]
    DB1 -. "registry/cache signaling" .-> R1
    DB2 -. "registry/cache signaling" .-> R2
Loading

3. Полная карта логических слоёв

Номера ниже обозначают порядок понимания, а не строгую однонаправленную зависимость: tools и base пересекают несколько уровней.

Логический слой Главные файлы Что он гарантирует
0 Runtime prerequisites release.py, init.py, _monkeypatches/* допустимые версии, UTC, GC, gevent mode, совместимость зависимостей
1 Публичный Python API api/, fields/, models/, orm/__init__.py, exceptions.py стабильные точки импорта и общая таксономия ошибок
2 Конфигурация и команды tools/config.py, cli/*, odoo-bin, __main__.py слияние настроек и выбор режима процесса
3 Addon namespace и metadata modules/module.py пути addons, manifests, Python/external dependencies, импорт пакетов
4 Граф и lifecycle модулей module_graph.py, loading.py, migration.py, db.py порядок base/install/upgrade/uninstall, data files, hooks, schema
5 Registry и сборка классов orm/registry.py, model_classes.py, models.py отдельная модельная вселенная на базу, _inherit/_inherits, setup
6 Recordset и Environment orm/models.py, environments.py, identifiers.py API работы с наборами записей и транзакционным состоянием
7 Система полей orm/fields*.py, commands.py, decorators.py descriptors, conversion, relational commands, compute/inverse/related
8 Query language orm/domains.py, tools/query.py, tools/sql.py, osv/expression.py Domain AST → оптимизированный Query → параметризованный SQL
9 Persistence и транзакции sql_db.py, CRUD в orm/models.py pool, cursor, savepoint, flush, commit/rollback callbacks, DB I/O
10 Кэш и реактивные зависимости environments.py, registry.py, tools/cache.py field cache, dirty state, recompute graph, method cache, invalidation
11 Security и tenancy ORM access checks + модели base ACL, record rules, field groups, company consistency, auth/session
12 HTTP и RPC http.py, service/model.py, service/common.py, service/security.py маршрутизация, dispatchers, CSRF, session, RPC boundary, retrying
13 Процессы и фоновые задачи service/server.py, netsvc.py, base.ir_cron threaded/prefork/gevent, workers, cron, reload, limits, logging
14 Данные и эволюция tools/convert.py, import_xml.rng, migration.py, upgrade_code/* XML/CSV/SQL import, migrations данных, source-to-source upgrade
15 Системный control plane odoo/addons/base поверхностно metadata базы, access, views/actions, cron, attachments, users/companies
16 Cross-cutting tools tools/* i18n, XML, media, mail, PDF, safe eval, profiler, collections
17 Тестовый runtime tests/* транзакционная изоляция, tags, HTTP/Chrome tests, reporting

4. Слой 0: запуск Python и ранние monkeypatches

odoo-bin импортирует odoo.cli и вызывает odoo.cli.main(). Запуск python -m odoo приходит в odoo/__main__.py и заканчивается тем же dispatcher команд.

init.py исполняется до тяжёлых импортов:

  • проверяет границы Python, заданные в release.py — для этого среза 3.10–3.13;
  • меняет пороги cyclic GC и включает измерение времени GC;
  • вызывает _monkeypatches.patch_init() до импорта затрагиваемых библиотек;
  • экспортирует SUPERUSER_ID, _, _lt и Command в верхнее пространство odoo.

Monkeypatch-слой построен аккуратнее, чем серия немедленных импортов. _monkeypatches/__init__.py вставляет finder первым в sys.meta_path. Если целевая библиотека уже загружена, её patch_module() вызывается сразу; если нет — loader оборачивается, и patch применяется сразу после будущего импорта.

Что нормализуется:

  • site.py: TZ=UTC, alias кодировок; специальная команда gevent выполняет gevent.monkey.patch_all(), включает psycopg2 wait callback и ставит odoo.evented=True;
  • werkzeug, urllib3, email, mimetypes: HTTP, headers, MIME и совместимость версий;
  • lxml, bs4, docutils, zeep: XML/HTML/SOAP поведение и безопасность;
  • csv, locale, pytz, num2words, stdnum: единообразие данных и локалей;
  • xlrd, xlwt, xlsxwriter: поведение spreadsheet-библиотек;
  • ast, re: совместимость Python runtime.

Это нижний compatibility substrate. Ошибка здесь влияет на все базы и все addons ещё до создания конфигурации или registry.

5. Слой 1: публичные фасады и ошибки

odoo.api, odoo.fields и odoo.models — фасады над odoo.orm, а не отдельные реализации. Они позволяют addon-коду зависеть от стабильных путей импорта, пока внутренние файлы ORM можно перестраивать. odoo.modules.registry аналогично оставляет compatibility import нового odoo.orm.registry.

orm/__init__.py документирует и соблюдает порядок импортов ORM. Порядок критичен из-за metaclass-регистрации, descriptors и циклических типов.

exceptions.py задаёт ошибки, пересекающие Python, HTTP и RPC:

  • UserError, ValidationError, RedirectWarning — ожидаемые пользовательские исходы;
  • AccessDenied — ошибка аутентификации, HTTP 403, без полезного traceback наружу;
  • AccessError — нарушение прав, HTTP 403;
  • MissingError — исчезнувшая/недоступная запись, HTTP 404;
  • LockError — конфликт блокировки, HTTP 409;
  • ConcurrencyError — внутренний сигнал об обязательном повторе транзакции;
  • CacheMiss — внутреннее отсутствие значения field cache, не бизнес-ошибка.

Важно: HTTP status — лишь одна проекция исключения. JSON-RPC может завернуть то же исключение в собственный envelope, а service.model.retrying() может вообще не вернуть его клиенту, а откатить и повторить транзакцию.

6. Слой 2: configuration pipeline и CLI

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

tools/config.py — типизированный registry опций и одновременно ChainMap со следующим приоритетом:

runtime overrides > command line > environment > config file > defaults

У каждой опции описано, разрешена ли она в CLI, environment и файле, как она парсится и экспортируется. Post-processing:

  • проверяет addons_path, upgrade_path, DB и worker settings;
  • сводит --init, --update, --reinit в maps состояний модулей;
  • выводит test_enable из tags и включает stop_after_init для тестов;
  • разворачивает --dev=all и logger overrides;
  • настраивает отдельные primary/readonly PostgreSQL endpoints и pools;
  • вычисляет data_dir, session_dir, filestore, базовые пути addons;
  • хеширует и проверяет master password администратора БД через crypt_context.

Три стандартных addon-корня имеют разную роль:

  1. odoo/addons — встроенный base;
  2. ${data_dir}/addons/<series> — addons из data directory;
  3. явно переданные пути и соседний <repo>/addons — прикладные модули.

6.2 Command dispatcher

cli/command.py автоматически регистрирует subclasses Command. Встроенные команды импортируются лениво. CLI addons могут быть найдены по путям и загружены точечно, без импорта всего addon. До выбора команды pre-parser понимает лишь общий --addons-path.

Если команда не указана, выбирается server; help показывает registry команд. Основные режимы:

  • server — полноценный запуск, создание отсутствующей явно запрошенной БД и init base;
  • start — developer shortcut: обнаруживает каталог addons, имя БД и запускает server;
  • shell — интерактивный Python с registry/environment;
  • db — create/drop/dump/restore/duplicate/rename и другие DB operations;
  • module, i18n, neutralize, populate, cloc — обслуживание addon metadata и данных;
  • scaffold — генерация default/theme/payroll skeletons;
  • deploy — упаковка addon и upload на удалённый Odoo endpoint;
  • upgrade_code — последовательные source transforms;
  • obfuscate — подготовка обезличенного dump;
  • help — справка.

cli/server.py проверяет опасный запуск от root/PostgreSQL system user, настраивает logging/PID, а затем передаёт управление service.server.start().

7. Слои 3–4: addon namespace, manifests и lifecycle модулей

7.1 Addon как единица расширения

modules/module.py создаёт namespace odoo.addons из упорядоченных addon paths. После инициализации namespace finders замораживаются, чтобы один и тот же module name не начал резолвиться иначе посреди процесса.

Manifest:

  • ищет __manifest__.py/legacy manifest names;
  • читает Python literal через ast.literal_eval, а не исполняет произвольный manifest;
  • добавляет defaults, валидирует обязательные поля и нормализует version;
  • перечисляет dependencies, data/demo, assets, hooks, external Python/binary dependencies;
  • даёт пути static/icon и определяет installability.

Импорт addon вызывает его __init__.py, регистрируя definition classes через MetaModel; post_load — процессный hook, который может исполняться до registry и поэтому должен быть database-agnostic.

7.2 Bootstrap новой базы

modules/db.py создаёт минимальную базу из base/data/base_data.sql сырым SQL, прежде чем ORM этой базы вообще существует. Затем manifests отражаются в ir_module_module, создаются зависимости/categories/XML IDs и рекурсивно отмечаются auto-install modules. Здесь же проверяются возможности PostgreSQL вроде unaccent/trigram.

Следствие: чистое Python-ядро импортируется без base, но рабочая Odoo database не существует без bootstrap-данных base.

7.3 Граф модулей

module_graph.py строит DAG из manifests и DB state. ModuleNode несёт versions, state, dependency depth, load phase и стабильное order_name; итоговая сортировка использует (phase, depth, order_name).

Граф:

  • отделяет base, обычную загрузку, install и upgrade phases;
  • отбрасывает отсутствующие, uninstallable и неподходящие по состоянию узлы;
  • обнаруживает cycles;
  • распространяет невозможность загрузки на dependents;
  • позволяет пройти граф в обратном порядке при uninstall.

7.4 load_modules() как компиляция системы

modules/loading.py — центральный build pipeline:

  1. Base phase. Инициализация путей/БД; определение уже переведённых и company-dependent колонок; импорт и setup base.
  2. State phase. Обновление списка manifests; перевод выбранных модулей в to install, to upgrade, to remove или reinit.
  3. Graph loop. Повторное расширение графа, потому что hooks во время загрузки могут изменить состояния других модулей.
  4. На каждый module node: pre-migrations → Python import → добавление definition classes в registry → incremental setup/schema → data/demo → post-migrations → translations → at_install tests → module state/version → flush и commit.
  5. End migrations и проверки: constraints, cleanup metadata, удалённые columns/XML IDs/cron residue, проверка extended/custom fields, schema и views.
  6. Uninstall. Reverse dependency order, uninstall hooks и data removal; затем рекурсивная полная пересборка registry.
  7. Finalization. register hooks, окончательные constraints/not-null, post_install tests на уровне preload.

Коммит после успешно загруженного модуля делает installation pipeline возобновляемым по модулям, но означает, что это не одна общая ACID-транзакция на весь upgrade набора.

flowchart LR
    A["Manifest paths"] --> B["ModuleGraph"]
    B --> C["pre migration"]
    C --> D["import Python definitions"]
    D --> E["registry.load"]
    E --> F["model/field setup + schema"]
    F --> G["XML/CSV/SQL + demo"]
    G --> H["post migration + translations"]
    H --> I["at_install tests"]
    I --> J["flush + commit module"]
    J --> K["constraints/cleanup/hooks"]
Loading

modules/migration.py ищет versioned pre-, post- и end- scripts в addon migrations/, upgrades/ и namespace odoo.upgrade; сравнение версий определяет, какие scripts лежат между установленной и целевой версиями. Они импортируются изолированно и получают cursor/version.

8. Слой 5: Registry и динамическая сборка классов

orm/registry.py — mapping model_name → model class для одной базы. Это не просто cache схемы.

8.1 Жизненный цикл Registry

Registry.new(dbname, update_module=...):

  1. берёт глобальную блокировку построения;
  2. создаёт и временно публикует registry, чтобы рекурсивный код уже мог его найти;
  3. подключает DB handles и inter-process signaling;
  4. проверяет marker незавершённого module update;
  5. вызывает load_modules();
  6. завершает setup, помечает loaded/ready и рассылает changes;
  7. при ошибке удаляет неполный registry и освобождает его ресурсы.

Глобальный LRU registries ограничен памятью: оценка — около 15 MB на одну базу, либо фиксированный fallback. Eviction закрывает DB pools/ресурсы registry данного процесса, но не меняет БД.

8.2 Definition class и registry class — разные сущности

orm/models.py использует MetaModel для сбора классов, импортированных из odoo.addons.<module>. Такой класс описывает вклад addon, но ещё не является итоговой моделью конкретной базы.

orm/model_classes.py строит отдельный dynamic class внутри Registry:

  • _name задаёт модель;
  • _inherit='x' без нового _name расширяет x in-place в registry;
  • _name='y', _inherit='x' создаёт классическое наследование модели;
  • _inherits={'x': 'x_id'} делегирует хранение через Many2one и создаёт inherited related fields;
  • кроме модели base, все модели неявно наследуют base;
  • MRO и __bases__ вычисляются из установленных именно в этой БД addons.

Поэтому один Python-процесс может обслуживать две базы, где одно имя модели имеет различный набор полей и методов.

8.3 Setup phases

Сборка идёт фазами:

  1. определить итоговый MRO;
  2. собрать и слить definitions полей;
  3. добавить manual models/fields из DB metadata;
  4. настроить attributes полей, related chains и comodels;
  5. построить compute dependencies, inverse maps, relation metadata и trigger trees;
  6. создать/изменить schema и reflection rows;
  7. зарегистрировать constraints, indexes, hooks и post-setup state.

Fields могут переиспользоваться между registries, если они неизменны; overridden, related и registry-specific fields клонируются. Это компромисс между memory footprint и строгой изоляцией.

8.4 Базовые типы моделей

  • AbstractModel: _auto=False, _abstract=True; mixin без собственной обычной таблицы.
  • Model: _auto=True, _abstract=False; постоянные записи.
  • TransientModel: временные записи, creator-oriented access и autovacuum по возрасту/количеству с минимальным окном около пяти минут.

9. Слой 6: Recordset, Environment и Transaction

9.1 Recordset — единственный объект модели

Экземпляр BaseModel имеет __slots__ = (env, _ids, _prefetch_ids). Отдельного класса «одна запись» нет:

  • пустой recordset — model proxy;
  • recordset из одного id — singleton;
  • несколько ids — упорядоченная коллекция;
  • iteration создаёт singleton recordsets, сохраняющие общий prefetch set;
  • |, &, -, membership и sequence operations работают над ids;
  • filtered, mapped, grouped, sorted сохраняют декларативный стиль.

NewId представляет псевдозапись до INSERT или onchange-копию. Он ложен в boolean context, может ссылаться на origin или ref и живёт только в памяти транзакции.

9.2 Environment

orm/environments.py связывает:

cursor + uid + immutable context + su flag + registry

Environments дедуплицируются внутри одной transaction. env['res.users'] берёт итоговый class из registry и возвращает пустой recordset, связанный с этим env.

Ленивые properties дают user, company, companies, lang, tz. Производные environments создаются через:

  • with_context — новый immutable context;
  • with_user — другой uid;
  • sudo — переключает bypass access checks, но сам по себе не обязан менять uid;
  • with_company — корректирует company context.

Это принципиальное различие: identity пользователя, superuser mode и allowed companies — три независимые координаты.

9.3 Transaction

Transaction прикрепляется к cursor лениво и обща для всех environments на нём. Она содержит:

  • WeakSet environments и default environment;
  • field cache и dirty record ids;
  • patches для x2many;
  • protected fields во время inverse/compute;
  • очередь tocompute;
  • compatibility cache;
  • временные файловые пути и cleanup state.

Environment — view на transaction с конкретными security/context coordinates; он не равен транзакции. Создание нового env на том же cursor не создаёт новый commit boundary.

10. Слой 7: поля как descriptors и schema contracts

Base Field в orm/fields.py одновременно является:

  • Python descriptor;
  • типом конвертации cache/record/write/column/export;
  • декларацией SQL column или relation;
  • узлом compute dependency graph;
  • носителем ACL groups, default, index, copy и prefetch policy.

10.1 Чтение descriptor

Упрощённый Field.__get__:

  1. проверить field-level groups и singleton;
  2. если stored compute ожидает recompute — выполнить его;
  3. попытаться взять значение из field cache;
  4. для real stored record запустить batch prefetch из БД;
  5. для NewId попробовать origin/default;
  6. вычислить non-stored compute;
  7. для delegated field пройти через _inherits parent;
  8. вернуть типизированное cache value или поднять MissingError для исчезнувшей реальной записи.

10.2 Запись descriptor

Для реальной записи assignment обычно вызывает write(). Для protected/new records descriptor меняет cache, inverse state и dependency notifications без немедленного SQL. Это позволяет onchange, inverse methods и графу recompute работать с виртуальным состоянием.

10.3 Семейства полей

  • fields_misc.py: Boolean, Json, Id и общие простые значения;
  • fields_numeric.py: Integer, Float, Monetary, rounding/digits;
  • fields_textual.py: Char, Text, Html; переводы могут храниться в JSONB, LangProxyDict даёт language view;
  • fields_temporal.py: Date, Datetime, timezone/context helpers;
  • fields_binary.py: Binary, Image, attachment-backed storage и image processing;
  • fields_selection.py: статические/динамические selections и extension;
  • fields_reference.py: polymorphic Reference, Many2oneReference;
  • fields_relational.py: Many2one, One2many, Many2many, inverse/relation tables/ondelete;
  • fields_properties.py: динамические JSONB properties и их schema definitions.

10.4 X2many command protocol

orm/commands.py делает явным старый tuple-протокол:

Код API Смысл
0 Command.create(values) создать связанную запись
1 Command.update(id, values) изменить связанную запись
2 Command.delete(id) удалить запись из БД
3 Command.unlink(id) разорвать связь
4 Command.link(id) добавить существующую связь
5 Command.clear() очистить связи
6 Command.set(ids) заменить множество связей

10.5 Decorators как compile-time metadata

orm/decorators.py не просто оборачивает вызовы. @depends, @depends_context, @constrains, @onchange, @ondelete, @autovacuum, @model, @model_create_multi, @private, @readonly оставляют metadata, которое registry собирает во время setup. @private вместе с именами _... закрывает метод для RPC.

11. Слой 8: Domain → Query → SQL

11.1 Domain AST

orm/domains.py в Odoo 19 представляет domain как immutable AST: boolean constants, Not, And, Or, Condition, Custom. Legacy prefix lists принимаются и преобразуются.

Поддерживаются comparisons, in, like-семейство, hierarchy operators и relation quantifiers any, not any, any!. any! намеренно обходит record rules comodel и потому должен применяться как привилегированный инструмент, а не как косметический вариант any.

Оптимизация выполняется до fixed point с защитным пределом:

  • flatten/constant folding и boolean algebra;
  • переписывание custom field search methods;
  • нормализация типов и ложных/null значений;
  • преобразование relational/hierarchy predicates;
  • присоединение access-rule domains на нужных моделях;
  • генерация SQL fragments и joins.

osv/expression.py сохраняет старый namespace и compatibility helpers, но новая семантическая модель находится в orm.domains.

11.2 Query

tools/query.py хранит FROM, joins, where, order, limit/offset и может memoize полученные ids. Это промежуточное представление, позволяющее полям и security rules добавлять joins/conditions без ручной конкатенации строки.

11.3 SQL

tools/sql.py задаёт composable SQL(code, params, to_flush=...):

  • code и parameters хранятся раздельно;
  • identifiers quote-ятся явно;
  • nested SQL objects безопасно композиционируются;
  • to_flush несёт metadata полей, которые нужно flush до выполнения запроса;
  • рядом находятся helpers introspection/DDL: tables, columns, indexes, constraints, FKs.

Это не sandbox для произвольного SQL, но основной способ не смешивать data с query text и связать raw SQL с pending ORM writes.

12. Слои 9–10: БД, flush, cache и recompute

12.1 Connection pools и cursors

sql_db.py содержит два глобальных pools — read/write и readonly. ConnectionPool:

  • ограничивает число соединений;
  • переиспользует подходящее idle connection по DSN;
  • сбрасывает state перед выдачей;
  • обнаруживает leaked/closed connections;
  • никогда не включает пароль в сравнение или diagnostic output.

Connection — настроенный handle; cursor() создаёт Odoo Cursor над psycopg2. Cursor работает в repeatable-read, на replica включает readonly session, считает queries/time и вызывает profiling hooks.

12.2 Cursor transaction protocol

BaseCursor имеет callback queues:

precommit → PostgreSQL COMMIT → clear transaction → postcommit
prerollback → PostgreSQL ROLLBACK → postrollback

Перед commit flush() повторяет transaction flush и precommit hooks до стабилизации, максимум десять циклов. Context manager коммитит только при отсутствии исключения и всегда закрывает cursor.

Savepoint бывает обычным и flushing. Flushing savepoint делает flush до/после, а при ошибке очищает transaction cache до rollback-to-savepoint, чтобы Python state не утверждал, будто откатившиеся данные существуют.

Критическое различие:

  • flush синхронизирует pending ORM state с текущей DB transaction;
  • commit делает transaction видимой другим connections;
  • invalidate забывает cache values;
  • clear/reset сбрасывает Python-side transaction state;
  • ни одна из этих операций не является синонимом другой.

12.3 Field cache

Field cache индексируется примерно так:

field → context-cache-key → record id → cache value

Context key включает только объявленные зависимости и специальные координаты: company, uid+su, lang, active_test, bin_size. Если compute зависит от произвольного context key, но не объявляет его через depends_context, значение может быть переиспользовано неверно.

Dirty field означает: cache уже содержит новое значение, а PostgreSQL ещё нет. Invalidation записи удаляет все context variants соответствующего поля/id.

12.4 Prefetch

Recordset хранит собственные ids и более широкий _prefetch_ids. Чтение одного stored field у singleton обычно загружает это поле сразу для всего prefetch batch. Related traversal распространяет prefetch на comodel. Поэтому idiomatic iteration не создаёт N+1 автоматически, но slicing, новые независимые browse-наборы и raw SQL могут разрушить batching.

12.5 Compute dependency graph

Во время registry setup зависимости полей превращаются в TriggerTree. modified(..., before=True/after=True) проходит inverse relations:

  • stored computes ставятся в tocompute;
  • non-stored cached values инвалидируются;
  • relation change отмечается до и после, чтобы затронуть и старых, и новых owners.

Environment.flush_all() выполняет fixed-point loop: recompute → dirty writes → новые dependency notifications, пока состояние не стабилизируется; защитный предел ловит циклы/неустойчивые computes.

12.6 Method cache и inter-process invalidation

tools/cache.py реализует ormcache families. Это registry-level LRU для результатов методов и не то же самое, что transaction field cache.

Registry ведёт именованные caches и sequences. Межпроцессная синхронизация использует append-only PostgreSQL signaling tables:

  • registry sequence mismatch требует перезагрузить структуру registry;
  • cache sequence mismatch очищает конкретный named cache;
  • signal_changes() публикует локальные invalidations после успешной transaction;
  • check_signaling() применяется на границе новых запросов/работ.

13. CRUD: что реально происходит при чтении и записи

13.1 Defaults

Порядок источников default values:

  1. context['default_<field>'];
  2. ir.default без company override;
  3. callable/static field.default;
  4. company-dependent fallback;
  5. defaults delegated parents для _inherits.

13.2 Search/read

search:

  1. нормализует/оптимизирует Domain;
  2. добавляет active_test, если применимо;
  3. проверяет model ACL;
  4. добавляет record-rule domain;
  5. строит Query/joins/order/limit;
  6. выполняет параметризованный SQL и создаёт recordset.

fetch проверяет field ACL, расширяет stored dependency fields, группирует prefetch и кладёт значения БД в cache, не перетирая dirty values текущей transaction.

13.3 Create

Упрощённая последовательность:

  1. model create ACL и field groups;
  2. defaults и log-access metadata;
  3. precomputed fields;
  4. create/update delegated _inherits parents;
  5. batch INSERT column fields;
  6. cache, inverse и dependency notifications;
  7. relational/non-column fields;
  8. recompute, Python/SQL constraints и company checks;
  9. optional XML ID при import mode.

13.4 Write

write проверяет ACL/rules/field groups, нормализует значения, защищает inverse/compute fields, делает before-notification relation dependencies, применяет column и relational updates в определённом приоритете, вызывает inverses, затем after-notification, constraints и company checks. Dirty scalar values могут быть объединены в batch UPDATE только при flush.

13.5 Unlink

unlink выполняет ACL и @ondelete hooks, flush необходимых данных, dependency notifications, chunked SQL DELETE, очистку XML IDs/attachments/defaults и company-dependent JSONB references, после чего инвалидирует затронутый cache. SQL FK policies остаются последней линией referential integrity.

13.6 Schema reflection

_auto_init/registry initialization сравнивают declarative model с PostgreSQL: создают tables/columns, конвертируют types, назначают recompute новых stored fields, применяют indexes/constraints/FKs и отражают результат в ir.model*. Это объясняет, почему module installation одновременно является import Python, migration engine и schema compiler.

14. Слой 11: security model

Security распределена между Python kernel и системными моделями base.

14.1 Четыре независимых проверки

  1. Authentication — кто пользователь/session/API key; HTTP и res.users.
  2. Model ACL — разрешены ли create/read/write/unlink для модели; ir.model.access.
  3. Record rules — разрешены ли конкретные rows; ir.rule добавляет domain по operation.
  4. Field groups — можно ли видеть/писать конкретное поле; metadata Field.groups.

Отдельно ORM проверяет multi-company consistency отношений. sudo() обходит access controls, но не должно восприниматься как автоматическая смена пользователя или разрешённых компаний.

ACL проверяется до row query; record rules становятся частью SQL/domain либо проверяются на recordset; field access применяется при fetch/read/write и descriptor access. Ошибка доступа может намеренно выглядеть как отсутствие записи, чтобы не раскрывать существование объекта.

14.2 RPC boundary

service/model.py отклоняет:

  • имена методов, начинающиеся с _;
  • unsafe/static/class attributes;
  • methods с @api.private.

call_kw определяет model-method или record-method форму, восстанавливает recordset по ids, применяет context и адаптирует специальные create conventions. Это boundary adapter; сами ACL всё равно обязаны применяться ORM/model code.

15. Слой 12: HTTP, sessions и RPC

http.py — transport kernel. DB-aware policy делегируется модели ir.http из base.

15.1 Маршруты и controllers

Controller classes также расширяются addons. При генерации routing rules Odoo перестраивает controller inheritance только из установленных модулей и сливает metadata нескольких @route по MRO.

Route metadata включает:

  • type: http, jsonrpc, json2;
  • auth: none, public, user, bearer;
  • methods, CORS, CSRF, captcha;
  • readonly policy;
  • session saving и response behavior.

auth='none' — единственный режим, пригодный без registry/DB. Bearer по умолчанию stateless. Controller method вызывает model/API code уже внутри request environment.

15.2 Выбор базы

Request пытается установить DB из валидной session, stateless header X-Odoo-Database, dbfilter или monodb. Нельзя одновременно принять cookie-session одной базы и противоречащий ей header другой. Database selection происходит до построения registry/env и является security boundary multi-tenancy.

15.3 Application flow

sequenceDiagram
    participant W as WSGI server
    participant A as Application
    participant H as http.py Request
    participant R as Registry
    participant IH as base.ir_http
    participant M as Controller/Model
    participant DB as PostgreSQL

    W->>A: WSGI environ
    A->>H: sanitize proxy + create request
    alt static asset
        H-->>A: file response
    else no database
        H->>M: auth=none route
    else database request
        H->>R: acquire/check signaling
        R->>DB: readonly or primary cursor
        H->>IH: _match + _authenticate
        IH->>M: dispatch endpoint
        M->>DB: ORM/SQL
        H->>DB: flush + commit
        H->>R: signal cache changes
    end
    A-->>W: response / mapped exception
Loading

DB request сначала получает Registry, открывает cursor, строит Environment и вызывает ir.http._match. Readonly route может пойти на replica. Если внутри обнаружилась запись, request повторяется с primary read/write cursor.

Весь endpoint исполняется через service.model.retrying(). На lock/serialization/deadlock/ConcurrencyError текущая transaction откатывается, env/registry/session/file streams сбрасываются и вызов повторяется до пяти раз с exponential random backoff. Следствие для addon-кода: до commit внешние side effects должны быть идемпотентными или отложенными в postcommit.

15.4 Dispatchers

  • HttpDispatcher: объединяет path/query/form/files, валидирует CSRF для unsafe methods, вызывает endpoint и строит обычный Response.
  • JsonRPCDispatcher: принимает JSON-RPC named params; поле method envelope исторически не является именем model method; возвращает JSON-RPC result/error envelope.
  • Json2Dispatcher: JSON body/args, более естественные HTTP statuses и сериализация ошибок.

Общий dispatcher pre/post слой обрабатывает content limits, preflight/CORS, сохранение/ротацию session, future response headers и CSP.

15.5 Sessions и CSRF

Filesystem session store разбивает файлы по префиксу SID и валидирует ключи. Session — JSON-normalized mutable mapping с flags dirty, new, rotate. Session token связывает user identity и SID; rotation/delete имеют grace semantics.

CSRF token — HMAC от DB secret, первых байтов SID и expiry. Проверка связана не только с cookie, но и с конкретной базой и сроком жизни. Аутентификация поддерживает partial pre-session и финализацию MFA; device metadata попадает в audit trail через security service/base models.

15.6 Legacy services

  • service/common.py: version и legacy authentication RPC;
  • service/db.py: master-password-guarded управление databases и filestore, pg_dump/restore, DB listing/filtering;
  • service/security.py: session token verification/migration и device logging;
  • http.dispatch_rpc: router старых common, db, object service namespaces.

16. Слой 13: process model, workers и cron

service/server.py выбирает topology:

odoo.evented == True  → GeventServer
workers > 0           → PreforkServer
иначе                 → ThreadedServer

Перед стартом загружаются server-wide modules — process-level packages, по умолчанию base и web. Затем preload_registries() может собрать registries указанных БД, выполнить updates/tests и только после этого принимать traffic.

16.1 ThreadedServer

  • Werkzeug HTTP server с thread-per-request;
  • daemon cron threads в том же процессе;
  • signal handling и graceful shutdown;
  • request, CPU/real time и memory limits;
  • watcher .py файлов с compile-before-reload;
  • optional systemd socket activation.

16.2 PreforkServer

Master process:

  • владеет listening socket;
  • fork-ит WorkerHTTP и WorkerCron;
  • отдельно запускает gevent long-polling/websocket process;
  • поддерживает требуемое количество поколений workers;
  • получает heartbeats/watchdog data через pipes;
  • убивает workers по memory/time/request limits;
  • делает graceful/hot reload, сохраняя socket.

Каждый worker имеет собственные Python heaps, registries и caches; общая координация проходит через PostgreSQL и OS primitives.

16.3 GeventServer

Gevent WSGI server получает monkeypatched stdlib ещё в site patch, кладёт raw socket в WSGI environ для websocket upgrade и использует собственный watchdog/memory accounting. Это cooperative concurrency; блокирующая библиотека без gevent integration блокирует event loop.

16.4 Cron

Worker/listener использует PostgreSQL LISTEN cron_trigger плюс periodic polling с jitter. Для найденной базы вызывается ir.cron._process_jobs. Cron model захватывает jobs, ведёт progress/failure state и исполняет actions в отдельных transaction boundaries. Это base control plane, а не scheduler внутри orm.

netsvc.py добавляет Odoo log levels/formatters, colored output, DB log handler, query/elapsed performance annotations и warning stacks. loglevels.py регистрирует специальные numeric levels, включая runbot-oriented output.

17. Слой 14: data import, migrations и upgrade code

17.1 Declarative data loader

tools/convert.py загружает module data:

  • XML валидируется import_xml.rng;
  • CSV идёт через Model.load и типовые converters;
  • SQL исполняется напрямую в module transaction;
  • JS data files игнорируются Python loader и предназначены другим consumers.

XML dialect включает record, delete, function, menuitem, template, asset, ref, eval, search, nested records, sequence. Loader ведёт XML IDs, noupdate, forcecreate, import context и связывает records между модулями.

17.2 Runtime DB migrations

Versioned migration scripts — часть module loader и исполняются внутри upgrade lifecycle до/после module data, а end scripts — после основного графа. neutralize.sql установленного набора модулей агрегируется modules/neutralize.py для обезвреживания production side effects в копии БД.

17.3 Source upgrade

cli/upgrade_code.py и upgrade_code/*.py — другой механизм. Он переписывает исходники addons при переходе версий: tree→list view naming, SQL constraints, JSON-RPC routes, translation constructs, dynamic dates, deprecated properties и отдельные налоговые API. Он не изменяет живую DB schema и не заменяет migration scripts.

18. Слой 15: что делает addons/base, не заходя в бизнес-логику

base называет себя kernel module, но архитектурно это DB-resident control plane поверх Python kernel. Его можно разделить на блоки:

Блок Модели/файлы Роль в ядре
Метамодель ir.model, ir.model.fields, ir.model.data, constraints/relations отражение Python models/fields и XML IDs в БД
Module state ir.module.module, dependencies/categories install/upgrade/uninstall state machine
Security users, groups, ir.model.access, ir.rule, API keys identity, ACL и row-level domains
HTTP control ir.http DB-aware route matching, auth, dispatch/error hooks
Presentation metadata views, menus, actions, QWeb, assets, reports декларативное описание интерфейса и rendering contracts
Automation ir.cron, autovacuum, sequences background jobs и housekeeping
Storage/config attachments, binary, config parameters, defaults filestore/blob access и системная конфигурация
Localization primitives languages, countries, currencies базовые shared entities, без прикладного поведения здесь
Tenancy/identity companies, users, partners, devices минимальные системные principals и company graph
Service support mail server, logging, exports, filters, profiles инфраструктурные сервисы базы

res.partner здесь достаточно понимать как базовую shared identity/contact entity, на которую опираются пользователи и множество addons. Её поля, UI и бизнес-правила не нужны для понимания механики ядра. То же относится к bank/country/currency/company domain details.

Архитектурный вывод: часть важнейших policies (ir.rule, ir.http, ir.cron) расширяема обычным module inheritance и потому намеренно вынесена из hard-coded Python kernel в base.

19. Слой 16: cross-cutting toolbox

tools — не один слой зависимости, а библиотека вертикалей.

19.1 Данные и локализация

  • translate.py: extraction Python/JS/XML, PO/CSV/tar readers/writers, import в БД, lazy gettext, language loading и translation upgrade queries;
  • babel/*, i18n.py: extractors, locale parsing и locale-sensitive formatting;
  • date_utils.py, float_utils.py, intervals.py: доменно-нейтральные операции дат, округления и множеств интервалов;
  • arabic_reshaper/*: shaping арабского текста для renderers без native shaping.

19.2 XML, views и templates

  • xml_utils.py: parsers, cleanup, XSD validation/fetching;
  • template_inheritance.py: поиск targets и применение XML inheritance specs;
  • view_validation.py: Relax NG и semantic validation expressions/domains;
  • rendering_tools.py: inline template syntax и QWeb conversion;
  • js_transpiler.py, sourcemap_generator.py: преобразование JS modules в Odoo module format и source maps.

19.3 Media и documents

  • image.py: decode/resize/crop/quality/orientation и color profile;
  • mimetypes.py: content sniffing, OOXML/OLE/SVG/WebP checks, extension correction и MIME neutering;
  • pdf/*: facade над несколькими pypdf/PyPDF2 APIs и digital signature support;
  • barcode.py: barcode rendering/check digits/font;
  • mail.py: address parsing, header/message helpers и email normalization;
  • rendering_tools.py, which.py: поиск внешних render executables и подготовка данных.

19.4 Безопасное-ish исполнение

safe_eval.py компилирует выражение, проверяет bytecode whitelist, рекурсивно проверяет вложенные code objects, запрещает imports, опасные stores, dunder/frames/code attributes и даёт ограниченный globals set.

Это механизм снижения доступных возможностей для server actions/domains, но не процессная/контейнерная изоляция. Любой новый разрешённый object в globals расширяет attack surface.

19.5 Collections и инфраструктура

  • lru.py, cache.py: LRU и registry method caches;
  • func.py: lazy/class properties, synchronization, descriptor helpers;
  • facade.py: proxy objects/functions для compatibility exports;
  • set_expression.py: ленивые union/intersection expressions над ID sets;
  • misc.py: широкая compatibility/utilities surface — filesystem, formatting, batching, locale, subprocess, collections;
  • osutil.py, appdirs.py, urls.py, json.py, pycompat.py: OS paths/archive, app directories, URL joining, script-safe JSON, Python compatibility;
  • query.py, sql.py: query/persistence primitives, рассмотренные выше;
  • populate.py: массовое синтетическое заполнение моделей с variation factors;
  • cloc.py: подсчёт строк addons и классификация файлов.

19.6 Интеграции

  • zeep/*: локальные wrappers/fixes SOAP client, WS-Addressing, WS-Security и WSDL helpers;
  • _vendor/send_file.py, sessions.py, useragents.py: закреплённые implementations web utilities, чтобы поведение не дрейфовало вместе с dependency versions.

20. Слой 17: observability и тестовый runtime

20.1 Profiling

tools/profiler.py поддерживает collectors:

  • SQL queries с full query, timing и Python stack;
  • asynchronous periodic stack sampling;
  • synchronous call/return tracing;
  • QWeb directive tracing;
  • memory/tracemalloc-oriented collection.

Collectors прикрепляются к thread hooks/cursor hooks, ExecutionContext добавляет semantic labels, а результат экспортируется в Speedscope-compatible data. Отдельные helpers отслеживают GC pauses и cache statistics.

20.2 Test cases

tests/common.py строит собственный DB-aware test runtime:

  • TransactionCase: одна outer transaction на class, setup data один раз; каждый test method внутри savepoint, который откатывается; commit/rollback/close test cursor запрещены;
  • SingleTransactionCase: все методы разделяют одну transaction до конца class;
  • HttpCase: transaction semantics плюс HTTP server/browser coordination;
  • ChromeBrowser: запускает headless Chrome, управляет DevTools Protocol/WebSocket, console/errors/screenshots/tours;
  • Form: Python-эмуляция form client, defaults/onchange и x2many proxies.

Tests патчат Registry так, чтобы новые cursors могли оборачивать test cursor, отключают реальное signaling, очищают field/registry caches и вручную garbage-collect attachment filestore.

loader.py импортирует tests/test_*.py addons и upgrade tests, собирает только локально объявленные unittest classes/methods. TagsSelector поддерживает include/exclude по module/class/method/file/parameters. at_install suites запускаются в module load, post_install — после preload/finalization. suite.py — vendored упрощённый unittest suite с Odoo class lifecycle; result.py собирает stats, failures и reports.

21. Сквозные жизненные циклы

21.1 Холодный старт процесса

odoo-bin / python -m odoo
→ import odoo
→ version check, UTC, GC, monkeypatch import hook
→ CLI command discovery
→ config CLI/env/file/default merge
→ logging and server-wide module import
→ server topology selection
→ optional Registry.new() for preload DBs
→ listen HTTP / start workers / start cron

21.2 Первый запрос к базе без registry в процессе

select database
→ Registry(db) miss
→ lock Registry.new
→ module graph from DB state + manifests
→ import definition classes
→ build dynamic per-DB model classes
→ setup fields/dependencies/schema/hooks
→ publish ready registry
→ open cursor + Environment
→ match/auth/dispatch request

21.3 Успешный write request

route match/auth
→ model ACL + record rules + field ACL
→ values in field cache, dirty marks, relational operations
→ modified() builds recompute work
→ flush fixed point
→ constraints/company checks
→ precommit hooks
→ PostgreSQL COMMIT
→ postcommit callbacks
→ publish cache/registry signaling
→ session/response finalization

21.4 Конфликтующая transaction

serialization/deadlock/lock/ConcurrencyError
→ rollback DB
→ reset Transaction and Environments
→ reset request session/file streams
→ exponential jitter
→ rerun complete endpoint (до 5 попыток)

21.5 Изменение структуры модуля

mark to install/upgrade/remove
→ Registry.new(update_module=True)
→ graph + migrations + Python import
→ rebuild model classes/fields
→ schema/data/translation/tests
→ per-module commit
→ registry sequence increment
→ другие процессы замечают signaling
→ rebuild their per-process registry

22. Инварианты, без которых легко неправильно прочитать код

  1. Registry принадлежит базе и процессу, не всему cluster.
  2. Imported model class не равен runtime model class; последний собирается для registry.
  3. Record — singleton recordset, а не отдельный entity object.
  4. Environment неизменяем по identity/context, но разделяет mutable Transaction на cursor.
  5. Flush не commit. Raw SQL после ORM write обязан учитывать pending dirty fields.
  6. Field cache и ormcache — разные кэши с разными сроками жизни и invalidation.
  7. sudo и with_user — не одно и то же; company context — третья ось.
  8. ACL, record rules, field groups и company checks независимы. Успех одной проверки не подразумевает другие.
  9. Compute должен объявить dependencies, включая context, иначе cache/recompute не узнают о причине изменения.
  10. Relation dependency требует before/after state, поэтому modified() вызывается с разными фазами.
  11. Readonly route может быть полностью повторён на primary, если обнаружилась запись.
  12. Request может быть повторён после rollback, поэтому внешний side effect до commit опасен.
  13. Install нескольких модулей не является одной глобальной транзакцией: loader коммитит завершённые modules.
  14. base — часть минимальной рабочей системы, хотя технически расположен как addon.
  15. Controller inheritance, как model inheritance, зависит от установленных addons конкретной БД.
  16. safe_eval — ограниченный evaluator, не security sandbox уровня процесса.
  17. Multi-worker cache coherence асинхронна на request boundaries и опирается на PostgreSQL signaling.
  18. Prefetch — свойство recordset lineage; случайное дробление recordsets меняет performance profile.

23. Где расширять ядро, а где нет

Нормальные extension points:

  • addon manifest и dependency graph;
  • model _inherit/_inherits, fields и decorators;
  • @route controller inheritance;
  • ir.http, ir.rule, ir.cron и другие control-plane models;
  • manifest data/demo/assets и XML inheritance;
  • init/pre/post/uninstall hooks;
  • versioned migration scripts;
  • named method caches и postcommit callbacks;
  • CLI Command из addon CLI package.

Опасные/низкоуровневые extension points:

  • monkeypatches и process-wide post_load;
  • прямое изменение registry classes/MRO после setup;
  • raw SQL без to_flush/flush/invalidation;
  • ручные commits внутри request/model methods;
  • side effects до успешного commit;
  • расширение globals/opcodes safe_eval;
  • полагание на in-process cache в multi-worker topology.

24. Навигационная карта: от симптома к коду

Вопрос Начать с
Почему сервер стартует в таком режиме? cli/server.pytools/config.pyservice/server.py
Почему addon не загружается? modules/module.pymodule_graph.pyloading.py
Откуда взялось/исчезло поле? MetaModelmodel_classes.py → field setup → ir.model.fields
Почему compute не пересчитался? field depends → Registry TriggerTreemodified()flush_all()
Почему запрос дал не те ids? Domain.optimize_full() → record rules → Query → generated SQL
Почему значение старое? transaction field cache vs registry ormcache vs interprocess signaling
Почему write виден в ORM, но не в SQL? dirty cache и необходимость flush
Почему endpoint вызвался дважды? readonly fallback или service.model.retrying()
Почему доступ запрещён? auth → ACL → record rule → field group → company check
Почему другой worker не видит изменение структуры? registry signaling sequence/check boundary
Почему установка частично сохранилась? per-module commits в load_module_graph()
Почему тест не может commit? TransactionCase cursor patches/savepoint cleanup

25. Приложение: назначение каждого файла ядра

Ниже — полный inventory вне odoo/addons/**. Описания намеренно кратки: детали уже сгруппированы по слоям выше.

25.1 Корень пакета

  • __main__.py — entry point python -m odoo.
  • init.py — ранняя инициализация пакета, runtime checks, monkeypatches, публичные constants.
  • release.py — series/version/protocol и поддерживаемые Python/PostgreSQL versions.
  • exceptions.py — общая иерархия пользовательских, access, missing, lock и concurrency ошибок.
  • http.py — WSGI application, controllers/routes, requests, sessions, dispatchers, CSRF/RPC.
  • sql_db.py — PostgreSQL connections/pools/cursors/transactions/savepoints/callbacks.
  • netsvc.py — logging bootstrap, DB/performance log handlers и formatters.
  • loglevels.py — дополнительные уровни logging.
  • import_xml.rng — Relax NG grammar declarative module XML.

25.2 _monkeypatches/

  • _monkeypatches/__init__.py — import hook и orchestration всех patches.
  • _monkeypatches/site.py — UTC/codecs и gevent/psycopg2 evented bootstrap.
  • _monkeypatches/ast.py — совместимость/ограничения Python AST.
  • _monkeypatches/bs4.py — нормализация BeautifulSoup.
  • _monkeypatches/csv.py — CSV dialect/field-size compatibility.
  • _monkeypatches/docutils.py — безопасное/стабильное поведение docutils.
  • _monkeypatches/email.py — исправления Python email parsing/serialization.
  • _monkeypatches/locale.py — предсказуемая locale handling.
  • _monkeypatches/lxml.py — XML/HTML parser behavior и compatibility.
  • _monkeypatches/mimetypes.py — детерминированная MIME database.
  • _monkeypatches/num2words.py — исправления локализованного преобразования чисел.
  • _monkeypatches/pytz.py — timezone compatibility.
  • _monkeypatches/re.py — regular-expression runtime compatibility.
  • _monkeypatches/stdnum.py — исправления python-stdnum.
  • _monkeypatches/urllib3.py — HTTP connection compatibility.
  • _monkeypatches/werkzeug.py — WSGI/request/response/session compatibility.
  • _monkeypatches/xlrd.py — чтение legacy Excel compatibility.
  • _monkeypatches/xlsxwriter.py — генерация XLSX compatibility.
  • _monkeypatches/xlwt.py — генерация XLS compatibility.
  • _monkeypatches/zeep.py — SOAP client compatibility/security fixes.

25.3 Публичные фасады

  • api/__init__.py — re-export Environment, decorators и API helpers.
  • fields/__init__.py — public field classes и Command.
  • models/__init__.py — public Model, AbstractModel, TransientModel и helpers.
  • orm/__init__.py — упорядоченная сборка внутреннего ORM namespace.

25.4 CLI и templates

  • cli/__init__.py — public CLI entry.
  • cli/command.py — command registry, discovery, parsing и dispatcher.
  • cli/server.py — production/default server command.
  • cli/start.py — быстрый project-aware local start.
  • cli/shell.py — interactive shell с DB environment.
  • cli/help.py — command help.
  • cli/db.py — database administration CLI.
  • cli/module.py — module install/upgrade/list operations.
  • cli/i18n.py — translation export/import/load operations.
  • cli/neutralize.py — запуск neutralization SQL.
  • cli/populate.py — synthetic data population.
  • cli/cloc.py — addon source line counting.
  • cli/scaffold.py — template renderer для нового addon.
  • cli/deploy.py — zip и upload addon на server endpoint.
  • cli/obfuscate.py — obfuscation/anonymization DB copy.
  • cli/upgrade_code.py — sequential source rewrite driver.
  • cli/templates/default/__init__.py.template — package init default addon.
  • cli/templates/default/__manifest__.py.template — default addon manifest.
  • cli/templates/default/controllers/__init__.py.template — controller package init.
  • cli/templates/default/controllers/controllers.py.template — пример HTTP controller.
  • cli/templates/default/demo/demo.xml.template — пример demo data.
  • cli/templates/default/models/__init__.py.template — model package init.
  • cli/templates/default/models/models.py.template — пример model definition.
  • cli/templates/default/security/ir.model.access.csv.template — ACL skeleton.
  • cli/templates/default/views/templates.xml.template — QWeb template skeleton.
  • cli/templates/default/views/views.xml.template — view/action/menu skeleton.
  • cli/templates/theme/__init__.py.template — theme package init.
  • cli/templates/theme/__manifest__.py.template — theme manifest.
  • cli/templates/theme/demo/pages.xml.template — demo pages.
  • cli/templates/theme/static/src/scss/custom.scss.template — custom theme SCSS.
  • cli/templates/theme/views/options.xml.template — theme options.
  • cli/templates/theme/views/snippets.xml.template — theme snippets.
  • cli/templates/l10n_payroll/__init__.py.template — payroll localization package init.
  • cli/templates/l10n_payroll/__manifest__.py.template — payroll localization manifest.
  • cli/templates/l10n_payroll/data/hr_payroll_structure_data.xml.template — structures skeleton.
  • cli/templates/l10n_payroll/data/hr_payroll_structure_type_data.xml.template — structure types skeleton.
  • cli/templates/l10n_payroll/data/hr_rule_parameters_data.xml.template — rule parameters skeleton.
  • cli/templates/l10n_payroll/data/hr_salary_rule_category_data.xml.template — salary categories skeleton.
  • cli/templates/l10n_payroll/data/hr_salary_rule_data.xml.template — salary rules skeleton.
  • cli/templates/l10n_payroll/data/l10n_{{code}}_hr_payroll_demo.xml.template — localized demo skeleton.
  • cli/templates/l10n_payroll/models/__init__.py.template — payroll models package init.
  • cli/templates/l10n_payroll/models/hr_payslip.py.template — payslip extension skeleton.
  • cli/templates/l10n_payroll/models/hr_payslip_worked_days.py.template — worked-days extension skeleton.
  • cli/templates/l10n_payroll/models/hr_version.py.template — HR version extension skeleton.
  • cli/templates/l10n_payroll/views/hr_payroll_report.xml.template — payroll report view skeleton.
  • cli/templates/l10n_payroll/views/report_payslip_templates.xml.template — payslip QWeb skeleton.

25.5 Module system

  • modules/__init__.py — module subsystem exports/initialization.
  • modules/module.py — addon namespace, Manifest, paths, dependency checks и Python import.
  • modules/module_graph.py — dependency DAG и phase/order filtering.
  • modules/loading.py — install/load/update/uninstall orchestration.
  • modules/db.py — new DB SQL bootstrap и module metadata initialization.
  • modules/migration.py — discovery/execution versioned migration scripts.
  • modules/neutralize.py — aggregation/execution neutralize.sql.
  • modules/registry/__init__.py — compatibility exports нового ORM Registry.

25.6 ORM

  • orm/models.pyMetaModel, BaseModel, recordsets, CRUD/search/schema и core model API.
  • orm/model_classes.py — dynamic per-registry class construction и setup phases.
  • orm/models_transient.pyAbstractModel, Model, TransientModel specializations и vacuum.
  • orm/registry.py — per-DB registry, setup, caches, signaling и schema coordination.
  • orm/environments.py — Environment, Transaction, field cache, dirty/recompute state.
  • orm/fields.py — base Field descriptor, setup, conversion, compute/inverse/related logic.
  • orm/fields_misc.py — Boolean/Json/Id-like fields.
  • orm/fields_numeric.py — Integer/Float/Monetary.
  • orm/fields_textual.py — Char/Text/Html и translated JSONB values.
  • orm/fields_temporal.py — Date/Datetime.
  • orm/fields_binary.py — Binary/Image и attachment/image interaction.
  • orm/fields_selection.py — Selection.
  • orm/fields_reference.py — Reference и Many2oneReference.
  • orm/fields_relational.py — Many2one/One2many/Many2many.
  • orm/fields_properties.py — dynamic JSONB Properties/definitions.
  • orm/domains.py — Domain AST, optimization и SQL conversion.
  • orm/commands.py — typed x2many command protocol.
  • orm/decorators.py — API/model metadata decorators.
  • orm/table_objects.py — Constraint/Index/UniqueIndex descriptors.
  • orm/identifiers.pyNewId и ID typing.
  • orm/types.py — shared ORM type aliases.
  • orm/utils.py — name validation, field expression и origin/id helpers.

25.7 Legacy OSV

  • osv/__init__.py — legacy namespace exports.
  • osv/expression.py — compatibility API поверх domain/expression machinery.

25.8 Services

  • service/__init__.py — service package marker/exports.
  • service/server.py — threaded, gevent, prefork servers, workers, cron и lifecycle.
  • service/model.py — model RPC dispatch, public method checks и transaction retrying.
  • service/common.py — version/auth legacy common service.
  • service/db.py — database/filestore administration service.
  • service/security.py — session token и device/audit helpers.

25.9 Tests

  • tests/__init__.py — test API exports и tagging helpers.
  • tests/case.py — vendored/custom unittest case primitives.
  • tests/common.py — DB/HTTP/Chrome test cases и общие assertions/helpers.
  • tests/form.py — Form/onchange client emulator и x2many proxies.
  • tests/loader.py — addon/upgrade test discovery и suite construction.
  • tests/suite.py — Odoo-aware vendored TestSuite lifecycle.
  • tests/tag_selector.py — include/exclude tag parser/matcher.
  • tests/result.py — test result, stats, logging и report aggregation.
  • tests/shell.py — запуск install-position suites из shell.
  • tests/test_cursor.py — cursor wrapper для test transaction reuse.
  • tests/test_module_operations.py — kernel tests module install/upgrade/uninstall semantics.
  • tests/dummy.js — fixture определения JS file/module.
  • tests/dummy.xml — fixture XML data/template parsing.

25.10 Tools: web/vendor и общие primitives

  • tools/__init__.py — curated public utility facade.
  • tools/_vendor/__init__.py — vendored namespace.
  • tools/_vendor/send_file.py — pinned HTTP file response implementation.
  • tools/_vendor/sessions.py — pinned filesystem session store primitives.
  • tools/_vendor/useragents.py — user-agent parsing.
  • tools/appdirs.py — cross-platform data/config/cache/log directories.
  • tools/constants.py — shared constants, regexes и sentinels.
  • tools/urls.py — safe normalized URL joining/dot-segment handling.
  • tools/json.py — Odoo JSON codec/defaults и script-safe output.
  • tools/pycompat.py — small Python/CSV/text compatibility layer.
  • tools/osutil.py — filename sanitization и deterministic ZIP creation.
  • tools/which.py — external executable discovery.
  • tools/facade.py — lazy proxy attributes/functions для compatibility APIs.
  • tools/func.py — descriptors, lazy values, locks, cached-property reset.
  • tools/gc.py — GC timing/info и temporary disabling context.
  • tools/lru.py — LRU mapping implementation.
  • tools/cache.pyormcache decorators, counters и diagnostics.
  • tools/set_expression.py — lazy unions/intersections/leaves ID sets.
  • tools/intervals.py — interval algebra.
  • tools/parse_version.py — semantic-ish Odoo version parsing/comparison.
  • tools/date_utils.py — date range, period, arithmetic и formatting helpers.
  • tools/float_utils.py — precision-aware float compare/round/split.
  • tools/misc.py — broad shared filesystem, formatting, batching, locale и collection helpers.

25.11 Tools: DB/data/code

  • tools/sql.py — composable SQL и schema helpers.
  • tools/query.py — lazy SELECT/join/where/order query representation.
  • tools/convert.py — XML/CSV/SQL module data import.
  • tools/cloc.py — addon code line analysis.
  • tools/populate.py — high-volume synthetic model population.
  • tools/safe_eval.py — restricted Python expression evaluator.
  • tools/js_transpiler.py — JS ES-module to Odoo-module transformation.
  • tools/sourcemap_generator.py — source-map encoding/generation.

25.12 Tools: i18n/XML/views

  • tools/i18n.py — locale formatting и Python↔JS locale conversion.
  • tools/translate.py — extraction, PO/CSV I/O, DB translations, gettext/lazy translate.
  • tools/babel/__init__.py — Babel integration namespace.
  • tools/babel/javascript_extractor.py — translatable terms из JavaScript.
  • tools/babel/python_extractor.py — translatable terms из Python.
  • tools/arabic_reshaper/__init__.py — public Arabic shaping API.
  • tools/arabic_reshaper/letters.py — Arabic forms/ligature data.
  • tools/xml_utils.py — XML parsing, cleanup и schema validation.
  • tools/template_inheritance.py — XML inheritance spec application.
  • tools/view_validation.py — view schemas и semantic expression validation.
  • tools/rendering_tools.py — inline templates и QWeb-compatible rendering helpers.

25.13 Tools: mail/media/PDF/reports

  • tools/mail.py — email addresses, headers, bodies и message utilities.
  • tools/mimetypes.py — content-based MIME detection и filename safety.
  • tools/image.py — image transforms, validation и color management.
  • tools/barcode.py — barcode rendering/checking.
  • tools/pdf/__init__.py — version-independent PDF facade.
  • tools/pdf/_pypdf.py — adapter современной pypdf.
  • tools/pdf/_pypdf2_1.py — adapter старого PyPDF2 API line.
  • tools/pdf/_pypdf2_2.py — adapter нового PyPDF2 API line.
  • tools/pdf/signature.py — PDF digital signing.
  • tools/rendering_tools.py — также inline report rendering helpers.
  • tools/test_reports.py — test helpers report actions/output.
  • tools/data/files/sRGB2014.icc — ICC color profile для image/PDF processing.
  • tools/data/files/sRGB2014.icc.LICENSE — лицензия встроенного ICC profile.

25.14 Tools: profiling и SOAP

  • tools/profiler.py — SQL/periodic/sync/QWeb/memory collectors и profiler orchestration.
  • tools/speedscope.py — Speedscope profile representation/export.
  • tools/zeep/__init__.py — SOAP wrapper exports.
  • tools/zeep/client.py — patched Zeep client behavior.
  • tools/zeep/exceptions.py — SOAP wrapper exceptions.
  • tools/zeep/helpers.py — SOAP value serialization helpers.
  • tools/zeep/ns.py — namespaces/constants.
  • tools/zeep/wsa.py — WS-Addressing helpers.
  • tools/zeep/wsdl/__init__.py — WSDL wrapper namespace.
  • tools/zeep/wsdl/utils.py — WSDL parsing utilities.
  • tools/zeep/wsse/__init__.py — WS-Security namespace.
  • tools/zeep/wsse/username/__init__.py — UsernameToken integration.

25.15 Upgrade namespaces

  • upgrade/.gitkeep — резервирует namespace odoo.upgrade для внешних upgrade packages.
  • upgrade_code/17.5-00-example.py — example source-upgrade script/framework contract.
  • upgrade_code/17.5-01-tree-to-list.py — migration старого tree view terminology к list.
  • upgrade_code/18.1-00-sql-constraint.py — преобразование SQL constraint declarations.
  • upgrade_code/18.1-02-route-jsonrpc.py — обновление JSON-RPC route declarations.
  • upgrade_code/18.2-00-l10n-translate.py — обновление localization translation patterns.
  • upgrade_code/18.3-00-l10n-fiscal-position-taxes.py — обновление fiscal-position tax code patterns.
  • upgrade_code/18.5-00-deprecated-properties.py — замена deprecated model/field properties.
  • upgrade_code/18.5-00-domain-dynamic-dates.py — перевод dynamic-date domain syntax.
  • upgrade_code/18.5-00-no-tax-tag-invert.py — удаление устаревшего tax-tag inversion pattern.

26. Итог

Минимальное точное описание Odoo звучит так:

Odoo — PostgreSQL-центричный, модульно компилируемый application runtime. Manifests и Python definition classes превращаются в отдельный Registry динамических моделей для каждой базы; recordsets исполняются внутри Environment/Transaction с descriptor fields, prefetch, cache и dependency recompute; HTTP/RPC/cron процессы оборачивают этот runtime аутентификацией, policy из base, retry/commit protocol и межпроцессным signaling.

Если держать в голове четыре объекта — Registry, Model class, Environment/Transaction и Request — большая часть остальных файлов раскладывается вокруг них: module loader строит Registry; ORM наполняет поведение Model class; cursor/cache обеспечивают Transaction; HTTP/server создают и завершают Request.

27. Дополнение после анализа addons/web

Подробный документ: /private/tmp/odoo-19-web-core-architecture-ru.md.

27.1. Исправление границы ядра

Фраза выше «web-клиент исключён» описывала исходный scope, но не архитектурную классификацию. /Users/max/Dev/odoo_19/addons/web действительно находится в соседнем addon namespace, однако функционально является третьей опорной частью ядра:

техническое ядро Odoo
  = Python runtime (`odoo/`)
  + системная метамодель (`odoo/addons/base`)
  + клиентский/представительный runtime (`addons/web`)

web зависит только от base, устанавливается автоматически и поставляет общий Web Client. Это инфраструктурный addon, а не бизнес-модуль.

27.2. Что добавилось к карте слоёв

Аудит 2 319 файлов web добавил следующие логические уровни:

  1. asset graph и собственный JavaScript module loader;
  2. HTML/session bootstrap и запуск Owl environment;
  3. generic JSON-RPC/ORM transport;
  4. серверный Web ORM protocol: web_read, web_search_read, web_save, web_read_group, search-panel и generic onchange;
  5. browser registries, dependency-started services, patch() и template inheritance;
  6. action stack, menus, router, breadcrumbs и deep links;
  7. загрузка/компиляция XML views;
  8. client relational draft model с x2many commands;
  9. SearchModel, filters, group-by, favorites и search panels;
  10. form/list/kanban/calendar/graph/pivot и field/widget registry;
  11. binary/export/report protocols;
  12. public interaction и PWA shell;
  13. design system, localization, responsive/RTL/dark/print;
  14. Hoot/QUnit/tours/mock-server test runtime.

27.3. Главная поправка к ментальной модели

В полной Odoo работают две динамические композиционные машины. Серверный Registry собирает итоговые Python-модели из вкладов addons. Web runtime собирает итоговое клиентское приложение из asset contributions, JS registries, services, Owl templates, patches и разрешённых сервером XML views.

UI-семантика поэтому распределена между сервером и браузером. XML view сам по себе недостаточен: его исполняют Web ORM projection/onchange protocol, action/view/search engines, field registry и client Record/StaticList. Это принципиально для любого анализа совместимости или попытки переписать Odoo.

27.4. Числа нельзя складывать механически

Исходные 207 файлов и 63 542 строки относятся только к Python core вне addons. web добавляет 2 319 tracked-файлов, но в них находятся крупные vendored libraries, fonts, browser maps, переводы, данные emoji и около 225 тыс. строк frontend tests. Содержательное production ядро web — примерно 6,5 тыс. строк Python backend плюс около 117 тыс. строк JS/TS, 10,6 тыс. строк XML templates и 16,8 тыс. строк SCSS/CSS. Эти величины нужны для масштаба, а не для оценки качества или прямого суммирования.

27.5. Обновлённый итог

Полное ядро Odoo — это не только компилятор моделей и транзакционный сервер. Это также универсальная виртуальная машина интерфейсов: сервер публикует metadata-oriented protocol, а браузер строит из него action-driven приложение и поддерживает до сохранения виртуальный граф записей. Бизнес-addons расширяют обе стороны одновременно.

addons/web в Odoo 19: клиентская и транспортная половина ядра

Срез исходников: Odoo 19.0, commit 7d3a7fac27332f440404383f41cc113bf3face53 от 14 марта 2026 года.
Исследованный каталог: /Users/max/Dev/odoo_19/addons/web.
Это дополнение к отчёту /private/tmp/odoo-19-core-architecture-ru.md и к сравнению /private/tmp/odoo-angee-arpee-comparison-ru.md.

1. Короткий вывод

addons/web — addon только по механизму упаковки. Архитектурно это обязательная клиентская, представительная и значительная часть транспортной половины платформы Odoo.

Это видно непосредственно из manifest:

  • модуль зависит только от base;
  • помечен auto_install=True;
  • имеет скрытую категорию;
  • сам описан как ядро Web Client;
  • определяет базовые asset bundles, без которых обычный внутренний интерфейс Odoo не существует.

Без web серверное ядро всё ещё умеет строить Registry, выполнять ORM, обслуживать HTTP и загружать addons. Но пропадает универсальная прикладная оболочка:

  • web-сессия и bootstrap браузера;
  • generic RPC-доступ к моделям;
  • клиентское выполнение actions;
  • загрузка и компиляция XML views;
  • form/list/kanban/calendar/graph/pivot;
  • search model, filters, group-by, favorites и search panels;
  • widgets полей;
  • клиентская модель редактируемой записи и onchange;
  • меню, breadcrumbs, router и deep links;
  • exports, reports, binary delivery;
  • frontend registries, services, patches и template inheritance;
  • PWA shell, public interactions и основная web test infrastructure.

Поэтому полная формула технического ядра выглядит так:

Odoo core = Python runtime (`odoo/`)
          + системная метамодель (`odoo/addons/base`)
          + web application runtime (`addons/web`)

Бизнес-модули не просто рисуют страницы поверх этого runtime. Они декларативно и программно встраиваются во все три части: расширяют серверные модели, метаданные base и клиентские registries/assets/templates web.

2. Охват и метод анализа

В каталоге обнаружено 2 319 отслеживаемых файлов. Все файлы были включены в структурную инвентаризацию; исполняемый Python/JavaScript/TypeScript, manifests, XML templates/views, SCSS и тестовая инфраструктура были разобраны по определениям, регистрациям, маршрутам и ключевым потокам выполнения. Изображения, шрифты, browser maps и vendored libraries учитывались как состав поставки, но не пересказывались побайтно.

2.1. Физический состав

Область Наблюдаемый объём Роль
controllers/ около 3,1 тыс. строк Python HTTP/JSON-RPC gateway, сессии, actions, reports, export, binary, PWA
models/ около 3,4 тыс. строк Python Web ORM protocol и расширения системной метамодели
backend tests около 3,5 тыс. строк Python контракты маршрутов, web-read/search, assets, menus, reports
static/src/ 1 010 файлов production frontend runtime
JavaScript/TypeScript в static/src/ около 116,8 тыс. строк Owl UI, services, views, record model, networking
XML templates в static/src/ около 10,6 тыс. строк Owl/QWeb component templates и inheritance
SCSS/CSS в static/src/ около 16,8 тыс. строк design system, Bootstrap adaptation, print/dark/RTL
static/tests/ около 225 тыс. строк Hoot/QUnit, mock server, tours, helpers, contracts
static/lib/ около 285 тыс. строк vendored third-party runtime и test libraries

Некоторые механические данные и библиотеки сильно искажают сырые числа: например, emoji data занимает десятки тысяч строк. Поэтому LOC здесь — только контроль охвата, а не оценка архитектурной значимости.

2.2. Основные frontend-зоны

Каталог static/src Примерный размер/состав Что в нём живёт
core/ 323 файла registries, services, RPC, browser abstractions, dialogs, UI primitives, expression/domain runtime
views/ 426 файлов view engine, form/list/kanban/calendar/graph/pivot, поля и widgets
webclient/ 122 файла root application, actions, menus, router, navbar, session, debug, PWA integration
search/ 46 файлов SearchModel, facets, filters, group-by, favorites, search panel, control panel
model/ 14 файлов generic client model hooks и data-point lifecycle
public/ 15 файлов interaction runtime для публичных страниц
legacy/ 7 файлов совместимость прежнего public widget/runtime
@types/ 17 файлов типизированные контракты registries и extension points

В production XML найдено около 378 собственных t-name templates и 26 template inheritances. В source зарегистрировано около 270 классов Owl Component. Это не «тонкая тема оформления», а самостоятельный application runtime.

3. Правильная ментальная модель

Odoo содержит две взаимосвязанные динамические машины композиции.

flowchart LR
    A["Addon manifests + Python imports"] --> SR["Server Registry на базу"]
    SR --> ORM["Models, fields, methods, ACL, rules"]
    ORM --> WP["Web protocol: actions, views, web_read/save, onchange"]
    WP <--> RPC["HTTP / JSON-RPC / binary"]
    RPC <--> CS["Client services"]
    CS --> AR["Action + router stack"]
    CS --> VE["View/search/field engines"]
    VE --> CR["Client relational model"]
    B["Asset contributions"] --> ML["JS module loader"]
    ML --> FR["Frontend registries"]
    T["Owl/QWeb templates + inheritance"] --> FR
    P["JS patch() contributions"] --> FR
    FR --> CS
    FR --> VE
Loading

Первая машина — серверный Registry: она собирает динамические Python-модели из установленного addon-графа для конкретной базы.

Вторая — web runtime:

  • asset graph собирает общий исполняемый клиент из вкладов addons;
  • JS module loader разрешает модули;
  • ordered registries собирают services, views, fields, actions, widgets и другие расширения;
  • patch() изменяет существующие классы и объекты;
  • Owl/QWeb template inheritance изменяет разметку компонентов;
  • XML view inheritance, уже разрешённая сервером, формирует прикладной UI;
  • action/view/search/record engines выполняют эту декларативную модель в браузере.

Полезно воспринимать web не как frontend отдельного ERP-продукта, а как универсальную виртуальную машину интерфейсов Odoo.

4. Где проходит граница между odoo/, base и web

Ответственность Владелец Что добавляет web
WSGI/HTTP request, routing, transaction/retry odoo/http.py, services конкретные web-маршруты, payloads, session bootstrap
Registry, Environment, recordsets, fields odoo/orm оптимизированные web_* методы и generic onchange protocol
ir.model, ir.ui.view, ir.actions.*, menus, users base web-oriented projections, menu tree, view metadata, action loading
access rights, record rules, companies core + base перенос контекста и состояния в браузер; проверяемые вызовы через ORM
XML view inheritance base клиентская компиляция уже собранной arch в Owl components
asset compiler/storage core + base metadata базовые bundles, module boot, lazy loading, browser cache
бизнес-модели и workflows бизнес-addons общие renderers/controllers/widgets для их метаданных

web не владеет бизнес-объектами. Его собственные model extensions в основном являются адаптерами протокола или опорными настройками интерфейса. Поэтому глубоко описывать res.partner здесь не требуется: web расширяет его, например, для vCard, но не определяет бизнес-семантику партнёра.

5. Логические слои addons/web

Ниже — карта слоёв. Номер — порядок понимания, а не жёсткая compile-time зависимость.

Слой Ключевые области Главная гарантия
W0 Addon bootstrap __manifest__.py, __init__.py автоматическое включение web-ядра поверх base
W1 Asset/module graph manifest assets, module_loader.js, assets.js детерминированная композиция и lazy delivery клиентского кода
W2 Web entry/session bootstrap controllers/home.py, webclient.py, webclient_templates.xml, start.js безопасная начальная HTML-страница и запуск Owl environment
W3 Transport adapters controllers/dataset.py, core/network/*, orm_service.js единый канал браузер ↔ model methods
W4 Web ORM protocol models/models.py универсальные read/search/save/group/onchange/search-panel операции
W5 Client extension runtime registry.js, patch.js, template inheritance, services композиция функциональности всех установленных addons в браузере
W6 Action/navigation runtime action_service.js, router, menu service открытие actions, controller stack, breadcrumbs, URL/deep links
W7 View compilation views/view.js, compiler, parsers превращение XML arch + field metadata в исполняемый Owl UI
W8 Client relational model model/, model/relational_model/ черновики записей, dirty state, virtual x2many, onchange/save
W9 Search/query UX search/ составление domain/context/groupby/order и пользовательских facets
W10 Generic views form/list/kanban/calendar/graph/pivot общие controllers/renderers/models для бизнес-моделей
W11 Fields/widgets views/fields/, view_widgets/ типизированный рендеринг и редактирование значений
W12 Shell services/UI core/, webclient/ dialogs, notifications, hotkeys, commands, overlays, uploads, localization
W13 Documents/data egress binary/export/report/pivot controllers файлы, изображения, CSV/XLSX, PDF/HTML/text reports
W14 Public/PWA runtime public/, service worker, webmanifest публичные interactions, installable shell и ограниченный offline fallback
W15 Design/accessibility/i18n SCSS, templates, translations единый адаптивный интерфейс, RTL/dark/print и локализация
W16 Test/runtime tooling Hoot, QUnit, tours, mock server, profiling проверяемый контракт web-платформы и addon extensions

6. W0–W1: упаковка, assets и module runtime

6.1. Manifest — это часть программной композиции

__manifest__.py задаёт не только список статических файлов. Asset declarations образуют DSL композиции:

  • glob-включения source;
  • включение другого bundle;
  • remove;
  • позиционные before и after;
  • отдельные production, lazy, dark, print, report и test bundles.

Основные bundles:

  • web.assets_backend — внутреннее приложение;
  • lazy graph/pivot assets — тяжёлые аналитические представления;
  • web.assets_web — общая web-основа;
  • web.assets_frontend_minimal и frontend/lazy — публичный runtime;
  • web.report_assets_common и web.report_assets_pdf — отчёты;
  • web.assets_backend_dark, print/dark variants;
  • внутренние helpers/core/bootstrap bundles;
  • Hoot, legacy QUnit, tour и clickbot bundles;
  • отдельные bundles для Ace, Chart.js и FullCalendar.

Business addons дополняют эти же graphs. Порядок критичен: переменные темы должны появиться до Bootstrap-компиляции, overrides — после исходного кода, а remove/replace способны изменить поведение базового web.

6.2. Собственный JavaScript module loader

static/src/module_loader.js реализует лёгкий runtime именованных модулей:

  • регистрация module factory;
  • учёт зависимостей;
  • очередь готовых jobs;
  • обнаружение отсутствующих зависимостей;
  • диагностика circular/failed modules;
  • debug banner для ошибок загрузки.

Это не полноценный npm runtime в браузере. Серверный asset transformer преобразует модули Odoo в формат loader, затем bundle исполняется как одна согласованная программа из core и addons.

6.3. Lazy loading

static/src/core/assets.js:

  • дедуплицирует параллельные загрузки;
  • загружает bundle, JS и CSS;
  • кэширует обещания загрузки;
  • обрабатывает ошибки и допускает retry.

/web/bundle/<bundle_name> возвращает описание фактических URL с versioned assets. Graph/pivot и некоторые большие libraries можно не включать в начальный backend bundle.

6.4. Следствие для архитектуры

Asset order — часть API addon. Перенести только Python и XML views недостаточно: addon может менять базовый UI через manifest assets, JS registry contribution, patch и inherited template. Любой Odoo→Angee compiler должен сначала построить AssetBundleIR, а затем классифицировать каждый frontend-вклад.

7. W2: web entry и bootstrap браузера

7.1. Серверный вход

controllers/home.py обслуживает /, /web, /odoo/... и scoped app routes. Маршрут web client имеет auth="none", потому что до выбора базы обычная user authentication ещё невозможна. Это не означает открытый backend: controller вручную устанавливает/проверяет DB/session и допускает внутреннего пользователя по соответствующему состоянию.

HTML-ответ получает:

  • Cache-Control: no-store для страницы с session-specific bootstrap;
  • защиту от встраивания через X-Frame-Options;
  • выбранные assets;
  • начальную информацию сессии;
  • browser-cache secret и registry version/hash.

Меню начинает загружаться параллельно с assets, чтобы сократить критический путь.

7.2. Начальное состояние

models/ir_http.py формирует session_info и frontend_session_info. В браузер передаются только данные, нужные runtime, в том числе:

  • uid и режим пользователя;
  • allowed companies и иерархия компаний;
  • пользовательский context и язык;
  • currencies и localization parameters;
  • server/version information;
  • ограничения загрузки файлов;
  • debug/test flags;
  • registry hash и cache secret;
  • доступные view types и client capabilities.

static/src/session.js забирает inline odoo.__session_info__; bootstrap не обязан делать дополнительный blocking RPC только для базовой сессии.

7.3. Запуск клиента

Поток main.jsstartWebClient()start.js:

  1. нормализует глобальный объект odoo.info;
  2. создаёт RPC cache, привязанный к версии Registry и browser secret;
  3. ждёт готовности DOM;
  4. запускает service graph;
  5. монтирует корневой Owl component;
  6. задаёт body classes для RTL, superuser/debug, touch и других режимов;
  7. выставляет odoo.isReady.

Корневой WebClient собирает NavBar, ActionContainer и MainComponentsContainer, синхронизирует menu/action/router/title, регистрирует глобальные события, debug systray и service worker.

sequenceDiagram
    participant B as Browser
    participant H as Home controller
    participant I as ir.http/session_info
    participant A as Asset server
    participant S as Service graph
    participant W as WebClient
    B->>H: GET /odoo/...
    H->>I: resolve DB, session and bootstrap info
    H-->>B: no-store HTML + inline session + asset URLs
    par preload menus
        B->>H: GET /web/webclient/load_menus
    and load code
        B->>A: versioned JS/CSS bundles
    end
    B->>S: start services by dependencies
    S-->>W: OdooEnv
    W->>W: restore router/action/menu state
    W-->>B: interactive client
Loading

8. W3: HTTP, JSON-RPC и ORM gateway

8.1. Generic model gateway

controllers/dataset.py содержит два центральных маршрута:

  • /web/dataset/call_kw — вызов опубликованного model method;
  • /web/dataset/call_button — button method с нормализацией action result.

Это не набор CRUD endpoints. Клиент передаёт model, method, positional args и kwargs, после чего сервер вызывает метод через ORM. Доступность определяется правилами published/private methods ядра; бизнес-авторизация остаётся в ACL, record rules и самом model method.

Controller вычисляет, можно ли использовать readonly cursor, по metadata вызываемого метода по всей MRO. Поэтому «RPC» и «транзакция записи» не тождественны: read-only web calls могут быть направлены в readonly execution path.

8.2. Browser RPC

core/network/rpc.js:

  • формирует JSON-RPC 2.0 envelope;
  • присваивает request id;
  • публикует события request/response через rpcBus;
  • различает server error, connection lost, aborted request и payload-too-large;
  • поддерживает silent calls, headers и test-injected XHR;
  • умеет направлять cacheable вызовы через RPCCache.

orm_service.js строит поверх generic RPC удобный API: call, search, read, searchRead, webSearchRead, create, write, unlink, readGroup, webReadGroup, onchange, webSave и relation command helpers. Это клиентский transport facade, а не независимая ORM.

8.3. Cache protocol

rpc_cache.js реализует:

  • RAM и IndexedDB tiers;
  • дедупликацию одинаковых одновременных запросов;
  • stale-while-refresh-подобное поведение для update mode;
  • deep-copy выдаваемого результата, чтобы потребитель не мутировал cache;
  • table-level и full invalidation;
  • запрет записывать результат запроса, инвалидированного во время исполнения;
  • ограничение disk usage примерно 2 GiB;
  • AES-GCM encryption значений в IndexedDB с session/browser secret;
  • versioning базы cache по Registry hash и алгоритму шифрования.

Шифрование здесь прежде всего не превращает IndexedDB в доверенное хранилище; оно не позволяет читать старые cache entries при смене ключа/сессии. Авторитетным источником всё равно остаётся сервер.

8.4. Необычный /json/1/...

controllers/json.py — отдельный экспериментальный readonly bearer endpoint, который представляет action/view/data как HTTP JSON. Он включён только специальной настройкой или demo-режимом и дополнительно требует export capability. Controller:

  • разрешает action или path;
  • выбирает view/spec;
  • выполняет web_read, web_search_read или read_group;
  • обрабатывает group/date parameters;
  • возвращает canonical redirects.

Это не основа backend клиента и не доказательство готовой REST API совместимости: обычный web client использует JSON-RPC.

9. W4: Web ORM protocol на сервере

Главный файл — models/models.py, расширяющий абстрактную base model. Он превращает низкоуровневые ORM операции в стабильный, UI-oriented протокол.

9.1. Основные методы

Метод Назначение
web_name_search автодополнение имён с web-compatible result
web_search_read search + projection + count/order/limit для списков
web_read чтение по вложенной field specification
web_save, web_save_multi create/write и возврат нужной клиенту projection
web_resequence массовое изменение sequence с корректным шагом
web_read_group группировка, formatting, group expansion и temporal fill
progress-bar helper агрегаты для kanban/list progress UI
search-panel methods ranges, multi-ranges, counters, hierarchy
onchange вычисление изменений виртуальной записи
web_override_translations web-oriented translation overrides

9.2. Field specification

web_read не ограничен плоским списком колонок. Клиент передаёт specification, где relation field может содержать собственные fields/specification. Сервер сериализует:

  • scalar values;
  • display names many2one/reference;
  • x2many records по вложенной спецификации;
  • properties и связанные metadata;
  • значения, необходимые конкретным widgets и subviews.

Это позволяет View/RelationalModel заказывать ровно ту форму графа данных, которую требует текущая arch. Следовательно, интерфейс Odoo — не generic CRUD поверх плоской таблицы.

9.3. web_read_group

Web grouping включает больше семантики, чем SQL GROUP BY:

  • агрегаты и count;
  • иерархическое раскрытие;
  • group expansion;
  • заполнение отсутствующих temporal buckets;
  • форматирование date/datetime intervals;
  • construction domain для drill-down;
  • ordering/limit и lazy grouping;
  • grouping sets в поддерживаемых сценариях.

Это общий аналитический engine для list/kanban/graph/pivot.

9.4. Search panel protocol

Search panels требуют специальных backend операций, потому что простое выполнение основного domain не даёт правильных counters и доступных значений. Protocol умеет:

  • category ranges;
  • multi-select ranges;
  • hierarchy parent/child;
  • counters с учётом текущего filter domain;
  • group-specific availability.

10. Generic onchange и RecordSnapshot

Это один из самых важных и сложных контрактов web.

10.1. Зачем он существует

Форма редактирует запись до сохранения. Изменение одного поля может:

  • вычислить другие поля;
  • изменить domain relation field;
  • вернуть warning;
  • создать или изменить вложенные x2many lines;
  • запустить несколько @api.onchange по dependency graph;
  • зависеть от context и текущих несохранённых значений.

Обычного PATCH persisted row недостаточно. Сервер должен временно выполнить модель на виртуальной записи.

10.2. Серверный алгоритм

Упрощённо generic onchange:

  1. проверяет права и допустимые fields;
  2. для новой записи объединяет default_get и клиентские значения;
  3. создаёт record через .new(), без SQL INSERT;
  4. подготавливает/prefetch-ит relation values;
  5. строит snapshot «до»;
  6. помечает изменившиеся зависимости и запускает recomputation/onchange methods;
  7. строит snapshot «после»;
  8. вычисляет минимальный diff;
  9. сериализует scalar и nested relation changes;
  10. возвращает value, warnings и дополнительные protocol data.

10.3. RecordSnapshot

Snapshot хранит web-visible состояние записи и рекурсивно сравнивает x2many children. Diff выражается той же семантикой relation commands:

  • CREATE;
  • UPDATE;
  • DELETE;
  • UNLINK;
  • LINK;
  • CLEAR/SET там, где это применимо.

Клиент получает не всю запись «как есть», а корректный набор изменений, который можно слить с локальным draft, не уничтожив пользовательское состояние.

10.4. Почему это существенно для переноса

В Odoo onchange — не validation и не persisted workflow. Это speculative server execution над виртуальным графом объектов. Чтобы переносить формы автоматически, Angee нужен либо эквивалентный generic draft/prefill protocol, либо компилятор каждого @api.onchange в декларативные client/server rules. Второй путь не покрывает произвольный Python; практически нужен общий server-side draft engine плюс typed result.

sequenceDiagram
    participant U as User
    participant R as Client Record
    participant O as ORM service
    participant V as Server virtual record
    U->>R: change field / x2many command
    R->>R: update local dirty draft
    R->>O: onchange(model, values, changed fields, spec)
    O->>V: new() + defaults + snapshot before
    V->>V: recompute dependencies and @api.onchange
    V->>V: snapshot after + relation diff
    V-->>R: values + warnings + nested commands
    R->>R: merge without persisting
    U->>R: Save
    R->>O: web_save(values, specification)
    O-->>R: authoritative saved projection
Loading

11. W5: frontend registries и extension runtime

11.1. Ordered registry

core/registry.js — главный frontend extension bus. Registry:

  • хранит записи с sequence;
  • выдаёт детерминированно отсортированный список;
  • генерирует UPDATE event;
  • поддерживает вложенные named categories;
  • валидирует entries в debug mode;
  • fail-fast отклоняет accidental duplicate keys, если add не помечен force.

Используются десятки категорий. Наиболее важные:

  • services;
  • views;
  • fields;
  • view_widgets;
  • actions;
  • main_components;
  • systray;
  • user_menuitems;
  • command_categories, command_providers;
  • error_handlers и error dialogs;
  • effects;
  • formatters, parsers;
  • public.interactions;
  • debug items и client preferences.

Файлы @types/* описывают shape многих extension points. JavaScript остаётся динамическим, но контракт намеренно типизируется и проверяется.

11.2. Service graph

env.js собирает services из registry. Service declaration содержит dependencies и start(env, deps). Runtime:

  • проверяет неизвестные и циклические зависимости;
  • запускает независимые services параллельно;
  • предоставляет результаты через env.services;
  • оборачивает объявленные async methods, чтобы вызовы уничтоженного component не продолжали небезопасно менять UI;
  • умеет стартовать service, добавленный в registry после первоначального boot.

В web зарегистрировано около 36 базовых services. Группы:

  • data/transport: orm, http, view, field, lazy_session;
  • application: action, menu, title, currency, localization;
  • interaction: dialog, notification, effect, overlay, popover, bottom_sheet, tooltip;
  • input/navigation: hotkey, command, sortable/tree processing;
  • platform: ui, file upload, datetime picker, PWA, share target;
  • diagnostics: error, SCSS error display, profiling;
  • settings/demo/user-invite helpers.

11.3. patch()

core/utils/patch.js изменяет объект или prototype и поддерживает super через цепочку property descriptors. Возвращается unpatch function, что важно для тестовой изоляции.

В самом web patches немногочисленны, но это один из основных API зависимых addons. Его семантика ближе к imperative monkey patch, чем к серверному _inherit: порядок загрузки и вызов super становятся частью поведения.

11.4. Template inheritance

Owl/QWeb templates могут расширяться XPath-подобными операциями:

  • replace;
  • attributes;
  • inside;
  • before;
  • after.

template_inheritance.js применяет modifications и в debug добавляет provenance comments. Это второй вид XML inheritance, не совпадающий с model view inheritance:

  • server view inheritance меняет ir.ui.view arch бизнес-интерфейса;
  • frontend template inheritance меняет внутреннюю разметку Owl components.

11.5. Практический вывод

Публичная surface area Odoo-addon состоит как минимум из четырёх механизмов:

  1. Python model/controller inheritance;
  2. XML records и server view inheritance;
  3. asset graph + frontend registries;
  4. JS patches + component template inheritance.

Переносчик, понимающий только первые два, систематически теряет клиентскую семантику.

12. W6: actions, menus, router и application stack

12.1. Action service — клиентское ядро навигации

webclient/actions/action_service.js имеет около 1 882 строк и управляет:

  • ir.actions.act_window;
  • client actions;
  • reports;
  • server actions;
  • URL actions;
  • close actions;
  • controller stack;
  • breadcrumbs;
  • dialog targets;
  • switching view modes;
  • browser history и deep links;
  • export/import/restoration action state;
  • защитой от ухода с dirty/uncommitted screen.

Публичный API включает doAction, doActionButton, switchView, restore, loadState, loadAction, а также current action/controller state.

Action — это не URL страницы. Это executable descriptor, который может разрешиться в другой action, открыть modal, запустить server method, скачать report, изменить breadcrumb stack или закрыть текущий controller.

12.2. Сервер actions

controllers/action.py:

  • загружает action по database id, XML id или path;
  • очищает и нормализует result для клиента;
  • выполняет server actions;
  • восстанавливает breadcrumb action descriptors;
  • проверяет доступ к связанным models/records.

12.3. Router

Router кодирует application state в path/query/hash, поддерживает legacy формы URL, push/replace/sync и скрытые state keys. Связка router/action обеспечивает:

  • bookmarkable views/records;
  • back/forward;
  • восстановление после reload;
  • соответствие menu/action/current controller;
  • controlled propagation state без полного reload.

12.4. Menus

ir.ui.menu.load_web_menus строит не просто список строк, а клиентский graph:

  • root applications;
  • children и hierarchy;
  • action references;
  • icons/background data;
  • fallback semantics для app action.

Menu service использует prefetched tree и делегирует запуск action service.

13. W7: загрузка и компиляция views

13.1. Полный pipeline

flowchart TD
    A["Window action"] --> VS["view service"]
    VS --> GV["ORM get_views"]
    GV --> SV["Server resolves inherited ir.ui.view arch"]
    SV --> M["arch + fields + related models + toolbar + filters"]
    M --> VX["View component parses XML"]
    VX --> VR["registry: js_class or view type"]
    VR --> AP["View-specific ArchParser"]
    AP --> VI["archInfo"]
    VI --> VC["ViewCompiler"]
    VC --> OT["Owl template/component tree"]
    VI --> VM["View Model"]
    VI --> SM["SearchModel / control panel"]
    OT --> UI["Interactive view"]
    VM --> UI
    SM --> UI
Loading
  1. Action service определяет model и набор view modes.
  2. View service вызывает серверный get_views с очищенным context и action/menu/filter options.
  3. Сервер применяет inheritance и возвращает итоговый arch, field descriptions, metadata связанных моделей, toolbar actions и filters.
  4. View парсит XML и выбирает descriptor по js_class либо по type.
  5. Graph/pivot bundle при необходимости подгружается лениво.
  6. View-specific ArchParser строит archInfo.
  7. Descriptor предоставляет Controller, Renderer, Model, Compiler и props extraction.
  8. Compiler превращает view arch в Owl template с dynamic expressions.
  9. SearchModel и control panel добавляют domain/context/group/order/favorites.

View service кэширует metadata и инвалидирует её после изменений view/filter. Сервер остаётся источником итоговой inherited arch; браузер не воспроизводит весь ir.ui.view inheritance graph.

13.2. View descriptor

Базовые registrations:

  • form;
  • list;
  • kanban;
  • calendar;
  • graph;
  • pivot;
  • settings-specific form variant.

Descriptor — это plugin contract. Он может заменить Controller/Renderer/Model/Compiler, определить button template, search menu types, props и limit. js_class в XML позволяет выбрать не стандартный type renderer, а специальную регистрацию.

13.3. View Compiler

Compiler понимает специальные узлы и attributes Odoo, а не просто HTML:

  • field;
  • buttons/actions;
  • widgets;
  • labels/groups/notebooks/pages;
  • control/create/delete sections;
  • dynamic modifiers;
  • subviews отношений;
  • decorations и context/domain expressions.

Результат — Owl component tree. Поэтому XML view — DSL программы UI, а не шаблон страницы.

14. Python expressions и domains в браузере

View metadata содержит Python-подобные expressions: modifiers, context, domain, decorations. Браузер не может выполнить CPython, поэтому core/py_js реализует безопасное подмножество:

  • tokenizer;
  • parser/AST;
  • evaluator с контролируемым evaluation context;
  • truthiness/comparison/operators/collections, необходимые expressions.

Domain умеет:

  • разбирать строковые и list domains;
  • нормализовать логические операторы;
  • комбинировать AND/OR/NOT;
  • вычислять domain на доступном record context;
  • сериализовать для сервера.

Это принципиальная часть совместимости view DSL. В новой платформе нельзя заменять её eval. Нужен typed ExpressionIR, whitelist функций и явное различение:

  • выражений, вычисляемых полностью на клиенте;
  • выражений, транслируемых в query;
  • выражений, требующих server evaluation;
  • неподдерживаемых выражений с диагностикой.

15. W8: client relational model

Odoo browser хранит не только response data. model/relational_model/ — клиентская модель графа редактируемых records.

15.1. Record

Record поддерживает:

  • server values и local changes отдельно;
  • dirty/valid/invalid state;
  • edit/readonly modes;
  • active fields и field specification;
  • evaluation contexts для modifiers/domain/context;
  • savepoints и discard;
  • create/save/duplicate/archive;
  • coordination onchange requests;
  • parent-child propagation;
  • relation subrecords;
  • urgent save через sendBeacon в ограниченных unload-сценариях.

Сохранение сначала строит write values с relation commands, вызывает web_save, затем принимает authoritative projection сервера.

15.2. StaticList и x2many

X2many в браузере — виртуальный список с:

  • persisted и virtual ids;
  • paging и loading;
  • local create/edit/delete/unlink/link;
  • queue relation commands;
  • resequence;
  • duplicate;
  • dialog-based extended editing;
  • выбором существующих связанных records;
  • nested onchanges и save/discard.

CREATE/UPDATE/DELETE/UNLINK/LINK/CLEAR/SET различаются семантически. Например, убрать строку из one2many и физически удалить объект — не всегда одно и то же.

15.3. Model lifecycle

Базовый Model и useModel связывают async loading с component lifecycle. RelationalModel умеет загружать:

  • одиночную запись;
  • новый virtual record;
  • flat list;
  • grouped list;
  • nested relation roots.

Requests должны переживать concurrency races: старый ответ не должен перезаписать более новое состояние; уничтоженный component не должен продолжать UI mutation.

15.4. Архитектурный смысл

Это mini unit-of-work в браузере. Он не заменяет server transaction и не гарантирует durability, но обеспечивает coherent draft до commit. Простая React form, хранящая JSON object и вызывающая GraphQL mutation, не эквивалентна этой модели.

16. W9: SearchModel и query UX

search/search_model.js — один из крупнейших клиентских state machines, около 2 341 строки. Он объединяет:

  • search view arch;
  • fields и search items;
  • action defaults;
  • text search;
  • filters;
  • group-by;
  • date/datetime intervals;
  • favorites;
  • custom filters;
  • search panels;
  • dynamic/property fields;
  • comparison periods;
  • domain, context, order и groupby output;
  • импорт/экспорт state при навигации.

16.1. Facets

Видимая строка поиска — проекция структурированного состояния. Facet может представлять несколько values одного filter, interval или group-by. Изменение facet перестраивает итоговый query triple:

domain + context + groupBy (+ orderBy)

16.2. Favorites

Favorite — не просто сохранённый текст domain. Он может включать:

  • domain;
  • context;
  • group-by;
  • sorting;
  • shared/personal visibility;
  • default activation;
  • action/model association.

16.3. Control panel

Control panel связывает search bars, breadcrumbs, view switcher, action/cog menus, pager и buttons view controller. Он является общей application chrome над разными view types.

16.4. Следствие для миграции

Generic list без SearchModel покрывает только небольшой CRUD-срез Odoo. Для переносимости нужен отдельный SearchIR с filters, contexts, group-by, time intervals, favorites и search-panel contract.

17. W10: стандартные views

17.1. Form

Form — оркестратор Record:

  • mode readonly/edit;
  • create/edit/save/discard;
  • autofocus и tab order;
  • status и action buttons;
  • notebook/groups/labels;
  • nested relation subviews;
  • chatter/attachment extension slots для зависимых addons;
  • warnings и validation;
  • duplicate/archive/delete;
  • beforeLeave protection.

Компилятор формы преобразует декларативные nodes в field components и layout structure.

17.2. List

List поддерживает:

  • flat/grouped data;
  • columns и optional columns;
  • inline editable rows;
  • multi-edit;
  • selection и bulk actions;
  • ordering;
  • handle/resequence;
  • paging/count/limits;
  • aggregate footers;
  • decorations;
  • group headers/buttons;
  • create/delete/control elements;
  • relation use как subview.

17.3. Kanban

Kanban включает:

  • card template compilation;
  • grouped/ungrouped columns;
  • drag/resequence;
  • quick create;
  • progress bars и colors;
  • group create/edit/delete/fold;
  • record actions;
  • responsive layouts.

Kanban template — полноценный DSL над record fields, а не фиксированная карточка.

17.4. Calendar

Calendar имеет специализированную model/rendering логику для ranges, scales, all-day events, filters, popovers, create/edit и navigation. Он опирается на FullCalendar bundle, но Odoo-specific model/action semantics остаются поверх библиотеки.

17.5. Graph и Pivot

Эти views загружаются лениво и используют read_group/web_read_group:

  • measures;
  • row/column groups;
  • intervals;
  • ordering;
  • comparison;
  • drill-down в records;
  • pivot expansion;
  • XLSX export;
  • Chart.js rendering для graph.

Это общая аналитика по любой модели с совместимыми fields, а не отдельный BI addon.

18. W11: fields и view widgets

В registry обнаружено около 100 field keys/aliases; исходные registrations распределены по десяткам файлов. Widget descriptor обычно задаёт:

  • Owl component;
  • поддерживаемые field types;
  • displayName;
  • supported options;
  • extractProps;
  • дополнительные relatedFields/field dependencies;
  • использование subview;
  • ширину/visual metadata.

Группы widgets:

  • базовые scalar: char, text, integer, float, boolean;
  • date/datetime и ranges;
  • monetary/percentage/progress;
  • selection/radio/badge/priority/status;
  • many2one, many2many, one2many и variants;
  • binary/image/file/signature;
  • HTML и rich text integration points;
  • properties и dynamic placeholders;
  • URL/email/phone/copy/handle;
  • domain/model/reference widgets;
  • specialized settings and relational selectors.

Один database field может иметь несколько UI representations. Поэтому mapping field type → React input недостаточен; нужен mapping:

(field type, widget key, options, view context, readonly state) → component contract

view_widgets — отдельная registry category для не-field компонентов, вставляемых <widget name="..."> в arch.

19. W12: общие services и UI primitives

19.1. Dialog/overlay/popover/bottom sheet

Это системные portals и lifecycle managers, а не просто CSS components. Они обеспечивают stack, focus, z-index, close semantics, responsive substitution и promise/callback coordination.

19.2. Notifications/effects/errors

RPC и application exceptions проходят через error service и registry handlers. Ошибка может стать dialog, notification, reconnect flow или специализированным экраном. Notification service управляет lifetime и actions; effect service — визуальными подтверждениями.

19.3. Hotkeys и commands

Hotkey service нормализует keyboard input с учётом scopes и priorities. Command service предоставляет палитру/registries providers. Addons могут добавлять команды, не меняя root shell.

19.4. UI service

Отслеживает viewport, blocking overlays, direction, touch/small-screen characteristics и другие глобальные UI states. Responsive поведение views опирается на сервис, а не только на media queries.

19.5. File upload и datetime picker

Оба являются stateful services/components с concurrency, progress/error semantics и общими contracts. File upload связан с binary endpoints; datetime picker — с localization/timezone rules.

19.6. Localization

Bootstrap translations и normal translations имеют отдельные endpoints и hash/cache. Browser runtime хранит locale metadata, переводы, direction, date/number formatting. Luxon используется как базовая date/time library, но Odoo добавляет свои serializers, parsers и business display conventions.

20. Multi-company как сквозной контекст

Session info содержит allowed companies и ancestor hierarchy. Клиент:

  • читает активные company ids из cids cookie/router-compatible state;
  • формирует allowed_company_ids в ORM context;
  • сортирует/нормализует ids для стабильных cache keys;
  • отображает company selector;
  • при переключении проверяет допустимость текущего record/action;
  • обновляет состояние или перезагружает application там, где это необходимо.

Company — не фильтр одной страницы. Это ambient execution context для:

  • defaults;
  • property fields;
  • record rules;
  • domains;
  • computed values;
  • currencies;
  • menus/actions;
  • кэширования.

Любая альтернативная платформа должна сделать tenant/company context частью каждого query/command и cache identity, а не доверять только client-side predicate.

21. W13: binary, exports и reports

21.1. Binary controller

controllers/binary.py обслуживает:

  • attachments/content;
  • model binary fields;
  • images и transformations/placeholders;
  • asset attachments;
  • user uploads;
  • company logos;
  • fonts/signature resources.

Versioned immutable URLs позволяют долгий browser/CDN cache. Некоторые логически read routes могут материализовать отсутствующий asset bundle attachment на write cursor — важное исключение при анализе read-only поведения.

21.2. Export

controllers/export.py предоставляет CSV/XLSX и metadata endpoints:

  • доступные formats;
  • exportable field tree;
  • сохранённые field lists;
  • flat и grouped data;
  • relation labels и property fields;
  • spreadsheet limits и formatting.

Export применяет ORM access semantics; это не прямой dump таблицы.

21.3. Reports

controllers/report.py поддерживает HTML/PDF/text report routes, download protocol, wkhtmltopdf status и barcode. Report action разрешается через report service/QWeb, context и record ids. Dynamic filename может вычисляться server expression после получения доступа к records.

Report assets отделены от backend assets, потому что HTML для PDF/печати имеет другую среду, размеры, fonts и CSS ограничения.

21.4. Pivot XLSX

Pivot экспортируется отдельным endpoint: клиент передаёт структурированную аналитическую таблицу/metadata, сервер формирует workbook.

22. Остальные backend controllers

Controller Ответственность Важная граница
session.py authenticate, session info/check/account/destroy/logout, languages/modules session и DB selection предшествуют обычному user RPC
database.py selector/manager/create/duplicate/drop/backup/restore/password/list административная плоскость, защищённая master password/config; отдельные POST имеют csrf=False по protocol design
domain.py syntactic/planning validation domain через query/EXPLAIN sudo проверяет валидность выражения, а не право читать будущий результат
view.py изменение custom view отдельный controlled customization path
model.py model definitions для test tooling не основной production CRUD API
profiling.py profiler configuration и Speedscope output debug/diagnostic plane
vcard.py vCard партнёра тонкий системный adapter, не бизнес-описание partner
webmanifest.py PWA/scoped manifests, service worker, offline page/icons installable application shell

Во всём controllers/ около 68 route declarations: примерно 22 JSON-RPC и 45 HTTP; по auth — около 22 none, 13 public, 32 user и один bearer. Сырые числа требуют контекста: auth=none используется и для pre-database flows, а не означает отсутствие проверок внутри controller.

23. Системные model extensions

Кроме общего base web protocol, каталог расширяет несколько опорных моделей:

  • ir.http — session/bootstrap information, registry hash, browser cache data, companies/currencies, upload limits, debug/test mode;
  • ir.ui.menu — web menu graph;
  • ir.ui.view — сведения о поддерживаемых view types;
  • ir.model — model selector/definitions;
  • QWeb rendering — web/image URL helpers;
  • base.document.layout — transient настройка report layout и preview;
  • res.company — обновление shared report SCSS asset;
  • user settings/embedded actions — системная персонализация клиента;
  • res.partner — ограниченная vCard integration.

Security/data каталоги добавляют только необходимые правила и начальные системные resources: права на собственные embedded action settings/document layout, placeholder attachment, report layouts/SCSS и neutralization banner. Это подтверждает инфраструктурный, а не бизнес-доменный характер модуля.

24. W14: PWA и public interaction runtime

24.1. Service worker

Service worker кэширует application shell/offline page. Особая мера защиты:

  • session-specific info сохраняется в памяти worker;
  • при помещении HTML в Cache API sensitive bootstrap вычищается;
  • при отдаче cached shell текущая session info инжектируется обратно.

Есть share-target flow для передачи файлов в Odoo и scoped app manifests/shortcuts.

Это не offline-first ERP: нет общего локального replicated ORM, conflict resolution и очереди транзакций для произвольных моделей. Offline capability относится в первую очередь к оболочке и fallback.

24.2. Public interactions

Новый interaction runtime адресует DOM по selector и предоставляет lifecycle:

  • setup;
  • willStart;
  • start;
  • destroy;
  • declared dynamic content;
  • automatic listener cleanup;
  • debounce/throttle/lock helpers;
  • возможность mount Owl component.

Регистрации идут через public.interactions. Это лёгкая component/controller model для website DOM, где полный backend WebClient не нужен. Рядом сохраняется legacy public_widget compatibility.

Для миграции website/eCommerce addons этот слой является отдельным классом задачи: generic resource UI Angee его автоматически не заменяет.

25. W15: design system, responsive, RTL, dark и print

SCSS организован несколькими уровнями:

  • primary variables, которые addons могут переопределить до Bootstrap;
  • Bootstrap variables/mixins/utilities;
  • Odoo component tokens;
  • backend/frontend-specific rules;
  • dark theme variables и overrides;
  • print/report styles;
  • RTL-aware layout;
  • touch/small-screen adaptations;
  • fonts/icons.

Важно, что theme extensibility связана с asset ordering. Просто скопировать итоговый CSS недостаточно: зависимые addons ожидают возможность добавить variables и component overrides.

Доступность реализуется распределённо: semantics/focus/keyboard в компонентах, hotkey service, dialogs/overlays, ARIA templates, contrast/theme и test assertions. Это не отдельный «accessibility layer», который можно перенести одним файлом.

Vendored runtime включает Owl, Hoot/hoot-dom, Bootstrap, Luxon, Popper, DOMPurify, FullCalendar, Chart.js, PDF.js, Ace, diff-match-patch, Prism, ZXing, signature pad, stacktrace и QUnit, плюс fonts/browser maps. jQuery присутствует преимущественно как compatibility/legacy dependency, а основное приложение построено на Owl.

26. W16: тестовая платформа

Тесты web — не только тесты конкретного модуля. Это executable specification клиентского контракта Odoo.

26.1. Hoot и hoot-dom

Современный suite использует Hoot:

  • component mounting;
  • deterministic async helpers;
  • DOM queries/actions/assertions;
  • mock browser APIs;
  • fake time/network/storage;
  • snapshots и cleanup.

Статический поиск даёт примерно 6 136 вызовов test(...) и 130 describe(...) во frontend suite. Это приближённые числа, но они показывают масштаб contract corpus.

26.2. Mock server — второе упрощённое исполнение Odoo

static/tests содержит моделируемый сервер с:

  • ServerModel;
  • field declarations и relations;
  • records;
  • CRUD/search/read_group;
  • actions/views;
  • access rights и record rules;
  • onchange behavior;
  • session/browser state.

Это не production ORM, но достаточно точная модель observable protocol для frontend tests. Для проекта миграции она особенно ценна: ожидаемое поведение можно закреплять differential tests между Odoo и Angee.

26.3. Framework helpers

Есть builders/helpers для:

  • environment/services;
  • components;
  • webclient/actions/router;
  • views и arch;
  • relational records;
  • search/kanban/list/form;
  • translations/localization;
  • session/company;
  • touch/browser/IndexedDB;
  • DOM events и uploads.

26.4. Legacy QUnit, tours и clickbot

Legacy suite поддерживается для addons, ещё не перенесённых на Hoot. Tours проверяют пользовательские сценарии поверх реального приложения; clickbot систематически исследует кликабельные элементы и ловит crashes.

26.5. Python tests

Backend tests покрывают controllers, assets, reports, session, menus/router inputs, domains, generic web read/search/group semantics и security edges.

26.6. Licensing

Тесты — прекрасная поведенческая спецификация, но копирование/трансляция source должно учитывать LGPL и лицензии vendored libraries. Для независимого продукта безопаснее извлекать observable contracts и писать собственную реализацию/fixtures, сохраняя provenance и юридическое решение проекта.

27. Сквозные жизненные циклы

27.1. Открытие list/form из меню

  1. Menu service получает descriptor action.
  2. Action service загружает/нормализует ir.actions.act_window.
  3. Router обновляет URL/state и controller stack.
  4. View component выбирает первый view mode.
  5. View service запрашивает get_views.
  6. Сервер разрешает XML inheritance и field metadata.
  7. View registry выбирает descriptor; arch parser строит archInfo.
  8. SearchModel активирует defaults/favorites и строит domain/context/groupby.
  9. RelationalModel вызывает web_search_read, web_read_group либо web_read.
  10. Renderer и fields отображают result.
  11. Switch view переиспользует action/search state, но создаёт подходящий model/controller.

27.2. Редактирование формы

  1. Field widget извлекает props из field metadata и arch options.
  2. Пользовательское значение записывается в client Record как dirty change.
  3. Evaluation context немедленно пересчитывает локальные modifiers/domain.
  4. При необходимости серверный onchange выполняется над virtual record.
  5. Returned diff сливается в Record и nested StaticLists.
  6. Warning/error показывается общим service.
  7. Save строит scalar values и relation commands.
  8. web_save выполняет реальный create/write в transaction.
  9. Сервер возвращает новую authoritative projection.
  10. Record очищает dirty state; action/router/title могут обновиться.

27.3. Button

  1. View compiler превращает XML button в component/action descriptor.
  2. Controller проверяет dirty state и при необходимости сохраняет.
  3. Action service вызывает /web/dataset/call_button.
  4. Model method выполняется с record ids/context.
  5. Result очищается как action descriptor.
  6. Action service открывает view/report/dialog/URL или закрывает текущую action.

27.4. Group/analytics

  1. SearchModel формирует domain, groupBy, measures и context.
  2. Specialized view model вызывает web_read_group.
  3. Сервер применяет ACL/rules, aggregates, group expansion и date intervals.
  4. Клиент строит groups/chart/pivot cells.
  5. Drill-down преобразует group domain в window action списка.

27.5. Report/download

  1. Button/action возвращает report descriptor.
  2. Action service строит download request и blocking UI.
  3. Report controller рендерит QWeb HTML/PDF/text с отдельными assets.
  4. Binary response скачивается; server error извлекается из download protocol.

28. Security boundaries

28.1. Браузер не является trusted enforcement point

Readonly/invisible/domain в UI улучшают UX, но не заменяют:

  • model access rights;
  • record rules;
  • field groups;
  • controller auth;
  • method-level checks;
  • company checks.

Generic call_kw делает особенно важным принцип: любой published method должен быть безопасен при прямом вызове, даже если кнопка скрыта.

28.2. Context — входные данные, а не полномочия

Клиент передаёт context, включая active ids/company ids/defaults. Сервер обязан нормализовать и проверять всё, что влияет на доступ. Наличие id в action/context не доказывает доступ.

28.3. Domain validation

/web/domain/validate с sudo подтверждает лишь то, что выражение синтаксически/SQL-планово допустимо. Он не выдаёт доступ к данным, совпадающим с domain.

28.4. Caching

Registry hash и browser secret разделяют поколения/сессии cache; AES-GCM защищает disk entries от чтения после смены key. Но cache invalidation и server revalidation остаются обязательными. Нельзя использовать client cache как доказательство текущего права доступа.

28.5. HTML и content

Rich content проходит sanitization/DOMPurify и server-side правила, однако templates/markup/extensions остаются потенциальной XSS surface. Custom t-raw, generated HTML и website interactions требуют отдельного аудита.

29. Performance model

Оптимизация распределена по всей архитектуре:

  • inline session avoids startup RPC;
  • menu prefetch идёт параллельно assets;
  • versioned immutable asset/binary URLs;
  • lazy graph/pivot/heavy libraries;
  • service dependency parallel start;
  • batched get_views и nested field specification;
  • web_search_read сокращает round trips;
  • ORM prefetch остаётся на сервере;
  • RPCCache дедуплицирует requests и использует RAM/IndexedDB;
  • readonly routing допускает read replicas/readonly cursor;
  • grouped/paged models не загружают весь dataset;
  • virtual drafts не пишут БД на каждый keypress;
  • concurrency utilities отбрасывают stale responses;
  • stable registry/menu/translation hashes управляют invalidation.

Важный анти-вывод: производительность нельзя воспроизвести добавлением CDN после переноса. Протокол специально агрегирует metadata/data и управляет состоянием на клиенте.

30. Что является публичным контрактом для addons

30.1. Декларативно переносимые поверхности

  • manifest assets и bundle operations;
  • XML views/actions/menus/search views;
  • стандартные field widgets/options;
  • modifiers/domain/context/group-by;
  • стандартные reports;
  • registry entries известных категорий;
  • service dependencies при совместимом API.

30.2. Условно переносимые

  • custom view js_class;
  • custom field/view widgets;
  • client actions;
  • Owl components;
  • template inheritance;
  • public interactions;
  • report templates со сложными helpers.

Их можно переносить через recognizers/adapters только для известных patterns.

30.3. Практически ручные

  • произвольный patch();
  • JavaScript, зависящий от undocumented internal state;
  • сложные Owl lifecycle/concurrency effects;
  • direct DOM mutations и legacy widgets;
  • arbitrary browser APIs;
  • custom canvas/chart/editor logic;
  • website interaction behavior, связанное с конкретной DOM/CSS структурой.

31. Поправка к сравнению Odoo и Angee

Предыдущий анализ без addons/web правильно описал server runtime, но недооценил размер UI-semantic gap. XML view и action — не просто серверные метаданные, которые можно отдать generic React renderer. Их смысл совместно реализуют:

server model metadata
+ resolved XML arch
+ Web ORM projection/onchange protocol
+ action/router/search state machines
+ client relational draft model
+ field/view registries
+ Owl templates/components/services

У Angee уже есть полезные современные кирпичи — Django models, GraphQL resources, React resource pages, REBAC, messages/files/jobs/transitions и deterministic addon composition. Но для эквивалентности стандартному Odoo web ему не хватает системного слоя между schema и authored React UI.

31.1. Что нужно добавить в Angee

A. Typed intermediate representations

  • AddonIR и dependency graph;
  • AssetBundleIR с include/remove/before/after/lazy semantics;
  • ModelIR/FieldIR;
  • ActionIR/MenuIR/RouterStateIR;
  • ViewIR с contribution и resolved forms;
  • SearchIR/SearchPanelIR;
  • WidgetIR и ClientExtensionIR;
  • TemplateIR;
  • ExpressionIR/DomainIR;
  • ReportIR;
  • Capability/UnsupportedIR с provenance.

B. Metadata-driven client runtime

  • action stack и unified doAction contract;
  • menu/router/deep-link restoration;
  • view descriptor registry;
  • arch parsers и React renderers form/list/kanban/calendar/graph/pivot;
  • generic field/widget registry;
  • SearchModel-equivalent;
  • toolbar/action/cog/favorite/control panel;
  • predictable extension API вместо случайных imports/monkey patches.

C. Generic draft/command engine

  • virtual new/existing records;
  • local dirty graph;
  • nested relation lists и stable virtual ids;
  • relation command semantics;
  • savepoints/discard;
  • server-side prefill/onchange over draft;
  • warnings/domain/modifier result;
  • authoritative post-save projection;
  • concurrency/version conflict policy.

D. Web transport contracts

  • batched/nested projection read;
  • searchRead/readGroup semantics;
  • server-enforced context/company/security;
  • binary/upload/download/report protocols;
  • cache keys/version/invalidation;
  • consistent structured errors.

E. Compiler and diagnostics

  • parse Odoo Python/XML/manifest/JS metadata;
  • resolve addon dependency and XML inheritance;
  • classify standard vs custom JS contributions;
  • emit Angee declarations/code;
  • never silently ignore unsupported js_class, widget, patch or expression;
  • generate a migration report down to source file/XML id/line.

F. Contract tests

  • fixtures generated from real Odoo addons;
  • differential queries/actions/onchanges;
  • view rendering assertions;
  • translated tours for critical workflows;
  • golden IR snapshots;
  • security and multi-company negative tests.

31.2. Чего не следует делать

Не следует буквально клонировать Owl или переносить action_service.js строка в строку. Цель — сохранить observable semantics там, где они необходимы бизнес-addon, но выразить их Angee-native декларативными contracts и React components.

Предпочтительная форма:

Odoo addon source
    -> scanner
    -> typed normalized IR
    -> capability analysis
    -> Angee model/security/data/UI generators
    -> explicit manual overlays
    -> differential contract tests

31.3. Уровни совместимости frontend

Уровень Что можно обещать
F0 inventory assets/views/widgets/patches и честный unsupported report
F1 стандартные actions, menus, form/list, базовые fields/modifiers/search
F2 relations, onchange drafts, buttons, kanban, favorites/search panels
F3 calendar/graph/pivot/reports/exports и распространённые standard widgets
F4 известные custom registry entries/templates через adapter library
F5 произвольный custom JS/Owl/patch/public interaction — manual overlay

Даже хороший F3 не означает автоматический перенос любого addon: arbitrary Python и JavaScript остаются общими языками программирования.

32. Поправка к оценке arpee-angee

arpee-angee остаётся ценным clean-room ERP corpus, но после анализа web его ограничения видны точнее.

32.1. Что в нём удачно

  • доменные модели и инварианты выражены Angee-native;
  • явные state transitions лучше скрытых side effects;
  • сильные PostgreSQL/concurrency tests;
  • аккуратная multi-company модель;
  • GraphQL resources и React screens доказывают пригодность Angee для реального ERP slice;
  • его можно использовать как golden target и набор acceptance scenarios.

32.2. Чего ему не хватает именно по web-контракту

  • универсального action/view/search runtime;
  • generic draft record и x2many command engine;
  • server virtual onchange/prefill с nested diff;
  • компиляции XML arch/widget/options/modifiers;
  • registry для полей/views/client actions;
  • deep-link/controller-stack semantics;
  • generic grouped analytics/search panels/favorites;
  • asset/template/JS-extension scanner;
  • переносимых tours и differential UI contracts.

Текущие authored forms и GraphQL nested-line diffs решают конкретные экраны, но не создают платформу, на которую можно указать произвольный список Odoo addons.

32.3. Развивать или начинать заново

Рекомендация не меняется, но становится жёстче сформулированной:

  • не выбрасывать arpee: сохранить domain code, tests и готовые screens;
  • не продолжать его как бесконечный ручной перенос экранов;
  • с нуля создать compiler/runtime track, включая web IR и generic UI/draft engine;
  • постепенно заменить повторяющийся authored CRUD в arpee generated declarations;
  • оставить сложные workflows, product UX и custom interactions ручными overlays.

Иначе каждый новый addon будет снова требовать ручного создания не только моделей, но и части скрытого web runtime.

33. Приоритетный план реализации после учёта web

Этап 0. Capability scanner

Сканировать manifests, asset operations, XML views/actions/menus/search, field widgets, js_class, JS registry additions, patch(), template inheritance и public interactions. Результат — полный source-provenance report, ничего не генерировать молча.

Этап 1. Stable IR и standard UI slice

Реализовать ActionIR, ViewIR, SearchIR, FieldIR, Domain/ExpressionIR; сгенерировать menu/router и form/list для read-only flows.

Этап 2. Draft runtime

Добавить virtual records, defaults, dirty state, nested relations, commands, onchange/prefill, save/discard и concurrency policy. Это важнее расширения числа бизнес-моделей.

Этап 3. Security/context parity

Встроить user/company/context в query/command/cache identity; negative tests для ACL/REBAC/field visibility; запрет доверия к UI modifiers.

Этап 4. Search и bulk UX

Filters, group-by, favorites, search panel, paging, selection, bulk actions, list inline editing, grouped reads.

Этап 5. Расширенные views и документы

Kanban/calendar/graph/pivot, reports, CSV/XLSX, attachment/image protocols.

Этап 6. Extension adapter SDK

Registry-like stable React extension API, template slots, known Odoo widget adapters. patch() не эмулировать напрямую: конвертер должен требовать named overlay point или ручное решение.

Этап 7. Differential migration corpus

Использовать arpee и несколько ограниченных Odoo addon-наборов как golden corpus. Для каждого — model/data/security/action/view/onchange/tour parity matrix.

34. Ключевые архитектурные инварианты

  1. web — часть ядра по функции, хотя поставляется addon.
  2. Сервер всегда остаётся authority для данных и доступа.
  3. Browser Record — draft/unit-of-work, а не authoritative entity.
  4. XML view — исполняемый UI DSL, а не статическая разметка.
  5. Action — команда навигационного runtime, а не URL.
  6. Domain/context/groupby образуют единый query contract.
  7. onchange выполняется над виртуальной записью и может менять вложенный граф.
  8. X2many command semantics нельзя свести к одному nested update.
  9. Frontend собирается из contributions установленных addons динамически.
  10. Registry keys, template inheritance и asset order — публичные extension contracts.
  11. UI visibility не является security enforcement.
  12. Company/context входят в семантику и cache identity.
  13. Lazy loading и protocol aggregation — часть performance model.
  14. Hoot/mock-server/tours — значительная часть спецификации observable behavior.
  15. PWA shell не равен offline ERP.

35. Навигационная карта по исходникам

Bootstrap и assets

  • /Users/max/Dev/odoo_19/addons/web/__manifest__.py
  • /Users/max/Dev/odoo_19/addons/web/controllers/home.py
  • /Users/max/Dev/odoo_19/addons/web/controllers/webclient.py
  • /Users/max/Dev/odoo_19/addons/web/views/webclient_templates.xml
  • /Users/max/Dev/odoo_19/addons/web/static/src/main.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/start.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/env.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/module_loader.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/assets.js

Transport и Web ORM

  • /Users/max/Dev/odoo_19/addons/web/controllers/dataset.py
  • /Users/max/Dev/odoo_19/addons/web/controllers/json.py
  • /Users/max/Dev/odoo_19/addons/web/models/models.py
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/network/rpc.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/network/rpc_cache.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/orm_service.js

Application shell

  • /Users/max/Dev/odoo_19/addons/web/static/src/webclient/webclient.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/webclient/actions/action_service.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/browser/router.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/webclient/menus/menu_service.js
  • /Users/max/Dev/odoo_19/addons/web/models/ir_ui_menu.py
  • /Users/max/Dev/odoo_19/addons/web/controllers/action.py

Views, search и records

  • /Users/max/Dev/odoo_19/addons/web/static/src/views/view.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/views/view_service.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/views/view_compiler.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/model/relational_model/relational_model.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/model/relational_model/record.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/model/relational_model/static_list.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/search/search_model.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/py_js/
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/domain.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/views/fields/

Extension runtime

  • /Users/max/Dev/odoo_19/addons/web/static/src/core/registry.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/utils/patch.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/core/template_inheritance.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/@types/

Files, reports, PWA и public

  • /Users/max/Dev/odoo_19/addons/web/controllers/binary.py
  • /Users/max/Dev/odoo_19/addons/web/controllers/export.py
  • /Users/max/Dev/odoo_19/addons/web/controllers/report.py
  • /Users/max/Dev/odoo_19/addons/web/controllers/webmanifest.py
  • /Users/max/Dev/odoo_19/addons/web/static/src/service_worker.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/public/interaction.js
  • /Users/max/Dev/odoo_19/addons/web/static/src/public/interaction_service.js

Tests

  • /Users/max/Dev/odoo_19/addons/web/tests/
  • /Users/max/Dev/odoo_19/addons/web/static/tests/
  • /Users/max/Dev/odoo_19/addons/web/static/tests/_framework/

36. Итог

После включения addons/web Odoo следует понимать как распределённый метафреймворк:

  • Python core строит динамическую серверную модель и транзакционный runtime;
  • base хранит системную метамодель приложений;
  • web исполняет модель приложения в браузере и задаёт общий протокол между UI и ORM;
  • бизнес-addons поставляют contributions во все эти механизмы.

Главное открытие для проекта Odoo→Angee: переносить нужно не «models и страницы», а программу, распределённую между Python classes, XML metadata, server Web ORM protocol, frontend registries/templates и client state machines.

Angee способен стать целевой платформой, но для обещания «указать список addons» ему нужен самостоятельный compiler/runtime product. Наивысший приоритет — не новые ERP screens, а typed IR, generic action/view/search runtime и виртуальная client/server draft model. arpee-angee следует сохранить как доменный эталон и постепенно посадить на этот новый фундамент, а не переписывать целиком и не продолжать бесконечно вручную.

Odoo 19 → Angee: сравнительная архитектура, аудит arpee-angee и план платформы миграции

Дата анализа: 3 августа 2026 года.

Исходные снимки:

  • Odoo: /Users/max/Dev/odoo_19/odoo, ветка 19.0, commit 7d3a7fac27332f440404383f41cc113bf3face53;
  • Angee: /Users/max/Dev/angee-django, commit 4f6e8235721f5e0d12266f425f6b665d3b972ece;
  • arpee: /Users/max/Dev/arpee-angee, commit aed6fba6fd474e2281c6f963f1742e426eed3307.

Связанные документы:

  • /private/tmp/odoo-19-core-architecture-ru.md — подробная карта серверного ядра Odoo;
  • /private/tmp/odoo-19-web-core-architecture-ru.md — отдельный аудит обязательного клиентского/транспортного ядра addons/web.

1. Короткий ответ

Angee — разумная основа для нового ERP, но сегодня это не среда исполнения Odoo и не платформа автоматической миграции Odoo-addons. У неё уже есть большая часть современных инфраструктурных кирпичей: Django ORM, детерминированная композиция addon-графа, генерируемые runtime-модели, GraphQL CRUD, REBAC, React resource UI, сообщения, файлы, фоновые задания, transitions, units/currencies/sequences, audit/history и data resources. Но между «кирпичи ERP присутствуют» и «любой бизнес-addon Odoo можно преобразовать» находится отдельный продукт: компилятор семантики Odoo, промежуточное представление, недостающий транзакционный runtime и система диагностики несовместимостей.

arpee-angee — полезная и в значительной части качественная ручная реализация ERP-среза. Это не попытка построить конвертер: это clean-room перенос бизнес-возможностей в Angee-native модели, GraphQL-схемы и React-страницы. В нём уже есть хорошие доменные решения, сильная multi-company модель, явные переходы состояний и серьёзные PostgreSQL/concurrency-тесты. Выбрасывать его и переписывать ERP с нуля я бы не стал.

Но продолжать его тем же способом — вручную писать ещё десятки доменов, повторяя модели, GraphQL resources и страницы — тоже не следует. Моя рекомендация:

  1. Сохранить arpee-angee как эталонный corpus и первую ERP-реализацию. Сохранить модели, доменные тесты, спецификации и удачные security/transaction patterns.
  2. Заморозить широкое наращивание бизнес-доменов, пока не построен семантический фундамент: derived/recompute engine, server-side prefill, command/unit-of-work, UI/security/data compiler и стабильная production-сборка.
  3. Начать с нуля только новый самостоятельный трек odoo2angee: scanner → typed IR → code generators → diagnostics → manual overlays → differential tests.
  4. Затем постепенно перевести повторяющиеся части arpee на генерацию, оставляя нетривиальную бизнес-логику в ручных overlay/services.

Иными словами: не начинать ERP заново; начать заново архитектуру мигратора.


2. Что именно было изучено и пределы вывода

Аудит охватывает все отслеживаемые файлы трёх снимков, исключая .git, виртуальные окружения, кэши и генерируемый/сторонний мусор. Для структуры использовались полный список файлов, AST-инвентаризация Python, поиск деклараций и связей; ключевые подсистемы, большие модели, тесты, manifests, ZED policies, спецификации, настройки сборки и web packages читались содержательно.

Размеры полезны не как метрика качества, а как контроль охвата:

Кодовая база Наблюдаемый объём
Ядро Odoo вне odoo/addons/** 207 файлов, около 63 542 строк
Обязательный addons/web 2 319 файлов; около 6,5 тыс. строк Python backend, 117 тыс. строк production JS/TS, 10,6 тыс. XML и 16,8 тыс. SCSS/CSS; tests/vendor отдельно
Angee (angee, addons/angee, тесты) 1 682 tracked-файла; 493 Python-файла, около 120 579 строк
arpee 280 tracked-файлов, около 55 587 строк со спецификациями и lockfile; 47 Python-файлов, около 20 087 строк

Для arpee обнаружены 46 runtime-моделей, 274 декларации полей и 44 Hasura-style GraphQL resource definitions. В тестах — примерно 266 backend test methods и 83 frontend-теста.

Это source audit, а не подтверждение зелёного CI: тестовые suites не запускались, потому что анализируемые sibling repositories доступны в этом окружении только на чтение и готовые зависимости/runtime отсутствуют. Все 47 Python-файлов arpee успешно разобрались AST-парсером. Там, где ниже говорится «покрыто тестом», это означает наличие тестового кода, а не выполненный в этой сессии тест.

Есть ещё важное методологическое ограничение. Требование «указать список Odoo-addons и получить аналогичный функционал» может означать три совершенно разных продукта:

  • анализатор, который выдаёт план и список несовместимостей;
  • генератор Angee-native каркаса с ручной доработкой;
  • почти полная автоматическая трансляция произвольного Python/XML/JS Odoo.

Третий вариант без ограничений практически требует заново реализовать Odoo runtime внутри Django. Поэтому ниже предложены уровни совместимости с честными границами, а не одна вводящая в заблуждение цифра покрытия.


3. Главная архитектурная разница

3.1. Odoo — динамическая прикладная виртуальная машина

Odoo — не просто ORM плюс web framework. На уровне одной базы данных она строит динамический Registry из установленных модулей. Модель — это не только Python-класс: итоговый класс собирается из вкладов _name, _inherit, _inherits, mixins и расширений установленного addon-графа. Поверх него работают recordsets, Environment, ambient context, cache/prefetch, dependency-aware recomputation, domains, access controls, XML data, views/actions/menus, QWeb, HTTP/RPC, cron и upgrade lifecycle.

Ключевое свойство: поведение итоговой модели и интерфейса зависит от набора модулей конкретной БД и собирается при загрузке Registry. Встроенные DSL и соглашения пронизывают все уровни.

3.2. Angee — детерминированный build-time compositor

Angee сознательно идёт в другую сторону. Addons объявляют Django source models и метаданные, AppGraph разрешает зависимости, composer строит итоговые Django runtime-классы и пишет генерируемые модули. После этого приложение живёт как Django/GraphQL/React stack. Наследование Angee — build-time композиция, а не per-database runtime patching.

У Angee два основных варианта расширения:

  • extends="app.Model", runtime=False — same-row donor: поля/методы вливаются в одну итоговую модель и таблицу;
  • extends="app.Model", runtime=True — materialized child: Django multi-table inheritance с отдельной таблицей дочерней модели.

Это хорошая архитектура для предсказуемых сборок, статической проверки и обычных инструментов Django. Но она меняет контракт модульности:

  • состав приложения фиксируется для release/stack, а не свободно меняется в каждой БД;
  • многие неявные механизмы Odoo должны стать явными командами, policies и dependency graphs;
  • Python-методы Odoo нельзя механически перенести, потому что у них нет recordset + env + context + registry окружения;
  • XML view inheritance нельзя считать просто frontend-шаблоном: это отдельный DSL композиции интерфейса.

3.3. Схема различия

flowchart LR
    subgraph O["Odoo: runtime конкретной БД"]
      OM["addons + manifests"] --> OR["Registry builder"]
      OR --> OC["собранные model classes"]
      OC --> OE["recordsets / Environment / cache"]
      OE --> OS["CRUD / compute / security / RPC"]
      OD["XML data / views / actions"] --> OR
      OD --> OS
    end

    subgraph A["Angee: build/release"]
      AM["addons + addon.toml"] --> AG["AppGraph + composer"]
      AG --> AI["generated Django runtime"]
      AI --> AJ["migrations + QuerySets + services"]
      AJ --> AX["GraphQL metadata + React"]
    end

    T["odoo2angee compiler"] -. "typed IR + diagnostics" .-> AG
    OM -.-> T
    OD -.-> T
Loading

Главный вывод: адаптером на несколько decorators этот разрыв не закрыть. Нужен компилятор и небольшой, но системный ERP semantic runtime поверх Angee.


4. Послойное сравнение Odoo и Angee

В таблице «паритет» означает не совпадение API, а наличие механизма, на который можно отобразить семантику.

Слой Odoo 19 Angee сейчас Разрыв для Odoo-addons
Boot odoo-bin, early monkey patches, config, CLI, server preload Django settings bootstrap, angee command, addon composition Нужен воспроизводимый production build; не писать runtime в immutable container при boot
Addon manifest __manifest__.py, depends/data/assets/demo/install hooks addon.toml, Python package graph, settings/resources/schema/web contributions Нужен импорт manifest и полная классификация его ключей/hooks
Dependency graph module graph, installed state per DB, auto-install детерминированный AppGraph, duplicate/cycle checks Нужен release-level resolver и ясная модель unsupported auto-install/per-DB variance
Module lifecycle install/upgrade/uninstall per DB, module tables/state Django app composition + migrations + resource load Нет эквивалента полного install/upgrade/uninstall lifecycle Odoo
Runtime registry итоговые classes строятся для каждой DB итоговые classes генерируются для stack Осознанно не эмулировать Registry; компилировать в release artifacts
Model extension _inherit, _inherits, delegation, abstract/transient same-row donor, MTI child, abstract source model Структурно близко, но нужны правила conflict/order/metadata и transient models
Record abstraction ordered recordsets, singleton/empty semantics, vectorized methods Django instance/QuerySet/Manager Автотрансляция Python требует semantic lowering; facade быстро превратится в клон Odoo
Ambient execution Environment(cr, uid, context, su), with_context, sudo actor/system contexts, Django transaction, явные args/services Нужен типизированный command context; нельзя бесконтрольно копировать ambient context
Fields descriptors, related/company-dependent/translated/computed Django fields + Angee custom fields/mixins Нет полного поля-семантического паритета
Relations M2O/O2M/M2M + command tuples FK/O2O/M2M, nested GraphQL line diff Структура есть; нужны полный command protocol, triggers/recompute и ограничения bulk paths
Defaults field default, default_get, context defaults Django defaults, client/impl prefill Нет generic server-side context-aware prefill/default pipeline
Onchange server-side virtual record mutation and warnings/domains в основном клиентские зависимости и authored code Нужен generic prefill/onchange protocol с patch/warning/domain result
Compute @api.depends, stored/non-stored, inverse/search, graph свойства, save()/services, Django GeneratedField точечно Нет dependency-aware cross-model recomputation engine
Constraints SQL constraints, @api.constrains, lifecycle integration DB constraints, full_clean, authored validators/services Нужно гарантировать все write paths и batch validation semantics
Domain/query prefix domain AST → Query → SQL, record rules injected GraphQL filters/order/group, Django Q/QuerySet, ZED read filtering Нужен canonical predicate IR и Odoo domain compiler
Cache/prefetch Environment cache, prefetch sets, invalidation Django QuerySet cache, select_related, prefetch_related, loaders Нет общей модели invalidation/recompute; производительность должна быть compiler-managed
CRUD ORM pipeline с access, defaults, inverse, compute, tracking Django save/delete + GraphQL mutation helpers + services Нет одного гарантированного unit-of-work pipeline
Transactions cursor/savepoints/retry, postcommit hooks transaction.atomic, locks, Celery, channels/on_commit Нужны retry policy, outbox, after-commit contract и idempotency layer
ACL model ACL additive by groups REBAC permissions/ZED Можно компилировать значительную часть
Record rules global AND, group-rule OR, domain evaluated in env relationship permissions and filtered managers Чистого REBAC недостаточно для всех attribute/context predicates
Field security group gates на fields field permission metadata/checks Механизм есть, нужен compiler и write-path guarantee
Superuser sudo, superuser/access flags system_context(reason=...) Нужна capability-scoped elevation и централизованный аудит
Multi-company allowed companies, company-dependent props, checks explicit Company + REBAC/scoping в arpee Основа хорошая; нет generic property engine и автоматического consistency contract
External IDs ir.model.data, noupdate, references, uninstall tiered resources и record references Нужен полноценный ledger update/noupdate/uninstall/rename semantics
Data files XML/CSV eval, functions, refs YAML/JSON-like resources + hooks Простые данные конвертируемы; function/eval требуют IR или ручного overlay
Schema auto-init/reflection + module upgrade generated Django models + migrations Сильная база; нужен стабильный artifact/release workflow и Odoo schema/data migration
Migrations versioned module migrations/upgrades Django migrations + addon manual migrations Нужен importer/ordering/checkpoints и policy для generated/manual migration conflicts
HTTP route decorators, auth/csrf/session/converters Django/ASGI/GraphQL; authored endpoints Контроллеры можно переносить только pattern-based или вручную
RPC JSON-RPC/web client model APIs typed GraphQL Не эмулировать RPC по умолчанию; генерировать GraphQL contracts и compatibility gateway только при нужде
Auth/session Odoo web session, DB selection, auth modes Django auth, IAM/OIDC, ASGI Современная замена есть; per-DB routing надо решить отдельно
Workers/cron threaded/gevent workers, cron, bus ASGI, Channels, Celery/Redis/beat, locks Функциональный mapping возможен; нужны transaction/outbox/idempotency conventions
Views XML tree/form/kanban/calendar/search/etc., inheritance React resource pages: Form/List/Board/Calendar/Dashboard/Gallery/Graph/Timeline/Tree Виджеты богаты; нет compiler-facing declarative page IR и XML inheritance compiler
Actions/menus ir.actions.*, menus, bindings, context/domain GraphQL resource actions, page/navigation manifests Нужен ActionIR/MenuIR и compiler-generated navigation
QWeb reports, mail/web templates, directives React frontend; отдельных report templates нет Нужен report/PDF/mail template subsystem; custom QWeb — manual island
Mail/chatter mail.thread, followers, tracking, activities, aliases messaging threads/messages/followers/activities/attachments Хороший functional target; нужен compiler mapping и полноценные template/alias flows
i18n PO terms, field/view/data translations, language context обычная Django/frontend i18n, но не Odoo field translation model Нужны translated field values и term extraction/import
Import/export ORM-aware CSV/XLSX workflows django-import-export/tablib building blocks Нужен product UI, relation resolution, validation preview и external-ID semantics
Reports/analytics QWeb PDF, graph/pivot, spreadsheet ecosystem Graph view/grouping, Dashboard; pivot/spreadsheet/report engine отсутствуют Существенный отдельный пласт
Testing Savepoint/Transaction/HttpCase, tagged tests, test mode Django/pytest-style tests, frontend tests Нужен translator/harness и differential test runner
Observability logging/SQL profiling/error handling Django logging, tasks/ASGI ecosystem Нужны correlation IDs по command, recompute/outbox metrics и migration provenance

Вывод из матрицы

Angee уже покрывает большую часть горизонтальной инфраструктуры современного бизнес-приложения. Основной дефицит — не количество widgets и не ещё одна ORM-фича. Отсутствует единый семантический позвоночник записи, который в Odoo связывает default/onchange/access/CRUD/constraints/recompute/tracking/cache/transaction. Пока каждый addon реализует этот позвоночник вручную, автоматическое преобразование невозможно, а корректность будет деградировать с ростом числа перекрёстных зависимостей.


5. Что в Angee уже готово и пригодно для цели

5.1. Композиция addon-графа

angee/compose/appgraph.py разрешает зависимости детерминированно, обнаруживает дубли и циклы. angee/compose/runtime.py строит runtime-модели; angee/compose/apps.py импортирует их в Django. Это сильная база для generator target: компилятор может выпускать обычные Angee addons, а не патчить framework во время выполнения.

Полезные свойства:

  • явная ownership-модель;
  • воспроизводимый порядок композиции;
  • same-row extension для Odoo-подобного вклада полей в существующую модель;
  • MTI для настоящих специализаций;
  • генерация permissions schema и web manifest рядом с моделями;
  • атомарная запись файлов и проверка stale state;
  • отдельный explicit build, который умеет чистить orphan output и материализовать addon-owned migrations.

Операционный недостаток: обычный boot вызывает emit_if_stale() и может писать generated Python. Для dev это удобно; production release должен быть immutable: build в CI, committed/published runtime artifact, --check при boot и отказ при расхождении.

5.2. Модель и API

AngeeModel и AngeeDataModel дают timestamps, public sqids, actor-aware managers/QuerySets и REBAC. В наличии:

  • StateField, transitions, guards, locking и optimistic checks;
  • EncryptedField, ImplClassField, record references;
  • archive, audit/history/revision/hierarchy mixins;
  • canonical identity для MTI;
  • GraphQL schema composition через SchemaParts;
  • Hasura-like CRUD, filters, order, aggregate/grouping, nested editable lines и delete preview;
  • метаданные полей, capabilities, grouping/filter axes, record representation и editable fields;
  • public IDs вместо сырых PK;
  • subscriptions с read gate/redaction и публикацией после commit.

Это гораздо больше, чем «голый Django». Для генерируемой административной/business UI основа уже реальна.

5.3. Безопасность

Связка actor-aware QuerySet + django-zed-rebac + ZED schema даёт полезный declarative security target. Есть object relations, permission checks, filtered reads, field-level gates и audited system_context(reason=...).

Однако компилятор не должен считать, что любая Odoo record rule переводится в REBAC. Правила вида «дата в диапазоне», «company входит в allowed companies», «поле равно значению из context», сложные OR по attributes требуют дополнительного predicate/scope слоя либо материализованных relationship facts.

5.4. Базовые addons

На уровне Angee уже присутствуют базовые контуры, которые не стоит повторно писать в arpee:

  • IAM, users/roles/OIDC;
  • parties/people/organizations/addresses;
  • storage/files;
  • resources и tiered seed data;
  • integrations/connectors;
  • messaging: thread, attachment, follower, activity, message и tracking/subscriptions;
  • workflows/tasks/scheduling;
  • money/currencies, sequence, UoM, tags;
  • platform/operator;
  • knowledge, agents и внешние integration addons.

Интерфейс уже содержит Form, List, Board, Calendar, Dashboard, Gallery, Graph, Timeline и Tree; есть editable lines, facets, relation widgets, action forms, DnD board и chatter. Некоторые arpee-спецификации по-прежнему помечают Graph/Calendar/Board DnD/Hierarchy/Money/Sequence/UoM/Tags как gaps — это устаревшие сведения, а не фактический status текущего Angee.

5.5. Что критически отсутствует

По текущему исходному коду не найдено универсальных механизмов для:

  • dependency-tracked stored derived fields;
  • серверного generic onchange/prefill;
  • generic copy/duplicate policy;
  • переводимых значений полей;
  • report/PDF engine;
  • pivot grid/spreadsheet.

Есть точечные аналоги и строительные блоки, но не системный контракт, пригодный для генератора.


6. Фактический состав arpee-angee

Репозиторий содержит 11 ERP addons:

Addon Основной срез
arp.base Company, company scopes/roles, donors для tags/groups/threads
arp.accounting chart accounts, journals, taxes, terms, entries/items, invoices, payments, reconciliation
arp.analytic analytic plans/accounts/lines/distribution
arp.calendar events и attendees
arp.crm stages, teams, leads, reasons, quotation bridge
arp.discuss rooms поверх messaging threads
arp.inventory locations, warehouses, operation types, transfers, moves, move lines, stock levels
arp.products categories, templates/variants, attributes/values, pricelists, suppliers
arp.purchase purchase orders/lines, receipt и bill bridges
arp.rating generic target ratings
arp.sales sales orders/lines, invoice and delivery bridges

Спецификации описывают существенно более широкую карту: около 175 Odoo-family capabilities в 18 доменах, сведённых примерно к 26 целевым addon-владельцам, волнам и вертикальным slices. Это хороший стратегический inventory, но текущий исполняемый код — лишь первая часть этой карты.

6.1. Что сделано удачно

  1. Не копируется структура res.* один в один. Company — ERP tenant/root в arp.base, внешние организации остаются parties.Organization. Это лучше механической транслитерации Odoo.
  2. Angee-native гранулярность. ERP-домены используют base addons и donors, а не создают параллельные IAM, messaging, files, tags, currency и UoM.
  3. Серьёзная multi-company дисциплина. Company scope и role ladder описаны явно, проверяются на write/read paths и покрыты тестами.
  4. Явные доменные verbs и transitions. Confirm/post/reconcile/receive/invoice flows выражены как осмысленные операции, а не как случайные изменения status.
  5. Хорошее использование same-row extensions. Cross-domain bridges — аналитика у проводки, invoice source, quotation/delivery/receipt donors — не плодят лишние сущности.
  6. Есть PostgreSQL-only concurrency tests. По коду видно 11 таких сценариев с locks/atomicity/idempotency, что для ERP важнее большого числа поверхностных unit tests.
  7. Тесты проверяют invariants, а не только CRUD. Multi-company isolation, повторный вызов команды, переходы, сверка, складские эффекты и concurrent execution рассматриваются как контракт.
  8. UI использует общие resource primitives. Это даёт шанс позже заменить ручные страницы генерируемыми декларациями.
  9. Manifests, resources и permission ownership в целом аккуратны. Видна сознательная архитектурная дисциплина, а не быстрый prototype.
  10. Clean-room specification ценна сама по себе. Она формулирует бизнес-возможности без необходимости тащить внутренности Odoo в новый runtime.

6.2. Что сделано неудачно или не масштабируется

Это ручной ERP, а не migration platform

Ни один слой не принимает Odoo addon как input. Модели, GraphQL resources, grants, resources и TSX pages написаны вручную. Нет parser, normalized IR, semantic analyzer, diagnostics, provenance, code generation, overlay protocol, differential runner или data migration pipeline. Поэтому текущая скорость переноса примерно линейна количеству доменной логики и не приближает требование «указать список addons».

Derived values поддерживаются вручную и уже образуют корректностную дыру

Например, SalesOrderLine.save() сначала рассчитывает amount_subtotal/amount_total, сохраняет строку и вызывает order.recompute_totals(). Но M2M taxes присоединяются GraphQL mutation layer после save(). Комментарий в after_resource_load прямо признаёт: при seed загрузке первый расчёт не видит M2M taxes, поэтому требуется отдельный повторный пересчёт.

Generic nested-line writer Angee (angee/graphql/data/hasura.py, _apply_line_diff) обновляет/создаёт строки через mutation resolvers, а удалённые строки удаляет массовым QuerySet .delete(). После установки M2M и после удаления нет общего parent-recompute hook. Следовательно возможны stale totals:

  • изменить только taxes строки — сохранённые суммы могут отражать старый набор налогов;
  • удалить строку nested diff — Line.save() не вызовется и итог parent может остаться прежним;
  • bulk update/delete, cascade и другие обходные write paths создают тот же класс ошибок;
  • аналогичный ручной pattern повторяется в purchase/accounting draft totals.

Это не локальный баг sales. Это доказательство отсутствия framework-level dependency/recompute engine.

0 смешивается с «не задано»

При создании sales line условие if self.pk is None and not self.price_unit заменяет нулевую цену вычисленной. Бесплатную позицию предлагается сначала создать с default, затем отредактировать. Значит write API не различает omitted, explicit null и explicit zero. Для генератора нужен трёхсоставный input-state и отдельный prefill phase.

Часть ERP-семантики сознательно урезана

Например, cross-currency pricing отклоняется validation error, а foreign-currency accounting ограничена. Для первого vertical slice это допустимо, но это нельзя называть функциональным паритетом Odoo. Такие ограничения должны жить в machine-readable capability manifest и diagnostics, а не только в комментариях/тестах.

Elevation разбросан по бизнес-коду

В production-коде arpee найдено 53 участка with ... system_context. Часто перед elevation действительно выполняется authored preflight, но convention трудно проверять при росте проекта. Нужна единая command boundary: authorize parent operation → lock → scoped capability elevation → execute effects → audit. Raw system_context в addon-коде стоит ограничить lint rule.

Слишком тесные Python imports между siblings

Много прямых from arp.accounting.models ... и аналогичных schema/model imports. Это противоречит заявленному правилу связываться через labels/apps.get_model и затрудняет автономное подключение addon-наборов. Решение не обязательно в полном запрете imports: лучше выделить стабильные contracts/services/events и явно разрешённые compile-time dependencies.

Слишком крупные файлы

Только некоторые примеры: accounting models — около 1969 строк, inventory — 1274, purchase — 1153, products — 1052; крупные schema-файлы достигают примерно 600–700 строк. Разделение должно идти не на микромодули, а внутри addon: models/, commands/, services/, policies/, projections/, schema/.

Seeds иногда становятся imperatively programmed

after_resource_load используется для восстановления производных фактов и сложной провизии. Hooks неизбежны для действительно нелинейных операций, но обычные references, update/noupdate, M2M и post-load recompute должны быть declarative или частью общего engine. Иначе data migration становится непредсказуемой.

Репозиторий не воспроизводим как самостоятельный release

README утверждает наличие корневого angee.yaml и запуск angee dev; фактически angee.yaml/.angee в корне и родительском scope не найден. Python-проект не фиксирует framework dependency обычным package mechanism, а JS workspace напрямую смотрит в sibling ../angee-django. Это рабочая dev-композиция конкретной машины, не production distribution.

Документация отстала от кода

Примеры:

  • specs говорят о Procrastinate, а текущий Angee использует Celery/Redis/beat;
  • старая taxonomy помещает Company в IAM, фактический код — в arp.base;
  • sales manifest отмечает MTI cross-reference как gap, хотя текущие Angee resource tests поддерживают ancestor refs;
  • runtime gaps продолжают перечислять уже реализованные Graph/Calendar/Board DnD/Hierarchy и некоторые base addons.

Статус возможностей должен генерироваться из source declarations и executable contract tests. Ручной список быстро становится ложным.


7. Вердикт по arpee-angee: развивать или переписать

Развивать — но изменить объект разработки

Сохранять и развивать стоит:

  • доменную модель и vocabulary;
  • модели Company/scope и role ladder;
  • transitions и явные бизнес-команды;
  • concurrency/locking/idempotency tests;
  • multi-company test corpus;
  • спецификации как capability map;
  • удачные donors/bridges между доменами;
  • UI scenarios как golden examples.

Перестать вручную масштабировать стоит:

  • однотипные GraphQL resource declarations;
  • однотипные list/form/board TSX pages;
  • ручные stored totals и каскадные recompute;
  • ad-hoc defaults/onchange внутри save();
  • прямое elevation в каждом verb;
  • дублирующиеся status tables в Markdown;
  • imperative seed hooks для того, что выражается данными;
  • новые домены до появления общей semantic spine.

Практическая стратегия ветвления

flowchart TD
  A["Текущий arpee"] --> G["golden domain corpus"]
  A --> R["точечные исправления correctness"]
  C["Новый odoo2angee"] --> I["typed IR"]
  I --> GEN["generated addon layer"]
  GEN --> O["manual overlay/services"]
  G --> D["differential/contract tests"]
  D --> GEN
  R --> O
  O --> P["постепенно регенерированный arpee release"]
Loading

Начинать совершенно новый ERP repository имеет смысл только если текущая product taxonomy признана неправильной. По коду оснований для этого нет. Начинать новый compiler package — обязательно, потому что в arpee для него сейчас нет архитектурной границы.


8. Правильная постановка цели: не «исполнять Odoo», а компилировать контракт

Есть соблазн реализовать в Angee models.Model, recordsets, env, with_context, domain tuples и decorators так, чтобы Python-код Odoo «почти запускался». Это плохой основной путь:

  • каждый новый поддержанный idiom затягивает cache, recompute, security и transaction semantics;
  • ошибки проявляются не при сборке, а в редких runtime combinations;
  • получится второй Odoo runtime внутри Django, но без многолетней зрелости первого;
  • Angee потеряет свои сильные свойства: статичность, явность, Django ecosystem и typed GraphQL.

Целевой контракт должен быть таким:

Для заданного зафиксированного набора Odoo-addons инструмент строит dependency/capability report, извлекает декларативную и распознанную поведенческую семантику в typed IR, генерирует детерминированные Angee addons, останавливается с точными diagnostics на неподдерживаемых конструкциях и оставляет стабильные extension points для ручного кода.

«Аналогичный функционал» должен проверяться business scenarios и invariants, а не совпадением Python API, table names или HTML.


9. Уровни совместимости

У каждого addon и каждой конструкции должен быть уровень; нельзя публиковать один общий процент.

Уровень Что обещает инструмент Типичный результат
L0 — Inventory manifest/dependency scan, models/views/security/data/controllers/assets inventory отчёт, graph, risks, effort и unsupported list
L1 — Structural models, fields, relations, constraints, simple security/data собираемый Angee addon + migrations/resources
L2 — Standard UI формы, списки, search, menus, actions, стандартные widgets/modifiers рабочий generic resource UI
L3 — Declarative behavior depends, related, constraints, default/onchange patterns, transitions рабочая семантика через shared runtime
L4 — Recognized code idioms whitelist Python AST patterns и известных Odoo APIs сгенерированные commands/services с provenance
L5 — Custom behavior произвольный Python, controllers, custom QWeb/Owl/JS, external integrations ручной overlay/adapter; scanner лишь описывает зависимость

Дополнительно каждой единице IR нужен status:

  • exact — семантика доказуемо сохраняется;
  • equivalent — другое устройство, тот же внешний business contract;
  • degraded — явное ограничение;
  • manual — требуется ручная реализация;
  • blocked — нет целевого runtime primitive;
  • ignored — сознательно вне scope.

Build должен падать, если production profile содержит blocked или неразрешённый manual, а не молча генерировать неполную систему.


10. Архитектура odoo2angee

10.1. Конвейер

flowchart LR
  S["Odoo addon sources"] --> P["safe parser / inventory"]
  DB["isolated Odoo introspection DB"] --> X["runtime facts exporter"]
  P --> N["normalizer"]
  X --> N
  N --> IR["versioned typed IR"]
  IR --> V["semantic validator"]
  V --> D["diagnostics + capability report"]
  V --> M["mapping/lowering"]
  M --> GM["Angee models/migrations"]
  M --> GS["security/resources/schema"]
  M --> GW["web/page declarations"]
  M --> GT["tests/data migration plan"]
  O["manual overlays"] --> B["Angee build"]
  GM --> B
  GS --> B
  GW --> B
  GT --> B
  B --> C["contract/differential verification"]
Loading

Статический parser и runtime introspection нужны одновременно.

  • Статический parser сохраняет provenance, AST методов, XML inheritance, comments/locations, manifest и конструкции, которые невозможно увидеть после сборки Registry.
  • Runtime exporter в изолированном disposable Odoo процессе раскрывает динамически собранные поля, финальную модель, selection values, inheritance contributions, external IDs и итоговые views.

Нельзя импортировать произвольные Odoo addons в процесс самого compiler: addon может выполнять Python при import/init и имеет доступ к окружению. Introspection должен работать в sandbox/container без secrets, с read-only source, временной PostgreSQL DB, ограниченной сетью и экспортировать только сериализованные facts.

10.2. Typed intermediate representation

IR должен быть публичным, versioned и сериализуемым. Минимальный состав:

PackageIR

  • technical name, human name, version, license;
  • depends/auto-install/external dependencies;
  • source root, commit, file hashes;
  • install/demo/update/uninstall hooks;
  • capabilities и required compatibility tier;
  • ownership и target addon mapping.

ModelIR

  • _name, _description, table, abstract/transient;
  • _inherit contributions с порядком и provenance;
  • _inherits delegation;
  • rec name/order/parent fields;
  • fields, constraints, methods, mixins;
  • security/tracking/translation/company semantics;
  • expected row lifecycle.

FieldIR

  • logical type, storage type, required/default/read-only/copy/index;
  • relation/comodel/inverse/relation table/ondelete;
  • compute/dependencies/store/inverse/search/related;
  • selection values/provider;
  • groups/tracking/translate/company-dependent;
  • monetary currency field, state conditions;
  • source location и confidence.

MethodIR

  • decorators and API shape;
  • effect classification: pure compute, validator, default, onchange, command, query, scheduler, integration;
  • read/write model-field sets;
  • calls to known Odoo APIs;
  • AST/control-flow subset;
  • transaction/elevation/side-effect needs;
  • lowering status and manual seam.

SecurityIR

  • groups and implied groups;
  • model ACLs;
  • record rules: global/group, operation flags, canonical predicate;
  • field group gates;
  • menu/action visibility;
  • known use of sudo/superuser.

ViewIR и ActionIR

  • view type, arch tree, inheritance chain и resolved arch;
  • field placements, groups/pages/notebooks;
  • labels/help/widgets/options;
  • modifiers, domains, contexts, attrs;
  • buttons/object actions;
  • search fields/filters/group-by;
  • window/client/server/report/url actions;
  • menu tree and bindings.

DataIR

  • external ID, model, values, refs;
  • noupdate/demo/tier;
  • eval/function constructs;
  • creation/update/delete ordering;
  • translation payloads;
  • provenance and checksum.

HttpIR, AssetsIR, MigrationIR, TestIR

  • routes/auth/csrf/methods/content type;
  • asset bundles and custom JS/Owl/QWeb dependencies;
  • pre/post/end migrations and upgrade utilities;
  • tagged scenarios, fixtures and observable business facts.

IR не должен содержать Django-specific code до lowering. Иначе scanner и migration planner будут жёстко связаны с одной версией Angee.

10.3. Provenance и overlays

Каждая сгенерированная декларация должна знать:

  • addon/file/line/XML external ID источника;
  • версию compiler и mapping rule;
  • hash исходного fragment;
  • уровень совместимости и diagnostics;
  • ручной overlay, если он заменяет/дополняет generated fragment.

Generated code не редактируется. Целевая структура addon:

addons/generated/<name>/
  addon.toml
  generated/
    models.py
    resources.yaml
    permissions.zed
    schema.py
    pages.json
    provenance.json
  overlays/
    models.py
    commands.py
    policies.py
    schema.py
    web/
  compatibility.yaml

При повторной генерации compiler меняет только generated/; overlay ссылается на стабильные logical IDs, а three-way compatibility report показывает удалённые и изменённые source contracts. Это принципиально лучше генерации в файлы, которые затем правятся руками.

10.4. Diagnostics как основной продукт

Первый полезный релиз может вообще не генерировать рабочую систему. Он должен для списка addons ответить:

  • полный транзитивный dependency set;
  • какие модели/поля/views/security/data будут перенесены exact/equivalent;
  • какие runtime primitives отсутствуют;
  • какие Python methods попали в известные patterns;
  • какие custom controllers/QWeb/Owl требуют ручной работы;
  • какие licenses и внешние зависимости обнаружены;
  • какие data volumes/attachments/translations надо мигрировать;
  • где есть cross-addon cycles и скрытые dynamic calls;
  • оценка effort по конкретным unsupported facts.

Этот scanner уже превращает неопределённый rewrite в управляемый migration backlog.


11. Отображение конструкций Odoo на Angee

11.1. Модели и наследование

Odoo Целевой mapping
новая _name модель Angee source model runtime=True
_inherit без _name same-row donor extends=..., runtime=False
_name + _inherit как specialization materialized child либо новая модель — решает analyzer по identity/table semantics
_inherits delegation явный OneToOne/ForeignKey + forwarding, реже MTI; не подменять автоматически без проверки delete/identity
AbstractModel abstract source/mixin
TransientModel новый ephemeral model primitive с TTL/cleanup/session ownership
_rec_name, _order representation/order metadata
_parent_store hierarchy mixin + materialized path/tree rebuild

Порядок _inherit contributions — часть семантики: последний override может вызвать super() и ожидать MRO Odoo. Composer должен иметь deterministic contribution order и конфликт-диагностику. Поля объединять проще, методы — только если распознан chain contract; произвольный MRO переводится вручную.

11.2. Recordsets и Environment

Не следует давать addons общий Odoo-like compatibility facade. Mapping должен быть смысловым:

Odoo idiom Angee-native форма
self.filtered/mapped/sorted QuerySet expression для DB-safe случая или explicit collection transform
browse(ids) scoped manager/query service по public/internal IDs
search(domain) canonical PredicateIR → QuerySet/policy compiler
ensure_one() command/query API, принимающий один aggregate ID
with_context() immutable typed CommandContext/QueryContext
sudo() scoped capability token/system executor с reason/audit
env.ref() external resource ledger resolver
env.company/companies explicit tenant scope в context и policy
env.cr запрещён в generated business code; выделенный migration/repository escape hatch

Можно написать небольшой compatibility library для ограниченного L4 AST lowering, но она должна быть внутренней деталью generated code и поддерживать только декларированный subset. Иначе она станет публичным API, который невозможно удалить.

11.3. Fields

Прямо отображаются basic scalar, date/datetime, binary/file, selection/state, M2O/FK, M2M, O2M. Дополнительные primitives нужны для:

  • translated values;
  • company-dependent property values;
  • computed/related store graph;
  • inverse/search functions;
  • reference/polymorphic relation с security-aware resolution;
  • selection providers, меняющихся от addon contributions;
  • Odoo copy behavior;
  • precision/rounding categories;
  • attachment-backed binary semantics;
  • HTML sanitation/translation;
  • sparse/property fields, если такие addons входят в scope.

Field metadata должна быть единственным источником для DB, GraphQL, UI, import/export, audit, translation и copy. Ручное повторение writable fields в schema и widgets в TSX разрушает возможность генерации.

11.4. x2many command protocol

Odoo commands create/update/delete/unlink/link/clear/set различают ownership и relation semantics. Текущий nested line diff Angee выражает «полный новый список» и удобен для owned lines, но не покрывает все случаи.

Нужен typed protocol, например:

  • create(values);
  • update(id, patch);
  • deleteOwned(id);
  • unlink(id);
  • link(id);
  • clear();
  • replace(ids).

Executor обязан:

  1. разрешить IDs под actor authority;
  2. проверить parent command permission;
  3. lock aggregate;
  4. применить changes в одной transaction;
  5. зарегистрировать dependency invalidations;
  6. выполнить constraints/recompute/tracking;
  7. сформировать outbox events после commit.

11.5. Domains

Нужен собственный canonical PredicateIR, а не непосредственная сборка Django Q из Odoo tuples. Он должен поддерживать:

  • prefix &, |, !;
  • scalar/null/text/membership/comparison operators;
  • relation traversal и collection quantifiers;
  • hierarchy child_of/parent_of;
  • date/datetime and timezone semantics;
  • dynamic operands: user, company, allowed companies, today/context;
  • field-to-field expressions в разрешённом subset;
  • normalization, type checking, null semantics;
  • разделение query predicate и authorization predicate.

Один IR затем компилируется в Django QuerySet, frontend filter descriptor, ZED relationship facts либо policy evaluator. Неподдерживаемый operator — build diagnostic, не silently false/true.


12. Главный недостающий компонент: write/recompute engine

12.1. Почему save() недостаточно

Django Model.save() не вызывается для QuerySet update(), bulk operations, M2M relation changes и некоторых cascade/delete paths. Даже когда вызывается, локальная модель не знает полного dependency graph installed addons. Поэтому ручные recompute_totals() из дочерних save() неизбежно пропускают paths и создают лишние N+1 пересчёты.

Odoo решает это через field dependency graph, cache invalidation и recompute queues. Angee не обязана копировать реализацию, но обязана дать эквивалентный инвариант.

12.2. Целевая модель

На build-time compiler/composer собирает graph:

sale.line.quantity ─┐
sale.line.price ────┼─> sale.line.subtotal ─┐
sale.line.discount ─┘                       ├─> sale.order.untaxed
sale.line.taxes[*] ───> sale.line.tax ─────┼─> sale.order.tax/total
                                           └─> invoiceable projections

У dependency edge есть:

  • source model/field/relation event;
  • target model/field;
  • traversal path от изменённой записи к targets;
  • compute function;
  • store/non-store;
  • batching key/aggregate root;
  • required lock и company scope;
  • context dependencies;
  • inverse/search support;
  • consistency mode: immediate, before-read, before-commit, asynchronous projection.

12.3. Два класса вычислений

  1. Database-generated/pure same-row. Простые deterministic expressions компилируются в PostgreSQL/Django GeneratedField или query annotation, если типы и migration lifecycle безопасны.
  2. Cross-row/cross-model derived facts. Transaction-local invalidation queue собирает затронутые aggregate IDs, topologically batches computes и записывает results до commit. Повторные invalidations coalesce.

Нельзя отправлять финансовые/складские consistency-critical recompute в обычный async task: после commit читатель увидит противоречивые данные. Async подходит для поискового индекса, thumbnails, external sync и аналитических projections, но не для суммы проведённой записи.

12.4. Обязательные write paths

Engine должен видеть:

  • instance create/update/delete;
  • QuerySet update/delete;
  • bulk_create/bulk_update;
  • M2M add/remove/clear/set;
  • cascade и relation reassignment;
  • data/resource loader;
  • migrations/import;
  • nested GraphQL mutations;
  • commands/services;
  • manual SQL через явно маркированный escape hatch.

Реалистичная политика: запретить unsafe bulk/queryset writes на моделях с managed derived dependencies либо заставить использовать CommandExecutor.bulk_*, который регистрирует invalidations. DB triggers можно применять точечно, но превращать всю доменную логику в скрытые triggers не стоит.

12.5. Семантика compute

Необходимы:

  • cycle detection на build;
  • topological ordering;
  • batch computes, а не вызов по записи;
  • depends_context в ограниченном typed виде;
  • related fields и dependency propagation через relations;
  • inverse command для editable computed fields;
  • search expression для non-stored fields;
  • compute under explicitly defined authority, обычно system read после authorizing the parent command;
  • rounding/currency/UoM context;
  • deterministic error/retry behavior;
  • instrumentation: queue size, recompute count/time, fan-out warnings.

12.6. Немедленные тесты для arpee

До появления engine следует добавить regression tests как минимум для:

  • изменение только M2M taxes у sales/purchase line обновляет line и parent totals;
  • удаление nested line обновляет parent totals;
  • replace/clear relation не оставляет stale values;
  • bulk delete/update либо корректно recompute, либо явно запрещены;
  • concurrent edits одной aggregate дают serialization/conflict, а не lost total;
  • explicit zero price не заменяется default;
  • cross-company relation нельзя создать ни одним nested/bulk path.

13. Command/unit-of-work как единая граница

Angee нужен не ещё один fat model convention, а стандартный application command pipeline.

sequenceDiagram
  participant UI as GraphQL/UI/import/task
  participant CE as CommandExecutor
  participant P as Policy
  participant DB as Aggregate/DB
  participant R as Recompute
  participant O as Outbox

  UI->>CE: typed command + actor/context + idempotency key
  CE->>P: authorize intent and referenced objects
  CE->>DB: atomic + lock roots
  CE->>DB: defaults/prefill then mutations
  CE->>DB: constraints
  CE->>R: flush invalidations topologically
  CE->>DB: final invariants + audit diff
  CE->>O: append durable events
  CE-->>UI: result / conflict / warnings
  DB-->>O: commit
  O-->>UI: subscriptions/integrations/tasks after commit
Loading

CommandContext должен явно содержать actor, tenant/company scope, locale/timezone, request/correlation ID, idempotency key и разрешённые capabilities. Произвольный словарь context допускается только как namespaced addon data с schema, а не глобальный канал изменения поведения.

Преимущества:

  • один путь для GraphQL, import, scheduler, tests и migration;
  • одно место для preflight/elevation;
  • audit trail объясняет не только изменение строки, но и бизнес-команду;
  • retry и optimistic conflict имеют единое поведение;
  • generated behavior и manual overlay комбинируются предсказуемо.

Нужны:

  • PostgreSQL retry policy для deadlock/serialization с ограничением и jitter;
  • durable outbox в той же transaction;
  • idempotency ledger для внешних и долгих команд;
  • post-commit hooks без ложной доставки до commit;
  • запрет network I/O внутри DB transaction, кроме специально изолированных adapters;
  • command-level metrics/traces.

system_context остаётся внутренним инструментом executor. Бизнес-код запрашивает capability вроде write_children_after_parent_authorized, а не отключает security целиком.


14. Defaults, onchange и интерактивное редактирование

14.1. Один серверный prefill protocol

Odoo-клиент опирается на default_get и onchange: ещё не сохранённая форма отправляется на сервер, сервер меняет virtual record и может вернуть warning/domain. В Angee это нужно воспроизвести функционально, не API-совместимо.

Предлагаемый GraphQL/application contract:

prefill(resource, mode=create|edit, suppliedFields, changedFields, draft, context)
  -> patch
  -> fieldDomains
  -> warnings/confirmations
  -> readonly/required modifiers
  -> dependency token/version

Ключевые правила:

  • отличать omitted от null, 0, false и пустой строки;
  • server-authoritative default никогда не зависит от того, что случайно сделал widget;
  • repeated prefill идемпотентен;
  • response объясняет provenance изменения;
  • save повторно проверяет значения и не доверяет prefill;
  • prefill не выполняет durable side effects;
  • expensive dependencies кэшируются по typed input.

14.2. Lowering Odoo onchange

Можно автоматически переносить:

  • присвоения полей из pure expressions;
  • lookups/selection по поддержанным relations;
  • warnings и domains через новый result contract;
  • known helper calls, уже сниженные в IR.

Нельзя автоматически обещать корректность для:

  • arbitrary DB writes внутри onchange;
  • calls в внешние системы;
  • сложной recordset mutation;
  • динамического Python/eval;
  • поведения, зависящего от undocumented JS widget state.

Такие методы получают manual diagnostic и skeleton overlay с исходным behavior contract.


15. Security compiler

15.1. ACL

Odoo model ACL по группам аддитивны. Это хорошо компилируется в Angee permissions: create/read/write/delete capabilities, role/group facts и implied role closure. Надо сохранить default-deny и различать read конкретного object и create на collection/tenant.

15.2. Record rules

Семантика Odoo требует осторожности:

  • все applicable global rules соединяются AND;
  • applicable group rules обычно соединяются OR, затем результат пересекается с globals;
  • правила различаются по операциям;
  • domain может ссылаться на user/company/context и attributes самой записи.

ZED/REBAC отлично выражает relationship facts: member, owner, manager, follower, ancestor. Но attribute predicates не следует искусственно превращать в миллионы relationship tuples. Целевой policy plan:

ACL capability
AND global predicate scopes
AND (group predicate scope 1 OR group predicate scope 2 ...)
AND field gates
AND explicit tenant boundary

Compiler выбирает:

  • relationship часть → ZED;
  • database attribute часть → QuerySet predicate;
  • request/context часть → typed policy evaluator;
  • слишком динамическое правило → manual policy.

Один и тот же plan должен ограничивать list QuerySet, object fetch, mutation target, relation ID resolution, aggregate/group queries, subscription delivery и export. Проверить только mutation entrypoint недостаточно.

15.3. Multi-company

Текущее решение arpee с Company в arp.base разумно. Переносить Company в generic IAM сейчас не нужно: company — ERP/legal/operational entity, а не просто authorization tenant. Если Angee понадобятся multi-tenant приложения вне ERP, framework должен получить абстрактный Scope/Tenant contract, а arpee Company реализует его.

Нужно добавить общий CompanyConsistency contract:

  • декларация company_field и allowed cross-company relations;
  • автоматическая валидация FK/M2M/nested lines;
  • propagation/default company;
  • scoped unique constraints;
  • query/prefill/import enforcement;
  • explicit shared/global records;
  • тестовый matrix, который генерируется для каждой company-bound модели.

Отдельно нужен аналог company-dependent properties: значение поля зависит от company, но identity записи общая. Это не всегда обычная колонка и не должно моделироваться копиями записи.

15.4. Field gates и elevation

Field access metadata должна влиять на read projection, GraphQL input, UI visibility/editability, import/export и audit redaction. Нельзя только скрыть поле в UI.

Для elevation:

  • запретить raw use вне framework/annotated service modules;
  • reason — enum/logical command, не свободная строка;
  • capability ограничивает модели/операции/scope;
  • actor сохраняется для audit;
  • relation inputs разрешаются до elevation;
  • каждый bypass виден в trace и security tests.

16. Data resources, external IDs и upgrade lifecycle

16.1. External resource ledger

Angee resources — хорошая стартовая точка, но для миграции Odoo ledger должен хранить:

  • namespace/addon + external ID;
  • target logical model и record public/internal ID;
  • source checksum и last applied compiler/release;
  • noupdate state;
  • created-by-addon ownership;
  • renamed/moved aliases;
  • delete/uninstall policy;
  • original source DB ID для migration reconciliation;
  • references и dependency ordering.

16.2. Update и uninstall

Нужно определить точную семантику:

  • install создаёт отсутствующие owned resources;
  • update изменяет managed fields, если не noupdate и нет approved local override;
  • пользовательские данные не уничтожаются из-за исчезновения XML record;
  • rename external ID сохраняет identity;
  • uninstall удаляет только безопасно owned configuration, либо архивирует/оставляет business records;
  • dangling references диагностируются до применения;
  • operation transactional и resumable.

Python after_resource_load должен быть последним escape hatch. Повторный запуск обязан быть идемпотентен; side effects идут через commands.

16.3. Миграции схемы и данных

Release artifact должен включать:

  • зафиксированный addon graph;
  • сгенерированный runtime source;
  • Django migrations;
  • manual data migrations;
  • resource ledger changes;
  • compatibility report;
  • rollback/forward recovery notes;
  • compiler/IR versions и source hashes.

Production boot только проверяет artifact. Генерация и makemigrations происходят в CI/development, а не на сервере.


17. UI compiler

17.1. Почему текущих React-компонентов недостаточно

Наличие Form/List/Board/Calendar/Graph и других widgets означает, что rendering primitives существуют. Но Odoo addon приносит views как данные с наследованием, modifiers, actions и context. Пока arpee вручную пишет TSX, структура одной модели повторяется в backend resource metadata и frontend page code.

Нужен PageIR, который renderer получает как данные:

  • resource, title, record representation;
  • list columns, ordering, decoration, inline edit;
  • form groups/tabs/lines/chatter;
  • board lanes/cards/drag transition;
  • search fields, saved filters, group-by;
  • graph/calendar/timeline/tree settings;
  • toolbar/object/bulk actions;
  • relation widgets and create/edit policies;
  • conditional visibility/read-only/required;
  • help/empty states;
  • menu/action bindings;
  • responsive variants и extension slots.

Ручной React нужен для уникального product experience, но стандартный business CRUD должен быть декларативным.

17.2. XML view inheritance

Compiler должен разобрать исходную и наследующие XML views, применить XPath/position операции на build-time и выпустить normalized tree. Нужны diagnostics для:

  • XPath не нашёл target;
  • несколько ambiguous targets;
  • конфликтующие replacements/attributes;
  • неизвестный widget/modifier;
  • dependency order изменил результат;
  • unsupported QWeb expression.

Полезно хранить и финальный resolved view, и contribution map. Это позволит объяснить пользователю, какой addon добавил поле/кнопку.

17.3. Modifiers и expressions

Не переносить Python-like expressions в browser как eval. Разобрать их в typed expression IR и компилировать в:

  • безопасный frontend evaluator для UI-only state;
  • server policy/validation для security/consistency;
  • dependency list для prefill.

UI modifier никогда не является security control.

17.4. Actions

Стандартные mappings:

  • window action → resource page route + default view/filter/context;
  • object button → typed command/action form;
  • report action → report service;
  • server action → workflow/command только для whitelist operations;
  • URL/client action → explicit adapter/manual frontend;
  • menus → navigation manifest с permission gates.

17.5. Reports, pivot и spreadsheet

Это отдельные продукты, отсутствующие в достаточном виде:

  • report templates с HTML/CSS/PDF rendering, locale, attachments, batching и audit;
  • mail template rendering с safe typed expressions;
  • pivot: dimensions/measures, subtotal, drill-down, saved layouts/export;
  • spreadsheet integration только после стабильного aggregate/query contract.

QWeb нельзя полностью механически превратить в React. Для стандартных reports можно строить ReportIR; custom directives/templates остаются manual.


18. Сообщения, задания и интеграции

18.1. Chatter mapping

Angee messaging уже имеет подходящий target: ThreadedModelMixin, Thread, ThreadAttachment, ThreadFollower, ThreadActivity, Message, tracking и subscriptions. Compiler может переносить:

  • mail.thread → threaded donor/mixin;
  • tracked fields → audit/tracking metadata;
  • followers/subtypes → notification subscription rules;
  • activities → typed activity/task;
  • attachments → storage links.

Нужно доделать:

  • mail templates;
  • outbound delivery attempts/retries/bounces;
  • inbound aliases/routing;
  • recipient computation и notification preferences;
  • safe rendering/translation;
  • message subtype semantics и access to historical messages;
  • idempotent external message ingestion.

18.2. Cron и background work

Текущий Angee использует Celery/Redis/beat, несмотря на старые документы arpee о Procrastinate. Это нормальный target. Compiler mapping:

  • ir.cron → registered scheduled command;
  • delayed job → durable task with idempotency key;
  • bus notification → after-commit channel event;
  • external sync → outbox-triggered integration workflow.

Задание обязано повторно установить actor/system capability и company scope, а не наследовать несуществующий HTTP context. Scheduler definition и operational state нужно разделить: definition versioned в addon, last run/next run/failure state — в DB.

18.3. Controllers и integrations

Стандартные JSON endpoints можно отображать на GraphQL/REST adapters. Custom webhooks, portals, payment callbacks и streaming endpoints требуют ручного adapter, но scanner должен извлечь route, auth, csrf, methods, request/response shape и вызываемые models.

Network side effects должны идти через ports/adapters и outbox. Это позволит тестировать бизнес-команду без реальной сети и гарантировать delivery after commit.


19. Перенос данных из существующей Odoo

Генерация кода и миграция production data — два разных конвейера. Даже идеальный converter моделей не переносит историю, external IDs, attachments, audit и незавершённые workflows.

19.1. Extractor

Extractor работает против read-only snapshot/replica Odoo DB и filestore. Он экспортирует:

  • module/version/registry fingerprint;
  • компании, пользователи, группы и membership;
  • records пакетами в dependency order;
  • Odoo DB IDs плюс external IDs;
  • translations/properties;
  • attachments и content hashes;
  • chatter/messages/activities, если в scope;
  • sequence state;
  • active/archive/deleted markers;
  • source timestamps и audit provenance;
  • M2M relations отдельно от rows при необходимости.

Вызов model.export_data() не должен быть единственным механизмом: он проходит через текущую Odoo semantics и может быть медленным/неполным. Для известных core structures допустим DB-aware exporter, но он обязан сверяться с Registry metadata и не делать предположений только по table names.

19.2. Transform

Mapping data использует logical IDs из IR, а не Python names generated code. Нужны:

  • type/precision/timezone/locale conversions;
  • partner/company decomposition в новую taxonomy;
  • merged/split model mappings;
  • status/state mapping;
  • currency/UoM rounding normalization;
  • relation fixups;
  • field translation mapping;
  • attachment deduplication;
  • custom per-addon transform overlays;
  • reject/quarantine channel для records, которые нельзя безопасно преобразовать.

19.3. Load

Loader должен быть:

  • resumable по checkpoint;
  • idempotent;
  • batch-oriented;
  • aware of constraints/recompute mode;
  • capable of deferred relation linking;
  • tenant/company scoped;
  • проверяемым через counts, checksums и business balances;
  • способным делать dry-run и validation-only;
  • документирующим every dropped/degraded value.

External ID ledger связывает source database identity с target record. Повторный прогон обновляет ту же запись, а не создаёт дубль.

19.4. Reconciliation

После загрузки недостаточно сравнить число строк. Нужны business facts:

  • trial balance по company/currency/date;
  • дебет = кредит и reconciliation остатки;
  • stock on hand/reserved/valuation по product/location;
  • sales/purchase totals и invoice/delivery relations;
  • open receivables/payables;
  • sequence continuity;
  • followers/messages/attachments counts и sampled hashes;
  • permissions matrix для репрезентативных actors;
  • orphan references и cross-company violations.

Для cutover потребуется delta strategy: freeze window либо CDC/domain-specific incremental sync. Это нельзя обещать как generic first release.


20. Проверка эквивалентности

20.1. Differential harness

Для каждого поддержанного scenario один declarative test запускается в двух adapters:

Given company/user/products/taxes/stock
When create quotation → confirm → deliver → invoice → post → pay
Then compare business facts

Сравнивать следует:

  • states и разрешённые следующие commands;
  • денежные/количественные итоги с rounding;
  • созданные business relations;
  • ledger/stock effects;
  • access decisions;
  • messages/activities/events;
  • errors/warnings для invalid paths.

Не сравниваются сырые IDs, SQL tables, порядок незначимых queries, GraphQL против RPC или пиксельное совпадение UI.

20.2. Виды тестов

  • IR parser golden tests;
  • source-to-IR provenance snapshots;
  • generator snapshots с deterministic rebuild;
  • composer/conflict tests;
  • framework contract tests для every field/relation/write path;
  • policy matrix tests;
  • domain predicate conformance;
  • recompute graph property tests;
  • concurrency/deadlock/idempotency tests на PostgreSQL;
  • differential business scenarios;
  • migration fixture snapshots и reconciliation;
  • browser tests стандартных generated pages;
  • performance budgets/query counts;
  • upgrade N→N+1 tests с реальными data fixtures.

20.3. Роль arpee

Текущие 11 addons — идеальный golden corpus, потому что уже существует ручная Angee-native интерпретация. Для каждой capability можно сравнивать:

  1. поведение Odoo;
  2. существующий ручной arpee;
  3. generated addon + overlay.

Если generated вариант не проходит доменные tests arpee, compiler mapping недостаточен. Если arpee отличается от Odoo сознательно, это фиксируется как equivalent или degraded, а не замазывается.


21. Приоритет недостающих возможностей Angee

P0 — блокирует саму migration platform

  1. Versioned IR, scanner и diagnostics. Без них нет измеримого входа и результата.
  2. Derived/recompute engine. Без него финансовые и складские invariants ненадёжны.
  3. Command/unit-of-work pipeline. Один контракт authorization → lock → mutate → validate → recompute → audit → outbox.
  4. Generic server prefill/onchange. Без него стандартные Odoo forms нельзя воспроизводимо генерировать.
  5. Declarative Page/View/Action/Menu IR и renderer. Иначе frontend перенос остаётся ручным.
  6. Security policy compiler. ACL, record rules, field gates, tenant scope через единый plan.
  7. Полный resource ledger. External IDs, update/noupdate, rename, uninstall, references.
  8. Immutable release artifacts. Generated runtime/migrations/resources собираются заранее и проверяются при boot.
  9. Explicit transaction/retry/outbox/idempotency primitives. Нужны до integrations и масштабирования workflows.
  10. Capability manifest из кода/tests. Устраняет устаревшие hand-written gap ledgers.

P1 — нужен для широкого back-office паритета

  • generic copy/duplicate с field copy и per-model hooks;
  • warning/confirmation protocol;
  • translated field values и import PO/data translations;
  • company-dependent property fields;
  • report/PDF service;
  • mail templates, inbound aliases, delivery tracking;
  • pivot analysis;
  • import/export UI с preview/errors/external IDs;
  • visual predicate/domain builder и saved filters;
  • public/portal/share-token security surfaces;
  • transient/wizard models и multi-step action forms;
  • attachment-backed binary and document lifecycle;
  • generalized precision/rounding categories;
  • standard server actions/workflow subset.

P2 — расширяет продуктовые семейства, но не должно задерживать compiler spine

  • website/CMS/e-commerce theme migration;
  • payment providers;
  • POS/offline synchronization;
  • barcode/mobile flows;
  • IoT/hardware;
  • spreadsheet engine;
  • e-sign;
  • marketing automation/social;
  • localization framework и затем отдельные l10n_* packs;
  • advanced payroll/manufacturing/field service отраслевые primitives.

P2 — не «маловажно». Это означает, что эти возможности целесообразнее строить как отдельные domain/product tracks после стабилизации P0.


22. Что конкретно изменить в Angee

В core/composer

  • Ввести extension metadata и stable logical IDs, пригодные для generated/manual layers.
  • Добавить compiler contribution hooks, не позволяя произвольную runtime registration.
  • Формализовать ordering/conflict resolution same-row donors, особенно methods/metadata.
  • Сделать production mode: angee build, angee build --check, отсутствие boot-time writes.
  • Версионировать generated artifact format и migration ownership.
  • Добавить ephemeral/transient model primitive.
  • Собирать dependency graph derived fields и UI/policy contributions.

В base model/application layer

  • Реализовать CommandExecutor, typed contexts, scoped elevation.
  • Реализовать recompute/invalidation queue и безопасные bulk writes.
  • Ввести omitted/null/value input semantics.
  • Стандартизировать defaults/prefill/onchange.
  • Ввести copy/duplicate policy.
  • Дать company scope/property/consistency contracts.
  • Централизовать audit diff, tracking и outbox.
  • Определить concurrency policy: optimistic version + pessimistic lock where needed.

В GraphQL

  • Направить все writes через command pipeline, а не прямые resolver save().
  • Расширить nested mutations до typed relation commands.
  • Вызывать recompute после M2M/delete и до response.
  • Применять один compiled policy к list/object/aggregate/subscription/relation resolution.
  • Добавить prefill endpoint и warnings/confirmations.
  • Генерировать schema/resource declarations из IR и model metadata.
  • Отдавать compatibility/provenance metadata для admin/debug UI.

В web

  • Ввести JSON/typed PageIR и generic renderers.
  • Реализовать actions/menus/view inheritance output.
  • Генерировать стандартные pages; ручные компоненты подключать через named slots.
  • Добавить wizard/action form runner.
  • Добавить report/pivot/import-export surfaces.
  • Не исполнять Odoo/Python expressions; использовать typed expression evaluator.

В resources/upgrades

  • Расширить ledger до external-ID lifecycle.
  • Уменьшить потребность в after_resource_load.
  • Ввести dry-run/plan/apply/reconcile и migration checkpoints.
  • Связать schema migrations, resource updates и compiler compatibility в один release plan.

В developer tooling

  • odoo2angee scan, plan, generate, verify, migrate-data, reconcile;
  • source-location diagnostics и HTML/JSON report;
  • IDE-friendly generated type stubs;
  • lint rules против raw system_context, unmanaged bulk writes и generated edits;
  • query/recompute fan-out budgets;
  • capability manifest и stale-doc checker.

23. Что изменить в arpee-angee немедленно

Correctness first

  1. Добавить regression tests из §12.6.
  2. Исправить totals так, чтобы M2M tax changes и nested deletions регистрировали parent invalidation. Временное решение — явный aggregate command; signals без transaction aggregation будут лишь промежуточным компромиссом.
  3. Различать omitted price и explicit zero.
  4. Провести audit всех stored totals/status projections и составить dependency table.
  5. Провести audit всех QuerySet/bulk/delete paths на обход save()/policy/recompute.
  6. Генерировать cross-company consistency tests для всех relations.

Архитектурные границы

  1. Перенести business verbs из крупных models.py в commands/services, оставив модели носителями invariants и query helpers.
  2. Создать единый arpee command executor adapter до появления framework-level реализации.
  3. Обернуть каждый elevation в named capability; запретить новые raw system_context lint rule.
  4. Выделить cross-addon public contracts/events вместо импорта внутренних classes/schemas.
  5. Разделить крупные файлы по responsibility, не меняя addon taxonomy.
  6. Сделать status/capability manifest machine-readable и сверять с тестами/Angee version.

Reproducibility

  1. Восстановить или исправить README относительно отсутствующего angee.yaml.
  2. Зафиксировать совместимую версию/commit Angee обычным dependency/release mechanism.
  3. Создать production-shaped stack, где runtime/, migrations и compatibility report являются release artifacts.
  4. Добавить clean checkout build test без неявного sibling state.
  5. Явно указать PostgreSQL как обязательный production/test backend для ERP semantics.

Остановить до появления compiler

  • ручное создание новых однотипных CRUD schemas/pages;
  • перенос следующих больших доменов только ради числа addons;
  • новые hand-maintained aggregate totals;
  • новые Python seed hooks без design review;
  • promises о паритете без capability status.

24. Что убрать или сознательно не строить

  • Не строить полный Odoo runtime compatibility layer внутри Angee.
  • Не обещать запуск исходного addon Python без преобразования.
  • Не сохранять Odoo model taxonomy только ради узнаваемости.
  • Не копировать XML/QWeb/Owl буквально там, где Angee имеет собственный UI contract.
  • Не давать compiler молча пропускать unsupported behavior.
  • Не поддерживать один «процент миграции» без разреза по моделям, UI, security, behavior, data и tests.
  • Не редактировать generated code руками.
  • Не дублировать метаданные fields/actions в model, GraphQL и TSX.
  • Не использовать raw system elevation как обычный service pattern.
  • Не считать save() единственной write boundary.
  • Не вычислять critical derived facts asynchronously.
  • Не писать runtime artifacts на production boot.
  • Не держать обычные seed relations/recompute в imperatively coded hooks.
  • Не продолжать ручной roadmap по устаревшим Markdown gap tables.
  • Не пытаться поддержать все community/custom addons в первой версии; нужен declared compatibility profile.

25. Пошаговая программа работ

Этап 0. Зафиксировать продуктовый контракт — 4–6 недель

Результат:

  • выбрать поддерживаемые Odoo 19 editions/addon repositories/licenses;
  • выбрать первые 15–30 addons и исключения;
  • утвердить levels L0–L5 и statuses;
  • определить functional equivalence criteria;
  • решить deployment model: один фиксированный addon set на stack/release;
  • legal review clean-room против source translation;
  • зафиксировать Angee baseline и PostgreSQL versions.

Gate: два независимых инженера одинаково классифицируют sample constructions; unsupported behavior не замалчивается.

Этап 1. Scanner и capability report — 2–3 месяца

Результат:

  • manifest/Python/XML/CSV/security/assets inventory;
  • dependency graph;
  • source provenance;
  • isolated Odoo registry exporter;
  • первая IR schema;
  • HTML/JSON diagnostics;
  • arpee spec status, автоматически сверенный с текущим Angee.

Gate: для golden addon set scanner объясняет не менее 95% source declarations; оставшиеся 5% перечислены поимённо, а не потеряны.

Этап 2. Structural generator L1 — 3–6 месяцев

Результат:

  • models/basic fields/relations/constraints;
  • _inherit donors и простой _inherits;
  • Django migrations;
  • simple resources/external IDs;
  • ACL/groups;
  • generated GraphQL resources;
  • deterministic regeneration/overlay protocol.

Gate: clean build дважды даёт byte-identical output; schema/migration tests зелёные; ручные overlays переживают regenerate.

Этап 3. Semantic spine L3 — 4–8 месяцев

Результат:

  • command executor;
  • recompute graph;
  • typed relation commands;
  • prefill/onchange;
  • constraints across all write paths;
  • transaction retry/outbox/idempotency;
  • company/property consistency;
  • copy semantics.

Gate: arpee sales/purchase/accounting/inventory regression and concurrency corpus проходит без ручных recompute loopholes.

Этап 4. UI, security и data compilers — 4–8 месяцев

Результат:

  • domains/policy compiler;
  • XML view inheritance → PageIR;
  • actions/menus/search/modifiers;
  • resource ledger noupdate/update/uninstall;
  • import/export and migration skeletons;
  • report template MVP.

Gate: стандартный CRUD addon L2/L3 генерируется без ручного TSX; policy differential tests совпадают.

Этап 5. Перевести arpee в golden generated+overlay corpus — 3–6 месяцев

Результат:

  • 11 существующих addons постепенно разделены на generated и manual layers;
  • ручные schemas/pages сокращены;
  • доменные tests не теряются;
  • capability deviations документированы;
  • production-shaped release собран из чистого checkout.

Gate: regeneration не меняет поведение; arpee current use cases проходят migration from previous release.

Этап 6. Вертикальные pilots — 6–12 месяцев

Порядок лучше строить по возрастанию связности:

  1. CRM/contacts/calendar — проверка structural/UI/security.
  2. Sales + products — pricing/defaults/taxes/commands.
  3. Purchase + inventory — concurrency, reservations, partial operations.
  4. Accounting — currencies, rounding, posting, reconciliation, immutable ledger.

Accounting нельзя использовать как самый первый generator pilot: он сразу смешивает все сложные проблемы и плохо локализует источник ошибки. Но без accounting нельзя объявлять ERP production parity.

Gate каждого pilot: differential business scenarios, data migration/reconciliation, performance/security/upgrade tests.

Этап 7. Production lifecycle — 3–6 месяцев параллельно pilots

  • immutable images/artifacts;
  • zero/low-downtime deployment policy;
  • migration checkpoints/recovery;
  • backup/restore and disaster exercises;
  • telemetry, command/recompute/outbox dashboards;
  • security review and tenant isolation tests;
  • support playbooks.

Этап 8. Расширение покрытия — многолетняя программа

После P0/P1 spine добавляются localization, manufacturing, payroll, e-commerce, POS, payments и отраслевые packs. Каждый новый family расширяет IR/runtime только через доказанный reusable primitive, а не через один-off compatibility hack.


26. Оценка масштаба

Грубые диапазоны для опытной команды, не календарное обещание:

Результат Оценка
Честный scanner/plan tool L0 2–4 инженер-месяца
Structural generator MVP L1/L2 ещё 6–12 инженер-месяцев
Production semantic/UI/security/data platform для ограниченного corpus суммарно 18–36+ инженер-месяцев
Широкий паритет стандартных бизнес-доменов Odoo многолетняя работа нескольких domain-команд

Диапазон радикально зависит от того, считается ли custom Python manual, нужны ли website/POS/accounting/localizations, переносится ли живая DB и какой порог differential equivalence. Обещание «перевести любой addon» без зафиксированного subset оценить честно нельзя.

Минимальная устойчивая команда:

  • compiler/static analysis;
  • Angee runtime/ORM/transactions;
  • web/UI compiler;
  • security/data migration;
  • domain engineers для accounting/inventory/sales;
  • QA/performance/operations как сквозная функция.

27. Licensing и clean-room

Текущий arpee заявлен как clean-room functional port. Автоматический translator, который читает исходники Odoo addon и создаёт производное представление/code, — другая методика. Нельзя одновременно утверждать строгий clean-room и использовать source-to-source conversion тех же файлов.

Практически нужны два режима:

  • Clean-room track: команда A пишет behavioral specs/tests, команда B реализует Angee без доступа к защищаемому source beyond approved public behavior. «Указать addons и автоматически перевести» здесь невозможно в полном смысле.
  • Licensed translation track: compiler читает source, сохраняет provenance/license, применяет per-addon license policy и формирует соответствующие notices/source obligations.

В Odoo ecosystem встречаются разные licenses и proprietary addons. Compiler должен блокировать неизвестную/запрещённую license policy. Этот отчёт не является юридическим заключением; до продукта нужен counsel по конкретному source corpus и способу распространения.


28. Риски и способы их контролировать

Риск Как проявится Контроль
Скрытая семантика Python сгенерировано, но business edge case неверен L4 whitelist, manual diagnostics, differential tests
Stale derived facts неправильные суммы/остатки framework recompute graph, запрет unmanaged writes
Security mismatch data leak через list/aggregate/subscription единый compiled policy plan, matrix tests
Addon order/MRO override потерян или super() другой contribution provenance, conflict diagnostics, no arbitrary method lowering
Migration drift новая версия создаёт дубли/теряет refs external-ID ledger, checkpoints, idempotency
UI semantic loss форма выглядит, но onchange/action неверны PageIR + prefill/action contracts, browser scenarios
Performance collapse N+1 и recompute fan-out generated loading plans, query budgets, batch recompute metrics
Runtime/build drift production сам генерирует другой code immutable artifacts, build --check
Framework churn arpee specs и code расходятся pinned baseline, capability manifest from executable tests
Scope explosion бесконечный паритет Odoo addon corpus и L-level gates per release
Legal ambiguity нельзя распространять output per-addon license gate/provenance/counsel
Sibling-repo coupling build работает только у автора packaged/versioned deps, clean checkout CI

29. Архитектурные решения, которые надо принять явно

Одна DB или много DB

Odoo естественно обслуживает множество баз с разными installed modules. Angee compositor фиксирует set на release. Рекомендация: один stack artifact на addon set; tenant/company вариативность внутри DB, разные addon sets — разные deployments. Не пытаться в первой версии повторить динамическую per-DB установку modules.

PostgreSQL как контракт

arpee уже использует advisory/row locks и PostgreSQL-only concurrency scenarios. Для ERP объявить PostgreSQL обязательным. Поддержка SQLite как production backend создаст ложную совместимость и скроет locking/constraint defects.

Сохранённые totals или query-time

Financial documents и stock projections часто требуют stored values для legal snapshot/performance. Их надо хранить, но только под управлением dependency engine. Pure UI counts можно вычислять query-time или как async projections.

Generic UI или custom product UI

По умолчанию compiler генерирует generic PageIR. Дорогой custom UX пишется как named overlay/island и не блокирует regeneration. Нельзя выбирать только один вариант для всей системы.

Events

Domain events — следствие успешно committed command, не замена transactional invariants. Внутри aggregate сначала consistency/recompute, после commit — notifications/integrations/analytics.

Совместимость с Odoo API

Не является product goal. Возможен отдельный narrow gateway для интеграций, ожидающих некоторые Odoo RPC endpoints, но он преобразует calls в Angee commands/queries и имеет явный supported method list.


30. Definition of Done для «указать список addons»

Функция считается реализованной не тогда, когда generator завершился без traceback, а когда:

  1. Входной addon set, versions, commits и licenses зафиксированы.
  2. Транзитивные dependencies разрешены без скрытых imports.
  3. Для каждого model/field/view/action/rule/data/method есть IR entry или explicit ignore diagnostic.
  4. Нет unacknowledged blocked/manual/degraded items.
  5. Generated output детерминирован и собирается из clean checkout.
  6. Django migrations и resource plan применяются к пустой и предыдущей DB.
  7. Security matrix проходит list/object/write/aggregate/subscription/export paths.
  8. Derived facts корректны при create/update/M2M/delete/bulk/concurrency.
  9. Standard UI scenarios работают без ручного schema/TSX для L2 scope.
  10. Differential scenarios подтверждают business equivalence.
  11. Реальные данные проходят dry-run, load и reconciliation.
  12. Performance budgets и operational recovery проверены.
  13. Generated/manual provenance позволяет обновить source addon и понять diff.
  14. Release не генерирует/меняет source при production boot.

Только после этого формулировка «для этого профиля addons получается аналогичный функционал» честна.


31. Рекомендуемые первые 90 дней

Недели 1–2

  • зафиксировать target addon corpus и licenses;
  • pin Angee baseline;
  • сделать capability/status schema;
  • устранить stale README/config arpee;
  • добавить regression tests M2M tax/delete/zero-price;
  • запретить новые raw elevations и stored totals без dependency declaration.

Недели 3–6

  • реализовать L0 manifest/Python/XML inventory и provenance;
  • поднять isolated Odoo introspection exporter;
  • описать IR v0.1;
  • автоматически сравнить arpee specs с фактическими Angee primitives;
  • построить dependency/unsupported report на sales/products/base sample.

Недели 7–10

  • сгенерировать basic models/fields/relations/resources/ACL в отдельный sandbox addon;
  • утвердить generated/manual overlay layout;
  • добавить deterministic regeneration tests;
  • спроектировать command/recompute APIs на основе найденных arpee bugs.

Недели 11–13

  • провести первый vertical micro-slice: простая CRM entity с form/list/search/security/data;
  • параллельно внедрить временный aggregate command в arpee sales и purchase;
  • получить первый differential scenario и точный backlog gaps;
  • принять go/no-go на L3 semantic spine по фактическим данным scanner.

За эти 90 дней не надо обещать автоматический ERP. Ценный результат — измеримый converter contract, первый reproducible generated slice и закрытая корректностная дыра текущего arpee.


32. Итоговая рекомендация

  1. Angee оставить целевой платформой. Её build-time composition, Django/GraphQL/React и REBAC — сильная основа и лучше подходят новой системе, чем буквальное клонирование Odoo runtime.
  2. arpee-angee не выбрасывать. Это качественный golden corpus и уже полезная ручная ERP-реализация.
  3. Не продолжать arpee прежним экстенсивным способом. Сначала исправить derived/write semantics, release reproducibility и security/elevation boundary.
  4. Создать отдельный odoo2angee с нуля. Scanner и typed IR — первый продукт; generator — второй; data migration и differential verification — обязательные части, а не последующая полировка.
  5. Целиться в semantic migration, не source compatibility. Декларативные части генерировать, распознанные idioms lowering-овать, произвольный Python/QWeb/Owl оставлять explicit manual overlays.
  6. Использовать arpee как тестовую линейку compiler. Постепенно заменить ручной boilerplate generated layers, сохранив доменные команды и tests.
  7. Не заявлять полный Odoo parity. Публиковать compatibility profile по addons и слоям.

Самая важная последовательность решений:

scanner/IR
  → command + recompute semantics
    → security/data/UI compilers
      → arpee regeneration
        → vertical production pilots
          → расширение доменов

Если сначала продолжить вручную переносить модули, будет создано больше хорошего, но дорогого и неоднородного ERP-кода. Если сначала построить compatibility facade, получится незрелый клон Odoo runtime. Предложенный путь сохраняет лучшие части обоих проектов: Odoo используется как источник проверяемой бизнес-семантики, Angee остаётся современной самостоятельной платформой, а arpee становится эталоном, который заставляет compiler и runtime быть практически полезными.


Приложение A. Основные точки исходного кода, подтверждающие выводы

Angee

  • /Users/max/Dev/angee-django/AGENTS.md — архитектурные правила и ownership boundaries.
  • /Users/max/Dev/angee-django/angee/compose/appgraph.py — dependency graph.
  • /Users/max/Dev/angee-django/angee/compose/runtime.py — runtime model composition и emission.
  • /Users/max/Dev/angee-django/angee/compose/apps.py — Django boot/import generated models.
  • /Users/max/Dev/angee-django/angee/base/models.pyAngeeModel, runtime/extends semantics, base QuerySet contracts.
  • /Users/max/Dev/angee-django/angee/graphql/schema.pySchemaParts.
  • /Users/max/Dev/angee-django/angee/graphql/data/hasura.py — CRUD/nested lines, relation mutation и _apply_line_diff.
  • /Users/max/Dev/angee-django/angee/graphql/deletion.py — delete preview/execution.
  • /Users/max/Dev/angee-django/angee/graphql/actions.py — resource actions и permission boundaries.
  • /Users/max/Dev/angee-django/addons/angee/messaging/ — thread/message/follower/activity foundation.
  • /Users/max/Dev/angee-django/addons/angee/money/, sequence/, uom/, tags/ — reusable ERP primitives.
  • /Users/max/Dev/angee-django/packages/ и addon web contributions — Form/List/Board/Calendar/Graph/Timeline/Tree resource UI.

arpee

  • /Users/max/Dev/arpee-angee/README.md — заявленная clean-room/dev-stack модель; содержит stale angee.yaml описание.
  • /Users/max/Dev/arpee-angee/settings.yaml — фактический addon composition.
  • /Users/max/Dev/arpee-angee/specs/ledger.md — capability ledger.
  • /Users/max/Dev/arpee-angee/specs/runtime-gaps.md — gap assessment, частично устаревший.
  • /Users/max/Dev/arpee-angee/specs/cloning-plan.md — waves/slices.
  • /Users/max/Dev/arpee-angee/specs/domains/ — domain plans.
  • /Users/max/Dev/arpee-angee/addons/arp/base/models.py — Company/scopes/donors.
  • /Users/max/Dev/arpee-angee/addons/arp/sales/models.py — SalesOrder/Line, price/tax/totals behavior и post-resource M2M workaround.
  • /Users/max/Dev/arpee-angee/addons/arp/purchase/models.py — purchase/receipt/bill flows и derived totals.
  • /Users/max/Dev/arpee-angee/addons/arp/inventory/models.py — transfer/move/stock flows.
  • /Users/max/Dev/arpee-angee/addons/arp/accounting/models.py — entries/invoices/payments/reconciliation.
  • /Users/max/Dev/arpee-angee/addons/arp/*/permissions.zed — фактическая REBAC модель.
  • /Users/max/Dev/arpee-angee/addons/arp/*/tests.py — multi-company, invariants, idempotency и concurrency corpus.

Приложение B. Быстрый decision record

Вопрос Решение
Переписывать arpee с нуля? Нет
Продолжать arpee без изменения подхода? Нет
Начать новый converter package? Да
Эмулировать Registry/recordsets/env полностью? Нет
Генерировать Angee-native addons через typed IR? Да
Считать arbitrary Python автоматически переносимым? Нет, только whitelist L4
Использовать arpee как golden corpus? Да
Сначала добавлять новые ERP-домены? Нет, сначала P0 semantic spine
Делать production runtime на boot? Нет, immutable prebuilt artifacts
Поддерживать SQLite production? Нет, PostgreSQL contract
Считать чистый REBAC заменой любых record rules? Нет, нужен hybrid policy plan
Совмещать clean-room claim и source translator? Нет, выбрать и юридически оформить режим

33. Поправка после полного анализа addons/web

33.1. Что прежняя оценка недооценивала

Предыдущий анализ подробно учитывал серверный Registry/ORM и наличие XML views/actions, но рассматривал клиентскую сторону слишком агрегированно. addons/web показывает, что UI Odoo — это не тонкий renderer метаданных. Наблюдаемая семантика стандартного addon распределена между:

  • resolved server XML arch и field metadata;
  • Web ORM methods web_read, web_search_read, web_save, web_read_group;
  • speculative onchange на virtual server record;
  • action/menu/router/controller stack;
  • ViewCompiler и view-specific ArchParsers;
  • SearchModel;
  • client Record и StaticList с dirty graph/x2many commands;
  • registries services/views/fields/widgets/actions;
  • asset graph, JS patch() и Owl template inheritance.

Следовательно, разрыв Angee↔Odoo на UI-уровне больше, чем следует из сравнения «XML view против React resource page».

33.2. Новые обязательные части целевой архитектуры

К ранее предложенному compiler spine необходимо добавить:

  1. AssetBundleIR для include/remove/before/after и lazy bundles;
  2. ClientExtensionIR для registries, services, actions, fields, view widgets, systray и commands;
  3. TemplateIR для Owl/QWeb templates и inheritance;
  4. расширенный ViewIR, сохраняющий js_class, widget/options, dependencies, buttons/control и modifiers;
  5. ExpressionIR вместо произвольного browser eval Python-подобных выражений;
  6. ActionStack/Menu/Router runtime с deep-link restoration;
  7. SearchIR с filters, group-by, favorites, comparison и search panels;
  8. generic client/server DraftRecord protocol с virtual ids, relation commands, savepoints, onchange и authoritative save result;
  9. asset/frontend capability scanner, который не теряет custom JS молча;
  10. переводимый contract/tour harness и differential tests.

33.3. Пересмотр приоритетов

Derived/recompute engine всё ещё критичен, но теперь рядом с ним находится второй равноправный platform spine: generic draft/action/view/search runtime. Без него генератор сможет создавать модели и отдельные ручные React pages, но не переносить стандартную Odoo UI-семантику.

Рекомендуемый порядок ближайшего фундамента:

scanner + provenance
  -> typed Model/Security/Data/View/Action/Search/Asset IR
  -> derived/recompute + command transaction engine
  -> generic draft/onchange protocol
  -> standard form/list/search/action runtime
  -> kanban/calendar/graph/pivot/report adapters
  -> known frontend extension adapters
  -> manual overlays for arbitrary JS/Owl/patches

33.4. Уточнённая оценка arpee-angee

Положительная оценка arpee сохраняется: его доменная модель, multi-company patterns, explicit transitions и database tests полезны. Но его authored React screens и GraphQL nested mutations — не зачаток эквивалента addons/web; это прикладная реализация выбранных workflows.

Поэтому:

  • arpee не переписывать целиком;
  • использовать его как golden domain/acceptance corpus;
  • временно ограничить ручное размножение CRUD screens;
  • новый compiler и metadata-driven web runtime строить отдельным platform track;
  • затем переводить повторяющиеся arpee screens на generated declarations, оставляя product-specific UX ручным.

33.5. Новая граница обещания «указать список addons»

Реалистичный продукт должен выдавать уровни покрытия:

  • стандартные models/security/data/actions/form/list/search/widgets — генерация;
  • relations/onchange/kanban/analytics/reports — генерация после появления соответствующего runtime;
  • известные custom registries/templates — adapters;
  • произвольный patch(), Owl component, client action или public interaction — явный manual overlay;
  • каждый unsupported construct — блокирующая или предупреждающая диагностика с source provenance.

Это не ухудшает стратегию; оно делает контракт честным. Полная автоматическая трансляция произвольных Python и JavaScript addons фактически означала бы реализацию совместимого Odoo runtime. Цель Angee должна быть другой: максимальное покрытие декларативной и распознаваемой семантики, устойчивый native runtime и контролируемые ручные границы.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment