weaver-work-cli 0.1.6 → 0.1.7
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 +14 -9
- package/dist/cmd/skills/index.js +60 -0
- package/dist/internal/e10/auth/commands.js +44 -31
- package/dist/internal/e10/auth/session.js +44 -3
- package/dist/internal/e10/auth/xiaoe.js +363 -11
- package/dist/internal/e10/context.js +4 -2
- package/dist/internal/skills/detector.js +42 -0
- package/dist/internal/skills/install.js +50 -3
- package/dist/shortcuts/archive/render/search-format.js +12 -3
- package/dist/shortcuts/ebuilder-form/continuation.js +70 -0
- package/dist/shortcuts/ebuilder-form/errors.js +54 -0
- package/dist/shortcuts/ebuilder-form/host.js +1238 -0
- package/dist/shortcuts/ebuilder-form/index.js +108 -0
- package/dist/shortcuts/ebuilder-form/manifest.js +347 -0
- package/dist/shortcuts/ebuilder-form/operations/read.js +157 -0
- package/dist/shortcuts/ebuilder-form/operations/registry.js +56 -0
- package/dist/shortcuts/ebuilder-form/operations/shared.js +132 -0
- package/dist/shortcuts/ebuilder-form/operations/types.js +19 -0
- package/dist/shortcuts/ebuilder-form/operations/write.js +277 -0
- package/dist/shortcuts/ebuilder-form/operations.js +8 -0
- package/dist/shortcuts/ehr/operations/salary.js +2 -140
- package/dist/shortcuts/im/continuation.js +70 -0
- package/dist/shortcuts/{yimiaoban → im}/errors.js +14 -14
- package/dist/shortcuts/{yimiaoban → im}/host.js +26 -26
- package/dist/shortcuts/{yimiaoban → im}/index.js +42 -42
- package/dist/shortcuts/{yimiaoban → im}/manifest.js +7 -6
- package/dist/shortcuts/{yimiaoban → im}/operations/file-user.js +54 -41
- package/dist/shortcuts/{yimiaoban → im}/operations/group-read.js +46 -44
- package/dist/shortcuts/{yimiaoban → im}/operations/group-write.js +113 -66
- package/dist/shortcuts/{yimiaoban → im}/operations/msg-sync.js +164 -141
- package/dist/shortcuts/{yimiaoban → im}/operations/msg-write.js +47 -47
- package/dist/shortcuts/{yimiaoban → im}/operations/person.js +10 -10
- package/dist/shortcuts/{yimiaoban → im}/operations/registry.js +10 -8
- package/dist/shortcuts/{yimiaoban → im}/operations/session.js +82 -83
- package/dist/shortcuts/im/operations/shared.js +442 -0
- package/dist/shortcuts/{yimiaoban → im}/operations/types.js +1 -1
- package/dist/shortcuts/im/operations/user-remind.js +201 -0
- package/dist/shortcuts/{yimiaoban → im}/operations/write-util.js +7 -7
- package/dist/shortcuts/{yimiaoban → im}/operations.js +3 -3
- package/dist/shortcuts/index.js +10 -2
- package/dist/shortcuts/invoice/manifest.js +113 -10
- package/dist/shortcuts/invoice/operations/import.js +39 -14
- package/dist/shortcuts/invoice/operations/reim.js +38 -18
- package/dist/shortcuts/invoice/operations/shared.js +35 -6
- package/dist/shortcuts/jiuchuanhui/manifest.js +3 -3
- package/dist/shortcuts/jiuchuanhui/operations/registry.js +2 -2
- package/dist/shortcuts/{yimiaoban → okr}/continuation.js +10 -10
- package/dist/shortcuts/okr/errors.js +70 -0
- package/dist/shortcuts/okr/host.js +169 -0
- package/dist/shortcuts/okr/index.js +155 -0
- package/dist/shortcuts/okr/manifest.js +70 -0
- package/dist/shortcuts/okr/operations/link.js +305 -0
- package/dist/shortcuts/okr/operations/read.js +481 -0
- package/dist/shortcuts/okr/operations/registry.js +31 -0
- package/dist/shortcuts/okr/operations/shared.js +381 -0
- package/dist/shortcuts/okr/operations/types.js +88 -0
- package/dist/shortcuts/okr/operations/write.js +838 -0
- package/dist/shortcuts/okr/operations.js +8 -0
- package/dist/shortcuts/project/continuation.js +70 -0
- package/dist/shortcuts/project/errors.js +53 -0
- package/dist/shortcuts/project/host.js +160 -0
- package/dist/shortcuts/project/index.js +106 -0
- package/dist/shortcuts/project/manifest.js +36 -0
- package/dist/shortcuts/project/operations/read.js +551 -0
- package/dist/shortcuts/project/operations/registry.js +31 -0
- package/dist/shortcuts/project/operations/shared.js +68 -0
- package/dist/shortcuts/project/operations/types.js +39 -0
- package/dist/shortcuts/project/operations/write.js +266 -0
- package/dist/shortcuts/project/operations.js +8 -0
- package/dist/shortcuts/workflow/continuation.js +508 -0
- package/dist/shortcuts/workflow/endpoints.js +149 -0
- package/dist/shortcuts/workflow/errors.js +112 -0
- package/dist/shortcuts/workflow/host.js +224 -0
- package/dist/shortcuts/workflow/index.js +116 -0
- package/dist/shortcuts/workflow/manifest.js +30 -0
- package/dist/shortcuts/workflow/operations/form.js +4159 -0
- package/dist/shortcuts/workflow/operations/read.js +3778 -0
- package/dist/shortcuts/workflow/operations/registry.js +33 -0
- package/dist/shortcuts/workflow/operations/shared.js +167 -0
- package/dist/shortcuts/workflow/operations/types.js +44 -0
- package/dist/shortcuts/workflow/operations/write.js +1991 -0
- package/dist/shortcuts/workflow/operations.js +119 -0
- package/dist/shortcuts/workflow/state.js +182 -0
- package/docs/SKILL.md +5 -3
- package/docs/_catalog.md +10 -1
- package/docs/agent-invoice.md +2 -2
- package/docs/agent-skill-install.md +2 -2
- package/docs/e10-auth.md +3 -1
- package/docs/ebuilder-form.md +38 -0
- package/docs/im.md +104 -0
- package/docs/invoice.md +5 -5
- package/docs/okr.md +122 -0
- package/docs/operation-manual.md +10 -7
- package/docs/project.md +69 -0
- package/docs/skill-review-2026-09-19.md +68 -0
- package/package.json +3 -2
- package/scripts/package-skill.mjs +38 -11
- package/scripts/sync-connector-skills.mjs +172 -0
- package/skill-template/business-info.json +357 -16
- package/skill-template/domains/ebuilder-form.md +5 -0
- package/skill-template/domains/okr.md +18 -0
- package/skill-template/domains/shijingran.md +3 -0
- package/skill-template/domains/skill-maker.md +4 -4
- package/skill-template/domains/workflow.md +22 -0
- package/skill-template/domains/yepiaotong.md +4 -2
- package/skill-template/domains/yimiaoban.md +13 -10
- package/skill-template/product.json +5 -0
- package/skill-template/skill-template.md +1 -0
- package/skills/weaver-e10-calendar/SKILL.md +5 -1
- package/skills/weaver-e10-calendar/references/calendar-query.md +7 -0
- package/skills/weaver-e10-calendar/references/source-manifest.json +314 -32
- package/skills/weaver-e10-ebuilder-form/SKILL.md +123 -0
- package/skills/weaver-e10-ebuilder-form/references/context-routing.md +90 -0
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +86 -0
- package/skills/weaver-e10-ebuilder-form/references/disabled-capabilities.md +50 -0
- package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +100 -0
- package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +91 -0
- package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +74 -0
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +86 -0
- package/skills/weaver-e10-ebuilder-form/references/source-manifest.json +107 -0
- package/skills/weaver-e10-esb/SKILL.md +1 -1
- package/skills/weaver-e10-esb/references/source-manifest.json +15 -14
- package/skills/weaver-e10-hrm/SKILL.md +1 -1
- package/skills/weaver-e10-hrm/references/source-manifest.json +357 -38
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +22 -4
- package/skills/weaver-e10-jiuchuanhui/references/clue.md +4 -2
- package/skills/weaver-e10-jiuchuanhui/references/contact.md +1 -1
- package/skills/weaver-e10-jiuchuanhui/references/customer.md +3 -3
- package/skills/weaver-e10-jiuchuanhui/references/disabled-capabilities.md +1 -1
- package/skills/weaver-e10-jiuchuanhui/references/discovery-config.md +10 -10
- package/skills/weaver-e10-jiuchuanhui/references/general-helpers.md +3 -3
- package/skills/weaver-e10-jiuchuanhui/references/sale.md +7 -7
- package/skills/weaver-e10-jiuchuanhui/references/source-manifest.json +3 -3
- package/skills/weaver-e10-jucailin/SKILL.md +2 -2
- package/skills/weaver-e10-mail/SKILL.md +22 -6
- package/skills/weaver-e10-meeting/SKILL.md +22 -1
- package/skills/weaver-e10-okr/SKILL.md +139 -0
- package/skills/weaver-e10-okr/references/okr-align.md +59 -0
- package/skills/weaver-e10-okr/references/okr-keyresult.md +68 -0
- package/skills/weaver-e10-okr/references/okr-link-routing.md +67 -0
- package/skills/weaver-e10-okr/references/okr-query.md +105 -0
- package/skills/weaver-e10-okr/references/okr-write.md +94 -0
- package/skills/weaver-e10-okr/references/source-manifest.json +672 -0
- package/skills/weaver-e10-plan/SKILL.md +3 -3
- package/skills/weaver-e10-plan/references/plan-report-write.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +20 -20
- package/skills/weaver-e10-qiyecheng/SKILL.md +21 -1
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +17 -17
- package/skills/weaver-e10-shared/SKILL.md +79 -11
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +41 -8
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +78 -0
- package/skills/weaver-e10-shared/references/weaver-e10-installation.md +39 -0
- package/skills/weaver-e10-shijingran/SKILL.md +83 -0
- package/skills/weaver-e10-shijingran/references/field-resolution.md +105 -0
- package/skills/weaver-e10-shijingran/references/operations.md +154 -0
- package/skills/weaver-e10-shijingran/references/source-manifest.json +1380 -0
- package/skills/weaver-e10-shijingran/references/write-operations.md +90 -0
- package/skills/weaver-e10-skill-maker/SKILL.md +26 -9
- package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +39 -8
- package/skills/weaver-e10-skill-maker/references/detector-validation.md +25 -8
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +66 -5
- package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +86 -0
- package/skills/weaver-e10-skill-maker/references/source-update-detection.md +59 -5
- package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +16 -8
- package/skills/weaver-e10-wenshuding/SKILL.md +28 -4
- package/skills/weaver-e10-wenshuding/references/archive-search.md +14 -0
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +27 -26
- package/skills/weaver-e10-workflow/SKILL.md +250 -0
- package/skills/weaver-e10-workflow/references/source-manifest.json +1062 -0
- package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +96 -0
- package/skills/weaver-e10-workflow/references/workflow-batch.md +275 -0
- package/skills/weaver-e10-workflow/references/workflow-create.md +182 -0
- package/skills/weaver-e10-workflow/references/workflow-edit.md +106 -0
- package/skills/weaver-e10-workflow/references/workflow-memory.md +165 -0
- package/skills/weaver-e10-workflow/references/workflow-operate.md +153 -0
- package/skills/weaver-e10-workflow/references/workflow-query.md +821 -0
- package/skills/weaver-e10-workflow/references/workflow-share.md +117 -0
- package/skills/weaver-e10-workflow/references/workflow-view.md +189 -0
- package/skills/weaver-e10-yepiaotong/SKILL.md +28 -28
- package/skills/weaver-e10-yepiaotong/references/invoice-disabled-capabilities.md +11 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-import.md +14 -8
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +9 -43
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +8 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +13 -17
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +6 -7
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +2 -2
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +1155 -553
- package/skills/weaver-e10-yimiaoban/SKILL.md +56 -38
- package/skills/weaver-e10-yimiaoban/references/ding-write.md +8 -8
- package/skills/weaver-e10-yimiaoban/references/error-codes.md +16 -0
- package/skills/weaver-e10-yimiaoban/references/field-resolution.md +10 -10
- package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +12 -12
- package/skills/weaver-e10-yimiaoban/references/group-read.md +16 -16
- package/skills/weaver-e10-yimiaoban/references/group-write.md +17 -15
- package/skills/weaver-e10-yimiaoban/references/msg-read.md +21 -17
- package/skills/weaver-e10-yimiaoban/references/msg-write.md +10 -10
- package/skills/weaver-e10-yimiaoban/references/person.md +6 -6
- package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +15 -11
- package/skills/weaver-e10-yimiaoban/references/session-sysmsg.md +11 -10
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +2164 -154
- package/skills/weaver-e10-yimiaoban/references/user-remind.md +75 -0
- package/skills/weaver-e10-ziguanjia/SKILL.md +4 -2
- package/skills/weaver-e10-ziguanjia/references/source-manifest.json +99 -29
- package/tools/weaver-skill-detector-1.0.13/SKILL.md +253 -0
- package/tools/weaver-skill-detector-1.0.13/_meta.json +6 -0
- package/tools/weaver-skill-detector-1.0.13/references/checklist.md +298 -0
- package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +4190 -0
- package/dist/shortcuts/yimiaoban/operations/shared.js +0 -236
- package/docs/yimiaoban.md +0 -86
- package/skills/weaver-e10-calendar/product.json +0 -8
- package/skills/weaver-e10-esb/product.json +0 -8
- package/skills/weaver-e10-hrm/product.json +0 -8
- package/skills/weaver-e10-jiuchuanhui/product.json +0 -8
- package/skills/weaver-e10-jucailin/product.json +0 -8
- package/skills/weaver-e10-mail/product.json +0 -8
- package/skills/weaver-e10-meeting/product.json +0 -8
- package/skills/weaver-e10-plan/product.json +0 -8
- package/skills/weaver-e10-qiyecheng/product.json +0 -8
- package/skills/weaver-e10-skill-maker/product.json +0 -8
- package/skills/weaver-e10-wenshuding/product.json +0 -8
- package/skills/weaver-e10-yepiaotong/product.json +0 -8
- package/skills/weaver-e10-yepiaotong/references/invoice-red.md +0 -261
- package/skills/weaver-e10-yimiaoban/product.json +0 -8
- package/skills/weaver-e10-ziguanjia/product.json +0 -8
- /package/dist/shortcuts/{yimiaoban → im}/operations/msg-parse.js +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { imConfirmationError, imValidationError } from '../errors.js';
|
|
3
|
+
import { imSuccess } from './types.js';
|
|
4
4
|
import { assertNoForbiddenKeys } from './shared.js';
|
|
5
5
|
function fingerprint(value) {
|
|
6
6
|
return createHash('sha256').update(JSON.stringify(value ?? null)).digest('hex').slice(0, 32);
|
|
7
7
|
}
|
|
8
8
|
function requireConfirm(action) {
|
|
9
|
-
throw
|
|
9
|
+
throw imConfirmationError(action, { hint: '重新调用时传入 confirm=true 与 prepare 返回的 continuation' });
|
|
10
10
|
}
|
|
11
11
|
async function verifyLease(continuationToken, action, env) {
|
|
12
12
|
const lease = await env.continuation.verify(continuationToken, action);
|
|
13
13
|
const session = await env.host.getSession();
|
|
14
14
|
if (lease.baseUrl !== session.baseUrl || lease.profile !== session.profile || lease.userId !== session.userId) {
|
|
15
|
-
throw
|
|
15
|
+
throw imValidationError('context_mismatch', '确认上下文已变化(租户或登录账号不同),请重新执行 prepare');
|
|
16
16
|
}
|
|
17
17
|
return lease;
|
|
18
18
|
}
|
|
@@ -30,7 +30,7 @@ export function makeWritePair(options) {
|
|
|
30
30
|
snapshotFingerprint: fingerprint(plan.snapshot),
|
|
31
31
|
plan: plan.snapshot,
|
|
32
32
|
});
|
|
33
|
-
return
|
|
33
|
+
return imSuccess(`${options.action}.prepare`, {
|
|
34
34
|
summary: plan.summary,
|
|
35
35
|
continuation: token,
|
|
36
36
|
hint: '确认无误后调用 ' + options.action + '.apply,传 confirm:true 与 continuation',
|
|
@@ -43,10 +43,10 @@ export function makeWritePair(options) {
|
|
|
43
43
|
const lease = await verifyLease(String(args.continuation ?? ''), options.action, env);
|
|
44
44
|
const plan = lease.plan;
|
|
45
45
|
if (lease.snapshotFingerprint !== fingerprint(plan)) {
|
|
46
|
-
throw
|
|
46
|
+
throw imValidationError('target_changed', '写入目标/内容与 prepare 时不一致,请重新执行 prepare');
|
|
47
47
|
}
|
|
48
48
|
const data = await options.execute(plan, env);
|
|
49
|
-
return
|
|
49
|
+
return imSuccess(`${options.action}.apply`, data);
|
|
50
50
|
},
|
|
51
51
|
};
|
|
52
52
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getImOperation } from './operations/registry.js';
|
|
2
2
|
import { asObject, assertNoForbiddenKeys } from './operations/shared.js';
|
|
3
|
-
export async function
|
|
4
|
-
const definition =
|
|
3
|
+
export async function executeImOperation(operation, rawInput, context) {
|
|
4
|
+
const definition = getImOperation(operation);
|
|
5
5
|
const input = asObject(rawInput);
|
|
6
6
|
assertNoForbiddenKeys(input);
|
|
7
7
|
return definition.handler(input, context);
|
package/dist/shortcuts/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { projectShortcut } from './project/index.js';
|
|
1
2
|
import { archiveShortcut } from './archive/index.js';
|
|
2
3
|
import { calendarShortcut } from './calendar/index.js';
|
|
3
4
|
import { ehrShortcut } from './ehr/index.js';
|
|
@@ -7,10 +8,13 @@ import { hrmShortcut } from './hrm/index.js';
|
|
|
7
8
|
import { invoiceShortcut } from './invoice/index.js';
|
|
8
9
|
import { mailShortcut } from './mail/index.js';
|
|
9
10
|
import { meetingShortcut } from './meeting/index.js';
|
|
11
|
+
import { okrShortcut } from './okr/index.js';
|
|
10
12
|
import { planShortcut } from './plan/index.js';
|
|
11
13
|
import { jiuchuanhuiShortcut } from './jiuchuanhui/index.js';
|
|
12
14
|
import { assetShortcut } from './asset/index.js';
|
|
13
|
-
import {
|
|
15
|
+
import { ebuilderFormShortcut } from './ebuilder-form/index.js';
|
|
16
|
+
import { imShortcut } from './im/index.js';
|
|
17
|
+
import { workflowShortcut } from './workflow/index.js';
|
|
14
18
|
export const builtinShortcuts = [
|
|
15
19
|
invoiceShortcut,
|
|
16
20
|
ehrShortcut,
|
|
@@ -20,9 +24,13 @@ export const builtinShortcuts = [
|
|
|
20
24
|
hrmShortcut,
|
|
21
25
|
mailShortcut,
|
|
22
26
|
meetingShortcut,
|
|
27
|
+
okrShortcut,
|
|
23
28
|
planShortcut,
|
|
24
29
|
esbShortcut,
|
|
30
|
+
ebuilderFormShortcut,
|
|
25
31
|
jiuchuanhuiShortcut,
|
|
26
32
|
assetShortcut,
|
|
27
|
-
|
|
33
|
+
imShortcut,
|
|
34
|
+
projectShortcut,
|
|
35
|
+
workflowShortcut
|
|
28
36
|
];
|
|
@@ -52,7 +52,96 @@ const listProperties = {
|
|
|
52
52
|
};
|
|
53
53
|
const positiveId = { type: 'string', pattern: '^[1-9]\\d*$' };
|
|
54
54
|
const positiveIdArray = { type: 'array', minItems: 1, maxItems: 100, items: positiveId };
|
|
55
|
+
const jsonObject = { type: 'object', additionalProperties: true };
|
|
55
56
|
const requestPayload = { type: 'object', minProperties: 1 };
|
|
57
|
+
const reimFieldValue = {
|
|
58
|
+
type: 'object',
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
properties: {
|
|
61
|
+
fieldId: { type: 'string', minLength: 1 },
|
|
62
|
+
fieldName: { type: 'string' },
|
|
63
|
+
value: {},
|
|
64
|
+
optionValue: {},
|
|
65
|
+
unable_to_fill: { type: 'boolean' },
|
|
66
|
+
need_search: { type: 'boolean' },
|
|
67
|
+
},
|
|
68
|
+
required: ['fieldId'],
|
|
69
|
+
};
|
|
70
|
+
const reimNeedUserConfirm = {
|
|
71
|
+
type: 'object',
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
properties: {
|
|
74
|
+
fieldId: { type: 'string', minLength: 1 },
|
|
75
|
+
fieldName: { type: 'string' },
|
|
76
|
+
dataIndex: { type: ['integer', 'null'], minimum: 1 },
|
|
77
|
+
reason: { type: 'string', minLength: 1 },
|
|
78
|
+
candidates: {
|
|
79
|
+
type: 'array',
|
|
80
|
+
items: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
additionalProperties: true,
|
|
83
|
+
properties: {
|
|
84
|
+
id: { type: 'string', minLength: 1 },
|
|
85
|
+
name: { type: 'string' },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ['fieldId', 'reason'],
|
|
91
|
+
};
|
|
92
|
+
const reimUnableToFill = {
|
|
93
|
+
type: 'object',
|
|
94
|
+
additionalProperties: false,
|
|
95
|
+
properties: {
|
|
96
|
+
fieldId: { type: 'string', minLength: 1 },
|
|
97
|
+
fieldName: { type: 'string' },
|
|
98
|
+
dataIndex: { type: ['integer', 'null'], minimum: 1 },
|
|
99
|
+
reason: { type: 'string', minLength: 1 },
|
|
100
|
+
},
|
|
101
|
+
required: ['fieldId', 'reason'],
|
|
102
|
+
};
|
|
103
|
+
const reimDetailRow = {
|
|
104
|
+
type: 'object',
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
properties: {
|
|
107
|
+
dataIndex: { type: 'integer', minimum: 1 },
|
|
108
|
+
subFormId: { type: 'string', minLength: 1 },
|
|
109
|
+
invoiceId: { type: 'string', minLength: 1 },
|
|
110
|
+
fields: { type: 'array', items: reimFieldValue },
|
|
111
|
+
},
|
|
112
|
+
required: ['dataIndex', 'subFormId', 'invoiceId', 'fields'],
|
|
113
|
+
};
|
|
114
|
+
const reimFlowCreateProperties = {
|
|
115
|
+
work_flow_id: { type: 'string', minLength: 1, description: '目标报销流程的工作流 ID。' },
|
|
116
|
+
mainFormId: { type: 'string', minLength: 1, description: '报销单主表 formId,来自 invoice.reim.form.structure。' },
|
|
117
|
+
request_name: { type: 'string', minLength: 1, description: '流程标题。' },
|
|
118
|
+
main_fields: { type: 'array', items: reimFieldValue, description: '主表字段值列表。' },
|
|
119
|
+
detail_rows: { type: 'array', minItems: 1, items: reimDetailRow, description: '明细表行数据。' },
|
|
120
|
+
need_user_confirm: { type: 'array', items: reimNeedUserConfirm, description: '需用户确认的字段。' },
|
|
121
|
+
unable_to_fill: { type: 'array', items: reimUnableToFill, description: '无法自动填写的字段。' },
|
|
122
|
+
};
|
|
123
|
+
const reimFlowCreateRequired = ['work_flow_id', 'mainFormId', 'request_name', 'main_fields', 'detail_rows'];
|
|
124
|
+
const reimFlowUpdateProperties = {
|
|
125
|
+
requestId: { type: 'string', minLength: 1, description: '要更新的流程 requestId。' },
|
|
126
|
+
mainFormId: { type: 'string', minLength: 1, description: '报销单主表 formId。' },
|
|
127
|
+
main_fields: { type: 'array', minItems: 1, items: reimFieldValue, description: '需要更新的主表字段列表。' },
|
|
128
|
+
detail_rows: { type: 'array', minItems: 1, items: reimDetailRow, description: '需要更新或新增的明细行数据。' },
|
|
129
|
+
deleteDetailRows: {
|
|
130
|
+
type: 'array',
|
|
131
|
+
minItems: 1,
|
|
132
|
+
items: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
additionalProperties: true,
|
|
135
|
+
properties: {
|
|
136
|
+
detailId: { type: 'string', minLength: 1 },
|
|
137
|
+
dataIndex: { type: 'integer', minimum: 1 },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
description: '需要删除的明细行。',
|
|
141
|
+
},
|
|
142
|
+
need_user_confirm: { type: 'array', items: reimNeedUserConfirm, description: '需用户确认的字段。' },
|
|
143
|
+
unable_to_fill: { type: 'array', items: reimUnableToFill, description: '无法自动填写的字段。' },
|
|
144
|
+
};
|
|
56
145
|
export const invoiceOperations = [
|
|
57
146
|
{
|
|
58
147
|
name: 'invoice.list',
|
|
@@ -133,6 +222,7 @@ export const invoiceOperations = [
|
|
|
133
222
|
additionalProperties: false,
|
|
134
223
|
properties: {
|
|
135
224
|
file: { type: 'string', minLength: 1 },
|
|
225
|
+
url: { type: 'string', format: 'uri' },
|
|
136
226
|
validate: {
|
|
137
227
|
type: 'boolean',
|
|
138
228
|
description: '是否在导入时请求服务端查验。正常上传发票流程默认应传 true;只有用户明确要求跳过查验时才传 false。',
|
|
@@ -141,8 +231,12 @@ export const invoiceOperations = [
|
|
|
141
231
|
type: 'boolean',
|
|
142
232
|
description: '是否同步到 OA。validate=true 时必须为 true;正常导入推荐传 true。',
|
|
143
233
|
},
|
|
234
|
+
fieldKey: { type: 'string', minLength: 1, description: '给成功发票设置的单个字段名;必须与 fieldValue 同时提供。' },
|
|
235
|
+
fieldValue: { type: 'string', description: 'fieldKey 对应字段值;必须与 fieldKey 同时提供。' },
|
|
236
|
+
filedMap: { ...jsonObject, description: '批量设置成功发票的默认字段。保持 JSON 对象输入,CLI 仅在 multipart 提交前序列化。' },
|
|
144
237
|
},
|
|
145
|
-
required: ['
|
|
238
|
+
required: ['validate', 'syncToOa'],
|
|
239
|
+
oneOf: [{ required: ['file'] }, { required: ['url'] }],
|
|
146
240
|
},
|
|
147
241
|
},
|
|
148
242
|
{
|
|
@@ -153,10 +247,17 @@ export const invoiceOperations = [
|
|
|
153
247
|
additionalProperties: false,
|
|
154
248
|
properties: {
|
|
155
249
|
file: { type: 'string', minLength: 1 },
|
|
250
|
+
url: { type: 'string', format: 'uri' },
|
|
251
|
+
validate: { type: 'boolean' },
|
|
252
|
+
syncToOa: { type: 'boolean' },
|
|
253
|
+
fieldKey: { type: 'string', minLength: 1 },
|
|
254
|
+
fieldValue: { type: 'string' },
|
|
255
|
+
filedMap: { ...jsonObject, description: '批量设置成功发票的默认字段。保持 JSON 对象输入,CLI 仅在 multipart 提交前序列化。' },
|
|
156
256
|
continuation: { type: 'string', minLength: 1 },
|
|
157
257
|
confirm: { const: true },
|
|
158
258
|
},
|
|
159
|
-
required: ['
|
|
259
|
+
required: ['continuation', 'confirm'],
|
|
260
|
+
oneOf: [{ required: ['file'] }, { required: ['url'] }],
|
|
160
261
|
},
|
|
161
262
|
},
|
|
162
263
|
{
|
|
@@ -391,8 +492,8 @@ export const invoiceOperations = [
|
|
|
391
492
|
inputSchema: {
|
|
392
493
|
type: 'object',
|
|
393
494
|
additionalProperties: false,
|
|
394
|
-
properties:
|
|
395
|
-
required:
|
|
495
|
+
properties: reimFlowCreateProperties,
|
|
496
|
+
required: reimFlowCreateRequired,
|
|
396
497
|
},
|
|
397
498
|
},
|
|
398
499
|
{
|
|
@@ -402,11 +503,11 @@ export const invoiceOperations = [
|
|
|
402
503
|
type: 'object',
|
|
403
504
|
additionalProperties: false,
|
|
404
505
|
properties: {
|
|
405
|
-
|
|
506
|
+
...reimFlowCreateProperties,
|
|
406
507
|
continuation: { type: 'string', minLength: 1 },
|
|
407
508
|
confirm: { const: true },
|
|
408
509
|
},
|
|
409
|
-
required: [
|
|
510
|
+
required: [...reimFlowCreateRequired, 'continuation', 'confirm'],
|
|
410
511
|
},
|
|
411
512
|
},
|
|
412
513
|
{
|
|
@@ -416,8 +517,9 @@ export const invoiceOperations = [
|
|
|
416
517
|
inputSchema: {
|
|
417
518
|
type: 'object',
|
|
418
519
|
additionalProperties: false,
|
|
419
|
-
properties:
|
|
420
|
-
required: ['
|
|
520
|
+
properties: reimFlowUpdateProperties,
|
|
521
|
+
required: ['requestId', 'mainFormId'],
|
|
522
|
+
anyOf: [{ required: ['main_fields'] }, { required: ['detail_rows'] }, { required: ['deleteDetailRows'] }],
|
|
421
523
|
},
|
|
422
524
|
},
|
|
423
525
|
{
|
|
@@ -427,11 +529,12 @@ export const invoiceOperations = [
|
|
|
427
529
|
type: 'object',
|
|
428
530
|
additionalProperties: false,
|
|
429
531
|
properties: {
|
|
430
|
-
|
|
532
|
+
...reimFlowUpdateProperties,
|
|
431
533
|
continuation: { type: 'string', minLength: 1 },
|
|
432
534
|
confirm: { const: true },
|
|
433
535
|
},
|
|
434
|
-
required: ['
|
|
536
|
+
required: ['requestId', 'mainFormId', 'continuation', 'confirm'],
|
|
537
|
+
anyOf: [{ required: ['main_fields'] }, { required: ['detail_rows'] }, { required: ['deleteDetailRows'] }],
|
|
435
538
|
},
|
|
436
539
|
},
|
|
437
540
|
{
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { InvoiceCliError, invoiceConfirmationError, invoicePartialError, invoiceValidationError, isInvoiceNetworkError } from '../errors.js';
|
|
2
|
-
import { asPositiveString, detailInfo, getDetail,
|
|
2
|
+
import { asPositiveString, detailInfo, getDetail, importOptions, importSource, objectFingerprint, preview, sessionMatches, workflow, } from './shared.js';
|
|
3
3
|
export class InvoiceImportPrepareOperation {
|
|
4
4
|
name = 'invoice.import.prepare';
|
|
5
5
|
async execute(input, { host, continuation }) {
|
|
6
6
|
const options = importOptions(input);
|
|
7
|
-
const prepared = await
|
|
7
|
+
const prepared = await importSource(input);
|
|
8
8
|
const session = await host.getSession();
|
|
9
9
|
const filePreview = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
source: prepared.kind,
|
|
11
|
+
...(prepared.kind === 'file'
|
|
12
|
+
? { name: prepared.file.name, size: prepared.size, sha256: prepared.sha256 }
|
|
13
|
+
: { url: prepared.url }),
|
|
13
14
|
validate: options.validate,
|
|
14
15
|
validationSkipped: !options.validate,
|
|
15
16
|
syncToOa: options.syncToOa,
|
|
17
|
+
fieldKey: options.fieldKey,
|
|
18
|
+
filedMap: options.filedMap,
|
|
16
19
|
targetScope: 'personal',
|
|
17
20
|
};
|
|
18
21
|
const continuationToken = await continuation.issue({
|
|
@@ -21,7 +24,8 @@ export class InvoiceImportPrepareOperation {
|
|
|
21
24
|
baseUrl: session.baseUrl,
|
|
22
25
|
profile: session.profile,
|
|
23
26
|
userId: session.userId,
|
|
24
|
-
|
|
27
|
+
sourceKind: prepared.kind,
|
|
28
|
+
sourceFingerprint: prepared.fingerprint,
|
|
25
29
|
options,
|
|
26
30
|
preview: filePreview,
|
|
27
31
|
});
|
|
@@ -42,24 +46,45 @@ export class InvoiceImportApplyOperation {
|
|
|
42
46
|
if (input.confirm !== true)
|
|
43
47
|
throw invoiceConfirmationError('invoice.import.apply', { hint: '重新调用时传入 confirm=true' });
|
|
44
48
|
const lease = await continuation.verify(input.continuation, 'invoice.import.apply');
|
|
45
|
-
if (!lease.options || typeof lease.options.validate !== 'boolean' || typeof lease.options.syncToOa !== 'boolean' || typeof lease.
|
|
49
|
+
if (!lease.options || typeof lease.options.validate !== 'boolean' || typeof lease.options.syncToOa !== 'boolean' || typeof lease.sourceFingerprint !== 'string') {
|
|
46
50
|
throw invoiceValidationError('continuation_invalid', '导入 continuation 缺少有效文件指纹或选项');
|
|
47
51
|
}
|
|
48
52
|
const session = await host.getSession();
|
|
49
53
|
if (!sessionMatches(lease, session))
|
|
50
54
|
throw invoiceValidationError('context_mismatch', '确认上下文已变化,请重新准备导入');
|
|
51
|
-
const prepared = await
|
|
52
|
-
if (prepared.
|
|
53
|
-
throw invoiceValidationError('target_changed', '
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const prepared = await importSource(input);
|
|
56
|
+
if (prepared.fingerprint !== lease.sourceFingerprint)
|
|
57
|
+
throw invoiceValidationError('target_changed', '导入来源与确认时不一致,请重新准备导入');
|
|
58
|
+
const options = importOptions({
|
|
59
|
+
...input,
|
|
60
|
+
validate: input.validate ?? lease.options.validate,
|
|
61
|
+
syncToOa: input.syncToOa ?? lease.options.syncToOa,
|
|
62
|
+
fieldKey: input.fieldKey ?? lease.options.fieldKey,
|
|
63
|
+
fieldValue: input.fieldValue ?? lease.options.fieldValue,
|
|
64
|
+
filedMap: input.filedMap ?? lease.options.filedMap,
|
|
65
|
+
});
|
|
66
|
+
if (objectFingerprint(options) !== objectFingerprint(lease.options))
|
|
67
|
+
throw invoiceValidationError('target_changed', '导入选项与确认时不一致,请重新准备导入');
|
|
68
|
+
const fields = {
|
|
69
|
+
...prepared.fields,
|
|
70
|
+
allSuccess: lease.options.validate ? '0' : '1',
|
|
71
|
+
};
|
|
72
|
+
if (lease.options.fieldKey !== undefined) {
|
|
73
|
+
fields.fieldKey = lease.options.fieldKey;
|
|
74
|
+
fields.fieldValue = lease.options.fieldValue || '';
|
|
75
|
+
}
|
|
76
|
+
const filedMap = { ...(lease.options.filedMap || {}) };
|
|
77
|
+
if (!lease.options.validate && filedMap.isToOa === undefined) {
|
|
78
|
+
filedMap.isToOa = lease.options.syncToOa ? '1' : '0';
|
|
79
|
+
}
|
|
80
|
+
if (Object.keys(filedMap).length)
|
|
81
|
+
fields.filedMap = JSON.stringify(filedMap);
|
|
57
82
|
let result;
|
|
58
83
|
try {
|
|
59
84
|
result = await host.invoiceMultipartRequest({
|
|
60
85
|
path: '/api/app/inc/biz/addInvoiceByFile',
|
|
61
86
|
fields,
|
|
62
|
-
file: prepared.file,
|
|
87
|
+
file: prepared.kind === 'file' ? prepared.file : undefined,
|
|
63
88
|
timeoutMs: 90_000,
|
|
64
89
|
});
|
|
65
90
|
}
|
|
@@ -80,27 +80,47 @@ function stringifyEmployeeSuperiorsIds(value) {
|
|
|
80
80
|
function employeeSuperiorsEnvelope(value) {
|
|
81
81
|
return resultEnvelope(stringifyEmployeeSuperiorsIds(value), 'invoice.reim.employee-superiors');
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
const flowCreateFields = ['work_flow_id', 'mainFormId', 'request_name', 'main_fields', 'detail_rows', 'need_user_confirm', 'unable_to_fill'];
|
|
84
|
+
const flowUpdateFields = ['requestId', 'mainFormId', 'main_fields', 'detail_rows', 'deleteDetailRows', 'need_user_confirm', 'unable_to_fill'];
|
|
85
|
+
const flowMetaFields = new Set(['continuation', 'confirm']);
|
|
86
|
+
function flowPayloadFromInput(input, allowedFields) {
|
|
87
|
+
const source = asObject(input, 'input');
|
|
88
|
+
if (source.payload !== undefined) {
|
|
89
|
+
return { value: structuredClone(asObject(source.payload, 'payload')), prefix: 'payload.' };
|
|
90
|
+
}
|
|
91
|
+
const allowed = new Set(allowedFields);
|
|
92
|
+
const value = {};
|
|
93
|
+
for (const [key, item] of Object.entries(source)) {
|
|
94
|
+
if (flowMetaFields.has(key))
|
|
95
|
+
continue;
|
|
96
|
+
if (!allowed.has(key))
|
|
97
|
+
throw invoiceValidationError('input_invalid', `${key} 不是支持的报销单字段`);
|
|
98
|
+
value[key] = item;
|
|
99
|
+
}
|
|
100
|
+
return { value: structuredClone(value), prefix: '' };
|
|
101
|
+
}
|
|
102
|
+
function ensureFlowCreatePayload(input) {
|
|
103
|
+
const { value, prefix } = flowPayloadFromInput(input, flowCreateFields);
|
|
104
|
+
asNonEmptyString(value.work_flow_id, `${prefix}work_flow_id`);
|
|
105
|
+
asNonEmptyString(value.mainFormId, `${prefix}mainFormId`);
|
|
106
|
+
asNonEmptyString(value.request_name, `${prefix}request_name`);
|
|
88
107
|
if (!Array.isArray(value.main_fields))
|
|
89
|
-
throw invoiceValidationError('input_invalid',
|
|
108
|
+
throw invoiceValidationError('input_invalid', `${prefix}main_fields 必须是数组`);
|
|
90
109
|
if (!Array.isArray(value.detail_rows) || value.detail_rows.length === 0) {
|
|
91
|
-
throw invoiceValidationError('input_invalid',
|
|
110
|
+
throw invoiceValidationError('input_invalid', `${prefix}detail_rows 必须是非空数组`);
|
|
92
111
|
}
|
|
93
112
|
return value;
|
|
94
113
|
}
|
|
95
|
-
function ensureFlowUpdatePayload(
|
|
96
|
-
const value =
|
|
97
|
-
asNonEmptyString(value.requestId,
|
|
98
|
-
asNonEmptyString(value.mainFormId,
|
|
114
|
+
function ensureFlowUpdatePayload(input) {
|
|
115
|
+
const { value, prefix } = flowPayloadFromInput(input, flowUpdateFields);
|
|
116
|
+
asNonEmptyString(value.requestId, `${prefix}requestId`);
|
|
117
|
+
asNonEmptyString(value.mainFormId, `${prefix}mainFormId`);
|
|
99
118
|
const hasMain = Array.isArray(value.main_fields) && value.main_fields.length > 0;
|
|
100
119
|
const hasDetails = Array.isArray(value.detail_rows) && value.detail_rows.length > 0;
|
|
101
120
|
const hasDeletes = Array.isArray(value.deleteDetailRows) && value.deleteDetailRows.length > 0;
|
|
102
121
|
if (!hasMain && !hasDetails && !hasDeletes) {
|
|
103
|
-
|
|
122
|
+
const target = prefix ? `${prefix}必须` : 'input 必须';
|
|
123
|
+
throw invoiceValidationError('input_invalid', `${target}包含 main_fields、detail_rows 或 deleteDetailRows 至少一类变更`);
|
|
104
124
|
}
|
|
105
125
|
return value;
|
|
106
126
|
}
|
|
@@ -257,7 +277,7 @@ export class InvoiceReimFieldSearchOperation {
|
|
|
257
277
|
export class InvoiceReimFlowCreatePrepareOperation {
|
|
258
278
|
name = 'invoice.reim.flow.create.prepare';
|
|
259
279
|
async execute(input, { host, continuation }) {
|
|
260
|
-
const payload = ensureFlowCreatePayload(input
|
|
280
|
+
const payload = ensureFlowCreatePayload(input);
|
|
261
281
|
const session = await host.getSession();
|
|
262
282
|
const continuationToken = await continuation.issue({
|
|
263
283
|
operation: 'invoice.reim.flow.create.apply',
|
|
@@ -285,9 +305,9 @@ export class InvoiceReimFlowCreateApplyOperation {
|
|
|
285
305
|
if (input.confirm !== true)
|
|
286
306
|
throw invoiceConfirmationError('invoice.reim.flow.create.apply', { hint: '重新调用时传入 confirm=true' });
|
|
287
307
|
const lease = await continuation.verify(input.continuation, 'invoice.reim.flow.create.apply');
|
|
288
|
-
const payload = ensureFlowCreatePayload(input
|
|
308
|
+
const payload = ensureFlowCreatePayload(input);
|
|
289
309
|
if (typeof lease.fingerprint !== 'string')
|
|
290
|
-
throw invoiceValidationError('continuation_invalid', '报销单创建 continuation
|
|
310
|
+
throw invoiceValidationError('continuation_invalid', '报销单创建 continuation 缺少报销单指纹');
|
|
291
311
|
if (objectFingerprint(payload) !== lease.fingerprint)
|
|
292
312
|
throw invoiceValidationError('target_changed', '报销单载荷与确认时不一致,请重新准备');
|
|
293
313
|
const session = await host.getSession();
|
|
@@ -331,7 +351,7 @@ export class InvoiceReimFlowCreateApplyOperation {
|
|
|
331
351
|
export class InvoiceReimFlowUpdatePrepareOperation {
|
|
332
352
|
name = 'invoice.reim.flow.update.prepare';
|
|
333
353
|
async execute(input, { host, continuation }) {
|
|
334
|
-
const payload = ensureFlowUpdatePayload(input
|
|
354
|
+
const payload = ensureFlowUpdatePayload(input);
|
|
335
355
|
const session = await host.getSession();
|
|
336
356
|
const continuationToken = await continuation.issue({
|
|
337
357
|
operation: 'invoice.reim.flow.update.apply',
|
|
@@ -359,9 +379,9 @@ export class InvoiceReimFlowUpdateApplyOperation {
|
|
|
359
379
|
if (input.confirm !== true)
|
|
360
380
|
throw invoiceConfirmationError('invoice.reim.flow.update.apply', { hint: '重新调用时传入 confirm=true' });
|
|
361
381
|
const lease = await continuation.verify(input.continuation, 'invoice.reim.flow.update.apply');
|
|
362
|
-
const payload = ensureFlowUpdatePayload(input
|
|
382
|
+
const payload = ensureFlowUpdatePayload(input);
|
|
363
383
|
if (typeof lease.fingerprint !== 'string')
|
|
364
|
-
throw invoiceValidationError('continuation_invalid', '报销单更新 continuation
|
|
384
|
+
throw invoiceValidationError('continuation_invalid', '报销单更新 continuation 缺少报销单指纹');
|
|
365
385
|
if (objectFingerprint(payload) !== lease.fingerprint)
|
|
366
386
|
throw invoiceValidationError('target_changed', '报销单更新载荷与确认时不一致,请重新准备');
|
|
367
387
|
const session = await host.getSession();
|
|
@@ -557,18 +557,28 @@ async function normalizeFileSource(source) {
|
|
|
557
557
|
file: { path: source.path, name: basename(source.path), mimeType: MIME_TYPES[extension] },
|
|
558
558
|
};
|
|
559
559
|
}
|
|
560
|
-
export async function
|
|
561
|
-
|
|
562
|
-
throw invoiceValidationError('import_url_unsupported', '确定性导入当前只接受本地 file,不接受远程 URL');
|
|
563
|
-
const source = uploadSource({ file: input.file });
|
|
560
|
+
export async function importSource(input) {
|
|
561
|
+
const source = uploadSource(input);
|
|
564
562
|
const prepared = await normalizeFileSource(source);
|
|
563
|
+
if (source.kind === 'url') {
|
|
564
|
+
return {
|
|
565
|
+
kind: 'url',
|
|
566
|
+
fields: { url: source.url },
|
|
567
|
+
url: source.url,
|
|
568
|
+
fingerprint: objectFingerprint({ kind: 'url', url: source.url }),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
565
571
|
if (!prepared.file)
|
|
566
572
|
throw invoiceValidationError('file_required', '必须提供本地 file 路径');
|
|
567
573
|
const content = await readFile(prepared.file.path);
|
|
574
|
+
const sha256 = createHash('sha256').update(content).digest('hex');
|
|
568
575
|
return {
|
|
576
|
+
kind: 'file',
|
|
577
|
+
fields: {},
|
|
569
578
|
file: prepared.file,
|
|
570
579
|
size: content.length,
|
|
571
|
-
sha256
|
|
580
|
+
sha256,
|
|
581
|
+
fingerprint: objectFingerprint({ kind: 'file', sha256 }),
|
|
572
582
|
};
|
|
573
583
|
}
|
|
574
584
|
export function importOptions(input) {
|
|
@@ -578,7 +588,26 @@ export function importOptions(input) {
|
|
|
578
588
|
if (input.validate && !input.syncToOa) {
|
|
579
589
|
throw invoiceValidationError('import_option_conflict', 'validate=true 分支由服务端固定同步 OA,不能设置 syncToOa=false');
|
|
580
590
|
}
|
|
581
|
-
|
|
591
|
+
const fieldKey = input.fieldKey === undefined || input.fieldKey === null || input.fieldKey === ''
|
|
592
|
+
? undefined
|
|
593
|
+
: asNonEmptyString(input.fieldKey, 'fieldKey');
|
|
594
|
+
const fieldValue = input.fieldValue === undefined || input.fieldValue === null
|
|
595
|
+
? undefined
|
|
596
|
+
: String(input.fieldValue);
|
|
597
|
+
if ((fieldKey === undefined) !== (fieldValue === undefined)) {
|
|
598
|
+
throw invoiceValidationError('import_field_pair_required', 'fieldKey 与 fieldValue 必须同时提供');
|
|
599
|
+
}
|
|
600
|
+
const filedMap = input.filedMap === undefined || input.filedMap === null
|
|
601
|
+
? undefined
|
|
602
|
+
: asObject(input.filedMap, 'filedMap');
|
|
603
|
+
if (filedMap)
|
|
604
|
+
assertNoForbiddenKeys(filedMap, 'filedMap');
|
|
605
|
+
return {
|
|
606
|
+
validate: input.validate,
|
|
607
|
+
syncToOa: input.validate ? true : input.syncToOa,
|
|
608
|
+
...(fieldKey === undefined ? {} : { fieldKey, fieldValue }),
|
|
609
|
+
...(filedMap === undefined ? {} : { filedMap }),
|
|
610
|
+
};
|
|
582
611
|
}
|
|
583
612
|
function normalizeUploadResponse(value, source) {
|
|
584
613
|
if (value?.fail === true) {
|
|
@@ -206,8 +206,8 @@ export const jiuchuanhuiOperations = [
|
|
|
206
206
|
inputSchema: { type: 'object', additionalProperties: true, properties: pageProps },
|
|
207
207
|
},
|
|
208
208
|
...writePair('jiuchuanhui.customer.update', '客户修改', { form: formProp }, ['form'], { path: '/api/ebuilder/form/formdata/v2/updateFormData/customer_update_api' }),
|
|
209
|
-
...writePair('jiuchuanhui.customer.transfer', '客户转移', esbWriteProps(['customerId', '
|
|
210
|
-
...writePair('jiuchuanhui.customer.open-sea.release', '客户释放公海', esbWriteProps(['customerId', 'openSeaId'
|
|
209
|
+
...writePair('jiuchuanhui.customer.transfer', '客户转移', esbWriteProps(['customerId', 'employee_MappingId', 'employeeName']), ['customerId'], { uniqueIndent: '1E44ED7DA9EB46329818E682C2914928' }),
|
|
210
|
+
...writePair('jiuchuanhui.customer.open-sea.release', '客户释放公海', esbWriteProps(['customerId', 'openSeaId']), ['customerId', 'openSeaId'], { uniqueIndent: '0E29AD0E708D4964B071D293288B92D7' }),
|
|
211
211
|
...writePair('jiuchuanhui.customer.open-sea.claim', '领取公海客户', esbWriteProps(['customer_id']), ['customer_id'], { uniqueIndent: 'D587CDC892FE41118DD073475C01A2CE' }),
|
|
212
212
|
{
|
|
213
213
|
name: 'jiuchuanhui.customer.duplicate.check',
|
|
@@ -311,7 +311,7 @@ export const jiuchuanhuiOperations = [
|
|
|
311
311
|
...writePair('jiuchuanhui.clue.update', '线索修改', { form: formProp }, ['form'], { path: '/api/ebuilder/form/formdata/v2/updateFormData/ebclue_ai_update_api' }),
|
|
312
312
|
...writePair('jiuchuanhui.clue.status-change', '线索状态变更', esbWriteProps(['clueid', 'type', 'newmanager', 'desc']), ['clueid', 'type'], { uniqueIndent: '257CC8DE42234F9191439B229839B8A7' }),
|
|
313
313
|
...writePair('jiuchuanhui.clue.to-customer', '线索转已有客户', esbWriteProps(['clueId', 'customerId']), ['clueId', 'customerId'], { uniqueIndent: '00914C6415EB455DB7FCCC32DBE6E0C3' }),
|
|
314
|
-
...writePair('jiuchuanhui.clue.contact-record.create', '新建线索跟进记录', esbWriteProps(['clueId', 'content', 'clueStatus_MappingId']), ['clueId', 'content'
|
|
314
|
+
...writePair('jiuchuanhui.clue.contact-record.create', '新建线索跟进记录', esbWriteProps(['clueId', 'content', 'clueStatus_MappingId']), ['clueId', 'content'], { uniqueIndent: 'B3DD120601344320A2B50A042B450681' }),
|
|
315
315
|
// ==================== 联系计划 ====================
|
|
316
316
|
...writePair('jiuchuanhui.contact-plan.create', '创建联系计划', { form: formProp }, ['form'], { path: '/api/ebuilder/form/formdata/v2/saveFormData/ebcontact_plan_create_api' }),
|
|
317
317
|
...writePair('jiuchuanhui.contact-plan.complete', '完成联系计划', esbWriteProps(['planId']), ['planId'], { uniqueIndent: '2595D3556CFC414F85B1A4A8D806EFE5' }),
|
|
@@ -33,9 +33,9 @@ function buildRegistry() {
|
|
|
33
33
|
new DatajsonWritePrepareOperation('jiuchuanhui.customer.update.prepare', 'jiuchuanhui.customer.update.apply'),
|
|
34
34
|
new DatajsonWriteApplyOperation('jiuchuanhui.customer.update.apply', '/api/ebuilder/form/formdata/v2/updateFormData/customer_update_api'),
|
|
35
35
|
// ===== 客户 ESB 写操作 =====
|
|
36
|
-
new EsbWritePrepareOperation('jiuchuanhui.customer.transfer.prepare', 'jiuchuanhui.customer.transfer.apply', '1E44ED7DA9EB46329818E682C2914928', ['customerId', '
|
|
36
|
+
new EsbWritePrepareOperation('jiuchuanhui.customer.transfer.prepare', 'jiuchuanhui.customer.transfer.apply', '1E44ED7DA9EB46329818E682C2914928', ['customerId', 'employee_MappingId', 'employeeName']),
|
|
37
37
|
new EsbWriteApplyOperation('jiuchuanhui.customer.transfer.apply', '1E44ED7DA9EB46329818E682C2914928'),
|
|
38
|
-
new EsbWritePrepareOperation('jiuchuanhui.customer.open-sea.release.prepare', 'jiuchuanhui.customer.open-sea.release.apply', '0E29AD0E708D4964B071D293288B92D7', ['customerId', 'openSeaId'
|
|
38
|
+
new EsbWritePrepareOperation('jiuchuanhui.customer.open-sea.release.prepare', 'jiuchuanhui.customer.open-sea.release.apply', '0E29AD0E708D4964B071D293288B92D7', ['customerId', 'openSeaId']),
|
|
39
39
|
new EsbWriteApplyOperation('jiuchuanhui.customer.open-sea.release.apply', '0E29AD0E708D4964B071D293288B92D7'),
|
|
40
40
|
new EsbWritePrepareOperation('jiuchuanhui.customer.open-sea.claim.prepare', 'jiuchuanhui.customer.open-sea.claim.apply', 'D587CDC892FE41118DD073475C01A2CE', ['customer_id']),
|
|
41
41
|
new EsbWriteApplyOperation('jiuchuanhui.customer.open-sea.claim.apply', 'D587CDC892FE41118DD073475C01A2CE'),
|
|
@@ -3,16 +3,16 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { resolve } from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
function
|
|
6
|
+
import { okrValidationError } from './errors.js';
|
|
7
|
+
export function okrStateRoot(ctx) {
|
|
8
8
|
const configured = ctx?.config?.path();
|
|
9
9
|
if (configured)
|
|
10
10
|
return resolve(configured, '..');
|
|
11
11
|
return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
12
12
|
}
|
|
13
13
|
async function loadKey(ctx) {
|
|
14
|
-
const keysDir = resolve(
|
|
15
|
-
const filename = resolve(keysDir, '
|
|
14
|
+
const keysDir = resolve(okrStateRoot(ctx), 'keys');
|
|
15
|
+
const filename = resolve(keysDir, 'okr-continuation.key');
|
|
16
16
|
await mkdir(keysDir, { recursive: true, mode: 0o700 });
|
|
17
17
|
if (existsSync(filename))
|
|
18
18
|
return readFile(filename);
|
|
@@ -33,7 +33,7 @@ function encode(value) {
|
|
|
33
33
|
function signature(payload, key) {
|
|
34
34
|
return createHmac('sha256', key).update(payload).digest('base64url');
|
|
35
35
|
}
|
|
36
|
-
export function
|
|
36
|
+
export function createOkrContinuationSigner(ctx) {
|
|
37
37
|
return {
|
|
38
38
|
async issue(payload, ttlMs = 10 * 60_000) {
|
|
39
39
|
const issuedAt = Date.now();
|
|
@@ -43,26 +43,26 @@ export function createYmbContinuationSigner(ctx) {
|
|
|
43
43
|
},
|
|
44
44
|
async verify(token, expectedOperation) {
|
|
45
45
|
if (typeof token !== 'string' || !token.includes('.')) {
|
|
46
|
-
throw
|
|
46
|
+
throw okrValidationError('continuation_invalid', 'continuation 无效');
|
|
47
47
|
}
|
|
48
48
|
const [body, provided] = token.split('.');
|
|
49
49
|
const expected = signature(body, await loadKey(ctx));
|
|
50
50
|
const valid = provided.length === expected.length
|
|
51
51
|
&& timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
|
|
52
52
|
if (!valid)
|
|
53
|
-
throw
|
|
53
|
+
throw okrValidationError('continuation_invalid', 'continuation 签名校验失败');
|
|
54
54
|
let value;
|
|
55
55
|
try {
|
|
56
56
|
value = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
|
|
57
57
|
}
|
|
58
58
|
catch {
|
|
59
|
-
throw
|
|
59
|
+
throw okrValidationError('continuation_invalid', 'continuation 内容无法解析');
|
|
60
60
|
}
|
|
61
61
|
if (value.operation !== expectedOperation) {
|
|
62
|
-
throw
|
|
62
|
+
throw okrValidationError('continuation_mismatch', 'continuation 不属于当前操作');
|
|
63
63
|
}
|
|
64
64
|
if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt < Date.now()) {
|
|
65
|
-
throw
|
|
65
|
+
throw okrValidationError('continuation_expired', 'continuation 已过期');
|
|
66
66
|
}
|
|
67
67
|
return value;
|
|
68
68
|
},
|