-- Legacy PostgreSQL 17 initialization snapshot for public schema -- Note: -- This file is kept only for historical reference. -- For new environments, use: sql/init_full_pg17.sql -- Usage: -- psql -d your_database -f sql/init_full_pg17.sql begin; set search_path to public; -- 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; $$; 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, archived_at timestamptz, 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() ); 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); create index if not exists idx_sys_user_org_id on sys_user(org_id); create unique index if not exists uk_crm_sales_expansion_owner_employee_no on crm_sales_expansion(owner_user_id, employee_no); create sequence if not exists crm_channel_expansion_code_seq start with 1 increment by 1 minvalue 1; 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_archived_at on crm_opportunity(archived_at); 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 unique index if not exists uk_crm_channel_expansion_code on crm_channel_expansion(channel_code) where channel_code is not null; 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;