2026-03-27 09:05:41 +00:00
|
|
|
-- 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
|
2026-03-20 08:39:07 +00:00
|
|
|
-- Usage:
|
2026-03-27 09:05:41 +00:00
|
|
|
-- psql -d your_database -f sql/init_full_pg17.sql
|
2026-03-20 08:39:07 +00:00
|
|
|
|
|
|
|
|
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),
|
2026-03-23 07:21:09 +00:00
|
|
|
org_id bigint,
|
2026-03-20 08:39:07 +00:00
|
|
|
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),
|
2026-03-23 07:21:09 +00:00
|
|
|
constraint uk_sys_user_mobile unique (mobile)
|
2026-03-20 08:39:07 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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,
|
2026-03-26 09:29:55 +00:00
|
|
|
sales_expansion_id bigint,
|
|
|
|
|
channel_expansion_id bigint,
|
|
|
|
|
project_location varchar(100),
|
|
|
|
|
operator_name varchar(100),
|
2026-03-20 08:39:07 +00:00
|
|
|
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),
|
2026-03-26 09:29:55 +00:00
|
|
|
stage varchar(50) not null default 'initial_contact',
|
2026-03-20 08:39:07 +00:00
|
|
|
opportunity_type varchar(50),
|
|
|
|
|
product_type varchar(100),
|
|
|
|
|
source varchar(50),
|
2026-03-26 09:29:55 +00:00
|
|
|
competitor_name varchar(200),
|
2026-03-27 09:05:41 +00:00
|
|
|
archived boolean not null default false,
|
2026-03-20 08:39:07 +00:00
|
|
|
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,
|
2026-03-26 09:29:55 +00:00
|
|
|
source_type varchar(30),
|
|
|
|
|
source_id bigint,
|
2026-03-20 08:39:07 +00:00
|
|
|
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,
|
2026-03-26 09:29:55 +00:00
|
|
|
employee_no varchar(50) not null,
|
2026-03-20 08:39:07 +00:00
|
|
|
candidate_name varchar(50) not null,
|
2026-03-26 09:29:55 +00:00
|
|
|
office_name varchar(100),
|
2026-03-20 08:39:07 +00:00
|
|
|
mobile varchar(20),
|
|
|
|
|
email varchar(100),
|
2026-03-26 09:29:55 +00:00
|
|
|
target_dept varchar(100),
|
2026-03-20 08:39:07 +00:00
|
|
|
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,
|
2026-03-26 09:29:55 +00:00
|
|
|
channel_code varchar(50),
|
2026-03-20 08:39:07 +00:00
|
|
|
province varchar(50),
|
2026-03-26 09:29:55 +00:00
|
|
|
channel_name varchar(200) not null,
|
|
|
|
|
office_address varchar(255),
|
|
|
|
|
channel_industry varchar(100),
|
2026-03-20 08:39:07 +00:00
|
|
|
annual_revenue numeric(18, 2),
|
|
|
|
|
staff_size integer check (staff_size is null or staff_size >= 0),
|
2026-03-26 09:29:55 +00:00
|
|
|
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,
|
2026-03-20 08:39:07 +00:00
|
|
|
contact_name varchar(50),
|
|
|
|
|
contact_title varchar(100),
|
|
|
|
|
contact_mobile varchar(20),
|
2026-03-26 09:29:55 +00:00
|
|
|
channel_attribute varchar(100),
|
|
|
|
|
internal_attribute varchar(100),
|
2026-03-20 08:39:07 +00:00
|
|
|
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()
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-26 09:29:55 +00:00
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-20 08:39:07 +00:00
|
|
|
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,
|
2026-03-26 09:29:55 +00:00
|
|
|
visit_start_time timestamptz,
|
|
|
|
|
evaluation_content text,
|
|
|
|
|
next_plan text,
|
|
|
|
|
source_type varchar(30),
|
|
|
|
|
source_id bigint,
|
2026-03-20 08:39:07 +00:00
|
|
|
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,
|
2026-03-26 09:29:55 +00:00
|
|
|
biz_type varchar(20) check (biz_type is null or biz_type in ('sales', 'channel', 'opportunity')),
|
|
|
|
|
biz_id bigint,
|
|
|
|
|
biz_name varchar(200),
|
2026-03-20 08:39:07 +00:00
|
|
|
longitude numeric(10, 6),
|
|
|
|
|
latitude numeric(10, 6),
|
|
|
|
|
location_text varchar(255) not null,
|
|
|
|
|
remark varchar(500),
|
2026-03-26 09:29:55 +00:00
|
|
|
user_name varchar(100),
|
|
|
|
|
dept_name varchar(200),
|
2026-03-20 08:39:07 +00:00
|
|
|
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()
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-26 09:29:55 +00:00
|
|
|
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 $$;
|
|
|
|
|
|
2026-03-20 08:39:07 +00:00
|
|
|
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);
|
2026-03-23 07:21:09 +00:00
|
|
|
create index if not exists idx_sys_user_org_id on sys_user(org_id);
|
2026-03-26 09:29:55 +00:00
|
|
|
create unique index if not exists uk_crm_sales_expansion_owner_employee_no
|
|
|
|
|
on crm_sales_expansion(owner_user_id, employee_no);
|
2026-04-02 05:19:22 +00:00
|
|
|
create sequence if not exists crm_channel_expansion_code_seq start with 1 increment by 1 minvalue 1;
|
2026-03-20 08:39:07 +00:00
|
|
|
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);
|
2026-03-26 09:29:55 +00:00
|
|
|
create index if not exists idx_crm_opportunity_sales_expansion on crm_opportunity(sales_expansion_id);
|
2026-03-20 08:39:07 +00:00
|
|
|
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);
|
2026-03-27 09:05:41 +00:00
|
|
|
create index if not exists idx_crm_opportunity_archived on crm_opportunity(archived);
|
2026-03-20 08:39:07 +00:00
|
|
|
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);
|
2026-04-02 05:19:22 +00:00
|
|
|
create unique index if not exists uk_crm_channel_expansion_code
|
|
|
|
|
on crm_channel_expansion(channel_code)
|
|
|
|
|
where channel_code is not null;
|
2026-03-26 09:29:55 +00:00
|
|
|
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);
|
2026-03-20 08:39:07 +00:00
|
|
|
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');
|
2026-03-26 09:29:55 +00:00
|
|
|
select create_trigger_if_not_exists('trg_crm_channel_expansion_contact_updated_at', 'crm_channel_expansion_contact');
|
2026-03-20 08:39:07 +00:00
|
|
|
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 '渠道拓展';
|
2026-03-26 09:29:55 +00:00
|
|
|
comment on table crm_channel_expansion_contact is '渠道拓展联系人';
|
2026-03-20 08:39:07 +00:00
|
|
|
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;
|