diff --git a/.logs/backend.err b/.logs/backend.err index 6acd386..65d2823 100644 --- a/.logs/backend.err +++ b/.logs/backend.err @@ -1,9 +1,21 @@ - Compiling erp-plugin v0.1.0 (G:\erp\crates\erp-plugin) - Compiling erp-server-migration v0.1.0 (G:\erp\crates\erp-server\migration) +warning: unused import: `PluginError` + --> crates\erp-plugin\src\plugin_validator.rs:1:20 + | +1 | use crate::error::{PluginError, PluginResult}; + | ^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +warning: unused import: `parse_manifest` + --> crates\erp-plugin\src\plugin_validator.rs:2:23 + | +2 | use crate::manifest::{parse_manifest, PluginManifest}; + | ^^^^^^^^^^^^^^ + warning: field `chk` is never read - --> crates\erp-plugin\src\data_service.rs:376:39 + --> crates\erp-plugin\src\data_service.rs:445:39 | -376 | struct RefCheck { chk: Option } +445 | struct RefCheck { chk: Option } | -------- ^^^ | | | field in this struct @@ -11,24 +23,23 @@ warning: field `chk` is never read = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default warning: field `chk` is never read - --> crates\erp-plugin\src\data_service.rs:477:51 + --> crates\erp-plugin\src\data_service.rs:684:51 | -477 | ... struct RefCheck { chk: Option } +684 | ... struct RefCheck { chk: Option } | -------- ^^^ | | | field in this struct warning: field `check_result` is never read - --> crates\erp-plugin\src\data_service.rs:1122:30 + --> crates\erp-plugin\src\data_service.rs:1329:30 | -1122 | struct ExistsCheck { check_result: Option } +1329 | struct ExistsCheck { check_result: Option } | ----------- ^^^^^^^^^^^^ | | | field in this struct -warning: `erp-plugin` (lib) generated 3 warnings - Compiling erp-server v0.1.0 (G:\erp\crates\erp-server) - Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 45s +warning: `erp-plugin` (lib) generated 5 warnings (run `cargo fix --lib -p erp-plugin` to apply 2 suggestions) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s Running `target\debug\erp-server.exe` Error: configuration file "config/default" not found error: process didn't exit successfully: `target\debug\erp-server.exe` (exit code: 1) diff --git a/.logs/backend.pid b/.logs/backend.pid new file mode 100644 index 0000000..e5b4135 --- /dev/null +++ b/.logs/backend.pid @@ -0,0 +1 @@ +10056 diff --git a/.logs/frontend.err b/.logs/frontend.err index 36f3422..e69de29 100644 --- a/.logs/frontend.err +++ b/.logs/frontend.err @@ -1,187 +0,0 @@ -08:34:12 [vite] (client) [console.error] Warning: [antd: Modal] `destroyOnClose` is deprecated. Please use `destroyOnHidden` instead. -08:34:38 [vite] (client) [console.error] Warning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop? -08:38:05 [vite] http proxy error: /api/v1/messages/unread-count -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:38:05 [vite] http proxy error: /api/v1/messages?page=1&page_size=5 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:39:35 [vite] (client) [console.error] Warning: [antd: Modal] `destroyOnClose` is deprecated. Please use `destroyOnHidden` instead. -08:39:54 [vite] (client) [console.error] Warning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop? -08:43:34 [vite] http proxy error: /api/v1/messages/unread-count -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:43:35 [vite] http proxy error: /api/v1/messages?page=1&page_size=5 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:44:34 [vite] http proxy error: /api/v1/messages/unread-count -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:44:34 [vite] http proxy error: /api/v1/messages?page=1&page_size=5 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:33 [vite] http proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:33 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/product?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:33 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/product/resolve-labels -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:33 [vite] (client) [console.error] Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead. -08:45:34 [vite] http proxy error: /api/v1/messages/unread-count -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:34 [vite] http proxy error: /api/v1/messages?page=1&page_size=5 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/warehouse?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/warehouse/resolve-labels -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/stock?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/stock/resolve-labels -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/supplier?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:35 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/supplier/resolve-labels -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:37 [vite] http proxy error: /api/v1/messages?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:38 [vite] http proxy error: /api/v1/workflow/definitions?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:38 [vite] http proxy error: /api/v1/workflow/definitions?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:38 [vite] (client) [Unhandled rejection] AxiosError: Request failed with status code 502 - > settle node_modules/.pnpm/axios@1.15.0/node_modules/axios/lib/core/settle.js:20:6 - > XMLHttpRequest.onloadend node_modules/.pnpm/axios@1.15.0/node_modules/axios/lib/adapters/xhr.js:62:8 - > Axios$1.request node_modules/.pnpm/axios@1.15.0/node_modules/axios/lib/core/Axios.js:46:40 - > listProcessDefinitions src/api/workflowDefinitions.ts:54:19 - 52 | - 53 | export async function listProcessDefinitions(page = 1, pageSize = 20) { - 54 | const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( - | ^ - 55 | '/workflow/definitions', - 56 | { params: { page, page_size: pageSize } }, - > src/pages/workflow/ProcessDefinitions.tsx:34:18 - -08:45:38 [vite] (client) [Unhandled rejection] AxiosError: Request failed with status code 502 - > settle node_modules/.pnpm/axios@1.15.0/node_modules/axios/lib/core/settle.js:20:6 - > XMLHttpRequest.onloadend node_modules/.pnpm/axios@1.15.0/node_modules/axios/lib/adapters/xhr.js:62:8 - > Axios$1.request node_modules/.pnpm/axios@1.15.0/node_modules/axios/lib/core/Axios.js:46:40 - > listProcessDefinitions src/api/workflowDefinitions.ts:54:19 - 52 | - 53 | export async function listProcessDefinitions(page = 1, pageSize = 20) { - 54 | const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( - | ^ - 55 | '/workflow/definitions', - 56 | { params: { page, page_size: pageSize } }, - > src/pages/workflow/ProcessDefinitions.tsx:34:18 - -08:45:39 [vite] http proxy error: /api/v1/organizations -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:39 [vite] http proxy error: /api/v1/organizations -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:39 [vite] http proxy error: /api/v1/roles?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:39 [vite] http proxy error: /api/v1/permissions -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:39 [vite] http proxy error: /api/v1/roles?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:39 [vite] http proxy error: /api/v1/permissions -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:41 [vite] http proxy error: /api/v1/users?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:41 [vite] http proxy error: /api/v1/roles?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:41 [vite] http proxy error: /api/v1/users?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:41 [vite] http proxy error: /api/v1/roles?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:42 [vite] http proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:42 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/purchase_order?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:42 [vite] http proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:42 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/purchase_order?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:43 [vite] http proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) -08:45:43 [vite] http proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/sales_order?page=1&page_size=20 -AggregateError [ECONNREFUSED]: - at internalConnectMultiple (node:net:1142:49) - at afterConnectMultiple (node:net:1723:7) diff --git a/.logs/frontend.log b/.logs/frontend.log index 15e24c4..d76d459 100644 --- a/.logs/frontend.log +++ b/.logs/frontend.log @@ -2,10 +2,9 @@ > web@0.0.0 dev G:\erp\apps\web > vite "--" "--strictPort" +Port 5174 is in use, trying another one... - VITE v8.0.8 ready in 372 ms + VITE v8.0.8 ready in 316 ms - ➜ Local: http://localhost:5174/ + ➜ Local: http://localhost:5175/  ➜ Network: use --host to expose -08:36:42 [vite] (client) hmr update /src/index.css, /src/pages/PluginCRUDPage.tsx, /src/components/EntitySelect.tsx -08:37:13 [vite] (client) hmr update /src/index.css, /src/components/EntitySelect.tsx diff --git a/.logs/frontend.pid b/.logs/frontend.pid index 71f8b4d..d23fc00 100644 --- a/.logs/frontend.pid +++ b/.logs/frontend.pid @@ -1 +1 @@ -45128 +50960 diff --git a/Cargo.lock b/Cargo.lock index dbf7528..b74dac9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1309,6 +1309,13 @@ dependencies = [ "wit-bindgen 0.55.0", ] +[[package]] +name = "erp-plugin-freelance" +version = "0.1.0" +dependencies = [ + "wit-bindgen 0.55.0", +] + [[package]] name = "erp-plugin-inventory" version = "0.1.0" diff --git a/docs/superpowers/plans/2026-04-19-shantou-zhijie-it-services-plugins-plan.md b/docs/superpowers/plans/2026-04-19-shantou-zhijie-it-services-plugins-plan.md new file mode 100644 index 0000000..0bf4531 --- /dev/null +++ b/docs/superpowers/plans/2026-04-19-shantou-zhijie-it-services-plugins-plan.md @@ -0,0 +1,484 @@ +# 汕头市智界科技 IT 服务插件 — 实施计划 + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 为汕头市智界科技有限公司创建 freelance(自由职业者工作台)和 itops(IT 运维服务台)两个 WASM 插件,覆盖其全部 12 条经营范围。 + +**Architecture:** 两个独立的 WASM 插件 crate,每个包含 Cargo.toml(cdylib)、src/lib.rs(Guest trait 实现)、plugin.toml(声明式 schema)。通过插件安装 API 上传到系统,平台自动创建动态表、注册权限、生成前端页面。itops 通过 ref_plugin 跨插件引用 freelance 的 client 实体。 + +**Tech Stack:** Rust (wit-bindgen 0.55, cdylib → WASM Component)、TOML manifest、Axum Host API + +**Spec:** `docs/superpowers/specs/2026-04-19-shantou-zhijie-it-services-plugins-design.md` + +--- + +## Chunk 1: freelance 插件 + +### Task 1: 创建 crate 目录和 Cargo.toml + +**Files:** +- Create: `crates/erp-plugin-freelance/Cargo.toml` +- Create: `crates/erp-plugin-freelance/src/lib.rs`(空文件占位) + +- [ ] **Step 1: 创建目录结构** + +```bash +mkdir -p crates/erp-plugin-freelance/src +``` + +- [ ] **Step 2: 编写 Cargo.toml** + +创建 `crates/erp-plugin-freelance/Cargo.toml`: + +```toml +[package] +name = "erp-plugin-freelance" +version = "0.1.0" +edition = "2024" +description = "自由职业者工作台 WASM 插件" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = "0.55" +``` + +- [ ] **Step 3: 编写 src/lib.rs** + +创建 `crates/erp-plugin-freelance/src/lib.rs`: + +```rust +//! 自由职业者工作台 WASM 插件 + +wit_bindgen::generate!({ + path: "../erp-plugin-prototype/wit/plugin.wit", + world: "plugin-world", +}); + +use crate::exports::erp::plugin::plugin_api::Guest; + +struct FreelancePlugin; + +impl Guest for FreelancePlugin { + fn init() -> Result<(), String> { + Ok(()) + } + + fn on_tenant_created(_tenant_id: String) -> Result<(), String> { + Ok(()) + } + + fn handle_event(_event_type: String, _payload: Vec) -> Result<(), String> { + Ok(()) + } +} + +export!(FreelancePlugin); +``` + +- [ ] **Step 4: 注册到 workspace** + +编辑根 `Cargo.toml`,在 `members` 数组末尾添加: + +```toml + "crates/erp-plugin-freelance", +``` + +- [ ] **Step 5: 验证编译** + +```bash +cargo check -p erp-plugin-freelance +``` + +Expected: 编译通过,无错误 + +- [ ] **Step 6: Commit** + +```bash +git add crates/erp-plugin-freelance/ Cargo.toml +git commit -m "feat(freelance): 创建插件 crate 骨架" +``` + +--- + +### Task 2: 编写 plugin.toml(freelance) + +**Files:** +- Create: `crates/erp-plugin-freelance/plugin.toml` + +- [ ] **Step 1: 从设计规格文档复制完整 plugin.toml 内容** + +从设计规格 `docs/superpowers/specs/2026-04-19-shantou-zhijie-it-services-plugins-design.md` 中提取 2.1(元数据)+ 2.2(权限)+ 2.3(10 个实体)+ 2.4(编号规则)+ 2.5(页面声明)的所有 TOML 内容,合并为完整的 `plugin.toml` 文件。 + +文件结构: +1. `[metadata]` 段 +2. `[[permissions]]` × 20 +3. `[[schema.entities]]` × 10(client, opportunity, quote, quote_line, contract, project, task, time_entry, invoice, expense),每个实体包含 fields 和 relations +4. `[[numbering]]` × 3(quote_number, contract_number, invoice_number) +5. `[[ui.pages]]` × 7(dashboard, tabs+detail+kanban for client, crud+detail for project, tabs for finance, crud for expense) + +注意要点: +- client 实体必须标记 `is_public = true`(被 itops 跨插件引用) +- quote 到 quote_line 有 cascade 关系 +- project 到 task 和 time_entry 有 cascade 关系 +- 所有 uuid 引用字段使用 `ui_widget = "entity_select"` + `ref_label_field` + `ref_search_fields` +- 所有 select 字段使用 `options = [{ label = "X", value = "x" }]` 格式 +- 长文本使用 `field_type = "string"` + `ui_widget = "textarea"` +- 金额使用 `field_type = "decimal"` +- 时间戳使用 `field_type = "date_time"` + +- [ ] **Step 2: 验证 TOML 格式** + +```bash +cargo check -p erp-plugin-freelance +``` + +- [ ] **Step 3: Commit** + +```bash +git add crates/erp-plugin-freelance/plugin.toml +git commit -m "feat(freelance): 添加 plugin.toml — 10 实体/20 权限/7 页面" +``` + +--- + +### Task 3: 编译 WASM 并安装 + +- [ ] **Step 1: 编译为 WASM** + +```bash +cargo build -p erp-plugin-freelance --target wasm32-unknown-unknown --release +``` + +Expected: 编译成功,产出 `target/wasm32-unknown-unknown/release/erp_plugin_freelance.wasm` + +- [ ] **Step 2: 转换为 Component** + +```bash +wasm-tools component new target/wasm32-unknown-unknown/release/erp_plugin_freelance.wasm -o target/erp_plugin_freelance.component.wasm +``` + +- [ ] **Step 3: 检查产物大小** + +```bash +ls -la target/erp_plugin_freelance.component.wasm +``` + +Expected: < 100KB(CRM 约 22KB) + +- [ ] **Step 4: 启动后端服务** + +```bash +cd crates/erp-server && cargo run +``` + +等待服务启动完成(看到 "listening on 0.0.0.0:3000" 日志) + +- [ ] **Step 5: 登录获取 Token** + +```bash +curl -s -X POST http://localhost:3000/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' | jq -r '.data.access_token' +``` + +保存输出的 token。 + +- [ ] **Step 6: 上传安装插件** + +```bash +TOKEN="<上一步的 token>" +MANIFEST=$(cat crates/erp-plugin-freelance/plugin.toml) + +curl -s -X POST http://localhost:3000/api/v1/admin/plugins/upload \ + -H "Authorization: Bearer $TOKEN" \ + -F "wasm=@target/erp_plugin_freelance.component.wasm" \ + -F "manifest=$MANIST" +``` + +Expected: 返回插件 ID,状态为 `installed` + +- [ ] **Step 7: 启用插件** + +使用上一步返回的插件 ID: + +```bash +PLUGIN_ID="<返回的插件 ID>" +curl -s -X POST "http://localhost:3000/api/v1/admin/plugins/$PLUGIN_ID/enable" \ + -H "Authorization: Bearer $TOKEN" +``` + +Expected: 状态变为 `running` + +- [ ] **Step 8: Commit** + +```bash +git add -A +git commit -m "feat(freelance): 编译 WASM 并验证安装" +``` + +--- + +### Task 4: 浏览器验证 freelance 插件 + +- [ ] **Step 1: 打开浏览器访问 http://localhost:5174** + +- [ ] **Step 2: 登录后检查侧边栏** + +Expected: 看到"自由职业者工作台"菜单组,包含:工作台、客户管理、商机看板、项目管理、项目详情、财务中心、支出管理 + +- [ ] **Step 3: 测试客户 CRUD** + +进入客户管理 → 新增客户(填写名称、联系人、电话、行业等)→ 保存 → 列表中可见 + +- [ ] **Step 4: 测试项目 → 任务级联** + +进入项目管理 → 新增项目 → 进入项目详情 → 新增任务 → 验证任务关联到项目 + +- [ ] **Step 5: 测试报价 → 报价明细级联** + +进入财务中心 → 报价管理 tab → 新增报价 → 验证明细行可添加 + +- [ ] **Step 6: 测试商机看板** + +进入商机看板 → 新增商机 → 拖拽改变阶段 → 验证数据更新 + +- [ ] **Step 7: 验证数据库表创建** + +```bash +PGPASSWORD=123123 "D:\postgreSQL\bin\psql.exe" -U postgres -h localhost -d erp -c "\dt plugin_erp_freelance_*" +``` + +Expected: 看到 10 张动态表 + +--- + +## Chunk 2: itops 插件 + +### Task 5: 创建 itops 插件 crate + +**Files:** +- Create: `crates/erp-plugin-itops/Cargo.toml` +- Create: `crates/erp-plugin-itops/src/lib.rs` +- Create: `crates/erp-plugin-itops/plugin.toml` + +- [ ] **Step 1: 创建目录结构** + +```bash +mkdir -p crates/erp-plugin-itops/src +``` + +- [ ] **Step 2: 编写 Cargo.toml** + +创建 `crates/erp-plugin-itops/Cargo.toml`: + +```toml +[package] +name = "erp-plugin-itops" +version = "0.1.0" +edition = "2024" +description = "IT 运维服务台 WASM 插件" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = "0.55" +``` + +- [ ] **Step 3: 编写 src/lib.rs** + +创建 `crates/erp-plugin-itops/src/lib.rs`: + +```rust +//! IT 运维服务台 WASM 插件 + +wit_bindgen::generate!({ + path: "../erp-plugin-prototype/wit/plugin.wit", + world: "plugin-world", +}); + +use crate::exports::erp::plugin::plugin_api::Guest; + +struct ItopsPlugin; + +impl Guest for ItopsPlugin { + fn init() -> Result<(), String> { + Ok(()) + } + + fn on_tenant_created(_tenant_id: String) -> Result<(), String> { + Ok(()) + } + + fn handle_event(_event_type: String, _payload: Vec) -> Result<(), String> { + Ok(()) + } +} + +export!(ItopsPlugin); +``` + +- [ ] **Step 4: 编写 plugin.toml** + +从设计规格文档 Section 3 提取完整内容: +1. `[metadata]` — id="erp-itops",无 dependencies(松耦合) +2. `[[permissions]]` × 8 +3. `[[schema.entities]]` × 4(service_contract, ticket, check_plan, check_record),每个实体包含 fields 和 relations +4. `[[numbering]]` × 1(contract_number) +5. `[[ui.pages]]` × 4(crud+detail for service_contract, tabs for ticket center) + +关键注意点: +- 4 个实体的 `client_id` 字段都使用 `ref_plugin = "erp-freelance"` + `ref_fallback_label = "外部客户"` +- `filterable` 只用于 string 类型的 status/type/category 字段,不用于 uuid 字段 +- `check_items` 和 `items_data` 使用 `field_type = "json"` +- `responded_at` / `resolved_at` / `closed_at` 使用 `field_type = "date_time"` + +- [ ] **Step 5: 注册到 workspace** + +编辑根 `Cargo.toml`,在 members 数组末尾添加: + +```toml + "crates/erp-plugin-itops", +``` + +- [ ] **Step 6: 验证编译** + +```bash +cargo check -p erp-plugin-itops +``` + +- [ ] **Step 7: Commit** + +```bash +git add crates/erp-plugin-itops/ Cargo.toml +git commit -m "feat(itops): 创建 IT 运维服务台插件 — 4 实体/8 权限/4 页面" +``` + +--- + +### Task 6: 编译 WASM 并安装 itops + +- [ ] **Step 1: 编译为 WASM** + +```bash +cargo build -p erp-plugin-itops --target wasm32-unknown-unknown --release +``` + +- [ ] **Step 2: 转换为 Component** + +```bash +wasm-tools component new target/wasm32-unknown-unknown/release/erp_plugin_itops.wasm -o target/erp_plugin_itops.component.wasm +``` + +- [ ] **Step 3: 上传安装插件** + +```bash +TOKEN="<之前获取的 token>" +MANIFEST=$(cat crates/erp-plugin-itops/plugin.toml) + +curl -s -X POST http://localhost:3000/api/v1/admin/plugins/upload \ + -H "Authorization: Bearer $TOKEN" \ + -F "wasm=@target/erp_plugin_itops.component.wasm" \ + -F "manifest=$MANIFEST" +``` + +- [ ] **Step 4: 启用插件** + +```bash +PLUGIN_ID="<返回的插件 ID>" +curl -s -X POST "http://localhost:3000/api/v1/admin/plugins/$PLUGIN_ID/enable" \ + -H "Authorization: Bearer $TOKEN" +``` + +--- + +### Task 7: 浏览器验证 itops 插件 + +- [ ] **Step 1: 检查侧边栏** + +Expected: 看到"IT 运维服务台"菜单组,包含:合同管理、合同详情、工单中心 + +- [ ] **Step 2: 测试维保合同 CRUD** + +进入合同管理 → 新增维保合同(选择客户时验证:如 freelance 已安装,客户下拉显示 freelance 的客户列表) + +- [ ] **Step 3: 测试跨插件引用** + +场景 A(freelance 已安装):创建工单时 client_id 字段显示为下拉选择器,可搜索 freelance.client +场景 B(freelance 未安装):client_id 降级为文本输入,显示"外部客户" + +- [ ] **Step 4: 测试合同 → 工单 → 巡检级联** + +进入合同详情 → 工单 tab → 新增工单 → 巡检计划 tab → 新增巡检计划 → 巡检记录 tab → 新增巡检记录 + +- [ ] **Step 5: 验证数据库表** + +```bash +PGPASSWORD=123123 "D:\postgreSQL\bin\psql.exe" -U postgres -h localhost -d erp -c "\dt plugin_erp_itops_*" +``` + +Expected: 看到 4 张动态表 + +--- + +## Chunk 3: 集成验证 + +### Task 8: 全链路端到端验证 + +- [ ] **Step 1: 创建客户** + +freelance → 客户管理 → 新增客户"汕头市XX科技有限公司" + +- [ ] **Step 2: 创建商机** + +商机看板 → 新增商机 → 选择客户 → 填写"官网开发"→ 拖拽到"成交"阶段 + +- [ ] **Step 3: 创建报价单** + +财务中心 → 报价管理 → 新增报价 → 选择客户 → 添加明细行 → 保存 + +- [ ] **Step 4: 创建合同** + +财务中心 → 合同管理 → 新增合同 → 选择客户 → 填写金额和日期 → 保存 + +- [ ] **Step 5: 创建项目** + +项目管理 → 新增项目 → 选择客户和合同 → 填写"官网开发项目" → 添加任务 → 记录工时 + +- [ ] **Step 6: 创建发票** + +财务中心 → 发票/收款 → 新增发票 → 选择客户和项目 → 填写金额 → 标记已收款 + +- [ ] **Step 7: 创建运维工单** + +itops → 合同管理 → 新增维保合同 → 选择客户(验证跨插件引用)→ 保存 +itops → 工单中心 → 新增工单 → 选择客户和合同 → 保存 + +- [ ] **Step 8: 记录支出** + +freelance → 支出管理 → 新增支出 → 选择类别"云服务" → 填写金额 → 保存 + +- [ ] **Step 9: 提交并推送** + +```bash +git add -A +git commit -m "feat(freelance,itops): 汕头市智界科技 IT 服务行业插件验证通过" +git push +``` + +--- + +## 关键参考文件 + +| 文件 | 用途 | +|------|------| +| `crates/erp-plugin-crm/Cargo.toml` | Cargo.toml 模板参考 | +| `crates/erp-plugin-crm/src/lib.rs` | lib.rs 代码模式参考 | +| `crates/erp-plugin-crm/plugin.toml` | plugin.toml 格式参考(同插件内引用) | +| `crates/erp-plugin-inventory/plugin.toml` | 跨插件引用格式参考(ref_plugin) | +| `crates/erp-plugin/src/manifest.rs` | PluginField/PluginFieldType 完整定义 | +| `crates/erp-plugin-prototype/wit/plugin.wit` | WIT 接口定义 | +| `wiki/infrastructure.md` | 数据库连接、端口、登录凭据 | +| `wiki/wasm-plugin.md` | 插件制作完整流程 | diff --git a/docs/superpowers/specs/2026-04-19-shantou-zhijie-it-services-plugins-design.md b/docs/superpowers/specs/2026-04-19-shantou-zhijie-it-services-plugins-design.md new file mode 100644 index 0000000..a198851 --- /dev/null +++ b/docs/superpowers/specs/2026-04-19-shantou-zhijie-it-services-plugins-design.md @@ -0,0 +1,1887 @@ +# 汕头市智界科技有限公司 — 行业插件设计规格 + +> 日期: 2026-04-19 +> 来源: 无主题发散式互动探讨(多专家头脑风暴) +> 状态: Draft + +--- + +## 1. 背景与动机 + +以汕头市智界科技有限公司(一人 IT 服务公司)为案例,设计针对 IT 服务行业的 ERP 插件组合,覆盖其全部经营范围。 + +**公司经营范围:** +1. 软件开发 +2. 数字文化创意软件开发 +3. 人工智能基础/应用软件开发 +4. 信息系统集成服务 +5. 网络技术服务 +6. 软件销售 +7. 软件外包服务 +8. 计算机软硬件及辅助设备批发/零售 +9. 信息技术咨询服务 +10. 信息系统运行维护服务 +11. 数字内容制作服务 +12. 市场营销策划 + +**设计原则:** +- 一人公司优先:所有功能对单人操作友好,减少上下文切换 +- 最少插件覆盖最多场景:3 个插件覆盖全部经营范围 +- 快捷操作优先:高频操作一键完成,不打开完整表单 +- 全局工作台:一个仪表盘掌握全局 + +**插件组合与经营范围对照:** + +| 插件 | 覆盖的经营范围 | +|------|--------------| +| freelance(自由职业者工作台) | 软件开发、AI开发、系统集成、外包、IT咨询、数字内容、营销策划 | +| itops(IT 运维服务台) | IT 运维服务、网络技术服务 | +| inventory(已有,需扩展) | 软硬件批发零售 | + +--- + +## 2. 插件 1:erp-plugin-freelance(自由职业者工作台) + +### 2.1 插件元数据 + +```toml +[metadata] +id = "erp-freelance" +name = "自由职业者工作台" +version = "0.1.0" +description = "一人 IT 服务公司的全链路业务管理:客户→商机→报价→合同→项目→工时→开票→收支" +author = "ERP Platform" +min_platform_version = "0.1.0" +``` + +### 2.2 权限声明(10 实体 × 2 = 20 个权限码) + +```toml +[[permissions]] +code = "client.list" +name = "查看客户" +description = "查看客户列表和详情" + +[[permissions]] +code = "client.manage" +name = "管理客户" +description = "创建、编辑、删除客户" + +[[permissions]] +code = "opportunity.list" +name = "查看商机" +description = "查看商机列表和详情" + +[[permissions]] +code = "opportunity.manage" +name = "管理商机" +description = "创建、编辑、删除商机" + +[[permissions]] +code = "quote.list" +name = "查看报价" +description = "查看报价单列表和详情" + +[[permissions]] +code = "quote.manage" +name = "管理报价" +description = "创建、编辑、删除报价单" + +[[permissions]] +code = "quote_line.list" +name = "查看报价明细" +description = "查看报价明细列表" + +[[permissions]] +code = "quote_line.manage" +name = "管理报价明细" +description = "创建、编辑、删除报价明细" + +[[permissions]] +code = "contract.list" +name = "查看合同" +description = "查看合同列表和详情" + +[[permissions]] +code = "contract.manage" +name = "管理合同" +description = "创建、编辑、删除合同" + +[[permissions]] +code = "project.list" +name = "查看项目" +description = "查看项目列表和详情" + +[[permissions]] +code = "project.manage" +name = "管理项目" +description = "创建、编辑、删除项目" + +[[permissions]] +code = "task.list" +name = "查看任务" +description = "查看任务列表和详情" + +[[permissions]] +code = "task.manage" +name = "管理任务" +description = "创建、编辑、删除任务" + +[[permissions]] +code = "time_entry.list" +name = "查看工时" +description = "查看工时记录列表" + +[[permissions]] +code = "time_entry.manage" +name = "管理工时" +description = "创建、编辑、删除工时记录" + +[[permissions]] +code = "invoice.list" +name = "查看发票" +description = "查看发票/收款列表和详情" + +[[permissions]] +code = "invoice.manage" +name = "管理发票" +description = "创建、编辑、删除发票/收款记录" + +[[permissions]] +code = "expense.list" +name = "查看支出" +description = "查看支出列表和详情" + +[[permissions]] +code = "expense.manage" +name = "管理支出" +description = "创建、编辑、删除支出记录" +``` + +### 2.3 实体设计(10 个实体) + +#### 2.3.1 client(客户) + +```toml +[[schema.entities]] +name = "client" +display_name = "客户" +is_public = true + + [[schema.entities.fields]] + name = "name" + field_type = "string" + required = true + display_name = "客户名称" + searchable = true + + [[schema.entities.fields]] + name = "contact_name" + field_type = "string" + display_name = "联系人" + + [[schema.entities.fields]] + name = "phone" + field_type = "string" + display_name = "电话" + + [[schema.entities.fields]] + name = "email" + field_type = "string" + display_name = "邮箱" + + [[schema.entities.fields]] + name = "industry" + field_type = "string" + display_name = "行业" + ui_widget = "select" + filterable = true + options = [ + { label = "制造业", value = "manufacturing" }, + { label = "零售", value = "retail" }, + { label = "教育", value = "education" }, + { label = "医疗", value = "healthcare" }, + { label = "政府", value = "government" }, + { label = "金融", value = "finance" }, + { label = "其他", value = "other" } + ] + + [[schema.entities.fields]] + name = "source" + field_type = "string" + display_name = "来源" + ui_widget = "select" + options = [ + { label = "转介绍", value = "referral" }, + { label = "线上", value = "online" }, + { label = "展会", value = "exhibition" }, + { label = "老客户", value = "repeat" }, + { label = "主动开发", value = "outreach" } + ] + + [[schema.entities.fields]] + name = "level" + field_type = "string" + display_name = "重要等级" + ui_widget = "select" + filterable = true + options = [ + { label = "A", value = "a" }, + { label = "B", value = "b" }, + { label = "C", value = "c" } + ] + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + filterable = true + default = "potential" + options = [ + { label = "潜在", value = "potential" }, + { label = "活跃", value = "active" }, + { label = "休眠", value = "dormant" }, + { label = "流失", value = "lost" } + ] + + [[schema.entities.fields]] + name = "address" + field_type = "string" + display_name = "地址" + + [[schema.entities.fields]] + name = "notes" + field_type = "string" + display_name = "备注" + ui_widget = "textarea" +``` + +#### 2.3.2 opportunity(商机) + +```toml +[[schema.entities]] +name = "opportunity" +display_name = "商机" + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + required = true + display_name = "客户" + ui_widget = "entity_select" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "title" + field_type = "string" + required = true + display_name = "商机名称" + searchable = true + + [[schema.entities.fields]] + name = "business_type" + field_type = "string" + required = true + display_name = "业务类型" + ui_widget = "select" + filterable = true + options = [ + { label = "软件开发", value = "software_development" }, + { label = "AI 开发", value = "ai_development" }, + { label = "系统集成", value = "system_integration" }, + { label = "软件外包", value = "software_outsourcing" }, + { label = "IT 咨询", value = "it_consulting" }, + { label = "数字内容", value = "digital_content" }, + { label = "营销策划", value = "marketing_planning" } + ] + + [[schema.entities.fields]] + name = "stage" + field_type = "string" + required = true + display_name = "阶段" + ui_widget = "select" + filterable = true + default = "visit" + options = [ + { label = "初访", value = "visit" }, + { label = "需求确认", value = "requirement" }, + { label = "报价", value = "quote" }, + { label = "谈判", value = "negotiation" }, + { label = "成交", value = "won" }, + { label = "失败", value = "lost" } + ] + + [[schema.entities.fields]] + name = "estimated_amount" + field_type = "decimal" + display_name = "预估金额" + sortable = true + + [[schema.entities.fields]] + name = "probability" + field_type = "integer" + display_name = "成交概率(%)" + + [[schema.entities.fields]] + name = "expected_close_date" + field_type = "date" + display_name = "预计成交日期" + + [[schema.entities.fields]] + name = "next_follow_up" + field_type = "date" + display_name = "下次跟进日期" + + [[schema.entities.fields]] + name = "description" + field_type = "string" + display_name = "需求描述" + ui_widget = "textarea" +``` + +#### 2.3.3 quote(报价单) + +```toml +[[schema.entities]] +name = "quote" +display_name = "报价单" + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + required = true + display_name = "客户" + ui_widget = "entity_select" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "opportunity_id" + field_type = "uuid" + display_name = "关联商机" + ref_entity = "opportunity" + + [[schema.entities.fields]] + name = "quote_number" + field_type = "string" + required = true + display_name = "报价单号" + unique = true + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + filterable = true + default = "draft" + options = [ + { label = "草稿", value = "draft" }, + { label = "已发送", value = "sent" }, + { label = "已接受", value = "accepted" }, + { label = "已拒绝", value = "rejected" }, + { label = "已过期", value = "expired" } + ] + + [[schema.entities.fields]] + name = "valid_until" + field_type = "date" + display_name = "有效期至" + + [[schema.entities.fields]] + name = "subtotal" + field_type = "decimal" + display_name = "小计" + sortable = true + + [[schema.entities.fields]] + name = "tax_rate" + field_type = "decimal" + display_name = "税率(%)" + + [[schema.entities.fields]] + name = "total_amount" + field_type = "decimal" + display_name = "总金额" + sortable = true + + [[schema.entities.fields]] + name = "notes" + field_type = "string" + display_name = "备注" + ui_widget = "textarea" + + [[schema.entities.relations]] + entity = "quote_line" + foreign_key = "quote_id" + on_delete = "cascade" + name = "lines" + type = "one_to_many" + display_field = "item_name" +``` + +#### 2.3.4 quote_line(报价明细行) + +```toml +[[schema.entities]] +name = "quote_line" +display_name = "报价明细" + + [[schema.entities.fields]] + name = "quote_id" + field_type = "uuid" + required = true + display_name = "报价单" + ref_entity = "quote" + + [[schema.entities.fields]] + name = "item_name" + field_type = "string" + required = true + display_name = "项目名称" + + [[schema.entities.fields]] + name = "description" + field_type = "string" + display_name = "描述" + + [[schema.entities.fields]] + name = "quantity" + field_type = "decimal" + required = true + display_name = "数量" + default = 1 + + [[schema.entities.fields]] + name = "unit_price" + field_type = "decimal" + required = true + display_name = "单价" + + [[schema.entities.fields]] + name = "unit" + field_type = "string" + display_name = "单位" + ui_widget = "select" + default = "project" + options = [ + { label = "项目", value = "project" }, + { label = "人月", value = "person_month" }, + { label = "人天", value = "person_day" }, + { label = "小时", value = "hour" }, + { label = "个", value = "piece" }, + { label = "套", value = "set" } + ] + + [[schema.entities.fields]] + name = "amount" + field_type = "decimal" + display_name = "金额" +``` + +#### 2.3.5 contract(合同) + +```toml +[[schema.entities]] +name = "contract" +display_name = "合同" + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + required = true + display_name = "客户" + ui_widget = "entity_select" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "opportunity_id" + field_type = "uuid" + display_name = "关联商机" + ref_entity = "opportunity" + + [[schema.entities.fields]] + name = "quote_id" + field_type = "uuid" + display_name = "关联报价" + ref_entity = "quote" + + [[schema.entities.fields]] + name = "contract_number" + field_type = "string" + required = true + display_name = "合同编号" + unique = true + + [[schema.entities.fields]] + name = "title" + field_type = "string" + required = true + display_name = "合同名称" + searchable = true + + [[schema.entities.fields]] + name = "type" + field_type = "string" + required = true + display_name = "合同类型" + ui_widget = "select" + filterable = true + options = [ + { label = "开发合同", value = "development" }, + { label = "集成合同", value = "integration" }, + { label = "外包合同", value = "outsourcing" }, + { label = "咨询合同", value = "consulting" }, + { label = "维保合同", value = "maintenance" }, + { label = "销售合同", value = "sales" }, + { label = "其他", value = "other" } + ] + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + filterable = true + default = "drafting" + options = [ + { label = "草拟中", value = "drafting" }, + { label = "待签署", value = "pending_signature" }, + { label = "执行中", value = "active" }, + { label = "已完成", value = "completed" }, + { label = "已终止", value = "terminated" } + ] + + [[schema.entities.fields]] + name = "amount" + field_type = "decimal" + required = true + display_name = "合同金额" + sortable = true + + [[schema.entities.fields]] + name = "paid_amount" + field_type = "decimal" + display_name = "已付金额" + default = 0 + + [[schema.entities.fields]] + name = "start_date" + field_type = "date" + display_name = "开始日期" + + [[schema.entities.fields]] + name = "end_date" + field_type = "date" + display_name = "结束日期" + + [[schema.entities.fields]] + name = "payment_terms" + field_type = "string" + display_name = "付款条款" + + [[schema.entities.fields]] + name = "notes" + field_type = "string" + display_name = "备注" + ui_widget = "textarea" +``` + +#### 2.3.6 project(项目) + +```toml +[[schema.entities]] +name = "project" +display_name = "项目" + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + required = true + display_name = "客户" + ui_widget = "entity_select" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "contract_id" + field_type = "uuid" + display_name = "关联合同" + ref_entity = "contract" + + [[schema.entities.fields]] + name = "opportunity_id" + field_type = "uuid" + display_name = "关联商机" + ref_entity = "opportunity" + + [[schema.entities.fields]] + name = "name" + field_type = "string" + required = true + display_name = "项目名称" + searchable = true + + [[schema.entities.fields]] + name = "business_type" + field_type = "string" + display_name = "业务类型" + ui_widget = "select" + filterable = true + options = [ + { label = "软件开发", value = "software_development" }, + { label = "AI 开发", value = "ai_development" }, + { label = "系统集成", value = "system_integration" }, + { label = "软件外包", value = "software_outsourcing" }, + { label = "IT 咨询", value = "it_consulting" }, + { label = "数字内容", value = "digital_content" }, + { label = "营销策划", value = "marketing_planning" } + ] + + [[schema.entities.fields]] + name = "deliverable_type" + field_type = "string" + display_name = "交付物类型" + ui_widget = "select" + options = [ + { label = "软件", value = "software" }, + { label = "网站", value = "website" }, + { label = "小程序", value = "miniprogram" }, + { label = "视频", value = "video" }, + { label = "设计稿", value = "design" }, + { label = "文档", value = "document" }, + { label = "咨询报告", value = "consulting_report" } + ] + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + filterable = true + default = "pending" + options = [ + { label = "待开始", value = "pending" }, + { label = "进行中", value = "in_progress" }, + { label = "已交付", value = "delivered" }, + { label = "验收中", value = "accepting" }, + { label = "已完成", value = "completed" }, + { label = "已搁置", value = "shelved" } + ] + + [[schema.entities.fields]] + name = "start_date" + field_type = "date" + display_name = "开始日期" + + [[schema.entities.fields]] + name = "end_date" + field_type = "date" + display_name = "截止日期" + + [[schema.entities.fields]] + name = "contract_amount" + field_type = "decimal" + display_name = "合同金额" + sortable = true + + [[schema.entities.fields]] + name = "description" + field_type = "string" + display_name = "项目说明" + ui_widget = "textarea" + + [[schema.entities.relations]] + entity = "task" + foreign_key = "project_id" + on_delete = "cascade" + name = "tasks" + type = "one_to_many" + display_field = "title" + + [[schema.entities.relations]] + entity = "time_entry" + foreign_key = "project_id" + on_delete = "cascade" + name = "time_entries" + type = "one_to_many" + display_field = "description" +``` + +#### 2.3.7 task(任务) + +```toml +[[schema.entities]] +name = "task" +display_name = "任务" + + [[schema.entities.fields]] + name = "project_id" + field_type = "uuid" + required = true + display_name = "所属项目" + ui_widget = "entity_select" + ref_entity = "project" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "title" + field_type = "string" + required = true + display_name = "任务标题" + searchable = true + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + filterable = true + default = "todo" + options = [ + { label = "待办", value = "todo" }, + { label = "进行中", value = "in_progress" }, + { label = "已完成", value = "done" }, + { label = "已取消", value = "cancelled" } + ] + + [[schema.entities.fields]] + name = "priority" + field_type = "string" + required = true + display_name = "优先级" + ui_widget = "select" + filterable = true + default = "medium" + options = [ + { label = "紧急", value = "urgent" }, + { label = "高", value = "high" }, + { label = "中", value = "medium" }, + { label = "低", value = "low" } + ] + + [[schema.entities.fields]] + name = "due_date" + field_type = "date" + display_name = "截止日期" + + [[schema.entities.fields]] + name = "estimated_hours" + field_type = "decimal" + display_name = "预估工时" + + [[schema.entities.fields]] + name = "actual_hours" + field_type = "decimal" + display_name = "实际工时" + + [[schema.entities.fields]] + name = "description" + field_type = "string" + display_name = "详细说明" + ui_widget = "textarea" +``` + +#### 2.3.8 time_entry(工时记录) + +```toml +[[schema.entities]] +name = "time_entry" +display_name = "工时记录" + + [[schema.entities.fields]] + name = "task_id" + field_type = "uuid" + display_name = "关联任务" + ui_widget = "entity_select" + ref_entity = "task" + ref_label_field = "title" + ref_search_fields = ["title"] + + [[schema.entities.fields]] + name = "project_id" + field_type = "uuid" + required = true + display_name = "关联项目" + ui_widget = "entity_select" + ref_entity = "project" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "date" + field_type = "date" + required = true + display_name = "日期" + + [[schema.entities.fields]] + name = "hours" + field_type = "decimal" + required = true + display_name = "时长(小时)" + + [[schema.entities.fields]] + name = "description" + field_type = "string" + display_name = "工作内容" +``` + +#### 2.3.9 invoice(发票/收款) + +```toml +[[schema.entities]] +name = "invoice" +display_name = "发票/收款" + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + required = true + display_name = "客户" + ui_widget = "entity_select" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "project_id" + field_type = "uuid" + display_name = "关联项目" + ref_entity = "project" + + [[schema.entities.fields]] + name = "contract_id" + field_type = "uuid" + display_name = "关联合同" + ref_entity = "contract" + + [[schema.entities.fields]] + name = "invoice_number" + field_type = "string" + required = true + display_name = "发票号" + unique = true + + [[schema.entities.fields]] + name = "type" + field_type = "string" + required = true + display_name = "类型" + ui_widget = "select" + filterable = true + options = [ + { label = "开票", value = "invoice" }, + { label = "收款记录", value = "payment" } + ] + + [[schema.entities.fields]] + name = "amount" + field_type = "decimal" + required = true + display_name = "金额" + sortable = true + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + filterable = true + default = "pending" + options = [ + { label = "待开票", value = "pending" }, + { label = "已开票", value = "issued" }, + { label = "部分收款", value = "partial" }, + { label = "已收款", value = "paid" }, + { label = "已逾期", value = "overdue" } + ] + + [[schema.entities.fields]] + name = "issue_date" + field_type = "date" + display_name = "开票日期" + + [[schema.entities.fields]] + name = "due_date" + field_type = "date" + display_name = "到期日" + + [[schema.entities.fields]] + name = "payment_date" + field_type = "date" + display_name = "实际收款日期" + + [[schema.entities.fields]] + name = "notes" + field_type = "string" + display_name = "备注" + ui_widget = "textarea" +``` + +#### 2.3.10 expense(支出) + +```toml +[[schema.entities]] +name = "expense" +display_name = "支出" + + [[schema.entities.fields]] + name = "project_id" + field_type = "uuid" + display_name = "关联项目" + ref_entity = "project" + + [[schema.entities.fields]] + name = "category" + field_type = "string" + required = true + display_name = "类别" + ui_widget = "select" + filterable = true + options = [ + { label = "硬件", value = "hardware" }, + { label = "软件订阅", value = "software" }, + { label = "云服务", value = "cloud" }, + { label = "差旅", value = "travel" }, + { label = "餐饮", value = "meal" }, + { label = "办公用品", value = "office" }, + { label = "其他", value = "other" } + ] + + [[schema.entities.fields]] + name = "amount" + field_type = "decimal" + required = true + display_name = "金额" + sortable = true + + [[schema.entities.fields]] + name = "date" + field_type = "date" + required = true + display_name = "日期" + + [[schema.entities.fields]] + name = "vendor" + field_type = "string" + display_name = "供应商" + + [[schema.entities.fields]] + name = "description" + field_type = "string" + display_name = "描述" +``` + +### 2.4 编号规则 + +```toml +[[numbering]] +entity = "quote" +field = "quote_number" +prefix = "QT" +format = "{PREFIX}-{YEAR}-{SEQ}" +seq_length = 4 + +[[numbering]] +entity = "contract" +field = "contract_number" +prefix = "CT" +format = "{PREFIX}-{YEAR}-{SEQ}" +seq_length = 4 + +[[numbering]] +entity = "invoice" +field = "invoice_number" +prefix = "INV" +format = "{PREFIX}-{YEAR}-{SEQ}" +seq_length = 4 +``` + +### 2.5 页面设计(4 个页面) + +#### 页面 1:全局工作台(Dashboard) + +展示内容: +- **财务概览**:本月收入/支出/利润、应收总额、逾期金额 +- **紧急待办**:今日到期任务、逾期收款、即将到期合同(7天内) +- **活跃项目**:进行中项目卡片(进度条 = 已用工时/预估工时) +- **最新工单**:最近 5 条 itops 工单(如已安装) +- **商机跟进提醒**:next_follow_up 在本周内的商机 + +快捷操作按钮条(页面顶部): +- 开始/停止计时(悬浮计时器) +- 快速记支出(3 字段:金额+分类+备注) +- 新建报价 +- 新建工单(如 itops 已安装) + +```toml +[[ui.pages]] +type = "dashboard" +label = "工作台" +icon = "DashboardOutlined" +``` + +#### 页面 2:客户管理 + +客户列表(支持搜索、按来源/行业/等级/状态筛选)+ 客户详情 Tab 页: +- Tab 1:基本信息 +- Tab 2:商机列表(看板视图,按阶段分列) +- Tab 3:报价/合同 +- Tab 4:项目 +- Tab 5:发票/收支 + +从客户详情一键生成:报价单 / 合同 / 项目 + +```toml +[[ui.pages]] +type = "tabs" +label = "客户管理" +icon = "team" + + [[ui.pages.tabs]] + label = "客户列表" + type = "crud" + entity = "client" + enable_search = true + +[[ui.pages]] +type = "detail" +entity = "client" +label = "客户详情" + + [[ui.pages.sections]] + type = "fields" + label = "基本信息" + fields = ["name", "contact_name", "phone", "email", "industry", "source", "level", "status", "address", "notes"] + + [[ui.pages.sections]] + type = "crud" + label = "商机" + entity = "opportunity" + filter_field = "client_id" + + [[ui.pages.sections]] + type = "crud" + label = "报价/合同" + entity = "quote" + filter_field = "client_id" + + [[ui.pages.sections]] + type = "crud" + label = "项目" + entity = "project" + filter_field = "client_id" + + [[ui.pages.sections]] + type = "crud" + label = "发票" + entity = "invoice" + filter_field = "client_id" + +[[ui.pages]] +type = "kanban" +entity = "opportunity" +label = "商机看板" +icon = "swap" +lane_field = "stage" +lane_order = ["visit", "requirement", "quote", "negotiation", "won", "lost"] +card_title_field = "title" +card_subtitle_field = "estimated_amount" +card_fields = ["business_type", "probability"] +enable_drag = true +``` + +#### 页面 3:项目工作台 + +项目列表 → 项目详情 Tab 页: +- Tab 1:项目信息 +- Tab 2:任务列表(状态分组,支持拖拽排序) +- Tab 3:工时记录(支持快速录入) +- Tab 4:关联发票 + +```toml +[[ui.pages]] +type = "crud" +entity = "project" +label = "项目管理" +icon = "project" +enable_search = true + +[[ui.pages]] +type = "detail" +entity = "project" +label = "项目详情" + + [[ui.pages.sections]] + type = "fields" + label = "项目信息" + fields = ["name", "client_id", "contract_id", "business_type", "deliverable_type", "status", "start_date", "end_date", "contract_amount", "description"] + + [[ui.pages.sections]] + type = "crud" + label = "任务" + entity = "task" + filter_field = "project_id" + + [[ui.pages.sections]] + type = "crud" + label = "工时记录" + entity = "time_entry" + filter_field = "project_id" + + [[ui.pages.sections]] + type = "crud" + label = "发票" + entity = "invoice" + filter_field = "project_id" +``` + +#### 页面 4:财务中心 + +Tab 1:报价管理(报价单列表 + 详情) +Tab 2:合同管理(合同列表 + 详情 + 到期预警) +Tab 3:发票/收款(发票列表 + 收支统计图表) + +```toml +[[ui.pages]] +type = "tabs" +label = "财务中心" +icon = "account-book" + + [[ui.pages.tabs]] + label = "报价管理" + type = "crud" + entity = "quote" + enable_search = true + + [[ui.pages.tabs]] + label = "合同管理" + type = "crud" + entity = "contract" + enable_search = true + + [[ui.pages.tabs]] + label = "发票/收款" + type = "crud" + entity = "invoice" + enable_search = true + +[[ui.pages]] +type = "crud" +entity = "expense" +label = "支出管理" +icon = "pay-circle" +enable_search = true +``` + +--- + +## 3. 插件 2:erp-plugin-itops(IT 运维服务台) + +### 3.1 插件元数据 + +```toml +[metadata] +id = "erp-itops" +name = "IT 运维服务台" +version = "0.1.0" +description = "IT 运维工单管理 + SLA 追踪 + 定期巡检" +author = "ERP Platform" +min_platform_version = "0.1.0" +``` + +注意:itops 对 freelance 是松耦合可选依赖,不在 `metadata.dependencies` 中声明。跨插件引用通过 `ref_plugin` + `ref_fallback_label` 运行时降级。 + +### 3.2 权限声明(4 实体 × 2 = 8 个权限码) + +```toml +[[permissions]] +code = "service_contract.list" +name = "查看维保合同" +description = "查看维保合同列表和详情" + +[[permissions]] +code = "service_contract.manage" +name = "管理维保合同" +description = "创建、编辑、删除维保合同" + +[[permissions]] +code = "ticket.list" +name = "查看工单" +description = "查看工单列表和详情" + +[[permissions]] +code = "ticket.manage" +name = "管理工单" +description = "创建、编辑、删除工单" + +[[permissions]] +code = "check_plan.list" +name = "查看巡检计划" +description = "查看巡检计划列表和详情" + +[[permissions]] +code = "check_plan.manage" +name = "管理巡检计划" +description = "创建、编辑、删除巡检计划" + +[[permissions]] +code = "check_record.list" +name = "查看巡检记录" +description = "查看巡检记录列表" + +[[permissions]] +code = "check_record.manage" +name = "管理巡检记录" +description = "创建、编辑、删除巡检记录" +``` + +### 3.3 实体设计(4 个实体) + +#### 3.3.1 service_contract(维保合同) + +```toml +[[schema.entities]] +name = "service_contract" +display_name = "维保合同" + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + display_name = "客户" + ui_widget = "entity_select" + ref_plugin = "erp-freelance" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + ref_fallback_label = "外部客户" + + [[schema.entities.fields]] + name = "contract_number" + field_type = "string" + required = true + display_name = "合同编号" + unique = true + + [[schema.entities.fields]] + name = "name" + field_type = "string" + required = true + display_name = "合同名称" + searchable = true + + [[schema.entities.fields]] + name = "service_scope" + field_type = "string" + display_name = "服务范围" + ui_widget = "textarea" + + [[schema.entities.fields]] + name = "sla_level" + field_type = "string" + required = true + display_name = "SLA 等级" + ui_widget = "select" + filterable = true + default = "standard" + options = [ + { label = "标准", value = "standard" }, + { label = "银牌", value = "silver" }, + { label = "金牌", value = "gold" } + ] + + [[schema.entities.fields]] + name = "sla_response_hours" + field_type = "integer" + display_name = "SLA 响应时间(小时)" + default = 8 + + [[schema.entities.fields]] + name = "sla_resolve_hours" + field_type = "integer" + display_name = "SLA 解决时间(小时)" + default = 48 + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + filterable = true + default = "active" + options = [ + { label = "生效中", value = "active" }, + { label = "即将到期", value = "expiring" }, + { label = "已过期", value = "expired" }, + { label = "已终止", value = "terminated" } + ] + + [[schema.entities.fields]] + name = "start_date" + field_type = "date" + required = true + display_name = "开始日期" + + [[schema.entities.fields]] + name = "end_date" + field_type = "date" + required = true + display_name = "结束日期" + + [[schema.entities.fields]] + name = "amount" + field_type = "decimal" + display_name = "合同金额" + sortable = true + + [[schema.entities.fields]] + name = "payment_terms" + field_type = "string" + display_name = "付款条款" + + [[schema.entities.fields]] + name = "notes" + field_type = "string" + display_name = "备注" + ui_widget = "textarea" + + [[schema.entities.relations]] + entity = "ticket" + foreign_key = "contract_id" + on_delete = "nullify" + name = "tickets" + type = "one_to_many" + display_field = "title" + + [[schema.entities.relations]] + entity = "check_plan" + foreign_key = "contract_id" + on_delete = "cascade" + name = "check_plans" + type = "one_to_many" + display_field = "name" +``` + +#### 3.3.2 ticket(工单) + +```toml +[[schema.entities]] +name = "ticket" +display_name = "工单" + + [[schema.entities.fields]] + name = "contract_id" + field_type = "uuid" + display_name = "维保合同" + ui_widget = "entity_select" + ref_entity = "service_contract" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + display_name = "客户" + ui_widget = "entity_select" + ref_plugin = "erp-freelance" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + ref_fallback_label = "外部客户" + + [[schema.entities.fields]] + name = "title" + field_type = "string" + required = true + display_name = "工单标题" + searchable = true + + [[schema.entities.fields]] + name = "type" + field_type = "string" + required = true + display_name = "类型" + ui_widget = "select" + filterable = true + default = "fault" + options = [ + { label = "故障", value = "fault" }, + { label = "巡检", value = "check" }, + { label = "咨询", value = "consult" }, + { label = "变更", value = "change" }, + { label = "其他", value = "other" } + ] + + [[schema.entities.fields]] + name = "priority" + field_type = "string" + required = true + display_name = "优先级" + ui_widget = "select" + filterable = true + default = "medium" + options = [ + { label = "紧急", value = "urgent" }, + { label = "高", value = "high" }, + { label = "中", value = "medium" }, + { label = "低", value = "low" } + ] + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + filterable = true + default = "open" + options = [ + { label = "待处理", value = "open" }, + { label = "处理中", value = "in_progress" }, + { label = "等待客户", value = "waiting_client" }, + { label = "已解决", value = "resolved" }, + { label = "已关闭", value = "closed" } + ] + + [[schema.entities.fields]] + name = "channel" + field_type = "string" + display_name = "来源渠道" + ui_widget = "select" + options = [ + { label = "电话", value = "phone" }, + { label = "微信", value = "wechat" }, + { label = "邮件", value = "email" }, + { label = "系统", value = "system" } + ] + + [[schema.entities.fields]] + name = "description" + field_type = "string" + display_name = "问题描述" + ui_widget = "textarea" + + [[schema.entities.fields]] + name = "resolution" + field_type = "string" + display_name = "解决方案" + ui_widget = "textarea" + + [[schema.entities.fields]] + name = "responded_at" + field_type = "date_time" + display_name = "首次响应时间" + + [[schema.entities.fields]] + name = "resolved_at" + field_type = "date_time" + display_name = "解决时间" + + [[schema.entities.fields]] + name = "closed_at" + field_type = "date_time" + display_name = "关闭时间" +``` + +#### 3.3.3 check_plan(巡检计划) + +```toml +[[schema.entities]] +name = "check_plan" +display_name = "巡检计划" + + [[schema.entities.fields]] + name = "contract_id" + field_type = "uuid" + required = true + display_name = "维保合同" + ui_widget = "entity_select" + ref_entity = "service_contract" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + display_name = "客户" + ui_widget = "entity_select" + ref_plugin = "erp-freelance" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + ref_fallback_label = "外部客户" + + [[schema.entities.fields]] + name = "name" + field_type = "string" + required = true + display_name = "计划名称" + searchable = true + + [[schema.entities.fields]] + name = "frequency" + field_type = "string" + required = true + display_name = "巡检频率" + ui_widget = "select" + options = [ + { label = "每周", value = "weekly" }, + { label = "每两周", value = "biweekly" }, + { label = "每月", value = "monthly" }, + { label = "每季度", value = "quarterly" } + ] + + [[schema.entities.fields]] + name = "check_items" + field_type = "json" + display_name = "检查项" + + [[schema.entities.fields]] + name = "status" + field_type = "string" + required = true + display_name = "状态" + ui_widget = "select" + default = "active" + options = [ + { label = "启用", value = "active" }, + { label = "停用", value = "inactive" } + ] + + [[schema.entities.fields]] + name = "next_check_date" + field_type = "date" + display_name = "下次巡检日期" + + [[schema.entities.fields]] + name = "notes" + field_type = "string" + display_name = "备注" + ui_widget = "textarea" + + [[schema.entities.relations]] + entity = "check_record" + foreign_key = "plan_id" + on_delete = "cascade" + name = "records" + type = "one_to_many" + display_field = "check_date" +``` + +#### 3.3.4 check_record(巡检记录) + +```toml +[[schema.entities]] +name = "check_record" +display_name = "巡检记录" + + [[schema.entities.fields]] + name = "plan_id" + field_type = "uuid" + required = true + display_name = "巡检计划" + ui_widget = "entity_select" + ref_entity = "check_plan" + ref_label_field = "name" + ref_search_fields = ["name"] + + [[schema.entities.fields]] + name = "contract_id" + field_type = "uuid" + display_name = "维保合同" + ref_entity = "service_contract" + + [[schema.entities.fields]] + name = "client_id" + field_type = "uuid" + display_name = "客户" + ui_widget = "entity_select" + ref_plugin = "erp-freelance" + ref_entity = "client" + ref_label_field = "name" + ref_search_fields = ["name"] + ref_fallback_label = "外部客户" + + [[schema.entities.fields]] + name = "check_date" + field_type = "date" + required = true + display_name = "巡检日期" + + [[schema.entities.fields]] + name = "result" + field_type = "string" + required = true + display_name = "结果" + ui_widget = "select" + filterable = true + options = [ + { label = "正常", value = "normal" }, + { label = "有异常", value = "abnormal" } + ] + + [[schema.entities.fields]] + name = "items_data" + field_type = "json" + display_name = "检查项结果" + + [[schema.entities.fields]] + name = "issues_found" + field_type = "string" + display_name = "发现的问题" + ui_widget = "textarea" + + [[schema.entities.fields]] + name = "actions_taken" + field_type = "string" + display_name = "采取措施" + ui_widget = "textarea" + + [[schema.entities.fields]] + name = "notes" + field_type = "string" + display_name = "备注" + ui_widget = "textarea" +``` + +### 3.4 编号规则 + +```toml +[[numbering]] +entity = "service_contract" +field = "contract_number" +prefix = "SC" +format = "{PREFIX}-{YEAR}-{SEQ}" +seq_length = 4 +``` + +### 3.5 页面设计(3 个页面) + +#### 页面 1:运维概览(全局工作台的一部分) + +作为全局工作台的一个区域展示: +- 活跃合同数 + 即将到期合同预警 +- 本月工单统计(按状态饼图) +- SLA 达标率(百分比 + 趋势) +- 今日待巡检列表 +- 最近 5 条工单 + +快捷操作:快速创建工单(3 字段:标题+客户+优先级) + +#### 页面 2:合同管理 + +维保合同列表(支持按状态/客户/到期日筛选)+ 合同详情 Tab 页: +- Tab 1:合同信息 +- Tab 2:关联工单 +- Tab 3:巡检计划 + 巡检记录 + +```toml +[[ui.pages]] +type = "crud" +entity = "service_contract" +label = "合同管理" +icon = "file-text" +enable_search = true + +[[ui.pages]] +type = "detail" +entity = "service_contract" +label = "合同详情" + + [[ui.pages.sections]] + type = "fields" + label = "合同信息" + fields = ["name", "client_id", "service_scope", "sla_level", "sla_response_hours", "sla_resolve_hours", "status", "start_date", "end_date", "amount", "payment_terms", "notes"] + + [[ui.pages.sections]] + type = "crud" + label = "工单" + entity = "ticket" + filter_field = "contract_id" + + [[ui.pages.sections]] + type = "crud" + label = "巡检计划" + entity = "check_plan" + filter_field = "contract_id" + + [[ui.pages.sections]] + type = "crud" + label = "巡检记录" + entity = "check_record" + filter_field = "contract_id" +``` + +#### 页面 3:工单中心 + +Tab 1:工单列表(按状态分 Tab:待处理/处理中/已解决/已关闭) +Tab 2:巡检管理(巡检计划列表 + 巡检记录列表) + +工单详情展示 SLA 倒计时(超时变红预警) + +```toml +[[ui.pages]] +type = "tabs" +label = "工单中心" +icon = "tool" + + [[ui.pages.tabs]] + label = "工单列表" + type = "crud" + entity = "ticket" + enable_search = true + + [[ui.pages.tabs]] + label = "巡检计划" + type = "crud" + entity = "check_plan" + enable_search = true + + [[ui.pages.tabs]] + label = "巡检记录" + type = "crud" + entity = "check_record" + enable_search = true +``` + +### 3.6 SLA 自动计算规则 + +| SLA 等级 | 响应时间 | 解决时间 | 创建工单时自动带入 | +|----------|---------|---------|-------------------| +| 标准 | 8 小时 | 48 小时 | sla_response_hours=8, sla_resolve_hours=48 | +| 银牌 | 4 小时 | 24 小时 | sla_response_hours=4, sla_resolve_hours=24 | +| 金牌 | 2 小时 | 8 小时 | sla_response_hours=2, sla_resolve_hours=8 | + +工单列表中实时显示 SLA 倒计时,超时变红预警。 + +--- + +## 4. 跨插件引用关系 + +``` +itops.service_contract.client_id → erp-freelance.client (ref_plugin + ref_fallback_label 降级) +itops.ticket.client_id → erp-freelance.client +itops.check_plan.client_id → erp-freelance.client +itops.check_record.client_id → erp-freelance.client +``` + +降级行为(freelance 未安装时):client_id 降级为文本输入字段,显示 ref_fallback_label "外部客户"。 + +--- + +## 5. inventory 插件扩展建议 + +在现有 inventory 插件的 product 实体增加字段: + +```toml + [[schema.entities.fields]] + name = "product_type" + field_type = "string" + display_name = "产品类型" + ui_widget = "select" + filterable = true + default = "physical" + options = [ + { label = "实物", value = "physical" }, + { label = "虚拟", value = "virtual" }, + { label = "服务", value = "service" } + ] +``` + +覆盖"计算机软硬件及辅助设备批发/零售"的经营范围。virtual/service 走零库存逻辑。 + +--- + +## 6. 全局工作台合并设计 + +freelance 和 itops 的仪表盘合并为一个全局工作台,布局如下: + +``` +┌─────────────────────────────────────────────────────┐ +│ 快捷操作栏:[计时] [记支出] [新报价] [新工单] │ +├──────────────────────┬──────────────────────────────┤ +│ 财务概览 │ 紧急待办 │ +│ 本月收入: ¥XX,XXX │ 逾期收款: 2 笔 │ +│ 本月支出: ¥X,XXX │ 今日到期任务: 3 项 │ +│ 利润: ¥XX,XXX │ 即将到期合同: 1 份(7天内) │ +│ 应收: ¥XX,XXX │ 待处理工单: 4 条 │ +├──────────────────────┼──────────────────────────────┤ +│ 活跃项目 (4) │ 运维概览 │ +│ [项目A ████░░ 60%] │ 活跃合同: 5 份 │ +│ [项目B ██░░░░ 30%] │ SLA 达标率: 96% │ +│ [项目C ██████ 100%] │ 今日待巡检: 2 项 │ +│ │ 最新工单: ... │ +├──────────────────────┴──────────────────────────────┤ +│ 商机跟进提醒 │ +│ 张三(XX公司) - 网站开发 - 明天需跟进 │ +│ 李四(YY公司) - AI方案 - 后天到期 │ +└─────────────────────────────────────────────────────┘ +``` + +--- + +## 7. 实施优先级 + +``` +P1: freelance 插件核心实体(client, opportunity, project, task, time_entry) +P2: freelance 插件财务实体(quote, quote_line, contract, invoice, expense) +P3: freelance 页面(全局工作台 + 客户管理 + 项目工作台 + 财务中心) +P4: itops 插件(全部实体 + 页面) +P5: inventory 扩展(product_type 字段) +P6: 全局工作台合并 + 快捷操作 +``` + +--- + +## 8. 验证矩阵 + +| 验证场景 | 预期结果 | +|---------|---------| +| 单独安装 freelance | 全部功能正常,客户字段内部关联 | +| 单独安装 itops(不装 freelance) | client_id 降级为文本输入 | +| 同时安装 freelance + itops | client_id 自动关联 freelance.client,下拉选择客户 | +| 创建商机 → 创建报价 → 转合同 → 创建项目 | 全链路数据流通 | +| 项目中记录工时 → 生成发票 | 工时汇总可关联到发票 | +| 创建维保合同 → 设定巡检计划 → 执行巡检 → 创建工单 | 运维全流程 | +| SLA 超时预警 | 工单创建后超时自动标红 | +| 合同到期提醒 | 全局工作台显示即将到期合同 | +| 报价单号/合同编号/发票号自动生成 | QT-2026-0001 / CT-2026-0001 / INV-2026-0001 | + +--- + +## 9. 讨论溯源 + +本文档基于 2026-04-19 的无主题发散式互动探讨产出,经过多专家头脑风暴审查(UX 专家、技术架构专家、业务顾问),采纳了以下关键建议: + +- 新增 contract 实体(B2B 必要环节) +- 页面从 10 个压缩到 7 个(合并仪表盘+整合 tab) +- project 增加 business_type/deliverable_type 枚举覆盖全部经营范围 +- 全局工作台 + 快捷操作(瑞士军刀原则) +- itops client_id 使用 ref_plugin + ref_fallback_label 实现可选跨插件引用 +- inventory 扩展 product_type 支持软硬件销售 + +Spec 审查修正记录: +- C1: options 改为 `{ label, value }` 对象数组 +- C2: `default_value` 改为 `default` +- C3: `generated = true` 改为 `[[numbering]]` 段 +- C4: 跨插件引用改用 `ref_plugin` + `ref_label_field` +- C5: `datetime` 改为 `date_time` +- C6: `text` 改为 `string` + `ui_widget = "textarea"` +- I2: `number` 改为 `decimal` / `integer` +- I5: uuid 字段移除 `filterable`,改用 `ui_widget = "entity_select"` diff --git a/plans/calm-forging-puddle.md b/plans/calm-forging-puddle.md new file mode 100644 index 0000000..0840b2f --- /dev/null +++ b/plans/calm-forging-puddle.md @@ -0,0 +1,172 @@ +# P1-P4 审计修复实施计划 + +## Context + +对 P1-P4 审计发现 8 项高/中优先级缺失:Excel/CSV 导入导出、市场后端 API、对账扫描、运行时监控、通知规则、编号 reset_rule。本计划按优先级分 3 批推进,每批独立可提交。 + +--- + +## 第一批:高优先级(Excel/CSV + 市场后端 + 对账扫描) + +### 1.1 Excel/CSV 导入导出 + +**思路**: 后端新增 `csv` + `rust_xlsxwriter` 依赖,export handler 支持 format 参数输出 CSV/XLSX;前端同时支持。 + +**后端改动**: + +1. `Cargo.toml` (workspace): 新增 `csv = "1"` 和 `rust_xlsxwriter = "0.82"` +2. `crates/erp-plugin/Cargo.toml`: 添加 `csv` 和 `rust_xlsxwriter` 依赖 +3. `crates/erp-plugin/src/data_service.rs`: + - `export()` 签名增加 `format: Option` 参数 + - 内部新增 `export_csv()` 和 `export_xlsx()` 私有方法,返回 `Vec` bytes + - format 为空/json 时返回原 JSON;csv/xlsx 时返回二进制 + - 返回类型改为 enum `ExportPayload { Json(Vec), Csv(Vec), Xlsx(Vec) }` +4. `crates/erp-plugin/src/data_dto.rs`: ExportParams 的 format 字段已有 +5. `crates/erp-plugin/src/handler/data_handler.rs`: + - `export_plugin_data` 根据 format 参数返回不同 Content-Type: + - JSON: `application/json` + - CSV: `text/csv` + `Content-Disposition: attachment` + - XLSX: `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` + - 返回类型改为 `axum::response::Response`(不是 Json<>) +6. 前端 `pluginData.ts`: `exportPluginData` 支持 format 参数,CSV/XLSX 时用 `responseType: 'blob'` +7. 前端 `PluginCRUDPage.tsx`: 导出按钮增加下拉菜单选择格式(JSON/CSV/Excel) + +**注意**: 导入仍保持 JSON(复杂度低),模板生成和导入历史不在本批范围。 + +### 1.2 P4 市场后端 API + +**思路**: 新建 `market_service.rs` + `market_handler.rs`,复用 DB 迁移已建好的 `plugin_market_entries` 和 `plugin_market_reviews` 表。 + +**新增文件**: +- `crates/erp-plugin/src/service/market_service.rs`: 市场业务逻辑 +- `crates/erp-plugin/src/handler/market_handler.rs`: 市场 API handler +- `crates/erp-plugin/src/entity/market_entry.rs`: SeaORM Entity +- `crates/erp-plugin/src/entity/market_review.rs`: SeaORM Entity + +**后端 API**: +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/v1/market/entries` | 浏览市场目录(分类/搜索/分页) | +| GET | `/api/v1/market/entries/{id}` | 市场条目详情 | +| POST | `/api/v1/market/entries/{id}/install` | 从市场一键安装 | +| GET | `/api/v1/market/entries/{id}/reviews` | 查看评论 | +| POST | `/api/v1/market/entries/{id}/reviews` | 提交评分/评论 | + +**一键安装逻辑**: 从 `plugin_market_entries` 取 `wasm_binary` + `manifest_toml`,调用已有的 `PluginService::upload` + `PluginService::install` + `PluginService::enable`。 + +**依赖提示**: install 时检查 manifest.dependencies,若目标插件未安装则返回警告(软提示,不阻塞)。 + +**前端改动**: +- `apps/web/src/api/plugins.ts`: 新增市场 API 函数 +- `apps/web/src/pages/PluginMarket.tsx`: 对接真实 API,替换 mock 数据;增加评分提交 UI;安装按钮对接真实 API + +### 1.3 P1 对账扫描 + +**思路**: 新增 `reconcile` service 方法和 handler,在插件重新启用时扫描悬空引用。 + +**后端改动**: +- `crates/erp-plugin/src/data_service.rs`: 新增 `reconcile_references()` 方法 + - 查找所有指向目标插件的 `ref_entity` 字段(从 plugin_entities schema_json 解析) + - 扫描这些字段的 UUID 值,验证目标表中是否存在 + - 返回 `ReconciliationReport { valid: N, dangling: M, details: Vec }` +- `crates/erp-plugin/src/data_dto.rs`: 新增 DTO +- `crates/erp-plugin/src/handler/data_handler.rs`: 新增 `reconcile_refs` handler +- `crates/erp-plugin/src/module.rs`: 注册路由 `POST /plugins/{plugin_id}/reconcile` + +**前端**: 暂不实现完整对账 UI(低优先级),仅提供 API 供后续使用。 + +--- + +## 第二批:中优先级(运行时监控 + 通知规则 + 编号 reset) + +### 2.1 P3 运行时监控 + +**后端改动**: +1. 新建迁移 `m20260420_000041_plugin_runtime_metrics.rs`: + - `plugin_runtime_metrics` 表: plugin_id, tenant_id, error_count, total_invocations, avg_response_ms, fuel_consumption_avg, memory_peak_bytes, last_error, updated_at +2. `crates/erp-plugin/src/engine.rs`: + - `LoadedPlugin` 新增 `metrics: Arc>` 字段 + - `execute_wasm` 中采集指标: 记录开始时间、成功/失败计数、fuel 消耗 + - 定期持久化到 DB(每 10 次调用或 60 秒) +3. `crates/erp-plugin/src/handler/plugin_handler.rs`: + - 扩展 `health_check` 返回 RuntimeMetrics + - 新增 `GET /admin/plugins/{id}/metrics` 端点 + +### 2.2 P2 通知规则引擎 + +**思路**: 复用 EventBus 的 `subscribe_filtered` + erp-message 的 `send_system`,在 plugin 模块启动时监听 `plugin.trigger.*` 前缀事件。 + +**后端改动**: +- `crates/erp-plugin/src/module.rs`: 启动事件监听(参考 erp-message 的 `start_event_listener` 模式) +- 新建 `crates/erp-plugin/src/notification.rs`: + - 订阅 `plugin.trigger.*` 事件 + - 查询 trigger_events 声明,匹配事件名 + - 调用 erp-message 的系统消息发送(通过 EventBus 发布 `message.send` 事件,或直接调用 message service 的 REST API) + - 通知对象: 通过 manifest 声明扩展(当前简化为通知所有管理员) + +### 2.3 P2 编号 reset_rule + +**思路**: 参考 erp-config 的 `numbering_service.rs` 的 `maybe_reset_sequence` 模式,替换 PostgreSQL 序列为表行 + advisory lock。 + +**后端改动**: +- `crates/erp-plugin/src/host.rs`: 重写 `numbering_generate` + - 改用 `pg_advisory_xact_lock` + 表行序列(而非 PostgreSQL SEQUENCE) + - 在事务内: 读序列行 → 检查 reset_rule 是否需要重置 → 递增/重置 → 写回 + - 序列表: 使用已有的动态表模式,或新建 `plugin_numbering_sequences` 表 +- `crates/erp-plugin/src/engine.rs`: `NumberingRule` 中 reset_rule 字段已被传递但未使用,直接在 host.rs 中消费 + +--- + +## 第三批:低优先级(配置变更通知 + 自定义视图) + +### 3.1 P2 配置变更通知 + +**后端改动**: +- `crates/erp-plugin/src/service.rs`: `update_config` 增加 `event_bus: &EventBus` 参数,更新成功后发布 `plugin.config.updated` 事件 +- `crates/erp-plugin/src/handler/plugin_handler.rs`: `update_plugin_config` handler 从 state 获取 event_bus 传入 +- `crates/erp-plugin/src/engine.rs`: 订阅 `plugin.config.updated` 事件,刷新内存中的 `plugin_config` + +### 3.2 P2 自定义视图 + +**后端改动**: +1. 新建迁移 `plugin_user_views` 表 +2. 新建 `crates/erp-plugin/src/service/view_service.rs`: CRUD user views +3. 新建 handler: `GET/POST/PUT/DELETE /plugins/{plugin_id}/{entity}/views` +4. **前端**: PluginCRUDPage 增加视图保存/加载 UI + +--- + +## 关键文件清单 + +| 文件 | 改动类型 | +|------|---------| +| `Cargo.toml` (workspace) | 新增 csv, rust_xlsxwriter 依赖 | +| `crates/erp-plugin/Cargo.toml` | 新增依赖 | +| `crates/erp-plugin/src/data_service.rs` | export format 支持, reconcile 方法 | +| `crates/erp-plugin/src/data_dto.rs` | ExportPayload enum, ReconciliationReport | +| `crates/erp-plugin/src/handler/data_handler.rs` | export 返回 Response, reconcile handler | +| `crates/erp-plugin/src/handler/market_handler.rs` | **新建** 市场 API | +| `crates/erp-plugin/src/service/market_service.rs` | **新建** 市场业务逻辑 | +| `crates/erp-plugin/src/entity/market_entry.rs` | **新建** SeaORM Entity | +| `crates/erp-plugin/src/entity/market_review.rs` | **新建** SeaORM Entity | +| `crates/erp-plugin/src/notification.rs` | **新建** 通知规则引擎 | +| `crates/erp-plugin/src/engine.rs` | LoadedPlugin 增加 metrics, 配置热更新 | +| `crates/erp-plugin/src/host.rs` | numbering_generate 重写 | +| `crates/erp-plugin/src/service.rs` | update_config 增加 event_bus | +| `crates/erp-plugin/src/module.rs` | 注册新路由, 启动通知监听 | +| `crates/erp-plugin/src/lib.rs` | 导出新模块 | +| `crates/erp-server/migration/src/m20260420_*.rs` | **新建** metrics 表迁移 | +| `apps/web/src/api/plugins.ts` | 市场前端 API | +| `apps/web/src/api/pluginData.ts` | export format 支持 | +| `apps/web/src/pages/PluginMarket.tsx` | 对接真实 API | +| `apps/web/src/pages/PluginCRUDPage.tsx` | 导出格式选择 | + +## 验证计划 + +1. `cargo check` — 全 workspace 编译通过 +2. `pnpm build` — 前端构建通过 +3. 启动后端 + 前端,浏览器中验证: + - CRM customer 导出 CSV/Excel 下载 + - 市场 API 返回数据(curl 测试) + - 插件 health 接口返回 metrics +4. 每批完成后独立提交推送 diff --git a/plans/skill-parallel-pixel-agent-a2852b5abd5e15119.md b/plans/skill-parallel-pixel-agent-a2852b5abd5e15119.md new file mode 100644 index 0000000..92181d1 --- /dev/null +++ b/plans/skill-parallel-pixel-agent-a2852b5abd5e15119.md @@ -0,0 +1,138 @@ +# UX 分析报告:一人 IT 公司 ERP 插件方案 + +> 基于智界科技(一人 IT 服务公司)的业务场景,对 freelance + itops 两个插件的 UX 审查。 + +--- + +## 1. 一人公司的 UX 痛点 + +**WHY**: 一个人没有分工,老板就是销售、项目经理、财务、运维工程师。每次切换页面等于中断心流,表单越复杂越容易填一半放弃。 + +**核心摩擦**: + +- **上下文切换成本高** -- 一个人同时处理客户咨询、写代码、记账、回工单。在"客户详情"和"工时记录"之间来回跳转,每次跳转丢失工作记忆。 +- **重复录入** -- 同一个客户信息在 client、opportunity、quote、invoice、ticket 中反复手填。一人公司没有人帮忙补数据。 +- **决策疲劳** -- 每天面对 10 个入口,要思考"这个操作该去哪个页面"。对于一人公司,ERP 应该像手机首页一样直觉。 +- **过度结构化** -- 一人公司的商机通常是微信聊几句就定了,不需要复杂的销售漏斗流程。 + +**HOW -- 减少操作的具体措施**: + +1. **全局搜索 + 命令面板**(Ctrl+K):输入"张三"直接跳到客户详情,输入"新工时"直接弹出计时器,输入"#102"跳到工单。一人公司的 ERP 应该像一个大的搜索框 + 几个快捷按钮。 +2. **自动填充上下文**:在项目工作台记工时时,自动关联当前活跃项目;从客户详情页创建报价单时,自动带入客户信息。减少手动关联操作。 +3. **合并创建流程**:新建项目时一步内同时创建第一个任务,不用先建项目再跳到任务页。 + +--- + +## 2. 页面布局合理性 -- 10 个页面是否太多 + +**结论:可以压缩到 7 个页面,但不应低于 5 个。** + +**WHY**: 一人公司的操作场景有明确的节奏切换(见客户 vs 做项目 vs 记账),完全合并会导致单页信息过载。但两个插件共 10 个页面确实有冗余。 + +**建议合并方案**: + +| 原方案 (10 页) | 优化方案 (7 页) | 理由 | +|---|---|---| +| freelance 仪表盘 | **全局工作台**(合并两个仪表盘) | 一人只需一个首页 | +| 客户管理 (360度) | 客户管理 (保留) | 核心入口,高频使用 | +| 商机跟进 (看板) | **并入客户管理**,作为客户详情的一个 tab | 一人公司的商机极少同时超过 5 个,看板过重 | +| 项目工作台 | 项目工作台 (保留) | 核心工作场景,需要独立空间 | +| 财务中心 | 财务中心 (保留) | 收支是独立节奏,需要集中视图 | +| 报价管理 | **并入财务中心**,作为 tab | 报价是财务流程的前置步骤,不放独立页面 | +| itops 运维仪表盘 | (已合并到全局工作台) | -- | +| 合同管理 | 合同管理 (保留) | 维保合同是独立业务实体 | +| 工单中心 | 工单中心 (保留) | 最高频运维操作 | +| 巡检管理 | **并入工单中心**,作为 tab 或筛选 | 巡检本质是周期性工单,不需要独立页面 | + +**HOW -- 实现层面**: +- freelance 插件减少为 4 个页面:全局工作台(dashboard)、客户管理(tabs 类型,含商机看板 tab)、项目工作台、财务中心(tabs 类型,含报价 tab) +- itops 插件减少为 3 个页面:工单中心(tabs 类型,含巡检 tab)、合同管理、(全局工作台跨插件共享) +- 跨插件共享的 dashboard 通过 ui.pages 的 `shared: true` 或放在 freelance 插件中声明,itops 通过 `dependencies = ["erp-freelance"]` 引用 + +--- + +## 3. 关键缺失场景 + +**WHY**: 一人 IT 公司有 3 个高频场景在当前方案中完全缺失,不做这些等于 ERP 只覆盖了 60% 的日常工作。 + +| 缺失场景 | 严重性 | 说明 | +|---|---|---| +| **合同/报价到期提醒** | 高 | 维保合同到期前 30 天没有提醒 = 流失续费收入。一人公司靠记忆管理,ERP 必须补上 | +| **工时 -> 开票 自动联动** | 高 | 项目完成后手动从工时记录汇总金额再创建发票,这个手工过程在一人公司中最容易被跳过,导致漏收 | +| **知识库/文档管理** | 中 | IT 运维的核心资产是文档(网络拓扑、服务器配置、密码记录)。当前方案只有结构化数据,缺非结构化知识 | +| **续约提醒 + 自动创建续约商机** | 中 | 维保合同到期时自动生成一个续约 opportunity,串联 freelance 和 itops | + +**HOW -- 实现建议**: + +1. **到期提醒**:在 itops 插件的 service_contract 实体上加 `end_date` 字段(已有),在后端增加定时事件检查 `contract.expiring`,通过消息中心的订阅机制推送到通知面板。 +2. **工时 -> 开票联动**:在 invoice 实体增加 `source_type = "time_entry"` 和 `source_ids` 字段,前端提供"从工时记录生成发票"的一键操作,按项目汇总自动填充。 +3. **知识库**:Phase 2 考虑。可以在 client 或 project 实体上加 `attachments` (json) 字段存储文件引用,先做轻量版。 + +--- + +## 4. 仪表盘设计建议 -- 合并为一个全局工作台 + +**WHY**: 一人只有一个视角(老板视角),不存在"销售看销售数据、运维看运维数据"的角色分离。两个仪表盘让用户每次登录还要选择看哪个,增加了无意义的决策。 + +**HOW -- 全局工作台设计**: + +``` ++------------------------------------------------------------------+ +| 全局工作台 | ++------------------------------------------------------------------+ +| 今日待办 (3) 本周收入: ¥12,500 | +| [ ] 回复张三报价 (2h前) 待开票: ¥8,200 | +| [ ] 完成服务器巡检 (今天) 本月支出: ¥3,400 | +| [ ] 提交项目A发票 (明天截止) 到期合同: 2个 (30天内) | ++------------------------------------------------------------------+ +| 活跃项目 (2) 最新工单 (3) | +| 项目A - 进行中 ██████░░ 75% #102 网络... 进行中 | +| 项目B - 待启动 ░░░░░░░░ 0% #101 备份... 已完成 | +| #100 升级... 待处理 | ++------------------------------------------------------------------+ +| [快速操作] +新建客户 +新建工单 +开始计时 +新建报价 | ++------------------------------------------------------------------+ +``` + +**布局规则**: +- 上方:紧急事项 + 财务概览(左右分栏) +- 中间:核心业务对象快照(活跃项目 + 最新工单) +- 下方:一键操作按钮条 + +**实现**:使用现有的 `PluginDashboardPage` 组件,通过 plugin.toml 的 `ui.pages` 中 type = "dashboard" 声明,dashboard widgets 跨插件聚合数据。freelance 插件声明这个 dashboard,itops 插件通过 `dependencies` 引用后注册自己的 widgets。 + +--- + +## 5. 快速操作 -- 一键完成的快捷入口 + +**WHY**: 一人公司最高频的操作是"快速记一笔"和"快速创建"。如果每次都要打开表单、填完所有字段、点击保存,摩擦太大导致用户放弃使用 ERP,回到微信记事本。 + +| 快速操作 | 频率 | HOW | +|---|---|---| +| **开始/停止计时** | 每天 3-5 次 | 全局悬浮按钮,点击选择项目 -> 开始计时,再点停止自动生成 time_entry。不需要打开任何页面 | +| **快速记工单** | 每天 2-3 次 | 工单中心的 "+新建" 按钮,弹出一个精简表单(只填标题+客户+紧急度),详情后续补充 | +| **快速记支出** | 每周 2-3 次 | 财务中心的"+记一笔"按钮,3 个字段:金额、分类、备注。日期默认今天 | +| **快速创建报价** | 每周 1-2 次 | 从客户详情页一键"生成报价",自动带入客户信息 + 最近的项目工时数据 | +| **快速创建工单 from 合同** | 每月 1-2 次 | 合同详情页"创建工单"按钮,自动关联合同+客户 | + +**实现要点**: +- 全局悬浮计时器通过前端组件实现,不依赖特定插件页面,放在 MainLayout 层 +- 快速操作按钮放在各页面的 PageHeader 区域,使用 Ant Design 的 `FloatButton` 或 `Button` 组件 +- 精简表单 = 只标记 `required = true` 的字段,其他字段全部可选,后续可补充 + +--- + +## 总结 -- 核心建议优先级 + +| 优先级 | 建议 | 预期收益 | +|---|---|---| +| P0 | 合并两个仪表盘为全局工作台 | 消除首次登录的困惑 | +| P0 | 全局悬浮计时器(开始/停止) | 工时记录从"每周补"变成"实时记" | +| P1 | 商机看板并入客户管理 tab | 减少 1 个页面,降低认知负担 | +| P1 | 工时 -> 发票一键生成 | 消除最大手工流程,防漏收 | +| P1 | 合同到期提醒 | 防止续费流失 | +| P2 | 报价并入财务中心 tab | 减少 1 个页面 | +| P2 | 巡检并入工单中心 tab | 减少 1 个页面 | +| P2 | 全局搜索命令面板 (Ctrl+K) | 极大提升操作效率 | + +**核心原则**:一人公司的 ERP 应该像瑞士军刀,不是像工具箱。不需要 10 个抽屉分门别类,需要一把刀随时打开就能用。 diff --git a/plans/skill-parallel-pixel-agent-a8e98f2c813be4f33.md b/plans/skill-parallel-pixel-agent-a8e98f2c813be4f33.md new file mode 100644 index 0000000..aec680d --- /dev/null +++ b/plans/skill-parallel-pixel-agent-a8e98f2c813be4f33.md @@ -0,0 +1,50 @@ +# Freelance + IT-OPS 插件技术评审 + +## 1. 实体数量合理性 + +**freelance 8 实体不过重。** 现有插件代码证实:WASM Guest 实现极其轻量(CRM 仅 30 行 Rust,只实现 Guest trait 的 3 个空方法),所有业务逻辑由 Host 侧的 `PluginDataService` + `DynamicTableManager` 通用处理。WASM 二进制不含 ORM/业务逻辑,因此 8 实体与 5 实体的 Guest 代码几乎无差别。manifest 解析(`manifest.rs`)和建表 DDL 均按 entity 循环处理,无硬上限。 + +真正的复杂度在前端页面数量和表间关联(quote/quote_line 父子关系),需确保 plugin.toml 的 `relations` 声明完整。 + +## 2. 跨插件引用性能 + +itops 4 个实体都引用 `freelance.client` 是合理的。代码显示跨插件引用走的是**同一数据库内 SQL 查询**(`resolve_cross_plugin_entity` 解析出 `plugin_erp-freelance_client` 表名后直接 JOIN/EXISTS),**没有 RPC 调用或跨服务开销**。列表查询时 `resolve_labels` 会批量解析 UUID→label,也是单次 IN 查询。 + +风险点:4 个实体同时查询时各做一次跨插件表 JOIN,并发高时需关注连接池。但每个查询都是标准 SQL,PostgreSQL 处理无压力。**建议 itops 的 client_id 字段设 `filterable = true` 使其走 generated column 索引。** + +## 3. select 枚举字段声明 + +现有代码已完全支持。CRM plugin.toml 中有大量实例: + +```toml +[[schema.entities.fields]] +name = "status" +field_type = "string" +ui_widget = "select" +filterable = true +options = [ + { label = "草稿", value = "draft" }, + { label = "已审核", value = "approved" }, +] +``` + +字段类型声明为 `string`,枚举值通过 `options` 数组提供(`label` + `value`)。解析侧 `PluginField.options: Option>` 兼容此格式。`filterable = true` 会自动创建 generated column + 索引以加速过滤查询。 + +## 4. WASM 体积预估 + +实测数据: +- CRM (5 实体): **22 KB** (raw), **22.8 KB** (component) +- Inventory (6 实体): **22 KB** (raw) +- test-sample (含 Host API 回调测试): **109 KB** + +freelance (8 实体) Guest 代码同样只有 Guest trait 空实现,预估 **22-23 KB**。体积与实体数量无关,取决于引入的 Host API 回调复杂度。itops 更小(4 实体),预估 22 KB。两者合计约 45 KB,对运行时内存和加载速度无影响。 + +## 5. 技术风险 + +1. **quote/quote_line 父子关系**:quote_line 引用 quote 是同插件内引用,需在 plugin.toml 中声明 `ref_entity = "quote"` + `relations` 的 `on_delete = "cascade"`。父实体删除时需级联软删除子记录 -- 当前 `validate_ref_entities` 只做引用存在性校验,级联软删除需确认 `DynamicTableManager` 是否支持(需检查 `on_delete: cascade` 在 list/create 流程中的实现)。 + +2. **itops 依赖声明**:`metadata.dependencies = ["erp-plugin-freelance"]`,但 `ref_plugin` 字段应填 manifest ID(即 `"erp-plugin-freelance"`)。需确认 manifest ID 与 Cargo crate name 的命名映射一致。 + +3. **freelance.client 需标记 `is_public = true`**:否则 itops 的跨插件 `ref_plugin` 查询会找不到目标实体。CRM 的 customer 已正确标记。 + +4. **权限码数量**:freelance 16 个权限码、itops 8 个,均在合理范围。注意每个实体必须声明 `.list` + `.manage`,缺 `.list` 会导致列表页 403。 diff --git a/plans/skill-parallel-pixel-agent-af894fc9048c54be3.md b/plans/skill-parallel-pixel-agent-af894fc9048c54be3.md new file mode 100644 index 0000000..414c5f4 --- /dev/null +++ b/plans/skill-parallel-pixel-agent-af894fc9048c54be3.md @@ -0,0 +1,74 @@ +# 智界科技 ERP 插件方案 -- 业务顾问分析 + +## 分析结论 + +### 1. 经营范围覆盖度 + +| 经营范围 | 覆盖插件 | 覆盖情况 | +|----------|---------|---------| +| 软件开发 | freelance(project/task) | 部分 -- 缺合同签约流程 | +| AI 开发 | 无 | 未覆盖 | +| 系统集成 | freelance(project) | 部分 | +| 软件销售(批发+零售) | 无 | 未覆盖 | +| IT 运维服务 | itops(service_contract/ticket/check_plan/check_record) | 覆盖良好 | +| 软件外包 | freelance(project/task/time_entry) | 部分 | +| IT 咨询 | freelance(opportunity/quote) | 部分 -- 缺知识产品化 | +| 数字内容制作 | 无 | 未覆盖 | +| 市场营销策划 | 无 | 未覆盖 | + +**覆盖 5/9,遗漏 4 条。** + +### 2. 最赚钱业务优先级 + +汕头市场实际排序: +1. **软件开发 + AI 开发**(利润率 70-90%,一人公司最佳赛道) +2. **IT 运维服务**(稳定年费收入,itops 已覆盖) +3. **系统集成**(客单价高,freelance 的 project 可部分支撑) +4. **软件销售批发零售**(需配合 inventory 插件) +5. **IT 咨询**(高毛利但低频) + +插件设计基本正确地优先了 1-3,但 freelance 插件缺少对"产品化销售"的支持。 + +### 3. 市场营销策划 -- 需要补充吗? + +**不需要独立插件。** 原因:一人公司做营销策划,本质是卖自己的专业能力,核心需求是: +- 客户管理(freelance.client 已覆盖) +- 报价(freelance.quote 已覆盖) +- 项目交付(freelance.project 已覆盖) + +在 freelance 的 project 实体中增加 `type` 字段(枚举:software/ai/integration/consulting/marketing/content),即可区分不同业务线,无需新增插件。 + +### 4. 软硬件批发零售 -- inventory 需要配合吗? + +**需要,但方式不同。** 软硬件批发零售有两种场景: +- **代理分销**(从供应商进货再卖)-- 需要 inventory 插件管库存 + freelance 的 invoice 开票 +- **纯中介/推荐**(帮客户选型,供应商直发)-- 只需 freelance 的 quote + invoice,库存量写 0 或标记"虚拟商品" + +建议:inventory 插件中增加 `product.type`(enum: physical/virtual/service),virtual 类型走零库存逻辑,physical 走完整进销存。freelance 的 invoice 关联 inventory 的 product 即可。 + +### 5. 数字内容制作 -- 需要什么? + +**不需要独立插件。** 数字内容制作(网站、小程序、视频、设计稿等)本质是项目制交付,与软件开发共用同一套 project/task/time_entry 流程。在 freelance 的 project 增加 `deliverable_type`(enum: software/website/miniprogram/video/design/document)即可。 + +--- + +## 调整建议(300 字版) + +**freelance 插件调整:** + +1. **project 实体增加字段:** + - `business_type`(enum: software_development/ai_development/system_integration/software_sales/it_outsourcing/it_consulting/marketing_planning/digital_content)-- 对齐 9 条经营范围 + - `deliverable_type`(enum: software/website/miniprogram/video/design/document/consulting_report) + +2. **client 实体增加字段:** + - `source`(enum: referral/marketing/tender/platform/repeat)-- 追踪客户来源,为营销策划提供数据 + +3. **新增 contract 实体:** 独立于 quote,合同签约、履约跟踪是法律实体,目前只有报价没有合同,这是 B2B 业务的核心缺失。字段:title/client_id/quote_id/amount/start_date/end_date/status/terms + +4. **invoice 关联 product:** 增加 `line_items`(JSON 数组),每行关联 inventory 的 product_id + quantity + unit_price,打通软硬件销售闭环。 + +**itops 插件:保持不变,设计合理。** + +**inventory 插件:** 增加 `product.type`(physical/virtual/service),virtual/service 走零库存逻辑。 + +**不新增独立插件。** 9 条经营范围通过 freelance 的分类字段 + inventory 配合即可全覆盖。一人公司最忌讳系统复杂度过高,三个插件(freelance + itops + inventory)足够。 diff --git a/plans/skill-parallel-pixel.md b/plans/skill-parallel-pixel.md new file mode 100644 index 0000000..da944e8 --- /dev/null +++ b/plans/skill-parallel-pixel.md @@ -0,0 +1,1301 @@ +# 汕头市智界科技有限公司 — 行业插件设计规格 + +> 日期: 2026-04-19 +> 来源: 无主题发散式互动探讨(多专家头脑风暴) +> 状态: Draft + +--- + +## 1. 背景与动机 + +以汕头市智界科技有限公司(一人 IT 服务公司)为案例,设计针对 IT 服务行业的 ERP 插件组合,覆盖其全部 9 条经营范围。 + +**公司经营范围:** +1. 软件开发 +2. 数字文化创意软件开发 +3. 人工智能基础/应用软件开发 +4. 信息系统集成服务 +5. 网络技术服务 +6. 软件销售 +7. 软件外包服务 +8. 计算机软硬件及辅助设备批发/零售 +9. 信息技术咨询服务 +10. 信息系统运行维护服务 +11. 数字内容制作服务 +12. 市场营销策划 + +**设计原则:** +- 一人公司优先:所有功能对单人操作友好,减少上下文切换 +- 最少插件覆盖最多场景:3 个插件覆盖全部经营范围 +- 快捷操作优先:高频操作一键完成,不打开完整表单 +- 全局工作台:一个仪表盘掌握全局 + +**插件组合与经营范围对照:** + +| 插件 | 覆盖的经营范围 | +|------|--------------| +| freelance(自由职业者工作台) | 软件开发、AI开发、系统集成、外包、IT咨询、数字内容、营销策划 | +| itops(IT 运维服务台) | IT 运维服务、网络技术服务 | +| inventory(已有,需扩展) | 软硬件批发零售 | + +--- + +## 2. 插件 1:erp-plugin-freelance(自由职业者工作台) + +### 2.1 插件元数据 + +```toml +[metadata] +id = "erp-freelance" +name = "自由职业者工作台" +version = "0.1.0" +description = "一人 IT 服务公司的全链路业务管理:客户→商机→报价→合同→项目→工时→开票→收支" +author = "ERP Platform" +``` + +### 2.2 实体设计(9 个实体) + +#### 2.2.1 client(客户) + +```toml +[[schema.entities]] +name = "client" +display_name = "客户" + +[[schema.entities.fields]] +name = "name" +field_type = "string" +display_name = "客户名称" +required = true + +[[schema.entities.fields]] +name = "contact_name" +field_type = "string" +display_name = "联系人" + +[[schema.entities.fields]] +name = "phone" +field_type = "string" +display_name = "电话" + +[[schema.entities.fields]] +name = "email" +field_type = "string" +display_name = "邮箱" + +[[schema.entities.fields]] +name = "industry" +field_type = "string" +display_name = "行业" +ui_widget = "select" +options = ["制造业", "零售", "教育", "医疗", "政府", "金融", "其他"] + +[[schema.entities.fields]] +name = "source" +field_type = "string" +display_name = "来源" +ui_widget = "select" +options = ["转介绍", "线上", "展会", "老客户", "主动开发"] + +[[schema.entities.fields]] +name = "level" +field_type = "string" +display_name = "重要等级" +ui_widget = "select" +options = ["A", "B", "C"] + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["潜在", "活跃", "休眠", "流失"] +default_value = "潜在" + +[[schema.entities.fields]] +name = "address" +field_type = "string" +display_name = "地址" + +[[schema.entities.fields]] +name = "notes" +field_type = "text" +display_name = "备注" +``` + +#### 2.2.2 opportunity(商机) + +```toml +[[schema.entities]] +name = "opportunity" +display_name = "商机" + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +required = true + +[[schema.entities.fields]] +name = "title" +field_type = "string" +display_name = "商机名称" +required = true + +[[schema.entities.fields]] +name = "business_type" +field_type = "string" +display_name = "业务类型" +ui_widget = "select" +options = ["software_development", "ai_development", "system_integration", "software_outsourcing", "it_consulting", "digital_content", "marketing_planning"] +required = true + +[[schema.entities.fields]] +name = "stage" +field_type = "string" +display_name = "阶段" +ui_widget = "select" +options = ["初访", "需求确认", "报价", "谈判", "成交", "失败"] +default_value = "初访" + +[[schema.entities.fields]] +name = "estimated_amount" +field_type = "number" +display_name = "预估金额" + +[[schema.entities.fields]] +name = "probability" +field_type = "number" +display_name = "成交概率(%)" + +[[schema.entities.fields]] +name = "expected_close_date" +field_type = "date" +display_name = "预计成交日期" + +[[schema.entities.fields]] +name = "next_follow_up" +field_type = "date" +display_name = "下次跟进日期" + +[[schema.entities.fields]] +name = "description" +field_type = "text" +display_name = "需求描述" +``` + +#### 2.2.3 quote(报价单) + +```toml +[[schema.entities]] +name = "quote" +display_name = "报价单" + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +required = true + +[[schema.entities.fields]] +name = "opportunity_id" +field_type = "uuid" +display_name = "关联商机" +ref_entity = "opportunity" + +[[schema.entities.fields]] +name = "quote_number" +field_type = "string" +display_name = "报价单号" +generated = true + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["草稿", "已发送", "已接受", "已拒绝", "已过期"] +default_value = "草稿" + +[[schema.entities.fields]] +name = "valid_until" +field_type = "date" +display_name = "有效期至" + +[[schema.entities.fields]] +name = "subtotal" +field_type = "number" +display_name = "小计" + +[[schema.entities.fields]] +name = "tax_rate" +field_type = "number" +display_name = "税率(%)" +default_value = 0 + +[[schema.entities.fields]] +name = "total_amount" +field_type = "number" +display_name = "总金额" + +[[schema.entities.fields]] +name = "notes" +field_type = "text" +display_name = "备注" +``` + +#### 2.2.4 quote_line(报价明细行) + +```toml +[[schema.entities]] +name = "quote_line" +display_name = "报价明细" + +[[schema.entities.fields]] +name = "quote_id" +field_type = "uuid" +display_name = "报价单" +ref_entity = "quote" +required = true + +[[schema.entities.fields]] +name = "item_name" +field_type = "string" +display_name = "项目名称" +required = true + +[[schema.entities.fields]] +name = "description" +field_type = "string" +display_name = "描述" + +[[schema.entities.fields]] +name = "quantity" +field_type = "number" +display_name = "数量" +required = true +default_value = 1 + +[[schema.entities.fields]] +name = "unit_price" +field_type = "number" +display_name = "单价" +required = true + +[[schema.entities.fields]] +name = "unit" +field_type = "string" +display_name = "单位" +ui_widget = "select" +options = ["项目", "人月", "人天", "小时", "个", "套"] +default_value = "项目" + +[[schema.entities.fields]] +name = "amount" +field_type = "number" +display_name = "金额" +``` + +#### 2.2.5 contract(合同) + +```toml +[[schema.entities]] +name = "contract" +display_name = "合同" + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +required = true + +[[schema.entities.fields]] +name = "opportunity_id" +field_type = "uuid" +display_name = "关联商机" +ref_entity = "opportunity" + +[[schema.entities.fields]] +name = "quote_id" +field_type = "uuid" +display_name = "关联报价" +ref_entity = "quote" + +[[schema.entities.fields]] +name = "contract_number" +field_type = "string" +display_name = "合同编号" +generated = true + +[[schema.entities.fields]] +name = "title" +field_type = "string" +display_name = "合同名称" +required = true + +[[schema.entities.fields]] +name = "type" +field_type = "string" +display_name = "合同类型" +ui_widget = "select" +options = ["开发合同", "集成合同", "外包合同", "咨询合同", "维保合同", "销售合同", "其他"] +required = true + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["草拟中", "待签署", "执行中", "已完成", "已终止"] +default_value = "草拟中" + +[[schema.entities.fields]] +name = "amount" +field_type = "number" +display_name = "合同金额" +required = true + +[[schema.entities.fields]] +name = "paid_amount" +field_type = "number" +display_name = "已付金额" +default_value = 0 + +[[schema.entities.fields]] +name = "start_date" +field_type = "date" +display_name = "开始日期" + +[[schema.entities.fields]] +name = "end_date" +field_type = "date" +display_name = "结束日期" + +[[schema.entities.fields]] +name = "payment_terms" +field_type = "string" +display_name = "付款条款" + +[[schema.entities.fields]] +name = "notes" +field_type = "text" +display_name = "备注" +``` + +#### 2.2.6 project(项目) + +```toml +[[schema.entities]] +name = "project" +display_name = "项目" + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +required = true + +[[schema.entities.fields]] +name = "contract_id" +field_type = "uuid" +display_name = "关联合同" +ref_entity = "contract" + +[[schema.entities.fields]] +name = "opportunity_id" +field_type = "uuid" +display_name = "关联商机" +ref_entity = "opportunity" + +[[schema.entities.fields]] +name = "name" +field_type = "string" +display_name = "项目名称" +required = true + +[[schema.entities.fields]] +name = "business_type" +field_type = "string" +display_name = "业务类型" +ui_widget = "select" +options = ["software_development", "ai_development", "system_integration", "software_outsourcing", "it_consulting", "digital_content", "marketing_planning"] + +[[schema.entities.fields]] +name = "deliverable_type" +field_type = "string" +display_name = "交付物类型" +ui_widget = "select" +options = ["software", "website", "miniprogram", "video", "design", "document", "consulting_report"] + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["待开始", "进行中", "已交付", "验收中", "已完成", "已搁置"] +default_value = "待开始" + +[[schema.entities.fields]] +name = "start_date" +field_type = "date" +display_name = "开始日期" + +[[schema.entities.fields]] +name = "end_date" +field_type = "date" +display_name = "截止日期" + +[[schema.entities.fields]] +name = "contract_amount" +field_type = "number" +display_name = "合同金额" + +[[schema.entities.fields]] +name = "description" +field_type = "text" +display_name = "项目说明" +``` + +#### 2.2.7 task(任务) + +```toml +[[schema.entities]] +name = "task" +display_name = "任务" + +[[schema.entities.fields]] +name = "project_id" +field_type = "uuid" +display_name = "所属项目" +ref_entity = "project" +required = true + +[[schema.entities.fields]] +name = "title" +field_type = "string" +display_name = "任务标题" +required = true + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["待办", "进行中", "已完成", "已取消"] +default_value = "待办" + +[[schema.entities.fields]] +name = "priority" +field_type = "string" +display_name = "优先级" +ui_widget = "select" +options = ["紧急", "高", "中", "低"] +default_value = "中" + +[[schema.entities.fields]] +name = "due_date" +field_type = "date" +display_name = "截止日期" + +[[schema.entities.fields]] +name = "estimated_hours" +field_type = "number" +display_name = "预估工时" + +[[schema.entities.fields]] +name = "actual_hours" +field_type = "number" +display_name = "实际工时" + +[[schema.entities.fields]] +name = "description" +field_type = "text" +display_name = "详细说明" +``` + +#### 2.2.8 time_entry(工时记录) + +```toml +[[schema.entities]] +name = "time_entry" +display_name = "工时记录" + +[[schema.entities.fields]] +name = "task_id" +field_type = "uuid" +display_name = "关联任务" +ref_entity = "task" + +[[schema.entities.fields]] +name = "project_id" +field_type = "uuid" +display_name = "关联项目" +ref_entity = "project" +required = true + +[[schema.entities.fields]] +name = "date" +field_type = "date" +display_name = "日期" +required = true + +[[schema.entities.fields]] +name = "hours" +field_type = "number" +display_name = "时长(小时)" +required = true + +[[schema.entities.fields]] +name = "description" +field_type = "string" +display_name = "工作内容" +``` + +#### 2.2.9 invoice(发票/收款) + +```toml +[[schema.entities]] +name = "invoice" +display_name = "发票/收款" + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +required = true + +[[schema.entities.fields]] +name = "project_id" +field_type = "uuid" +display_name = "关联项目" +ref_entity = "project" + +[[schema.entities.fields]] +name = "contract_id" +field_type = "uuid" +display_name = "关联合同" +ref_entity = "contract" + +[[schema.entities.fields]] +name = "invoice_number" +field_type = "string" +display_name = "发票号" +generated = true + +[[schema.entities.fields]] +name = "type" +field_type = "string" +display_name = "类型" +ui_widget = "select" +options = ["开票", "收款记录"] +required = true + +[[schema.entities.fields]] +name = "amount" +field_type = "number" +display_name = "金额" +required = true + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["待开票", "已开票", "部分收款", "已收款", "已逾期"] +default_value = "待开票" + +[[schema.entities.fields]] +name = "issue_date" +field_type = "date" +display_name = "开票日期" + +[[schema.entities.fields]] +name = "due_date" +field_type = "date" +display_name = "到期日" + +[[schema.entities.fields]] +name = "payment_date" +field_type = "date" +display_name = "实际收款日期" + +[[schema.entities.fields]] +name = "notes" +field_type = "text" +display_name = "备注" +``` + +#### 2.2.10 expense(支出) + +```toml +[[schema.entities]] +name = "expense" +display_name = "支出" + +[[schema.entities.fields]] +name = "project_id" +field_type = "uuid" +display_name = "关联项目" +ref_entity = "project" + +[[schema.entities.fields]] +name = "category" +field_type = "string" +display_name = "类别" +ui_widget = "select" +options = ["硬件", "软件订阅", "云服务", "差旅", "餐饮", "办公用品", "其他"] +required = true + +[[schema.entities.fields]] +name = "amount" +field_type = "number" +display_name = "金额" +required = true + +[[schema.entities.fields]] +name = "date" +field_type = "date" +display_name = "日期" +required = true + +[[schema.entities.fields]] +name = "vendor" +field_type = "string" +display_name = "供应商" + +[[schema.entities.fields]] +name = "description" +field_type = "string" +display_name = "描述" +``` + +### 2.3 权限声明(10 实体 x 2 = 20 个权限码) + +```toml +[[permissions]] +code = "client.list" +name = "查看客户" + +[[permissions]] +code = "client.manage" +name = "管理客户" + +[[permissions]] +code = "opportunity.list" +name = "查看商机" + +[[permissions]] +code = "opportunity.manage" +name = "管理商机" + +[[permissions]] +code = "quote.list" +name = "查看报价" + +[[permissions]] +code = "quote.manage" +name = "管理报价" + +[[permissions]] +code = "quote_line.list" +name = "查看报价明细" + +[[permissions]] +code = "quote_line.manage" +name = "管理报价明细" + +[[permissions]] +code = "contract.list" +name = "查看合同" + +[[permissions]] +code = "contract.manage" +name = "管理合同" + +[[permissions]] +code = "project.list" +name = "查看项目" + +[[permissions]] +code = "project.manage" +name = "管理项目" + +[[permissions]] +code = "task.list" +name = "查看任务" + +[[permissions]] +code = "task.manage" +name = "管理任务" + +[[permissions]] +code = "time_entry.list" +name = "查看工时" + +[[permissions]] +code = "time_entry.manage" +name = "管理工时" + +[[permissions]] +code = "invoice.list" +name = "查看发票" + +[[permissions]] +code = "invoice.manage" +name = "管理发票" + +[[permissions]] +code = "expense.list" +name = "查看支出" + +[[permissions]] +code = "expense.manage" +name = "管理支出" +``` + +### 2.4 页面设计(4 个页面) + +#### 页面 1:全局工作台(Dashboard) + +展示内容: +- **财务概览**:本月收入/支出/利润、应收总额、逾期金额 +- **紧急待办**:今日到期任务、逾期收款、即将到期合同(7天内) +- **活跃项目**:进行中项目卡片(进度条 = 已用工时/预估工时) +- **最新工单**:最近 5 条 itops 工单(如已安装) +- **商机跟进提醒**:next_follow_up 在本周内的商机 + +快捷操作按钮条(页面顶部): +- 开始/停止计时(悬浮计时器) +- 快速记支出(3 字段:金额+分类+备注) +- 新建报价 +- 新建工单(如 itops 已安装) + +#### 页面 2:客户管理 + +客户列表(支持搜索、按来源/行业/等级/状态筛选)+ 客户详情 Tab 页: +- Tab 1:基本信息 +- Tab 2:商机列表(看板视图,按阶段分列) +- Tab 3:报价/合同 +- Tab 4:项目 +- Tab 5:发票/收支 + +从客户详情一键生成:报价单 / 合同 / 项目 + +#### 页面 3:项目工作台 + +项目列表 → 项目详情 Tab 页: +- Tab 1:项目信息 +- Tab 2:任务列表(状态分组,支持拖拽排序) +- Tab 3:工时记录(支持快速录入) +- Tab 4:关联发票 + +#### 页面 4:财务中心 + +Tab 1:报价管理(报价单列表 + 详情) +Tab 2:合同管理(合同列表 + 详情 + 到期预警) +Tab 3:发票/收款(发票列表 + 收支统计图表) + +--- + +## 3. 插件 2:erp-plugin-itops(IT 运维服务台) + +### 3.1 插件元数据 + +```toml +[metadata] +id = "erp-itops" +name = "IT 运维服务台" +version = "0.1.0" +description = "IT 运维工单管理 + SLA 追踪 + 定期巡检" +author = "ERP Platform" +``` + +### 3.2 跨插件依赖声明 + +```toml +[dependencies.freelance] +optional = true +description = "自由职业者工作台 — 自动关联客户数据,未安装时客户字段为手动输入" +``` + +### 3.3 实体设计(4 个实体) + +#### 3.3.1 service_contract(维保合同) + +```toml +[[schema.entities]] +name = "service_contract" +display_name = "维保合同" + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +ref_scope = "external" +ref_display_field = "name" +ref_search_fields = ["name", "phone"] +ref_fallback_label = "外部客户" +filterable = true + +[[schema.entities.fields]] +name = "contract_number" +field_type = "string" +display_name = "合同编号" +generated = true + +[[schema.entities.fields]] +name = "name" +field_type = "string" +display_name = "合同名称" +required = true + +[[schema.entities.fields]] +name = "service_scope" +field_type = "text" +display_name = "服务范围" + +[[schema.entities.fields]] +name = "sla_level" +field_type = "string" +display_name = "SLA 等级" +ui_widget = "select" +options = ["标准", "银牌", "金牌"] +required = true +default_value = "标准" + +[[schema.entities.fields]] +name = "sla_response_hours" +field_type = "number" +display_name = "SLA 响应时间(小时)" +default_value = 8 + +[[schema.entities.fields]] +name = "sla_resolve_hours" +field_type = "number" +display_name = "SLA 解决时间(小时)" +default_value = 48 + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["生效中", "即将到期", "已过期", "已终止"] +default_value = "生效中" + +[[schema.entities.fields]] +name = "start_date" +field_type = "date" +display_name = "开始日期" +required = true + +[[schema.entities.fields]] +name = "end_date" +field_type = "date" +display_name = "结束日期" +required = true + +[[schema.entities.fields]] +name = "amount" +field_type = "number" +display_name = "合同金额" + +[[schema.entities.fields]] +name = "payment_terms" +field_type = "string" +display_name = "付款条款" + +[[schema.entities.fields]] +name = "notes" +field_type = "text" +display_name = "备注" +``` + +#### 3.3.2 ticket(工单) + +```toml +[[schema.entities]] +name = "ticket" +display_name = "工单" + +[[schema.entities.fields]] +name = "contract_id" +field_type = "uuid" +display_name = "维保合同" +ref_entity = "service_contract" + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +ref_scope = "external" +ref_display_field = "name" +ref_search_fields = ["name", "phone"] +ref_fallback_label = "外部客户" +filterable = true + +[[schema.entities.fields]] +name = "title" +field_type = "string" +display_name = "工单标题" +required = true + +[[schema.entities.fields]] +name = "type" +field_type = "string" +display_name = "类型" +ui_widget = "select" +options = ["故障", "巡检", "咨询", "变更", "其他"] +default_value = "故障" + +[[schema.entities.fields]] +name = "priority" +field_type = "string" +display_name = "优先级" +ui_widget = "select" +options = ["紧急", "高", "中", "低"] +default_value = "中" + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["待处理", "处理中", "等待客户", "已解决", "已关闭"] +default_value = "待处理" + +[[schema.entities.fields]] +name = "channel" +field_type = "string" +display_name = "来源渠道" +ui_widget = "select" +options = ["电话", "微信", "邮件", "系统"] + +[[schema.entities.fields]] +name = "description" +field_type = "text" +display_name = "问题描述" + +[[schema.entities.fields]] +name = "resolution" +field_type = "text" +display_name = "解决方案" + +[[schema.entities.fields]] +name = "responded_at" +field_type = "datetime" +display_name = "首次响应时间" + +[[schema.entities.fields]] +name = "resolved_at" +field_type = "datetime" +display_name = "解决时间" + +[[schema.entities.fields]] +name = "closed_at" +field_type = "datetime" +display_name = "关闭时间" +``` + +#### 3.3.3 check_plan(巡检计划) + +```toml +[[schema.entities]] +name = "check_plan" +display_name = "巡检计划" + +[[schema.entities.fields]] +name = "contract_id" +field_type = "uuid" +display_name = "维保合同" +ref_entity = "service_contract" +required = true + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +ref_scope = "external" +ref_display_field = "name" +ref_search_fields = ["name", "phone"] +ref_fallback_label = "外部客户" +filterable = true + +[[schema.entities.fields]] +name = "name" +field_type = "string" +display_name = "计划名称" +required = true + +[[schema.entities.fields]] +name = "frequency" +field_type = "string" +display_name = "巡检频率" +ui_widget = "select" +options = ["每周", "每两周", "每月", "每季度"] +required = true + +[[schema.entities.fields]] +name = "check_items" +field_type = "text" +display_name = "检查项(JSON)" + +[[schema.entities.fields]] +name = "status" +field_type = "string" +display_name = "状态" +ui_widget = "select" +options = ["启用", "停用"] +default_value = "启用" + +[[schema.entities.fields]] +name = "next_check_date" +field_type = "date" +display_name = "下次巡检日期" + +[[schema.entities.fields]] +name = "notes" +field_type = "text" +display_name = "备注" +``` + +#### 3.3.4 check_record(巡检记录) + +```toml +[[schema.entities]] +name = "check_record" +display_name = "巡检记录" + +[[schema.entities.fields]] +name = "plan_id" +field_type = "uuid" +display_name = "巡检计划" +ref_entity = "check_plan" +required = true + +[[schema.entities.fields]] +name = "contract_id" +field_type = "uuid" +display_name = "维保合同" +ref_entity = "service_contract" + +[[schema.entities.fields]] +name = "client_id" +field_type = "uuid" +display_name = "客户" +ref_entity = "client" +ref_scope = "external" +ref_display_field = "name" +ref_search_fields = ["name", "phone"] +ref_fallback_label = "外部客户" +filterable = true + +[[schema.entities.fields]] +name = "check_date" +field_type = "date" +display_name = "巡检日期" +required = true + +[[schema.entities.fields]] +name = "result" +field_type = "string" +display_name = "结果" +ui_widget = "select" +options = ["正常", "有异常"] +required = true + +[[schema.entities.fields]] +name = "items_data" +field_type = "text" +display_name = "检查项结果(JSON)" + +[[schema.entities.fields]] +name = "issues_found" +field_type = "text" +display_name = "发现的问题" + +[[schema.entities.fields]] +name = "actions_taken" +field_type = "text" +display_name = "采取措施" + +[[schema.entities.fields]] +name = "notes" +field_type = "text" +display_name = "备注" +``` + +### 3.4 权限声明(4 实体 x 2 = 8 个权限码) + +```toml +[[permissions]] +code = "service_contract.list" +name = "查看维保合同" + +[[permissions]] +code = "service_contract.manage" +name = "管理维保合同" + +[[permissions]] +code = "ticket.list" +name = "查看工单" + +[[permissions]] +code = "ticket.manage" +name = "管理工单" + +[[permissions]] +code = "check_plan.list" +name = "查看巡检计划" + +[[permissions]] +code = "check_plan.manage" +name = "管理巡检计划" + +[[permissions]] +code = "check_record.list" +name = "查看巡检记录" + +[[permissions]] +code = "check_record.manage" +name = "管理巡检记录" +``` + +### 3.5 页面设计(3 个页面) + +#### 页面 1:运维概览(共享工作台的一部分) + +作为全局工作台的一个区域展示: +- 活跃合同数 + 即将到期合同预警 +- 本月工单统计(按状态饼图) +- SLA 达标率(百分比 + 趋势) +- 今日待巡检列表 +- 最近 5 条工单 + +快捷操作:快速创建工单(3 字段:标题+客户+优先级) + +#### 页面 2:合同管理 + +维保合同列表(支持按状态/客户/到期日筛选)+ 合同详情 Tab 页: +- Tab 1:合同信息 +- Tab 2:关联工单 +- Tab 3:巡检计划 + 巡检记录 + +#### 页面 3:工单中心 + +Tab 1:工单列表(按状态分 Tab:待处理/处理中/已解决/已关闭) +Tab 2:巡检管理(巡检计划列表 + 巡检记录列表) + +工单详情展示 SLA 倒计时(超时变红预警) + +### 3.6 SLA 自动计算规则 + +| SLA 等级 | 响应时间 | 解决时间 | 创建工单时自动带入 | +|----------|---------|---------|-------------------| +| 标准 | 8 小时 | 48 小时 | sla_response_hours=8, sla_resolve_hours=48 | +| 银牌 | 4 小时 | 24 小时 | sla_response_hours=4, sla_resolve_hours=24 | +| 金牌 | 2 小时 | 8 小时 | sla_response_hours=2, sla_resolve_hours=8 | + +工单列表中实时显示 SLA 倒计时。 + +--- + +## 4. 跨插件引用关系 + +``` +itops.service_contract.client_id → freelance.client (ref_scope=external) +itops.ticket.client_id → freelance.client (ref_scope=external) +itops.check_plan.client_id → freelance.client (ref_scope=external) +itops.check_record.client_id → freelance.client (ref_scope=external) +``` + +降级行为(freelance 未安装时):client_id 降级为文本输入字段,显示 ref_fallback_label "外部客户"。 + +--- + +## 5. inventory 插件扩展建议 + +在现有 inventory 插件的 product 实体增加字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| product_type | select: physical/virtual/service | 产品类型,virtual/service 走零库存 | + +覆盖"计算机软硬件及辅助设备批发/零售"的经营范围。 + +--- + +## 6. 全局工作台合并设计 + +freelance 和 itops 的仪表盘合并为一个全局工作台,布局如下: + +``` +┌─────────────────────────────────────────────────────┐ +│ 快捷操作栏:[⏱ 计时] [💰 记支出] [📋 新报价] [🔧 新工单] │ +├──────────────────────┬──────────────────────────────┤ +│ 财务概览 │ 紧急待办 │ +│ 本月收入: ¥XX,XXX │ 🔴 逾期收款: 2 笔 │ +│ 本月支出: ¥X,XXX │ 🟡 今日到期任务: 3 项 │ +│ 利润: ¥XX,XXX │ 🟡 即将到期合同: 1 份(7天内) │ +│ 应收: ¥XX,XXX │ 🔵 待处理工单: 4 条 │ +├──────────────────────┼──────────────────────────────┤ +│ 活跃项目 (4) │ 运维概览 │ +│ [项目A ████░░ 60%] │ 活跃合同: 5 份 │ +│ [项目B ██░░░░ 30%] │ SLA 达标率: 96% │ +│ [项目C ██████ 100%] │ 今日待巡检: 2 项 │ +│ │ 最新工单: ... │ +├──────────────────────┴──────────────────────────────┤ +│ 商机跟进提醒 │ +│ 📅 张三(XX公司) - 网站开发 - 明天需跟进 │ +│ 📅 李四(YY公司) - AI方案 - 后天到期 │ +└─────────────────────────────────────────────────────┘ +``` + +--- + +## 7. 实施优先级 + +``` +P1: freelance 插件核心实体(client, opportunity, project, task, time_entry) +P2: freelance 插件财务实体(quote, quote_line, contract, invoice, expense) +P3: freelance 页面(全局工作台 + 客户管理 + 项目工作台 + 财务中心) +P4: itops 插件(全部实体 + 页面) +P5: inventory 扩展(product_type 字段) +P6: 全局工作台合并 + 快捷操作 +``` + +--- + +## 8. 验证矩阵 + +| 验证场景 | 预期结果 | +|---------|---------| +| 单独安装 freelance | 全部功能正常,客户字段内部关联 | +| 单独安装 itops(不装 freelance) | client_id 降级为文本输入 | +| 同时安装 freelance + itops | client_id 自动关联 freelance.client,下拉选择客户 | +| 创建商机 → 创建报价 → 转合同 → 创建项目 | 全链路数据流通 | +| 项目中记录工时 → 生成发票 | 工时汇总可关联到发票 | +| 创建维保合同 → 设定巡检计划 → 执行巡检 → 创建工单 | 运维全流程 | +| SLA 超时预警 | 工单创建后超时自动标红 | +| 合同到期提醒 | 全局工作台显示即将到期合同 | + +--- + +## 9. 讨论溯源 + +本文档基于 2026-04-19 的无主题发散式互动探讨产出,经过多专家头脑风暴审查(UX 专家、技术架构专家、业务顾问),采纳了以下关键建议: + +- 新增 contract 实体(B2B 必要环节) +- 页面从 10 个压缩到 7 个(合并仪表盘+整合 tab) +- project 增加 business_type/deliverable_type 枚举覆盖全部经营范围 +- 全局工作台 + 快捷操作(瑞士军刀原则) +- itops client_id 加 filterable + generated column 索引 +- inventory 扩展 product.type 支持软硬件销售