Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1afbe06021 | ||
|
|
3ad1a08e30 |
No files matched your search
@@ -127,6 +127,7 @@ def _payment_detail_row(d: BizProtocolPaymentDetail, pay_mode: str, idx: int) ->
|
|||||||
"installmentDueDate": _fmt_ymd(d.installment_due_date),
|
"installmentDueDate": _fmt_ymd(d.installment_due_date),
|
||||||
"dueAmount": _dec(d.due_amount),
|
"dueAmount": _dec(d.due_amount),
|
||||||
"transferAmount": _dec(d.transfer_amount),
|
"transferAmount": _dec(d.transfer_amount),
|
||||||
|
"refundAmount": _dec(getattr(d, "refund_amount", None)),
|
||||||
"payStatus": st,
|
"payStatus": st,
|
||||||
"payStatusLabel": _PAY_STATUS_LABELS.get(st, st or "—"),
|
"payStatusLabel": _PAY_STATUS_LABELS.get(st, st or "—"),
|
||||||
"transferTime": _fmt_dt(getattr(d, "transfer_time", None)),
|
"transferTime": _fmt_dt(getattr(d, "transfer_time", None)),
|
||||||
@@ -156,6 +157,14 @@ def build_protocol_payment_summary(
|
|||||||
detail_rows = [_payment_detail_row(d, pay_mode, i) for i, d in enumerate(sorted_details)]
|
detail_rows = [_payment_detail_row(d, pay_mode, i) for i, d in enumerate(sorted_details)]
|
||||||
received = _received_amount_from_details(sorted_details)
|
received = _received_amount_from_details(sorted_details)
|
||||||
pending = max(contract - received, 0) if contract is not None else None
|
pending = max(contract - received, 0) if contract is not None else None
|
||||||
|
refund_total = 0.0
|
||||||
|
has_refund = False
|
||||||
|
for d in sorted_details:
|
||||||
|
amt = _dec(getattr(d, "refund_amount", None))
|
||||||
|
if amt is None:
|
||||||
|
continue
|
||||||
|
has_refund = True
|
||||||
|
refund_total += float(amt)
|
||||||
statuses = [(d.pay_status or "").strip() for d in sorted_details]
|
statuses = [(d.pay_status or "").strip() for d in sorted_details]
|
||||||
overall = _protocol_overall_status(statuses, contract, pending)
|
overall = _protocol_overall_status(statuses, contract, pending)
|
||||||
hint = _protocol_status_hint(pay_mode, statuses) or overall["overallPayStatusLabel"]
|
hint = _protocol_status_hint(pay_mode, statuses) or overall["overallPayStatusLabel"]
|
||||||
@@ -165,6 +174,7 @@ def build_protocol_payment_summary(
|
|||||||
"contractAmount": contract,
|
"contractAmount": contract,
|
||||||
"receivedAmount": received,
|
"receivedAmount": received,
|
||||||
"pendingAmount": pending,
|
"pendingAmount": pending,
|
||||||
|
"refundAmount": refund_total if has_refund else None,
|
||||||
"statusHint": hint,
|
"statusHint": hint,
|
||||||
"installmentCount": len(detail_rows) if pay_mode in ("fq", "djfq") else None,
|
"installmentCount": len(detail_rows) if pay_mode in ("fq", "djfq") else None,
|
||||||
"paymentDetails": detail_rows,
|
"paymentDetails": detail_rows,
|
||||||
|
|||||||
@@ -45,10 +45,25 @@ describe('buildPresignLedgerRows', () => {
|
|||||||
assert.equal(row.length, PRESIGN_HEADERS.length)
|
assert.equal(row.length, PRESIGN_HEADERS.length)
|
||||||
assert.deepEqual(row.slice(0, 6), ['1', '2026', '4', '2', 'ZKZX1', '杨育蕾'])
|
assert.deepEqual(row.slice(0, 6), ['1', '2026', '4', '2', 'ZKZX1', '杨育蕾'])
|
||||||
assert.equal(row[9], '趸交')
|
assert.equal(row[9], '趸交')
|
||||||
assert.equal(row[12], '1000元')
|
assert.equal(row[12], '1000元(定金)')
|
||||||
assert.equal(row[13], '2026年4月2日16时49分42秒')
|
assert.equal(row[13], '2026年4月2日16时49分42秒')
|
||||||
assert.equal(row[18], '')
|
assert.equal(row[18], '')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('fills refund amount for voided deposit orders', () => {
|
||||||
|
const [row] = buildPresignLedgerRows([
|
||||||
|
{
|
||||||
|
orderDate: '20250919',
|
||||||
|
motherName: '陈影',
|
||||||
|
confirmedDepositAmount: 1000,
|
||||||
|
transferTimes: ['2025-09-19 17:26:13'],
|
||||||
|
orderStatus: 'void',
|
||||||
|
remark: '悔存-申请退定金'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
assert.equal(row[12], '1000元(定金)')
|
||||||
|
assert.equal(row[18], '1000元')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('buildCellStorageLedgerRows', () => {
|
describe('buildCellStorageLedgerRows', () => {
|
||||||
@@ -77,6 +92,7 @@ describe('buildCellStorageLedgerRows', () => {
|
|||||||
freightId: 'fr-cell-1',
|
freightId: 'fr-cell-1',
|
||||||
freightTransportLabel: '专线车',
|
freightTransportLabel: '专线车',
|
||||||
freightAmountLabel: '120',
|
freightAmountLabel: '120',
|
||||||
|
monthlyVerifyNo: '202509012',
|
||||||
paymentSummary: {
|
paymentSummary: {
|
||||||
payMode: 'fq',
|
payMode: 'fq',
|
||||||
contractAmount: 22880,
|
contractAmount: 22880,
|
||||||
@@ -105,6 +121,7 @@ describe('buildCellStorageLedgerRows', () => {
|
|||||||
const row = rows[0]
|
const row = rows[0]
|
||||||
assert.equal(row.length, CELL_STORAGE_HEADERS.length)
|
assert.equal(row.length, CELL_STORAGE_HEADERS.length)
|
||||||
assert.equal(CELL_STORAGE_HEADERS[13], '涡阳县人民医院')
|
assert.equal(CELL_STORAGE_HEADERS[13], '涡阳县人民医院')
|
||||||
|
assert.equal(row[4], '202509012')
|
||||||
assert.equal(row[5], 'B0000004')
|
assert.equal(row[5], 'B0000004')
|
||||||
assert.equal(row[6], '生效中')
|
assert.equal(row[6], '生效中')
|
||||||
assert.equal(row[9], 'ZKZX34000849')
|
assert.equal(row[9], 'ZKZX34000849')
|
||||||
@@ -113,6 +130,7 @@ describe('buildCellStorageLedgerRows', () => {
|
|||||||
assert.equal(row[14], '分期')
|
assert.equal(row[14], '分期')
|
||||||
assert.equal(row[16], '22880')
|
assert.equal(row[16], '22880')
|
||||||
assert.equal(row[17], '6480')
|
assert.equal(row[17], '6480')
|
||||||
|
assert.equal(row[20], '')
|
||||||
assert.equal(row[22], '2025年9月15日11时13分01秒')
|
assert.equal(row[22], '2025年9月15日11时13分01秒')
|
||||||
assert.equal(row[24], '16400')
|
assert.equal(row[24], '16400')
|
||||||
assert.equal(row[25], '16400')
|
assert.equal(row[25], '16400')
|
||||||
@@ -122,6 +140,27 @@ describe('buildCellStorageLedgerRows', () => {
|
|||||||
assert.equal(row[53], '120')
|
assert.equal(row[53], '120')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('writes negative refund amount for voided protocols', () => {
|
||||||
|
const { rows } = buildCellStorageLedgerRows([
|
||||||
|
{
|
||||||
|
protocol: { protocolStatus: 'yzf', remark: '', voidType: 'khhc', voidDesc: '悔存' },
|
||||||
|
sampleCollectTime: '2025-09-07 13:45:00',
|
||||||
|
motherName: '王晴晴',
|
||||||
|
sampleCode: 'S-VOID',
|
||||||
|
sampleTypeName: '围产期造血干细胞',
|
||||||
|
paymentSummary: {
|
||||||
|
payMode: 'dj',
|
||||||
|
contractAmount: 5000,
|
||||||
|
receivedAmount: 5000,
|
||||||
|
refundAmount: 5000,
|
||||||
|
paymentDetails: []
|
||||||
|
}
|
||||||
|
} as unknown as ProtocolAuditFlatRow
|
||||||
|
])
|
||||||
|
assert.equal(rows[0][19], '5000')
|
||||||
|
assert.equal(rows[0][20], '-5000')
|
||||||
|
})
|
||||||
|
|
||||||
it('merges transport and freight columns for the same freight batch', () => {
|
it('merges transport and freight columns for the same freight batch', () => {
|
||||||
const base = {
|
const base = {
|
||||||
protocol: { protocolStatus: 'sxz', remark: '', voidType: '', voidDesc: '' },
|
protocol: { protocolStatus: 'sxz', remark: '', voidType: '', voidDesc: '' },
|
||||||
@@ -195,6 +234,7 @@ describe('buildResearchLedgerRows', () => {
|
|||||||
assert.equal(row[4], '202508001')
|
assert.equal(row[4], '202508001')
|
||||||
assert.equal(row[6], '实验室废弃')
|
assert.equal(row[6], '实验室废弃')
|
||||||
assert.equal(row[7], '母血乙肝阳性')
|
assert.equal(row[7], '母血乙肝阳性')
|
||||||
|
assert.equal(row[9], '实验室废弃')
|
||||||
assert.equal(row[16], '专线车')
|
assert.equal(row[16], '专线车')
|
||||||
assert.equal(row[17], '100')
|
assert.equal(row[17], '100')
|
||||||
assert.equal(row[18], '女')
|
assert.equal(row[18], '女')
|
||||||
@@ -203,6 +243,24 @@ describe('buildResearchLedgerRows', () => {
|
|||||||
assert.equal(row[23], '1')
|
assert.equal(row[23], '1')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps sample type in column 10 for active research rows', () => {
|
||||||
|
const { rows } = buildResearchLedgerRows([
|
||||||
|
{
|
||||||
|
protocol: { donationStatus: 'sxz', voidType: '', voidDesc: '', monthlyVerifyNo: '202508010' },
|
||||||
|
sampleCollectTime: '2025-08-30 10:00:00',
|
||||||
|
motherName: '正常',
|
||||||
|
sampleTypeName: '围产期造血干细胞',
|
||||||
|
regionName: '亳州',
|
||||||
|
hospitalName: '涡阳县人民医院',
|
||||||
|
freightId: '',
|
||||||
|
freightTransportLabel: '',
|
||||||
|
freightAmountLabel: ''
|
||||||
|
} as unknown as ProtocolAuditFlatRow
|
||||||
|
])
|
||||||
|
assert.equal(rows[0][6], '')
|
||||||
|
assert.equal(rows[0][9], '围产期造血干细胞')
|
||||||
|
})
|
||||||
|
|
||||||
it('maps sdfq void type code to 属地废弃 labels', () => {
|
it('maps sdfq void type code to 属地废弃 labels', () => {
|
||||||
const flat = {
|
const flat = {
|
||||||
protocol: {
|
protocol: {
|
||||||
@@ -237,6 +295,7 @@ describe('buildResearchLedgerRows', () => {
|
|||||||
const row = rows[0]
|
const row = rows[0]
|
||||||
assert.equal(row[6], '属地废弃')
|
assert.equal(row[6], '属地废弃')
|
||||||
assert.equal(row[7], '属地废弃')
|
assert.equal(row[7], '属地废弃')
|
||||||
|
assert.equal(row[9], '属地废弃')
|
||||||
assert.equal(row[20], '剖腹产')
|
assert.equal(row[20], '剖腹产')
|
||||||
assert.equal(row[22], '无母血')
|
assert.equal(row[22], '无母血')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -470,11 +470,26 @@ function transportText(row: ProtocolAuditFlatRow): string {
|
|||||||
return label(TRANSPORT, row.transportType || transport.transportType)
|
return label(TRANSPORT, row.transportType || transport.transportType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function presignDepositText(amount: unknown): string {
|
||||||
|
const money = formatLedgerMoney(amount)
|
||||||
|
return money ? `${money}元(定金)` : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function presignRefundText(row: Row): string {
|
||||||
|
const explicit = formatLedgerMoney(row.refundAmount)
|
||||||
|
if (explicit) return `${explicit}元`
|
||||||
|
const status = text(row.orderStatus)
|
||||||
|
const remark = text(row.remark)
|
||||||
|
const voided = status === 'void' || remark.includes('悔存') || remark.includes('退定金')
|
||||||
|
if (!voided) return ''
|
||||||
|
const deposit = formatLedgerMoney(row.confirmedDepositAmount)
|
||||||
|
return deposit ? `${deposit}元` : ''
|
||||||
|
}
|
||||||
|
|
||||||
export function buildPresignLedgerRows(rows: Row[]): string[][] {
|
export function buildPresignLedgerRows(rows: Row[]): string[][] {
|
||||||
return rows.map((row, index) => {
|
return rows.map((row, index) => {
|
||||||
const orderDate = row.orderDate
|
const orderDate = row.orderDate
|
||||||
const times = Array.isArray(row.transferTimes) ? row.transferTimes : []
|
const times = Array.isArray(row.transferTimes) ? row.transferTimes : []
|
||||||
const received = formatLedgerMoney(row.confirmedDepositAmount)
|
|
||||||
return [
|
return [
|
||||||
String(index + 1),
|
String(index + 1),
|
||||||
yearText(orderDate, ''),
|
yearText(orderDate, ''),
|
||||||
@@ -488,17 +503,31 @@ export function buildPresignLedgerRows(rows: Row[]): string[][] {
|
|||||||
payModeText(row.payMode),
|
payModeText(row.payMode),
|
||||||
formatLedgerDateTime(row.sampleCollectTime),
|
formatLedgerDateTime(row.sampleCollectTime),
|
||||||
formatLedgerMoney(row.contractAmount),
|
formatLedgerMoney(row.contractAmount),
|
||||||
received ? `${received}元` : '',
|
presignDepositText(row.confirmedDepositAmount),
|
||||||
times.map((item) => formatLedgerDateTime(item)).filter(Boolean).join(','),
|
times.map((item) => formatLedgerDateTime(item)).filter(Boolean).join(','),
|
||||||
formatLedgerDate(row.expectedDeliveryDate),
|
formatLedgerDate(row.expectedDeliveryDate),
|
||||||
text(row.salesUserName),
|
text(row.salesUserName),
|
||||||
text(row.regionManagerUserName),
|
text(row.regionManagerUserName),
|
||||||
text(row.remark),
|
text(row.remark),
|
||||||
''
|
presignRefundText(row)
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 自存表退款金额:有退费记录写负值;无退费但已作废时写 -已缴金额。 */
|
||||||
|
function cellStorageRefundText(summary: Row | null, voided: boolean, received: string): string {
|
||||||
|
const explicit = summary?.refundAmount
|
||||||
|
if (explicit != null && explicit !== '') {
|
||||||
|
const amount = Number(explicit)
|
||||||
|
if (Number.isFinite(amount) && amount !== 0) {
|
||||||
|
const abs = formatLedgerMoney(Math.abs(amount))
|
||||||
|
return abs ? `-${abs}` : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!voided || !received) return ''
|
||||||
|
return `-${received}`
|
||||||
|
}
|
||||||
|
|
||||||
function storageLedgerRow(row: ProtocolAuditFlatRow, index: number, research: boolean): string[] {
|
function storageLedgerRow(row: ProtocolAuditFlatRow, index: number, research: boolean): string[] {
|
||||||
const protocol = row.protocol
|
const protocol = row.protocol
|
||||||
const summary = (row.paymentSummary || protocol.paymentSummary) as Row | null
|
const summary = (row.paymentSummary || protocol.paymentSummary) as Row | null
|
||||||
@@ -525,7 +554,8 @@ function storageLedgerRow(row: ProtocolAuditFlatRow, index: number, research: bo
|
|||||||
cells[6] = researchStorageStatus(status, protocol.voidType)
|
cells[6] = researchStorageStatus(status, protocol.voidType)
|
||||||
cells[7] = voidReason(protocol)
|
cells[7] = voidReason(protocol)
|
||||||
cells[8] = text(row.motherName)
|
cells[8] = text(row.motherName)
|
||||||
cells[9] = text(row.sampleTypeName)
|
// 标准大表废弃行第 10 列复用储存状态文案;正常行写样本类型
|
||||||
|
cells[9] = cells[6] || text(row.sampleTypeName)
|
||||||
cells[10] = text(row.regionName)
|
cells[10] = text(row.regionName)
|
||||||
cells[11] = text(row.hospitalName)
|
cells[11] = text(row.hospitalName)
|
||||||
cells[12] = formatLedgerDateTime(row.newbornDeliveryTime)
|
cells[12] = formatLedgerDateTime(row.newbornDeliveryTime)
|
||||||
@@ -547,6 +577,7 @@ function storageLedgerRow(row: ProtocolAuditFlatRow, index: number, research: bo
|
|||||||
cells[1] = yearText(collectTime, '')
|
cells[1] = yearText(collectTime, '')
|
||||||
cells[2] = monthText(collectTime)
|
cells[2] = monthText(collectTime)
|
||||||
cells[3] = dayText(collectTime)
|
cells[3] = dayText(collectTime)
|
||||||
|
cells[4] = text(row.monthlyVerifyNo) || text(protocol.monthlyVerifyNo)
|
||||||
cells[5] = text(row.labStorageCode)
|
cells[5] = text(row.labStorageCode)
|
||||||
cells[6] = statusText(status, protocol.voidType)
|
cells[6] = statusText(status, protocol.voidType)
|
||||||
cells[7] = voidReason(protocol)
|
cells[7] = voidReason(protocol)
|
||||||
@@ -562,6 +593,7 @@ function storageLedgerRow(row: ProtocolAuditFlatRow, index: number, research: bo
|
|||||||
cells[17] = formatLedgerMoney(first.due)
|
cells[17] = formatLedgerMoney(first.due)
|
||||||
cells[18] = received
|
cells[18] = received
|
||||||
cells[19] = voided ? received : ''
|
cells[19] = voided ? received : ''
|
||||||
|
cells[20] = cellStorageRefundText(summary, voided, received)
|
||||||
cells[21] = text(protocol.remark)
|
cells[21] = text(protocol.remark)
|
||||||
cells[22] = formatLedgerDateTime(firstPaidTime)
|
cells[22] = formatLedgerDateTime(firstPaidTime)
|
||||||
cells[23] = gapText(first.due, first.paid)
|
cells[23] = gapText(first.due, first.paid)
|
||||||
|
|||||||
Reference in new issue
Block a user