Files
DrsakuraandClaude Opus 5 241fb229c6 feat(crm): 建档一步到位 + 客户详情加公司简介
痛点:原来建客户只能填公司名等 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]>
2026-10-28 16:40:29 +08:00

676 lines
21 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ========================================
-- 外贸供应链管理系统数据库结构
-- ========================================
-- ================== 供应商与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);