weaver-work-cli 0.1.28 → 0.1.29

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 (40) hide show
  1. package/dist/shortcuts/invoice/host.js +2 -1
  2. package/dist/shortcuts/invoice/manifest.js +2 -1
  3. package/dist/shortcuts/invoice/operations/issuing-qys.js +132 -36
  4. package/dist/shortcuts/workflow/index.js +3 -0
  5. package/dist/shortcuts/workflow/operations/read.js +13 -6
  6. package/dist/shortcuts/workflow/operations/registry.js +2 -0
  7. package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +331 -0
  8. package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +133 -0
  9. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +326 -0
  10. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +113 -0
  11. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +259 -0
  12. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoListRows.js +64 -0
  13. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoOutputPlanner.js +28 -0
  14. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.aggregate.js +70 -0
  15. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +267 -0
  16. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +392 -0
  17. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +165 -0
  18. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +448 -0
  19. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +348 -0
  20. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +222 -0
  21. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +66 -0
  22. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +502 -0
  23. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +540 -0
  24. package/docs/_catalog.md +1 -0
  25. package/docs/invoice.md +3 -3
  26. package/docs/todo-smart-convert/README.md +171 -0
  27. package/docs/todo-smart-convert/data-contract.md +236 -0
  28. package/docs/todo-smart-convert/mock/ai-todo-query-target.json +209 -0
  29. package/docs/todo-smart-convert/subgroup-rules.md +245 -0
  30. package/package.json +1 -1
  31. package/scripts/package-skill.mjs +1 -1
  32. package/skill-template/domains/yepiaotong.md +2 -2
  33. package/skills/weaver-e10-workflow/SKILL.md +23 -1
  34. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +282 -0
  35. package/skills/weaver-e10-workflow/scripts/todo-summary.cjs +695 -0
  36. package/skills/weaver-e10-yepiaotong/SKILL.md +6 -6
  37. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +10 -7
  38. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +41 -14
  39. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +54 -26
  40. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +2 -2
@@ -185,7 +185,7 @@ export function createInvoiceHost(ctx, dependencies = {}) {
185
185
  throw businessError(value);
186
186
  }
187
187
  async function invoiceRequest(request, canRetryExpiredToken = true) {
188
- const { method = 'POST', path, query, body, operation, needsInvoiceToken = true, timeoutMs = 30_000, } = request;
188
+ const { method = 'POST', path, query, headers, body, operation, needsInvoiceToken = true, timeoutMs = 30_000, } = request;
189
189
  assertPath(path);
190
190
  const token = await ensureContext(needsInvoiceToken, timeoutMs, path);
191
191
  const response = await (await runtime()).requestJson({
@@ -194,6 +194,7 @@ export function createInvoiceHost(ctx, dependencies = {}) {
194
194
  method,
195
195
  path,
196
196
  query,
197
+ headers,
197
198
  body,
198
199
  timeoutMs,
199
200
  trustedHeaders: trustedInvoiceHeaders(token),
@@ -671,6 +671,7 @@ const qysListOutputSchema = objectSchema({
671
671
  goodsNames: arrayOf({ type: 'string' }),
672
672
  taxRates: arrayOf({ type: 'string' }),
673
673
  specifications: arrayOf({ type: 'string' }, { description: '可开票行“规格型号”去重(开票流程主表“所售产品版本号”取此值)' }),
674
+ sellerNames: arrayOf({ type: 'string' }, { description: '台账“核算公司”去重,即所属公司(销售方);空名称不计入。用于选择开票流程' }),
674
675
  rowCount: { type: 'integer' },
675
676
  formDataIds: arrayOf({ type: 'string' }),
676
677
  totalAmount: { type: 'string', description: '可开票行“开票金额”合计,按分累加(开票流程按含税金额使用)' },
@@ -1197,7 +1198,7 @@ export const invoiceOperations = [
1197
1198
  {
1198
1199
  name: 'invoice.issuing.qys.list',
1199
1200
  risk: 'read',
1200
- fixed: { path: '/api/ebuilder/form/eblist/getData', ledger: '契约锁合同商品信息' },
1201
+ fixed: { path: '/api/ebuilder/form/list/getListDatas', ledger: '待开票合同数据' },
1201
1202
  inputSchema: {
1202
1203
  type: 'object',
1203
1204
  additionalProperties: false,
@@ -1,13 +1,16 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { InvoiceCliError, invoiceValidationError } from '../errors.js';
2
3
  import { asNonEmptyString, assertBusinessSuccess } from './shared.js';
3
- const LEDGER_PATH = '/api/ebuilder/form/eblist/getData';
4
- const LEDGER_PAGE_ID = '1314988320430981804';
4
+ const LEDGER_PATH = '/api/ebuilder/form/list/getListDatas';
5
+ const LEDGER_LIST_ID = '1201416963938369627';
6
+ const LEDGER_PASS_ID = '1201416959744065575';
5
7
  const LEDGER_APP_ID = '846297243998732291';
6
- const LEDGER_MENU_ID = '5419015526244625718';
7
- const LEDGER_PAGE_TITLE = '5aWR57qm6ZSB5ZCI5ZCM5ZWG5ZOB5L h5oGv';
8
+ const LEDGER_MENU_ID = '5416292802968839298';
9
+ /** urlPageTitle:合同商品信息 */
10
+ const LEDGER_PAGE_TITLE = '5ZCI5ZCM5ZWG5ZOB5L+h5oGv';
8
11
  const LEDGER_PAGE_SIZE = 50;
9
12
  const LEDGER_MAX_PAGES = 40;
10
- /** 契约锁合同商品信息表:数据 key → 可读字段。 */
13
+ /** 待开票合同数据:数据 key → 可读字段。含契约锁、泛微及其他合同。 */
11
14
  export const QYS_LEDGER_FIELDS = [
12
15
  { id: '1314988311673275798', key: 'contractNo', label: '合同号', kind: 'text' },
13
16
  { id: '1314988311673275801', key: 'contractFlow', label: '合同审批流程', kind: 'relation' },
@@ -106,49 +109,136 @@ export function qysRowBlockers(row) {
106
109
  blockers.push('已废弃');
107
110
  return blockers;
108
111
  }
109
- async function fetchLedgerPage(host, pageNo, operation) {
110
- const fieldFilter = {
111
- cusMenuId: LEDGER_MENU_ID,
112
- urlPageTitle: LEDGER_PAGE_TITLE,
113
- [`appId_${LEDGER_PAGE_ID}`]: LEDGER_APP_ID,
114
- cusview: '0',
112
+ function parseSpan(value) {
113
+ if (typeof value === 'string' && value.trim().startsWith('[')) {
114
+ try {
115
+ return optionValue(JSON.parse(value));
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ return optionValue(value);
122
+ }
123
+ function cellText(data, display, key) {
124
+ const raw = data[key] !== undefined && data[key] !== null && data[key] !== '' ? data[key] : display[key];
125
+ return textValue(raw);
126
+ }
127
+ function moneyText(data, display, key) {
128
+ const text = cellText(data, display, key);
129
+ return text === null ? null : text.replace(/,/g, '');
130
+ }
131
+ /** 获取待开票合同数据:getListDatas 的 data / displayData 行,转成与旧列表相同的可读字段。 */
132
+ export function convertPendingLedgerRow(data, display = {}) {
133
+ const formDataId = textValue(data.form_data_id) ?? textValue(data.id);
134
+ if (!formDataId)
135
+ throw invoiceValidationError('ledger_row_invalid', '台账行缺少 form_data_id');
136
+ const statusText = textValue(display.data_status);
137
+ return {
138
+ formDataId,
139
+ title: textValue(data.name) ?? textValue(display.name),
140
+ dataStatus: statusText && !/^\d+$/u.test(statusText) ? statusText : null,
141
+ contractNo: cellText(data, display, 'hth'),
142
+ contractFlow: parseSpan(display.htsplcSpan),
143
+ contractCategory: cellText(data, display, 'htzl'),
144
+ productType: parseSpan(display.cplxSpan),
145
+ productTypeDetail: cellText(data, display, 'cplxmx'),
146
+ accountingCompany: parseSpan(display.hsgs_idSpan),
147
+ invoiceGroupFlag: cellText(data, display, 'kpfzbs'),
148
+ sourceFlag: cellText(data, display, 'lybs'),
149
+ flowDetailId: cellText(data, display, 'lcmxid'),
150
+ invoiceType: parseSpan(display.fplxSpan),
151
+ purchaserName: cellText(data, display, 'gmfmc'),
152
+ purchaserTaxNo: cellText(data, display, 'gmfsh'),
153
+ purchaserAddress: cellText(data, display, 'gmfdz'),
154
+ purchaserTel: cellText(data, display, 'gmfdh'),
155
+ purchaserBankName: cellText(data, display, 'gmfkhx'),
156
+ purchaserBankAccount: cellText(data, display, 'gmfkhxzh'),
157
+ invoiceItemName: cellText(data, display, 'kpmc'),
158
+ goodsName: cellText(data, display, 'spmc'),
159
+ revenueCode: cellText(data, display, 'spshflbm'),
160
+ taxRate: parseSpan(display.spslSpan),
161
+ specification: cellText(data, display, 'ggxh'),
162
+ unit: cellText(data, display, 'dw'),
163
+ quantity: cellText(data, display, 'spslkwk'),
164
+ unitPrice: moneyText(data, display, 'spdjkwk'),
165
+ amount: moneyText(data, display, 'kpje'),
166
+ uninvoicedAmount: moneyText(data, display, 'wkpje'),
167
+ invoicedAmount: moneyText(data, display, 'ykpje'),
168
+ invoicingAmount: moneyText(data, display, 'kpzje'),
169
+ amountDiffers: parseSpan(display.kpjeczcySpan),
170
+ thirdPartyPurchase: parseSpan(display.sfsfcgSpan),
171
+ invoiceNo: cellText(data, display, 'fphm'),
172
+ invoiceFlow: parseSpan(display.kplcidSpan),
173
+ invoiceDate: cellText(data, display, 'kpsj'),
174
+ discarded: parseSpan(display.sfzfSpan),
175
+ discardedDate: cellText(data, display, 'zfsj'),
176
+ contactName: cellText(data, display, 'khlxr'),
177
+ contactPhone: cellText(data, display, 'khlxdh'),
178
+ contactEmail: cellText(data, display, 'khlxyx'),
115
179
  };
116
- const result = await host.invoiceMultipartRequest({
180
+ }
181
+ async function fetchLedgerPage(host, pageNo, tableuid, operation) {
182
+ const result = await host.invoiceRequest({
117
183
  path: LEDGER_PATH,
118
- headers: { ebbusinessid: LEDGER_PAGE_ID },
119
- fields: {
120
- ...fieldFilter,
121
- filter: '[]',
122
- fieldFilter,
123
- pageFilter: { filter: {}, filterIsNull: true, esbFilter: [] },
124
- pageAppId: LEDGER_APP_ID,
125
- compId: 'e00e8244de584b369417a4fea31d7bed',
126
- pageId: LEDGER_PAGE_ID,
127
- pageNo: String(pageNo),
128
- pageSize: String(LEDGER_PAGE_SIZE),
129
- tableuid: 'a74d995bddd54a4f831551b468071632',
130
- refPluginPackageId: '933846626099339271',
131
- },
184
+ query: { passid: LEDGER_PASS_ID, cid: LEDGER_PASS_ID },
185
+ headers: { ebbusinessid: LEDGER_LIST_ID },
132
186
  operation,
187
+ body: {
188
+ current: pageNo,
189
+ pageSize: LEDGER_PAGE_SIZE,
190
+ requiredMatch: 1,
191
+ tableuid,
192
+ mapResult: '1',
193
+ recheckQuery: '1',
194
+ cusMenuId: LEDGER_MENU_ID,
195
+ urlPageTitle: LEDGER_PAGE_TITLE,
196
+ [`appId_${LEDGER_LIST_ID}`]: LEDGER_APP_ID,
197
+ searchParamData: {
198
+ searchType: '3',
199
+ quickSearchFields: {},
200
+ commonSearchFields: {},
201
+ type: 'and',
202
+ groupFields: {},
203
+ },
204
+ listId: LEDGER_LIST_ID,
205
+ isFollow: 0,
206
+ isShareToMe: 0,
207
+ filterCondition: {},
208
+ browser: 'Chrome',
209
+ },
133
210
  });
134
- assertBusinessSuccess(result, 'qys_ledger_query_failed', '契约锁开票台账查询失败');
135
- const list = result?.data?.list;
136
- if (!Array.isArray(list)) {
137
- throw new InvoiceCliError('api', 'qys_ledger_query_failed', '契约锁开票台账返回缺少 data.list', { exitCode: 1 });
211
+ assertBusinessSuccess(result, 'qys_ledger_query_failed', '获取待开票合同数据失败');
212
+ const payload = result?.data;
213
+ const dataRows = payload?.data;
214
+ const displayRows = payload?.displayData;
215
+ if (!Array.isArray(dataRows) || !Array.isArray(displayRows)) {
216
+ throw new InvoiceCliError('api', 'qys_ledger_query_failed', '获取待开票合同数据的返回缺少 data.data 或 data.displayData', { exitCode: 1 });
138
217
  }
139
- return list;
218
+ return dataRows.map((data, index) => ({
219
+ data,
220
+ display: displayRows[index] && typeof displayRows[index] === 'object' ? displayRows[index] : {},
221
+ }));
140
222
  }
141
- /** 接口返回的 count 恒为 0,只能按“本页未取满”判断结束。 */
223
+ /** total / hasMore 不可靠,按本页未取满或出现重复行结束。 */
142
224
  async function scanLedger(host, operation, visit) {
225
+ const tableuid = randomUUID().replace(/-/g, '');
226
+ const seen = new Set();
143
227
  let scanned = 0;
144
228
  for (let pageNo = 1; pageNo <= LEDGER_MAX_PAGES; pageNo += 1) {
145
- const page = await fetchLedgerPage(host, pageNo, operation);
146
- for (const raw of page) {
229
+ const page = await fetchLedgerPage(host, pageNo, tableuid, operation);
230
+ let fresh = 0;
231
+ for (const pair of page) {
232
+ const row = convertPendingLedgerRow(pair.data, pair.display);
233
+ if (seen.has(row.formDataId))
234
+ continue;
235
+ seen.add(row.formDataId);
236
+ fresh += 1;
147
237
  scanned += 1;
148
- if (visit(convertQysLedgerRow(raw)))
238
+ if (visit(row))
149
239
  return { scanned, complete: true };
150
240
  }
151
- if (page.length < LEDGER_PAGE_SIZE)
241
+ if (page.length < LEDGER_PAGE_SIZE || fresh === 0)
152
242
  return { scanned, complete: true };
153
243
  }
154
244
  return { scanned, complete: false };
@@ -198,6 +288,7 @@ export class QysLedgerGrouper {
198
288
  goodsNames: new Set(),
199
289
  taxRates: new Set(),
200
290
  specifications: new Set(),
291
+ sellerNames: new Set(),
201
292
  bankName: null,
202
293
  bankAccount: null,
203
294
  formDataIds: [],
@@ -207,6 +298,8 @@ export class QysLedgerGrouper {
207
298
  };
208
299
  this.groups.set(key, group);
209
300
  }
301
+ if (row.accountingCompany?.name)
302
+ group.sellerNames.add(row.accountingCompany.name);
210
303
  if (blocked) {
211
304
  group.blockedRows += 1;
212
305
  return;
@@ -255,6 +348,8 @@ export class QysLedgerGrouper {
255
348
  }
256
349
  if (!group.contractNo)
257
350
  warnings.push('台账行缺少合同号,无法按合同号发起开票流程');
351
+ if (group.sellerNames.size > 1)
352
+ warnings.push(`同组销售方(核算公司)不一致:${[...group.sellerNames].join('、')}`);
258
353
  if (group.invoiceTypes.size > 1)
259
354
  warnings.push(`同组发票类型不一致:${[...group.invoiceTypes].join('、')}`);
260
355
  if (group.purchaserTaxNos.size > 1)
@@ -277,6 +372,7 @@ export class QysLedgerGrouper {
277
372
  goodsNames: [...group.goodsNames],
278
373
  taxRates: [...group.taxRates],
279
374
  specifications: [...group.specifications],
375
+ sellerNames: [...group.sellerNames],
280
376
  rowCount: group.formDataIds.length,
281
377
  formDataIds: group.formDataIds,
282
378
  totalAmount: formatCents(group.amountCents),
@@ -95,6 +95,9 @@ export function printWorkflowResult(ctx, operation, result) {
95
95
  ctx.output.write(successEnvelope(operation, result), {
96
96
  json: true,
97
97
  compactPaths: compactStdoutPaths(operation),
98
+ // todoSmartConvert 的业务契约要求直接返回完整 requestDatas/displayItems;
99
+ // 禁止公共输出层按 24KB 预算递归折叠该分支(2026-10-27 用户要求,勿删)。
100
+ persist: operation !== 'workflow.todoSmartConvert',
98
101
  });
99
102
  }
100
103
  async function executeAndPrint(ctx, operation, input) {
@@ -892,7 +892,7 @@ async function resolveFilterFieldItem(env, raw, fields, workflowLabel, ordinal)
892
892
  throw workflowValidationError('field_type_unsupported', `字段「${field.fieldName}」的组件类型 ${field.componentKey} 暂不支持作为筛选条件(filterFields 第 ${ordinal} 项),`
893
893
  + '本次查询已停止。请改用文本/数值/日期/选项类字段。');
894
894
  }
895
- async function buildSearchQuery(env, args, mode) {
895
+ export async function buildSearchQuery(env, args, mode) {
896
896
  // 1. 原始 JSON 直传(--query-json / --query-file 透传;CLI 侧用 queryJson 字段承载)
897
897
  const rawJson = args.queryJson;
898
898
  let query;
@@ -3131,7 +3131,7 @@ const workflowDetail = {
3131
3131
  });
3132
3132
  },
3133
3133
  };
3134
- function normalizeDetailResponse(response) {
3134
+ export function normalizeDetailResponse(response) {
3135
3135
  // ★ 层级:`detailDataOf` 负责"`responseData()` 已解一层 `data`"这件事(真机实测响应为
3136
3136
  // `{message, data:{workflowBaseInfo, otherInfo, formData}}`、**无内层 `data`**,与 V11.3 同口径)。
3137
3137
  // 旧实现在这里又取了一次 `.data` ⇒ 实际拿到 `response.data.data`(不存在)⇒ `workflow.detail` 与
@@ -4472,14 +4472,14 @@ async function enrichWorkflowCardSummaries(env, normalized, maxItems = 10) {
4472
4472
  };
4473
4473
  return normalized;
4474
4474
  }
4475
- function normalizeDetailSummaryValue(_name, value) {
4475
+ function normalizeDetailSummaryValue(_name, value, maxChars = 180) {
4476
4476
  let text = stripHtml(value);
4477
4477
  text = text.replace(/https?:\/\/\S+/gi, '').trim();
4478
4478
  // 与 V11.3 一致:标点只裁**首尾**(Python `.strip(" ::;;,.,。")`),不删正文里的标点——
4479
4479
  // 旧实现用 `/[...]/g` 删掉了「金额:1000」这类值里的冒号,摘要读起来是「金额1000」。
4480
4480
  text = text.replace(/\s+/g, ' ').replace(/^[ ::;;,.,。]+|[ ::;;,.,。]+$/g, '');
4481
4481
  // 截断同样走 V11.3 的标点边界口径(旧实现是硬 slice,会把词截成半截)
4482
- return truncateText(text, 180);
4482
+ return truncateText(text, maxChars);
4483
4483
  }
4484
4484
  function isMeaninglessSummaryValue(value) {
4485
4485
  const text = (value || '').trim();
@@ -4505,13 +4505,20 @@ function looksLikeUrlOrDoc(value) {
4505
4505
  const stripped = text.replace(/https?:\/\/\S+/gi, '').trim().replace(/[::;;,.,。"'“” \t\r\n]/g, '');
4506
4506
  return stripped.length < 20;
4507
4507
  }
4508
- function selectSummaryFields(fields, limit = 12) {
4508
+ /**
4509
+ * 摘要字段筛选(业务字段白名单 + 信息量排序,最多 `limit` 个)。
4510
+ * `maxValueChars` 默认 180(列表卡片口径,既有调用方不受影响)。
4511
+ *
4512
+ * `options.fullValue = true` 时**不做任何值裁剪**(不截断、不切句),原值直接给下游
4513
+ * ——待办汇总喂 AI 用这个口径(用户 2026-10-21:全量给,不做 400 字截断)。
4514
+ */
4515
+ export function selectSummaryFields(fields, limit = 12, maxValueChars = 180, options = {}) {
4509
4516
  const candidates = [];
4510
4517
  const seen = new Set();
4511
4518
  const collectField = (field) => {
4512
4519
  const name = stripHtml(field?.fieldName ?? '');
4513
4520
  const rawValue = stripHtml(field?.value ?? '');
4514
- const value = normalizeDetailSummaryValue(name, rawValue);
4521
+ const value = options.fullValue ? rawValue : normalizeDetailSummaryValue(name, rawValue, maxValueChars);
4515
4522
  if (!name || !value)
4516
4523
  return false;
4517
4524
  if (isMeaninglessSummaryValue(rawValue))
@@ -1,6 +1,7 @@
1
1
  import { workflowValidationError } from '../errors.js';
2
2
  import { workflowFormOperations } from './form.js';
3
3
  import { workflowReadOperations } from './read.js';
4
+ import { workflowTodoSmartConvertOperations } from './todoSmartConvert/todoSmartConvert.js';
4
5
  import { workflowWriteOperations } from './write.js';
5
6
  function buildRegistry() {
6
7
  const registry = new Map();
@@ -8,6 +9,7 @@ function buildRegistry() {
8
9
  ...workflowReadOperations,
9
10
  ...workflowFormOperations,
10
11
  ...workflowWriteOperations,
12
+ ...workflowTodoSmartConvertOperations,
11
13
  ];
12
14
  for (const definition of definitions) {
13
15
  if (registry.has(definition.name)) {