import type { QcProtocolPaymentSummary } from '../api/qc-work-order' /** 细胞存储协议 / 科研捐赠列表行(均为 Record) */ export type ProtocolAuditSourceRow = Record 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 | null { return v && typeof v === 'object' ? (v as Record) : 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[] { 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() 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]] }