feat(freelance): 编译 WASM 并验证安装 — 10 实体/20 权限已创建
This commit is contained in:
@@ -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<i32> }
|
||||
445 | struct RefCheck { chk: Option<i32> }
|
||||
| -------- ^^^
|
||||
| |
|
||||
| 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<i32> }
|
||||
684 | ... struct RefCheck { chk: Option<i32> }
|
||||
| -------- ^^^
|
||||
| |
|
||||
| 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<i32> }
|
||||
1329 | struct ExistsCheck { check_result: Option<i32> }
|
||||
| ----------- ^^^^^^^^^^^^
|
||||
| |
|
||||
| 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)
|
||||
|
||||
1
.logs/backend.pid
Normal file
1
.logs/backend.pid
Normal file
@@ -0,0 +1 @@
|
||||
10056
|
||||
@@ -1,187 +0,0 @@
|
||||
[2m08:34:12[22m [31m[1m[vite][22m[39m [31m[2m(client)[22m[39m [2m[console.error] [22mWarning: [antd: Modal] `destroyOnClose` is deprecated. Please use `destroyOnHidden` instead.
|
||||
[2m08:34:38[22m [31m[1m[vite][22m[39m [31m[2m(client)[22m[39m [2m[console.error] [22mWarning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop?
|
||||
[2m08:38:05[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages/unread-count[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:38:05[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages?page=1&page_size=5[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:39:35[22m [31m[1m[vite][22m[39m [31m[2m(client)[22m[39m [2m[console.error] [22mWarning: [antd: Modal] `destroyOnClose` is deprecated. Please use `destroyOnHidden` instead.
|
||||
[2m08:39:54[22m [31m[1m[vite][22m[39m [31m[2m(client)[22m[39m [2m[console.error] [22mWarning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop?
|
||||
[2m08:43:34[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages/unread-count[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:43:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages?page=1&page_size=5[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:44:34[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages/unread-count[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:44:34[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages?page=1&page_size=5[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:33[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:33[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/product?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:33[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/product/resolve-labels[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:33[22m [31m[1m[vite][22m[39m [31m[2m(client)[22m[39m [2m[console.error] [22mWarning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.
|
||||
[2m08:45:34[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages/unread-count[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:34[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages?page=1&page_size=5[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/warehouse?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/warehouse/resolve-labels[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/stock?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/stock/resolve-labels[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/supplier?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:35[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/supplier/resolve-labels[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:37[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/messages?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:38[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/workflow/definitions?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:38[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/workflow/definitions?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:38[22m [31m[1m[vite][22m[39m [31m[2m(client)[22m[39m [31m[Unhandled rejection] [1mAxiosError[22m: Request failed with status code 502
|
||||
[39m > 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<ProcessDefinitionInfo> }>(
|
||||
| ^
|
||||
55 | '/workflow/definitions',
|
||||
56 | { params: { page, page_size: pageSize } },
|
||||
> src/pages/workflow/ProcessDefinitions.tsx:34:18
|
||||
|
||||
[2m08:45:38[22m [31m[1m[vite][22m[39m [31m[2m(client)[22m[39m [31m[Unhandled rejection] [1mAxiosError[22m: Request failed with status code 502
|
||||
[39m > 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<ProcessDefinitionInfo> }>(
|
||||
| ^
|
||||
55 | '/workflow/definitions',
|
||||
56 | { params: { page, page_size: pageSize } },
|
||||
> src/pages/workflow/ProcessDefinitions.tsx:34:18
|
||||
|
||||
[2m08:45:39[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/organizations[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:39[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/organizations[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:39[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/roles?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:39[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/permissions[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:39[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/roles?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:39[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/permissions[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:41[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/users?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:41[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/roles?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:41[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/users?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:41[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/roles?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:42[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:42[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/purchase_order?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:42[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:42[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/purchase_order?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:43[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/admin/plugins/019da31e-17ae-7801-a1e8-640b5248f352/schema[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
[2m08:45:43[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/v1/plugins/019da31e-17ae-7801-a1e8-640b5248f352/sales_order?page=1&page_size=20[39m
|
||||
AggregateError [ECONNREFUSED]:
|
||||
at internalConnectMultiple (node:net:1142:49)
|
||||
at afterConnectMultiple (node:net:1723:7)
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
> web@0.0.0 dev G:\erp\apps\web
|
||||
> vite "--" "--strictPort"
|
||||
|
||||
Port 5174 is in use, trying another one...
|
||||
|
||||
[32m[1mVITE[22m v8.0.8[39m [2mready in [0m[1m372[22m[2m[0m ms[22m
|
||||
[32m[1mVITE[22m v8.0.8[39m [2mready in [0m[1m316[22m[2m[0m ms[22m
|
||||
|
||||
[32m➜[39m [1mLocal[22m: [36mhttp://localhost:[1m5174[22m/[39m
|
||||
[32m➜[39m [1mLocal[22m: [36mhttp://localhost:[1m5175[22m/[39m
|
||||
[2m [32m➜[39m [1mNetwork[22m[2m: use [22m[1m--host[22m[2m to expose[22m
|
||||
[2m08:36:42[22m [36m[1m[vite][22m[39m [90m[2m(client)[22m[39m [32mhmr update [39m[2m/src/index.css, /src/pages/PluginCRUDPage.tsx, /src/components/EntitySelect.tsx[22m
|
||||
[2m08:37:13[22m [36m[1m[vite][22m[39m [90m[2m(client)[22m[39m [32mhmr update [39m[2m/src/index.css, /src/components/EntitySelect.tsx[22m
|
||||
|
||||
@@ -1 +1 @@
|
||||
45128
|
||||
50960
|
||||
|
||||
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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<u8>) -> 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<u8>) -> 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` | 插件制作完整流程 |
|
||||
File diff suppressed because it is too large
Load Diff
172
plans/calm-forging-puddle.md
Normal file
172
plans/calm-forging-puddle.md
Normal file
@@ -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<String>` 参数
|
||||
- 内部新增 `export_csv()` 和 `export_xlsx()` 私有方法,返回 `Vec<u8>` bytes
|
||||
- format 为空/json 时返回原 JSON;csv/xlsx 时返回二进制
|
||||
- 返回类型改为 enum `ExportPayload { Json(Vec<Value>), Csv(Vec<u8>), Xlsx(Vec<u8>) }`
|
||||
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<DanglingRef> }`
|
||||
- `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<RwLock<RuntimeMetrics>>` 字段
|
||||
- `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. 每批完成后独立提交推送
|
||||
138
plans/skill-parallel-pixel-agent-a2852b5abd5e15119.md
Normal file
138
plans/skill-parallel-pixel-agent-a2852b5abd5e15119.md
Normal file
@@ -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 个抽屉分门别类,需要一把刀随时打开就能用。
|
||||
50
plans/skill-parallel-pixel-agent-a8e98f2c813be4f33.md
Normal file
50
plans/skill-parallel-pixel-agent-a8e98f2c813be4f33.md
Normal file
@@ -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<Vec<serde_json::Value>>` 兼容此格式。`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。
|
||||
74
plans/skill-parallel-pixel-agent-af894fc9048c54be3.md
Normal file
74
plans/skill-parallel-pixel-agent-af894fc9048c54be3.md
Normal file
@@ -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)足够。
|
||||
1301
plans/skill-parallel-pixel.md
Normal file
1301
plans/skill-parallel-pixel.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user