unis_crm/sql/init_full_pg17.sql

697 lines
26 KiB
PL/PgSQL

-- PostgreSQL 17 full initialization script for public schema
-- Usage:
-- psql -d your_database -f sql/init_full_pg17.sql
--
-- Structure:
-- 1) base schema objects
-- 2) indexes / triggers / comments
-- 3) compatibility absorption for old environments
--
-- Notes:
-- - This is the current canonical initialization entry.
-- - Historical incremental scripts are archived under sql/archive/.
begin;
set search_path to public;
-- =====================================================================
-- Section 1. Utilities
-- =====================================================================
-- Unified trigger function for updated_at maintenance.
create or replace function set_updated_at()
returns trigger
language plpgsql
as $$
begin
new.updated_at = now();
return new;
end;
$$;
create or replace function create_trigger_if_not_exists(trigger_name text, table_name text)
returns void
language plpgsql
as $$
begin
if not exists (
select 1
from pg_trigger t
join pg_class c on c.oid = t.tgrelid
join pg_namespace n on n.oid = c.relnamespace
where t.tgname = trigger_name
and c.relname = table_name
and n.nspname = current_schema()
) then
execute format(
'create trigger %I before update on %I for each row execute function set_updated_at()',
trigger_name,
table_name
);
end if;
end;
$$;
-- =====================================================================
-- Section 2. Base tables
-- =====================================================================
create table if not exists sys_user (
id bigint generated by default as identity primary key,
user_code varchar(50),
username varchar(50) not null,
real_name varchar(50) not null,
mobile varchar(20),
email varchar(100),
org_id bigint,
job_title varchar(100),
status smallint not null default 1,
hire_date date,
avatar_url varchar(255),
password_hash varchar(255),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint uk_sys_user_username unique (username),
constraint uk_sys_user_mobile unique (mobile)
);
create table if not exists crm_customer (
id bigint generated by default as identity primary key,
customer_code varchar(50),
customer_name varchar(200) not null,
customer_type varchar(50),
industry varchar(50),
province varchar(50),
city varchar(50),
address varchar(255),
owner_user_id bigint,
source varchar(50),
status varchar(30) not null default 'potential'
check (status in ('potential', 'following', 'won', 'lost')),
remark text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint uk_crm_customer_code unique (customer_code)
);
create table if not exists crm_opportunity (
id bigint generated by default as identity primary key,
opportunity_code varchar(50) not null,
opportunity_name varchar(200) not null,
customer_id bigint not null,
owner_user_id bigint not null,
sales_expansion_id bigint,
channel_expansion_id bigint,
project_location varchar(100),
operator_name varchar(100),
amount numeric(18, 2) not null default 0,
expected_close_date date,
confidence_pct smallint not null default 0 check (confidence_pct between 0 and 100),
stage varchar(50) not null default 'initial_contact',
opportunity_type varchar(50),
product_type varchar(100),
source varchar(50),
competitor_name varchar(200),
archived boolean not null default false,
pushed_to_oms boolean not null default false,
oms_push_time timestamptz,
description text,
status varchar(30) not null default 'active'
check (status in ('active', 'won', 'lost', 'closed')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint uk_crm_opportunity_code unique (opportunity_code),
constraint fk_crm_opportunity_customer foreign key (customer_id) references crm_customer(id)
);
create table if not exists crm_opportunity_followup (
id bigint generated by default as identity primary key,
opportunity_id bigint not null,
followup_time timestamptz not null,
followup_type varchar(50) not null,
content text not null,
next_action varchar(255),
followup_user_id bigint not null,
source_type varchar(30),
source_id bigint,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint fk_crm_opportunity_followup_opportunity
foreign key (opportunity_id) references crm_opportunity(id) on delete cascade
);
create table if not exists crm_sales_expansion (
id bigint generated by default as identity primary key,
employee_no varchar(50) not null,
candidate_name varchar(50) not null,
office_name varchar(100),
mobile varchar(20),
email varchar(100),
target_dept varchar(100),
industry varchar(50),
title varchar(100),
intent_level varchar(20) not null default 'medium'
check (intent_level in ('high', 'medium', 'low')),
stage varchar(50) not null default 'initial_contact',
has_desktop_exp boolean not null default false,
in_progress boolean not null default true,
employment_status varchar(20) not null default 'active'
check (employment_status in ('active', 'left', 'joined', 'abandoned')),
expected_join_date date,
owner_user_id bigint not null,
remark text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists crm_channel_expansion (
id bigint generated by default as identity primary key,
channel_code varchar(50),
province varchar(50),
channel_name varchar(200) not null,
office_address varchar(255),
channel_industry varchar(100),
annual_revenue numeric(18, 2),
staff_size integer check (staff_size is null or staff_size >= 0),
contact_established_date date,
intent_level varchar(20) not null default 'medium'
check (intent_level in ('high', 'medium', 'low')),
has_desktop_exp boolean not null default false,
contact_name varchar(50),
contact_title varchar(100),
contact_mobile varchar(20),
channel_attribute varchar(100),
internal_attribute varchar(100),
stage varchar(50) not null default 'initial_contact',
landed_flag boolean not null default false,
expected_sign_date date,
owner_user_id bigint not null,
remark text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists crm_channel_expansion_contact (
id bigint generated by default as identity primary key,
channel_expansion_id bigint not null,
contact_name varchar(50),
contact_mobile varchar(20),
contact_title varchar(100),
sort_order integer not null default 1,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint fk_crm_channel_expansion_contact_channel
foreign key (channel_expansion_id) references crm_channel_expansion(id) on delete cascade
);
create table if not exists crm_expansion_followup (
id bigint generated by default as identity primary key,
biz_type varchar(20) not null check (biz_type in ('sales', 'channel')),
biz_id bigint not null,
followup_time timestamptz not null,
followup_type varchar(50) not null,
content text not null,
next_action varchar(255),
followup_user_id bigint not null,
visit_start_time timestamptz,
evaluation_content text,
next_plan text,
source_type varchar(30),
source_id bigint,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists work_checkin (
id bigint generated by default as identity primary key,
user_id bigint not null,
checkin_date date not null,
checkin_time timestamptz not null,
biz_type varchar(20) check (biz_type is null or biz_type in ('sales', 'channel', 'opportunity')),
biz_id bigint,
biz_name varchar(200),
longitude numeric(10, 6),
latitude numeric(10, 6),
location_text varchar(255) not null,
remark varchar(500),
user_name varchar(100),
dept_name varchar(200),
status varchar(30) not null default 'normal'
check (status in ('normal', 'abnormal', 'reissue')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists work_daily_report (
id bigint generated by default as identity primary key,
user_id bigint not null,
report_date date not null,
work_content text,
tomorrow_plan text,
source_type varchar(30) not null default 'manual'
check (source_type in ('manual', 'voice')),
submit_time timestamptz,
status varchar(30) not null default 'draft'
check (status in ('draft', 'submitted', 'read', 'reviewed')),
score integer check (score is null or score between 0 and 100),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint uk_work_daily_report_user_date unique (user_id, report_date)
);
create table if not exists work_daily_report_comment (
id bigint generated by default as identity primary key,
report_id bigint not null,
reviewer_user_id bigint not null,
score integer check (score is null or score between 0 and 100),
comment_content text,
reviewed_at timestamptz not null default now(),
created_at timestamptz not null default now(),
constraint fk_work_daily_report_comment_report
foreign key (report_id) references work_daily_report(id) on delete cascade
);
create table if not exists work_todo (
id bigint generated by default as identity primary key,
user_id bigint not null,
title varchar(200) not null,
biz_type varchar(30) not null default 'other'
check (biz_type in ('opportunity', 'expansion', 'report', 'other')),
biz_id bigint,
due_date timestamptz,
status varchar(20) not null default 'todo'
check (status in ('todo', 'done', 'canceled')),
priority varchar(20) not null default 'medium'
check (priority in ('high', 'medium', 'low')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists sys_activity_log (
id bigint generated by default as identity primary key,
biz_type varchar(30) not null,
biz_id bigint,
action_type varchar(50) not null,
title varchar(200) not null,
content varchar(500),
operator_user_id bigint,
created_at timestamptz not null default now()
);
-- =====================================================================
-- Section 3. Indexes / triggers / comments
-- =====================================================================
do $$
begin
if not exists (
select 1
from pg_constraint
where conname = 'fk_crm_opportunity_channel_expansion'
) then
alter table crm_opportunity
add constraint fk_crm_opportunity_channel_expansion
foreign key (channel_expansion_id) references crm_channel_expansion(id);
end if;
end $$;
create index if not exists idx_crm_customer_owner on crm_customer(owner_user_id);
create index if not exists idx_crm_customer_name on crm_customer(customer_name);
do $$
begin
if exists (
select 1
from information_schema.columns
where table_schema = current_schema()
and table_name = 'sys_user'
and column_name = 'org_id'
) then
execute 'create index if not exists idx_sys_user_org_id on sys_user(org_id)';
end if;
end;
$$;
create unique index if not exists uk_crm_sales_expansion_owner_employee_no
on crm_sales_expansion(owner_user_id, employee_no);
create index if not exists idx_crm_opportunity_customer on crm_opportunity(customer_id);
create index if not exists idx_crm_opportunity_owner on crm_opportunity(owner_user_id);
create index if not exists idx_crm_opportunity_sales_expansion on crm_opportunity(sales_expansion_id);
create index if not exists idx_crm_opportunity_stage on crm_opportunity(stage);
create index if not exists idx_crm_opportunity_expected_close on crm_opportunity(expected_close_date);
create index if not exists idx_crm_opportunity_archived on crm_opportunity(archived);
create index if not exists idx_crm_opportunity_followup_opportunity_time
on crm_opportunity_followup(opportunity_id, followup_time desc);
create index if not exists idx_crm_opportunity_followup_user on crm_opportunity_followup(followup_user_id);
create index if not exists idx_crm_sales_expansion_owner on crm_sales_expansion(owner_user_id);
create index if not exists idx_crm_sales_expansion_stage on crm_sales_expansion(stage);
create index if not exists idx_crm_sales_expansion_mobile on crm_sales_expansion(mobile);
create index if not exists idx_crm_channel_expansion_owner on crm_channel_expansion(owner_user_id);
create index if not exists idx_crm_channel_expansion_stage on crm_channel_expansion(stage);
create index if not exists idx_crm_channel_expansion_name on crm_channel_expansion(channel_name);
create index if not exists idx_crm_channel_expansion_contact_channel on crm_channel_expansion_contact(channel_expansion_id);
create index if not exists idx_crm_opportunity_channel_expansion on crm_opportunity(channel_expansion_id);
create index if not exists idx_crm_expansion_followup_biz_time
on crm_expansion_followup(biz_type, biz_id, followup_time desc);
create index if not exists idx_crm_expansion_followup_user on crm_expansion_followup(followup_user_id);
create index if not exists idx_work_checkin_user_date on work_checkin(user_id, checkin_date desc);
create index if not exists idx_work_daily_report_user_date on work_daily_report(user_id, report_date desc);
create index if not exists idx_work_daily_report_status on work_daily_report(status);
create index if not exists idx_work_daily_report_comment_report on work_daily_report_comment(report_id);
create index if not exists idx_sys_activity_log_created on sys_activity_log(created_at desc);
create index if not exists idx_sys_activity_log_biz on sys_activity_log(biz_type, biz_id);
select create_trigger_if_not_exists('trg_sys_user_updated_at', 'sys_user');
select create_trigger_if_not_exists('trg_crm_customer_updated_at', 'crm_customer');
select create_trigger_if_not_exists('trg_crm_opportunity_updated_at', 'crm_opportunity');
select create_trigger_if_not_exists('trg_crm_opportunity_followup_updated_at', 'crm_opportunity_followup');
select create_trigger_if_not_exists('trg_crm_sales_expansion_updated_at', 'crm_sales_expansion');
select create_trigger_if_not_exists('trg_crm_channel_expansion_updated_at', 'crm_channel_expansion');
select create_trigger_if_not_exists('trg_crm_channel_expansion_contact_updated_at', 'crm_channel_expansion_contact');
select create_trigger_if_not_exists('trg_crm_expansion_followup_updated_at', 'crm_expansion_followup');
select create_trigger_if_not_exists('trg_work_checkin_updated_at', 'work_checkin');
select create_trigger_if_not_exists('trg_work_daily_report_updated_at', 'work_daily_report');
select create_trigger_if_not_exists('trg_work_todo_updated_at', 'work_todo');
comment on table sys_user is '系统用户';
comment on table crm_customer is '客户主表';
comment on table crm_opportunity is '商机主表';
comment on table crm_opportunity_followup is '商机跟进记录';
comment on table crm_sales_expansion is '销售人员拓展';
comment on table crm_channel_expansion is '渠道拓展';
comment on table crm_channel_expansion_contact is '渠道拓展联系人';
comment on table crm_expansion_followup is '拓展跟进记录';
comment on table work_checkin is '外勤打卡';
comment on table work_daily_report is '日报';
comment on table work_daily_report_comment is '日报点评';
comment on table work_todo is '待办事项';
comment on table sys_activity_log is '首页动态日志';
commit;
-- =====================================================================
-- Compatibility DDL section
-- Purpose:
-- 1) make this script usable for both fresh installs and old-environment upgrades
-- 2) absorb historical DDL migration scripts into a single deployment entry
-- 3) keep base-framework tables managed by unisbase itself
--
-- Base framework dependencies not created here:
-- sys_org, sys_dict_item, sys_tenant_user, sys_role, sys_user_role ...
-- =====================================================================
begin;
set search_path to public;
-- sys_user compatibility: dept_id -> org_id
DO $$
BEGIN
IF to_regclass('public.sys_user') IS NOT NULL THEN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'sys_user'
AND column_name = 'org_id'
) THEN
ALTER TABLE public.sys_user ADD COLUMN org_id bigint;
END IF;
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'sys_user'
AND column_name = 'dept_id'
) THEN
EXECUTE 'update public.sys_user set org_id = coalesce(org_id, dept_id) where dept_id is not null';
ALTER TABLE public.sys_user DROP COLUMN dept_id;
END IF;
END IF;
END $$;
DROP INDEX IF EXISTS public.idx_sys_user_dept_id;
CREATE INDEX IF NOT EXISTS idx_sys_user_org_id ON public.sys_user(org_id);
-- crm_sales_expansion compatibility: ensure employee_no / office_name / target_dept text
ALTER TABLE IF EXISTS crm_sales_expansion
ADD COLUMN IF NOT EXISTS employee_no varchar(50),
ADD COLUMN IF NOT EXISTS office_name varchar(100),
ADD COLUMN IF NOT EXISTS target_dept varchar(100);
DO $$
BEGIN
IF to_regclass('public.crm_sales_expansion') IS NOT NULL THEN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'crm_sales_expansion'
AND column_name = 'target_dept_id'
) THEN
UPDATE crm_sales_expansion s
SET target_dept = COALESCE(
NULLIF(s.target_dept, ''),
(
SELECT d.item_label
FROM sys_dict_item d
WHERE d.type_code = 'tz_ssbm'
AND d.item_value = s.target_dept_id::varchar
AND d.status = 1
AND COALESCE(d.is_deleted, 0) = 0
ORDER BY d.sort_order ASC NULLS LAST, d.dict_item_id ASC
LIMIT 1
),
(
SELECT o.org_name
FROM sys_org o
WHERE o.id = s.target_dept_id
LIMIT 1
),
s.target_dept_id::varchar
)
WHERE s.target_dept_id IS NOT NULL;
ALTER TABLE crm_sales_expansion DROP COLUMN target_dept_id;
END IF;
UPDATE crm_sales_expansion
SET employee_no = concat('EMP', lpad(id::text, 6, '0'))
WHERE employee_no IS NULL OR btrim(employee_no) = '';
WITH duplicated AS (
SELECT
id,
row_number() over (partition by owner_user_id, employee_no order by id asc) AS rn
FROM crm_sales_expansion
WHERE employee_no IS NOT NULL
AND btrim(employee_no) <> ''
)
UPDATE crm_sales_expansion s
SET employee_no = concat(s.employee_no, '-', s.id)
FROM duplicated d
WHERE s.id = d.id
AND d.rn > 1;
IF NOT EXISTS (
SELECT 1 FROM crm_sales_expansion WHERE employee_no IS NULL OR btrim(employee_no) = ''
) THEN
ALTER TABLE crm_sales_expansion
ALTER COLUMN employee_no SET NOT NULL;
END IF;
END IF;
END $$;
DO $$
BEGIN
IF to_regclass('public.crm_sales_expansion') IS NOT NULL THEN
IF NOT EXISTS (
SELECT 1
FROM pg_indexes
WHERE schemaname = 'public'
AND indexname = 'uk_crm_sales_expansion_owner_employee_no'
) THEN
CREATE UNIQUE INDEX uk_crm_sales_expansion_owner_employee_no
ON crm_sales_expansion(owner_user_id, employee_no);
END IF;
END IF;
END $$;
-- crm_opportunity compatibility: absorb old extension fields and relationships
ALTER TABLE IF EXISTS crm_opportunity
ADD COLUMN IF NOT EXISTS sales_expansion_id bigint,
ADD COLUMN IF NOT EXISTS channel_expansion_id bigint,
ADD COLUMN IF NOT EXISTS project_location varchar(100),
ADD COLUMN IF NOT EXISTS operator_name varchar(100),
ADD COLUMN IF NOT EXISTS competitor_name varchar(200);
DO $$
BEGIN
IF to_regclass('public.crm_opportunity') IS NOT NULL THEN
ALTER TABLE public.crm_opportunity
DROP CONSTRAINT IF EXISTS crm_opportunity_stage_check;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'fk_crm_opportunity_sales_expansion'
) THEN
ALTER TABLE public.crm_opportunity
ADD CONSTRAINT fk_crm_opportunity_sales_expansion
FOREIGN KEY (sales_expansion_id) REFERENCES public.crm_sales_expansion(id);
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'fk_crm_opportunity_channel_expansion'
) THEN
ALTER TABLE public.crm_opportunity
ADD CONSTRAINT fk_crm_opportunity_channel_expansion
FOREIGN KEY (channel_expansion_id) REFERENCES public.crm_channel_expansion(id);
END IF;
END IF;
END $$;
-- crm_channel_expansion compatibility: absorb detail columns and contact sub-table
ALTER TABLE IF EXISTS crm_channel_expansion
ADD COLUMN IF NOT EXISTS channel_code varchar(50),
ADD COLUMN IF NOT EXISTS office_address varchar(255),
ADD COLUMN IF NOT EXISTS channel_industry varchar(100),
ADD COLUMN IF NOT EXISTS contact_established_date date,
ADD COLUMN IF NOT EXISTS intent_level varchar(20),
ADD COLUMN IF NOT EXISTS has_desktop_exp boolean,
ADD COLUMN IF NOT EXISTS channel_attribute varchar(100),
ADD COLUMN IF NOT EXISTS internal_attribute varchar(100);
DO $$
BEGIN
IF to_regclass('public.crm_channel_expansion') IS NOT NULL THEN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'crm_channel_expansion'
AND column_name = 'industry'
) THEN
EXECUTE 'update public.crm_channel_expansion set channel_industry = coalesce(channel_industry, industry) where channel_industry is null and industry is not null';
END IF;
UPDATE crm_channel_expansion
SET intent_level = COALESCE(intent_level, 'medium'),
has_desktop_exp = COALESCE(has_desktop_exp, false);
ALTER TABLE crm_channel_expansion
ALTER COLUMN intent_level SET DEFAULT 'medium',
ALTER COLUMN has_desktop_exp SET DEFAULT false;
IF EXISTS (
SELECT 1 FROM crm_channel_expansion WHERE intent_level IS NULL
) THEN
RAISE NOTICE 'crm_channel_expansion.intent_level still has null values before not-null enforcement';
ELSE
ALTER TABLE crm_channel_expansion
ALTER COLUMN intent_level SET NOT NULL;
END IF;
IF EXISTS (
SELECT 1 FROM crm_channel_expansion WHERE has_desktop_exp IS NULL
) THEN
RAISE NOTICE 'crm_channel_expansion.has_desktop_exp still has null values before not-null enforcement';
ELSE
ALTER TABLE crm_channel_expansion
ALTER COLUMN has_desktop_exp SET NOT NULL;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'crm_channel_expansion_intent_level_check'
) THEN
ALTER TABLE crm_channel_expansion
ADD CONSTRAINT crm_channel_expansion_intent_level_check
CHECK (intent_level IN ('high', 'medium', 'low'));
END IF;
END IF;
END $$;
CREATE TABLE IF NOT EXISTS crm_channel_expansion_contact (
id bigint generated by default as identity primary key,
channel_expansion_id bigint not null,
contact_name varchar(50),
contact_mobile varchar(20),
contact_title varchar(100),
sort_order integer not null default 1,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint fk_crm_channel_expansion_contact_channel
foreign key (channel_expansion_id) references crm_channel_expansion(id) on delete cascade
);
DO $$
BEGIN
IF to_regclass('public.crm_channel_expansion') IS NOT NULL
AND to_regclass('public.crm_channel_expansion_contact') IS NOT NULL THEN
INSERT INTO crm_channel_expansion_contact (
channel_expansion_id,
contact_name,
contact_mobile,
contact_title,
sort_order,
created_at,
updated_at
)
SELECT c.id, c.contact_name, c.contact_mobile, c.contact_title, 1, now(), now()
FROM crm_channel_expansion c
WHERE (COALESCE(btrim(c.contact_name), '') <> ''
OR COALESCE(btrim(c.contact_mobile), '') <> ''
OR COALESCE(btrim(c.contact_title), '') <> '')
AND NOT EXISTS (
SELECT 1
FROM crm_channel_expansion_contact cc
WHERE cc.channel_expansion_id = c.id
);
END IF;
END $$;
-- follow-up compatibility: source fields and structured work-report fields
ALTER TABLE IF EXISTS crm_expansion_followup
ADD COLUMN IF NOT EXISTS visit_start_time timestamptz,
ADD COLUMN IF NOT EXISTS evaluation_content text,
ADD COLUMN IF NOT EXISTS next_plan text,
ADD COLUMN IF NOT EXISTS source_type varchar(30),
ADD COLUMN IF NOT EXISTS source_id bigint;
ALTER TABLE IF EXISTS crm_opportunity_followup
ADD COLUMN IF NOT EXISTS source_type varchar(30),
ADD COLUMN IF NOT EXISTS source_id bigint;
-- work_checkin compatibility: relation fields
ALTER TABLE IF EXISTS work_checkin
ADD COLUMN IF NOT EXISTS biz_type varchar(20),
ADD COLUMN IF NOT EXISTS biz_id bigint,
ADD COLUMN IF NOT EXISTS biz_name varchar(200),
ADD COLUMN IF NOT EXISTS user_name varchar(100),
ADD COLUMN IF NOT EXISTS dept_name varchar(200);
DO $$
BEGIN
IF to_regclass('public.work_checkin') IS NOT NULL
AND NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conrelid = 'public.work_checkin'::regclass
AND conname = 'work_checkin_biz_type_check'
) THEN
ALTER TABLE public.work_checkin
ADD CONSTRAINT work_checkin_biz_type_check
CHECK (biz_type IS NULL OR biz_type IN ('sales', 'channel', 'opportunity'));
END IF;
END $$;
-- additional indexes absorbed from historical DDLs
CREATE INDEX IF NOT EXISTS idx_crm_opportunity_channel_expansion
ON crm_opportunity(channel_expansion_id);
CREATE INDEX IF NOT EXISTS idx_crm_expansion_followup_source
ON crm_expansion_followup(source_type, source_id);
CREATE INDEX IF NOT EXISTS idx_crm_opportunity_followup_source
ON crm_opportunity_followup(source_type, source_id);
CREATE INDEX IF NOT EXISTS idx_crm_channel_expansion_contact_channel
ON crm_channel_expansion_contact(channel_expansion_id);
commit;