@qinsilk/scm-agent-tools 0.0.0-stage → 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 +48 -2
- package/cli/api.js +114 -0
- package/cli/commands/data.js +112 -0
- package/cli/commands/docs.js +121 -0
- package/cli/commands/log.js +152 -0
- package/cli/commands/query.js +285 -0
- package/cli/commands/report.js +213 -0
- package/cli/commands/staff.js +505 -0
- package/cli/config.js +46 -0
- package/cli/datasets.js +541 -0
- package/cli/scm-agent.js +110 -0
- package/cli/skills.js +89 -0
- package/cli/table.js +43 -0
- package/docs/api.md +2067 -0
- package/docs/cli.md +252 -0
- package/package.json +27 -4
- package/reports/excel.js +107 -0
- package/reports/factoryEvaluation.js +128 -0
- package/reports/goodsBom.js +648 -0
- package/reports/goodsFirstOrder.js +409 -0
- package/reports/goodsInventory.js +220 -0
- package/reports/goodsSize.js +440 -0
- package/reports/image.js +216 -0
- package/reports/leakCheck.js +694 -0
- package/reports/materialPurchaseExport.js +393 -0
- package/reports/materialTransit.js +593 -0
- package/reports/processFollowup.js +312 -0
- package/reports/produceBoard.js +353 -0
- package/reports/produceCost.js +366 -0
- package/reports/produceCostDetail.js +622 -0
- package/reports/produceDaily.js +485 -0
- package/reports/storeInGoods.js +276 -0
- package/reports/supplierBalance.js +671 -0
- package/reports/ticketStatistics.js +289 -0
- package/reports/util.js +83 -0
- package/skills/bom/345/225/206/345/223/201/344/277/241/346/201/257/SKILL.md +40 -0
- package/skills/qinsilk-scm/SKILL.md +126 -0
- package/skills//344/276/233/345/272/224/345/225/206/345/257/271/350/264/246/345/257/274/345/207/272/SKILL.md +38 -0
- package/skills//345/225/206/345/223/201/345/260/272/345/257/270/344/277/241/346/201/257/SKILL.md +40 -0
- package/skills//345/225/206/345/223/201/351/246/226/345/215/225/351/235/242/346/226/231/345/267/245/345/272/217/345/215/225/344/273/267/SKILL.md +39 -0
- package/skills//345/267/245/345/216/202/350/257/204/344/274/260/346/212/245/350/241/250/SKILL.md +39 -0
- package/skills//346/210/220/345/223/201/345/205/245/345/272/223/346/212/245/350/241/250/SKILL.md +40 -0
- package/skills//346/234/215/350/243/205/346/210/220/345/223/201/347/233/230/345/255/230/350/241/250/SKILL.md +39 -0
- package/skills//346/274/217/346/226/231/346/243/200/346/265/213/346/227/245/346/212/245/SKILL.md +40 -0
- package/skills//347/211/251/346/226/231/345/234/250/351/200/224/347/212/266/346/200/201/346/212/245/350/241/250/SKILL.md +40 -0
- package/skills//347/211/251/346/226/231/351/207/207/350/264/255/345/257/274/345/207/272/SKILL.md +39 -0
- package/skills//347/224/237/344/272/247/345/215/225/345/267/245/345/272/217/350/267/237/350/277/233/350/241/250/SKILL.md +39 -0
- package/skills//347/224/237/344/272/247/346/210/220/346/234/254/346/212/245/350/241/250/SKILL.md +41 -0
- package/skills//347/224/237/344/272/247/346/210/220/346/234/254/346/212/245/350/241/250/346/230/216/347/273/206/SKILL.md +40 -0
- package/skills//347/224/237/344/272/247/346/227/245/346/212/245/SKILL.md +42 -0
- package/skills//347/224/237/344/272/247/347/212/266/345/206/265/347/273/237/350/256/241/350/241/250/SKILL.md +39 -0
- package/skills//347/224/237/344/272/247/350/212/202/347/202/271/347/234/213/346/235/277/SKILL.md +39 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// 成品入库报表:列与「报表分析-成品入库报表」页面导出一致,单品图列嵌商品主图。
|
|
2
|
+
// 关联生产单按企业配置决定显示生产批次号还是生产单号。
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { call } = require('../cli/api');
|
|
6
|
+
const { dateStr, shiftDate, timestamp, fetchAll } = require('./util');
|
|
7
|
+
const { createWorkbook, saveWorkbook, embedImage } = require('./excel');
|
|
8
|
+
const { downloadImages } = require('./image');
|
|
9
|
+
|
|
10
|
+
// 业务类型文案(与页面的 businessItemMap 一致)
|
|
11
|
+
const BUSINESS_ITEM_NAMES = { 1: '生产入库', 2: '调拨入库', 3: '次品返厂入库', 76: '出库退货' };
|
|
12
|
+
// 生产类型文案(与页面的 prodTypeList 一致)
|
|
13
|
+
const PRODUCE_TYPE_NAMES = { 1: '自主生产', 2: '单工序外发', 3: '委外加工(全工序外发)', 4: '成品采购' };
|
|
14
|
+
// 结算方式文案(与页面的 settlementMethodOptions 一致)
|
|
15
|
+
const SETTLEMENT_NAMES = { 1: '现结', 2: '日结', 3: '周结', 4: '半月结', 5: '月结', 6: '季结', 7: '年结', 8: '单结', 9: '其他' };
|
|
16
|
+
// 金额格式:人民币两位小数
|
|
17
|
+
const MONEY_FORMAT = '¥#,##0.00;-¥#,##0.00';
|
|
18
|
+
|
|
19
|
+
// 列定义:标题、取值字段、类型。列顺序与页面「导出」按钮的可见列顺序一致
|
|
20
|
+
const COLUMNS = [
|
|
21
|
+
{ header: '业务类型', field: 'businessItem', kind: 'businessItem' },
|
|
22
|
+
{ header: '单号', field: 'ordersSn', kind: 'text' },
|
|
23
|
+
{ header: '关联业务单号', field: 'businessSn', kind: 'text' },
|
|
24
|
+
{ header: '生产单', field: 'produceOrders', kind: 'produceSn' },
|
|
25
|
+
{ header: '床次', field: 'bedNumbers', kind: 'beds' },
|
|
26
|
+
{ header: '生产类型', field: 'produceOrders', kind: 'produceType' },
|
|
27
|
+
{ header: '订单类型', field: 'produceOrders', kind: 'produceJoin', joinField: 'orderTypeName' },
|
|
28
|
+
{ header: '客户', field: 'produceOrders', kind: 'produceJoin', joinField: 'clientName' },
|
|
29
|
+
{ header: '单品图', field: 'imgUrl', kind: 'image' },
|
|
30
|
+
{ header: '商品名称', field: 'goodsName', kind: 'text' },
|
|
31
|
+
{ header: '商品货号', field: 'goodsSn', kind: 'text' },
|
|
32
|
+
{ header: '一级分类', field: 'firstGoodsCategoryName', kind: 'text' },
|
|
33
|
+
{ header: '颜色', field: 'skuColorName', kind: 'text' },
|
|
34
|
+
{ header: '尺码', field: 'skuSizeName', kind: 'text' },
|
|
35
|
+
{ header: '入库数', field: 'number', kind: 'number' },
|
|
36
|
+
{ header: '入库仓库', field: 'storehouseName', kind: 'text' },
|
|
37
|
+
{ header: '结算方式', field: 'settlementMethod', kind: 'settlement' },
|
|
38
|
+
{ header: '备注', field: 'remark', kind: 'text' },
|
|
39
|
+
{ header: '委外加工费/采购单价', field: 'producePrice', kind: 'money' },
|
|
40
|
+
{ header: '加工厂/供应商', field: 'supplierName', kind: 'supplier' },
|
|
41
|
+
{ header: '生产组', field: 'productionGroupName', kind: 'text' },
|
|
42
|
+
{ header: '金额', field: 'amount', kind: 'money' },
|
|
43
|
+
{ header: '结算账户', field: 'accountName', kind: 'text' },
|
|
44
|
+
{ header: '入库业务日期', field: 'businessTime', kind: 'date' },
|
|
45
|
+
{ header: '创建日期', field: 'createTime', kind: 'date' },
|
|
46
|
+
{ header: '创建人', field: 'handlerName', kind: 'text' },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const BORDER = {
|
|
50
|
+
top: { style: 'thin', color: { argb: 'FF000000' } },
|
|
51
|
+
left: { style: 'thin', color: { argb: 'FF000000' } },
|
|
52
|
+
bottom: { style: 'thin', color: { argb: 'FF000000' } },
|
|
53
|
+
right: { style: 'thin', color: { argb: 'FF000000' } },
|
|
54
|
+
};
|
|
55
|
+
// 表头底色:浅灰,与原脚本一致
|
|
56
|
+
const HEADER_FILL = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFC0C0C0' } };
|
|
57
|
+
// 单品图单元格底色:浅蓝,与原脚本一致
|
|
58
|
+
const IMAGE_FILL = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE8F1FB' } };
|
|
59
|
+
|
|
60
|
+
/** 数值列的空值兜底为 0(与原脚本的 number() 一致) */
|
|
61
|
+
function number(value) {
|
|
62
|
+
const parsed = Number(value);
|
|
63
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 文本归一化:空值返回空字符串 */
|
|
67
|
+
function text(value) {
|
|
68
|
+
return value == null ? '' : String(value).trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 从关联生产单列表取同名字段,用「、」连接;列表为空返回空字符串 */
|
|
72
|
+
function joinProduceField(orders, field) {
|
|
73
|
+
if (!Array.isArray(orders)) {
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
return orders
|
|
77
|
+
.map((order) => text(order[field]))
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join('、');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 生产单列:开启生产批次号时优先显示批次号,否则显示生产单号(与页面一致)。
|
|
84
|
+
*/
|
|
85
|
+
function produceSnText(orders, useBatchSn) {
|
|
86
|
+
if (!Array.isArray(orders)) {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
const parts = [];
|
|
90
|
+
for (const order of orders) {
|
|
91
|
+
const batch = text(order.produceBatchSn);
|
|
92
|
+
const sn = useBatchSn && batch ? batch : text(order.ordersSn);
|
|
93
|
+
if (sn) {
|
|
94
|
+
parts.push(sn);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return parts.join('、');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 计算一行的单元格显示值(单品图列留空,图片单独嵌入) */
|
|
101
|
+
function cellValue(row, column, useBatchSn) {
|
|
102
|
+
const value = row[column.field];
|
|
103
|
+
switch (column.kind) {
|
|
104
|
+
case 'businessItem':
|
|
105
|
+
return BUSINESS_ITEM_NAMES[value] || text(value);
|
|
106
|
+
case 'settlement':
|
|
107
|
+
// 结算方式未知时显示空,与页面下拉的取值范围一致
|
|
108
|
+
return SETTLEMENT_NAMES[value] || '';
|
|
109
|
+
case 'date':
|
|
110
|
+
return text(value);
|
|
111
|
+
case 'number':
|
|
112
|
+
case 'money':
|
|
113
|
+
return number(value);
|
|
114
|
+
case 'image':
|
|
115
|
+
return '';
|
|
116
|
+
case 'produceSn':
|
|
117
|
+
return produceSnText(value, useBatchSn);
|
|
118
|
+
case 'beds': {
|
|
119
|
+
// 床次为空时显示「-」,与页面导出的格式化一致
|
|
120
|
+
const parts = Array.isArray(value) ? value.map(text).filter(Boolean) : [];
|
|
121
|
+
return parts.join('、') || '-';
|
|
122
|
+
}
|
|
123
|
+
case 'produceType':
|
|
124
|
+
// 生产类型是枚举值,逐项翻译成文案
|
|
125
|
+
if (!Array.isArray(value)) {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
return value
|
|
129
|
+
.map((order) => PRODUCE_TYPE_NAMES[order.type] || text(order.type))
|
|
130
|
+
.filter(Boolean)
|
|
131
|
+
.join('、');
|
|
132
|
+
case 'produceJoin':
|
|
133
|
+
return joinProduceField(value, column.joinField);
|
|
134
|
+
case 'supplier':
|
|
135
|
+
// 有生产单时取生产单上的供应商,否则取单据自带的供应商
|
|
136
|
+
return joinProduceField(value, 'supplierName') || text(row.supplierName);
|
|
137
|
+
default:
|
|
138
|
+
return text(value);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 按列类型计算列宽(与原脚本的宽度规则一致) */
|
|
143
|
+
function columnWidth(column) {
|
|
144
|
+
if (column.kind === 'image') {
|
|
145
|
+
return 10;
|
|
146
|
+
}
|
|
147
|
+
if (column.kind === 'money') {
|
|
148
|
+
return 15;
|
|
149
|
+
}
|
|
150
|
+
if (column.kind === 'number') {
|
|
151
|
+
return 10;
|
|
152
|
+
}
|
|
153
|
+
if (column.kind === 'date') {
|
|
154
|
+
return 13;
|
|
155
|
+
}
|
|
156
|
+
return Math.max(12, Math.min(24, column.header.length * 3 + 4));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 生成成品入库报表 Excel。
|
|
161
|
+
* argv.from/to:业务日期区间,默认结束日今天、起始日往前 30 个日历日;
|
|
162
|
+
* argv.sn:单号关键词(对应页面搜索单号/商品名称/商品货号);argv.out:输出目录。
|
|
163
|
+
*/
|
|
164
|
+
async function generate(argv = {}) {
|
|
165
|
+
const to = argv.to || dateStr();
|
|
166
|
+
const from = argv.from || shiftDate(-29, new Date(`${to}T00:00:00`));
|
|
167
|
+
if (from > to) {
|
|
168
|
+
throw new Error('起始日期不能晚于结束日期');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const outDir = argv.out || process.cwd();
|
|
172
|
+
const filePath = path.resolve(outDir, `成品入库报表${timestamp()}.xlsx`);
|
|
173
|
+
if (fs.existsSync(filePath)) {
|
|
174
|
+
throw new Error(`输出文件已存在:${filePath};如需替换请先删除旧文件或更换输出目录`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 企业配置:produceBatchSnEnable 决定生产单列显示批次号还是生产单号;读取失败按未开启处理
|
|
178
|
+
let useBatchSn = false;
|
|
179
|
+
try {
|
|
180
|
+
const config = await call('GET', '/api/agent/v1/company-config');
|
|
181
|
+
useBatchSn = config && config.produceBatchSnEnable === true;
|
|
182
|
+
} catch (e) {
|
|
183
|
+
// 配置接口失败不中断导出,与原脚本一致
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const { list: rows } = await fetchAll(
|
|
187
|
+
(page, limit) => call('GET', '/api/agent/v1/statistics/store-in-goods', {
|
|
188
|
+
from,
|
|
189
|
+
to,
|
|
190
|
+
searchKey: argv.sn,
|
|
191
|
+
storehouseId: argv.storehouse,
|
|
192
|
+
page,
|
|
193
|
+
limit,
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
if (!rows.length) {
|
|
197
|
+
throw new Error('没有符合条件的成品入库报表数据');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 批量下载单品图;下载失败的地址不在 Map 里,对应单元格留空
|
|
201
|
+
const imageUrlByRow = rows.map((row) => text(row.imgUrl));
|
|
202
|
+
const images = await downloadImages(imageUrlByRow);
|
|
203
|
+
|
|
204
|
+
const workbook = createWorkbook();
|
|
205
|
+
const sheet = workbook.addWorksheet('成品入库报表', {
|
|
206
|
+
views: [{ state: 'frozen', ySplit: 2 }],
|
|
207
|
+
pageSetup: { orientation: 'landscape', fitToPage: true, fitToWidth: 1, fitToHeight: 0 },
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// 第 1 行:表标题,整行合并
|
|
211
|
+
const titleRow = sheet.addRow(['成品入库报表']);
|
|
212
|
+
titleRow.height = 26;
|
|
213
|
+
titleRow.getCell(1).font = { bold: true };
|
|
214
|
+
titleRow.getCell(1).fill = HEADER_FILL;
|
|
215
|
+
titleRow.getCell(1).alignment = { vertical: 'middle', horizontal: 'center' };
|
|
216
|
+
|
|
217
|
+
// 第 2 行:表头
|
|
218
|
+
const headerRow = sheet.addRow(COLUMNS.map((column) => column.header));
|
|
219
|
+
headerRow.height = 30;
|
|
220
|
+
headerRow.eachCell((cell) => {
|
|
221
|
+
cell.font = { bold: true };
|
|
222
|
+
cell.fill = HEADER_FILL;
|
|
223
|
+
cell.border = BORDER;
|
|
224
|
+
cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// 数据行:单品图行高 46,图片以 60x60 嵌入图片列
|
|
228
|
+
const imageCol = COLUMNS.findIndex((column) => column.kind === 'image') + 1;
|
|
229
|
+
// 左对齐的列类型:文本类、日期类与两个枚举翻译列(与原脚本文本样式一致);
|
|
230
|
+
// 数值与金额右对齐,图片列居中
|
|
231
|
+
const LEFT_KINDS = new Set(['text', 'date', 'businessItem', 'settlement', 'produceSn', 'beds', 'produceType', 'produceJoin', 'supplier']);
|
|
232
|
+
rows.forEach((row, index) => {
|
|
233
|
+
const values = COLUMNS.map((column) => cellValue(row, column, useBatchSn));
|
|
234
|
+
const added = sheet.addRow(values);
|
|
235
|
+
added.height = 46;
|
|
236
|
+
added.eachCell((cell, colNumber) => {
|
|
237
|
+
const kind = COLUMNS[colNumber - 1].kind;
|
|
238
|
+
cell.border = BORDER;
|
|
239
|
+
cell.alignment = {
|
|
240
|
+
vertical: 'middle',
|
|
241
|
+
horizontal: LEFT_KINDS.has(kind) ? 'left' : kind === 'image' ? 'center' : 'right',
|
|
242
|
+
wrapText: kind !== 'number' && kind !== 'money',
|
|
243
|
+
};
|
|
244
|
+
if (kind === 'money') {
|
|
245
|
+
cell.numFmt = MONEY_FORMAT;
|
|
246
|
+
}
|
|
247
|
+
if (kind === 'image') {
|
|
248
|
+
cell.fill = IMAGE_FILL;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
const url = imageUrlByRow[index];
|
|
252
|
+
if (url && images.has(url)) {
|
|
253
|
+
embedImage(workbook, sheet, images.get(url), index + 3, imageCol, { width: 60, height: 60 });
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
COLUMNS.forEach((column, i) => {
|
|
258
|
+
sheet.getColumn(i + 1).width = columnWidth(column);
|
|
259
|
+
});
|
|
260
|
+
const lastColLetter = sheet.getColumn(COLUMNS.length).letter;
|
|
261
|
+
sheet.mergeCells(1, 1, 1, COLUMNS.length);
|
|
262
|
+
// 表头行加筛选,与页面导出一致
|
|
263
|
+
sheet.autoFilter = `A2:${lastColLetter}${rows.length + 2}`;
|
|
264
|
+
|
|
265
|
+
await saveWorkbook(workbook, filePath);
|
|
266
|
+
|
|
267
|
+
const skipped = imageUrlByRow.filter((url) => url && !images.has(url)).length;
|
|
268
|
+
return {
|
|
269
|
+
filePaths: [filePath],
|
|
270
|
+
summary: `成品入库报表已生成:业务日期 ${from} ~ ${to} 共 ${rows.length} 行、${COLUMNS.length} 列,` +
|
|
271
|
+
`生产单显示${useBatchSn ? '生产批次号优先' : '生产单号'},嵌单品图 ${images.size} 张` +
|
|
272
|
+
(skipped ? `(${skipped} 张下载失败已跳过)` : ''),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = { generate };
|