gm-printer 10.42.2 → 10.42.3-beta.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.
@@ -5,6 +5,7 @@ export const INDEX_KEY = '序号'
5
5
 
6
6
  const SPECIAL_ROW_KEYS = [
7
7
  '_special',
8
+ '_specialText', // 分类标题行(三级分类/商品分类的品类名行),漏掉会在多栏重排时被当普通行丢弃
8
9
  '_diyCategorySubtotal',
9
10
  '_diyTagSubtotal',
10
11
  '_customTr'
@@ -345,6 +346,58 @@ export const ensureRowSerialKeys = (
345
346
  return next
346
347
  }
347
348
 
349
+ /**
350
+ * 统一收敛多栏序号,修复「半行续号」与「空行起始序号」两处独立计算导致的序号重复
351
+ * (如 5 条数据双栏:第三行第二列续号为 6,第四行空行起始又编出 6)。
352
+ * - 先给最后一个实数据行的右侧空格续号并写回(渲染侧 ensureRowSerialKeys 不再另行补号)
353
+ * - 空行序号以「全表已用最大序号 + 1」连续重编,与半行续号同源
354
+ * 幂等,重复执行结果不变;仅 fillIndex 开启时生效
355
+ */
356
+ export const normalizeMultiSerials = (rows, colNumber, fillIndex) => {
357
+ if (!fillIndex || !rows?.length || colNumber < 1) return rows
358
+ const keys = getSerialKeys(colNumber)
359
+ const isFill = row => row && !row._isEmptyData && !isSpecialTableRow(row)
360
+
361
+ const result = rows.slice()
362
+
363
+ // 实数据行已用最大序号
364
+ let max = 0
365
+ result.forEach(row => {
366
+ if (!isFill(row)) return
367
+ keys.forEach(key => {
368
+ const n = toSerialNumber(row?.[key])
369
+ if (n != null && n > max) max = n
370
+ })
371
+ })
372
+
373
+ // 1. 最后一个实数据行的空格续号(写回数据,保证序号只有一处来源)
374
+ for (let i = result.length - 1; i >= 0; i--) {
375
+ if (!isFill(result[i])) continue
376
+ const next = { ...result[i] }
377
+ keys.forEach(key => {
378
+ if (!isFilledIndexValue(next[key])) {
379
+ max += 1
380
+ next[key] = max
381
+ }
382
+ })
383
+ result[i] = next
384
+ break
385
+ }
386
+
387
+ // 2. 空行从 max+1 连续重编,不会与实数据序号/半行续号撞号
388
+ result.forEach((row, i) => {
389
+ if (!row?._isEmptyData || isSpecialTableRow(row)) return
390
+ const next = { ...row }
391
+ keys.forEach(key => {
392
+ max += 1
393
+ next[key] = max
394
+ })
395
+ result[i] = next
396
+ })
397
+
398
+ return result
399
+ }
400
+
348
401
  /** 最后一行右侧空单元格续编序号 */
349
402
  export const fillPartialLastRow = (rows, colNumber, fillIndex) => {
350
403
  if (!fillIndex || !rows?.length || colNumber <= 1) return rows
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import _ from 'lodash'
3
- import { getDataKey, isMultiTable } from '../util'
3
+ import { getDataKey, isMultiTable, isMaskedValue, MASKED_VALUE } from '../util'
4
4
  import Big from 'big.js'
5
5
  import { observer } from 'mobx-react'
6
6
  import { get } from 'mobx'
@@ -19,6 +19,8 @@ const regExp = text => {
19
19
  * @returns {*|string}
20
20
  */
21
21
  const sumCol = (key, dataList, summaryFieldsResultToNumber = []) => {
22
+ // 任一值脱敏 '***' 则合计显 '***'(原逻辑 Big('***') 抛异常被 catch,合计显空串)
23
+ if (dataList.some(item => isMaskedValue(item[key]))) return MASKED_VALUE
22
24
  let result
23
25
  try {
24
26
  result = dataList
@@ -132,7 +132,7 @@ class Printer extends React.Component {
132
132
  printerStore.changeTableData()
133
133
  printerStore.computedPages()
134
134
  }
135
- // 获取剩余空白高度,传到editor
135
+ // 获取剩余空白高度,传到 editor
136
136
  getremainpageHeight && getremainpageHeight(printerStore.remainPageHeight)
137
137
  }
138
138
  printerStore.setReady(true)
@@ -7,7 +7,8 @@ import {
7
7
  getDataKey,
8
8
  caclSingleDetailsPageHeight,
9
9
  getArrayMid,
10
- getOverallOrderTrHeight
10
+ getOverallOrderTrHeight,
11
+ isMaskedValue
11
12
  } from '../util'
12
13
  import {
13
14
  isDetailOneRowSelectKey,
@@ -23,6 +24,7 @@ import {
23
24
  detectColNumber,
24
25
  ensureRowSerialKeys,
25
26
  flattenHorizontalPackedRows,
27
+ normalizeMultiSerials,
26
28
  repackVerticalTableByPageRanges,
27
29
  packVerticalNewspaper,
28
30
  packHorizontalRowMajor,
@@ -45,6 +47,8 @@ const canSplitRowDetails = (dataKey, row) => {
45
47
  }
46
48
 
47
49
  const price = (n, f = 2) => {
50
+ // 脱敏值 '***' 原样返回(不能走 isNaN 判空,空单元格仍需显空)
51
+ if (isMaskedValue(n)) return n
48
52
  if (isNaN(n)) return null
49
53
  return Big(n || 0).toFixed(f)
50
54
  }
@@ -394,7 +398,7 @@ class PrinterStore {
394
398
  const currentPageMinimumHeight =
395
399
  allPagesHaveThisHeight + allTableHaveThisHeight
396
400
  // 最后一项是表格时,末页仍要渲染签名:装行可用高度预留签名,避免空行顶进签名区
397
- const isLastContent = index === this.config.contents.length - 1
401
+ const isLastContent = index === contentsLength - 1
398
402
  const signReserve = isLastContent ? this.getSignHeight() : 0
399
403
  /** 当前page可容纳的table高度 */
400
404
  let pageAccomodateTableHeight = roundHeight(
@@ -486,7 +490,7 @@ class PrinterStore {
486
490
  )
487
491
  break
488
492
  }
489
- // 行无法拆分(普通长文本列)或拆分失败
493
+ // 行无法拆分(普通长文本列)或拆分失败时,强制推进 end 防止死循环
490
494
  if (end === begin) {
491
495
  // 新一页可容纳的 table 高度(页高 - 页眉页脚)
492
496
  const fullPageAccomodateTableHeight = roundHeight(
@@ -605,9 +609,8 @@ class PrinterStore {
605
609
  currentPageHeight += this.getSignHeight()
606
610
  }
607
611
 
608
- this.remainPageHeight = Math.max(
609
- 0,
610
- Math.round(pageHeight - currentPageHeight)
612
+ this.remainPageHeight = Math.round(
613
+ Math.max(0, pageHeight - currentPageHeight)
611
614
  )
612
615
  this.isSave = false
613
616
  this.config.isSave = false
@@ -1045,7 +1048,12 @@ class PrinterStore {
1045
1048
  )
1046
1049
  )
1047
1050
  withPartial.push(...fillList)
1048
- this.data._table[dataKey] = withPartial
1051
+ // 半行续号与空行起始序号统一收敛,防止两处各自计算造成序号重复(如双栏 5 条出现两个 6)
1052
+ this.data._table[dataKey] = normalizeMultiSerials(
1053
+ withPartial,
1054
+ colNumber,
1055
+ this.fillIndex
1056
+ )
1049
1057
  }
1050
1058
 
1051
1059
  @action.bound
@@ -2,10 +2,9 @@ import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import _ from 'lodash'
4
4
  // import Big from 'big.js'
5
- import { coverDigit2Uppercase, getDataKey } from '../util'
5
+ import { coverDigit2Uppercase, getDataKey, maskedSum } from '../util'
6
6
  import { observer } from 'mobx-react'
7
7
  import classNames from 'classnames'
8
- import Big from 'big.js'
9
8
 
10
9
  const DiyOverallOrder = props => {
11
10
  const {
@@ -15,20 +14,14 @@ const DiyOverallOrder = props => {
15
14
  } = props
16
15
  const tableData = printerStore.data._table[getDataKey(dataKey)] || []
17
16
 
18
- // 计算合计
17
+ // 计算合计:任一渲染结果脱敏 '***' 则合计显 '***'(整格判定假设同 table_diy_summary_tr)
19
18
  const sumData = field => {
20
- return _.reduce(
21
- tableData,
22
- (a, b, i) => {
23
- let result = a
24
- const bRes = printerStore
25
- .templateTable(field, dataKey, i, pageIndex)
26
- .replace(/\(\)/g, '')
27
- result = a.plus(+bRes || 0)
28
- return result
29
- },
30
- Big(0)
31
- ).toFixed(2)
19
+ const values = _.map(tableData, (b, i) =>
20
+ printerStore
21
+ .templateTable(field, dataKey, i, pageIndex)
22
+ .replace(/\(\)/g, '')
23
+ )
24
+ return maskedSum(values)
32
25
  }
33
26
 
34
27
  if (!diyOverallOrder?.show || !printerStore?.ready) {
@@ -1,11 +1,15 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import _ from 'lodash'
4
- import { coverDigit2Uppercase, getDataKey, isMultiTable } from '../util'
4
+ import {
5
+ coverDigit2Uppercase,
6
+ getDataKey,
7
+ isMultiTable,
8
+ maskedSum
9
+ } from '../util'
5
10
  import { MULTI_SUFFIX, MULTI_SUFFIX3 } from '../config'
6
11
  import { observer } from 'mobx-react'
7
12
  import classNames from 'classnames'
8
- import Big from 'big.js'
9
13
 
10
14
  function buildTemplateField(fieldStr, suffix) {
11
15
  // 如果 field 是 "{{列.下单数}}",提取出 "下单数"
@@ -45,39 +49,18 @@ const DiySummary = props => {
45
49
 
46
50
  // 计算合计
47
51
  const sumData = field => {
48
- if (isMulti) {
49
- return _.reduce(
50
- tableData,
51
- (a, b, i) => {
52
- let result = a
53
- // 先通过 templateTable 获取原始字段的值
54
- const bRes = getRes(field, i)
55
- result = a.plus(+bRes || 0)
56
-
57
- // 双栏
58
- const multiField = buildTemplateField(field, MULTI_SUFFIX)
59
- result = result.plus(+getRes(multiField, i) || 0)
60
-
61
- // 三栏
62
- const multiField3 = buildTemplateField(field, MULTI_SUFFIX3)
63
- result = result.plus(+getRes(multiField3, i) || 0)
64
-
65
- return result
66
- },
67
- Big(0)
68
- ).toFixed(2)
69
- }
70
-
71
- return _.reduce(
72
- tableData,
73
- (a, b, i) => {
74
- let result = a
75
- const bRes = getRes(field, i)
76
- result = a.plus(+bRes || 0)
77
- return result
78
- },
79
- Big(0)
80
- ).toFixed(2)
52
+ // 收集本列渲染结果(含多栏后缀列),任一脱敏 '***' 则合计显 '***'
53
+ // 注意:基于渲染后字符串判定,假设模板整格就是脱敏值;带前后缀的模板(如 '{{列.金额}}元')无法识别
54
+ const values = []
55
+ _.each(tableData, (b, i) => {
56
+ values.push(getRes(field, i))
57
+ if (isMulti) {
58
+ // 双栏/三栏取后缀字段
59
+ values.push(getRes(buildTemplateField(field, MULTI_SUFFIX), i))
60
+ values.push(getRes(buildTemplateField(field, MULTI_SUFFIX3), i))
61
+ }
62
+ })
63
+ return maskedSum(values)
81
64
 
82
65
  function getRes(field, i) {
83
66
  return printerStore
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
4
4
  import _ from 'lodash'
5
5
  import { MULTI_SUFFIX, MULTI_SUFFIX3 } from '../config'
6
6
  import Big from 'big.js'
7
- import { coverDigit2Uppercase, getDataKey } from '../util'
7
+ import { coverDigit2Uppercase, getDataKey, maskedSum } from '../util'
8
8
  import { observer } from 'mobx-react'
9
9
  import { get } from 'mobx'
10
10
  import classNames from 'classnames'
@@ -76,21 +76,18 @@ const SubtotalTr = props => {
76
76
  if (field === 'real_item_price') field = '出库金额'
77
77
  if (field === 'settle_money2') field = '结算金额'
78
78
  if (field === 'total_money2') field = '金额'
79
- return _.reduce(
80
- list,
81
- (a, b) => {
82
- let result = a
83
- result = a.plus(b[field] || 0)
84
- if (b?.[field + MULTI_SUFFIX]) {
85
- result = result.plus(b?.[field + MULTI_SUFFIX])
86
- }
87
- if (b?.[field + MULTI_SUFFIX3]) {
88
- result = result.plus(b?.[field + MULTI_SUFFIX3])
89
- }
90
- return result
91
- },
92
- Big(0)
93
- ).toFixed(2)
79
+ // 收集本列所有值(含多栏后缀列),任一脱敏 '***' 则合计显 '***',否则 Big('***') 会崩溃
80
+ const values = []
81
+ _.each(list, b => {
82
+ values.push(b[field])
83
+ if (b?.[field + MULTI_SUFFIX]) {
84
+ values.push(b[field + MULTI_SUFFIX])
85
+ }
86
+ if (b?.[field + MULTI_SUFFIX3]) {
87
+ values.push(b[field + MULTI_SUFFIX3])
88
+ }
89
+ })
90
+ return maskedSum(values)
94
91
  }
95
92
 
96
93
  const getData = field => {
package/src/util.js CHANGED
@@ -120,7 +120,23 @@ function getOverallOrderTrHeight(overallOrder) {
120
120
  }
121
121
 
122
122
  // eslint-disable-next-line
123
+ // 成本权限脱敏值:后端对无成本权限用户的金额字段返回 '***'(商品维度混合脱敏)
124
+ const MASKED_VALUE = '***'
125
+ const isMaskedValue = v => v === MASKED_VALUE
126
+
127
+ /**
128
+ * 脱敏感知求和:任一值为 '***' 则整体返回 '***'(口径:合计不能暴露脱敏行的贡献)
129
+ * 非数字值按 0 容错,同时兼容原始数值字段与渲染后字符串
130
+ * @param {Array} values 参与求和的值
131
+ */
132
+ const maskedSum = values => {
133
+ if (values.some(isMaskedValue)) return MASKED_VALUE
134
+ return values.reduce((a, b) => a.plus(+b || 0), Big(0)).toFixed(2)
135
+ }
136
+
123
137
  const coverDigit2Uppercase = n => {
138
+ // 脱敏值直接透传:否则 Math.abs('***') 得 NaN,Big(NaN) 会抛异常
139
+ if (isMaskedValue(n)) return MASKED_VALUE
124
140
  if (_.isNil(n) || _.isNaN(n)) {
125
141
  return '-'
126
142
  }
@@ -384,5 +400,8 @@ export {
384
400
  caclSingleDetailsPageHeight,
385
401
  getArrayMid,
386
402
  getColSpanLength,
387
- getOverallOrderTrHeight
403
+ getOverallOrderTrHeight,
404
+ MASKED_VALUE,
405
+ isMaskedValue,
406
+ maskedSum
388
407
  }