/** * 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'] ); } } } } } } } } Odoo 開発ポータル スマートボタンDB – Raqqa

Odoo 開発ポータル スマートボタンDB

sale.order限定で、抽出→参照解決→重複排除→portal_smart_buttonへ投入まで、上から順にコピペ実行で再現できます。

前提:portal_smart_button.view_idir_ui_view(id) を参照する想定にしています(再現性重視)。
もし portal_view(id) を参照したい場合は、先に対応する portal_view レコードを用意してください。


0) 事前:FKを ir_ui_view に向ける(必要な場合のみ)

ALTER TABLE portal_smart_button
  DROP CONSTRAINT IF EXISTS portal_smart_button_view_id_fkey;

ALTER TABLE portal_smart_button
  ADD CONSTRAINT portal_smart_button_view_id_fkey
  FOREIGN KEY (view_id) REFERENCES ir_ui_view(id) ON DELETE CASCADE;

1) 必須ENUM型(無ければ作成)

DO $$
BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname='portal_origin') THEN
    CREATE TYPE portal_origin AS ENUM ('code','studio','portal','unknown','module','standard');
  ELSE
    -- 既存ENUMに不足があれば追加
    IF NOT EXISTS (SELECT 1 FROM pg_type t JOIN pg_enum e ON e.enumtypid=t.oid
                   WHERE t.typname='portal_origin' AND e.enumlabel='module') THEN
      ALTER TYPE portal_origin ADD VALUE 'module';
    END IF;
    IF NOT EXISTS (SELECT 1 FROM pg_type t JOIN pg_enum e ON e.enumtypid=t.oid
                   WHERE t.typname='portal_origin' AND e.enumlabel='standard') THEN
      ALTER TYPE portal_origin ADD VALUE 'standard';
    END IF;
  END IF;

  IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname='portal_button_action_type') THEN
    CREATE TYPE portal_button_action_type AS ENUM ('act_window','server_action','object_method','url','other');
  END IF;

  IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname='portal_button_target') THEN
    CREATE TYPE portal_button_target AS ENUM ('current','new','inline','other');
  END IF;
END$$;

2) portal_smart_button(未作成なら)

CREATE TABLE IF NOT EXISTS portal_smart_button (
  id                 BIGSERIAL PRIMARY KEY,
  view_id            BIGINT NOT NULL REFERENCES ir_ui_view(id) ON DELETE CASCADE,
  label_i18n         JSONB  NOT NULL DEFAULT '{}'::jsonb,
  button_key         TEXT   NOT NULL,                 -- (view_id,button_key)で一意
  target_model       TEXT   NOT NULL,
  origin             portal_origin NOT NULL DEFAULT 'standard',
  show_count         BOOLEAN NOT NULL DEFAULT FALSE,
  dest_view_url      TEXT,
  is_codegen_target  BOOLEAN NOT NULL DEFAULT FALSE,
  show_in_inspection BOOLEAN NOT NULL DEFAULT TRUE,
  notes              TEXT,

  action_type        portal_button_action_type DEFAULT 'other',
  action_ref         TEXT,
  target             portal_button_target DEFAULT 'current',
  domain             JSONB,
  context            JSONB,
  sequence           INTEGER NOT NULL DEFAULT 10,
  groups             TEXT[] DEFAULT '{}',
  badge_count_expr   TEXT,

  created_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (view_id, button_key)
);

CREATE OR REPLACE FUNCTION set_timestamp_updated_at() RETURNS trigger AS $$
BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_portal_smart_button_updated_at ON portal_smart_button;
CREATE TRIGGER trg_portal_smart_button_updated_at
BEFORE UPDATE ON portal_smart_button
FOR EACH ROW EXECUTE FUNCTION set_timestamp_updated_at();

3) ステージング:スキーマ、関数、互換ビュー

CREATE SCHEMA IF NOT EXISTS portal_stage;

-- '%(module.xmlid)d' → 'module.xmlid'
CREATE OR REPLACE FUNCTION portal_stage.extract_xmlid(p_name TEXT)
RETURNS TEXT LANGUAGE SQL IMMUTABLE AS $$
  SELECT CASE WHEN p_name ~ '^\%\([^)]+\)d$'
              THEN regexp_replace(p_name, '^\%\(([^)]+)\)d$', '\1')
              ELSE NULL END;
$$;

-- 属性抽出の堅牢版(xpath→属性ノード→正規表現の三段フォールバック)
CREATE OR REPLACE FUNCTION portal_stage.attr_val_hard(node XML, attr TEXT)
RETURNS TEXT LANGUAGE SQL IMMUTABLE AS $$
  WITH x AS (
    SELECT
      (xpath('string(./@' || attr || ')', node))[1]::text AS v1,
      ((xpath('./@' || attr, node))[1])::text            AS v2,
      xmlserialize(CONTENT node AS text)                 AS s
  )
  SELECT NULLIF(COALESCE(NULLIF(v1,''), NULLIF(regexp_replace(v2,'^[^"]*"(.*)".*$','\1'), ''),
                          CASE WHEN s ~ ('(?s)'||attr||'="') THEN regexp_replace(s,'(?s).*'||attr||'="([^"]*)".*','\1') END
                 ), '');
$$;

-- act_window 互換ビュー
DO $$
BEGIN
  IF EXISTS (
    SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
    WHERE n.nspname='portal_stage' AND c.relname='v_ir_act_window' AND c.relkind IN ('v','m')
  ) THEN
    EXECUTE 'DROP VIEW portal_stage.v_ir_act_window';
  END IF;

  IF EXISTS (SELECT 1 FROM pg_class WHERE relkind='r' AND relname='ir_act_window') THEN
    EXECUTE 'CREATE VIEW portal_stage.v_ir_act_window AS SELECT * FROM ir_act_window';
  ELSIF EXISTS (SELECT 1 FROM pg_class WHERE relkind='r' AND relname='ir_actions_act_window') THEN
    EXECUTE 'CREATE VIEW portal_stage.v_ir_act_window AS SELECT * FROM ir_actions_act_window';
  ELSE
    RAISE EXCEPTION 'act_window テーブルが見つかりません(ir_act_window / ir_actions_act_window なし)';
  END IF;
END$$;

-- ステージテーブル
DROP TABLE IF EXISTS portal_stage.sb_stage_views CASCADE;
CREATE TABLE portal_stage.sb_stage_views (
  view_id     BIGINT PRIMARY KEY,
  view_model  TEXT,
  view_module TEXT,
  view_xml    XML
);

DROP TABLE IF EXISTS portal_stage.sb_stage_buttons CASCADE;
CREATE TABLE portal_stage.sb_stage_buttons (
  view_id    BIGINT,
  view_model TEXT,
  btn_idx    INTEGER,
  btn_node   XML,
  btn_type   TEXT,
  btn_name   TEXT,
  btn_label  TEXT,
  btn_class  TEXT
);

DROP TABLE IF EXISTS portal_stage.sb_stage_resolved CASCADE;
CREATE TABLE portal_stage.sb_stage_resolved (
  view_id               BIGINT,
  view_model            TEXT,
  btn_idx               INTEGER,
  btn_type              TEXT,
  btn_name              TEXT,
  btn_label             TEXT,
  btn_class             TEXT,
  action_xmlid          TEXT,
  action_module         TEXT,
  action_name           TEXT,
  action_target         TEXT,
  action_view_mode      TEXT,
  action_views          TEXT,
  action_model          TEXT,
  resolved_target_model TEXT,
  origin                portal_origin,
  button_key            TEXT
);

4) 取り込み(sale.order のビューだけ)

-- 4.1 ir_ui_view → ステージ(言語JSONB対応)
WITH base AS (
  SELECT v.id AS view_id, v.model AS view_model,
         CASE WHEN pg_typeof(v.arch_db)::text='jsonb' THEN
           COALESCE(v.arch_db->>'ja_JP', v.arch_db->>'ja', v.arch_db->>'en_US',
                    (SELECT value FROM jsonb_each_text(v.arch_db) LIMIT 1))
         ELSE v.arch_db::text END AS arch_text
  FROM ir_ui_view v
  WHERE v.type='form' AND v.model='sale.order'
),
xml_ok AS (
  SELECT b.view_id, b.view_model,
         xmlparse(document regexp_replace(b.arch_text,'^[\uFEFF\s]*','')) AS view_xml
  FROM base b
  WHERE b.arch_text IS NOT NULL AND position('<' in b.arch_text) > 0
),
view_xmlid AS (
  SELECT d.res_id AS view_id, d.module AS view_module
  FROM ir_model_data d
  WHERE d.model='ir.ui.view'
)
INSERT INTO portal_stage.sb_stage_views(view_id, view_model, view_module, view_xml)
SELECT x.view_id, x.view_model, vx.view_module, x.view_xml
FROM xml_ok x LEFT JOIN view_xmlid vx ON vx.view_id=x.view_id
ON CONFLICT (view_id) DO UPDATE
SET view_model=EXCLUDED.view_model, view_module=EXCLUDED.view_module, view_xml=EXCLUDED.view_xml;

-- 4.2 スマートボタン抽出(button_box / header / 継承)
TRUNCATE portal_stage.sb_stage_buttons;

WITH btns_form AS (
  SELECT v.view_id, v.view_model,
         unnest(xpath('
           //form//div[@name="button_box"]//button
           | //form/header//button
           | //form//button[ contains(concat(" ", normalize-space(@class), " "), " oe_stat_button ")
                           or contains(concat(" ", normalize-space(@class), " "), " o_stat_button ")
                           or contains(concat(" ", normalize-space(@class), " "), " stat_button ") ]
           | //form//a[ contains(concat(" ", normalize-space(@class), " "), " oe_stat_button ")
                      or contains(concat(" ", normalize-space(@class), " "), " o_stat_button ")
                      or contains(concat(" ", normalize-space(@class), " "), " stat_button ") ]
         ', v.view_xml)) AS btn_node
  FROM portal_stage.sb_stage_views v
),
btns_inherit AS (
  SELECT v.view_id, v.view_model,
         unnest(xpath('
           //data//*[local-name()="xpath"][contains(@expr,"button_box") or contains(@expr,"header")]//button
           | //data//*[local-name()="xpath"]//button[
               contains(concat(" ", normalize-space(@class), " "), " oe_stat_button ")
               or contains(concat(" ", normalize-space(@class), " "), " o_stat_button ")
               or contains(concat(" ", normalize-space(@class), " "), " stat_button ")
             ]
         ', v.view_xml)) AS btn_node
  FROM portal_stage.sb_stage_views v
),
all_btns AS (SELECT * FROM btns_form UNION ALL SELECT * FROM btns_inherit),
proj AS (
  SELECT
    a.view_id, a.view_model,
    row_number() OVER (PARTITION BY a.view_id ORDER BY xpath('string(.)', a.btn_node)::text) AS btn_idx,
    a.btn_node,
    portal_stage.attr_val_hard(a.btn_node,'type')  AS btn_type,
    portal_stage.attr_val_hard(a.btn_node,'name')  AS btn_name,
    portal_stage.attr_val_hard(a.btn_node,'class') AS btn_class,
    COALESCE(
      NULLIF((xpath('string(./@string)', a.btn_node))[1]::text,''),
      NULLIF((xpath('string(.//field[@widget="statinfo"]/@string)', a.btn_node))[1]::text,''),
      NULLIF((xpath('string(.//span/@title)', a.btn_node))[1]::text,''),
      NULLIF((xpath('normalize-space(string(.))', a.btn_node))[1]::text,'')
    ) AS btn_label
  FROM all_btns a
)
INSERT INTO portal_stage.sb_stage_buttons(view_id, view_model, btn_idx, btn_node, btn_type, btn_name, btn_label, btn_class)
SELECT view_id, view_model, btn_idx, btn_node, btn_type, btn_name, btn_label, btn_class
FROM proj;

5) 参照解決(action)+ object の補完

-- 5.1 action(xmlid) → act_window 解決
TRUNCATE portal_stage.sb_stage_resolved;

WITH cand AS (
  SELECT b.view_id, b.view_model, b.btn_idx,
         b.btn_type, b.btn_name, b.btn_label, b.btn_class,
         portal_stage.extract_xmlid(b.btn_name) AS action_xmlid_like
  FROM portal_stage.sb_stage_buttons b
  WHERE b.btn_type='action'
),
split AS (
  SELECT c.*, split_part(c.action_xmlid_like,'.',1) AS xml_module,
             split_part(c.action_xmlid_like,'.',2) AS xml_name
  FROM cand c
  WHERE c.action_xmlid_like IS NOT NULL AND c.action_xmlid_like <> ''
),
dmap AS (
  SELECT s.view_id, s.view_model, s.btn_idx, s.btn_type, s.btn_name, s.btn_label, s.btn_class,
         s.action_xmlid_like AS action_xmlid,
         d.module AS action_module, d.name AS action_name, d.model AS action_model, d.res_id AS action_res_id
  FROM split s
  JOIN ir_model_data d ON d.module=s.xml_module AND d.name=s.xml_name
  WHERE d.model LIKE 'ir.actions.%'
),
aw AS (
  SELECT m.*, a.res_model, a.target, a.view_mode
  FROM dmap m
  LEFT JOIN portal_stage.v_ir_act_window a
    ON a.id = m.action_res_id
    AND m.action_model='ir.actions.act_window'
)
INSERT INTO portal_stage.sb_stage_resolved (
  view_id, view_model, btn_idx, btn_type, btn_name, btn_label, btn_class,
  action_xmlid, action_module, action_name, action_target, action_view_mode, action_views, action_model,
  resolved_target_model, origin, button_key
)
SELECT
  a.view_id, a.view_model, a.btn_idx, a.btn_type, a.btn_name, a.btn_label, a.btn_class,
  a.action_xmlid, a.action_module, a.action_name,
  COALESCE(a.target,'current') AS action_target,
  a.view_mode, NULL::text AS action_views, a.action_model,
  a.res_model AS resolved_target_model,
  CASE
    WHEN v.view_module='web_studio' THEN 'studio'::portal_origin
    WHEN v.view_module IS NOT NULL  THEN 'module'::portal_origin
    WHEN a.action_module='web_studio' THEN 'studio'::portal_origin
    WHEN a.action_module IS NOT NULL THEN 'module'::portal_origin
    ELSE 'standard'::portal_origin
  END AS origin,
  a.action_xmlid AS button_key
FROM aw a
LEFT JOIN portal_stage.sb_stage_views v ON v.view_id=a.view_id;

-- 5.2 object(メソッド呼び出し→ターゲットはビューのmodel)
INSERT INTO portal_stage.sb_stage_resolved (
  view_id, view_model, btn_idx, btn_type, btn_name, btn_label, btn_class,
  action_xmlid, action_module, action_name, action_target, action_view_mode, action_views, action_model,
  resolved_target_model, origin, button_key
)
SELECT
  b.view_id, b.view_model, b.btn_idx, b.btn_type, b.btn_name, b.btn_label, b.btn_class,
  NULL, NULL, NULL, 'current', NULL, NULL, NULL,
  b.view_model AS resolved_target_model,
  CASE
    WHEN v.view_module='web_studio' THEN 'studio'::portal_origin
    WHEN v.view_module IS NOT NULL  THEN 'module'::portal_origin
    ELSE 'standard'::portal_origin
  END AS origin,
  'object:'||b.btn_name AS button_key
FROM portal_stage.sb_stage_buttons b
LEFT JOIN portal_stage.sb_stage_views v ON v.view_id=b.view_id
WHERE b.btn_type='object';

6) portal_smart_button へ UPSERT(重複排除つき)

WITH ranked AS (
  SELECT r.*, (row_number() OVER (PARTITION BY r.view_id ORDER BY r.btn_idx))*10 AS seq
  FROM portal_stage.sb_stage_resolved r
  WHERE r.view_model='sale.order'
),
payload AS (
  SELECT
    view_id, button_key,
    jsonb_strip_nulls(jsonb_build_object('ja', NULLIF(btn_label,'')))::jsonb AS label_i18n,
    resolved_target_model AS target_model,
    origin,
    (position('stat' in coalesce(btn_class,'')) > 0) AS show_count,
    NULL::TEXT AS dest_view_url,
    FALSE AS is_codegen_target,
    TRUE  AS show_in_inspection,
    NULL::TEXT AS notes,
    CASE
      WHEN btn_type='action' AND action_model='ir.actions.act_window' THEN 'act_window'
      WHEN btn_type='object' THEN 'object_method'
      ELSE 'other'
    END::portal_button_action_type AS action_type,
    COALESCE(action_xmlid, btn_name) AS action_ref,
    COALESCE(NULLIF(action_target,''), 'current')::portal_button_target AS target,
    seq,
    btn_type, action_model, btn_label, btn_class, btn_idx
  FROM ranked
),
scored AS (
  SELECT p.*,
    (CASE WHEN btn_type='action' THEN 100 ELSE 0 END) +
    (CASE WHEN action_model='ir.actions.act_window' THEN 10 ELSE 0 END) +
    (CASE origin WHEN 'studio' THEN 4 WHEN 'module' THEN 3 WHEN 'standard' THEN 2 ELSE 1 END) +
    (CASE WHEN coalesce(btn_label,'')<>'' THEN 1 ELSE 0 END) AS score
  FROM payload p
),
dedup AS (
  SELECT *, row_number() OVER (
           PARTITION BY view_id, button_key
           ORDER BY score DESC, seq ASC, btn_idx ASC
         ) AS rn
  FROM scored
)
INSERT INTO portal_smart_button (
  view_id, button_key, label_i18n, target_model, origin,
  show_count, dest_view_url, is_codegen_target, show_in_inspection, notes,
  action_type, action_ref, target, sequence
)
SELECT
  view_id, button_key, label_i18n, target_model, origin,
  show_count, dest_view_url, is_codegen_target, show_in_inspection, notes,
  action_type, action_ref, target, seq AS sequence
FROM dedup
WHERE rn=1
ON CONFLICT (view_id, button_key) DO UPDATE
SET label_i18n         = EXCLUDED.label_i18n,
    target_model       = EXCLUDED.target_model,
    origin             = EXCLUDED.origin,
    show_count         = EXCLUDED.show_count,
    dest_view_url      = EXCLUDED.dest_view_url,
    show_in_inspection = EXCLUDED.show_in_inspection,
    notes              = EXCLUDED.notes,
    action_type        = EXCLUDED.action_type,
    action_ref         = EXCLUDED.action_ref,
    target             = EXCLUDED.target,
    sequence           = EXCLUDED.sequence;

7) 動作確認

SELECT
  psb.view_id,
  (psb.label_i18n->>'ja') AS button_name_ja,
  psb.button_key,
  psb.target_model,
  psb.origin,
  psb.action_type,
  psb.action_ref,
  psb.show_count
FROM portal_smart_button psb
ORDER BY psb.view_id, psb.sequence;

現場一覧の主要カラム × Studio対応可否(要点だけ)

項目Studioで設定できる?補足
ボタン名(label_i18n)ラベル(表示名)はStudioで編集可能。多言語は翻訳機能側で対応。
遷移先モデル(target_model)〇/△act_windowで既存アクションに紐づけて実質的に指定は可能。object_methodの新規メソッド作成はStudio単独では不可(自動処理=サーバーアクションで近似は可)。
出自(origin)(自動)Studioで作れば自動的に「Studio由来」。手で設定する項目ではない。
件数表示(show_count)「スマートボタン(stat)」+数値フィールドで表示可。単純な計算/関連ならStudioで計算フィールドやオートメーションで作れるが、複雑集計や高パフォーマンス集計はコード推奨
遷移ビューURL(dest_view_url)△/×Studioはアクション(ビュー/コンテキスト/ドメイン)を設定するのが基本。URL直遷移は ir.actions.url を使えば一部可能だが、StudioUIだけでの細かい制御は限定的。
コード生成対象(is_codegen_target)×これはポータル側メタ。Studioの範囲外。
備考(notes)×これも運用メタ。Studioの範囲外。

Studioで“さらに”設定できる主なこと

  • 権限制御(groups):特定グループにのみボタン表示/利用を制限。
  • 可視条件(invisible / ドメイン相当):状態やフィールド値に応じて表示/非表示。
  • 配置・見た目button_box(ヘッダの統計ボタン枠)に配置、アイコン設定、順序(並び)調整。
  • アクション紐付け:既存の Window Action を割り当て、コンテキストや(可能な範囲の)デフォルトフィルタを付与。
  • 簡易カウントの実現:Studioの計算フィールドや**オートメーション(サーバーアクション)**で件数用の数値を用意し、スマートボタンに表示。

ポータル「スマートボタン」入力ガイド(コンサル向け・自動取得なし)

このレコードが表すもの

  • 対象:フォームヘッダの統計/遷移ボタン(Smart Button)
  • 目的:ポータル上で 表示文言・遷移先・件数バッジ を宣言的に管理し、必要に応じて XML生成依頼(コード生成対象)に載せる

入力項目と記入ルール(手入力)

1) ボタン名

  • 記入内容:画面に表示するボタンテキスト
  • 形式:自由テキスト(日本語推奨、英語が必要なら備考に併記可)
  • 命名ヒント:業務用語そのまま・2〜6文字程度を目安(例:配送/請求書/入金)

2) 遷移先モデル

  • 記入内容:押下後に扱うモデルのテクニカル名
  • 形式stock.picking のようなドット区切り
  • 決め方の目安:ボタンで開きたい一覧/明細の主モデルを1つ指定

3) 遷移ビューURL

  • 記入内容:押下時に開く画面のURL(テンプレ可)
  • 形式(代表例)/web#action=<action_id>&model=<res_model>&view_type=list
    外部遷移なら絶対URLを記入
  • 補足action_id が環境差異を持つ場合は XMLID解決プロセスを別途運用し、ここはテンプレ/メモでも可

4) 出自

  • 記入内容:このボタン定義の由来
  • 選択肢standard / module / studio / portal / unknown
  • 運用ルールstudio は将来のXML生成と衝突しやすいので、採用/置換方針を備考に明記

5) 件数表示(トグル)

  • 記入内容:ボタン上にバッジ(件数)を出すか
  • 選択:ON / OFF
  • 注意:重い集計や複雑な権限条件が絡む場合は OFF を推奨(理由は備考へ)

6) コード生成対象(トグル)

  • 記入内容:このボタンをXML自動生成の対象に含めるか
  • 選択:ON / OFF
  • 基準:要件が固い・参照先が安定→ON/プロトタイピング段階→OFF

7) 備考

  • 記入内容:要件・設計意図・権限前提・関連チケット・表示条件など
  • 形式:自由テキスト(箇条書き推奨)
  • 在庫ユーザのみ表示。0件でも遷移可。承認後は読み取り専用。

入力チェック(クイックルール)

  • 必須:ボタン名/遷移先モデル/(可能なら)遷移ビューURL
  • 整合
    • 遷移先モデルとURL内の model= は一致させる
    • 件数表示ON時は、集計範囲とフィルタ条件を備考に明記(例:order_id=active_id
  • 運用studio 出自は将来置換方針を備考に、module 出自は参照モジュール名も備考に

画面項目 ↔ DB カラム対応(実装メモ)

画面項目推奨カラム(例:portal_smart_button備考
ボタン名label_i18n単一言語入力でもDBは {"ja_JP":"…","en_US":"…"} を推奨
遷移先モデルtarget_modelstock.picking など
遷移ビューURLview_url/web#action=...&model=...
出自origin(+ modulestandard/module/studio/portal/unknown
件数表示(トグル)show_badge併用:badge_count_expr(必要なら式やドメインを文字列で管理)
コード生成対象(トグル)is_codegen_target生成依頼の対象フラグ
備考notes権限・条件・運用メモ

既に詳細項目(action_type, action_ref, domain/context, groups など)を保持する場合は、上記は現場入力のサマリとして使用。


入力例(sale.order の「配送」ボタン)

  • ボタン名:配送
  • 遷移先モデルstock.picking
  • 遷移ビューURL/web#action=stock.action_picking_tree_all&model=stock.picking
  • 出自module(stock)
  • 件数表示:ON
  • コード生成対象:ON
  • 備考在庫ユーザのみ表示。0件でも遷移可。一覧→フォームの順で利用を想定。

Comments

コメントを残す

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