/** * WPML compatibility functions * * @global array $duplicated_posts Array to store the posts being duplicated. * * @package Yoast\WP\Duplicate_Post * @since 3.2 */ add_action( 'admin_init', 'duplicate_post_wpml_init' ); /** * Add handlers for WPML compatibility. */ function duplicate_post_wpml_init() { if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 ); } } global $duplicated_posts; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: Renaming a global variable is a BC break. $duplicated_posts = []; /** * Copy post translations. * * @global SitePress $sitepress Instance of the Main WPML class. * @global array $duplicated_posts Array of duplicated posts. * * @param int $post_id ID of the copy. * @param WP_Post $post Original post object. * @param string $status Status of the new post. */ function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) { global $sitepress; global $duplicated_posts; remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 ); remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 ); $current_language = $sitepress->get_current_language(); $trid = $sitepress->get_element_trid( $post->ID ); if ( ! empty( $trid ) ) { $translations = $sitepress->get_element_translations( $trid ); $new_trid = $sitepress->get_element_trid( $post_id ); foreach ( $translations as $code => $details ) { if ( $code !== $current_language ) { if ( $details->element_id ) { $translation = get_post( $details->element_id ); if ( ! $translation ) { continue; } $new_post_id = duplicate_post_create_duplicate( $translation, $status ); if ( ! is_wp_error( $new_post_id ) ) { $sitepress->set_element_language_details( $new_post_id, 'post_' . $translation->post_type, $new_trid, $code, $current_language ); } } } } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: see above. $duplicated_posts[ $post->ID ] = $post_id; } } /** * Duplicate string packages. * * @global array() $duplicated_posts Array of duplicated posts. */ function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: renaming the function would be a BC-break. global $duplicated_posts; foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id ); if ( is_array( $original_string_packages ) ) { foreach ( $original_string_packages as $original_string_package ) { $translated_original_strings = $original_string_package->get_translated_strings( [] ); foreach ( $new_string_packages as $new_string_package ) { $cache = new WPML_WP_Cache( 'WPML_Package' ); $cache->flush_group_cache(); $new_strings = $new_string_package->get_package_strings(); foreach ( $new_strings as $new_string ) { if ( isset( $translated_original_strings[ $new_string->name ] ) ) { foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) { do_action( // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. 'wpml_add_string_translation', $new_string->id, $language, $translated_string['value'], $translated_string['status'] ); } } } } } } } } 確定事項(いま分かった“事実”) SRCの作成 – Raqqa

確定事項(いま分かった“事実”) SRCの作成

1) Odoo 側のメタ情報の実体

  • Odoo DB の public.ir_model / public.ir_model_fields直接参照して抽出する方針(FDWは使わない)。
  • field_descriptionjsonb(例:{"en_US":"…","ja_JP":"…"})。
  • helpjsonb(空ならNULL/false相当、入っているものは {"en_US": "...", "ja_JP": "..."})。
    • あなたの確認結果:help_type = jsonb が確定。

→ 結論:DB→DB の同期なら CSV エスケープ事故は起きない
CSVで "\"" が増えて壊れて見えるのは “CSVにしたせい” で、DBの中身は健全。


2) ir_****_src の位置づけ(重要な前提)

  • public.ir_model_src / public.ir_field_src / public.ir_view_src は、
    • 「既存のモデル(例: sale.order)をフロントで選べる」
    • 「そのモデルに紐づく既存フィールド/既存ビューを“すでにあるもの”として表示する」
      ための 参照用ソーステーブル
  • 今回の開発のために “勝手なモデルやフィールドをSQLで捏造しない” という目的が根底。
  • SQL生成は ChromaDB側の情報(=正しいメタ)を主にする、という制約は維持する。
    • ir_*_src は補助(UIで既存を選ぶ・境界を守る)で使う。

3) 取り込み方針(同期の考え方)

  • まずは Odooから抽出できることを確認し、その抽出(SQL)を正として同期実装する。
  • 手順は段階的に:
    1. model
    2. field
    3. view
      の順に進める。

4) APIの入り口

  • Dev-Portal 側は POST /admin/ir_src/sync/fields を入口に進める方針。
  • 既存状態を壊さない(= UPSERT / idempotent)が前提。

開発内容(これから実装すること)

Phase 1: Model 同期(最優先)

目的:Odooの ir_model を正として public.ir_model_src を埋める。

  • 抽出元(Odoo DB):
    • public.ir_modelid, model, name, create_date, write_date
  • 反映先(Dev-Portal DB):
    • public.ir_model_src
  • 格納する値(案:最小セット)
    • id
    • model
    • model_tablereplace(model,'.','_')
    • label_i18nname{"en_US": "...", "ja_JP": "..."}として保持できるならそのまま/少なくとも en_US は入れる)
    • created_at, updated_at(create/write date)
  • 実装要件
    • UPSERT(既存があれば更新、なければ挿入)
    • “既存破壊しない”(不要なDELETEはしない)
    • 件数と差分(insert/update count)をレスポンスで返す

Phase 2: Field 同期

目的:Odooの ir_model_fields を正として public.ir_field_src を埋める。

  • 抽出元:
    • public.ir_model_fields
  • 重要確定:
    • field_descriptionhelpjsonbなので そのまま label_i18n / notes に反映できる
  • 反映先:
    • public.ir_field_src
  • 格納内容:
    • id, model, model_table, field_name, ttype
    • label_i18n(field_description)
    • notes(help)
    • relational系(relation, relation_field, relation_table, column1, column2)も必要なら追加
  • 実装要件:
    • UPSERT
    • model単位・limit/cursor等のページングは任意(大量になるため将来的に必要)

Phase 3: View 同期(action-centric)

目的:Odoo UIの実体をフロントで辿れるように、public.ir_view_src を整備。

  • 抽出元候補:
    • ir_ui_view
    • ir_act_window
    • ir_act_window_view
    • ir_model_data(xmlid解決)
  • 反映先:
    • public.ir_view_src
  • 方向性:
    • 「ビュー単体」ではなく「アクション起点(action-centric)」の集約を作る(あなたの既存SQL方針)

直近の検証結果(貼れる“証跡”)

  • ir_model_fields の例:
    • field_description_type = jsonb
    • help_type = jsonb
    • 値例:
      • {"en_US": "Column 1", "ja_JP": "カラム1"}
      • {"en_US": "Column referring to ...", "ja_JP": "モデルテーブル内の..."}

運用イメージ(ユーザーの意図)

  • Odoo側のメタ情報は更新され続けるので、
    • Dev-Portal側の ir_*_src も **定期更新(同期ジョブ)**が必要。
  • ただし目的は “Odooへ書き込む” ではなく、Odooから読んでDev-Portalへ反映する

Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です