|
|
@@ -0,0 +1,296 @@
|
|
|
+import * as XLSX from 'xlsx-js-style'
|
|
|
+
|
|
|
+/** 入库批量导出的行数据(一行对应一条入库明细) */
|
|
|
+export interface MaterialsInExportRow {
|
|
|
+ /** 入库单号 */
|
|
|
+ orderNo?: string
|
|
|
+ /** 入库日期 yyyy-MM-dd */
|
|
|
+ orderDate?: string
|
|
|
+ /** 供应商 */
|
|
|
+ supplierName?: string
|
|
|
+ /** 备注 */
|
|
|
+ remark?: string
|
|
|
+ /** 仓库 */
|
|
|
+ storeName?: string
|
|
|
+ /** 物资类别 */
|
|
|
+ categoryName?: string
|
|
|
+ /** 物资编码 */
|
|
|
+ materialNumber?: string
|
|
|
+ /** 物资名称 */
|
|
|
+ materialName?: string
|
|
|
+ /** 计量单位 */
|
|
|
+ materialUnit?: string
|
|
|
+ /** 生产日期 yyyy-MM-dd */
|
|
|
+ produceDate?: string
|
|
|
+ /** 入库数量 */
|
|
|
+ inQty?: number | string
|
|
|
+ /** 入库单价 */
|
|
|
+ inUnitPrice?: number | string
|
|
|
+ /** 金额 */
|
|
|
+ amount?: number | string
|
|
|
+}
|
|
|
+
|
|
|
+/** 出库批量导出的行数据(一行对应一条出库明细) */
|
|
|
+export interface MaterialsOutExportRow {
|
|
|
+ /** 出库单号 */
|
|
|
+ orderNo?: string
|
|
|
+ /** 出库日期 yyyy-MM-dd */
|
|
|
+ orderDate?: string
|
|
|
+ /** 领用部门 */
|
|
|
+ outDeptName?: string
|
|
|
+ /** 领用人 */
|
|
|
+ outUser?: string
|
|
|
+ /** 出库原因 */
|
|
|
+ outReason?: string
|
|
|
+ /** 备注 */
|
|
|
+ remark?: string
|
|
|
+ /** 仓库 */
|
|
|
+ storeName?: string
|
|
|
+ /** 关联入库单号 */
|
|
|
+ refInOrderNo?: string
|
|
|
+ /** 物资编码 */
|
|
|
+ materialNumber?: string
|
|
|
+ /** 物资名称 */
|
|
|
+ materialName?: string
|
|
|
+ /** 计量单位 */
|
|
|
+ materialUnit?: string
|
|
|
+ /** 生产日期 yyyy-MM-dd */
|
|
|
+ produceDate?: string
|
|
|
+ /** 出库数量 */
|
|
|
+ quantity?: number | string
|
|
|
+ /** 入库单价 */
|
|
|
+ inUnitPrice?: number | string
|
|
|
+ /** 出库单价 */
|
|
|
+ saleUnitPrice?: number | string
|
|
|
+ /** 金额 */
|
|
|
+ amount?: number | string
|
|
|
+}
|
|
|
+
|
|
|
+/** 入库明细表头 */
|
|
|
+const IN_HEADER = [
|
|
|
+ '入库单号',
|
|
|
+ '入库日期',
|
|
|
+ '供应商',
|
|
|
+ '备注',
|
|
|
+ '仓库',
|
|
|
+ '物资类别',
|
|
|
+ '物资编码',
|
|
|
+ '物资名称',
|
|
|
+ '计量单位',
|
|
|
+ '生产日期',
|
|
|
+ '入库数量',
|
|
|
+ '入库单价',
|
|
|
+ '金额'
|
|
|
+]
|
|
|
+const IN_COLUMN_WIDTH = [
|
|
|
+ { wch: 38 },
|
|
|
+ { wch: 12 },
|
|
|
+ { wch: 34 },
|
|
|
+ { wch: 16 },
|
|
|
+ { wch: 16 },
|
|
|
+ { wch: 16 },
|
|
|
+ { wch: 14 },
|
|
|
+ { wch: 30 },
|
|
|
+ { wch: 10 },
|
|
|
+ { wch: 12 },
|
|
|
+ { wch: 10 },
|
|
|
+ { wch: 10 },
|
|
|
+ { wch: 10 }
|
|
|
+]
|
|
|
+/** 数量 / 单价 / 金额列下标(导出为数值,便于 Excel 直接统计) */
|
|
|
+const IN_NUMBER_COL = [10, 11, 12]
|
|
|
+/** 左对齐的文本列下标 */
|
|
|
+const IN_LEFT_COL = [0, 2, 7]
|
|
|
+
|
|
|
+/** 出库明细表头 */
|
|
|
+const OUT_HEADER = [
|
|
|
+ '出库单号',
|
|
|
+ '出库日期',
|
|
|
+ '领用部门',
|
|
|
+ '领用人',
|
|
|
+ '出库原因',
|
|
|
+ '备注',
|
|
|
+ '仓库',
|
|
|
+ '关联入库单号',
|
|
|
+ '物资编码',
|
|
|
+ '物资名称',
|
|
|
+ '计量单位',
|
|
|
+ '生产日期',
|
|
|
+ '出库数量',
|
|
|
+ '入库单价',
|
|
|
+ '出库单价',
|
|
|
+ '金额'
|
|
|
+]
|
|
|
+const OUT_COLUMN_WIDTH = [
|
|
|
+ { wch: 38 },
|
|
|
+ { wch: 12 },
|
|
|
+ { wch: 16 },
|
|
|
+ { wch: 12 },
|
|
|
+ { wch: 14 },
|
|
|
+ { wch: 16 },
|
|
|
+ { wch: 16 },
|
|
|
+ { wch: 38 },
|
|
|
+ { wch: 14 },
|
|
|
+ { wch: 30 },
|
|
|
+ { wch: 10 },
|
|
|
+ { wch: 12 },
|
|
|
+ { wch: 10 },
|
|
|
+ { wch: 10 },
|
|
|
+ { wch: 10 },
|
|
|
+ { wch: 10 }
|
|
|
+]
|
|
|
+const OUT_NUMBER_COL = [12, 13, 14, 15]
|
|
|
+const OUT_LEFT_COL = [0, 2, 3, 5, 7, 9]
|
|
|
+
|
|
|
+const FULL_BORDER = {
|
|
|
+ top: { style: 'thin', color: { rgb: 'FF000000' } },
|
|
|
+ bottom: { style: 'thin', color: { rgb: 'FF000000' } },
|
|
|
+ left: { style: 'thin', color: { rgb: 'FF000000' } },
|
|
|
+ right: { style: 'thin', color: { rgb: 'FF000000' } }
|
|
|
+}
|
|
|
+
|
|
|
+const HEADER_STYLE = {
|
|
|
+ fill: { patternType: 'solid', fgColor: { rgb: 'FFA6A6A6' } },
|
|
|
+ font: { sz: 11, bold: true, color: { rgb: 'FF000000' }, name: '宋体' },
|
|
|
+ alignment: { vertical: 'center', horizontal: 'center', wrapText: true },
|
|
|
+ border: FULL_BORDER
|
|
|
+}
|
|
|
+
|
|
|
+/** 取 yyyy-MM-dd */
|
|
|
+const dateText = (d?: string) => (d ? String(d).slice(0, 10) : '')
|
|
|
+
|
|
|
+/** 数值转换:非数字返回空串 */
|
|
|
+const numText = (v?: number | string) => {
|
|
|
+ if (v === null || v === undefined || v === '') return ''
|
|
|
+ const num = Number(v)
|
|
|
+ return Number.isNaN(num) ? '' : num
|
|
|
+}
|
|
|
+
|
|
|
+const strText = (v?: string | number) => (v === null || v === undefined ? '' : String(v))
|
|
|
+
|
|
|
+const toInRow = (row: MaterialsInExportRow): (string | number)[] => [
|
|
|
+ strText(row.orderNo),
|
|
|
+ dateText(row.orderDate),
|
|
|
+ strText(row.supplierName),
|
|
|
+ strText(row.remark),
|
|
|
+ strText(row.storeName),
|
|
|
+ strText(row.categoryName),
|
|
|
+ strText(row.materialNumber),
|
|
|
+ strText(row.materialName),
|
|
|
+ strText(row.materialUnit),
|
|
|
+ dateText(row.produceDate),
|
|
|
+ numText(row.inQty),
|
|
|
+ numText(row.inUnitPrice),
|
|
|
+ numText(row.amount)
|
|
|
+]
|
|
|
+
|
|
|
+const toOutRow = (row: MaterialsOutExportRow): (string | number)[] => [
|
|
|
+ strText(row.orderNo),
|
|
|
+ dateText(row.orderDate),
|
|
|
+ strText(row.outDeptName),
|
|
|
+ strText(row.outUser),
|
|
|
+ strText(row.outReason),
|
|
|
+ strText(row.remark),
|
|
|
+ strText(row.storeName),
|
|
|
+ strText(row.refInOrderNo),
|
|
|
+ strText(row.materialNumber),
|
|
|
+ strText(row.materialName),
|
|
|
+ strText(row.materialUnit),
|
|
|
+ dateText(row.produceDate),
|
|
|
+ numText(row.quantity),
|
|
|
+ numText(row.inUnitPrice),
|
|
|
+ numText(row.saleUnitPrice),
|
|
|
+ numText(row.amount)
|
|
|
+]
|
|
|
+
|
|
|
+/**
|
|
|
+ * 生成明细表:表头带底色 + 全表边框,数量/单价/金额为数值列
|
|
|
+ * @param header 表头
|
|
|
+ * @param rows 数据行
|
|
|
+ * @param columnWidth 列宽
|
|
|
+ * @param numberCols 数值列下标
|
|
|
+ * @param leftCols 左对齐列下标
|
|
|
+ * @param fileName 文件名(不带后缀)
|
|
|
+ * @param sheetName 工作表名
|
|
|
+ */
|
|
|
+const writeDetailSheet = (
|
|
|
+ header: string[],
|
|
|
+ rows: (string | number)[][],
|
|
|
+ columnWidth: { wch: number }[],
|
|
|
+ numberCols: number[],
|
|
|
+ leftCols: number[],
|
|
|
+ fileName: string,
|
|
|
+ sheetName: string
|
|
|
+) => {
|
|
|
+ const aoa: (string | number)[][] = [header, ...rows]
|
|
|
+
|
|
|
+ const ws = XLSX.utils.aoa_to_sheet(aoa)
|
|
|
+ ws['!cols'] = columnWidth
|
|
|
+ ws['!rows'] = aoa.map(() => ({ hpt: 22 }))
|
|
|
+
|
|
|
+ const range = XLSX.utils.decode_range(ws['!ref'] || 'A1')
|
|
|
+ for (let R = range.s.r; R <= range.e.r; R++) {
|
|
|
+ for (let C = range.s.c; C <= range.e.c; C++) {
|
|
|
+ const cellRef = XLSX.utils.encode_cell({ r: R, c: C })
|
|
|
+ if (!ws[cellRef]) ws[cellRef] = { v: '', t: 's' }
|
|
|
+ const cell: Recordable = ws[cellRef]
|
|
|
+ if (R === 0) {
|
|
|
+ cell.s = HEADER_STYLE
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ cell.s = {
|
|
|
+ font: { sz: 11, name: '宋体' },
|
|
|
+ alignment: {
|
|
|
+ vertical: 'center',
|
|
|
+ horizontal: numberCols.includes(C) ? 'right' : leftCols.includes(C) ? 'left' : 'center',
|
|
|
+ wrapText: true
|
|
|
+ },
|
|
|
+ border: FULL_BORDER
|
|
|
+ }
|
|
|
+ // 保障数量/单价/金额为数值类型
|
|
|
+ if (numberCols.includes(C) && typeof cell.v === 'number') {
|
|
|
+ cell.t = 'n'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const wb = XLSX.utils.book_new()
|
|
|
+ XLSX.utils.book_append_sheet(wb, ws, sheetName)
|
|
|
+ XLSX.writeFile(wb, `${fileName}.xlsx`)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 导出物资入库明细(一行一条物资)
|
|
|
+ */
|
|
|
+export const exportMaterialsInBatch = (
|
|
|
+ rows: MaterialsInExportRow[],
|
|
|
+ fileName = '入库单明细'
|
|
|
+) => {
|
|
|
+ writeDetailSheet(
|
|
|
+ IN_HEADER,
|
|
|
+ (Array.isArray(rows) ? rows : []).map(toInRow),
|
|
|
+ IN_COLUMN_WIDTH,
|
|
|
+ IN_NUMBER_COL,
|
|
|
+ IN_LEFT_COL,
|
|
|
+ fileName,
|
|
|
+ '入库单明细'
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 导出物资出库明细(一行一条物资)
|
|
|
+ */
|
|
|
+export const exportMaterialsOutBatch = (
|
|
|
+ rows: MaterialsOutExportRow[],
|
|
|
+ fileName = '出库单明细'
|
|
|
+) => {
|
|
|
+ writeDetailSheet(
|
|
|
+ OUT_HEADER,
|
|
|
+ (Array.isArray(rows) ? rows : []).map(toOutRow),
|
|
|
+ OUT_COLUMN_WIDTH,
|
|
|
+ OUT_NUMBER_COL,
|
|
|
+ OUT_LEFT_COL,
|
|
|
+ fileName,
|
|
|
+ '出库单明细'
|
|
|
+ )
|
|
|
+}
|