Files
zkxw/web-admin/src/utils/protocol-audit-flatten.ts
T
zlwuandCursor d413398b3d fix(web-admin): 科研捐赠导出补齐运费并按批次合并单元格
列表未返回样本运费批次导致运费列为空。导出按同一运费批次合并运输方式与运费列,并按模板对齐储存状态等字段。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-06 12:06:49 +08:00

206 lines
6.8 KiB
TypeScript

import type { QcProtocolPaymentSummary } from '../api/qc-work-order'
/** 细胞存储协议 / 科研捐赠列表行(均为 Record) */
export type ProtocolAuditSourceRow = Record<string, unknown>
export type ProtocolAuditSampleTransport = {
transportType?: string
destSiteName?: string
expressCompanyName?: string
waybillNo?: string
carrierName?: string
carrierPhone?: string
etaTime?: string
}
export type ProtocolAuditFlatRow = {
rowKey: string
protocolId: string
protocol: ProtocolAuditSourceRow
sampleCount: number
sampleIndex: number
isCoStored: boolean
/** 协议级项目归属,供扁平行表格 prop 绑定 */
projectName: string
teamName: string
collectDate: string
sampleCollectTime: string
motherName: string
/** 协议编号或捐赠单号 */
protocolNo: string
regionName: string
hospitalName: string
salesUserName: string
regionManagerUserName: string
sampleTypeName: string
sampleCode: string
labStorageCode: string
transportType: string
transportLabel: string
/** 样本运费批次;多样本共用时 id 相同 */
freightId: string
freightTransportLabel: string
freightAmountLabel: string
paymentSummary: QcProtocolPaymentSummary | null
monthlyVerifyNo: string
donationStatus: string
voidDesc: string
voidType: string
newbornDeliveryTime: string
newbornSex: string
newbornWeightGram: string
deliveryMode: string
birthOrder: string
maternalBloodCollectTime: string
consentFormUrls: string
infectiousDiseaseReportUrls: string
attachmentUrls: string
remark: string
}
function asRecord(v: unknown): Record<string, unknown> | null {
return v && typeof v === 'object' ? (v as Record<string, unknown>) : null
}
function str(v: unknown): string {
return v == null ? '' : String(v).trim()
}
export function extractCollectDate(raw?: string | null): string {
const s = str(raw)
if (!s) return ''
const m = s.match(/^(\d{4}-\d{2}-\d{2})/)
return m ? m[1] : ''
}
function readSampleItems(protocol: ProtocolAuditSourceRow): Record<string, unknown>[] {
const raw = protocol.sampleItems
if (Array.isArray(raw) && raw.length) {
return raw.map((x) => asRecord(x) || {})
}
return []
}
export function flattenProtocolAuditRows(
list: ProtocolAuditSourceRow[]
): ProtocolAuditFlatRow[] {
const out: ProtocolAuditFlatRow[] = []
for (const protocol of list || []) {
const protocolId = str(protocol.id)
const items = readSampleItems(protocol)
const sampleCount = items.length
const isCoStored = sampleCount >= 2
const slots = sampleCount > 0 ? items : [null]
slots.forEach((item, sampleIndex) => {
const rec = item || {}
const transport = asRecord(rec.transport)
const freight = asRecord(rec.freight)
const transportType = str(transport?.transportType)
const freightAmountLabel = str(freight?.freightAmountLabel)
const freightAmount =
freight?.freightAmount == null || freight?.freightAmount === ''
? ''
: String(freight.freightAmount)
out.push({
rowKey: `${protocolId}|${str(rec.sampleCode) || sampleIndex}`,
protocolId,
protocol,
sampleCount,
sampleIndex,
isCoStored,
projectName: str(protocol.projectName),
teamName: str(protocol.teamName),
collectDate: extractCollectDate(str(protocol.sampleCollectTime)),
sampleCollectTime: str(protocol.sampleCollectTime),
motherName: str(protocol.motherName),
protocolNo: str(protocol.protocolNo) || str(protocol.donationNo),
regionName: str(protocol.regionName),
hospitalName: str(protocol.hospitalName),
salesUserName: str(protocol.salesUserName),
regionManagerUserName: str(protocol.regionManagerUserName),
sampleTypeName: str(rec.sampleTypeName),
sampleCode: str(rec.sampleCode),
labStorageCode: str(rec.labStorageCode),
transportType,
transportLabel: '',
freightId: str(freight?.id),
freightTransportLabel: str(freight?.transportModeLabel) || str(freight?.transportMode),
freightAmountLabel: freightAmountLabel || freightAmount,
paymentSummary:
protocol.paymentSummary && typeof protocol.paymentSummary === 'object'
? (protocol.paymentSummary as QcProtocolPaymentSummary)
: null,
monthlyVerifyNo: str(protocol.monthlyVerifyNo),
donationStatus: str(protocol.donationStatus),
voidDesc: str(protocol.voidDesc),
voidType: str(protocol.voidType),
newbornDeliveryTime: str(protocol.newbornDeliveryTime),
newbornSex: str(protocol.newbornSex),
newbornWeightGram:
protocol.newbornWeightGram == null || protocol.newbornWeightGram === ''
? ''
: String(protocol.newbornWeightGram),
deliveryMode: str(protocol.deliveryMode),
birthOrder:
protocol.birthOrder == null || protocol.birthOrder === ''
? ''
: String(protocol.birthOrder),
maternalBloodCollectTime: str(protocol.maternalBloodCollectTime),
consentFormUrls: str(protocol.consentFormUrls),
infectiousDiseaseReportUrls: str(protocol.infectiousDiseaseReportUrls),
attachmentUrls: str(protocol.attachmentUrls),
remark: str(protocol.remark)
})
})
}
return out
}
export function uniqueProtocolsFromFlatRows(
rows: ProtocolAuditFlatRow[]
): ProtocolAuditSourceRow[] {
const seen = new Set<string>()
const out: ProtocolAuditSourceRow[] = []
for (const r of rows) {
if (!r.protocolId || seen.has(r.protocolId)) continue
seen.add(r.protocolId)
out.push(r.protocol)
}
return out
}
/**
* 首期缴费时间:优先用后端 firstPayTransferTime(第 1 期转账最早一笔);
* 否则从 paymentDetails 中取第 1 期 transferTime 最早的一笔。
* 无转账时间时返回空(不回退应缴日)。
*/
export function firstPayTimeLines(
summary: QcProtocolPaymentSummary | null | undefined
): string[] {
const preferred = str(summary?.firstPayTransferTime)
if (preferred) return [preferred]
const details = summary?.paymentDetails
if (!Array.isArray(details) || !details.length) return []
const numbered = details.filter(
(d) => d.installmentNo != null && String(d.installmentNo).trim() !== ''
)
let periodRows = details
if (numbered.some((d) => Number(d.installmentNo) === 1)) {
periodRows = details.filter((d) => Number(d.installmentNo) === 1)
} else if (numbered.length) {
const minNo = Math.min(...numbered.map((d) => Number(d.installmentNo)))
periodRows = details.filter((d) => Number(d.installmentNo) === minNo)
} else {
periodRows = [details[0]]
}
const times = periodRows
.map((d) => str(d.transferTime))
.filter(Boolean)
.sort()
if (!times.length) return []
return [times[0]]
}