docs: 落定业务口径并补充人工支出登记草案
口径确认(2026-10-17): - 收入 = 用户实付(orders.total_amount),已核验与快手 paymentFen 100% 一致 - 打手报酬 = 支出/成本(最终提现打款),按总额法 - 人工块第一优先级改为「人工支出可追溯」,SLA 监管延后 - 退款/仲裁暂未接入,先预留字段与冲正关系 变更: - 新增 docs/drafts/2026-10-17-人工支出登记-DDL草案.sql(manual_fulfillment_cases / manual_expense_records / manual_expense_events) - 成本中心与销售事实-DDL草案.sql:revenue_basis 改为 user_payment,补预留说明与已确认口径 - 成本中心与三块履约模型.md:§8 顺序调整、§11 改为口径确认结果、§12 改为本地克隆基线 - 财务系统集成方案.md:拍板清单加状态,分录表补人工垫付与预留冲正 - 项目全量分析报告与本轮报告同步口径,docs/README.md 登记新草案
This commit is contained in:
@@ -66,6 +66,7 @@
|
||||
| --- | --- |
|
||||
| [drafts/2026-10-17-载体判定与人工块量化.sql](drafts/2026-10-17-载体判定与人工块量化.sql) | 载体分布/人工块量化查询、`carrier_type` 判定与回填、守恒校验、人工成本区间 |
|
||||
| [drafts/2026-10-17-成本中心与销售事实-DDL草案.sql](drafts/2026-10-17-成本中心与销售事实-DDL草案.sql) | `product_catalog`、`channel_product_mappings`、`cost_items/versions/snapshots/exceptions`、`sales_daily_facts` 建表草案 |
|
||||
| [drafts/2026-10-17-人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql) | `manual_fulfillment_cases`、`manual_expense_records`(付款项目/金额/订单号/付款人/凭证)、`manual_expense_events`;依赖成本中心草案,须在其后应用 |
|
||||
|
||||
## 三、厂商协议与外部资料
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
-- ============================================================================
|
||||
-- 人工支出登记 + 轻量人工处理记录(建表草案)
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 状态:草案(DRAFT)。**未接入** apps/backend/src/db/migrations。
|
||||
-- 依赖:本文引用 product_catalog / cost_items / cost_snapshots,
|
||||
-- 必须**在** `2026-10-17-成本中心与销售事实-DDL草案.sql` **之后**应用。
|
||||
-- 接入时应拆成 `104_manual_cases.sql`、`105_manual_expenses.sql`,并先跑 `npm run check:sql`。
|
||||
-- 基线:main @ 801bfa0b;依据:2026-10-17 业务口径确认(见 docs/载体判定与成本中心落地-复核与草案-2026-10-17.md §九)。
|
||||
--
|
||||
-- 业务原话(定优先级):
|
||||
-- 「人工块现在意义不大,主要是把流程搞清楚:哪个人处理了哪些东西,付了多少钱。
|
||||
-- 现在人工完全监管不到,很多地方需要人工付款,也不知道付款项目、金额、订单号啥的。」
|
||||
--
|
||||
-- 因此本草案的顺序是:
|
||||
-- ① 人工支出可追溯(付款项目 / 金额 / 订单号 / 谁付的 / 凭证)——**核心诉求**
|
||||
-- ② 处理人可见(谁在处理哪个订单项)——用轻量 case 承载
|
||||
-- ③ SLA / 超时升级 / 监管指标——**延后**,不进本轮最小实现
|
||||
-- ============================================================================
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- §1 轻量人工处理记录(谁在处理哪些东西)
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 与 docs/成本中心与三块履约模型.md §8.3 的差异:去掉 due_at/SLA 强约束与优先级体系,
|
||||
-- 只保留“谁、什么时候、处理了哪个订单项、结果是什么”。SLA 字段等 ③ 阶段再加。
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS manual_fulfillment_cases (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
case_no TEXT NOT NULL,
|
||||
order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
||||
order_item_id BIGINT REFERENCES order_items(id) ON DELETE CASCADE,
|
||||
provider TEXT NOT NULL DEFAULT '',
|
||||
shop_id TEXT NOT NULL DEFAULT '',
|
||||
canonical_product_id BIGINT REFERENCES product_catalog(id) ON DELETE RESTRICT,
|
||||
quantity INTEGER NOT NULL DEFAULT 1,
|
||||
status TEXT NOT NULL DEFAULT 'pending_assign',
|
||||
assignee_admin_user_id BIGINT,
|
||||
assignee_name TEXT NOT NULL DEFAULT '',
|
||||
assigned_at TIMESTAMPTZ,
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
outcome TEXT NOT NULL DEFAULT '',
|
||||
delivery_reference TEXT NOT NULL DEFAULT '',
|
||||
evidence_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_manual_cases_case_no ON manual_fulfillment_cases (case_no);
|
||||
-- 一个订单项最多一个 case:与「一个订单项只有一个执行载体」保持一致
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_manual_cases_order_item
|
||||
ON manual_fulfillment_cases (order_item_id) WHERE order_item_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_cases_status ON manual_fulfillment_cases (status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_cases_assignee ON manual_fulfillment_cases (assignee_admin_user_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_cases_order ON manual_fulfillment_cases (order_id);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_cases_status') THEN
|
||||
ALTER TABLE manual_fulfillment_cases
|
||||
ADD CONSTRAINT chk_manual_cases_status
|
||||
CHECK (status IN ('pending_assign', 'assigned', 'processing', 'waiting_customer',
|
||||
'pending_review', 'completed', 'failed', 'cancelled'));
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_cases_outcome') THEN
|
||||
ALTER TABLE manual_fulfillment_cases
|
||||
ADD CONSTRAINT chk_manual_cases_outcome
|
||||
CHECK (outcome IN ('', 'delivered', 'refunded', 'failed', 'cancelled'));
|
||||
END IF;
|
||||
-- 完成必须有结果;避免“点完成但什么都没填”
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_cases_completed') THEN
|
||||
ALTER TABLE manual_fulfillment_cases
|
||||
ADD CONSTRAINT chk_manual_cases_completed
|
||||
CHECK (status <> 'completed' OR (completed_at IS NOT NULL AND outcome <> ''));
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
COMMENT ON TABLE manual_fulfillment_cases IS '轻量人工处理记录:谁在处理哪个订单项、结果如何(SLA 阶段延后)';
|
||||
COMMENT ON COLUMN manual_fulfillment_cases.evidence_json IS '交付凭证:截图对象 key、参考号等;完成后必填(由服务层校验)';
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- §2 人工支出登记(核心诉求)
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 必须能回答:这笔钱是什么项目、多少钱、关联哪个订单/订单项、谁付的、付给谁、什么时候、凭证在哪。
|
||||
-- 与成本中心的关系:
|
||||
-- cost_item_id = “付款项目”,指向 cost_items(cost_type = 'manual_service' 或其它)
|
||||
-- approved 后生成 cost_snapshots(subject_type = 'manual_case' 或 'order_item',source = 'manual_input')
|
||||
-- 影子总账期:登记 approved 即产生一条业务事件,进复式分录(借:履约成本/费用,贷:其他应付/银行存款)
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS manual_expense_records (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
expense_no TEXT NOT NULL,
|
||||
case_id BIGINT REFERENCES manual_fulfillment_cases(id) ON DELETE SET NULL,
|
||||
order_id BIGINT REFERENCES orders(id) ON DELETE SET NULL,
|
||||
order_item_id BIGINT REFERENCES order_items(id) ON DELETE SET NULL,
|
||||
work_order_id BIGINT REFERENCES work_orders(id) ON DELETE SET NULL,
|
||||
cost_item_id BIGINT REFERENCES cost_items(id) ON DELETE RESTRICT,
|
||||
expense_category TEXT NOT NULL DEFAULT 'other',
|
||||
amount_fen BIGINT NOT NULL,
|
||||
currency TEXT NOT NULL DEFAULT 'CNY',
|
||||
paid_by_admin_user_id BIGINT,
|
||||
paid_by_name TEXT NOT NULL DEFAULT '',
|
||||
payee_type TEXT NOT NULL DEFAULT 'other',
|
||||
payee_name TEXT NOT NULL DEFAULT '',
|
||||
payee_account TEXT NOT NULL DEFAULT '',
|
||||
payment_method TEXT NOT NULL DEFAULT 'other',
|
||||
paid_at TIMESTAMPTZ NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'submitted',
|
||||
approved_by TEXT NOT NULL DEFAULT '',
|
||||
approved_at TIMESTAMPTZ,
|
||||
reject_reason TEXT NOT NULL DEFAULT '',
|
||||
evidence_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
cost_snapshot_id BIGINT REFERENCES cost_snapshots(id) ON DELETE RESTRICT,
|
||||
-- 预留:退款/仲裁接入后用反向登记冲正,不修改已通过的原始记录
|
||||
reversal_of_id BIGINT REFERENCES manual_expense_records(id) ON DELETE RESTRICT,
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_manual_expenses_expense_no ON manual_expense_records (expense_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_expenses_order ON manual_expense_records (order_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_expenses_item ON manual_expense_records (order_item_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_expenses_case ON manual_expense_records (case_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_expenses_paid_at ON manual_expense_records (paid_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_expenses_status ON manual_expense_records (status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_expenses_cost_item ON manual_expense_records (cost_item_id);
|
||||
-- 付款人日结对账用
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_expenses_payer ON manual_expense_records (paid_by_admin_user_id, paid_at DESC);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_expenses_amount') THEN
|
||||
ALTER TABLE manual_expense_records
|
||||
ADD CONSTRAINT chk_manual_expenses_amount CHECK (amount_fen <> 0);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_expenses_status') THEN
|
||||
ALTER TABLE manual_expense_records
|
||||
ADD CONSTRAINT chk_manual_expenses_status
|
||||
CHECK (status IN ('draft', 'submitted', 'approved', 'rejected', 'reimbursed', 'reversed'));
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_expenses_payee_type') THEN
|
||||
ALTER TABLE manual_expense_records
|
||||
ADD CONSTRAINT chk_manual_expenses_payee_type
|
||||
CHECK (payee_type IN ('worker', 'upstream', 'customer', 'platform', 'other'));
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_expenses_method') THEN
|
||||
ALTER TABLE manual_expense_records
|
||||
ADD CONSTRAINT chk_manual_expenses_method
|
||||
CHECK (payment_method IN ('wechat', 'alipay', 'bank', 'cash', 'platform_balance', 'other'));
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_expenses_category') THEN
|
||||
ALTER TABLE manual_expense_records
|
||||
ADD CONSTRAINT chk_manual_expenses_category
|
||||
CHECK (expense_category IN ('goods_purchase', 'compensation', 'refund_advance', 'service_fee',
|
||||
'freight', 'recharge', 'other'));
|
||||
END IF;
|
||||
-- 已通过/已报销必须有审批信息
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_manual_expenses_approved') THEN
|
||||
ALTER TABLE manual_expense_records
|
||||
ADD CONSTRAINT chk_manual_expenses_approved
|
||||
CHECK (status NOT IN ('approved', 'reimbursed') OR (approved_at IS NOT NULL AND approved_by <> ''));
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
COMMENT ON TABLE manual_expense_records IS '人工支出登记:付款项目/金额/订单号/付款人/收款人/凭证,解决人工付款不可追溯';
|
||||
COMMENT ON COLUMN manual_expense_records.cost_item_id IS '付款项目 -> cost_items;为空表示未归类,必须进异常队列而不是默认其他';
|
||||
COMMENT ON COLUMN manual_expense_records.expense_category IS '兜底分类:cost_item_id 未填时的粗分类,报表需同时暴露未归类占比';
|
||||
COMMENT ON COLUMN manual_expense_records.paid_by_admin_user_id IS '实际垫付/付款的后台用户;用于“谁付了多少钱”对账';
|
||||
COMMENT ON COLUMN manual_expense_records.evidence_json IS '凭证:支付截图对象 key、流水号、聊天记录 key 等;提交时必填(服务层校验)';
|
||||
COMMENT ON COLUMN manual_expense_records.reversal_of_id IS '预留:退款/仲裁接入后按反向登记冲正,禁止 UPDATE 已通过记录';
|
||||
COMMENT ON COLUMN manual_expense_records.status IS 'draft草稿/submitted待审/approved已通过/rejected已驳回/reimbursed已报销/reversed已冲正';
|
||||
|
||||
-- 状态变更审计(不可改)
|
||||
CREATE TABLE IF NOT EXISTS manual_expense_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
expense_id BIGINT NOT NULL REFERENCES manual_expense_records(id) ON DELETE CASCADE,
|
||||
event_type TEXT NOT NULL,
|
||||
from_status TEXT NOT NULL DEFAULT '',
|
||||
to_status TEXT NOT NULL DEFAULT '',
|
||||
actor_type TEXT NOT NULL DEFAULT 'admin',
|
||||
actor_id TEXT NOT NULL DEFAULT '',
|
||||
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_expense_events_expense
|
||||
ON manual_expense_events (expense_id, created_at);
|
||||
|
||||
COMMENT ON TABLE manual_expense_events IS '人工支出状态变更审计:只追加,不更新、不删除';
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- §3 校验与报表查询
|
||||
-- ============================================================================
|
||||
|
||||
-- Q1 「谁付了多少钱」:按付款人 × 项目 汇总(日结/周结用)
|
||||
-- SELECT r.paid_by_name, coalesce(ci.name, r.expense_category) AS item, count(*) AS records,
|
||||
-- round(sum(r.amount_fen) / 100.0, 2) AS yuan
|
||||
-- FROM manual_expense_records r
|
||||
-- LEFT JOIN cost_items ci ON ci.id = r.cost_item_id
|
||||
-- WHERE r.status IN ('approved', 'reimbursed') AND r.paid_at >= now() - interval '7 days'
|
||||
-- GROUP BY 1, 2 ORDER BY sum(r.amount_fen) DESC;
|
||||
|
||||
-- Q2 未归类占比(必须持续为 0,否则报表口径不可信)
|
||||
-- SELECT count(*) FILTER (WHERE cost_item_id IS NULL) AS unclassified,
|
||||
-- count(*) AS total,
|
||||
-- round(100.0 * count(*) FILTER (WHERE cost_item_id IS NULL) / nullif(count(*), 0), 2) AS unclassified_pct
|
||||
-- FROM manual_expense_records WHERE status <> 'draft';
|
||||
|
||||
-- Q3 没有关联订单号的支出(你说的“也不知道订单号”,这里必须能点名)
|
||||
-- SELECT count(*) FILTER (WHERE order_id IS NULL AND order_item_id IS NULL) AS no_order_link,
|
||||
-- round(sum(amount_fen) FILTER (WHERE order_id IS NULL AND order_item_id IS NULL) / 100.0, 2) AS yuan
|
||||
-- FROM manual_expense_records WHERE status IN ('approved', 'reimbursed');
|
||||
|
||||
-- Q4 订单损益:一个订单项的收入 - 各类成本(含人工支出)
|
||||
-- SELECT oi.id AS order_item_id, o.provider, o.shop_id,
|
||||
-- oi.revenue_amount_fen AS revenue_fen,
|
||||
-- coalesce(sum(cs.total_amount_fen), 0) AS cost_fen,
|
||||
-- coalesce(sum(r.amount_fen), 0) AS manual_expense_fen
|
||||
-- FROM order_items oi
|
||||
-- JOIN orders o ON o.id = oi.order_id
|
||||
-- LEFT JOIN cost_snapshots cs ON cs.order_item_id = oi.id
|
||||
-- LEFT JOIN manual_expense_records r ON r.order_item_id = oi.id AND r.status IN ('approved', 'reimbursed')
|
||||
-- WHERE oi.id = $1
|
||||
-- GROUP BY 1, 2, 3, 4;
|
||||
|
||||
-- Q5 与成本中心对账:approved 的支出必须都有 cost_snapshot(影子期允许延迟,转正期不允许缺)
|
||||
-- SELECT count(*) AS approved_without_snapshot
|
||||
-- FROM manual_expense_records
|
||||
-- WHERE status IN ('approved', 'reimbursed') AND cost_snapshot_id IS NULL;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- §4 与财务系统(影子总账)的对接约定
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 事件类型(幂等键):
|
||||
-- manual_expense:{expense_no}:approved 人工支出登记通过
|
||||
-- manual_expense:{expense_no}:reimbursed 已报销(垫付 -> 公司支出)
|
||||
-- manual_expense:{expense_no}:reversed 冲正(退款/仲裁接入后启用,本轮仅预留)
|
||||
--
|
||||
-- 建议分录(approved 时,金额 A):
|
||||
-- 借:5101 履约成本-人工(或按 cost_items 映射的科目) A
|
||||
-- 贷:2005 其他应付-垫付人 A
|
||||
-- reimbursed 时:
|
||||
-- 借:2005 其他应付-垫付人 A
|
||||
-- 贷:1001 银行存款 / 1002 支付通道待结算 A
|
||||
--
|
||||
-- 退款/仲裁仍未接入:本轮只保留 reversal_of_id 与 status='reversed',
|
||||
-- 不实现自动冲正逻辑;接入后按“反向登记 + 新分录”处理,禁止改动原记录。
|
||||
-- ============================================================================
|
||||
@@ -254,7 +254,15 @@ COMMENT ON TABLE cost_exceptions IS '成本缺失/冲突异常队列:报表必
|
||||
-- 且 quantity 不可信(316,585 项里 316,578 项 = 1,名称带「N个」的有 104,710 项)。
|
||||
-- 现状利好:目前每笔已支付订单恰好 1 个订单项(316,585 单 = 316,585 项),
|
||||
-- 所以 orders.total_amount 暂时可当订单项收入;但 schema 允许多项,属**潜在**缺口。
|
||||
-- 收入口径实测差异:91 = maxAmount、PDD = order_amount、抖音 = pay_amount,不能直接相加。
|
||||
--
|
||||
-- 【口径已确认 2026-10-17】收入 = 用户付款金额,取 orders.total_amount:
|
||||
-- · 91:total_amount 即用户付款(与快手发码配合)。数据核验:91 已支付 312,569 笔中
|
||||
-- 235,271 笔含快手 kuaishouSendCode.paymentFen,其中 235,268 笔与 total_amount **完全一致**
|
||||
-- (100.00%),仅 3 笔差 ¥1.80~2.00(2026-09-05 / 09-18 / 10-04,可单独抽查)。
|
||||
-- · PDD:order_amount;抖音:pay_amount。三者同为“用户实付”,故统一 revenue_basis = 'user_payment'。
|
||||
-- · 打手报酬 = **支出/成本**(最终由打手提现、我方付款),因此按**总额法**:
|
||||
-- 收入记用户实付全额,打手报酬作为履约成本,不做代收代付净额。
|
||||
-- · 退款/仲裁**暂未接入**:refunded_amount_fen / refunded_at 为**预留字段**,本轮不写逻辑。
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE order_items
|
||||
@@ -270,7 +278,7 @@ BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_order_items_revenue_basis') THEN
|
||||
ALTER TABLE order_items
|
||||
ADD CONSTRAINT chk_order_items_revenue_basis
|
||||
CHECK (revenue_basis IN ('', 'ninety_one_max_amount', 'pdd_order_amount', 'douyin_pay_amount', 'manual'));
|
||||
CHECK (revenue_basis IN ('', 'user_payment', 'manual_adjustment'));
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_order_items_revenue_amount') THEN
|
||||
ALTER TABLE order_items ADD CONSTRAINT chk_order_items_revenue_amount CHECK (revenue_amount_fen IS NULL OR revenue_amount_fen >= 0);
|
||||
@@ -280,8 +288,8 @@ END $$;
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_canonical_product ON order_items (canonical_product_id);
|
||||
|
||||
COMMENT ON COLUMN order_items.normalized_quantity IS '归一化件数:从 SKU 名称解析并固化,禁止直接用 quantity 做销量';
|
||||
COMMENT ON COLUMN order_items.revenue_amount_fen IS '统一收入口径(分),必须同时写 revenue_basis 说明来源';
|
||||
COMMENT ON COLUMN order_items.refunded_at IS '退款时间:按日净销售额必需,当前 orders 只有 pay_status=refunded 与 updated_at';
|
||||
COMMENT ON COLUMN order_items.revenue_amount_fen IS '收入(分)= 用户实付,取 orders.total_amount;revenue_basis 通常为 user_payment';
|
||||
COMMENT ON COLUMN order_items.refunded_at IS '【预留】退款时间:退款/仲裁暂未接入,本轮不写逻辑;接入后按日净销售额 = 销售额 - 退款额';
|
||||
|
||||
-- 过渡:让工单规则逐步改用成本中心(有 cost_item_id 走成本中心,否则回退旧金额字段)
|
||||
ALTER TABLE work_product_rules
|
||||
@@ -292,6 +300,8 @@ CREATE INDEX IF NOT EXISTS idx_work_product_rules_cost_item ON work_product_rule
|
||||
|
||||
COMMENT ON COLUMN work_product_rules.cost_item_id IS '迁移用过渡列:非空则金额取成本中心,空则回退 reward_amount/unit_price_fen';
|
||||
COMMENT ON COLUMN work_product_rules.legacy_cost_source IS '回退来源标记:rule_amount=旧规则金额,便于统计未迁移规则数';
|
||||
-- 口径已确认(2026-10-17):打手报酬是**支出**(工单结算 -> 可提现 -> 提现打款),
|
||||
-- 按总额法计入履约成本,与用户实付收入分开记,不做代收代付净额。
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
@@ -375,9 +385,16 @@ COMMENT ON COLUMN sales_daily_facts.cost_missing_count IS '成本缺失件数:
|
||||
-- 4) §4 销售事实表 -> 店铺/日期/皮肤 的销量、收入、成本、毛利
|
||||
-- 5) 之后才是影子总账(docs/财务系统集成方案.md)
|
||||
--
|
||||
-- 待业务确认(阻塞项):
|
||||
-- a) 91 的 total_amount(maxAmount)到底是收入还是结算上限?
|
||||
-- b) 打手报酬是成本还是代收代付?
|
||||
-- c) 人工块按单成本取值,以及是否需要按工时核算?
|
||||
-- d) 退款/仲裁的冲减规则与归期口径。
|
||||
-- 【口径已确认 2026-10-17】
|
||||
-- a) 收入 = 用户付款金额(orders.total_amount),计收入;已用数据核验与快手 paymentFen 100% 对齐。
|
||||
-- b) 打手报酬 = 支出/成本(最终提现打款),按总额法。
|
||||
-- c) 人工块:第一优先级是**人工支出可追溯**(付款项目/金额/订单号/付款人/凭证),
|
||||
-- 见 `2026-10-17-人工支出登记-DDL草案.sql`;按单还是按小时的取值口径仍待定,先按单登记。
|
||||
-- d) 退款/仲裁:**暂未接入,先预留**(字段 + reversal_of_id + 事件类型,不实现逻辑)。
|
||||
--
|
||||
-- 【仍待确认】
|
||||
-- 1) 收入确认时点(付款 / 发货 / 验收 / 售后期结束)——影响利润表与预收账款;
|
||||
-- 2) 自动发货 cost_amount 是否稳定、是否含平台积分/汇率;
|
||||
-- 3) 平台服务费/支付通道费由谁承担、结算周期与账单口径;
|
||||
-- 4) 寄卖货款的采购成本确认时点。
|
||||
-- ============================================================================
|
||||
|
||||
+32
-24
@@ -332,16 +332,23 @@ orders + order_items + channel_product_mappings + cost_snapshots
|
||||
|
||||
## 八、人工客服块(当前最需要补的)
|
||||
|
||||
> **口径确认(2026-10-17)**:业务明确人工块当前**不以 SLA 监管为目标**,第一优先级是
|
||||
> **人工支出可追溯** ——「主要是把流程搞清楚:哪个人处理了哪些东西、付了多少钱。现在人工完全监管不到,
|
||||
> 很多地方需要人工付款,也不知道付款项目、金额、订单号」。
|
||||
> 因此本章顺序调整为:**① 人工支出登记 → ② 处理人可见(轻量 case) → ③ SLA 与监管指标(延后)**。
|
||||
> DDL 见 [drafts/2026-10-17-人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql)。
|
||||
|
||||
### 8.1 问题本质
|
||||
|
||||
不是“客服效率低”,而是**人工单没有进入系统状态机**:
|
||||
不是“客服效率低”,而是**人工单没有进入系统状态机**,且**人工付款完全没有台账**:
|
||||
|
||||
- 没有 case;
|
||||
- 没有处理人;
|
||||
- 没有开始/完成时间;
|
||||
- 没有交付证据;
|
||||
- 没有人工成本;
|
||||
- 订单完成状态无法回写。
|
||||
- 订单完成状态无法回写;
|
||||
- **付款项目、金额、关联订单号、付款人、凭证全部缺失** —— 这是当前最直接的损失点。
|
||||
|
||||
### 8.2 载体判定
|
||||
|
||||
@@ -497,35 +504,36 @@ flowchart LR
|
||||
|
||||
---
|
||||
|
||||
## 十一、需要业务确认的问题
|
||||
## 十一、业务口径确认结果(2026-10-17)
|
||||
|
||||
1. **91 的 `orders.total_amount = maxAmount` 到底是“我方收入”还是“成本上限”?**
|
||||
2. 收入确认时点:付款 / 发货 / 验收 / 售后期结束?
|
||||
3. 打手报酬是成本,还是代收代付?
|
||||
4. 自动发货的 `cost_amount` 是否稳定、是否含平台积分/汇率?
|
||||
5. 人工成本按单还是按小时?客服是否在系统内记录工时?
|
||||
6. 退款/仲裁按什么日期冲减收入?
|
||||
7. 平台服务费/支付通道费由谁承担?
|
||||
8. 高价值人工单的复核阈值是多少?
|
||||
### 11.1 已确认
|
||||
|
||||
| # | 问题 | 结论 | 依据 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 91 的 `orders.total_amount` 是收入还是成本上限? | **收入**:`total_amount` 就是用户付款金额,与快手发码配合,计入收入 | 业务确认;数据核验:91 已支付 312,569 笔中 235,271 笔含快手 `paymentFen`,其中 235,268 笔与 `total_amount` **完全一致(100.00%)**,仅 3 笔差 ¥1.80~2.00 |
|
||||
| 2 | 打手报酬是成本还是代收代付? | **支出/成本**:工单结算后打手会提现,我方实际付款给他们 → 按**总额法**(收入记用户实付全额,报酬记履约成本) | 业务确认 |
|
||||
| 3 | 人工块的目标是什么? | **先做流程可见与支出可追溯**(谁处理了哪些、付了多少钱、什么项目、关联订单号、凭证),SLA/监管指标延后 | 业务确认 |
|
||||
| 4 | 退款/仲裁怎么冲减? | **暂未接入,先预留**:字段、反向冲正关系与事件类型先留好,本轮不实现逻辑 | 业务确认 |
|
||||
|
||||
### 11.2 仍待确认
|
||||
|
||||
1. 收入确认时点:付款 / 发货 / 验收 / 售后期结束?(金额口径已定 = 用户实付)
|
||||
2. 自动发货的 `cost_amount` 是否稳定、是否含平台积分/汇率?
|
||||
3. 人工支出按单还是按小时归集?(先按单登记,工时字段后续再加)
|
||||
4. 平台服务费/支付通道费由谁承担、结算周期与账单口径?
|
||||
5. 寄卖货款的采购成本确认时点?
|
||||
|
||||
---
|
||||
|
||||
## 十二、生产只读与克隆建议
|
||||
## 十二、分析基线与克隆现状(2026-10-17 更新)
|
||||
|
||||
本次已通过 SSH 连接生产库并执行只读聚合:
|
||||
生产只读聚合(`SET default_transaction_read_only = on`)已完成,无写入、无改表、无改数据。
|
||||
|
||||
```sql
|
||||
SET default_transaction_read_only = on;
|
||||
SET statement_timeout = '20s';
|
||||
```
|
||||
**现有基线**:本机已存在可反复使用的**生产快照克隆**(本地 docker `order_site-postgres-1`,`orders` 316,597 行,数据截至 2026-10-14),
|
||||
因此**后续 SQL 迭代一律打本地克隆,不必再连生产**;需要更新时再取新快照。
|
||||
|
||||
没有写入、没有改表、没有改数据。
|
||||
|
||||
建议暂时不做全量克隆:
|
||||
|
||||
- 聚合数据已足够支撑第一轮设计;
|
||||
- 全量库包含买家信息、原始报文、平台凭据,复制到本地有安全风险;
|
||||
- 后续需要反复迭代 SQL 时,再做**选定表只读逻辑克隆**:`orders`、`order_items`、`work_orders`、`fulfillment_tasks`、`work_product_rules`、`worker_wallet_ledgers`、`worker_finance_requests`。
|
||||
本机备份目录已收敛:`backups/` 及子目录 `700`、两份 dump `600`,并删除了一个残留临时 dump(137 MB)。
|
||||
如需长期保留留档,建议只保留一份最新 dump,并确认磁盘加密。
|
||||
|
||||
---
|
||||
|
||||
|
||||
+20
-12
@@ -274,7 +274,7 @@ CREATE TABLE finance_reconciliation_items (
|
||||
|
||||
| 事件 | 借方 | 贷方 | 备注 |
|
||||
| --- | --- | --- | --- |
|
||||
| 订单支付 | 1002 支付通道待结算 / 1101 应收平台 | 2201 预收账款 | 收入确认时点需业务拍板 |
|
||||
| 订单支付 | 1002 支付通道待结算 / 1101 应收平台 | 2201 预收账款 | 金额 = 用户实付(已确认);确认时点待拍板 |
|
||||
| 履约完成/验收 | 2201 预收账款 | 4001 商品销售收入 | 或按交付时点确认 |
|
||||
| 工单验收结算 | 5101 履约成本 | 2001 应付打手报酬 | 按份额逐笔 |
|
||||
| 打手报酬转可提现 | 2001 应付打手报酬 | 2004 打手可提现余额 | 与现有 `reward_settlement` 对齐 |
|
||||
@@ -288,8 +288,12 @@ CREATE TABLE finance_reconciliation_items (
|
||||
| 寄卖验收货款 | 5001 商品采购成本 | 2002 应付卖家货款 | 货款金额取 `cost_price_fen || asking_price_fen` |
|
||||
| 支付卖家货款 | 2002 应付卖家货款 | 1001 银行存款 | 需结算单 |
|
||||
| 平台服务费 | 5401 平台服务费 | 2101 应付平台服务费 | 从平台账单导入 |
|
||||
| 人工支出登记通过(垫付) | 5101 履约成本-人工(按 `cost_item_id` 映射科目) | 2005 其他应付-垫付人 | 付款项目/订单号/付款人必填;见 [人工支出登记草案](drafts/2026-10-17-人工支出登记-DDL草案.sql) |
|
||||
| 人工垫付报销 | 2005 其他应付-垫付人 | 1001 银行存款 | 需凭证与审批留痕 |
|
||||
| 退款冲正(预留) | 反向凭证 `reversal_of_id` 指向原凭证 | — | 退款/仲裁暂未接入,先预留不实现 |
|
||||
|
||||
> 具体收入确认时点(付款 / 发货 / 验收 / 售后期结束)必须由业务与财务共同拍板;这会直接影响利润表。
|
||||
> **已确认(2026-10-17)**:收入金额口径 = 用户实付;打手报酬 = 支出(总额法);退款/仲裁先预留;人工支出以登记表为入口。
|
||||
> **仍待拍板**:收入**确认时点**(付款 / 发货 / 验收 / 售后期结束),它直接影响利润表与预收账款。
|
||||
|
||||
---
|
||||
|
||||
@@ -448,16 +452,20 @@ POST /periods/:key/close
|
||||
|
||||
## 八、需要业务拍板的问题
|
||||
|
||||
| # | 问题 | 影响 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 收入确认时点:付款 / 发货 / 验收 / 售后期结束? | 决定利润表与预收账款 |
|
||||
| 2 | 平台结算周期与账单口径(91、快手、PDD、抖音、affiliate-dash) | 决定对账与应收 |
|
||||
| 3 | 打手报酬是“成本”还是“代收代付”? | 决定收入总额法/净额法 |
|
||||
| 4 | 寄卖货款的采购成本确认时点 | 决定存货/成本 |
|
||||
| 5 | 提现通道费、平台服务费由谁承担 | 决定费用科目与结算 |
|
||||
| 6 | 退款、拒付、仲裁的冲减规则 | 决定售后与收入冲回 |
|
||||
| 7 | 是否需要发票/税务 | 决定发票表与税额 |
|
||||
| 8 | 是否多币种 | 当前 CNY,若否则需汇率与汇兑损益 |
|
||||
> 2026-10-17 已确认 4 条口径,明细见 [成本中心与三块履约模型.md](成本中心与三块履约模型.md) §11。
|
||||
|
||||
| # | 问题 | 状态 | 影响 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 收入金额口径 | ✅ **已确认:收入 = 用户实付(`orders.total_amount`)**,与快手 `paymentFen` 数据核验 100% 一致 | 收入基数 |
|
||||
| 2 | 收入确认时点:付款 / 发货 / 验收 / 售后期结束? | ⏳ 待定(金额口径已定) | 决定利润表与预收账款 |
|
||||
| 3 | 平台结算周期与账单口径(91、快手、PDD、抖音、affiliate-dash) | ⏳ 待定 | 决定对账与应收 |
|
||||
| 4 | 打手报酬是“成本”还是“代收代付”? | ✅ **已确认:支出/成本**(最终提现打款),按**总额法** | 收入总额法/净额法 |
|
||||
| 5 | 寄卖货款的采购成本确认时点 | ⏳ 待定 | 决定存货/成本 |
|
||||
| 6 | 提现通道费、平台服务费由谁承担 | ⏳ 待定 | 决定费用科目与结算 |
|
||||
| 7 | 退款、拒付、仲裁的冲减规则 | ✅ **已确认:暂未接入,先预留**(字段 + `reversal_of_id` + 事件类型,不实现逻辑) | 决定售后与收入冲回 |
|
||||
| 8 | 人工支出如何入账 | ✅ **已确认:以「人工支出登记」为入口**(付款项目/金额/订单号/付款人/凭证),SLA 监管延后 | 决定人工成本科目与总账事件 |
|
||||
| 9 | 是否需要发票/税务 | ⏳ 待定 | 决定发票表与税额 |
|
||||
| 10 | 是否多币种 | ⏳ 待定(当前 CNY) | 若否则需汇率与汇兑损益 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -129,11 +129,12 @@ Top 店铺(人工块):向日葵游戏服务店 14,903 项 / ¥183.9 万;
|
||||
| 文件 | 内容 |
|
||||
| --- | --- |
|
||||
| [载体判定与人工块量化.sql](drafts/2026-10-17-载体判定与人工块量化.sql) | §1 八组只读量化查询(含实测值注释);§2 判定规则与优先级(含 `legacy` 与冲突处理);§3 加列 + 回填 + 索引 + 回滚;§4 守恒校验与异常队列查询;§5 人工成本区间 |
|
||||
| [成本中心与销售事实-DDL草案.sql](drafts/2026-10-17-成本中心与销售事实-DDL草案.sql) | `product_catalog`、`channel_product_mappings`、`cost_items`、`cost_versions`、`cost_snapshots`、`cost_exceptions`、`sales_daily_facts`;订单项口径补齐(`normalized_quantity`/`revenue_amount_fen`/`revenue_basis`/`refunded_at`);`work_product_rules.cost_item_id` 过渡列;§5 对平校验;§6 落地顺序与待业务确认 |
|
||||
| [成本中心与销售事实-DDL草案.sql](drafts/2026-10-17-成本中心与销售事实-DDL草案.sql) | `product_catalog`、`channel_product_mappings`、`cost_items`、`cost_versions`、`cost_snapshots`、`cost_exceptions`、`sales_daily_facts`;订单项口径补齐(`normalized_quantity`/`revenue_amount_fen`/`revenue_basis`/`refunded_at`);`work_product_rules.cost_item_id` 过渡列;§5 对平校验;§6 已确认口径与剩余待确认 |
|
||||
| [人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql) | `manual_fulfillment_cases`(轻量处理记录)+ `manual_expense_records`(付款项目/金额/订单号/付款人/收款人/凭证/审批)+ `manual_expense_events` 审计;§3 校验查询;§4 与影子总账的分录约定 |
|
||||
|
||||
**验证方式**:在本地副本上 `pg_dump --schema-only` 建了一个一次性沙箱库(55 张表),按顺序 `psql -v ON_ERROR_STOP=1` 应用两份草案 —— **零错误**,生成 7 张新表(62 张)、`order_items` 由 9 列增至 19 列、新增 18 个 `CHECK` 约束;校验查询与回填语句均可执行。验证后沙箱库已 `DROP`。
|
||||
|
||||
关键设计约束(两份草案共同遵循):
|
||||
关键设计约束(三份草案共同遵循):
|
||||
|
||||
1. **改价只发生在 `cost_versions`**,历史成本靠 `cost_snapshots` 固化,永不回写;
|
||||
2. **匹配不到成本不默认 0**,写入 `cost_exceptions`,报表必须暴露 `open` 数量;
|
||||
@@ -158,8 +159,31 @@ Top 店铺(人工块):向日葵游戏服务店 14,903 项 / ¥183.9 万;
|
||||
|
||||
---
|
||||
|
||||
## 八、下一步
|
||||
## 八、业务口径确认(2026-10-17)
|
||||
|
||||
1. **业务先拍板 4 个周长问题**(阻塞建表):91 的 `total_amount` 是收入还是结算上限;打手报酬是成本还是代收代付;人工块单均成本取值与是否按工时;退款/仲裁的冲减与归期口径。
|
||||
2. **工程先做两件不改业务代码的事**:跑 `channel_product_mappings` 的初始映射(把 47 个码归到 1 个标准商品);把 `normalized_quantity` 用现有 `resolveSkuNameQuantity` 解析后回填订单项。
|
||||
3. 之后再按「商品主数据 → 成本中心影子期 → 人工 case → 影子总账 → 对账转正」推进;`manual_fulfillment_cases` 的 DDL 尚未产出(本轮只做载体判定与成本/销售事实),可作为下一份草案。
|
||||
业务已就四个阻塞问题给出结论;其中两条已用克隆数据独立核验。
|
||||
|
||||
| # | 问题 | 结论 | 数据核验 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 91 的 `total_amount` 是收入还是成本上限? | **收入**:它就是**用户付款金额**,与快手发码配合 | ✅ 91 已支付 312,569 笔,其中 235,271 笔含快手 `kuaishouSendCode.paymentFen`;**235,268 笔与 `total_amount` 完全一致(100.00%)**,仅 3 笔差 ¥1.80 / ¥1.85 / ¥2.00(2026-09-05 / 09-18 / 10-04) |
|
||||
| 2 | 打手报酬是成本还是代收代付? | **支出/成本**:工单结算 → 打手可提现 → 提现打款,属我方真实支出 → **总额法** | — |
|
||||
| 3 | 人工块的目标? | **先把流程搞清楚:谁处理了哪些、付了多少钱**。现状「很多地方需要人工付款,但不知道付款项目、金额、订单号」→ 新增**人工支出登记**草案;SLA/监管指标延后 | — |
|
||||
| 4 | 退款/仲裁? | **暂未接入,先预留**:字段 + `reversal_of_id` + 事件类型,本轮不实现逻辑 | — |
|
||||
|
||||
影响:
|
||||
|
||||
- 收入口径统一为 `revenue_amount_fen = orders.total_amount`(`revenue_basis = 'user_payment'`),草案中原本枚举的三家字段名已简化;
|
||||
- 成本侧确认「打手报酬 = 支出」,`cost_items.cost_type = 'work_order_reward'` 计入履约成本,不做净额;
|
||||
- 人工块方案重心从「case + SLA」改为「**人工支出登记**(付款项目 / 金额 / 订单号 / 付款人 / 收款人 / 凭证 / 审批)」;
|
||||
- 退款相关列与冲正关系保留为预留,不写逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 九、下一步
|
||||
|
||||
1. **业务仍待拍板 1 个关键口径**:收入**确认时点**(付款 / 发货 / 验收 / 售后期结束)—— 它决定利润表与预收账款;金额口径已定。
|
||||
2. **工程可先做两件不改业务代码的事**:
|
||||
- 建 `channel_product_mappings` 初始映射,把 47 个 `sku_code` 归到 1 个标准商品;
|
||||
- 用现有 `resolveSkuNameQuantity` 解析并回填 `normalized_quantity`。
|
||||
3. **紧接着做人工支出登记**(业务当前最痛):`manual_fulfillment_cases` + `manual_expense_records`,先解决「谁付的、什么项目、多少钱、哪个订单」。
|
||||
4. 之后按「商品主数据 → 成本中心影子期 → 人工支出 → 影子总账 → 对账转正」推进。
|
||||
|
||||
@@ -156,7 +156,7 @@ pending_material → unassigned → open → in_progress
|
||||
- 91 无载体订单 42,391、PDD 2,258、抖音 8;全部 `claim_status=active`。
|
||||
- `manual_dispatch` 任务全库只有 6 条,说明人工块基本没进任务系统。
|
||||
- 已验收工单的 `orders.order_status` 仍为 `paid`,订单主状态不能代表履约完成。
|
||||
- 最近 30 天已验收工单:91 报酬/订单金额约 86.0%、PDD 80.0%、抖音 96.5%(91 的 `total_amount` 是 `maxAmount`,口径待确认)。
|
||||
- 最近 30 天已验收工单:91 报酬/订单金额约 86.0%、PDD 80.0%、抖音 96.5%。**口径已确认(2026-10-17):`total_amount` 即用户付款金额、计收入,打手报酬计支出(总额法)**;91 的 `total_amount` 与快手 `paymentFen` 数据核验 100% 一致。详见 [载体判定与成本中心落地-复核与草案-2026-10-17.md](载体判定与成本中心落地-复核与草案-2026-10-17.md) §九。
|
||||
- 同一个商品名对应多个 `sku_code`:「指挥官密钥1个」27 个、「苏尔南钻石100颗」35 个,不能直接按 `sku_code` 做销量统计。
|
||||
- 当前 30 天订单基本是一单一品,但模型必须按订单项设计。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user