列表未返回样本运费批次导致运费列为空。导出按同一运费批次合并运输方式与运费列,并按模板对齐储存状态等字段。 Co-authored-by: Cursor <cursoragent@cursor.com>
206 lines
6.8 KiB
TypeScript
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]]
|
|
}
|