perf(reconcile): chunked Promise.all in member loop
This commit is contained in:
parent
678fdfe388
commit
a6304e1c23
2 changed files with 36 additions and 17 deletions
|
|
@ -65,37 +65,56 @@ export default defineEventHandler(async (event) => {
|
|||
let skipped = 0
|
||||
let memberErrors = 0
|
||||
|
||||
for (const member of members) {
|
||||
async function processMember(member) {
|
||||
let txs
|
||||
try {
|
||||
txs = await listTransactionsWithRetry(member.helcimCustomerId)
|
||||
} catch (err) {
|
||||
memberErrors++
|
||||
console.error(`[reconcile] member=${member._id}: ${err?.message || err}`)
|
||||
continue
|
||||
return { error: true }
|
||||
}
|
||||
|
||||
const result = { error: false, txExamined: 0, created: 0, existed: 0, skipped: 0 }
|
||||
|
||||
for (const tx of txs) {
|
||||
txExamined++
|
||||
result.txExamined++
|
||||
if (!RECONCILABLE_STATUSES.has(tx?.status)) {
|
||||
skipped++
|
||||
result.skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
if (!apply) {
|
||||
const existing = await Payment.findOne({ helcimTransactionId: tx.id })
|
||||
if (existing) existed++
|
||||
else created++
|
||||
if (existing) result.existed++
|
||||
else result.created++
|
||||
continue
|
||||
}
|
||||
|
||||
// Note: deliberately NOT passing sendConfirmation — cron back-fills must
|
||||
// not re-send confirmation emails for transactions the member has already
|
||||
// been notified about (or that pre-date Mongo Payment tracking entirely).
|
||||
const result = await upsertPaymentFromHelcim(member, tx)
|
||||
if (result.created) created++
|
||||
else if (result.payment) existed++
|
||||
else skipped++
|
||||
const upsertResult = await upsertPaymentFromHelcim(member, tx)
|
||||
if (upsertResult.created) result.created++
|
||||
else if (upsertResult.payment) result.existed++
|
||||
else result.skipped++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const CHUNK_SIZE = 8
|
||||
for (let i = 0; i < members.length; i += CHUNK_SIZE) {
|
||||
const chunk = members.slice(i, i + CHUNK_SIZE)
|
||||
const results = await Promise.all(chunk.map((m) => processMember(m)))
|
||||
for (const r of results) {
|
||||
if (r.error) {
|
||||
memberErrors++
|
||||
continue
|
||||
}
|
||||
txExamined += r.txExamined
|
||||
created += r.created
|
||||
existed += r.existed
|
||||
skipped += r.skipped
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -162,12 +162,12 @@ describe('POST /api/internal/reconcile-payments', () => {
|
|||
{ _id: 'm3', helcimCustomerId: 'cust-3' }
|
||||
]))
|
||||
// m1 succeeds first try, m2 fails all 3 retries, m3 succeeds first try.
|
||||
listHelcimCustomerTransactions
|
||||
.mockResolvedValueOnce([{ id: 'tx1', status: 'paid', amount: 5 }])
|
||||
.mockRejectedValueOnce(new Error('helcim 503'))
|
||||
.mockRejectedValueOnce(new Error('helcim 503'))
|
||||
.mockRejectedValueOnce(new Error('helcim 503'))
|
||||
.mockResolvedValueOnce([{ id: 'tx3', status: 'paid', amount: 7 }])
|
||||
// Keyed by customerCode so it works regardless of call order (chunked Promise.all).
|
||||
listHelcimCustomerTransactions.mockImplementation((customerCode) => {
|
||||
if (customerCode === 'cust-1') return Promise.resolve([{ id: 'tx1', status: 'paid', amount: 5 }])
|
||||
if (customerCode === 'cust-3') return Promise.resolve([{ id: 'tx3', status: 'paid', amount: 7 }])
|
||||
return Promise.reject(new Error('helcim 503'))
|
||||
})
|
||||
upsertPaymentFromHelcim.mockResolvedValue({ created: true, payment: { _id: 'p' } })
|
||||
|
||||
vi.useFakeTimers()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue