/** * 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 create poral_tables – Raqqa

Odoo create poral_tables

BEGIN;

-- 0) 必要拡張
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- 0-1) updated_at を自動更新する共通トリガ関数
CREATE OR REPLACE FUNCTION public.tg_touch_updated_at()
RETURNS trigger AS $$
BEGIN
  NEW.updated_at := now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

------------------------------------------------------------
-- 1) コア: モデル
------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.portal_model (
  id           bigserial PRIMARY KEY,
  model        text        NOT NULL UNIQUE, -- 例: 'demo.task'
  model_table  text        NOT NULL,        -- 例: 'demo_task'
  label_i18n   jsonb       DEFAULT '{}'::jsonb,
  notes        text,
  created_at   timestamptz NOT NULL DEFAULT now(),
  updated_at   timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_portal_model_model_table ON public.portal_model(model_table);

CREATE TRIGGER trg_touch_portal_model
BEFORE UPDATE ON public.portal_model
FOR EACH ROW EXECUTE FUNCTION public.tg_touch_updated_at();

------------------------------------------------------------
-- 2) フィールド定義
--   ※ 既存環境の日本語列を持たせています。チェック制約は広めに設定。
------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.portal_fields (
  id                 bigserial PRIMARY KEY,
  model_id           bigint      REFERENCES public.portal_model(id) ON DELETE CASCADE,
  model              text        NOT NULL,
  model_table        text        NOT NULL,
  field_name         text        NOT NULL,              -- 技術名(英語)
  ttype              text        NOT NULL,              -- 例: char, integer, json...
  label_i18n         jsonb       DEFAULT '{}'::jsonb,   -- 表示名 i18n
  code_status        text,
  notes              text,
  origin             text        DEFAULT 'portal',
  created_at         timestamptz NOT NULL DEFAULT now(),
  updated_at         timestamptz NOT NULL DEFAULT now(),
  -- 既存環境に合わせた日本語カラム
  "モデル技術名"       text        NOT NULL DEFAULT '',
  "モデル物理名"       text        NOT NULL DEFAULT '',
  "フィールド技術名"   text        NOT NULL DEFAULT '',
  "データ型"           text        NOT NULL DEFAULT '文字列'
);

-- 日本語「データ型」のチェック制約(広めに許容)
DO $$
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM pg_constraint WHERE conname = 'ck_ttype_jp'
      AND conrelid = 'public.portal_fields'::regclass
  ) THEN
    ALTER TABLE public.portal_fields
      ADD CONSTRAINT ck_ttype_jp CHECK (
        "データ型" IN (
          '文字列','テキスト','長文',
          '整数','数値','小数','実数',
          '真偽','ブール','論理値',
          '日付','日時','タイムスタンプ',
          'JSON','構造化',
          '参照','外部キー',
          '複数参照','1対多','多対多'
        )
      );
  END IF;
END$$;

CREATE INDEX IF NOT EXISTS idx_portal_fields_model ON public.portal_fields(model);
CREATE INDEX IF NOT EXISTS idx_portal_fields_model_id ON public.portal_fields(model_id);

CREATE TRIGGER trg_touch_portal_fields
BEFORE UPDATE ON public.portal_fields
FOR EACH ROW EXECUTE FUNCTION public.tg_touch_updated_at();

------------------------------------------------------------
-- 3) ビュー定義 
------------------------------------------------------------
BEGIN;

-- =========================================================
-- Safety: touch関数(updated_at自動更新)が無ければ定義
-- =========================================================
CREATE OR REPLACE FUNCTION public.tg_touch_updated_at()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
  NEW.updated_at := now();
  RETURN NEW;
END
$$;

-- =========================================================
-- 既存の関連テーブルを削除(存在するもののみ)
-- ※ 旧構成:portal_view / portal_view_common / 種別設定 / 権限 等
-- =========================================================
DROP TABLE IF EXISTS public.portal_list_settings      CASCADE;
DROP TABLE IF EXISTS public.portal_form_settings      CASCADE;
DROP TABLE IF EXISTS public.portal_search_settings    CASCADE;
DROP TABLE IF EXISTS public.portal_view_permissions   CASCADE;
DROP TABLE IF EXISTS public.portal_view_creation      CASCADE;
DROP TABLE IF EXISTS public.portal_view               CASCADE;
DROP TABLE IF EXISTS public.portal_view_common        CASCADE;

-- =========================================================
-- 新: portal_view_common
--  - portal_view_src(= ir_view_src CSV)の全カラムを同名で保持
--  - 共通UI設定・AI要約・作成挙動も保持
--  - action_xmlid を業務キー(1:1)として一意化
-- =========================================================
CREATE TABLE public.portal_view_common (
  id                   bigserial PRIMARY KEY,
  -- === ir_view_src 由来(同名保持) ===
  action_xmlid         text      NOT NULL,
  action_id            text,
  action_name          text,
  model_label          text,
  model_tech           text,
  model_table          text,
  view_types           text,
  primary_view_type    text,
  help_i18n_html       jsonb     DEFAULT '{}'::jsonb,
  help_ja_html         text,
  help_ja_text         text,
  help_en_html         text,
  help_en_text         text,
  view_mode            text,
  context              jsonb,
  domain               jsonb,

  -- === 既存共通UI(従来の portal_view_common の継続カラム) ===
  display_fields       jsonb     NOT NULL DEFAULT '[]'::jsonb,
  sort_field           text,
  sort_dir             text      NOT NULL DEFAULT 'asc',
  default_group_by     text,
  default_filters      jsonb     NOT NULL DEFAULT '{}'::jsonb,

  -- === AI向けメタ(portal_view から移設) ===
  ai_purpose           text,
  ai_purpose_i18n      jsonb     NOT NULL DEFAULT '{}'::jsonb,

  -- === 作成挙動(portal_view_creation を統合) ===
  creation_mode        text      NOT NULL DEFAULT 'open_new_page',
  default_values       text,
  allow_duplicate      boolean   NOT NULL DEFAULT false,
  enable_archive       boolean   NOT NULL DEFAULT true,

  -- 監査
  created_at           timestamptz NOT NULL DEFAULT now(),
  updated_at           timestamptz NOT NULL DEFAULT now(),

  CONSTRAINT uq_pvc_action_xmlid UNIQUE (action_xmlid),
  CONSTRAINT ck_pvc_sort_dir CHECK (sort_dir IN ('asc','desc')),
  CONSTRAINT ck_pvc_creation_mode CHECK (creation_mode IN ('open_new_page','inline_create','modal_create','quick_create'))
);

CREATE INDEX IF NOT EXISTS idx_pvc_model_tech ON public.portal_view_common(model_tech);

CREATE TRIGGER trg_touch_portal_view_common
BEFORE UPDATE ON public.portal_view_common
FOR EACH ROW EXECUTE FUNCTION public.tg_touch_updated_at();

-- =========================================================
-- 新: portal_view
--  - 1レコード = 1ビュー種(全種対応)
--  - common_id で portal_view_common にぶら下がる
--  - 同一(common_id, view_type)は1件のみ
--  - 指定のビュー種ごとの項目をカラムで内包
-- =========================================================
CREATE TABLE public.portal_view (
  id                         bigserial PRIMARY KEY,
  common_id                  bigint NOT NULL REFERENCES public.portal_view_common(id) ON DELETE CASCADE,
  view_type                  text   NOT NULL,
  -- 共通(全種)
  view_name                  text,
  model                      text,
  priority_num               integer,
  enabled                    boolean NOT NULL DEFAULT true,
  is_primary                 boolean NOT NULL DEFAULT false,

  -- === Form ===
  form_show_header           boolean,
  form_show_footer           boolean,

  -- === Kanban ===
  kanban_default_group_by    text,
  kanban_quick_create        boolean,
  kanban_draggable_field     text,

  -- === List ===
  list_inline_edit           boolean,
  list_export_button         boolean,
  list_page_size             integer,

  -- === Calendar ===
  calendar_start_field       date,     -- ※ご指定により text → date
  calendar_end_field         date,     -- ※ご指定により text → date
  calendar_color_field       text,
  calendar_default_view      text,

  -- === Search ===
  search_fields              jsonb,
  search_filters             jsonb,
  search_group_by_filters    jsonb,

  -- === Graph(暫定ドラフト) ===
  graph_type                 text,
  graph_measure_fields       jsonb,
  graph_row_group_by         text,
  graph_col_group_by         text,
  graph_stacked              boolean,

  -- === Pivot(暫定ドラフト) ===
  pivot_measures             jsonb,
  pivot_rows                 jsonb,
  pivot_cols                 jsonb,
  pivot_show_totals          boolean,

  -- === Dashboard(暫定ドラフト) ===
  dashboard_layout           jsonb,
  dashboard_widgets          jsonb,
  dashboard_refresh_secs     integer,

  -- === Tree(暫定ドラフト) ===
  tree_parent_field          text,
  tree_expand_all            boolean,

  -- === Map(暫定ドラフト) ===
  map_lat_field              text,
  map_lng_field              text,
  map_address_field          text,
  map_color_field            text,
  map_cluster                boolean,
  map_default_zoom           integer,

  -- 監査
  created_at                 timestamptz NOT NULL DEFAULT now(),
  updated_at                 timestamptz NOT NULL DEFAULT now(),

  CONSTRAINT uq_pv_common_viewtype UNIQUE (common_id, view_type),
  CONSTRAINT ck_pv_view_type CHECK (
    view_type IN (
      'form','kanban','list','calendar','search','graph','pivot','dashboard','tree','map'
    )
  ),
  CONSTRAINT ck_pv_calendar_default_view CHECK (
    calendar_default_view IS NULL OR calendar_default_view IN ('month','week','day')
  )
);

CREATE INDEX IF NOT EXISTS idx_pv_common_id ON public.portal_view(common_id);
CREATE INDEX IF NOT EXISTS idx_pv_view_type ON public.portal_view(view_type);

CREATE TRIGGER trg_touch_portal_view
BEFORE UPDATE ON public.portal_view
FOR EACH ROW EXECUTE FUNCTION public.tg_touch_updated_at();

-- グループ内の is_primary を単一化するトリガ(任意:ONにする場合)
CREATE OR REPLACE FUNCTION public.tg_enforce_single_primary_per_common()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
  IF NEW.is_primary THEN
    UPDATE public.portal_view
      SET is_primary = false, updated_at = now()
      WHERE common_id = NEW.common_id
        AND id <> NEW.id
        AND is_primary = true;
  END IF;
  RETURN NEW;
END
$$;

DROP TRIGGER IF EXISTS trg_single_primary_per_common ON public.portal_view;
CREATE TRIGGER trg_single_primary_per_common
BEFORE INSERT OR UPDATE OF is_primary, common_id ON public.portal_view
FOR EACH ROW EXECUTE FUNCTION public.tg_enforce_single_primary_per_common();

COMMIT;

------------------------------------------------------------
-- 4) タブ(portal_tab_portal)
------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.portal_tab_portal (
  id                  bigserial PRIMARY KEY,
  view_id             bigint NOT NULL REFERENCES public.portal_view(id) ON DELETE CASCADE,
  tab_key             text   NOT NULL,
  page_idx            integer NOT NULL DEFAULT 10,
  tab_label_ja        text,
  tab_label_en        text,
  child_model         text,
  child_link_field    text,
  origin              text,
  module              text,
  is_codegen_target   boolean DEFAULT false,
  notes               text,
  github_url          text,
  view_mode           text,
  tree_view_xmlid     text,
  form_view_xmlid     text,
  subview_policy_tree text,
  subview_policy_form text,
  use_domain          boolean,
  domain_raw          text,
  use_context         boolean,
  context_raw         text,
  inline_edit         boolean,
  allow_create_rows   boolean,
  allow_delete_rows   boolean,
  options_raw         jsonb,
  created_at          timestamptz NOT NULL DEFAULT now(),
  updated_at          timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT uq_portal_tab_portal_view_tab UNIQUE(view_id, tab_key)
);
CREATE INDEX IF NOT EXISTS idx_portal_tab_portal_view ON public.portal_tab_portal(view_id);

CREATE TRIGGER trg_touch_portal_tab_portal
BEFORE UPDATE ON public.portal_tab_portal
FOR EACH ROW EXECUTE FUNCTION public.tg_touch_updated_at();

------------------------------------------------------------
-- 5) スマートボタン(portal_smart_button)
------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.portal_smart_button (
  id                 bigserial PRIMARY KEY,
  view_id            bigint  NOT NULL REFERENCES public.portal_view(id) ON DELETE CASCADE,
  label_i18n         jsonb   DEFAULT '{}'::jsonb,
  button_key         text    NOT NULL,
  target_model       text,
  origin             text,
  action_type        text,
  action_ref         text,
  target             text,
  show_count         boolean,
  sequence           integer,
  notes              text,
  created_at         timestamptz NOT NULL DEFAULT now(),
  updated_at         timestamptz NOT NULL DEFAULT now(),
  notes_i18n         jsonb,
  view_xmlid         text,
  cached_ui_view_id  bigint,
  dest_view_url      text,
  is_codegen_target  boolean,
  show_in_inspection boolean,
  groups             jsonb,
  badge_count_expr   text,
  domain             jsonb,
  context            jsonb,
  CONSTRAINT uq_portal_smart_button_view_key UNIQUE(view_id, button_key)
);
CREATE INDEX IF NOT EXISTS idx_portal_smart_button_view ON public.portal_smart_button(view_id);

CREATE TRIGGER trg_touch_portal_smart_button
BEFORE UPDATE ON public.portal_smart_button
FOR EACH ROW EXECUTE FUNCTION public.tg_touch_updated_at();

------------------------------------------------------------
-- 6) 補助関数:日本語データ型ラベルの選定(ck_ttype_jpと整合)
------------------------------------------------------------
CREATE OR REPLACE FUNCTION public._pick_jp_datatype_label(p_ttype text)
RETURNS text AS $$
DECLARE
  allowed text[];
  cand    text[];
  v       text;
BEGIN
  SELECT array_agg(m[1]) INTO allowed
  FROM pg_constraint c
  CROSS JOIN LATERAL regexp_matches(pg_get_constraintdef(c.oid), '''([^'']+)''', 'g') AS m
  WHERE c.conname = 'ck_ttype_jp'
    AND c.conrelid = 'public.portal_fields'::regclass;

  IF allowed IS NULL OR array_length(allowed,1) IS NULL THEN
    RETURN '文字列';
  END IF;

  p_ttype := lower(coalesce(p_ttype,''));
  cand := CASE p_ttype
    WHEN 'char'      THEN ARRAY['テキスト','文字列','文字','テキスト型','文字列型']
    WHEN 'text'      THEN ARRAY['テキスト','長文','文字列']
    WHEN 'integer'   THEN ARRAY['整数','数値','整数型']
    WHEN 'float'     THEN ARRAY['小数','実数','浮動小数点']
    WHEN 'boolean'   THEN ARRAY['真偽','ブール','論理値']
    WHEN 'date'      THEN ARRAY['日付']
    WHEN 'datetime'  THEN ARRAY['日時','タイムスタンプ']
    WHEN 'json'      THEN ARRAY['JSON','構造化']
    WHEN 'many2one'  THEN ARRAY['参照','外部キー']
    WHEN 'one2many'  THEN ARRAY['複数参照','1対多']
    WHEN 'many2many' THEN ARRAY['多対多']
    ELSE ARRAY['テキスト','文字列']
  END;

  FOREACH v IN ARRAY cand LOOP
    IF v = ANY(allowed) THEN
      RETURN v;
    END IF;
  END LOOP;
  RETURN allowed[1];
END;
$$ LANGUAGE plpgsql STABLE;

------------------------------------------------------------
-- 7) スキャフォールド本体(tabs=portal_tab_portal / buttons=portal_smart_button)
------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.portal_scaffold_model(
  p_model_id     bigint,
  p_view_types   text[] DEFAULT ARRAY['list','form','search'],
  p_make_tabs    boolean DEFAULT true,
  p_make_buttons boolean DEFAULT true
) RETURNS void AS $$
DECLARE
  v_model        text;
  v_model_table  text;
  v_vtype        text;
  v_view_id      bigint;

  base_cols   text := 'model_id, model, model_table, field_name, ttype, label_i18n, code_status, notes, updated_at';
  label_expr  text := 'jsonb_build_object(''ja_JP'',''(プレースホルダ)'',''en_US'',''(placeholder)'')';
  extra_cols  text := '';
  extra_vals  text := '';
  columns_sql text;
  values_sql  text;
  full_sql    text;

  rec RECORD;
  def_sql text;
  jp_dtype text;
BEGIN
  -- 対象モデル
  SELECT pm.model, COALESCE(pm.model_table, replace(pm.model,'.','_'))
    INTO v_model, v_model_table
  FROM public.portal_model pm
  WHERE pm.id = p_model_id;

  IF v_model IS NULL THEN
    RAISE EXCEPTION 'portal_model id % not found', p_model_id;
  END IF;

  PERFORM pg_advisory_xact_lock(hashtext(v_model));

  -- 日本語データ型ラベル(ck_ttype_jp に合わせる)
  jp_dtype := public._pick_jp_datatype_label('char');

  -- ===== portal_fields:NOT NULL/既定無し列を安全値で補完 =====
  FOR rec IN
    SELECT att.attname AS col,
           format_type(att.atttypid, att.atttypmod) AS typ
    FROM pg_attribute att
    JOIN pg_class t ON t.oid = att.attrelid
    JOIN pg_namespace n ON n.oid = t.relnamespace
    WHERE n.nspname='public' AND t.relname='portal_fields'
      AND att.attnum > 0 AND NOT att.attisdropped
      AND att.attnotnull AND NOT att.atthasdef
      AND att.attname NOT IN ('id','model_id','model','model_table','field_name','ttype',
                              'label_i18n','code_status','notes','updated_at')
  LOOP
    IF     rec.col = 'モデル技術名'     THEN def_sql := quote_literal(v_model)       || '::text';
    ELSIF  rec.col = 'モデル物理名'     THEN def_sql := quote_literal(v_model_table) || '::text';
    ELSIF  rec.col = 'フィールド技術名' THEN def_sql := quote_literal('__placeholder__') || '::text';
    ELSIF  rec.col = 'データ型'         THEN def_sql := quote_literal(jp_dtype)      || '::text';
    ELSE
      def_sql :=
        CASE
          WHEN rec.typ IN ('jsonb','json')               THEN '''{}''::'||rec.typ
          WHEN rec.typ LIKE '%[]'                        THEN '''{}''::'||rec.typ
          WHEN rec.typ = 'boolean'                       THEN 'false'
          WHEN rec.typ LIKE 'timestamp%'                 THEN 'now()'
          WHEN rec.typ = 'date'                          THEN 'current_date'
          WHEN rec.typ = 'uuid'                          THEN 'gen_random_uuid()'
          WHEN rec.typ IN ('smallint','integer','bigint','numeric','real','double precision')
                                                         THEN '0'
          ELSE 'CAST('''' AS '||rec.typ||')'
        END;
    END IF;

    extra_cols := extra_cols || CASE WHEN extra_cols = '' THEN '' ELSE ', ' END || quote_ident(rec.col);
    extra_vals := extra_vals || CASE WHEN extra_vals = '' THEN '' ELSE ', ' END || def_sql;
  END LOOP;

  columns_sql := base_cols || CASE WHEN extra_cols <> '' THEN ', '||extra_cols ELSE '' END;
  values_sql  := format(
                   '%s, %L, %L, %L, %L, %s, %L, %L, now()',
                   p_model_id, v_model, v_model_table,
                   '__placeholder__', 'char', label_expr,
                   'placeholder', 'scaffold: fields empty box'
                 );

  full_sql := 'INSERT INTO public.portal_fields ('||columns_sql||') '||
              'SELECT '||values_sql||
              CASE WHEN extra_vals <> '' THEN ', '||extra_vals ELSE '' END||
              ' WHERE NOT EXISTS (SELECT 1 FROM public.portal_fields WHERE model='||quote_literal(v_model)||');';
  EXECUTE full_sql;

  -- ===== portal_view + 付随 =====
  FOREACH v_vtype IN ARRAY p_view_types LOOP
    INSERT INTO public.portal_view(model_id, model, view_type, view_name, enabled, priority_num)
    SELECT p_model_id, v_model, v_vtype, v_model || ' ' || v_vtype, true, 16
    WHERE NOT EXISTS (
      SELECT 1 FROM public.portal_view WHERE model_id = p_model_id AND view_type = v_vtype
    )
    RETURNING id INTO v_view_id;

    IF v_view_id IS NULL THEN
      SELECT id INTO v_view_id
        FROM public.portal_view
       WHERE model_id = p_model_id AND view_type = v_vtype
       ORDER BY id ASC LIMIT 1;
    END IF;

    IF v_view_id IS NOT NULL THEN
      INSERT INTO public.portal_view_common(view_id, display_fields)
      SELECT v_view_id, '[]'::jsonb
      ON CONFLICT (view_id) DO NOTHING;

      INSERT INTO public.portal_view_permissions(view_id, can_create, can_edit, can_delete, inline_edit, mass_edit, show_invisible)
      SELECT v_view_id, true, true, false, false, false, false
      ON CONFLICT (view_id) DO NOTHING;

      IF v_vtype = 'list' THEN
        INSERT INTO public.portal_list_settings(view_id, inline_edit)
        SELECT v_view_id, false
        ON CONFLICT (view_id) DO NOTHING;
      END IF;

      IF v_vtype = 'form' THEN
        INSERT INTO public.portal_form_settings(view_id, show_header, show_footer)
        SELECT v_view_id, true, true
        ON CONFLICT (view_id) DO NOTHING;
      END IF;

      IF v_vtype = 'search' THEN
        INSERT INTO public.portal_search_settings(view_id)
        SELECT v_view_id
        ON CONFLICT (view_id) DO NOTHING;
      END IF;

      -- TABS: portal_tab_portal
      IF p_make_tabs AND v_vtype = 'form' THEN
        INSERT INTO public.portal_tab_portal(view_id, tab_key, page_idx, tab_label_ja, tab_label_en, origin, notes)
        SELECT v_view_id, x.tab_key, x.page_idx, x.tab_label_ja, x.tab_label_en, 'portal', 'scaffold: tabs empty'
        FROM (VALUES
          ('main',      10, '基本',     'Main'),
          ('relations', 20, '関連',     'Relations'),
          ('notes',     30, 'メモ',     'Notes')
        ) AS x(tab_key, page_idx, tab_label_ja, tab_label_en)
        WHERE NOT EXISTS (
          SELECT 1 FROM public.portal_tab_portal t
           WHERE t.view_id = v_view_id AND t.tab_key = x.tab_key
        );
      END IF;

      -- SMART BUTTONS: portal_smart_button
      IF p_make_buttons AND v_vtype = 'form' THEN
        INSERT INTO public.portal_smart_button(
          view_id, button_key, label_i18n, origin, action_type, target, show_count, sequence, notes
        )
        SELECT v_view_id, x.button_key,
               jsonb_build_object('ja_JP', x.label_ja, 'en_US', x.label_en),
               'portal', 'other', 'current', false, x.sequence, 'scaffold: button placeholder'
        FROM (VALUES
          ('placeholder', 'ボタン', 'Button', 10)
        ) AS x(button_key, label_ja, label_en, sequence)
        WHERE NOT EXISTS (
          SELECT 1 FROM public.portal_smart_button b
           WHERE b.view_id = v_view_id AND b.button_key = x.button_key
        );
      END IF;
    END IF;

    v_view_id := NULL;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

------------------------------------------------------------
-- 8) モデル挿入時の自動スキャフォールド・トリガ
------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.trg_portal_model_scaffold()
RETURNS trigger AS $$
BEGIN
  PERFORM public.portal_scaffold_model(NEW.id, ARRAY['list','form','search'], true, true);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_after_ins_portal_model_scaffold ON public.portal_model;
CREATE TRIGGER trg_after_ins_portal_model_scaffold
AFTER INSERT ON public.portal_model
FOR EACH ROW EXECUTE FUNCTION public.trg_portal_model_scaffold();

COMMIT;

-- ■ 動作確認(任意)-----------------------------------------
-- 例: モデル1件を作ると、自動で fields / views / tabs / buttons が生成されます
-- INSERT INTO public.portal_model(model, model_table, label_i18n)
-- VALUES ('demo.task', 'demo_task', '{"ja_JP":"タスク","en_US":"Task"}');

Comments

コメントを残す

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