weaver-work-cli 0.1.25 → 0.1.26

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.
Files changed (81) hide show
  1. package/README.md +30 -30
  2. package/dist/cmd/root.js +1 -0
  3. package/dist/core/runtime-agent.js +19 -0
  4. package/dist/internal/e10/auth/commands.js +47 -27
  5. package/dist/internal/e10/auth/xiaoe.js +62 -43
  6. package/dist/internal/e10/context.js +5 -27
  7. package/dist/shortcuts/archive-oa/constants.js +66 -0
  8. package/dist/shortcuts/archive-oa/continuation.js +70 -0
  9. package/dist/shortcuts/archive-oa/errors.js +53 -0
  10. package/dist/shortcuts/archive-oa/host.js +157 -0
  11. package/dist/shortcuts/archive-oa/index.js +225 -0
  12. package/dist/shortcuts/archive-oa/manifest.js +62 -0
  13. package/dist/shortcuts/archive-oa/operations/borrow-car.js +159 -0
  14. package/dist/shortcuts/archive-oa/operations/borrow-url.js +108 -0
  15. package/dist/shortcuts/archive-oa/operations/confirm.js +48 -0
  16. package/dist/shortcuts/archive-oa/operations/contract.js +120 -0
  17. package/dist/shortcuts/archive-oa/operations/locate.js +50 -0
  18. package/dist/shortcuts/archive-oa/operations/registry.js +40 -0
  19. package/dist/shortcuts/archive-oa/operations/shared.js +181 -0
  20. package/dist/shortcuts/archive-oa/operations/types.js +45 -0
  21. package/dist/shortcuts/archive-oa/operations.js +13 -0
  22. package/dist/shortcuts/index.js +2 -0
  23. package/dist/shortcuts/invoice/host.js +2 -1
  24. package/dist/shortcuts/invoice/manifest.js +111 -0
  25. package/dist/shortcuts/invoice/operations/issuing-qys.js +361 -0
  26. package/dist/shortcuts/invoice/operations/issuing.js +5 -5
  27. package/dist/shortcuts/invoice/operations/registry.js +4 -0
  28. package/dist/shortcuts/workflow/endpoints.js +11 -0
  29. package/dist/shortcuts/workflow/host.js +2 -2
  30. package/dist/shortcuts/workflow/index.js +6 -36
  31. package/dist/shortcuts/workflow/operations/chatscene.js +270 -0
  32. package/dist/shortcuts/workflow/operations/form.js +899 -158
  33. package/dist/shortcuts/workflow/operations/read.js +111 -6
  34. package/dist/shortcuts/workflow/operations/sign-input.js +106 -0
  35. package/dist/shortcuts/workflow/operations/write.js +9 -1
  36. package/dist/shortcuts/workflow/stdout-compact.js +9 -1
  37. package/docs/archive-oa.md +79 -0
  38. package/docs/e10-auth.md +7 -6
  39. package/package.json +1 -1
  40. package/skill-template/business-info.json +16 -0
  41. package/skill-template/domains/archive-oa.md +19 -0
  42. package/skill-template/domains/yepiaotong.md +2 -2
  43. package/skills/weaver-e10-archive-oa/SKILL.md +159 -0
  44. package/skills/weaver-e10-archive-oa/references/archive-locate.md +49 -0
  45. package/skills/weaver-e10-archive-oa/references/borrow-car.md +76 -0
  46. package/skills/weaver-e10-archive-oa/references/borrow-flow.md +53 -0
  47. package/skills/weaver-e10-archive-oa/references/contract-search.md +63 -0
  48. package/skills/weaver-e10-archive-oa/references/disabled-capabilities.md +29 -0
  49. package/skills/weaver-e10-archive-oa/references/qys-contract-search.md +47 -0
  50. package/skills/weaver-e10-archive-oa/references/source-manifest.json +400 -0
  51. package/skills/weaver-e10-builder/SKILL.md +22 -0
  52. package/skills/weaver-e10-caizhilian/SKILL.md +37 -15
  53. package/skills/weaver-e10-caizhilian/references/agent-entry.md +7 -3
  54. package/skills/weaver-e10-caizhilian/references/collaboration.md +5 -3
  55. package/skills/weaver-e10-caizhilian/references/document.md +33 -11
  56. package/skills/weaver-e10-caizhilian/references/file-collection.md +4 -3
  57. package/skills/weaver-e10-caizhilian/references/file-upload.md +2 -3
  58. package/skills/weaver-e10-caizhilian/references/files.md +9 -3
  59. package/skills/weaver-e10-caizhilian/references/folder.md +48 -16
  60. package/skills/weaver-e10-caizhilian/references/setting-attach.md +13 -39
  61. package/skills/weaver-e10-caizhilian/references/setting-backup.md +13 -51
  62. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +20 -51
  63. package/skills/weaver-e10-caizhilian/references/setting-storage.md +82 -147
  64. package/skills/weaver-e10-caizhilian/references/withheld.md +20 -44
  65. package/skills/weaver-e10-shared/SKILL.md +22 -20
  66. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +14 -15
  67. package/skills/weaver-e10-shared/references/non-interactive-environment.md +10 -10
  68. package/skills/weaver-e10-workflow/references/source-manifest.json +32 -22
  69. package/skills/weaver-e10-workflow/references/workflow-create.md +68 -28
  70. package/skills/weaver-e10-yepiaotong/SKILL.md +10 -7
  71. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +49 -1
  72. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-qys-ledger.md +66 -0
  73. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +59 -36
  74. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +54 -509
  75. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +182 -345
  76. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -5
  77. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +153 -345
  78. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +5 -5
  79. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +182 -154
  80. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +96 -0
  81. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +0 -119
@@ -673,6 +673,66 @@ const issuingApplyOutputSchema = objectSchema({
673
673
  result: rawObject,
674
674
  workflow: workflowSchema,
675
675
  }, ['mode', 'ledgerOnly', 'result', 'workflow']);
676
+ const qysOptionValue = {
677
+ type: ['object', 'null'],
678
+ additionalProperties: false,
679
+ properties: { id: { type: 'string' }, name: { type: 'string' } },
680
+ };
681
+ const nullableText = { type: ['string', 'null'] };
682
+ const qysLedgerRowSchema = objectSchema({
683
+ formDataId: { type: 'string' },
684
+ title: nullableText,
685
+ dataStatus: nullableText,
686
+ contractNo: nullableText,
687
+ contractFlow: qysOptionValue,
688
+ invoiceType: qysOptionValue,
689
+ purchaserName: nullableText,
690
+ purchaserTaxNo: nullableText,
691
+ invoiceItemName: nullableText,
692
+ goodsName: nullableText,
693
+ revenueCode: nullableText,
694
+ taxRate: qysOptionValue,
695
+ quantity: nullableText,
696
+ unitPrice: nullableText,
697
+ amount: { ...nullableText, description: '开票金额(不含税)' },
698
+ invoiceNo: nullableText,
699
+ invoiceFlow: qysOptionValue,
700
+ discarded: qysOptionValue,
701
+ }, ['formDataId'], true);
702
+ const qysListOutputSchema = objectSchema({
703
+ items: arrayOf(objectSchema({
704
+ issuable: { type: 'boolean' },
705
+ blockers: arrayOf({ type: 'string' }),
706
+ }, ['formDataId', 'issuable', 'blockers'], true)),
707
+ matched: { type: 'integer' },
708
+ truncated: { type: 'boolean' },
709
+ scanned: { type: 'integer' },
710
+ scanComplete: { type: 'boolean' },
711
+ }, ['items', 'matched', 'truncated', 'scanned', 'scanComplete']);
712
+ const qysPreviewSchema = objectSchema({
713
+ ...makeInvoicePreviewSchema.properties,
714
+ formDataId: { type: 'string' },
715
+ contractNo: { type: 'string' },
716
+ contractFlowName: { type: 'string' },
717
+ invoiceTypeName: { type: 'string' },
718
+ goodsName: { type: 'string' },
719
+ taxRate: { type: 'string' },
720
+ }, [...makeInvoicePreviewSchema.required, 'formDataId', 'goodsName', 'taxRate']);
721
+ const qysPrepareOutputSchema = continuationPreviewOutput(qysPreviewSchema, {
722
+ warnings: arrayOf({ type: 'string' }),
723
+ ledgerRow: qysLedgerRowSchema,
724
+ normalizedPayload: requestPayload,
725
+ });
726
+ const issuingResultOutputSchema = objectSchema({
727
+ externalDocumentNo: { type: 'string' },
728
+ state: { type: 'string', enum: ['SUCCESS', 'PENDING', 'FAILED'] },
729
+ returnCode: { type: 'string' },
730
+ returnMessage: { type: 'string' },
731
+ invoiceNo: { type: 'string' },
732
+ invoiceTime: { type: 'string' },
733
+ serialNo: { type: 'string' },
734
+ result: rawObject,
735
+ }, ['externalDocumentNo', 'state', 'returnCode', 'returnMessage', 'invoiceNo', 'result']);
676
736
  export const invoiceOperations = [
677
737
  {
678
738
  name: 'invoice.list',
@@ -1219,6 +1279,57 @@ export const invoiceOperations = [
1219
1279
  },
1220
1280
  outputSchema: issuingApplyOutputSchema,
1221
1281
  },
1282
+ {
1283
+ name: 'invoice.issuing.qys.list',
1284
+ risk: 'read',
1285
+ fixed: { path: '/api/ebuilder/form/eblist/getData', ledger: '契约锁合同商品信息' },
1286
+ inputSchema: {
1287
+ type: 'object',
1288
+ additionalProperties: false,
1289
+ properties: {
1290
+ keyword: { type: 'string', description: '模糊匹配合同号、购买方名称、合同审批流程名称、商品名称或开票名称' },
1291
+ contractNo: { type: 'string', description: '按合同号精确筛选' },
1292
+ includeIssued: { type: 'boolean', default: false, description: '是否包含已开票、开票中或已废弃的行。默认 false 只返回可开票行。' },
1293
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },
1294
+ },
1295
+ },
1296
+ outputSchema: qysListOutputSchema,
1297
+ },
1298
+ {
1299
+ name: 'invoice.issuing.qys.prepare',
1300
+ risk: 'read-before-write',
1301
+ fixed: {
1302
+ path: '/api/ebuilder/form/eblist/getData',
1303
+ externalDocumentNo: 'QYS-{formDataId}',
1304
+ amountBasis: '开票金额为不含税金额,taxSign=0,税额=金额×税率四舍五入到分',
1305
+ applyOperation: 'invoice.issuing.make.apply',
1306
+ },
1307
+ inputSchema: {
1308
+ type: 'object',
1309
+ additionalProperties: false,
1310
+ properties: {
1311
+ formDataId: { type: 'string', pattern: '^\\d+$', description: 'invoice.issuing.qys.list 返回的台账行 formDataId' },
1312
+ mode: { type: 'string', enum: ['issue', 'ledger'] },
1313
+ },
1314
+ required: ['formDataId', 'mode'],
1315
+ },
1316
+ outputSchema: qysPrepareOutputSchema,
1317
+ },
1318
+ {
1319
+ name: 'invoice.issuing.result',
1320
+ risk: 'read',
1321
+ fixed: { path: '/api/inc/book/open/getMakeInvoiceResult', taxNo: '9131000070322836XD' },
1322
+ inputSchema: {
1323
+ type: 'object',
1324
+ additionalProperties: false,
1325
+ properties: {
1326
+ externalDocumentNo: { type: 'string', minLength: 1 },
1327
+ serialNo: { type: 'string' },
1328
+ },
1329
+ required: ['externalDocumentNo'],
1330
+ },
1331
+ outputSchema: issuingResultOutputSchema,
1332
+ },
1222
1333
  ];
1223
1334
  export const invoiceManifest = {
1224
1335
  schemaVersion: 1,
@@ -0,0 +1,361 @@
1
+ import { InvoiceCliError, invoiceValidationError } from '../errors.js';
2
+ import { SELLER_TAX_NO, makeInvoicePreview, normalizeMakeInvoicePayload, normalizeMode, } from './issuing.js';
3
+ import { asNonEmptyString, assertBusinessSuccess, objectFingerprint, workflow } from './shared.js';
4
+ const LEDGER_PATH = '/api/ebuilder/form/eblist/getData';
5
+ const LEDGER_PAGE_ID = '1314988320430981804';
6
+ const LEDGER_APP_ID = '846297243998732291';
7
+ const LEDGER_MENU_ID = '5419015526244625718';
8
+ const LEDGER_PAGE_TITLE = '5aWR57qm6ZSB5ZCI5ZCM5ZWG5ZOB5L h5oGv';
9
+ const LEDGER_PAGE_SIZE = 50;
10
+ const LEDGER_MAX_PAGES = 40;
11
+ const RESULT_PATH = '/api/inc/book/open/getMakeInvoiceResult';
12
+ /** 契约锁合同商品信息表:数据 key → 可读字段。 */
13
+ export const QYS_LEDGER_FIELDS = [
14
+ { id: '1314988311673275798', key: 'contractNo', label: '合同号', kind: 'text' },
15
+ { id: '1314988311673275801', key: 'contractFlow', label: '合同审批流程', kind: 'relation' },
16
+ { id: '1314988311673275775', key: 'contractCategory', label: '合同种类', kind: 'text' },
17
+ { id: '1314988311673275790', key: 'productType', label: '产品类型', kind: 'option' },
18
+ { id: '1314988311673275776', key: 'productTypeDetail', label: '产品类型明细', kind: 'text' },
19
+ { id: '1314988311673275783', key: 'accountingCompany', label: '核算公司', kind: 'option' },
20
+ { id: '1314988311673275784', key: 'invoiceGroupFlag', label: '开票分组标识', kind: 'number' },
21
+ { id: '1314988311673275793', key: 'sourceFlag', label: '来源标识', kind: 'text' },
22
+ { id: '1314988311673275808', key: 'flowDetailId', label: '流程明细的id', kind: 'text' },
23
+ { id: '1314988311673275781', key: 'invoiceType', label: '发票类型', kind: 'option' },
24
+ { id: '1314988311673275806', key: 'purchaserName', label: '购买方名称', kind: 'text' },
25
+ { id: '1314988311673275807', key: 'purchaserTaxNo', label: '购买方税号', kind: 'text' },
26
+ { id: '1314988311673275809', key: 'purchaserAddress', label: '购买方地址', kind: 'text' },
27
+ { id: '1314988311673275810', key: 'purchaserTel', label: '购买方电话', kind: 'text' },
28
+ { id: '1314988311673275811', key: 'purchaserBankName', label: '购买方开户行', kind: 'text' },
29
+ { id: '1314988311673275802', key: 'purchaserBankAccount', label: '购买方开户行账号', kind: 'text' },
30
+ { id: '1314988311673275800', key: 'invoiceItemName', label: '开票名称', kind: 'text' },
31
+ { id: '1314988311673275799', key: 'goodsName', label: '商品名称', kind: 'text' },
32
+ { id: '1314988311673275803', key: 'revenueCode', label: '商品税收分类编码', kind: 'text' },
33
+ { id: '1314988311673275804', key: 'taxRate', label: '商品税率', kind: 'relation' },
34
+ { id: '1314988311673275780', key: 'specification', label: '规格型号', kind: 'text' },
35
+ { id: '1314988311673275785', key: 'unit', label: '单位', kind: 'text' },
36
+ { id: '1314988311673275805', key: 'quantity', label: '商品数量', kind: 'number' },
37
+ { id: '1314988311673275794', key: 'unitPrice', label: '商品单价', kind: 'money' },
38
+ { id: '1314988311673275795', key: 'amount', label: '开票金额', kind: 'money' },
39
+ { id: '1314988311673275789', key: 'uninvoicedAmount', label: '未开票金额', kind: 'money' },
40
+ { id: '1314988311673275787', key: 'invoicedAmount', label: '已开票金额', kind: 'money' },
41
+ { id: '1314988311673275788', key: 'invoicingAmount', label: '开票中金额', kind: 'money' },
42
+ { id: '1314988311673275777', key: 'amountDiffers', label: '开票金额存在差异', kind: 'option' },
43
+ { id: '1314988311673275796', key: 'thirdPartyPurchase', label: '是否三方采购商品', kind: 'option' },
44
+ { id: '1314988311673275812', key: 'invoiceNo', label: '发票号码', kind: 'text' },
45
+ { id: '1314988311673275797', key: 'invoiceFlow', label: '开票流程ID', kind: 'relation' },
46
+ { id: '1314988311673275791', key: 'invoiceDate', label: '开票时间', kind: 'date' },
47
+ { id: '1314988311673275792', key: 'discarded', label: '是否废弃', kind: 'option' },
48
+ { id: '1314988311673275778', key: 'discardedDate', label: '废弃时间', kind: 'date' },
49
+ { id: '1314988311673275779', key: 'contactName', label: '客户联系人', kind: 'text' },
50
+ { id: '1314988311673275782', key: 'contactPhone', label: '客户联系人电话', kind: 'text' },
51
+ { id: '1314988311673275786', key: 'contactEmail', label: '客户联系人邮箱', kind: 'text' },
52
+ ];
53
+ function textValue(value) {
54
+ if (value === undefined || value === null)
55
+ return null;
56
+ const text = String(value);
57
+ return text.trim() === '' ? null : text;
58
+ }
59
+ function optionValue(value) {
60
+ const first = Array.isArray(value) ? value[0] : value;
61
+ if (!first || typeof first !== 'object')
62
+ return null;
63
+ const item = first;
64
+ const id = textValue(item.id);
65
+ const name = textValue(item.name);
66
+ if (id === null && name === null)
67
+ return null;
68
+ return { id: id ?? '', name: name ?? '' };
69
+ }
70
+ export function convertQysLedgerRow(raw) {
71
+ const formDataId = textValue(raw.form_data_id) ?? textValue(raw.id);
72
+ if (!formDataId)
73
+ throw invoiceValidationError('ledger_row_invalid', '台账行缺少 form_data_id');
74
+ const row = {
75
+ formDataId,
76
+ title: textValue(raw.name),
77
+ dataStatus: optionValue(raw.data_status)?.name ?? null,
78
+ };
79
+ for (const field of QYS_LEDGER_FIELDS) {
80
+ const value = raw[field.id];
81
+ row[field.key] = field.kind === 'option' || field.kind === 'relation' ? optionValue(value) : textValue(value);
82
+ }
83
+ return row;
84
+ }
85
+ function toCents(value, name) {
86
+ if (value === null || !/^\d+(?:\.\d{1,2})?$/u.test(value)) {
87
+ throw invoiceValidationError('ledger_amount_invalid', `${name} 必须是最多两位小数的非负金额,当前值:${value ?? '空'}`);
88
+ }
89
+ const [whole, fraction = ''] = value.split('.');
90
+ return Number(whole) * 100 + Number(fraction.padEnd(2, '0'));
91
+ }
92
+ function formatCents(cents) {
93
+ return `${Math.floor(cents / 100)}.${String(cents % 100).padStart(2, '0')}`;
94
+ }
95
+ /** “6.0%” → 600(万分比),只接受开票接口支持的税率。 */
96
+ export function parseTaxRateBasisPoints(name) {
97
+ const match = typeof name === 'string' ? /^\s*(\d+(?:\.\d+)?)\s*%\s*$/u.exec(name) : null;
98
+ const basisPoints = match ? Math.round(Number(match[1]) * 100) : Number.NaN;
99
+ if (![0, 100, 300, 500, 600, 900, 1300].includes(basisPoints)) {
100
+ throw invoiceValidationError('ledger_tax_rate_invalid', `商品税率无法识别或不在允许范围内:${name ?? '空'}`);
101
+ }
102
+ return basisPoints;
103
+ }
104
+ function isPositiveCents(value) {
105
+ return value !== null && /^\d+(?:\.\d+)?$/u.test(value) && Number(value) > 0;
106
+ }
107
+ /** 已开票、开票中或已废弃的行不能再开票,返回原因;可开票时返回空数组。 */
108
+ export function qysRowBlockers(row) {
109
+ const blockers = [];
110
+ if (row.invoiceNo)
111
+ blockers.push(`已有发票号码 ${row.invoiceNo}`);
112
+ if (row.invoiceFlow)
113
+ blockers.push(`已关联开票流程 ${row.invoiceFlow.name || row.invoiceFlow.id}`);
114
+ if (isPositiveCents(row.invoicingAmount))
115
+ blockers.push(`开票中金额 ${row.invoicingAmount}`);
116
+ if (row.discarded?.name === '是' || row.discardedDate)
117
+ blockers.push('已废弃');
118
+ return blockers;
119
+ }
120
+ async function fetchLedgerPage(host, pageNo, operation) {
121
+ const fieldFilter = {
122
+ cusMenuId: LEDGER_MENU_ID,
123
+ urlPageTitle: LEDGER_PAGE_TITLE,
124
+ [`appId_${LEDGER_PAGE_ID}`]: LEDGER_APP_ID,
125
+ cusview: '0',
126
+ };
127
+ const result = await host.invoiceMultipartRequest({
128
+ path: LEDGER_PATH,
129
+ headers: { ebbusinessid: LEDGER_PAGE_ID },
130
+ fields: {
131
+ ...fieldFilter,
132
+ filter: '[]',
133
+ fieldFilter,
134
+ pageFilter: { filter: {}, filterIsNull: true, esbFilter: [] },
135
+ pageAppId: LEDGER_APP_ID,
136
+ compId: 'e00e8244de584b369417a4fea31d7bed',
137
+ pageId: LEDGER_PAGE_ID,
138
+ pageNo: String(pageNo),
139
+ pageSize: String(LEDGER_PAGE_SIZE),
140
+ tableuid: 'a74d995bddd54a4f831551b468071632',
141
+ refPluginPackageId: '933846626099339271',
142
+ },
143
+ operation,
144
+ });
145
+ assertBusinessSuccess(result, 'qys_ledger_query_failed', '契约锁开票台账查询失败');
146
+ const list = result?.data?.list;
147
+ if (!Array.isArray(list)) {
148
+ throw new InvoiceCliError('api', 'qys_ledger_query_failed', '契约锁开票台账返回缺少 data.list', { exitCode: 1 });
149
+ }
150
+ return list;
151
+ }
152
+ /** 接口返回的 count 恒为 0,只能按“本页未取满”判断结束。 */
153
+ async function scanLedger(host, operation, visit) {
154
+ let scanned = 0;
155
+ for (let pageNo = 1; pageNo <= LEDGER_MAX_PAGES; pageNo += 1) {
156
+ const page = await fetchLedgerPage(host, pageNo, operation);
157
+ for (const raw of page) {
158
+ scanned += 1;
159
+ if (visit(convertQysLedgerRow(raw)))
160
+ return { scanned, complete: true };
161
+ }
162
+ if (page.length < LEDGER_PAGE_SIZE)
163
+ return { scanned, complete: true };
164
+ }
165
+ return { scanned, complete: false };
166
+ }
167
+ function matchesKeyword(row, keyword) {
168
+ const haystack = [row.contractNo, row.purchaserName, row.contractFlow?.name, row.goodsName, row.invoiceItemName, row.formDataId];
169
+ return haystack.some((value) => typeof value === 'string' && value.includes(keyword));
170
+ }
171
+ export class InvoiceIssuingQysListOperation {
172
+ name = 'invoice.issuing.qys.list';
173
+ async execute(input, { host }) {
174
+ const keyword = input.keyword === undefined || input.keyword === '' ? undefined : asNonEmptyString(input.keyword, 'keyword');
175
+ const contractNo = input.contractNo === undefined || input.contractNo === '' ? undefined : asNonEmptyString(input.contractNo, 'contractNo');
176
+ const includeIssued = input.includeIssued === true;
177
+ const limit = input.limit === undefined ? 20 : Number(input.limit);
178
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100)
179
+ throw invoiceValidationError('input_invalid', 'limit 必须是 1-100 的整数');
180
+ const items = [];
181
+ let matched = 0;
182
+ const scan = await scanLedger(host, this.name, (row) => {
183
+ if (contractNo && row.contractNo !== contractNo)
184
+ return false;
185
+ if (keyword && !matchesKeyword(row, keyword))
186
+ return false;
187
+ const blockers = qysRowBlockers(row);
188
+ if (!includeIssued && blockers.length)
189
+ return false;
190
+ matched += 1;
191
+ if (items.length < limit)
192
+ items.push({ ...row, issuable: blockers.length === 0, blockers });
193
+ return false;
194
+ });
195
+ return {
196
+ status: 'COMPLETE',
197
+ data: {
198
+ items,
199
+ matched,
200
+ truncated: matched > items.length,
201
+ scanned: scan.scanned,
202
+ scanComplete: scan.complete,
203
+ },
204
+ };
205
+ }
206
+ }
207
+ export function buildQysMakeInvoicePayload(row) {
208
+ const warnings = [];
209
+ const invoiceType = row.invoiceType?.id;
210
+ if (!invoiceType)
211
+ throw invoiceValidationError('ledger_field_missing', '台账行缺少发票类型');
212
+ const goodsName = row.invoiceItemName ?? row.goodsName;
213
+ if (!goodsName)
214
+ throw invoiceValidationError('ledger_field_missing', '台账行缺少开票名称和商品名称');
215
+ if (!row.purchaserName)
216
+ throw invoiceValidationError('ledger_field_missing', '台账行缺少购买方名称');
217
+ const basisPoints = parseTaxRateBasisPoints(row.taxRate?.name);
218
+ const amountCents = toCents(row.amount, '开票金额');
219
+ if (amountCents === 0)
220
+ throw invoiceValidationError('ledger_amount_invalid', '开票金额必须大于 0');
221
+ const taxCents = Math.floor((amountCents * basisPoints + 5000) / 10000);
222
+ const amount = formatCents(amountCents);
223
+ const taxAmount = formatCents(taxCents);
224
+ const amountWithTax = formatCents(amountCents + taxCents);
225
+ if (row.quantity !== null && row.unitPrice !== null) {
226
+ const expected = Number(row.quantity) * Number(row.unitPrice);
227
+ if (!Number.isFinite(expected) || Math.abs(expected * 100 - amountCents) > 1) {
228
+ warnings.push(`商品数量 ${row.quantity} × 商品单价 ${row.unitPrice} 与开票金额 ${row.amount} 不一致`);
229
+ }
230
+ }
231
+ if (row.uninvoicedAmount !== null && row.uninvoicedAmount !== row.amount && Number(row.uninvoicedAmount) !== Number(row.amount)) {
232
+ warnings.push(`未开票金额 ${row.uninvoicedAmount} 与开票金额 ${row.amount} 不一致,本次按开票金额全额开具`);
233
+ }
234
+ if (row.dataStatus && row.dataStatus !== '提交')
235
+ warnings.push(`台账数据状态为“${row.dataStatus}”`);
236
+ const detail = {
237
+ goodsName,
238
+ taxSign: '0',
239
+ taxRate: basisPoints / 10000,
240
+ amount,
241
+ taxAmount,
242
+ amountWithTax,
243
+ };
244
+ for (const key of ['revenueCode', 'specification', 'unit', 'quantity', 'unitPrice']) {
245
+ if (row[key] !== null)
246
+ detail[key] = row[key];
247
+ }
248
+ const payload = {
249
+ invoiceType,
250
+ externalDocumentNo: `QYS-${row.formDataId}`,
251
+ purchaserName: row.purchaserName,
252
+ taxSign: '0',
253
+ totalAmount: amount,
254
+ taxAmount,
255
+ totalAmountWithTax: amountWithTax,
256
+ details: [detail],
257
+ };
258
+ for (const key of ['purchaserTaxNo', 'purchaserAddress', 'purchaserTel', 'purchaserBankName', 'purchaserBankAccount']) {
259
+ if (row[key] !== null)
260
+ payload[key] = row[key];
261
+ }
262
+ return { payload, warnings };
263
+ }
264
+ export class InvoiceIssuingQysPrepareOperation {
265
+ name = 'invoice.issuing.qys.prepare';
266
+ async execute(input, { host, continuation }) {
267
+ const mode = normalizeMode(input.mode);
268
+ const formDataId = asNonEmptyString(input.formDataId, 'formDataId');
269
+ if (!/^\d+$/u.test(formDataId))
270
+ throw invoiceValidationError('input_invalid', 'formDataId 必须是台账行的数字 ID');
271
+ let row;
272
+ const scan = await scanLedger(host, this.name, (candidate) => {
273
+ if (candidate.formDataId !== formDataId)
274
+ return false;
275
+ row = candidate;
276
+ return true;
277
+ });
278
+ if (!row) {
279
+ throw invoiceValidationError('ledger_row_not_found', `契约锁开票台账中未找到 formDataId=${formDataId} 的行`, {
280
+ scanned: scan.scanned,
281
+ scanComplete: scan.complete,
282
+ });
283
+ }
284
+ const blockers = qysRowBlockers(row);
285
+ if (blockers.length) {
286
+ throw invoiceValidationError('ledger_row_not_issuable', `该台账行不能开票:${blockers.join(';')}`, { formDataId, blockers });
287
+ }
288
+ const { payload, warnings } = buildQysMakeInvoicePayload(row);
289
+ const normalizedPayload = normalizeMakeInvoicePayload(payload, mode);
290
+ const preview = {
291
+ ...makeInvoicePreview(normalizedPayload, mode),
292
+ formDataId,
293
+ contractNo: row.contractNo ?? '',
294
+ contractFlowName: row.contractFlow?.name ?? '',
295
+ invoiceTypeName: row.invoiceType?.name ?? '',
296
+ goodsName: normalizedPayload.details[0].goodsName,
297
+ taxRate: row.taxRate?.name ?? '',
298
+ };
299
+ const session = await host.getSession();
300
+ const continuationToken = await continuation.issue({
301
+ operation: 'invoice.issuing.make.apply',
302
+ workflow: workflow('invoice.issuing.make', 'AWAITING_CONFIRMATION'),
303
+ baseUrl: session.baseUrl,
304
+ profile: session.profile,
305
+ userId: session.userId,
306
+ mode,
307
+ fingerprint: objectFingerprint(normalizedPayload),
308
+ preview,
309
+ });
310
+ return {
311
+ status: 'AWAITING_CONFIRMATION',
312
+ data: {
313
+ preview,
314
+ warnings,
315
+ ledgerRow: row,
316
+ normalizedPayload,
317
+ continuation: continuationToken,
318
+ expiresInSeconds: 600,
319
+ workflow: workflow('invoice.issuing.make', 'AWAITING_CONFIRMATION'),
320
+ },
321
+ };
322
+ }
323
+ }
324
+ function resultState(returnCode) {
325
+ if (returnCode === '00')
326
+ return 'SUCCESS';
327
+ if (returnCode === '01')
328
+ return 'PENDING';
329
+ return 'FAILED';
330
+ }
331
+ export class InvoiceIssuingResultOperation {
332
+ name = 'invoice.issuing.result';
333
+ async execute(input, { host }) {
334
+ const externalDocumentNo = asNonEmptyString(input.externalDocumentNo, 'externalDocumentNo');
335
+ const invoiceReq = { businessNo: externalDocumentNo, taxNo: SELLER_TAX_NO };
336
+ if (input.serialNo !== undefined && input.serialNo !== '')
337
+ invoiceReq.serialNo = asNonEmptyString(input.serialNo, 'serialNo');
338
+ const result = await host.invoiceRequest({
339
+ path: RESULT_PATH,
340
+ method: 'POST',
341
+ body: { invoiceReq },
342
+ operation: this.name,
343
+ timeoutMs: 60_000,
344
+ });
345
+ assertBusinessSuccess(result, 'issuing_result_failed', '查询开票结果失败');
346
+ const data = result?.data || {};
347
+ return {
348
+ status: 'COMPLETE',
349
+ data: {
350
+ externalDocumentNo,
351
+ state: resultState(data.returnCode),
352
+ returnCode: data.returnCode ?? '',
353
+ returnMessage: data.returnMessage ?? '',
354
+ invoiceNo: data.digitInvoiceNo || data.invoiceNo || '',
355
+ invoiceTime: data.invoiceTime ?? '',
356
+ serialNo: data.serialNo ?? '',
357
+ result,
358
+ },
359
+ };
360
+ }
361
+ }
@@ -1,6 +1,6 @@
1
1
  import { invoiceConfirmationError, invoicePartialError, invoiceValidationError, isInvoiceNetworkError } from '../errors.js';
2
2
  import { asNonEmptyString, asObject, assertBusinessSuccess, objectFingerprint, sessionMatches, workflow, } from './shared.js';
3
- const SELLER_TAX_NO = '9131000070322836XD';
3
+ export const SELLER_TAX_NO = '9131000070322836XD';
4
4
  const SELLER_NAME = '上海泛微网络科技股份有限公司';
5
5
  const TAX_RATES = new Set([0, 0.01, 0.03, 0.05, 0.06, 0.09, 0.13]);
6
6
  function optionalText(value, name) {
@@ -38,7 +38,7 @@ function taxNo(value, name, { required = false } = {}) {
38
38
  }
39
39
  return text;
40
40
  }
41
- function normalizeMode(value) {
41
+ export function normalizeMode(value) {
42
42
  if (value !== 'issue' && value !== 'ledger') {
43
43
  throw invoiceValidationError('input_invalid', 'mode 必须是 issue 或 ledger');
44
44
  }
@@ -61,7 +61,7 @@ function normalizeDetail(value, index) {
61
61
  }
62
62
  return detail;
63
63
  }
64
- function normalizeMakeInvoicePayload(rawPayload, mode) {
64
+ export function normalizeMakeInvoicePayload(rawPayload, mode) {
65
65
  const payload = structuredClone(asObject(rawPayload, 'payload'));
66
66
  const purchaserName = asNonEmptyString(payload.purchaserName, 'payload.purchaserName');
67
67
  const invoiceType = asNonEmptyString(payload.invoiceType, 'payload.invoiceType');
@@ -104,7 +104,7 @@ function normalizeMakeInvoicePayload(rawPayload, mode) {
104
104
  delete normalized.operateType;
105
105
  return normalized;
106
106
  }
107
- function makeInvoicePreview(payload, mode) {
107
+ export function makeInvoicePreview(payload, mode) {
108
108
  return {
109
109
  mode,
110
110
  purchaserName: payload.purchaserName,
@@ -163,7 +163,7 @@ export class InvoiceIssuingMakeApplyOperation {
163
163
  throw invoiceValidationError('context_mismatch', '确认上下文已变化,请重新准备开票');
164
164
  let result;
165
165
  try {
166
- result = await host.invoiceRequest({ path: '/api/inc/book/open/makeInvoice', method: 'POST', body: payload, operation: this.name, timeoutMs: 90_000 });
166
+ result = await host.invoiceRequest({ path: '/api/inc/book/open/makeInvoice', method: 'POST', body: { invoiceReq: payload }, operation: this.name, timeoutMs: 90_000 });
167
167
  }
168
168
  catch (error) {
169
169
  if (isInvoiceNetworkError(error)) {
@@ -7,6 +7,7 @@ import { InvoiceDownloadOperation } from './download.js';
7
7
  import { InvoiceGetOperation } from './get.js';
8
8
  import { InvoiceImportApplyOperation, InvoiceImportPrepareOperation } from './import.js';
9
9
  import { InvoiceIssuingMakeApplyOperation, InvoiceIssuingMakePrepareOperation } from './issuing.js';
10
+ import { InvoiceIssuingQysListOperation, InvoiceIssuingQysPrepareOperation, InvoiceIssuingResultOperation, } from './issuing-qys.js';
10
11
  import { InvoiceListOperation } from './list.js';
11
12
  import { InvoiceOcrPreviewOperation } from './ocr-preview.js';
12
13
  import { InvoiceReimEmployeeSuperiorsOperation, InvoiceReimFieldSearchOperation, InvoiceReimFileOcrPreviewOperation, InvoiceReimFlowCreateApplyOperation, InvoiceReimFlowCreatePrepareOperation, InvoiceReimFlowUpdateApplyOperation, InvoiceReimFlowUpdatePrepareOperation, InvoiceReimFormStructureOperation, InvoiceReimRequestsListOperation, InvoiceReimRowInfoBatchOperation, InvoiceReimRowInfoOperation, InvoiceReimWorkflowResolveOperation, } from './reim.js';
@@ -52,6 +53,9 @@ function buildRegistry() {
52
53
  new InvoiceReimFlowUpdateApplyOperation(),
53
54
  new InvoiceIssuingMakePrepareOperation(),
54
55
  new InvoiceIssuingMakeApplyOperation(),
56
+ new InvoiceIssuingQysListOperation(),
57
+ new InvoiceIssuingQysPrepareOperation(),
58
+ new InvoiceIssuingResultOperation(),
55
59
  ];
56
60
  for (const handler of handlers) {
57
61
  if (registry.has(handler.name))
@@ -50,6 +50,11 @@ export const WORKFLOW_ENDPOINTS = {
50
50
  /** 我发起总数 / 列表 */
51
51
  MINE_TOTAL: '/api/workflow/list/total/getMineListTotal',
52
52
  MINE_LIST: '/api/workflow/list/data/getMineListData',
53
+ /**
54
+ * 我发起——**工作流树**(`data.data[]` = 流程大类,`children` = 具体工作流,`id` 形如 `wf_<workflowId>`)。
55
+ * 入参与 `MINE_LIST` 一致、去掉分页参数。发起侧消歧用(2026-10-20)。
56
+ */
57
+ MINE_TREE: '/api/workflow/list/tree/getMineListTree',
53
58
  /** 全部总数 / 列表 */
54
59
  ALL_TOTAL: '/api/workflow/list/total/getAllListTotal',
55
60
  ALL_LIST: '/api/workflow/list/data/getAllListData',
@@ -138,6 +143,12 @@ export const FORM_ENDPOINTS = {
138
143
  verificationConditionRule: '/api/bs/workflow/pathdef/nodeConformance/verificationConditionRule',
139
144
  /** 提交已创建的流程(创建后页面重载拿到 view 页鉴权再提交,与真机同路径) */
140
145
  flowSubmit: '/api/workflow/core/flow/submit',
146
+ /**
147
+ * 签字意见的默认样式(字体 / 字号 / 是否纯文本输入)。
148
+ *
149
+ * 提交前取一次,用来把意见内容包成服务端能正确渲染的富文本形态;取不到就退回原样式。
150
+ */
151
+ signInputInfo: '/api/workflow/core/flowPage/loadSignInputInfo',
141
152
  /** 租户级文件上传设置(单文件大小上限 + 格式黑白名单) */
142
153
  tenantSetting: '/api/file/tenant/setting',
143
154
  };
@@ -150,7 +150,7 @@ export function createWorkflowHost(ctx, dependencies = {}) {
150
150
  return sessionContext;
151
151
  }
152
152
  async function run(request, responseType) {
153
- const { method = 'POST', path, query, body, form, formData, operation, timeoutMs = DEFAULT_TIMEOUT_MS, } = request;
153
+ const { method = 'POST', path, query, headers, body, form, formData, operation, timeoutMs = DEFAULT_TIMEOUT_MS, } = request;
154
154
  const runtime = createE10RequestRuntime(ctx, await ensureSession(), {
155
155
  interceptors: dependencies.interceptors,
156
156
  fetch: dependencies.fetch,
@@ -160,7 +160,7 @@ export function createWorkflowHost(ctx, dependencies = {}) {
160
160
  });
161
161
  let response;
162
162
  try {
163
- const base = { method, path, query, body, form, formData, operation, timeoutMs, module: 'workflow' };
163
+ const base = { method, path, query, headers, body, form, formData, operation, timeoutMs, module: 'workflow' };
164
164
  response = responseType === 'binary'
165
165
  ? await runtime.requestBinary(base)
166
166
  : await runtime.requestJson(base);
@@ -81,47 +81,17 @@ function failureEnvelope(error) {
81
81
  */
82
82
  export { compactStdoutPaths };
83
83
  /**
84
- * 「stdout 整体直出」的判定 —— 目前**只有小e 环境的 `BROWSER_FILL` 一档**适用。
85
- *
86
- * 用户 2026-10-15 裁定:「xiaoe 下应该直接输出 `<browsertool>` 标签」。即该档的 stdout **就是**
87
- * 那段标签块本身,**不再套** `{schemaVersion, ok, operation, data, warnings}` 信封 —— 于是
88
- * `workflow-create.md` 里「返回内容以 `<browsertool>` 开头 ⇒ 原样输出」这句是**字面成立**的。
89
- * 代价(用户已知悉并接受):这一档模型拿不到 `status` / `contextId`(本档不写服务端、句柄也未消费,
90
- * 需要重发时由用户重新发起即可)。
91
- *
92
- * 两个条件必须同时成立,缺一就退回信封 —— 宁可不直出,也不让调用方拿到半截或空输出:
93
- * - `data.status === 'BROWSER_FILL'`:小e 专用档,其它状态(含 `NEEDS_INPUT`)一律维持信封;
94
- * - `data.finalMarkdown` 以 `<browsertool>` 开头:与文档口径同字面,渲染档万一变了也不会直出一个
95
- * 看起来像标签、实际不是的东西。
96
- *
97
- * 必须走 `ctx.output.raw()` 而不是 `write(value, { text })`:后者在**全局 `--json`** 下仍会退化成
98
- * JSON(`core/output.ts`:`useJson = globalOptions.json || options.json || !options.text`),
99
- * 而本 skill 的文档一律带 `--json`。
100
- */
101
- export function rawStdoutOf(result) {
102
- const data = result.data;
103
- if (!data || typeof data !== 'object')
104
- return undefined;
105
- if (data.status !== 'BROWSER_FILL')
106
- return undefined;
107
- const markdown = data.finalMarkdown;
108
- if (typeof markdown !== 'string' || !markdown.startsWith('<browsertool>'))
109
- return undefined;
110
- return markdown;
111
- }
112
- /**
113
- * 把一次 operation 的结果打到 stdout —— **形态二选一**(判定见 `rawStdoutOf`)。
84
+ * 把一次 operation 的结果打到 stdout —— **只有一种形态:JSON 信封**。
114
85
  *
115
86
  * 独立成函数并**导出**,是为了让用例直接锁住这条接线(与 `compactStdoutPaths` 同一理由:
116
87
  * 回归要在这里红,而不是等到真机上模型拿到一份"既不是 JSON、也不是标签块"的东西才发现)。
88
+ *
89
+ * ★ 2026-10-21 用户裁定(**改口径**):小e 的 `BROWSER_FILL` 档**不再直出** `<browsertool>` 标签块,
90
+ * 一律走 JSON 信封 —— 标签块放在 `data.finalMarkdown` 里,与 `createRequestDirect` 统一成一种结构,
91
+ * 于是 `durationMs` / `apiDurations` / `authMs` 三个耗时键在这一档也可见。
92
+ * 原来的 `rawStdoutOf()` 直出通道随之删除(它当时只为"stdout 就是标签块"服务)。
117
93
  */
118
94
  export function printWorkflowResult(ctx, operation, result) {
119
- const raw = rawStdoutOf(result);
120
- if (raw !== undefined) {
121
- // 小e:stdout 就是标签块本身 —— 不套信封、不进 `prepare()` 的超线落盘(这段本来就不长)。
122
- ctx.output.raw(raw);
123
- return;
124
- }
125
95
  ctx.output.write(successEnvelope(operation, result), {
126
96
  json: true,
127
97
  compactPaths: compactStdoutPaths(operation),