feat(m6): 额度流水、套餐开通、数据删除、运营后台(M6 剩下的 P0)
Some checks failed
ci / server (push) Has been cancelled
ci / web (push) Has been cancelled
ci / contract (push) Has been cancelled

决策 M6-1–M6-10(docs/architecture/m6-plan.md、docs/decisions.md)。

用户端:
- 额度与套餐页:本期额度、按功能分布、扣点规则、流水(逐条余额、关联项目和任务、导出 CSV)
- 开通 / 续费申请(对公转账,运营确认到账后开通),80% / 100% 提醒
- 专业版到期:整个账号只读、额度清零,到期前 30 / 7 / 0 天提醒(worker 每小时 billing_tick)
- 删除项目(彻底删除、销毁数据密钥),注销账号(企业名称 + 短信验证码)
- 通知铃铛、账号页

运营后台:
- 独立进程 bid-doctor ops(/ops/v1)和 apps/admin;用户名 + 密码 + TOTP + IP 白名单
- 开通申请(到账 / 开通 / 退回 / 发票)、企业查询和调额度、误报 / 漏报、失败任务、审计;不看业务内容
- 左侧菜单(标准 logo、可展开收起的分组、图标、待办数、环境标签)
- 运营人员角色只留结构:ops_admin.role、权限点、每个接口的检查点、菜单按权限过滤

数据库:迁移 0010(排队的订阅、申请单号、通知类型、billing_due)、0011(bd_ops 权限)、0012(运营角色)。
另修:取消解析 / 检查接口报 500;uuid7 进程内单调;data_key 销毁后不再新建;审计 None 存 SQL NULL。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-05 15:06:06 +08:00
parent 54cbb556d0
commit dd8c04001d
78 changed files with 5064 additions and 104 deletions

View File

@@ -129,6 +129,23 @@ export interface paths {
patch?: never;
trace?: never;
};
"/me/sms-codes": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** 登录后发验证码到自己的手机号(注销账号的二次确认,前端不需要完整号码) */
post: operations["sendMySmsCode"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/tenant/close": {
parameters: {
query?: never;
@@ -140,7 +157,9 @@ export interface paths {
put?: never;
/**
* 注销账号(删除企业下全部业务数据,不可恢复)
* @description data-model §6.2。需要短信验证码和企业名称两次确认。成功后当前会话失效。
* @description data-model §6.2,决策 M6-8。需要短信验证码(`POST /me/sms-codes`)和企业名称两次确认;
* 企业名称不一致返回 422 `tenant_name_mismatch`。成功后所有会话失效,手机号可以重新注册;项目、资质库等由后台删除。
*
*/
post: operations["closeTenant"];
delete?: never;
@@ -1572,17 +1591,31 @@ export interface components {
balance: number;
/** @description 正在预扣、还没结算的 */
held?: number;
/** @description 本期发放(按当前这一期订阅) */
period_granted: number;
/** @description 本期净用量(预扣 − 退回) */
period_used: number;
/** @description 本期退回的次数(失败、超时、取消) */
period_released?: number;
/** @description 0~1;≥ 0.8 时界面变赭色(Q9) */
used_ratio?: number;
/** @description 按功能分布,如解析、检查 */
by_feature?: {
/** @enum {string} */
feature: "parse" | "check";
used: number;
}[];
}[];
/** @description 本期按功能分布(没被退回的预扣);deleted 是项目已删除、看不出用途的;waived 是 24 小时内复查的免扣次数 */
usage: {
tender: number;
/** @description 补遗和澄清 */
addendum: number;
check: number;
deleted: number;
waived: number;
};
/** @description 已续费、排队等当前一期结束后生效的那一期 */
renewal?: null | {
starts_at: components["schemas"]["Timestamp"];
ends_at: components["schemas"]["Timestamp"];
};
/** @description 还在等转账或开通的申请 */
pending_request?: null | components["schemas"]["ActivationRequest"];
};
QuotaEntry: {
id: components["schemas"]["Id"];
@@ -1590,6 +1623,10 @@ export interface components {
kind: components["schemas"]["QuotaEntryKind"];
/** @description 对余额的影响;结算、免扣为 0 */
amount: number;
/** @description 记完这一条以后的账户余额 */
balance_after: number;
/** @description 结算、退回对应的预扣 */
hold_entry_id?: components["schemas"]["Id"] | null;
/** @description 调整原因;免扣时为规则说明,如「24 小时内复查」 */
reason?: string;
project?: {
@@ -1612,6 +1649,7 @@ export interface components {
plan_code: "pro";
invoice_info: components["schemas"]["InvoiceInfo"];
};
/** @description 专票(invoice_type = special)还要地址电话和开户行账号 */
InvoiceInfo: {
/** @description 发票抬头 */
title: string;
@@ -1621,18 +1659,30 @@ export interface components {
bank_account?: string;
/** @enum {string} */
invoice_type?: "general" | "special";
contact_name?: string;
contact_phone?: string;
contact_name: string;
contact_phone: string;
};
ActivationRequest: {
id: components["schemas"]["Id"];
/** @description 申请单号,对公转账时写在备注里 */
request_no: string;
plan_code: components["schemas"]["PlanCode"];
/** @enum {string} */
/**
* @description 等待转账 / 已到账 / 已开通 / 已退回 / 已撤回
* @enum {string}
*/
status: "submitted" | "paid" | "activated" | "rejected" | "cancelled";
invoice_info?: components["schemas"]["InvoiceInfo"];
/** @description 对公转账账户和备注要求 */
payment_instructions?: string;
ops_note?: string;
/** @description 收款信息(配置项;没配时为「(待定)」,决策 M6-6)。页面不写价格,运营联系确认金额 */
payment: {
payee_name: string;
payee_bank: string;
payee_account: string;
};
/** @description 退回原因 */
ops_note?: string | null;
invoice_no?: string | null;
handled_at?: components["schemas"]["Timestamp"] | null;
created_at: components["schemas"]["Timestamp"];
};
/** @description 就绪状态只在 status = checked 时有意义(G7);未就绪也不阻止导出和递交(红线 4) */
@@ -2309,10 +2359,11 @@ export interface components {
Notification: {
id: components["schemas"]["Id"];
/** @enum {string} */
kind: "parse_succeeded" | "parse_failed" | "check_succeeded" | "check_failed" | "report_succeeded" | "report_failed" | "quota_80" | "quota_100" | "subscription_activated" | "subscription_expiring";
kind: "parse_succeeded" | "parse_failed" | "check_succeeded" | "check_failed" | "report_succeeded" | "report_failed" | "quota_80" | "quota_100" | "subscription_activated" | "subscription_expiring" | "subscription_expired" | "activation_rejected";
title: string;
project_id?: components["schemas"]["Id"] | null;
/** @description 前端路由,如 /projects/<id>/results */
project_name?: string | null;
/** @description 前端路由,如 /projects/<id>/results、/quota */
link?: string | null;
read_at?: components["schemas"]["Timestamp"] | null;
created_at: components["schemas"]["Timestamp"];
@@ -2650,6 +2701,36 @@ export interface operations {
404: components["responses"]["NotFound"];
};
};
sendMySmsCode: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": {
/** @enum {string} */
purpose: "close_tenant";
};
};
};
responses: {
/** @description 已发送 */
202: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
resend_after_s: number;
};
};
};
429: components["responses"]["RateLimited"];
};
};
closeTenant: {
parameters: {
query?: never;