diff --git a/web-admin/src/utils/collection-ledger-export.test.ts b/web-admin/src/utils/collection-ledger-export.test.ts index 556b307a..4879d0ba 100644 --- a/web-admin/src/utils/collection-ledger-export.test.ts +++ b/web-admin/src/utils/collection-ledger-export.test.ts @@ -10,7 +10,8 @@ import { buildFreightMergeRanges, buildPresignLedgerRows, buildResearchLedgerRows, - formatLedgerDateTime + formatLedgerDateTime, + groupFlatRowsByFreightBatch } from './collection-ledger-export' import type { ProtocolAuditFlatRow } from './protocol-audit-flatten' @@ -194,6 +195,71 @@ describe('buildCellStorageLedgerRows', () => { { startRow: 2, endRow: 3, startCol: 54, endCol: 54 } ]) }) + + it('groups non-adjacent same freight batch before merging cells', () => { + const shared = { + protocol: { protocolStatus: 'sxz', remark: '', voidType: '', voidDesc: '' }, + sampleCollectTime: '2025-09-11 18:13:00', + sampleTypeName: 'NK', + regionName: '常州', + hospitalName: '溧阳市人民医院', + freightTransportLabel: '专线车', + freightAmountLabel: '100' + } + const { rows, merges } = buildCellStorageLedgerRows([ + { + ...shared, + motherName: '甲', + sampleCode: 'S1', + labStorageCode: 'UBMC1', + freightId: 'batch-cell' + } as unknown as ProtocolAuditFlatRow, + { + ...shared, + motherName: '丙', + sampleCode: 'S3', + labStorageCode: 'UBMC3', + freightId: 'other', + freightTransportLabel: '快递', + freightAmountLabel: '25' + } as unknown as ProtocolAuditFlatRow, + { + ...shared, + motherName: '乙', + sampleCode: 'S2', + labStorageCode: 'UBMC2', + freightId: 'batch-cell' + } as unknown as ProtocolAuditFlatRow + ]) + assert.equal(rows[0][8], '甲') + assert.equal(rows[1][8], '乙') + assert.equal(rows[2][8], '丙') + assert.equal(rows[0][52], '专线车') + assert.equal(rows[0][53], '100') + assert.equal(rows[1][52], '') + assert.equal(rows[1][53], '') + assert.equal(rows[2][52], '快递') + assert.equal(rows[2][53], '25') + assert.deepEqual(merges, [ + { startRow: 2, endRow: 3, startCol: 53, endCol: 53 }, + { startRow: 2, endRow: 3, startCol: 54, endCol: 54 } + ]) + }) +}) + +describe('groupFlatRowsByFreightBatch', () => { + it('keeps first-seen batch order and leaves empty freight rows in place', () => { + const arranged = groupFlatRowsByFreightBatch([ + { motherName: 'A', freightId: 'f1' }, + { motherName: 'B', freightId: '' }, + { motherName: 'C', freightId: 'f1' }, + { motherName: 'D', freightId: 'f2' } + ] as unknown as ProtocolAuditFlatRow[]) + assert.deepEqual( + arranged.map((row) => row.motherName), + ['A', 'C', 'B', 'D'] + ) + }) }) describe('buildResearchLedgerRows', () => { @@ -335,6 +401,50 @@ describe('buildResearchLedgerRows', () => { { startRow: 3, endRow: 4, startCol: 18, endCol: 18 } ]) }) + + it('groups non-adjacent same freight batch before merging research cells', () => { + const shared = { + protocol: { donationStatus: 'sxz', voidType: '', voidDesc: '', monthlyVerifyNo: '' }, + sampleCollectTime: '2025-09-11 18:13:00', + sampleTypeName: 'NK', + regionName: '常州', + hospitalName: '溧阳市人民医院', + freightTransportLabel: '专线车', + freightAmountLabel: '100' + } + const { rows, merges } = buildResearchLedgerRows([ + { + ...shared, + motherName: '甲', + labStorageCode: 'A1', + freightId: 'batch-a' + } as unknown as ProtocolAuditFlatRow, + { + ...shared, + motherName: '散', + labStorageCode: 'A9', + freightId: '', + freightTransportLabel: '', + freightAmountLabel: '' + } as unknown as ProtocolAuditFlatRow, + { + ...shared, + motherName: '乙', + labStorageCode: 'A2', + freightId: 'batch-a' + } as unknown as ProtocolAuditFlatRow + ]) + assert.equal(rows[0][8], '甲') + assert.equal(rows[1][8], '乙') + assert.equal(rows[2][8], '散') + assert.equal(rows[0][16], '专线车') + assert.equal(rows[1][16], '') + assert.equal(rows[1][17], '') + assert.deepEqual(merges, [ + { startRow: 2, endRow: 3, startCol: 17, endCol: 17 }, + { startRow: 2, endRow: 3, startCol: 18, endCol: 18 } + ]) + }) }) describe('buildCustomerServiceLedgerRows', () => { diff --git a/web-admin/src/utils/collection-ledger-export.ts b/web-admin/src/utils/collection-ledger-export.ts index 8206bbe0..7fd14d98 100644 --- a/web-admin/src/utils/collection-ledger-export.ts +++ b/web-admin/src/utils/collection-ledger-export.ts @@ -1,8 +1,5 @@ import type { ProtocolAuditFlatRow } from './protocol-audit-flatten' -/** 采集大表导出入口开关;验收未完成前隐藏按钮,改 true 即可恢复。 */ -export const COLLECTION_LEDGER_EXPORT_ENABLED = false - type Row = Record type PayDetail = { installmentNo?: number | null @@ -341,6 +338,36 @@ function researchMaternalBloodText(value: unknown): string { return text(value) ? text(value) : '无母血' } +/** + * 将同一运费批次的行聚拢到一起(按首次出现顺序),无批次行保持相对位置。 + * 模版要求同批次运输方式/运费合并单元格,故导出前先归拢再合并。 + */ +export function groupFlatRowsByFreightBatch( + rows: ProtocolAuditFlatRow[] +): ProtocolAuditFlatRow[] { + const groups = new Map() + const sequence: Array<{ kind: 'batch'; id: string } | { kind: 'single'; row: ProtocolAuditFlatRow }> = + [] + for (const row of rows) { + const freightId = text(row.freightId) + if (!freightId) { + sequence.push({ kind: 'single', row }) + continue + } + if (!groups.has(freightId)) { + groups.set(freightId, []) + sequence.push({ kind: 'batch', id: freightId }) + } + groups.get(freightId)!.push(row) + } + const arranged: ProtocolAuditFlatRow[] = [] + for (const item of sequence) { + if (item.kind === 'single') arranged.push(item.row) + else arranged.push(...(groups.get(item.id) || [])) + } + return arranged +} + /** * 相邻且 freightId 相同的导出行,合并运输方式/运费列(Excel 行号含表头)。 * @param transportCol 运输方式列(1-based) @@ -626,9 +653,10 @@ export function buildCellStorageLedgerRows(rows: ProtocolAuditFlatRow[]): { rows: string[][] merges: LedgerMergeRange[] } { - const freightIds = rows.map((row) => text(row.freightId)) - const built = rows.map((row, index) => storageLedgerRow(row, index, false)) - // 自存表:运输方式=53,运费=54 + const arranged = groupFlatRowsByFreightBatch(rows) + const freightIds = arranged.map((row) => text(row.freightId)) + const built = arranged.map((row, index) => storageLedgerRow(row, index, false)) + // 自存表:运输方式=53,运费=54(对齐模版《自存表》) return { rows: built, merges: applyFreightMerges(built, freightIds, 53, 54) } } @@ -636,9 +664,10 @@ export function buildResearchLedgerRows(rows: ProtocolAuditFlatRow[]): { rows: string[][] merges: LedgerMergeRange[] } { - const freightIds = rows.map((row) => text(row.freightId)) - const built = rows.map((row, index) => storageLedgerRow(row, index, true)) - // 科研表:运输方式=17,运费=18 + const arranged = groupFlatRowsByFreightBatch(rows) + const freightIds = arranged.map((row) => text(row.freightId)) + const built = arranged.map((row, index) => storageLedgerRow(row, index, true)) + // 科研表:运输方式=17,运费=18(对齐模版《科研表》) return { rows: built, merges: applyFreightMerges(built, freightIds, 17, 18) } } diff --git a/web-admin/src/views/cell-storage/protocol-audit/DonationProtocolTab.vue b/web-admin/src/views/cell-storage/protocol-audit/DonationProtocolTab.vue index d90e8162..ba0c0606 100644 --- a/web-admin/src/views/cell-storage/protocol-audit/DonationProtocolTab.vue +++ b/web-admin/src/views/cell-storage/protocol-audit/DonationProtocolTab.vue @@ -75,12 +75,7 @@ 新增 - + 导出 Excel @@ -467,7 +462,6 @@ } from '@/utils/protocol-doc-label' import { loadVoidTypeDict } from '@/utils/void-type-dict' import { - COLLECTION_LEDGER_EXPORT_ENABLED, RESEARCH_HEADERS, buildResearchLedgerRows, fetchAllPages, diff --git a/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue b/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue index 7eb522c8..78a3a4cc 100644 --- a/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue +++ b/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue @@ -79,12 +79,7 @@ 新增 - + 导出 Excel @@ -435,7 +430,6 @@ } from '@/utils/status-tag-registry' import { loadVoidTypeDict } from '@/utils/void-type-dict' import { - COLLECTION_LEDGER_EXPORT_ENABLED, CELL_STORAGE_HEADERS, buildCellStorageLedgerRows, fetchAllPages, diff --git a/web-admin/src/views/cell-storage/sample-info/UnifiedSampleInfoPage.vue b/web-admin/src/views/cell-storage/sample-info/UnifiedSampleInfoPage.vue index 4c98a081..8ea14bda 100644 --- a/web-admin/src/views/cell-storage/sample-info/UnifiedSampleInfoPage.vue +++ b/web-admin/src/views/cell-storage/sample-info/UnifiedSampleInfoPage.vue @@ -93,7 +93,7 @@ 登记入库