運用をできるだけシンプルにして、**「初期一括取り込み → 以降はポータルを正として上書き or 追加」**に固定した手順を、開発向けにまとめます。差分チェックは一切しません。
運用ポリシー(前提)
- 単一の正はポータル。
取り込み後は、Odoo側で直接編集しない(Studio含む)。 - 既存の変更が必要になったら、ポータルで編集 → XML生成 → Odooへ上書き。
- 新規ビューはポータルに追加定義 → XML生成 → Odooへ反映。
- 差分検知・監査は行わない(その代わり、Odoo直編集は禁止・最小権限)。
0) 事前準備(1回だけ)
- ポータルDBに
portal_*テーブル&ENUMを作成済みであること。 - Odoo DBに接続できる(同一PGかFDWでも可)。
- Studio編集を原則禁止(権限付与を外す or 各環境で周知)。
1) 初期取り込み(初期Odoo → ポータル)※一度だけ
1-1. 既存のポータルデータを(モデル単位で)空にする
-- 例: sale.order を取り込み直す場合
DROP TABLE IF EXISTS tmp_victims;
CREATE TEMP TABLE tmp_victims AS
SELECT id AS view_id FROM portal_view WHERE model = 'sale.order';
DELETE FROM portal_menu WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_action WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_search_settings WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_calendar_settings WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_kanban_settings WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_list_settings WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_form_settings WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_view_creation WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_view_permissions WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_view_common WHERE view_id IN (SELECT view_id FROM tmp_victims);
DELETE FROM portal_view WHERE id IN (SELECT view_id FROM tmp_victims);
DROP TABLE IF EXISTS tmp_victims;
外部キーに
ON DELETE CASCADEを張っているならDELETE FROM portal_view WHERE model='sale.order';だけでOK。
1-2. ir_ui_view → portal_view へ取り込み
(name が text/jsonb どちらでも動作、未知 type は other に丸める)
WITH params AS (SELECT 'sale.order'::text AS model_name),
src AS (
SELECT
v.id AS odoo_view_id,
v.model,
CASE v.type
WHEN 'tree' THEN 'list' WHEN 'form' THEN 'form'
WHEN 'kanban' THEN 'kanban' WHEN 'calendar' THEN 'calendar'
WHEN 'search' THEN 'search' WHEN 'graph' THEN 'graph'
WHEN 'pivot' THEN 'pivot' WHEN 'gantt' THEN 'gantt'
ELSE 'other' END AS view_type_norm,
CASE
WHEN v.name IS NULL THEN NULL
WHEN v.name::text LIKE '{%' THEN COALESCE((v.name::jsonb)->>'ja_JP',(v.name::jsonb)->>'en_US',v.name::text)
ELSE v.name::text END AS view_name,
v.priority AS priority_num,
CASE WHEN v.priority<=8 THEN 'high'
WHEN v.priority>=25 THEN 'low'
ELSE 'standard' END AS priority_ui_txt,
v.arch_db, v.inherit_id, v.write_date,
imd.module AS origin_module, (imd.module||'.'||imd.name) AS xmlid,
(imd.module = 'web_studio') AS is_studio
FROM ir_ui_view v
JOIN params p ON v.model = p.model_name
LEFT JOIN ir_model_data imd ON imd.model='ir.ui.view' AND imd.res_id=v.id
)
INSERT INTO portal_view (
source_view_id, source_xmlid, model, view_type, view_name,
priority_ui, priority_num, enabled, ai_purpose,
arch_db_snapshot, is_custom, origin, origin_module
)
SELECT
s.odoo_view_id, s.xmlid, s.model, s.view_type_norm::view_type, s.view_name,
s.priority_ui_txt::view_priority, s.priority_num, TRUE, NULL,
s.arch_db, s.is_studio,
CASE WHEN s.is_studio THEN 'studio'
WHEN s.origin_module IS NOT NULL THEN 'module'
ELSE 'standard' END::view_origin,
s.origin_module
FROM src s;
1-3. common settings 自動生成(表示フィールド / 既定ソート)
WITH params AS (SELECT 'sale.order'::text AS model_name),
pv AS (
SELECT id, arch_db_snapshot, view_type
FROM portal_view JOIN params p ON portal_view.model=p.model_name
),
fields AS (
SELECT pv.id AS view_id,
(regexp_matches(pv.arch_db_snapshot,'<field\s+name="([a-zA-Z0-9_\.]+)"','g'))[1] AS fname,
row_number() OVER (PARTITION BY pv.id ORDER BY ordinality) AS rn
FROM pv, regexp_matches(pv.arch_db_snapshot,'<field\s+name="([a-zA-Z0-9_\.]+)"','g') WITH ORDINALITY
),
first_pos AS (
SELECT view_id, fname, MIN(rn) AS first_rn
FROM fields GROUP BY view_id, fname
),
agg AS (
SELECT view_id, jsonb_agg(fname ORDER BY first_rn) AS display_fields
FROM first_pos GROUP BY view_id
),
order_raw AS (
SELECT pv.id AS view_id,
(regexp_matches(pv.arch_db_snapshot,'default_order="([a-zA-Z0-9_]+)\s+(asc|desc)"'))[1] AS sort_field,
(regexp_matches(pv.arch_db_snapshot,'default_order="([a-zA-Z0-9_]+)\s+(asc|desc)"'))[2] AS sort_dir_txt
FROM pv WHERE pv.view_type='list'
),
order_norm AS (
SELECT view_id, sort_field,
CASE WHEN lower(sort_dir_txt)='asc' THEN 'asc'::sort_order ELSE 'desc'::sort_order END AS sort_dir
FROM order_raw
)
INSERT INTO portal_view_common (view_id, display_fields, sort_field, sort_dir, default_group_by, default_filters)
SELECT pv.id, COALESCE(agg.display_fields,'[]'::jsonb), onm.sort_field, onm.sort_dir, NULL, NULL
FROM (SELECT id FROM portal_view pv JOIN params p ON pv.model=p.model_name) pv
LEFT JOIN agg ON agg.view_id=pv.id
LEFT JOIN order_norm onm ON onm.view_id=pv.id
ON CONFLICT (view_id) DO UPDATE
SET display_fields=EXCLUDED.display_fields, sort_field=EXCLUDED.sort_field, sort_dir=EXCLUDED.sort_dir;
必要に応じ、List/Kanban/Calendar/Search の補助抽出(インライン編集/quick_create/開始・終了/検索優先度)も実行可。省略可。
2) 以降の運用(超シンプル)
2-1. 既存ビューを変更したいとき
- ポータルで編集(display_fields、並び、group_by、検索フィルタ等)
- XMLを生成(ポータルのテンプレで
arch_dbを作る) - Odooへ上書き(対象
ir_ui_view.id = portal_view.source_view_idに対して)
-- :view_id は portal_view.id、:xml は生成したXML
UPDATE ir_ui_view
SET arch_db = :xml, write_date = now()
WHERE id = (SELECT source_view_id FROM portal_view WHERE id=:view_id);
- ポータル側スナップショットも更新(“正”がポータルなので、生成XMLで保持)
UPDATE portal_view SET arch_db_snapshot = :xml WHERE id = :view_id;
差分確認はしないポリシーのため、常にポータル→Odooへ上書き。
2-2. 新規ビューを追加したいとき
- ポータルで新規行を作成(
portal_viewと各設定テーブル)- 命名規則・優先度(High/Standard/Low)を入力
- XML生成 → Odooに挿入
- 新規ビューは
ir_model_dataのXMLID を付けないなら、単純INSERTでも動きます
- 新規ビューは
-- 新規挿入(arch_dbのみ記載。name/model/type/priority もセット)
-- 例: listビュー
INSERT INTO ir_ui_view (name, model, type, priority, arch_db, create_date, write_date)
VALUES ('Portal Generated: Sales Orders List', 'sale.order', 'tree', 16, :xml, now(), now())
RETURNING id;
- 得られた
ir_ui_view.idをportal_view.source_view_idに保存(ひも付け)
UPDATE portal_view SET source_view_id = :new_view_id WHERE id = :portal_view_id;
- スナップショット保存
UPDATE portal_view SET arch_db_snapshot = :xml WHERE id = :portal_view_id;
XMLIDが必要(モジュール配布や移行を意識)な場合は、モジュール側のXMLとして出力し、
ir_model_data連携まで行う方式に切替。
3) 環境フロー(推奨)
- Dev:ポータルで編集→XML生成→Dev Odooへ反映
- Staging:Devで確認後、同じXMLで反映
- Prod:Staging確認後に反映(SQL一式をOpsが実行)
- すべて ポータルを唯一の編集窓口にする(Odoo/Studio直編集は行わない)
4) 失敗時のリカバリ(最低限)
- 直前のXMLを ポータル側に保存(
arch_db_snapshotが常に最新=ロールバック用)。 - 誤配備時は そのXMLで再上書きすれば戻せます。
5) 開発者向けチェックリスト
- 編集は必ずポータルで(Odoo/Studio直編集禁止)
- 既存変更:ポータル編集→XML生成→
ir_ui_view.arch_db上書き→arch_db_snapshot更新 - 新規追加:ポータル追加→XML生成→
ir_ui_viewINSERT→source_view_id保存→arch_db_snapshot更新 - 優先度(High/Standard/Low)と view_type を正しく設定
- (任意)アクション/メニューが必要なら、別途
ir_actions_act_window/ir_ui_menuを作成
これで、差分チェックなしでも、
「初期取り込み後は常にポータルが正」「既存は上書き/新規は追加」の運用が回ります。
コメントを残す