@qinsilk/scm-agent-tools 0.1.0 → 0.2.0
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 +1 -1
- package/cli/commands/data.js +112 -0
- package/cli/commands/docs.js +121 -0
- package/cli/commands/query.js +58 -2
- package/cli/datasets.js +541 -0
- package/cli/scm-agent.js +6 -1
- package/docs/api.md +2067 -0
- package/docs/cli.md +72 -0
- package/package.json +2 -2
- package/skills/qinsilk-scm/SKILL.md +32 -1
package/cli/datasets.js
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
// data 命令族的数据集定义:每个数据集对应一个后端报表数据接口。
|
|
2
|
+
// 这些接口原本只给官方报表(reports/*.js)在内部调用,这里给 AI 一条受控入口:
|
|
3
|
+
// 强制条件在本文件声明,校验不通过时不发请求;分页数据集一次只取一页。
|
|
4
|
+
// 后端接口的参数与默认值一律不改(官方报表依赖它们拉全量),本文件只做参数映射。
|
|
5
|
+
//
|
|
6
|
+
// 字段含义:
|
|
7
|
+
// name 命令里写的数据集名(scm-agent data <name>)
|
|
8
|
+
// describe 一句话用途,data --list 与报错时展示
|
|
9
|
+
// path 后端接口路径(全部是 GET)
|
|
10
|
+
// paged true 返回 { page, limit, total, list };false 返回单个对象或数组
|
|
11
|
+
// options 参数映射:name 是命令参数(--name),api 是后端参数名;type 供 yargs 声明
|
|
12
|
+
// require 强制条件,见 validateArgs;null 表示不要求(名录或单条配置类)
|
|
13
|
+
// columns 不加 --json 时的表格列;单条对象不设 columns,直接输出 JSON
|
|
14
|
+
// example 校验失败时展示的可复制示例命令
|
|
15
|
+
|
|
16
|
+
/** 日期区间数据集的跨度上限(日历日),已由开发者确认 */
|
|
17
|
+
const MAX_SPAN_DAYS = 92;
|
|
18
|
+
|
|
19
|
+
/** material-data 一次最多查的生产单数(服务端硬限制) */
|
|
20
|
+
const MAX_SNS = 20;
|
|
21
|
+
|
|
22
|
+
const DATASETS = [
|
|
23
|
+
{
|
|
24
|
+
name: 'company-config',
|
|
25
|
+
describe: '公司配置白名单:物料小数位、是否启用生产批次号',
|
|
26
|
+
path: '/api/agent/v1/company-config',
|
|
27
|
+
paged: false,
|
|
28
|
+
options: [],
|
|
29
|
+
require: null,
|
|
30
|
+
columns: null,
|
|
31
|
+
example: 'scm-agent data company-config --json',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'goods-rich',
|
|
35
|
+
describe: '商品富化列表(档案、价格、库存、首单面料、翻单信息);默认只返回上架款',
|
|
36
|
+
path: '/api/agent/v1/goods/rich-list',
|
|
37
|
+
paged: true,
|
|
38
|
+
options: [
|
|
39
|
+
{ name: 'sn', api: 'sn', type: 'string', describe: '商品货号' },
|
|
40
|
+
{ name: 'searchKey', api: 'searchKey', type: 'string', describe: '关键词:名称/条码' },
|
|
41
|
+
{ name: 'onSale', api: 'isOnSale', type: 'number', describe: '上下架过滤:1 只看上架(默认) 0 只看下架 -1 全部' },
|
|
42
|
+
],
|
|
43
|
+
require: { anyOf: ['sn', 'searchKey'] },
|
|
44
|
+
columns: [
|
|
45
|
+
{ field: 'goodsSn', label: '款号' },
|
|
46
|
+
{ field: 'name', label: '名称' },
|
|
47
|
+
{ field: 'categoryName', label: '分类' },
|
|
48
|
+
{ field: 'storedNumber', label: '当前库存' },
|
|
49
|
+
{ field: 'availableNumber', label: '可用库存' },
|
|
50
|
+
{ field: 'produceNumber', label: '生产中' },
|
|
51
|
+
{ field: 'costPrice', label: '成本价' },
|
|
52
|
+
{ field: 'devTime', label: '开发日期' },
|
|
53
|
+
],
|
|
54
|
+
example: 'scm-agent data goods-rich --sn A123 --json',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'goods-bom',
|
|
58
|
+
describe: '商品完整详情:BOM 物料清单、工序单价、尺寸表(129 个字段,单条)',
|
|
59
|
+
path: '/api/agent/v1/goods/full-detail',
|
|
60
|
+
paged: false,
|
|
61
|
+
options: [
|
|
62
|
+
{ name: 'id', api: 'id', type: 'number', describe: '商品id(可先用 goods-rich 或 goods search 查到)' },
|
|
63
|
+
],
|
|
64
|
+
require: { allOf: ['id'] },
|
|
65
|
+
columns: null,
|
|
66
|
+
example: 'scm-agent data goods-bom --id 12345 --json',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'sku-inventory',
|
|
70
|
+
describe: '单品库存列表(只含已转大货的商品);必须写明单仓库还是全部仓库合计',
|
|
71
|
+
path: '/api/agent/v1/inventory/sku-list',
|
|
72
|
+
paged: true,
|
|
73
|
+
options: [
|
|
74
|
+
{ name: 'storehouse', api: 'storehouseId', type: 'number', describe: '仓库 id(只统计该仓库的库存)' },
|
|
75
|
+
{ name: 'allStorehouses', api: null, type: 'boolean', describe: '全部仓库合计(与 --storehouse 互斥)' },
|
|
76
|
+
],
|
|
77
|
+
require: { choice: ['storehouse', 'allStorehouses'] },
|
|
78
|
+
columns: [
|
|
79
|
+
{ field: 'goodsSn', label: '款号' },
|
|
80
|
+
{ field: 'goodsName', label: '商品' },
|
|
81
|
+
{ field: 'colorName', label: '颜色' },
|
|
82
|
+
{ field: 'sizeName', label: '尺码' },
|
|
83
|
+
{ field: 'storedNumber', label: '库存数' },
|
|
84
|
+
{ field: 'availableNumber', label: '可用库存' },
|
|
85
|
+
{ field: 'winNumber', label: '待入库' },
|
|
86
|
+
{ field: 'woutNumber', label: '待出库' },
|
|
87
|
+
],
|
|
88
|
+
example: 'scm-agent data sku-inventory --storehouse 5 --json(或 --all-storehouses)',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'produce-cost',
|
|
92
|
+
describe: '生产成本报表数据(每行含五类成本明细);服务端默认状态 4,2,3、默认计算规则 1',
|
|
93
|
+
path: '/api/agent/v1/report/produce-cost',
|
|
94
|
+
paged: true,
|
|
95
|
+
options: [
|
|
96
|
+
{ name: 'from', api: 'from', type: 'string', describe: '业务日期起(yyyy-MM-dd)' },
|
|
97
|
+
{ name: 'to', api: 'to', type: 'string', describe: '业务日期止(yyyy-MM-dd)' },
|
|
98
|
+
{ name: 'state', api: 'state', type: 'string', describe: '状态筛选,逗号分隔;不传用服务端默认 4,2,3' },
|
|
99
|
+
{ name: 'calWay', api: 'calWay', type: 'number', describe: '成本计算规则 1 默认 2 按成衣入库数 3 按裁床数;不传默认 1' },
|
|
100
|
+
{ name: 'searchKey', api: 'searchKey', type: 'string', describe: '综合关键词:款号/名称/货号/条码/供应商货号' },
|
|
101
|
+
{ name: 'ordersSn', api: 'ordersSn', type: 'string', describe: '生产单号或批次号' },
|
|
102
|
+
],
|
|
103
|
+
require: { allOf: ['from', 'to'], spanDays: MAX_SPAN_DAYS },
|
|
104
|
+
columns: [
|
|
105
|
+
{ field: 'ordersSn', label: '生产单号' },
|
|
106
|
+
{ field: 'designSn', label: '款号' },
|
|
107
|
+
{ field: 'businessTime', label: '业务日期' },
|
|
108
|
+
{ field: 'sumNumber', label: '下单数' },
|
|
109
|
+
{ field: 'sumCutBedNumber', label: '裁床数' },
|
|
110
|
+
{ field: 'sumStoreInNumber', label: '已入库数' },
|
|
111
|
+
],
|
|
112
|
+
example: 'scm-agent data produce-cost --from 2026-09-01 --to 2026-09-30 --json',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'produce-material',
|
|
116
|
+
describe: '物料需求明细(在途与缺口);服务端默认只查用料进行中的行',
|
|
117
|
+
path: '/api/agent/v1/material-requirement/list',
|
|
118
|
+
paged: true,
|
|
119
|
+
options: [
|
|
120
|
+
{ name: 'from', api: 'from', type: 'string', describe: '业务日期起(yyyy-MM-dd)' },
|
|
121
|
+
{ name: 'to', api: 'to', type: 'string', describe: '业务日期止(yyyy-MM-dd)' },
|
|
122
|
+
{ name: 'spoState', api: 'spoState', type: 'string', describe: '生产单状态,逗号分隔,如 2,4' },
|
|
123
|
+
{ name: 'useState', api: 'useState', type: 'number', describe: '用料状态 0 进行中(默认) 1 已结束' },
|
|
124
|
+
],
|
|
125
|
+
require: { allOf: ['from', 'to'], spanDays: MAX_SPAN_DAYS },
|
|
126
|
+
columns: [
|
|
127
|
+
{ field: 'ordersSn', label: '生产单号' },
|
|
128
|
+
{ field: 'goodsSn', label: '款号' },
|
|
129
|
+
{ field: 'sn', label: '物料货号' },
|
|
130
|
+
{ field: 'name', label: '物料名称' },
|
|
131
|
+
{ field: 'colorName', label: '颜色' },
|
|
132
|
+
{ field: 'orderRequirementNumber', label: '预计用量' },
|
|
133
|
+
{ field: 'purchaseNumber', label: '采购数' },
|
|
134
|
+
{ field: 'purchaseWinNumber', label: '待入库' },
|
|
135
|
+
],
|
|
136
|
+
example: 'scm-agent data produce-material --from 2026-09-01 --to 2026-09-30 --json',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'produce-store-in',
|
|
140
|
+
describe: '生产入库单列表',
|
|
141
|
+
path: '/api/agent/v1/produce-store-in/list',
|
|
142
|
+
paged: true,
|
|
143
|
+
options: [
|
|
144
|
+
{ name: 'from', api: 'from', type: 'string', describe: '业务日期起(yyyy-MM-dd)' },
|
|
145
|
+
{ name: 'to', api: 'to', type: 'string', describe: '业务日期止(yyyy-MM-dd)' },
|
|
146
|
+
{ name: 'sn', api: 'sn', type: 'string', describe: '入库单号' },
|
|
147
|
+
{ name: 'searchKey', api: 'searchKey', type: 'string', describe: '关键词' },
|
|
148
|
+
],
|
|
149
|
+
require: { allOf: ['from', 'to'], spanDays: MAX_SPAN_DAYS },
|
|
150
|
+
columns: [
|
|
151
|
+
{ field: 'ordersSn', label: '入库单号' },
|
|
152
|
+
{ field: 'produceOrdersSn', label: '生产单号' },
|
|
153
|
+
{ field: 'businessTime', label: '业务日期' },
|
|
154
|
+
{ field: 'goodsSn', label: '款号' },
|
|
155
|
+
{ field: 'sumStoreInNumber', label: '入库数量' },
|
|
156
|
+
{ field: 'sumInNumber', label: '已入库' },
|
|
157
|
+
{ field: 'storehouseName', label: '仓库' },
|
|
158
|
+
],
|
|
159
|
+
example: 'scm-agent data produce-store-in --from 2026-09-01 --to 2026-09-30 --json',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'produce-store-in-detail',
|
|
163
|
+
describe: '生产入库单详情(入库商品明细与委外加工费,单条)',
|
|
164
|
+
path: '/api/agent/v1/produce-store-in/detail',
|
|
165
|
+
paged: false,
|
|
166
|
+
options: [
|
|
167
|
+
{ name: 'sn', api: 'sn', type: 'string', describe: '入库单号(可先用 produce-store-in 列表查到)' },
|
|
168
|
+
],
|
|
169
|
+
require: { allOf: ['sn'] },
|
|
170
|
+
columns: null,
|
|
171
|
+
example: 'scm-agent data produce-store-in-detail --sn SCRK202609010001 --json',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'store-in-statistics',
|
|
175
|
+
describe: '成品入库统计(按业务日期,行级含颜色尺码与金额)',
|
|
176
|
+
path: '/api/agent/v1/statistics/store-in-goods',
|
|
177
|
+
paged: true,
|
|
178
|
+
options: [
|
|
179
|
+
{ name: 'from', api: 'from', type: 'string', describe: '业务日期起(yyyy-MM-dd)' },
|
|
180
|
+
{ name: 'to', api: 'to', type: 'string', describe: '业务日期止(yyyy-MM-dd)' },
|
|
181
|
+
{ name: 'searchKey', api: 'searchKey', type: 'string', describe: '关键词' },
|
|
182
|
+
{ name: 'storehouse', api: 'storehouseId', type: 'number', describe: '仓库 id(可选)' },
|
|
183
|
+
],
|
|
184
|
+
require: { allOf: ['from', 'to'], spanDays: MAX_SPAN_DAYS },
|
|
185
|
+
columns: [
|
|
186
|
+
{ field: 'businessTime', label: '业务日期' },
|
|
187
|
+
{ field: 'ordersSn', label: '单号' },
|
|
188
|
+
{ field: 'goodsSn', label: '货号' },
|
|
189
|
+
{ field: 'skuColorName', label: '颜色' },
|
|
190
|
+
{ field: 'skuSizeName', label: '尺码' },
|
|
191
|
+
{ field: 'number', label: '入库数' },
|
|
192
|
+
{ field: 'storehouseName', label: '仓库' },
|
|
193
|
+
],
|
|
194
|
+
example: 'scm-agent data store-in-statistics --from 2026-09-01 --to 2026-09-30 --json',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'ticket-statistics',
|
|
198
|
+
describe: '菲票统计明细(含领包人与部门);服务端默认只查已领包',
|
|
199
|
+
path: '/api/agent/v1/ticket/statistics',
|
|
200
|
+
paged: true,
|
|
201
|
+
options: [
|
|
202
|
+
{ name: 'from', api: 'from', type: 'string', describe: '编菲日期起(yyyy-MM-dd)' },
|
|
203
|
+
{ name: 'to', api: 'to', type: 'string', describe: '编菲日期止(yyyy-MM-dd)' },
|
|
204
|
+
{ name: 'lendPackageState', api: 'lendPackageState', type: 'number', describe: '领包状态 1 已领包(默认) 0 未领包' },
|
|
205
|
+
],
|
|
206
|
+
require: { allOf: ['from', 'to'], spanDays: MAX_SPAN_DAYS },
|
|
207
|
+
columns: [
|
|
208
|
+
{ field: 'ticketSn', label: '菲票号' },
|
|
209
|
+
{ field: 'produceOrdersSn', label: '生产单号' },
|
|
210
|
+
{ field: 'goodsSn', label: '货号' },
|
|
211
|
+
{ field: 'lendPackageUserName', label: '领包人' },
|
|
212
|
+
{ field: 'departmentName', label: '部门' },
|
|
213
|
+
{ field: 'totalNumber', label: '领货数量' },
|
|
214
|
+
],
|
|
215
|
+
example: 'scm-agent data ticket-statistics --from 2026-09-01 --to 2026-09-30 --json',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'factory-evaluation',
|
|
219
|
+
describe: '工厂评估(按加工厂聚合单数、裁床数、完成件数、按时交货率;到期判断以今天为准)',
|
|
220
|
+
path: '/api/agent/v1/report/factory-evaluation',
|
|
221
|
+
paged: true,
|
|
222
|
+
options: [
|
|
223
|
+
{ name: 'from', api: 'from', type: 'string', describe: '业务日期起(yyyy-MM-dd)' },
|
|
224
|
+
{ name: 'to', api: 'to', type: 'string', describe: '业务日期止(yyyy-MM-dd)' },
|
|
225
|
+
],
|
|
226
|
+
require: { allOf: ['from', 'to'], spanDays: MAX_SPAN_DAYS },
|
|
227
|
+
columns: [
|
|
228
|
+
{ field: 'factory', label: '加工厂' },
|
|
229
|
+
{ field: 'orders', label: '单数' },
|
|
230
|
+
{ field: 'sumNumber', label: '下单数' },
|
|
231
|
+
{ field: 'sumCutBedNumber', label: '裁床数' },
|
|
232
|
+
{ field: 'produceStoreInNumber', label: '完成件数' },
|
|
233
|
+
{ field: 'onTimeRate', label: '按时交货率' },
|
|
234
|
+
],
|
|
235
|
+
example: 'scm-agent data factory-evaluation --from 2026-09-01 --to 2026-09-30 --json',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'supplier-list',
|
|
239
|
+
describe: '供应商名录(对账的第一步:先取供应商 id)',
|
|
240
|
+
path: '/api/agent/v1/supplier/list',
|
|
241
|
+
paged: true,
|
|
242
|
+
options: [
|
|
243
|
+
{ name: 'supplierName', api: 'supplierName', type: 'string', describe: '按供应商名称过滤' },
|
|
244
|
+
],
|
|
245
|
+
require: null,
|
|
246
|
+
columns: [
|
|
247
|
+
{ field: 'supplierName', label: '供应商' },
|
|
248
|
+
{ field: 'supplierType', label: '类型' },
|
|
249
|
+
{ field: 'status', label: '状态' },
|
|
250
|
+
{ field: 'cellphone', label: '电话' },
|
|
251
|
+
{ field: 'id', label: 'id' },
|
|
252
|
+
],
|
|
253
|
+
example: 'scm-agent data supplier-list --json',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: 'supplier-balance',
|
|
257
|
+
describe: '单供应商对账数据(11 表结构,单条;欠款字段受权限遮蔽)',
|
|
258
|
+
path: '/api/agent/v1/supplier/balance-record',
|
|
259
|
+
paged: false,
|
|
260
|
+
options: [
|
|
261
|
+
{ name: 'supplierId', api: 'supplierId', type: 'number', describe: '供应商 id(先用 supplier-list 查到)' },
|
|
262
|
+
{ name: 'from', api: 'from', type: 'string', describe: '对账起始日(yyyy-MM-dd)' },
|
|
263
|
+
{ name: 'to', api: 'to', type: 'string', describe: '对账结束日(yyyy-MM-dd)' },
|
|
264
|
+
{ name: 'payerId', api: 'payerId', type: 'number', describe: '付款人 uid(可选)' },
|
|
265
|
+
{ name: 'payWay', api: 'payWay', type: 'number', describe: '付款方式(可选)' },
|
|
266
|
+
{ name: 'statementState', api: 'statementState', type: 'number', describe: '结算状态(可选)' },
|
|
267
|
+
{ name: 'produceSearchKey', api: 'produceSearchKey', type: 'string', describe: '按生产单关键词过滤(可选)' },
|
|
268
|
+
],
|
|
269
|
+
require: { allOf: ['supplierId', 'from', 'to'], spanDays: MAX_SPAN_DAYS },
|
|
270
|
+
columns: null,
|
|
271
|
+
example: 'scm-agent data supplier-balance --supplier-id 88 --from 2026-09-01 --to 2026-09-30 --json',
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: 'work-processes',
|
|
275
|
+
describe: '生产单工序节点列表(含预计与实际时间、汇报次数)',
|
|
276
|
+
path: '/api/agent/v1/produce/work-processes',
|
|
277
|
+
paged: false,
|
|
278
|
+
options: [
|
|
279
|
+
{ name: 'sn', api: 'sn', type: 'string', describe: '生产单号' },
|
|
280
|
+
],
|
|
281
|
+
require: { allOf: ['sn'] },
|
|
282
|
+
columns: [
|
|
283
|
+
{ field: 'workProcessName', label: '工序' },
|
|
284
|
+
{ field: 'core', label: '关键(2裁床)' },
|
|
285
|
+
{ field: 'state', label: '状态' },
|
|
286
|
+
{ field: 'progress', label: '进度' },
|
|
287
|
+
{ field: 'produceNumber', label: '目标数' },
|
|
288
|
+
{ field: 'reportNumber', label: '已汇报' },
|
|
289
|
+
{ field: 'beginTime', label: '实际开始' },
|
|
290
|
+
{ field: 'factoryName', label: '加工厂' },
|
|
291
|
+
],
|
|
292
|
+
example: 'scm-agent data work-processes --sn SC202609010001 --json',
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'work-process-detail',
|
|
296
|
+
describe: '单个工序节点的汇报记录(单条;workProcessId 来自 work-processes 的 id 字段)',
|
|
297
|
+
path: '/api/agent/v1/produce/work-process-detail',
|
|
298
|
+
paged: false,
|
|
299
|
+
options: [
|
|
300
|
+
{ name: 'workProcessId', api: 'workProcessId', type: 'number', describe: '工序节点 id' },
|
|
301
|
+
],
|
|
302
|
+
require: { allOf: ['workProcessId'] },
|
|
303
|
+
columns: null,
|
|
304
|
+
example: 'scm-agent data work-process-detail --work-process-id 123456 --json',
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: 'material-data',
|
|
308
|
+
describe: '批量物料占用与使用状态(最多 20 个生产单;物料名称需从 produce detail 的 materialList 关联)',
|
|
309
|
+
path: '/api/agent/v1/produce/material-data',
|
|
310
|
+
paged: false,
|
|
311
|
+
options: [
|
|
312
|
+
{ name: 'sns', api: 'sns', type: 'string', describe: `生产单号,逗号分隔,最多 ${MAX_SNS} 个` },
|
|
313
|
+
],
|
|
314
|
+
require: { allOf: ['sns'], maxSns: MAX_SNS },
|
|
315
|
+
columns: [
|
|
316
|
+
{ field: 'produceOrdersSn', label: '生产单号' },
|
|
317
|
+
{ field: 'materialId', label: '物料id' },
|
|
318
|
+
{ field: 'materialSkuId', label: '物料单品id' },
|
|
319
|
+
{ field: 'state', label: '使用状态' },
|
|
320
|
+
{ field: 'purchaseNumber', label: '采购数' },
|
|
321
|
+
{ field: 'purchaseWinNumber', label: '采购待入库' },
|
|
322
|
+
{ field: 'pickedNumber', label: '已领料' },
|
|
323
|
+
{ field: 'lackNumberByOrderNumber', label: '本单缺料' },
|
|
324
|
+
],
|
|
325
|
+
example: 'scm-agent data material-data --sns SC001,SC002 --json',
|
|
326
|
+
},
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
/** 按名称找数据集;找不到返回 undefined */
|
|
330
|
+
function findDataset(name) {
|
|
331
|
+
return DATASETS.find((dataset) => dataset.name === name);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** data --list 用的行:数据集名、必填条件(人话)、用途 */
|
|
335
|
+
function listDatasets() {
|
|
336
|
+
return DATASETS.map((dataset) => ({
|
|
337
|
+
name: dataset.name,
|
|
338
|
+
require: requireText(dataset),
|
|
339
|
+
describe: dataset.describe,
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** 把 require 规则翻译成一句话,用于列表与报错 */
|
|
344
|
+
function requireText(dataset) {
|
|
345
|
+
const rule = dataset.require;
|
|
346
|
+
if (!rule) {
|
|
347
|
+
return '无';
|
|
348
|
+
}
|
|
349
|
+
const parts = [];
|
|
350
|
+
if (rule.allOf) {
|
|
351
|
+
parts.push(rule.allOf.map((k) => `--${kebab(k)}`).join(' 与 '));
|
|
352
|
+
}
|
|
353
|
+
if (rule.anyOf) {
|
|
354
|
+
parts.push(rule.anyOf.map((k) => `--${kebab(k)}`).join(' 或 ') + ' 至少一项');
|
|
355
|
+
}
|
|
356
|
+
if (rule.choice) {
|
|
357
|
+
parts.push(rule.choice.map((k) => `--${kebab(k)}`).join(' 与 ') + ' 二选一');
|
|
358
|
+
}
|
|
359
|
+
if (rule.spanDays) {
|
|
360
|
+
parts.push(`跨度不超过 ${rule.spanDays} 天`);
|
|
361
|
+
}
|
|
362
|
+
if (rule.maxSns) {
|
|
363
|
+
parts.push(`最多 ${rule.maxSns} 个单号`);
|
|
364
|
+
}
|
|
365
|
+
return parts.join(';');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** camelCase 转 kebab-case,用于把字段名写成命令参数的样子 */
|
|
369
|
+
function kebab(name) {
|
|
370
|
+
return String(name).replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** 校验日期字符串格式 yyyy-MM-dd,合法返回当天 0 点的毫秒数 */
|
|
374
|
+
function parseDate(value) {
|
|
375
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(value || ''));
|
|
376
|
+
if (!match) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
const time = Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
|
380
|
+
// 月或日超出范围时 Date.UTC 会自动进位,与格式不符,视为非法
|
|
381
|
+
const check = new Date(time);
|
|
382
|
+
if (check.getUTCMonth() !== Number(match[2]) - 1 || check.getUTCDate() !== Number(match[3])) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
return time;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 校验参数是否满足数据集的强制条件。
|
|
390
|
+
* 满足返回 null;不满足返回一段可读错误(含示例命令),调用方据此报错且不发请求。
|
|
391
|
+
*/
|
|
392
|
+
function validateArgs(dataset, argv) {
|
|
393
|
+
const rule = dataset.require;
|
|
394
|
+
if (rule) {
|
|
395
|
+
if (rule.anyOf) {
|
|
396
|
+
const hit = rule.anyOf.filter((key) => argv[key] !== undefined && argv[key] !== '');
|
|
397
|
+
if (hit.length === 0) {
|
|
398
|
+
return `请至少给一项条件:${rule.anyOf.map((k) => '--' + kebab(k)).join(' 或 ')}。示例:${dataset.example}`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (rule.allOf) {
|
|
402
|
+
const missing = rule.allOf.filter((key) => argv[key] === undefined || argv[key] === '');
|
|
403
|
+
if (missing.length) {
|
|
404
|
+
return `缺少必填参数:${missing.map((k) => '--' + kebab(k)).join('、')}。示例:${dataset.example}`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (rule.choice) {
|
|
408
|
+
const [a, b] = rule.choice;
|
|
409
|
+
const hasA = argv[a] !== undefined && argv[a] !== '';
|
|
410
|
+
const hasB = Boolean(argv[b]);
|
|
411
|
+
if (hasA && hasB) {
|
|
412
|
+
return `--${kebab(a)} 与 --${kebab(b)} 只能二选一。示例:${dataset.example}`;
|
|
413
|
+
}
|
|
414
|
+
if (!hasA && !hasB) {
|
|
415
|
+
return `请二选一给出:--${kebab(a)} 或 --${kebab(b)}。示例:${dataset.example}`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// 有跨度要求、或者给了 from/to 的数据集,都做同样的日期校验
|
|
419
|
+
if (rule.spanDays || hasDateParams(dataset)) {
|
|
420
|
+
const error = checkDateRange(argv, rule.spanDays);
|
|
421
|
+
if (error) {
|
|
422
|
+
return `${error}。示例:${dataset.example}`;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (rule.maxSns && argv.sns) {
|
|
426
|
+
const count = String(argv.sns).split(',').map((s) => s.trim()).filter(Boolean).length;
|
|
427
|
+
if (count === 0) {
|
|
428
|
+
return `--sns 至少给一个生产单号。示例:${dataset.example}`;
|
|
429
|
+
}
|
|
430
|
+
if (count > rule.maxSns) {
|
|
431
|
+
return `一次最多 ${rule.maxSns} 个生产单号,本次给了 ${count} 个;请分批查询。示例:${dataset.example}`;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
} else if (hasDateParams(dataset)) {
|
|
435
|
+
// 不强制日期的数据集,给了日期也校验格式与跨度,避免无效日期发到服务端
|
|
436
|
+
const error = checkDateRange(argv, MAX_SPAN_DAYS);
|
|
437
|
+
if (error) {
|
|
438
|
+
return `${error}。示例:${dataset.example}`;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** 数据集是否带 from/to 参数 */
|
|
445
|
+
function hasDateParams(dataset) {
|
|
446
|
+
return dataset.options.some((option) => option.name === 'from' || option.name === 'to');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** 校验 --from/--to 的格式、先后与跨度 */
|
|
450
|
+
function checkDateRange(argv, spanDays) {
|
|
451
|
+
const hasFrom = argv.from !== undefined && argv.from !== '';
|
|
452
|
+
const hasTo = argv.to !== undefined && argv.to !== '';
|
|
453
|
+
if (!hasFrom && !hasTo) {
|
|
454
|
+
return spanDays ? '请给 --from 与 --to(yyyy-MM-dd)' : null;
|
|
455
|
+
}
|
|
456
|
+
if (!hasFrom || !hasTo) {
|
|
457
|
+
return '--from 与 --to 要同时给出(yyyy-MM-dd)';
|
|
458
|
+
}
|
|
459
|
+
const fromTime = parseDate(argv.from);
|
|
460
|
+
const toTime = parseDate(argv.to);
|
|
461
|
+
if (fromTime === null || toTime === null) {
|
|
462
|
+
return '日期格式应为 yyyy-MM-dd';
|
|
463
|
+
}
|
|
464
|
+
if (fromTime > toTime) {
|
|
465
|
+
return '--from 不能晚于 --to';
|
|
466
|
+
}
|
|
467
|
+
if (spanDays) {
|
|
468
|
+
const days = Math.round((toTime - fromTime) / 86400000);
|
|
469
|
+
if (days > spanDays) {
|
|
470
|
+
return `日期跨度 ${days} 天,超过上限 ${spanDays} 天;请拆成多段或缩小范围`;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* 把命令参数组装成后端查询参数。
|
|
478
|
+
* 返回值是对象;值为 undefined 的键由调用方过滤。
|
|
479
|
+
* --all-storehouses 不发给服务端(不传 storehouseId 就是全部仓库合计)。
|
|
480
|
+
*/
|
|
481
|
+
function buildQuery(dataset, argv) {
|
|
482
|
+
const query = {};
|
|
483
|
+
for (const option of dataset.options) {
|
|
484
|
+
if (option.api === null) {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const value = argv[option.name];
|
|
488
|
+
if (value !== undefined && value !== '' && value !== false) {
|
|
489
|
+
query[option.api] = value;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (argv.page !== undefined) {
|
|
493
|
+
query.page = argv.page;
|
|
494
|
+
}
|
|
495
|
+
if (argv.limit !== undefined) {
|
|
496
|
+
query.limit = argv.limit;
|
|
497
|
+
}
|
|
498
|
+
return query;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** 该数据集不接受的多余参数(用于提示写错的参数名) */
|
|
502
|
+
function unknownOptions(dataset, argv) {
|
|
503
|
+
const known = new Set(dataset.options.map((option) => option.name));
|
|
504
|
+
const extra = [];
|
|
505
|
+
for (const option of ALL_OPTIONS) {
|
|
506
|
+
if (known.has(option.name)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (argv[option.name] !== undefined) {
|
|
510
|
+
extra.push('--' + kebab(option.name));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return extra;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** 全部数据集的参数并集(yargs 声明用),由定义推导,不手工维护 */
|
|
517
|
+
const ALL_OPTIONS = (() => {
|
|
518
|
+
const map = new Map();
|
|
519
|
+
for (const dataset of DATASETS) {
|
|
520
|
+
for (const option of dataset.options) {
|
|
521
|
+
if (!map.has(option.name)) {
|
|
522
|
+
map.set(option.name, option);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return [...map.values()];
|
|
527
|
+
})();
|
|
528
|
+
|
|
529
|
+
module.exports = {
|
|
530
|
+
DATASETS,
|
|
531
|
+
MAX_SPAN_DAYS,
|
|
532
|
+
MAX_SNS,
|
|
533
|
+
findDataset,
|
|
534
|
+
listDatasets,
|
|
535
|
+
requireText,
|
|
536
|
+
validateArgs,
|
|
537
|
+
buildQuery,
|
|
538
|
+
unknownOptions,
|
|
539
|
+
ALL_OPTIONS,
|
|
540
|
+
kebab,
|
|
541
|
+
};
|
package/cli/scm-agent.js
CHANGED
|
@@ -6,10 +6,12 @@ const { login } = require('./api');
|
|
|
6
6
|
const { readConfig, DEFAULT_SERVER } = require('./config');
|
|
7
7
|
const { installSkills } = require('./skills');
|
|
8
8
|
const { execSync } = require('child_process');
|
|
9
|
-
const { goods, material, produce, materialPurchase, design } = require('./commands/query');
|
|
9
|
+
const { goods, material, produce, materialPurchase, design, designOrder } = require('./commands/query');
|
|
10
10
|
const { report } = require('./commands/report');
|
|
11
11
|
const { log } = require('./commands/log');
|
|
12
12
|
const { user, role, permission } = require('./commands/staff');
|
|
13
|
+
const { docs } = require('./commands/docs');
|
|
14
|
+
const { data } = require('./commands/data');
|
|
13
15
|
|
|
14
16
|
// npm 包名:安装与升级都从 npm 仓库获取,不依赖 git
|
|
15
17
|
const PACKAGE_NAME = '@qinsilk/scm-agent-tools';
|
|
@@ -92,11 +94,14 @@ const cli = yargs
|
|
|
92
94
|
.command(produce)
|
|
93
95
|
.command(materialPurchase)
|
|
94
96
|
.command(design)
|
|
97
|
+
.command(designOrder)
|
|
95
98
|
.command(report)
|
|
96
99
|
.command(log)
|
|
97
100
|
.command(user)
|
|
98
101
|
.command(role)
|
|
99
102
|
.command(permission)
|
|
103
|
+
.command(docs)
|
|
104
|
+
.command(data)
|
|
100
105
|
.demandCommand(1, '请指定命令。查看全部命令:scm-agent --help')
|
|
101
106
|
.strict()
|
|
102
107
|
.help()
|