-- PostgreSQL 17 initialization script for public schema -- Usage: -- psql -d your_database -f sql/init_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_department ( id bigint generated by default as identity primary key, dept_code varchar(50), dept_name varchar(100) not null, parent_id bigint, manager_user_id bigint, status smallint not null default 1, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint uk_sys_department_code unique (dept_code) ); 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), dept_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), constraint fk_sys_user_dept foreign key (dept_id) references sys_department(id) ); 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, 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' check (stage in ( 'initial_contact', 'solution_discussion', 'bidding', 'business_negotiation', 'won', 'lost' )), opportunity_type varchar(50), product_type varchar(100), source varchar(50), 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, 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, candidate_name varchar(50) not null, mobile varchar(20), email varchar(100), target_dept_id bigint, 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_name varchar(200) not null, province varchar(50), industry varchar(50), annual_revenue numeric(18, 2), staff_size integer check (staff_size is null or staff_size >= 0), contact_name varchar(50), contact_title varchar(100), contact_mobile varchar(20), 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_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, 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, longitude numeric(10, 6), latitude numeric(10, 6), location_text varchar(255) not null, remark varchar(500), 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_checkin_attachment ( id bigint generated by default as identity primary key, checkin_id bigint not null, file_url varchar(255) not null, file_type varchar(30) not null check (file_type in ('image', 'audio', 'video')), file_name varchar(255), file_size bigint check (file_size is null or file_size >= 0), created_at timestamptz not null default now(), constraint fk_work_checkin_attachment_checkin foreign key (checkin_id) references work_checkin(id) on delete cascade ); 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() ); 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_dept_id on sys_user(dept_id); 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_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_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_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_checkin_attachment_checkin on work_checkin_attachment(checkin_id); 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_department_updated_at', 'sys_department'); 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_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_department is '组织部门'; 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_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;