weaver-work-cli 0.1.8 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -17
- package/dist/cmd/action.js +76 -0
- package/dist/cmd/index.js +2 -0
- package/dist/cmd/refs/index.js +103 -0
- package/dist/cmd/register.js +31 -0
- package/dist/cmd/root.js +1 -0
- package/dist/cmd/setup/index.js +25 -5
- package/dist/cmd/skills/index.js +37 -5
- package/dist/core/connector.js +54 -0
- package/dist/core/context.js +2 -1
- package/dist/core/input.js +48 -0
- package/dist/core/output.js +298 -3
- package/dist/core/refs.js +435 -0
- package/dist/core/state.js +26 -0
- package/dist/internal/e10/auth/commands.js +34 -19
- package/dist/internal/e10/auth/session.js +74 -2
- package/dist/internal/e10/auth/xiaoe.js +359 -94
- package/dist/internal/e10/context.js +18 -6
- package/dist/internal/e10/request-runtime.js +8 -1
- package/dist/internal/skills/install.js +148 -28
- package/dist/internal/skills/reader.js +12 -9
- package/dist/shortcuts/blog/continuation.js +74 -0
- package/dist/shortcuts/blog/errors.js +60 -0
- package/dist/shortcuts/blog/host.js +175 -0
- package/dist/shortcuts/blog/index.js +324 -0
- package/dist/shortcuts/blog/manifest.js +415 -0
- package/dist/shortcuts/blog/operations/comment.js +125 -0
- package/dist/shortcuts/blog/operations/comments.js +31 -0
- package/dist/shortcuts/blog/operations/edit.js +131 -0
- package/dist/shortcuts/blog/operations/export.js +46 -0
- package/dist/shortcuts/blog/operations/like.js +80 -0
- package/dist/shortcuts/blog/operations/publish.js +157 -0
- package/dist/shortcuts/blog/operations/query-user.js +58 -0
- package/dist/shortcuts/blog/operations/query.js +96 -0
- package/dist/shortcuts/blog/operations/registry.js +54 -0
- package/dist/shortcuts/blog/operations/remind.js +176 -0
- package/dist/shortcuts/blog/operations/shared.js +330 -0
- package/dist/shortcuts/blog/operations/types.js +16 -0
- package/dist/shortcuts/blog/operations.js +9 -0
- package/dist/shortcuts/caizhilian/continuation.js +70 -0
- package/dist/shortcuts/caizhilian/errors.js +53 -0
- package/dist/shortcuts/caizhilian/host.js +190 -0
- package/dist/shortcuts/caizhilian/index.js +186 -0
- package/dist/shortcuts/caizhilian/manifest.js +42 -0
- package/dist/shortcuts/caizhilian/operations/api.js +801 -0
- package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
- package/dist/shortcuts/caizhilian/operations/files.js +306 -0
- package/dist/shortcuts/caizhilian/operations/form.js +170 -0
- package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
- package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +1227 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
- package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
- package/dist/shortcuts/caizhilian/operations/handlers/folder.js +1024 -0
- package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
- package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
- package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
- package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
- package/dist/shortcuts/caizhilian/operations/specs.js +698 -0
- package/dist/shortcuts/caizhilian/operations/types.js +57 -0
- package/dist/shortcuts/caizhilian/operations.js +21 -0
- package/dist/shortcuts/contract/continuation.js +70 -0
- package/dist/shortcuts/contract/errors.js +62 -0
- package/dist/shortcuts/contract/host.js +205 -0
- package/dist/shortcuts/contract/index.js +318 -0
- package/dist/shortcuts/contract/manifest.js +596 -0
- package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
- package/dist/shortcuts/contract/operations/form-info.js +371 -0
- package/dist/shortcuts/contract/operations/read.js +118 -0
- package/dist/shortcuts/contract/operations/registry.js +43 -0
- package/dist/shortcuts/contract/operations/request.js +300 -0
- package/dist/shortcuts/contract/operations/shared.js +133 -0
- package/dist/shortcuts/contract/operations/subject.js +157 -0
- package/dist/shortcuts/contract/operations/template.js +81 -0
- package/dist/shortcuts/contract/operations/types.js +7 -0
- package/dist/shortcuts/contract/operations/workflow.js +52 -0
- package/dist/shortcuts/contract/operations/write.js +131 -0
- package/dist/shortcuts/contract/operations.js +8 -0
- package/dist/shortcuts/hrm/index.js +83 -2
- package/dist/shortcuts/hrm/manifest.js +16 -9
- package/dist/shortcuts/hrm/operations/employee.js +160 -30
- package/dist/shortcuts/hrm/operations/org.js +17 -3
- package/dist/shortcuts/hrm/operations/position.js +18 -2
- package/dist/shortcuts/hrm/operations/registry.js +2 -0
- package/dist/shortcuts/hrm/operations/resolve.js +449 -0
- package/dist/shortcuts/index.js +5 -1
- package/dist/shortcuts/invoice/index.js +74 -36
- package/dist/shortcuts/invoice/manifest.js +674 -4
- package/dist/shortcuts/invoice/markdown.js +172 -0
- package/dist/shortcuts/invoice/operations/add.js +3 -3
- package/dist/shortcuts/invoice/operations/delete.js +2 -2
- package/dist/shortcuts/invoice/operations/get.js +10 -2
- package/dist/shortcuts/invoice/operations/import.js +2 -2
- package/dist/shortcuts/invoice/operations/registry.js +8 -1
- package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
- package/dist/shortcuts/invoice/operations/reim.js +85 -2
- package/dist/shortcuts/invoice/operations/shared.js +75 -8
- package/dist/shortcuts/invoice/operations/update.js +2 -2
- package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
- package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
- package/dist/shortcuts/invoice/projection.js +328 -0
- package/dist/shortcuts/meeting/host.js +18 -1
- package/dist/shortcuts/meeting/manifest.js +30 -18
- package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
- package/dist/shortcuts/meeting/operations/env.js +8 -1
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/signs.js +216 -2
- package/dist/shortcuts/workflow/endpoints.js +2 -1
- package/dist/shortcuts/workflow/errors.js +8 -2
- package/dist/shortcuts/workflow/host.js +11 -4
- package/dist/shortcuts/workflow/operations/form.js +1016 -590
- package/dist/shortcuts/workflow/operations/read.js +913 -231
- package/dist/shortcuts/workflow/operations/shared.js +21 -0
- package/dist/shortcuts/workflow/operations/write.js +750 -52
- package/dist/shortcuts/workflow/operations.js +36 -10
- package/docs/_catalog.md +6 -0
- package/docs/agent-invoice.md +5 -1
- package/docs/agent-io-channel-design.md +468 -0
- package/docs/agent-skill-install.md +9 -4
- package/docs/blog.md +123 -0
- package/docs/caizhilian.md +304 -0
- package/docs/contract.md +91 -0
- package/docs/e10-auth.md +4 -2
- package/docs/hrm.md +53 -11
- package/docs/invoice.md +5 -1
- package/docs/meeting.md +1 -1
- package/docs/operation-manual.md +15 -3
- package/docs/skill-review-2026-09-19.md +2 -2
- package/package.json +1 -1
- package/scripts/package-skill.mjs +32 -20
- package/scripts/xiaoe_env.py +118 -0
- package/skill-template/business-info.json +113 -14
- package/skill-template/domains/blog.md +24 -0
- package/skill-template/domains/caizhilian.md +22 -0
- package/skill-template/domains/hrm.md +2 -1
- package/skill-template/domains/jinchengda.md +23 -0
- package/skill-template/domains/meeting.md +1 -1
- package/skill-template/domains/workflow.md +1 -1
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +111 -0
- package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
- package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
- package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
- package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
- package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
- package/skills/weaver-e10-blog/references/blog-export.md +42 -0
- package/skills/weaver-e10-blog/references/blog-like.md +50 -0
- package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
- package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
- package/skills/weaver-e10-blog/references/blog-query.md +49 -0
- package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
- package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
- package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +217 -0
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
- package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
- package/skills/weaver-e10-caizhilian/references/document.md +111 -0
- package/skills/weaver-e10-caizhilian/references/file-collection.md +173 -0
- package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
- package/skills/weaver-e10-caizhilian/references/files.md +120 -0
- package/skills/weaver-e10-caizhilian/references/folder.md +122 -0
- package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +440 -0
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +5694 -0
- package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
- package/skills/weaver-e10-calendar/SKILL.md +4 -3
- package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
- package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
- package/skills/weaver-e10-esb/SKILL.md +2 -2
- package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
- package/skills/weaver-e10-hrm/SKILL.md +104 -9
- package/skills/weaver-e10-hrm/references/employee.md +89 -15
- package/skills/weaver-e10-hrm/references/org.md +114 -5
- package/skills/weaver-e10-hrm/references/position.md +14 -7
- package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +167 -0
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
- package/skills/weaver-e10-jucailin/SKILL.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +4 -11
- package/skills/weaver-e10-meeting/SKILL.md +66 -32
- package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
- package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
- package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
- package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
- package/skills/weaver-e10-okr/SKILL.md +6 -11
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +3 -3
- package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
- package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
- package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
- package/skills/weaver-e10-shared/SKILL.md +42 -34
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +16 -43
- package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +21 -14
- package/skills/weaver-e10-shijingran/SKILL.md +2 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
- package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
- package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
- package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
- package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
- package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
- package/skills/weaver-e10-workflow/SKILL.md +41 -46
- package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
- package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
- package/skills/weaver-e10-workflow/references/workflow-create.md +83 -109
- package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
- package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
- package/skills/weaver-e10-workflow/references/workflow-query.md +40 -27
- package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
- package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
- package/skills/weaver-e10-yepiaotong/SKILL.md +74 -39
- package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
- package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
- package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
- package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
- package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
- package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
- package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
- package/scripts/sync-connector-skills.mjs +0 -172
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** 写操作链路的统一状态名,与业票通保持一致。 */
|
|
2
|
+
export const CONTRACT_WORKFLOW = {
|
|
3
|
+
awaitingConfirmation: (operation) => ({ operation, status: 'AWAITING_CONFIRMATION' }),
|
|
4
|
+
executing: (operation) => ({ operation, status: 'EXECUTING' }),
|
|
5
|
+
verifying: (operation) => ({ operation, status: 'VERIFYING' }),
|
|
6
|
+
complete: (operation) => ({ operation, status: 'COMPLETE' }),
|
|
7
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { contractValidationError } from '../errors.js';
|
|
2
|
+
import { ContractReadOperation, projectWorkflowBase, projectWorkflowList } from './read.js';
|
|
3
|
+
import { queryFrom, requireId, requireNonEmptyString } from './shared.js';
|
|
4
|
+
import { contractWorkflowFormInfoOperation } from './form-info.js';
|
|
5
|
+
export const contractWorkflowQueryOperation = new ContractReadOperation('contract.workflow.query', {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
path: '/api/workflow/pathdef/openapi/getBaseInfoByCondition',
|
|
8
|
+
userIdField: 'userid',
|
|
9
|
+
body: (input) => ({
|
|
10
|
+
workflowName: requireNonEmptyString(input.workflowName, 'workflowName'),
|
|
11
|
+
...queryFrom(input, ['workflowType', 'status']),
|
|
12
|
+
}),
|
|
13
|
+
shape: projectWorkflowBase,
|
|
14
|
+
});
|
|
15
|
+
export const contractWorkflowListOperation = new ContractReadOperation('contract.workflow.list', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
path: '/api/workflow/list/paService/v1/getCreateWorkflowList',
|
|
18
|
+
userIdField: 'userid',
|
|
19
|
+
body: (input) => {
|
|
20
|
+
const wfpCode = Number(input.wfpCode);
|
|
21
|
+
if (wfpCode !== 0 && wfpCode !== 1) {
|
|
22
|
+
throw contractValidationError('wfp_code_invalid', 'wfpCode 只能是 0(个人审批)或 1(团队审批)');
|
|
23
|
+
}
|
|
24
|
+
return { wfpCode, ...queryFrom(input, ['filterHide']) };
|
|
25
|
+
},
|
|
26
|
+
shape: projectWorkflowList,
|
|
27
|
+
});
|
|
28
|
+
export const contractWorkflowFieldOptionsOperation = new ContractReadOperation('contract.workflow.field.options', {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
path: '/api/workflow/core/paService/v1/getFieldOption',
|
|
31
|
+
userIdField: 'userid',
|
|
32
|
+
body: (input) => ({
|
|
33
|
+
fieldId: requireId(input.fieldId, 'fieldId'),
|
|
34
|
+
...queryFrom(input, ['pageNo', 'pageSize']),
|
|
35
|
+
}),
|
|
36
|
+
shape: (value) => {
|
|
37
|
+
const data = (value.data ?? {});
|
|
38
|
+
const rows = Array.isArray(data.data) ? data.data : [];
|
|
39
|
+
return {
|
|
40
|
+
message: value.message,
|
|
41
|
+
total: data.total,
|
|
42
|
+
maxPage: data.maxPage,
|
|
43
|
+
options: rows.map((item) => ({ id: item?.id, name: item?.name })),
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
export const contractWorkflowOperations = [
|
|
48
|
+
contractWorkflowQueryOperation,
|
|
49
|
+
contractWorkflowListOperation,
|
|
50
|
+
contractWorkflowFormInfoOperation,
|
|
51
|
+
contractWorkflowFieldOptionsOperation,
|
|
52
|
+
];
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ContractCliError, contractConfirmationError, contractPartialError, contractValidationError } from '../errors.js';
|
|
2
|
+
import { objectFingerprint, sessionMatches } from './shared.js';
|
|
3
|
+
import { CONTRACT_WORKFLOW } from './types.js';
|
|
4
|
+
function baseNameOf(operation) {
|
|
5
|
+
return operation.replace(/\.(prepare|apply)$/u, '');
|
|
6
|
+
}
|
|
7
|
+
function defaultFingerprint(input) {
|
|
8
|
+
return objectFingerprint(input);
|
|
9
|
+
}
|
|
10
|
+
export class ContractWritePrepareOperation {
|
|
11
|
+
name;
|
|
12
|
+
applyName;
|
|
13
|
+
spec;
|
|
14
|
+
constructor(name, applyName, spec) {
|
|
15
|
+
this.name = name;
|
|
16
|
+
this.applyName = applyName;
|
|
17
|
+
this.spec = spec;
|
|
18
|
+
}
|
|
19
|
+
async execute(input, { host, continuation }) {
|
|
20
|
+
this.spec.validate?.(input);
|
|
21
|
+
const precheck = this.spec.precheck ? await this.spec.precheck(input, host) : undefined;
|
|
22
|
+
const session = await host.getSession();
|
|
23
|
+
const preview = this.spec.preview(input);
|
|
24
|
+
const baseName = baseNameOf(this.name);
|
|
25
|
+
const continuationToken = await continuation.issue({
|
|
26
|
+
operation: this.applyName,
|
|
27
|
+
path: this.spec.path,
|
|
28
|
+
baseUrl: session.baseUrl,
|
|
29
|
+
profile: session.profile,
|
|
30
|
+
userId: session.userId,
|
|
31
|
+
fingerprint: (this.spec.fingerprint ?? defaultFingerprint)(input),
|
|
32
|
+
preview,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
status: 'AWAITING_CONFIRMATION',
|
|
36
|
+
data: {
|
|
37
|
+
preview,
|
|
38
|
+
continuation: continuationToken,
|
|
39
|
+
expiresInSeconds: 600,
|
|
40
|
+
workflow: CONTRACT_WORKFLOW.awaitingConfirmation(baseName),
|
|
41
|
+
...(precheck ? { precheck } : {}),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class ContractWriteApplyOperation {
|
|
47
|
+
name;
|
|
48
|
+
spec;
|
|
49
|
+
constructor(name, spec) {
|
|
50
|
+
this.name = name;
|
|
51
|
+
this.spec = spec;
|
|
52
|
+
}
|
|
53
|
+
async execute(input, { host, continuation }) {
|
|
54
|
+
this.spec.validate?.(input);
|
|
55
|
+
if (input.confirm !== true) {
|
|
56
|
+
throw contractConfirmationError(this.name, { hint: '重新调用时传入 confirm=true 与 prepare 返回的 continuation' });
|
|
57
|
+
}
|
|
58
|
+
const lease = await continuation.verify(input.continuation, this.name);
|
|
59
|
+
if (typeof lease.fingerprint !== 'string') {
|
|
60
|
+
throw contractValidationError('continuation_invalid', 'continuation 缺少写入指纹,请重新 prepare');
|
|
61
|
+
}
|
|
62
|
+
const session = await host.getSession();
|
|
63
|
+
if (!sessionMatches(lease, session)) {
|
|
64
|
+
throw contractValidationError('context_mismatch', '确认上下文已变化,请重新 prepare');
|
|
65
|
+
}
|
|
66
|
+
if ((this.spec.fingerprint ?? defaultFingerprint)(input) !== lease.fingerprint) {
|
|
67
|
+
throw contractValidationError('target_changed', '待写入内容与确认时不一致,请重新 prepare');
|
|
68
|
+
}
|
|
69
|
+
const baseName = baseNameOf(this.name);
|
|
70
|
+
const body = {
|
|
71
|
+
...(this.spec.defaults ?? {}),
|
|
72
|
+
...this.spec.body(input),
|
|
73
|
+
};
|
|
74
|
+
let result;
|
|
75
|
+
try {
|
|
76
|
+
result = await host.contractRequest({
|
|
77
|
+
method: this.spec.method,
|
|
78
|
+
path: this.spec.path,
|
|
79
|
+
body,
|
|
80
|
+
...(this.spec.userIdField ? { userIdField: this.spec.userIdField } : {}),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof ContractCliError && this.spec.mapError) {
|
|
85
|
+
const mapped = this.spec.mapError(error);
|
|
86
|
+
if (mapped)
|
|
87
|
+
throw mapped;
|
|
88
|
+
}
|
|
89
|
+
if (error instanceof ContractCliError && error.type === 'network') {
|
|
90
|
+
throw contractPartialError('写请求发送后连接中断,结果不确定,禁止自动重试;先用查询接口确认是否已生效', {
|
|
91
|
+
preview: lease.preview,
|
|
92
|
+
workflow: CONTRACT_WORKFLOW.executing(baseName),
|
|
93
|
+
}, { stage: 'write' });
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
if (!this.spec.readback) {
|
|
98
|
+
return {
|
|
99
|
+
status: 'COMPLETE',
|
|
100
|
+
data: {
|
|
101
|
+
result,
|
|
102
|
+
verified: false,
|
|
103
|
+
workflow: CONTRACT_WORKFLOW.complete(baseName),
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
let verified;
|
|
108
|
+
try {
|
|
109
|
+
verified = await this.spec.readback(result, input, host);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof ContractCliError && error.type === 'partial')
|
|
113
|
+
throw error;
|
|
114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
+
throw contractPartialError(`写入接口已返回成功,但回读校验失败,结果不确定:${message}`, {
|
|
116
|
+
preview: lease.preview,
|
|
117
|
+
result,
|
|
118
|
+
workflow: CONTRACT_WORKFLOW.verifying(baseName),
|
|
119
|
+
}, { stage: 'readback' });
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
status: 'COMPLETE',
|
|
123
|
+
data: {
|
|
124
|
+
result,
|
|
125
|
+
verified: true,
|
|
126
|
+
workflow: CONTRACT_WORKFLOW.complete(baseName),
|
|
127
|
+
...(verified ? { readback: verified } : {}),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getContractOperation } from './operations/registry.js';
|
|
2
|
+
import { asObject, assertNoForbiddenKeys } from './operations/shared.js';
|
|
3
|
+
export async function executeContractOperation(operation, rawInput, context) {
|
|
4
|
+
const handler = getContractOperation(operation);
|
|
5
|
+
const input = asObject(rawInput);
|
|
6
|
+
assertNoForbiddenKeys(input);
|
|
7
|
+
return handler.execute(input, context);
|
|
8
|
+
}
|
|
@@ -57,6 +57,51 @@ function parseJsonInput(raw, label) {
|
|
|
57
57
|
throw hrmValidationError('input_invalid', `${label} 不是合法 JSON 对象`);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
/** 解析 run-many 的 JSON 数组输入:[{ "op": "...", "input": {...} }, ...] */
|
|
61
|
+
export async function inputArrayFrom(options) {
|
|
62
|
+
const opts = asOptions(options);
|
|
63
|
+
if (opts.input && opts.inputJson) {
|
|
64
|
+
throw hrmValidationError('input_conflict', '不能同时传 --input 和 --input-json');
|
|
65
|
+
}
|
|
66
|
+
let raw;
|
|
67
|
+
if (opts.inputJson) {
|
|
68
|
+
raw = String(opts.inputJson);
|
|
69
|
+
}
|
|
70
|
+
else if (opts.input) {
|
|
71
|
+
try {
|
|
72
|
+
raw = stripBom(opts.input === '-' ? await readStdin() : await readFile(String(opts.input), 'utf8'));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw hrmValidationError('input_unreadable', `无法读取 --input:${String(opts.input)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
throw hrmValidationError('input_required', 'run-many 需要 --input 或 --input-json 提供 JSON 数组');
|
|
80
|
+
}
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(stripBom(raw));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw hrmValidationError('input_invalid', 'run-many 输入不是合法 JSON');
|
|
87
|
+
}
|
|
88
|
+
if (!Array.isArray(parsed)) {
|
|
89
|
+
throw hrmValidationError('input_invalid', 'run-many 输入必须是数组:[{"op":"...","input":{...}}, ...]');
|
|
90
|
+
}
|
|
91
|
+
return parsed.map((item, index) => {
|
|
92
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
93
|
+
throw hrmValidationError('input_invalid', `run-many 第 ${index + 1} 项必须是对象`);
|
|
94
|
+
}
|
|
95
|
+
const obj = item;
|
|
96
|
+
if (typeof obj.op !== 'string' || !obj.op.trim()) {
|
|
97
|
+
throw hrmValidationError('input_invalid', `run-many 第 ${index + 1} 项缺少 op 字段`);
|
|
98
|
+
}
|
|
99
|
+
const input = obj.input && typeof obj.input === 'object' && !Array.isArray(obj.input)
|
|
100
|
+
? obj.input
|
|
101
|
+
: {};
|
|
102
|
+
return { op: obj.op.trim(), input };
|
|
103
|
+
});
|
|
104
|
+
}
|
|
60
105
|
export function successEnvelope(operation, result) {
|
|
61
106
|
return {
|
|
62
107
|
schemaVersion: 1,
|
|
@@ -96,8 +141,25 @@ export const hrmShortcut = {
|
|
|
96
141
|
const hrm = program.command('hrm').description('泛微E10组织管理(员工查询/组织架构/岗位查询)');
|
|
97
142
|
hrm.command('schema')
|
|
98
143
|
.description('Print hrm operation manifest for agents')
|
|
99
|
-
.
|
|
100
|
-
|
|
144
|
+
.option('--full', 'print full manifest with complete inputSchema')
|
|
145
|
+
.action(action((ctx, options) => {
|
|
146
|
+
if (asOptions(options).full === true) {
|
|
147
|
+
ctx.output.write(hrmManifest, { json: true });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// 精简版:name + summary + 参数名 + 必填项,供 Agent 快速路由,避免全量 schema 撑爆上下文
|
|
151
|
+
const brief = {
|
|
152
|
+
schemaVersion: hrmManifest.schemaVersion,
|
|
153
|
+
name: hrmManifest.name,
|
|
154
|
+
version: hrmManifest.version,
|
|
155
|
+
operations: hrmManifest.operations.map((o) => ({
|
|
156
|
+
name: o.name,
|
|
157
|
+
summary: o.summary,
|
|
158
|
+
params: Object.keys(o.inputSchema?.properties ?? {}),
|
|
159
|
+
required: o.inputSchema?.required ?? [],
|
|
160
|
+
})),
|
|
161
|
+
};
|
|
162
|
+
ctx.output.write(brief, { json: true });
|
|
101
163
|
}));
|
|
102
164
|
hrm.command('run')
|
|
103
165
|
.description('Run an hrm operation with JSON input')
|
|
@@ -107,5 +169,24 @@ export const hrmShortcut = {
|
|
|
107
169
|
.action(action(async (ctx, operation, options) => {
|
|
108
170
|
await executeAndPrint(ctx, String(operation), await inputFrom(options));
|
|
109
171
|
}));
|
|
172
|
+
hrm.command('run-many')
|
|
173
|
+
.description('Run multiple hrm operations in a single process (shared session)')
|
|
174
|
+
.option('--input <path>', 'JSON array file, or - for stdin')
|
|
175
|
+
.option('--input-json <json>', 'inline JSON array')
|
|
176
|
+
.action(action(async (ctx, options) => {
|
|
177
|
+
const items = await inputArrayFrom(options);
|
|
178
|
+
const host = createHrmHost(ctx);
|
|
179
|
+
const results = [];
|
|
180
|
+
for (const item of items) {
|
|
181
|
+
try {
|
|
182
|
+
const result = await executeHrmOperation(item.op, item.input, { ctx, host });
|
|
183
|
+
results.push(successEnvelope(item.op, result));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
results.push({ operation: item.op, ...failureEnvelope(error) });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
ctx.output.write({ schemaVersion: 1, ok: true, count: results.length, results }, { json: true });
|
|
190
|
+
}));
|
|
110
191
|
},
|
|
111
192
|
};
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { hrmOperationDefinitions } from './operations/registry.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* 明确「不提供」的能力边界(不得编造、不得绕过):
|
|
4
|
+
* - `hrm.employee.dls`:代理商查询仅泛微自用系统域名支持(自用域名白名单),未实现。
|
|
5
|
+
* - `hrm.org.virtuals`:多维(组织维度)清单**不单独暴露**——维度获取与匹配已并入
|
|
6
|
+
* `hrm.org.resolve` 内部;「列出该租户有哪些多维」属暂不支持的诉求,
|
|
7
|
+
* 不要构造一个不存在的维度名去套 `availableVirtuals`,也不要绕过。
|
|
5
8
|
*/
|
|
6
|
-
const
|
|
9
|
+
const WITHHELD = [
|
|
10
|
+
{
|
|
11
|
+
name: 'hrm.employee.dls',
|
|
12
|
+
reason: '代理商查询(containsDls=true)仅泛微自用系统域名支持(自用域名白名单),客户/其他环境禁止触发;未实现,不得编造或绕过。',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'hrm.org.virtuals',
|
|
16
|
+
reason: '多维(组织维度)清单不单独暴露:维度获取与名称匹配已并入 hrm.org.resolve 内部(其 not_found 会顺带回 availableVirtuals 供纠错)。「列出该租户有哪些多维」暂不支持,不要构造不存在的维度名去套 availableVirtuals,也不要编造或绕过。',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
7
19
|
function buildOperations() {
|
|
8
20
|
return hrmOperationDefinitions().map((definition) => ({
|
|
9
21
|
name: definition.name,
|
|
@@ -23,10 +35,5 @@ export const hrmManifest = {
|
|
|
23
35
|
sourceSkill: 'weaver-e10-hrm',
|
|
24
36
|
capabilities: ['employee', 'org', 'position'],
|
|
25
37
|
operations: buildOperations(),
|
|
26
|
-
withheldOperations:
|
|
27
|
-
...DLS_WITHHELD.map((name) => ({
|
|
28
|
-
name,
|
|
29
|
-
reason: '代理商查询(containsDls=true)仅泛微自用系统域名支持(自用域名白名单),客户/其他环境禁止触发;未实现,不得编造或绕过。',
|
|
30
|
-
})),
|
|
31
|
-
],
|
|
38
|
+
withheldOperations: WITHHELD,
|
|
32
39
|
};
|
|
@@ -26,6 +26,125 @@ function formatMember(r, baseUrl) {
|
|
|
26
26
|
deptLink: deptId ? orgLink(baseUrl, deptId) : '',
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* 人员卡片默认输出的核心字段白名单(顺序即输出顺序)。
|
|
31
|
+
* 完整卡片有 85 个顶层键、~5.3KB,其中大部分是登录/管理元数据(password、last_android、
|
|
32
|
+
* hideFieldList、setting、*_admin…),Agent 用不上;投影后 ~1.2KB。
|
|
33
|
+
* 需要完整卡片时传 `full: true`。
|
|
34
|
+
*/
|
|
35
|
+
const DETAIL_CORE_FIELDS = [
|
|
36
|
+
'id', 'name', 'username', 'job_num', 'sex', 'avatar',
|
|
37
|
+
'personnel_status', 'status', 'type',
|
|
38
|
+
'department', 'subcompany', 'organization', 'position', 'jobsetid', 'jobset_level',
|
|
39
|
+
'superior', 'other_superior', 'only_superior', 'subordinate', 'assistant',
|
|
40
|
+
'mobile', 'email', 'telephone', 'office', 'location', 'active_date',
|
|
41
|
+
];
|
|
42
|
+
/** 实体数组字段(人员状态/部门/分部/岗位/职级/地点):只留 id + name。 */
|
|
43
|
+
const DETAIL_ENTITY_FIELDS = ['personnel_status', 'department', 'subcompany', 'position', 'jobsetid', 'jobset_level', 'location'];
|
|
44
|
+
/** 人员数组字段(上级/其他上级/助理):保留 id + name + statusCode + 瘦身头像。 */
|
|
45
|
+
const DETAIL_PERSON_FIELDS = ['superior', 'other_superior', 'assistant'];
|
|
46
|
+
/** 头像瘦身:原响应重复给出 7 个等价 URL(url/downloadUrl/appDownloadUrl/previewUrl/…)+ p1..p5,只留 1 个 url。 */
|
|
47
|
+
function slimAvatar(avatar) {
|
|
48
|
+
if (!avatar || typeof avatar !== 'object')
|
|
49
|
+
return avatar;
|
|
50
|
+
const a = avatar;
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const key of ['url', 'name', 'userId']) {
|
|
53
|
+
if (a[key] !== undefined && a[key] !== '')
|
|
54
|
+
out[key] = a[key];
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** 实体数组瘦身:只留 id + name,丢掉 _entityType/icon/tenantKey/delete 等前端元数据。 */
|
|
59
|
+
function slimEntities(value) {
|
|
60
|
+
if (!Array.isArray(value))
|
|
61
|
+
return value;
|
|
62
|
+
return value.map((item) => {
|
|
63
|
+
if (!item || typeof item !== 'object')
|
|
64
|
+
return item;
|
|
65
|
+
const e = item;
|
|
66
|
+
return e.name === undefined ? { id: e.id } : { id: e.id, name: e.name };
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** 人员数组瘦身:id + name + statusCode + 瘦身头像。 */
|
|
70
|
+
function slimPersons(value) {
|
|
71
|
+
if (!Array.isArray(value))
|
|
72
|
+
return value;
|
|
73
|
+
return value.map((item) => {
|
|
74
|
+
if (!item || typeof item !== 'object')
|
|
75
|
+
return item;
|
|
76
|
+
const p = item;
|
|
77
|
+
const out = { id: p.id, name: p.name };
|
|
78
|
+
if (p.statusCode !== undefined)
|
|
79
|
+
out.statusCode = p.statusCode;
|
|
80
|
+
if (p.avatar !== undefined)
|
|
81
|
+
out.avatar = slimAvatar(p.avatar);
|
|
82
|
+
return out;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** 默认精简投影:只保留核心业务字段(`full: true` 时跳过,直接回完整卡片)。 */
|
|
86
|
+
function pickDetailCore(raw) {
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const key of DETAIL_CORE_FIELDS) {
|
|
89
|
+
const value = raw[key];
|
|
90
|
+
if (value === undefined || value === null)
|
|
91
|
+
continue;
|
|
92
|
+
if (DETAIL_PERSON_FIELDS.includes(key))
|
|
93
|
+
out[key] = slimPersons(value);
|
|
94
|
+
else if (DETAIL_ENTITY_FIELDS.includes(key))
|
|
95
|
+
out[key] = slimEntities(value);
|
|
96
|
+
else if (key === 'avatar')
|
|
97
|
+
out[key] = slimAvatar(value);
|
|
98
|
+
else
|
|
99
|
+
out[key] = value;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 「人员列表」精简行:只留 id + name(外加派生的 link,供 `[姓名](link)` 渲染)。
|
|
105
|
+
* 用于 `subordinate.mine` / `subordinate.by-id` / `dept.members` 的默认输出。
|
|
106
|
+
* 对比原始行:by-id 原始行单行 960B(其中上游的 superior 对象一项就占 485B)、
|
|
107
|
+
* `formatMember` 行 366B;精简后约 120B。需要部门/岗位/电话等完整行时传 `full: true`。
|
|
108
|
+
*/
|
|
109
|
+
function formatPersonBrief(r, baseUrl) {
|
|
110
|
+
const id = r.id ?? r.employeeId;
|
|
111
|
+
return { id, name: r.name ?? r.username, link: personLink(baseUrl, id) };
|
|
112
|
+
}
|
|
113
|
+
/** 下属完整行(`full: true` 时用于 by-id):by-id 原始行的显式投影。 */
|
|
114
|
+
function formatSubordinateFull(r, baseUrl) {
|
|
115
|
+
const id = r.id ?? r.employeeId;
|
|
116
|
+
return {
|
|
117
|
+
id,
|
|
118
|
+
name: r.name ?? r.username,
|
|
119
|
+
pinyin: r.pinyin,
|
|
120
|
+
telephone: r.telephone,
|
|
121
|
+
sex: r.sex,
|
|
122
|
+
personnelStatus: r.personnelStatus,
|
|
123
|
+
department: r.department,
|
|
124
|
+
position: r.position,
|
|
125
|
+
superior: r.superior,
|
|
126
|
+
link: personLink(baseUrl, id),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* `employee.query` 默认行:只回「人员信息」——人员 id / 姓名 / 部门 / 岗位 / 状态 + 卡片 link。
|
|
131
|
+
* 相对完整行(`formatMember`,368B)去掉的是:
|
|
132
|
+
* ① 另两套 id —— `userid`(登录信息 id)、`cid`(IM 联系人 id):查人场景用不到,且极易和人员 id 混用;
|
|
133
|
+
* ② `mobile`:本接口恒为空串(要脱敏手机号得用 `employee.batch`);
|
|
134
|
+
* ③ `departmentId` + `deptLink`:部门卡片链接,需要时用 `org.detail` 或传 `full: true`。
|
|
135
|
+
* 注意人员 id 是唯一应该传给 `employee.detail` / `employee.batch` / `subordinate.by-id` 的 id。
|
|
136
|
+
*/
|
|
137
|
+
function formatQueryRowBrief(r, baseUrl) {
|
|
138
|
+
const id = r.id;
|
|
139
|
+
return {
|
|
140
|
+
id,
|
|
141
|
+
name: r.name,
|
|
142
|
+
department: r.department,
|
|
143
|
+
position: r.position,
|
|
144
|
+
statusCode: r.statusCode,
|
|
145
|
+
link: personLink(baseUrl, id),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
29
148
|
/** 批量详情行 → 精简人员行。 */
|
|
30
149
|
function formatBatch(u, baseUrl) {
|
|
31
150
|
const deptId = u.departmentId != null && u.departmentId !== '' ? String(u.departmentId) : '';
|
|
@@ -73,7 +192,7 @@ const employeeQuery = {
|
|
|
73
192
|
resource: 'employee',
|
|
74
193
|
method: 'employee.query',
|
|
75
194
|
action: 'hrm.employee.query',
|
|
76
|
-
summary: '按工号/姓名/手机/邮箱(keyword 自动推断或显式字段)查询员工;默认在职,status 传 ["6"]
|
|
195
|
+
summary: '按工号/姓名/手机/邮箱(keyword 自动推断或显式字段)查询员工;默认在职,status 传 ["6"] 查离职;默认每行只回人员信息',
|
|
77
196
|
risk: READ,
|
|
78
197
|
inputSchema: schemaOf({
|
|
79
198
|
keyword: { type: 'string', description: '查询关键词,自动推断:含 @→邮箱;11 位纯数字→手机;纯数字或字母开头含数字→工号;其余→姓名(模糊)' },
|
|
@@ -82,6 +201,7 @@ const employeeQuery = {
|
|
|
82
201
|
mobile: { type: 'string', description: '手机号(模糊匹配)' },
|
|
83
202
|
email: { type: 'string', description: '邮箱(模糊匹配)' },
|
|
84
203
|
status: { type: 'array', items: { type: 'string', enum: [...EMPLOYEE_STATUS] }, description: '默认 ["normal"] 在职;["6"] 离职。不能单独作为收敛条件' },
|
|
204
|
+
full: { type: 'boolean', description: '默认 false:每行只回人员信息(id/name/department/position/statusCode/link);true:回完整行(另含 userid/cid/mobile/departmentId/deptLink)' },
|
|
85
205
|
...SCHEMA_PAGE,
|
|
86
206
|
}),
|
|
87
207
|
handler: async (args, { host }) => {
|
|
@@ -117,11 +237,13 @@ const employeeQuery = {
|
|
|
117
237
|
body.pageSize = pageSize;
|
|
118
238
|
const session = await host.getSession();
|
|
119
239
|
const baseUrl = session.baseUrl.replace(/\/+$/, '');
|
|
120
|
-
|
|
240
|
+
// 不请求 count 接口(省一次 HTTP 往返);data 接口的 count 字段不可靠(恒 0),
|
|
241
|
+
// 用返回行数作为 total。收敛条件查询结果通常 < pageSize,行数即总数。
|
|
121
242
|
const dataResp = await host.request({ method: 'POST', path: '/api/hrm/common/browser/data/resource', body });
|
|
122
|
-
const total = Number(countResp?.data?.total ?? 0);
|
|
123
243
|
const rawRows = dataResp?.data?.data ?? [];
|
|
124
|
-
const
|
|
244
|
+
const total = rawRows.length;
|
|
245
|
+
// 默认只回人员信息(id/name/department/position/statusCode/link);完整行传 full: true。
|
|
246
|
+
const rows = rawRows.map((r) => (args.full === true ? formatMember(r, baseUrl) : formatQueryRowBrief(r, baseUrl)));
|
|
125
247
|
const lookingNormal = statusText.includes('normal');
|
|
126
248
|
const hint = lookingNormal && rows.length === 0
|
|
127
249
|
? `在职员工未查到「${value}」;若为离职人员,请用 status=["6"] 再查`
|
|
@@ -170,18 +292,30 @@ const employeeDetail = {
|
|
|
170
292
|
resource: 'employee',
|
|
171
293
|
method: 'employee.detail',
|
|
172
294
|
action: 'hrm.employee.detail',
|
|
173
|
-
summary: '按人员 id
|
|
295
|
+
summary: '按人员 id 取人员卡片(默认只回核心业务字段;full=true 回完整卡片 85 个顶层键)',
|
|
174
296
|
risk: READ,
|
|
175
|
-
inputSchema: schemaOf({
|
|
297
|
+
inputSchema: schemaOf({
|
|
298
|
+
id: SCHEMA_ID,
|
|
299
|
+
full: { type: 'boolean', description: '默认 false:只回核心业务字段(姓名/工号/组织/岗位/上级/助理/联系方式/办公地点等,体积约为完整卡片的 1/5);true:回完整人员卡片(含 password、last_android、hideFieldList、setting、*_admin 等登录/管理元数据)' },
|
|
300
|
+
}, { required: ['id'] }),
|
|
176
301
|
handler: async (args, { host }) => {
|
|
177
302
|
const id = String(args.id).trim();
|
|
178
303
|
if (!id)
|
|
179
304
|
throw hrmValidationError('id_required', 'id 必填');
|
|
180
|
-
const resp = await host.request({ method: 'GET', path: '/api/hrm/profile/get', query: { id } });
|
|
181
|
-
const data = resp?.data ?? {};
|
|
182
305
|
const session = await host.getSession();
|
|
183
306
|
const baseUrl = session.baseUrl.replace(/\/+$/, '');
|
|
184
|
-
|
|
307
|
+
// readOnly=true 必需:非只读且非本人/非基础信息管理员时服务端会拒绝(checkCardHasRight)。
|
|
308
|
+
const resp = await host.request({
|
|
309
|
+
method: 'GET',
|
|
310
|
+
path: `/api/hrm/card/get/${id}`,
|
|
311
|
+
query: { id, readOnly: true },
|
|
312
|
+
headers: { Referer: `${baseUrl}/sp/hrm/profileInfo/${id}` },
|
|
313
|
+
});
|
|
314
|
+
// 第一层:只提取 data.data 的业务字段,砍掉 groups/layout/items 等前端表单元数据(几千行)。
|
|
315
|
+
const raw = resp?.data?.data ?? {};
|
|
316
|
+
// 第二层:默认再投影成核心字段(85 键/~5.3KB → 26 键/~1.2KB);full=true 时保留完整卡片。
|
|
317
|
+
const payload = args.full === true ? raw : pickDetailCore(raw);
|
|
318
|
+
return hrmSuccess('hrm.employee.detail', { ...payload, id, link: personLink(baseUrl, id) });
|
|
185
319
|
},
|
|
186
320
|
};
|
|
187
321
|
const subordinateMine = {
|
|
@@ -189,10 +323,11 @@ const subordinateMine = {
|
|
|
189
323
|
resource: 'employee',
|
|
190
324
|
method: 'subordinate.mine',
|
|
191
325
|
action: 'hrm.subordinate.mine',
|
|
192
|
-
summary: '查当前登录人的下属(all 所有/direct 直接/indirect
|
|
326
|
+
summary: '查当前登录人的下属(all 所有/direct 直接/indirect 间接),基于当前会话身份;默认每行只回 id+name',
|
|
193
327
|
risk: READ,
|
|
194
328
|
inputSchema: schemaOf({
|
|
195
329
|
scope: { type: 'string', enum: [...SUBORDINATE_SCOPE], description: '默认 all;direct 直接 / indirect 间接 / all 所有' },
|
|
330
|
+
full: { type: 'boolean', description: '默认 false:每行只回 id/name/link;true:回完整行(部门/岗位/手机/状态等)' },
|
|
196
331
|
...SCHEMA_PAGE,
|
|
197
332
|
}),
|
|
198
333
|
handler: async (args, { host }) => {
|
|
@@ -217,11 +352,13 @@ const subordinateMine = {
|
|
|
217
352
|
body.pageSize = pageSize;
|
|
218
353
|
const session = await host.getSession();
|
|
219
354
|
const baseUrl = session.baseUrl.replace(/\/+$/, '');
|
|
220
|
-
|
|
355
|
+
// 不请求 count 接口(省一次 HTTP 往返);data 接口的 count 字段不可靠(恒 0),
|
|
356
|
+
// 用返回行数作为 total。收敛条件查询结果通常 < pageSize,行数即总数。
|
|
221
357
|
const dataResp = await host.request({ method: 'POST', path: '/api/hrm/common/browser/data/resource', body });
|
|
222
|
-
const total = Number(countResp?.data?.total ?? 0);
|
|
223
358
|
const rawRows = dataResp?.data?.data ?? [];
|
|
224
|
-
const
|
|
359
|
+
const total = rawRows.length;
|
|
360
|
+
// 默认只回 id + name(+ link);需要部门/岗位/手机等完整行时传 full: true。
|
|
361
|
+
const rows = rawRows.map((r) => (args.full === true ? formatMember(r, baseUrl) : formatPersonBrief(r, baseUrl)));
|
|
225
362
|
return hrmSuccess('hrm.subordinate.mine', { rows, total, scope }, { current: current ?? 1, pageSize: pageSize ?? 200 });
|
|
226
363
|
},
|
|
227
364
|
};
|
|
@@ -230,13 +367,14 @@ const subordinateById = {
|
|
|
230
367
|
resource: 'employee',
|
|
231
368
|
method: 'subordinate.by-id',
|
|
232
369
|
action: 'hrm.subordinate.by-id',
|
|
233
|
-
summary: '按目标人员 id
|
|
370
|
+
summary: '按目标人员 id 查其直接/间接下属(无需切换会话);默认每行只回 id+name;所有下属 = direct + indirect 两次并集',
|
|
234
371
|
risk: READ,
|
|
235
372
|
inputSchema: schemaOf({
|
|
236
373
|
id: SCHEMA_ID,
|
|
237
374
|
scope: { type: 'string', enum: [...SUBORDINATE_BYID_SCOPE], description: '默认 direct;direct 直接 / indirect 间接' },
|
|
238
|
-
|
|
239
|
-
|
|
375
|
+
full: { type: 'boolean', description: '默认 false:每行只回 id/name/link;true:回完整行(部门/岗位/上级/电话等)' },
|
|
376
|
+
pageNo: { ...SCHEMA_ID, description: '页码,从 1 开始。⚠️ 本操作用 pageNo(不是 current)' },
|
|
377
|
+
pageSize: { ...SCHEMA_ID, description: '每页条数,默认 10' },
|
|
240
378
|
}, { required: ['id'] }),
|
|
241
379
|
handler: async (args, { host }) => {
|
|
242
380
|
const id = String(args.id).trim();
|
|
@@ -258,18 +396,8 @@ const subordinateById = {
|
|
|
258
396
|
});
|
|
259
397
|
const page = resp?.data?.page ?? {};
|
|
260
398
|
const rawRows = Array.isArray(page.result) ? page.result : [];
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
name: r.name ?? r.username,
|
|
264
|
-
pinyin: r.pinyin,
|
|
265
|
-
telephone: r.telephone,
|
|
266
|
-
sex: r.sex,
|
|
267
|
-
personnelStatus: r.personnelStatus,
|
|
268
|
-
department: r.department,
|
|
269
|
-
position: r.position,
|
|
270
|
-
superior: r.superior,
|
|
271
|
-
link: personLink(baseUrl, r.id ?? r.employeeId),
|
|
272
|
-
}));
|
|
399
|
+
// 默认只回 id + name(+ link);需要部门/岗位/上级/电话等完整行时传 full: true。
|
|
400
|
+
const rows = rawRows.map((r) => (args.full === true ? formatSubordinateFull(r, baseUrl) : formatPersonBrief(r, baseUrl)));
|
|
273
401
|
return hrmSuccess('hrm.subordinate.by-id', {
|
|
274
402
|
rows,
|
|
275
403
|
total: Number(page.totalCount ?? rows.length),
|
|
@@ -288,10 +416,11 @@ const deptMembers = {
|
|
|
288
416
|
resource: 'employee',
|
|
289
417
|
method: 'dept.members',
|
|
290
418
|
action: 'hrm.dept.members',
|
|
291
|
-
summary: '查某部门/分部下的直接成员(部门名或 id;部门名需唯一,重名改用 id
|
|
419
|
+
summary: '查某部门/分部下的直接成员(部门名或 id;部门名需唯一,重名改用 id);默认每行只回 id+name',
|
|
292
420
|
risk: READ,
|
|
293
421
|
inputSchema: schemaOf({
|
|
294
422
|
dept: { type: 'string', description: '部门/分部名称(需唯一)或组织节点 id' },
|
|
423
|
+
full: { type: 'boolean', description: '默认 false:每行只回 id/name/link;true:回完整行(部门/岗位/手机/状态/部门卡片链接等)' },
|
|
295
424
|
...SCHEMA_PAGE,
|
|
296
425
|
}, { required: ['dept'] }),
|
|
297
426
|
handler: async (args, { host }) => {
|
|
@@ -324,7 +453,8 @@ const deptMembers = {
|
|
|
324
453
|
const rawRows = resp?.data?.data ?? [];
|
|
325
454
|
const session = await host.getSession();
|
|
326
455
|
const baseUrl = session.baseUrl.replace(/\/+$/, '');
|
|
327
|
-
|
|
456
|
+
// 默认只回 id + name(+ link);需要部门/岗位/手机/状态等完整行时传 full: true。
|
|
457
|
+
const rows = rawRows.map((r) => (args.full === true ? formatMember(r, baseUrl) : formatPersonBrief(r, baseUrl)));
|
|
328
458
|
return hrmSuccess('hrm.dept.members', { dept: { id: deptId }, rows, total: rows.length }, { current: current ?? 1, pageSize: pageSize ?? 200 });
|
|
329
459
|
},
|
|
330
460
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hrmValidationError } from '../errors.js';
|
|
2
2
|
import { READ, SCHEMA_ID, SCHEMA_PAGE, hrmSuccess, schemaOf, } from './types.js';
|
|
3
|
-
import { browserBaseBody, orgLink, toNumber, toPositiveInt, toStringIdList, } from './shared.js';
|
|
3
|
+
import { browserBaseBody, orgLink, pickDefined, toNumber, toPositiveInt, toStringIdList, } from './shared.js';
|
|
4
4
|
const ORG_TYPES = ['subcompany', 'department'];
|
|
5
5
|
/**
|
|
6
6
|
* 组织浏览接口(分部 subcompany / 部门 department)统一入口。
|
|
@@ -100,8 +100,22 @@ const orgDetail = {
|
|
|
100
100
|
query: { id, readOnly: true, includeSub: true, quote: true, isMobile: false },
|
|
101
101
|
headers: { Referer: `${baseUrl}/sp/hrm/orgCard/${id}` },
|
|
102
102
|
});
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
// 只提取 data.data 的业务字段,砍掉 groups/layout/items 等前端表单元数据(几千行)。
|
|
104
|
+
const raw = resp?.data?.data ?? {};
|
|
105
|
+
const parentName = Array.isArray(raw.parent) && raw.parent[0]?.name ? String(raw.parent[0].name) : '';
|
|
106
|
+
const subcompanyName = Array.isArray(raw.subcompany) && raw.subcompany[0]?.name ? String(raw.subcompany[0].name) : '';
|
|
107
|
+
return hrmSuccess('hrm.org.detail', {
|
|
108
|
+
id,
|
|
109
|
+
name: raw.name,
|
|
110
|
+
type: raw.type,
|
|
111
|
+
code: raw.code,
|
|
112
|
+
fullPath: raw.fullPath,
|
|
113
|
+
status: raw.status,
|
|
114
|
+
...(parentName ? { parentName } : {}),
|
|
115
|
+
...(subcompanyName ? { subcompanyName } : {}),
|
|
116
|
+
...pickDefined(raw, ['description', 'remark']),
|
|
117
|
+
link: orgLink(baseUrl, id),
|
|
118
|
+
});
|
|
105
119
|
},
|
|
106
120
|
};
|
|
107
121
|
export const hrmOrgOperations = [
|