痛点:原来建客户只能填公司名等 7 个字段,联系人必须先保存、进详情页、 再加一遍,一件事拆成三步;详情页也没地方记公司背景。 建档表单 · 补上后端本来就收但表单没暴露的字段:公司英文名、地址、来源 · 新增「主要联系人」一组(姓名/职位/邮箱/电话/WhatsApp),自动标为主联系人 · 联系人与客户在**同一个事务**里创建 —— 分两个请求的话,客户写成功、 联系人写失败会留下一条没人能联系的半成品客户 · 姓名留空则整块不创建,「只想先记个公司名」的用法完全不受影响 公司简介(customers 新增 5 列) · founded_year / employee_count / annual_revenue / profile,外加复用 address · employee_count 与 annual_revenue 用 TEXT 而非数字:实际拿到的都是 「50-200 人」「€5-10M」这种带单位的区间,用途是给人看、不参与计算 · founded_year 卡 1800~今年:见过把「2024 年营收」当成立年份填进来的, 也见过手滑打成 20225,不合理的值宁可拒掉 · 一个字段都没填时显示引导语而不是一排「未填写」—— 说明白这些信息拿来 干什么,人才有动力填 · 跟「编辑客户信息」分成两个弹窗:基本信息是成交必须的,公司简介是 了解多少写多少,混在一起会让人以为后者也得填 为 AI 背调预留:research_at / research_sources 两列已建,背调结果将落到 上述字段,并要求每条结论带来源 URL。 Co-Authored-By: Claude Opus 5 <[email protected]>
676 lines
21 KiB
SQL
676 lines
21 KiB
SQL
-- ========================================
|
||
-- 外贸供应链管理系统数据库结构
|
||
-- ========================================
|
||
|
||
-- ================== 供应商与SKU管理 ==================
|
||
|
||
CREATE TABLE IF NOT EXISTS suppliers (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL UNIQUE,
|
||
short_name TEXT,
|
||
contact_person TEXT,
|
||
phone TEXT,
|
||
email TEXT,
|
||
address TEXT,
|
||
website TEXT,
|
||
payment_terms TEXT,
|
||
main_categories TEXT,
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS product_groups (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL,
|
||
group_key TEXT,
|
||
brand TEXT,
|
||
category TEXT,
|
||
description TEXT,
|
||
supplier_id INTEGER REFERENCES suppliers(id),
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS products (
|
||
sku TEXT PRIMARY KEY,
|
||
display_sku TEXT,
|
||
name TEXT,
|
||
name_en TEXT,
|
||
brand TEXT,
|
||
category TEXT,
|
||
spec TEXT,
|
||
price REAL,
|
||
currency TEXT DEFAULT 'CNY',
|
||
moq INTEGER,
|
||
description TEXT,
|
||
supplier_id INTEGER REFERENCES suppliers(id),
|
||
contract_id INTEGER REFERENCES contracts(id),
|
||
group_id INTEGER REFERENCES product_groups(id),
|
||
parse_method TEXT DEFAULT 'rules',
|
||
last_updated TEXT,
|
||
source_contract TEXT,
|
||
confidence TEXT DEFAULT 'high',
|
||
-- 上传者 / 归属组织 / 撞号副本来源:产品库是组织共享的,谁都能传自己那一版,
|
||
-- 「谁传的、复制自谁」必须可追,见 lib/skuCatalog.js 的 insertItem。
|
||
uploader TEXT,
|
||
org_id INTEGER,
|
||
duplicated_from TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
-- 自定义参数(包装参数、产品规格等)
|
||
CREATE TABLE IF NOT EXISTS item_attributes (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
group_id INTEGER REFERENCES product_groups(id),
|
||
sku TEXT,
|
||
name TEXT NOT NULL,
|
||
value TEXT,
|
||
sort INTEGER DEFAULT 0
|
||
);
|
||
|
||
-- 阶梯价格表
|
||
CREATE TABLE IF NOT EXISTS tier_prices (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
sku TEXT NOT NULL,
|
||
supplier_id INTEGER REFERENCES suppliers(id),
|
||
moq_from INTEGER NOT NULL,
|
||
moq_to INTEGER,
|
||
price REAL NOT NULL,
|
||
currency TEXT DEFAULT 'CNY',
|
||
valid_from TEXT,
|
||
valid_until TEXT,
|
||
quote_id INTEGER REFERENCES supplier_quotes(id),
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
-- 供应商报价记录
|
||
CREATE TABLE IF NOT EXISTS supplier_quotes (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
supplier_id INTEGER REFERENCES suppliers(id),
|
||
quote_no TEXT,
|
||
quote_date TEXT NOT NULL,
|
||
valid_until TEXT,
|
||
currency TEXT DEFAULT 'CNY',
|
||
payment_terms TEXT,
|
||
notes TEXT,
|
||
pdf_file TEXT,
|
||
status TEXT DEFAULT 'active',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
-- 价格历史
|
||
CREATE TABLE IF NOT EXISTS price_history (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
sku TEXT NOT NULL,
|
||
old_price REAL,
|
||
new_price REAL,
|
||
old_moq INTEGER,
|
||
new_moq INTEGER,
|
||
currency TEXT,
|
||
supplier_id INTEGER REFERENCES suppliers(id),
|
||
source_contract TEXT,
|
||
confidence TEXT,
|
||
-- 谁改的价:报价单里的采购价要能追到出处,和 insertItem 的手工留痕同源
|
||
changed_by TEXT,
|
||
changed_at TEXT NOT NULL
|
||
);
|
||
|
||
-- 合同
|
||
CREATE TABLE IF NOT EXISTS contracts (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
filename TEXT NOT NULL,
|
||
supplier_id INTEGER REFERENCES suppliers(id),
|
||
file_type TEXT,
|
||
source_path TEXT,
|
||
column_report TEXT,
|
||
terms TEXT,
|
||
parse_method TEXT DEFAULT 'rules',
|
||
ai_rejected INTEGER DEFAULT 0,
|
||
processed_at TEXT NOT NULL,
|
||
rows_matched INTEGER DEFAULT 0,
|
||
rows_new INTEGER DEFAULT 0,
|
||
status TEXT,
|
||
notes TEXT
|
||
);
|
||
|
||
-- 产品图片
|
||
CREATE TABLE IF NOT EXISTS product_images (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
sku TEXT NOT NULL,
|
||
filename TEXT NOT NULL,
|
||
original_name TEXT,
|
||
is_primary INTEGER DEFAULT 0,
|
||
group_id INTEGER REFERENCES product_groups(id),
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
/* 小推车:产品页里挑出来的货号暂存区,出报价单前的中转台。
|
||
全局唯一一份(不按用户分车)—— 这是当前口径,改成分人分车要同时动前端。 */
|
||
CREATE TABLE IF NOT EXISTS cart_items (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
sku TEXT NOT NULL UNIQUE,
|
||
qty INTEGER,
|
||
note TEXT,
|
||
sort INTEGER DEFAULT 0,
|
||
added_at TEXT NOT NULL
|
||
);
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* 二进制存储:图片与合同原件存进库,不摊在磁盘上。
|
||
* 单独成表而不是给 product_images / contracts 加 BLOB 列 —— 那两张表都用
|
||
* SELECT * 查,把二进制挂在同一行会让每次列表查询都把整张图读进内存。
|
||
* --------------------------------------------------------------------------- */
|
||
CREATE TABLE IF NOT EXISTS product_image_files (
|
||
image_id INTEGER PRIMARY KEY REFERENCES product_images(id),
|
||
bytes BLOB NOT NULL,
|
||
size INTEGER,
|
||
stored_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS contract_files (
|
||
contract_id INTEGER PRIMARY KEY REFERENCES contracts(id),
|
||
filename TEXT,
|
||
mime TEXT,
|
||
size INTEGER,
|
||
sha256 TEXT,
|
||
bytes BLOB NOT NULL,
|
||
stored_at TEXT
|
||
);
|
||
|
||
-- ================== 客户管理(CRM) ==================
|
||
|
||
CREATE TABLE IF NOT EXISTS customers (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
company TEXT NOT NULL,
|
||
company_en TEXT,
|
||
country TEXT,
|
||
industry TEXT,
|
||
website TEXT,
|
||
address TEXT,
|
||
phone TEXT,
|
||
email TEXT,
|
||
rating TEXT,
|
||
status TEXT DEFAULT 'active',
|
||
/* 开发结果:pending 未成交 / won 已成交 / lost 已流失(新客开发页按此分栏) */
|
||
deal_status TEXT DEFAULT 'pending',
|
||
/* 归属:谁建的归谁,默认只有归属人看得见;创建人主动丢进公海才对同组织开放 */
|
||
owner_id INTEGER REFERENCES users(id),
|
||
/* created_by 单独记「谁建的」—— 公海被领取后 owner_id 会换人,创建人这一笔仍要留痕 */
|
||
created_by INTEGER REFERENCES users(id),
|
||
org_id INTEGER REFERENCES organizations(id),
|
||
/* pool_status:owned 名下有主 / public 已丢入公海(同组织可见、可领取) */
|
||
pool_status TEXT DEFAULT 'owned',
|
||
pool_at TEXT,
|
||
pool_note TEXT,
|
||
/* 公司概况(详情页「公司简介」卡片 + AI 背调的落点)。全部选填。
|
||
employee_count / annual_revenue 用 TEXT 而非数字:实际拿到的几乎都是
|
||
「50-200 人」「€5-10M」这种带单位的区间表述,用途是给人看、不参与计算。 */
|
||
founded_year INTEGER,
|
||
employee_count TEXT,
|
||
annual_revenue TEXT,
|
||
profile TEXT,
|
||
/* 背调留痕:没有来源的结论不能拿来做决策,来源 URL 必须跟结论一起存 */
|
||
research_at TEXT,
|
||
research_sources TEXT,
|
||
source TEXT,
|
||
tags TEXT,
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT,
|
||
last_contact_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS contacts (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
customer_id INTEGER NOT NULL REFERENCES customers(id),
|
||
name TEXT NOT NULL,
|
||
name_en TEXT,
|
||
position TEXT,
|
||
department TEXT,
|
||
email TEXT,
|
||
phone TEXT,
|
||
whatsapp TEXT,
|
||
wechat TEXT,
|
||
is_primary INTEGER DEFAULT 0,
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
-- 商机
|
||
CREATE TABLE IF NOT EXISTS deals (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
customer_id INTEGER NOT NULL REFERENCES customers(id),
|
||
title TEXT NOT NULL,
|
||
amount REAL,
|
||
currency TEXT DEFAULT 'USD',
|
||
stage TEXT NOT NULL,
|
||
probability INTEGER,
|
||
expected_close_date TEXT,
|
||
actual_close_date TEXT,
|
||
products TEXT,
|
||
owner_id INTEGER REFERENCES users(id),
|
||
status TEXT DEFAULT 'open',
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
-- 跟进活动
|
||
CREATE TABLE IF NOT EXISTS activities (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
customer_id INTEGER REFERENCES customers(id),
|
||
deal_id INTEGER REFERENCES deals(id),
|
||
order_id INTEGER REFERENCES orders(id),
|
||
activity_type TEXT NOT NULL,
|
||
subject TEXT,
|
||
content TEXT,
|
||
files TEXT,
|
||
next_action TEXT,
|
||
next_action_date TEXT,
|
||
created_by INTEGER REFERENCES users(id),
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
-- 待办事项
|
||
CREATE TABLE IF NOT EXISTS todos (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
title TEXT NOT NULL,
|
||
description TEXT,
|
||
due_date TEXT,
|
||
priority TEXT DEFAULT 'medium',
|
||
status TEXT DEFAULT 'pending',
|
||
list TEXT NOT NULL DEFAULT 'schedule',
|
||
customer_id INTEGER REFERENCES customers(id),
|
||
deal_id INTEGER REFERENCES deals(id),
|
||
order_id INTEGER REFERENCES orders(id),
|
||
assigned_to INTEGER REFERENCES users(id),
|
||
created_by INTEGER REFERENCES users(id),
|
||
created_at TEXT NOT NULL,
|
||
completed_at TEXT
|
||
);
|
||
|
||
-- ================== 订单管理 ==================
|
||
|
||
CREATE TABLE IF NOT EXISTS orders (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
order_no TEXT NOT NULL UNIQUE,
|
||
customer_id INTEGER NOT NULL REFERENCES customers(id),
|
||
order_date TEXT NOT NULL,
|
||
delivery_date TEXT,
|
||
payment_terms TEXT,
|
||
total_amount REAL,
|
||
currency TEXT DEFAULT 'USD',
|
||
status TEXT DEFAULT 'draft',
|
||
owner_id INTEGER REFERENCES users(id),
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS order_items (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
order_id INTEGER NOT NULL REFERENCES orders(id),
|
||
sku TEXT NOT NULL,
|
||
product_name TEXT,
|
||
qty INTEGER NOT NULL,
|
||
unit_price REAL NOT NULL,
|
||
total_price REAL NOT NULL,
|
||
currency TEXT DEFAULT 'USD',
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
-- ================== 客户报价单 ==================
|
||
|
||
CREATE TABLE IF NOT EXISTS customer_quotations (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
quote_no TEXT NOT NULL UNIQUE,
|
||
customer_id INTEGER NOT NULL REFERENCES customers(id),
|
||
quote_date TEXT NOT NULL,
|
||
valid_until TEXT,
|
||
currency TEXT DEFAULT 'USD',
|
||
-- 条款结构化字段:payment_terms / delivery_terms 仍保留可读文本,供老页面直接展示
|
||
pi_no TEXT,
|
||
payment_method TEXT,
|
||
deposit_percent REAL,
|
||
balance_percent REAL,
|
||
delivery_method TEXT,
|
||
delivery_port TEXT,
|
||
validity_days INTEGER,
|
||
payment_terms TEXT,
|
||
delivery_terms TEXT,
|
||
status TEXT DEFAULT 'draft',
|
||
notes TEXT,
|
||
-- 系统备注:只给内部看,报价单对外不显示
|
||
internal_notes TEXT,
|
||
created_by INTEGER REFERENCES users(id),
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS quotation_items (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
quotation_id INTEGER NOT NULL REFERENCES customer_quotations(id),
|
||
sku TEXT NOT NULL,
|
||
product_name TEXT,
|
||
product_name_en TEXT,
|
||
spec TEXT,
|
||
qty INTEGER NOT NULL,
|
||
unit_price REAL NOT NULL,
|
||
total_price REAL NOT NULL,
|
||
currency TEXT DEFAULT 'USD',
|
||
-- 成本口径快照:报价单发出去之后,供应商再调价也不能让这张单的历史毛利变样
|
||
purchase_price REAL,
|
||
exchange_rate REAL,
|
||
markup_rate REAL,
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
-- ================== 自动获客(新客开发) ==================
|
||
|
||
CREATE TABLE IF NOT EXISTS prospect_runs (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL,
|
||
product_keywords TEXT,
|
||
countries TEXT,
|
||
customer_types TEXT,
|
||
exclude_keywords TEXT,
|
||
sources TEXT,
|
||
result_limit INTEGER DEFAULT 100,
|
||
status TEXT DEFAULT 'draft',
|
||
owner_id INTEGER REFERENCES users(id),
|
||
created_at TEXT NOT NULL,
|
||
completed_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS prospect_leads (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
run_id INTEGER REFERENCES prospect_runs(id),
|
||
company TEXT NOT NULL,
|
||
website TEXT,
|
||
country TEXT,
|
||
business TEXT,
|
||
contact TEXT,
|
||
email TEXT,
|
||
phone TEXT,
|
||
description TEXT,
|
||
icp_score INTEGER,
|
||
confidence INTEGER,
|
||
source TEXT,
|
||
source_url TEXT,
|
||
status TEXT DEFAULT 'new',
|
||
owner_id INTEGER REFERENCES users(id),
|
||
customer_id INTEGER REFERENCES customers(id),
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
-- ================== 系统管理 ==================
|
||
|
||
-- 组织(团队):产品/客户的归属单位,「同组织的人均可参考」以它为边界。
|
||
CREATE TABLE IF NOT EXISTS organizations (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL UNIQUE,
|
||
code TEXT,
|
||
description TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
username TEXT NOT NULL UNIQUE,
|
||
password_hash TEXT NOT NULL,
|
||
display_name TEXT,
|
||
email TEXT,
|
||
/* role = 权限角色(董事长 / 总经理 / 销售经理 / 销售业务员 …),也是「角色 → 权限组」映射的键 */
|
||
role TEXT DEFAULT 'user',
|
||
/* position = 对外展示的职位称谓,纯文字,与角色解耦 */
|
||
position TEXT,
|
||
phone TEXT,
|
||
address TEXT,
|
||
avatar TEXT,
|
||
org_id INTEGER REFERENCES organizations(id),
|
||
is_active INTEGER NOT NULL DEFAULT 1,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT,
|
||
last_login_at TEXT,
|
||
failed_count INTEGER NOT NULL DEFAULT 0,
|
||
locked_until TEXT
|
||
);
|
||
|
||
-- 权限组:一套命名的权限范围。角色被指派进某个组,即获得该组的全部权限点。
|
||
CREATE TABLE IF NOT EXISTS permission_groups (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL UNIQUE,
|
||
description TEXT,
|
||
is_system INTEGER NOT NULL DEFAULT 0,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
-- 权限组 → 权限点(permission_key 由后端 PERMISSIONS 清单定义)
|
||
CREATE TABLE IF NOT EXISTS permission_group_permissions (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
group_id INTEGER NOT NULL REFERENCES permission_groups(id),
|
||
permission_key TEXT NOT NULL,
|
||
UNIQUE (group_id, permission_key)
|
||
);
|
||
|
||
-- 角色 → 权限组(一个角色同时只属于一个组,改指派即换组)
|
||
CREATE TABLE IF NOT EXISTS role_permission_groups (
|
||
role TEXT PRIMARY KEY,
|
||
group_id INTEGER NOT NULL REFERENCES permission_groups(id),
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS sessions (
|
||
id TEXT PRIMARY KEY,
|
||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||
created_at TEXT NOT NULL,
|
||
last_seen_at TEXT NOT NULL,
|
||
expires_at TEXT NOT NULL,
|
||
user_agent TEXT,
|
||
ip TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS settings (
|
||
key TEXT PRIMARY KEY,
|
||
value TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS audit_log (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
at TEXT NOT NULL,
|
||
username TEXT,
|
||
action TEXT NOT NULL,
|
||
detail TEXT,
|
||
ip TEXT,
|
||
user_agent TEXT,
|
||
ok INTEGER NOT NULL DEFAULT 1
|
||
);
|
||
|
||
-- ================== 索引 ==================
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_products_supplier ON products(supplier_id);
|
||
CREATE INDEX IF NOT EXISTS idx_products_group ON products(group_id);
|
||
CREATE INDEX IF NOT EXISTS idx_products_name ON products(name);
|
||
CREATE INDEX IF NOT EXISTS idx_tier_prices_sku ON tier_prices(sku);
|
||
CREATE INDEX IF NOT EXISTS idx_price_history_sku ON price_history(sku);
|
||
CREATE INDEX IF NOT EXISTS idx_customers_owner ON customers(owner_id);
|
||
CREATE INDEX IF NOT EXISTS idx_customers_country ON customers(country);
|
||
CREATE INDEX IF NOT EXISTS idx_contacts_customer ON contacts(customer_id);
|
||
CREATE INDEX IF NOT EXISTS idx_deals_customer ON deals(customer_id);
|
||
CREATE INDEX IF NOT EXISTS idx_deals_owner ON deals(owner_id);
|
||
CREATE INDEX IF NOT EXISTS idx_activities_customer ON activities(customer_id);
|
||
CREATE INDEX IF NOT EXISTS idx_activities_deal ON activities(deal_id);
|
||
CREATE INDEX IF NOT EXISTS idx_orders_customer ON orders(customer_id);
|
||
CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_id);
|
||
CREATE INDEX IF NOT EXISTS idx_quotations_customer ON customer_quotations(customer_id);
|
||
CREATE INDEX IF NOT EXISTS idx_quotation_items_quotation ON quotation_items(quotation_id);
|
||
CREATE INDEX IF NOT EXISTS idx_prospect_leads_run ON prospect_leads(run_id);
|
||
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
||
CREATE INDEX IF NOT EXISTS idx_sessions_exp ON sessions(expires_at);
|
||
CREATE INDEX IF NOT EXISTS idx_audit_at ON audit_log(at);
|
||
|
||
-- ================== 询盘与报价主链路 ==================
|
||
CREATE TABLE IF NOT EXISTS inquiries (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
inquiry_no TEXT NOT NULL UNIQUE,
|
||
customer_id INTEGER NOT NULL REFERENCES customers(id),
|
||
inquiry_date TEXT NOT NULL,
|
||
title TEXT,
|
||
status TEXT NOT NULL DEFAULT 'draft',
|
||
currency TEXT DEFAULT 'USD',
|
||
source TEXT,
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS inquiry_items (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
inquiry_id INTEGER NOT NULL REFERENCES inquiries(id),
|
||
sku TEXT NOT NULL,
|
||
product_name TEXT,
|
||
supplier_id INTEGER REFERENCES suppliers(id),
|
||
supplier_name TEXT,
|
||
purchase_price REAL NOT NULL,
|
||
currency TEXT NOT NULL DEFAULT 'CNY',
|
||
exchange_rate REAL NOT NULL DEFAULT 1,
|
||
markup_rate REAL NOT NULL DEFAULT 0,
|
||
markup_factor REAL NOT NULL DEFAULT 1,
|
||
exw REAL NOT NULL,
|
||
quantity INTEGER NOT NULL DEFAULT 1,
|
||
packaging_snapshot TEXT,
|
||
product_snapshot TEXT,
|
||
quoted_at TEXT NOT NULL,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS follow_up_records (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
customer_id INTEGER NOT NULL REFERENCES customers(id),
|
||
activity_type TEXT NOT NULL,
|
||
content TEXT NOT NULL,
|
||
next_action TEXT,
|
||
next_action_date TEXT,
|
||
contact_id INTEGER REFERENCES contacts(id),
|
||
inquiry_id INTEGER REFERENCES inquiries(id),
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS product_packaging (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
sku TEXT NOT NULL UNIQUE REFERENCES products(sku),
|
||
inner_pack_qty INTEGER,
|
||
carton_qty INTEGER,
|
||
carton_length REAL,
|
||
carton_width REAL,
|
||
carton_height REAL,
|
||
carton_weight REAL,
|
||
unit_length REAL,
|
||
unit_width REAL,
|
||
unit_height REAL,
|
||
unit_weight REAL,
|
||
packaging_material TEXT,
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS contract_import_jobs (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
job_no TEXT NOT NULL UNIQUE,
|
||
status TEXT NOT NULL DEFAULT 'queued',
|
||
file_count INTEGER NOT NULL DEFAULT 0,
|
||
source_files TEXT,
|
||
parse_method TEXT NOT NULL DEFAULT 'rules',
|
||
rule_version TEXT NOT NULL DEFAULT 'mvp-v1',
|
||
-- column_map:本次认列结果(字段 → 列号/表头/得分),出问题时唯一能自证的现场
|
||
column_map TEXT,
|
||
-- parse_source:这份结果是怎么来的 —— rules(字符规则)/ ai(模型解析)
|
||
parse_source TEXT DEFAULT 'rules',
|
||
notes TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT,
|
||
completed_at TEXT
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS contract_import_items (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
job_id INTEGER NOT NULL REFERENCES contract_import_jobs(id),
|
||
filename TEXT NOT NULL,
|
||
row_no INTEGER,
|
||
raw_text TEXT,
|
||
sku TEXT,
|
||
product_name TEXT,
|
||
spec TEXT,
|
||
purchase_price REAL,
|
||
currency TEXT,
|
||
quantity INTEGER,
|
||
moq INTEGER,
|
||
status TEXT NOT NULL DEFAULT 'pending_review',
|
||
review_note TEXT,
|
||
parse_source TEXT DEFAULT 'rules',
|
||
reviewed_by INTEGER REFERENCES users(id),
|
||
reviewed_at TEXT,
|
||
-- committed_at / product_sku:入库留痕 —— 什么时候进的库、进成了哪个货号
|
||
-- (撞号时实际落库的是 -COPY2,光看 sku 列看不出来)
|
||
committed_at TEXT,
|
||
product_sku TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_inquiries_customer ON inquiries(customer_id);
|
||
CREATE INDEX IF NOT EXISTS idx_inquiries_status_date ON inquiries(status, inquiry_date);
|
||
CREATE INDEX IF NOT EXISTS idx_inquiry_items_inquiry ON inquiry_items(inquiry_id);
|
||
CREATE INDEX IF NOT EXISTS idx_inquiry_items_sku ON inquiry_items(sku);
|
||
CREATE INDEX IF NOT EXISTS idx_follow_up_customer_date ON follow_up_records(customer_id, next_action_date);
|
||
CREATE INDEX IF NOT EXISTS idx_follow_up_inquiry ON follow_up_records(inquiry_id);
|
||
CREATE INDEX IF NOT EXISTS idx_product_packaging_sku ON product_packaging(sku);
|
||
CREATE INDEX IF NOT EXISTS idx_contract_import_jobs_status ON contract_import_jobs(status, created_at);
|
||
CREATE INDEX IF NOT EXISTS idx_contract_import_items_job ON contract_import_items(job_id, status);
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 订单支出(业务员自己记的杂费:快递费、吊卡费、商检、报关、拖车、打样…)
|
||
--
|
||
-- 为什么单独一张表而不是往 orders 加几列:一笔订单的杂费是**多笔、不定项**的,
|
||
-- 寄三次样品就是三条快递费,各自有单号和日期。塞进 orders 只能存一个合计,
|
||
-- 事后对不出「这 800 块是哪来的」。
|
||
--
|
||
-- 币种是关键:快递费、吊卡费都是人民币,订单却可能是美元。所以每笔支出带自己的
|
||
-- currency,外加一个 rate_to_order —— **下单当时的汇率快照**。不存快照的话,
|
||
-- 三个月后汇率变了,历史订单的毛利会跟着变,账就永远对不上。
|
||
-- ---------------------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS order_expenses (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
order_id INTEGER NOT NULL REFERENCES orders(id),
|
||
category TEXT NOT NULL, -- 快递费 / 吊卡费 / 商检费 / 报关费 / 拖车费 / 打样费 / 唛头印刷 / 其他
|
||
amount REAL NOT NULL,
|
||
currency TEXT NOT NULL DEFAULT 'CNY',
|
||
-- 支出币种 → 订单币种的汇率快照;同币种为 1
|
||
rate_to_order REAL NOT NULL DEFAULT 1,
|
||
spent_at TEXT, -- 发生日期 YYYY-MM-DD
|
||
payee TEXT, -- 收款方:顺丰 / 某印刷厂
|
||
supplier_id INTEGER REFERENCES suppliers(id),
|
||
voucher_no TEXT, -- 快递单号 / 发票号,事后核对用
|
||
is_paid INTEGER NOT NULL DEFAULT 0,
|
||
note TEXT,
|
||
created_by INTEGER REFERENCES users(id),
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_order_expenses_order ON order_expenses(order_id);
|
||
CREATE INDEX IF NOT EXISTS idx_order_expenses_date ON order_expenses(spent_at);
|