Skip to content

Commit d00da91

Browse files
authored
Merge pull request #3486 from DFXswiss/develop
Release: develop -> main
2 parents 80032a9 + b3ecbdd commit d00da91

2 files changed

Lines changed: 136 additions & 87 deletions

File tree

src/subdomains/supporting/log/__tests__/log-job.service.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,4 +465,67 @@ describe('LogJobService', () => {
465465
receiver: receiverTx.slice(7),
466466
});
467467
});
468+
469+
// --- getUnmatchedSenders (reference-based matching) ---
470+
471+
it('should match sender and receiver by reference', () => {
472+
const senderTx = [createCustomBankTx({ id: 1, created: Util.hoursBefore(24), remittanceInfo: 'DEPOSIT-100' })];
473+
const receiverTx = [createCustomExchangeTx({ id: 1, created: Util.hoursBefore(20), txId: 'DEPOSIT-100' })];
474+
475+
expect(service.getUnmatchedSenders(senderTx, receiverTx)).toEqual([]);
476+
});
477+
478+
it('should return sender when references do not match', () => {
479+
const senderTx = [createCustomBankTx({ id: 1, created: Util.hoursBefore(24), remittanceInfo: 'DEPOSIT-100' })];
480+
const receiverTx = [createCustomExchangeTx({ id: 1, created: Util.hoursBefore(20), txId: 'DEPOSIT-200' })];
481+
482+
expect(service.getUnmatchedSenders(senderTx, receiverTx)).toEqual(senderTx);
483+
});
484+
485+
it('should return sender when sender has no reference', () => {
486+
const senderTx = [createCustomBankTx({ id: 1, created: Util.hoursBefore(24), remittanceInfo: undefined })];
487+
const receiverTx = [createCustomExchangeTx({ id: 1, created: Util.hoursBefore(20), txId: 'DEPOSIT-100' })];
488+
489+
expect(service.getUnmatchedSenders(senderTx, receiverTx)).toEqual(senderTx);
490+
});
491+
492+
it('should return sender when receiver has no reference', () => {
493+
const senderTx = [createCustomBankTx({ id: 1, created: Util.hoursBefore(24), remittanceInfo: 'DEPOSIT-100' })];
494+
const receiverTx = [createCustomExchangeTx({ id: 1, created: Util.hoursBefore(20), txId: undefined })];
495+
496+
expect(service.getUnmatchedSenders(senderTx, receiverTx)).toEqual(senderTx);
497+
});
498+
499+
it('should filter out senders older than 7 days', () => {
500+
const senderTx = [createCustomBankTx({ id: 1, created: Util.hoursBefore(200), remittanceInfo: 'DEPOSIT-100' })];
501+
const receiverTx = [createCustomExchangeTx({ id: 1, created: Util.hoursBefore(20), txId: 'DEPOSIT-200' })];
502+
503+
expect(service.getUnmatchedSenders(senderTx, receiverTx)).toEqual([]);
504+
});
505+
506+
it('should return all senders when receiver list is empty', () => {
507+
const senderTx = [
508+
createCustomBankTx({ id: 1, created: Util.hoursBefore(24), remittanceInfo: 'DEPOSIT-100' }),
509+
createCustomBankTx({ id: 2, created: Util.hoursBefore(24), remittanceInfo: 'DEPOSIT-200' }),
510+
];
511+
512+
expect(service.getUnmatchedSenders(senderTx, [])).toEqual(senderTx);
513+
});
514+
515+
it('should match multiple senders partially', () => {
516+
const senderTx = [
517+
createCustomBankTx({ id: 1, created: Util.hoursBefore(48), remittanceInfo: 'DEPOSIT-100' }),
518+
createCustomBankTx({ id: 2, created: Util.hoursBefore(24), remittanceInfo: 'DEPOSIT-200' }),
519+
];
520+
const receiverTx = [createCustomExchangeTx({ id: 1, created: Util.hoursBefore(20), txId: 'DEPOSIT-100' })];
521+
522+
expect(service.getUnmatchedSenders(senderTx, receiverTx)).toEqual([senderTx[1]]);
523+
});
524+
525+
it('should match ExchangeTx senders by txId against BankTx receivers by remittanceInfo', () => {
526+
const senderTx = [createCustomExchangeTx({ id: 1, created: Util.hoursBefore(24), txId: 'WITHDRAWAL-50' })];
527+
const receiverTx = [createCustomBankTx({ id: 1, created: Util.hoursBefore(20), remittanceInfo: 'WITHDRAWAL-50' })];
528+
529+
expect(service.getUnmatchedSenders(senderTx, receiverTx)).toEqual([]);
530+
});
468531
});

src/subdomains/supporting/log/log-job.service.ts

Lines changed: 73 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ export class LogJobService {
371371
(b) => b.accountIban === yapealChfBank.iban && b.creditDebitIndicator === BankTxIndicator.DEBIT,
372372
);
373373
const chfReceiverScryptExchangeTx = recentScryptExchangeTx.filter(
374-
(k) => k.type === ExchangeTxType.DEPOSIT && k.status === 'ok' && k.currency === 'CHF',
374+
(k) => k.type === ExchangeTxType.DEPOSIT && k.status !== 'failed' && k.currency === 'CHF',
375375
);
376376

377377
// sender and receiver data
@@ -395,47 +395,38 @@ export class LogJobService {
395395

396396
// EUR: Bank -> Scrypt
397397
const eurSenderScryptBankTx = recentScryptBankTx.filter(
398-
(b) => eurBankIbans.includes(b.accountIban) && b.creditDebitIndicator === BankTxIndicator.DEBIT,
398+
(b) =>
399+
eurBankIbans.includes(b.accountIban) &&
400+
b.creditDebitIndicator === BankTxIndicator.DEBIT &&
401+
b.instructedCurrency,
399402
);
400403
const eurReceiverScryptExchangeTx = recentScryptExchangeTx.filter(
401-
(k) => k.type === ExchangeTxType.DEPOSIT && k.status === 'ok' && k.currency === 'EUR' && k.txId,
404+
(k) => k.type === ExchangeTxType.DEPOSIT && k.status !== 'failed' && k.currency === 'EUR',
402405
);
403406

404407
// CHF: Scrypt -> Yapeal
405408
const chfSenderScryptExchangeTx = recentScryptExchangeTx.filter(
406-
(k) => k.type === ExchangeTxType.WITHDRAWAL && k.status === 'ok' && k.currency === 'CHF',
409+
(k) => k.type === ExchangeTxType.WITHDRAWAL && k.status !== 'failed' && k.currency === 'CHF',
407410
);
408411
const chfReceiverScryptBankTx = recentScryptBankTx.filter(
409412
(b) => b.accountIban === yapealChfBank.iban && b.creditDebitIndicator === BankTxIndicator.CREDIT,
410413
);
411414

412415
// EUR: Scrypt -> Bank
413416
const eurSenderScryptExchangeTx = recentScryptExchangeTx.filter(
414-
(k) => k.type === ExchangeTxType.WITHDRAWAL && k.status === 'ok' && k.currency === 'EUR',
417+
(k) => k.type === ExchangeTxType.WITHDRAWAL && k.status !== 'failed' && k.currency === 'EUR',
415418
);
416419
const eurReceiverScryptBankTx = recentScryptBankTx.filter(
417420
(b) => eurBankIbans.includes(b.accountIban) && b.creditDebitIndicator === BankTxIndicator.CREDIT,
418421
);
419422

420-
// sender and receiver data for Bank -> Scrypt
421-
const { sender: recentChfYapealScryptTx, receiver: recentChfBankTxScrypt } = this.filterSenderPendingList(
422-
chfSenderScryptBankTx,
423-
chfReceiverScryptExchangeTx,
424-
);
425-
const { sender: recentEurBankToScryptTx, receiver: recentEurBankTxScrypt } = this.filterSenderPendingList(
426-
eurSenderScryptBankTx,
427-
eurReceiverScryptExchangeTx,
428-
);
423+
// Bank -> Scrypt: 1:1 matching, unmatched senders only
424+
const recentChfYapealScryptTx = this.getUnmatchedSenders(chfSenderScryptBankTx, chfReceiverScryptExchangeTx);
425+
const recentEurBankToScryptTx = this.getUnmatchedSenders(eurSenderScryptBankTx, eurReceiverScryptExchangeTx);
429426

430-
// sender and receiver data for Scrypt -> Bank
431-
const { sender: recentChfScryptYapealTx, receiver: recentChfScryptBankTx } = this.filterSenderPendingList(
432-
chfSenderScryptExchangeTx,
433-
chfReceiverScryptBankTx,
434-
);
435-
const { sender: recentEurScryptToBankTx, receiver: recentEurScryptBankTx } = this.filterSenderPendingList(
436-
eurSenderScryptExchangeTx,
437-
eurReceiverScryptBankTx,
438-
);
427+
// Scrypt -> Bank: 1:1 matching, unmatched senders only
428+
const recentChfScryptYapealTx = this.getUnmatchedSenders(chfSenderScryptExchangeTx, chfReceiverScryptBankTx);
429+
const recentEurScryptToBankTx = this.getUnmatchedSenders(eurSenderScryptExchangeTx, eurReceiverScryptBankTx);
439430

440431
// assetLog
441432
return assets.reduce((prev, curr) => {
@@ -593,55 +584,38 @@ export class LogJobService {
593584
[...recentChfYapealScryptTx, ...recentEurBankToScryptTx],
594585
BankTxType.SCRYPT,
595586
);
596-
const pendingChfBankScryptMinusAmount = this.getPendingBankAmount(
597-
[curr],
598-
recentChfBankTxScrypt,
599-
ExchangeTxType.DEPOSIT,
600-
yapealChfBank.iban,
601-
);
602-
const pendingEurBankScryptMinusAmount = isScryptEurAsset
603-
? this.getPendingBankAmount([curr], recentEurBankTxScrypt, ExchangeTxType.DEPOSIT)
604-
: isEurBankAsset
605-
? 0
606-
: this.getPendingBankAmount([curr], recentEurBankTxScrypt, ExchangeTxType.DEPOSIT, yapealEurBank.iban);
587+
// With 1:1 matching, matched receivers are already excluded from sender lists — no minus needed
588+
const pendingChfBankScryptMinusAmount = 0;
589+
const pendingEurBankScryptMinusAmount = 0;
607590

608-
// unfiltered lists
591+
// unfiltered lists (1:1 matching)
609592
const pendingBankScryptPlusAmountUnfiltered = isScryptEurAsset
610593
? this.getPendingBankAmount(
611594
eurBankAssets,
612-
eurSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.bankTxId),
595+
this.getUnmatchedSenders(
596+
eurSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.bankTxId),
597+
eurReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.exchangeTxId),
598+
),
613599
BankTxType.SCRYPT,
614600
)
615601
: isEurBankAsset
616602
? 0
617603
: this.getPendingBankAmount(
618604
[curr],
619605
[
620-
...chfSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.chf?.bankTxId),
621-
...eurSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.bankTxId),
606+
...this.getUnmatchedSenders(
607+
chfSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.chf?.bankTxId),
608+
chfReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.chf?.exchangeTxId),
609+
),
610+
...this.getUnmatchedSenders(
611+
eurSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.bankTxId),
612+
eurReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.exchangeTxId),
613+
),
622614
],
623615
BankTxType.SCRYPT,
624616
);
625-
const pendingChfBankScryptMinusAmountUnfiltered = this.getPendingBankAmount(
626-
[curr],
627-
chfReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.chf?.exchangeTxId),
628-
ExchangeTxType.DEPOSIT,
629-
yapealChfBank.iban,
630-
);
631-
const pendingEurBankScryptMinusAmountUnfiltered = isScryptEurAsset
632-
? this.getPendingBankAmount(
633-
[curr],
634-
eurReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.exchangeTxId),
635-
ExchangeTxType.DEPOSIT,
636-
)
637-
: isEurBankAsset
638-
? 0
639-
: this.getPendingBankAmount(
640-
[curr],
641-
eurReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.exchangeTxId),
642-
ExchangeTxType.DEPOSIT,
643-
yapealEurBank.iban,
644-
);
617+
const pendingChfBankScryptMinusAmountUnfiltered = 0;
618+
const pendingEurBankScryptMinusAmountUnfiltered = 0;
645619

646620
// Scrypt to Bank //
647621

@@ -657,17 +631,16 @@ export class LogJobService {
657631
: isEurBankAsset
658632
? 0
659633
: this.getPendingBankAmount([curr], recentEurScryptToBankTx, ExchangeTxType.WITHDRAWAL, yapealEurBank.iban);
660-
const pendingScryptBankMinusAmount = isScryptEurAsset
661-
? this.getPendingBankAmount(eurBankAssets, recentEurScryptBankTx, BankTxType.SCRYPT)
662-
: isEurBankAsset
663-
? 0
664-
: this.getPendingBankAmount([curr], [...recentChfScryptBankTx, ...recentEurScryptBankTx], BankTxType.SCRYPT);
634+
const pendingScryptBankMinusAmount = 0;
665635

666-
// unfiltered lists
636+
// unfiltered lists (1:1 matching)
667637
const pendingChfScryptBankPlusAmountUnfiltered = financeLogPairIds?.fromScrypt?.chf?.exchangeTxId
668638
? this.getPendingBankAmount(
669639
[curr],
670-
chfSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.chf.exchangeTxId),
640+
this.getUnmatchedSenders(
641+
chfSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.chf.exchangeTxId),
642+
chfReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.chf.bankTxId),
643+
),
671644
ExchangeTxType.WITHDRAWAL,
672645
yapealChfBank.iban,
673646
)
@@ -676,7 +649,10 @@ export class LogJobService {
676649
? financeLogPairIds?.fromScrypt?.eur?.exchangeTxId
677650
? this.getPendingBankAmount(
678651
[curr],
679-
eurSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.exchangeTxId),
652+
this.getUnmatchedSenders(
653+
eurSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.exchangeTxId),
654+
eurReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.bankTxId),
655+
),
680656
ExchangeTxType.WITHDRAWAL,
681657
)
682658
: 0
@@ -685,31 +661,15 @@ export class LogJobService {
685661
: financeLogPairIds?.fromScrypt?.eur?.exchangeTxId
686662
? this.getPendingBankAmount(
687663
[curr],
688-
eurSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.exchangeTxId),
664+
this.getUnmatchedSenders(
665+
eurSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.exchangeTxId),
666+
eurReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.bankTxId),
667+
),
689668
ExchangeTxType.WITHDRAWAL,
690669
yapealEurBank.iban,
691670
)
692671
: 0;
693-
const pendingScryptBankMinusAmountUnfiltered = isScryptEurAsset
694-
? financeLogPairIds?.fromScrypt?.eur?.bankTxId
695-
? this.getPendingBankAmount(
696-
eurBankAssets,
697-
eurReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.bankTxId),
698-
BankTxType.SCRYPT,
699-
)
700-
: 0
701-
: isEurBankAsset
702-
? 0
703-
: financeLogPairIds?.fromScrypt?.chf?.bankTxId || financeLogPairIds?.fromScrypt?.eur?.bankTxId
704-
? this.getPendingBankAmount(
705-
[curr],
706-
[
707-
...chfReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.chf.bankTxId),
708-
...eurReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.bankTxId),
709-
],
710-
BankTxType.SCRYPT,
711-
)
712-
: 0;
672+
const pendingScryptBankMinusAmountUnfiltered = 0;
713673

714674
const fromKrakenUnfiltered =
715675
pendingChfKrakenYapealPlusAmountUnfiltered +
@@ -1097,6 +1057,32 @@ export class LogJobService {
10971057
);
10981058
}
10991059

1060+
public getUnmatchedSenders(
1061+
senderTx: (BankTx | ExchangeTx)[],
1062+
receiverTx: (BankTx | ExchangeTx)[],
1063+
): (BankTx | ExchangeTx)[] {
1064+
const before7Days = Util.daysBefore(7);
1065+
const recentSenders = senderTx.filter((s) => s.created > before7Days);
1066+
1067+
if (!recentSenders.length || !receiverTx.length) return [...recentSenders];
1068+
1069+
const receiverRefs = new Set<string>();
1070+
for (const r of receiverTx) {
1071+
const ref = this.getTxReference(r);
1072+
if (ref) receiverRefs.add(ref);
1073+
}
1074+
1075+
return recentSenders.filter((s) => {
1076+
const ref = this.getTxReference(s);
1077+
return !ref || !receiverRefs.has(ref);
1078+
});
1079+
}
1080+
1081+
private getTxReference(tx: BankTx | ExchangeTx): string | undefined {
1082+
if (tx instanceof BankTx) return tx.remittanceInfo?.trim() || undefined;
1083+
return tx.txId?.trim() || undefined;
1084+
}
1085+
11001086
public filterSenderPendingList(
11011087
senderTx: (BankTx | ExchangeTx)[],
11021088
receiverTx: (BankTx | ExchangeTx)[] | undefined,

0 commit comments

Comments
 (0)