diff --git a/src/subdomains/supporting/log/log-job.service.ts b/src/subdomains/supporting/log/log-job.service.ts index 149962fce9..e24c2be8b8 100644 --- a/src/subdomains/supporting/log/log-job.service.ts +++ b/src/subdomains/supporting/log/log-job.service.ts @@ -395,7 +395,10 @@ export class LogJobService { // EUR: Bank -> Scrypt const eurSenderScryptBankTx = recentScryptBankTx.filter( - (b) => eurBankIbans.includes(b.accountIban) && b.creditDebitIndicator === BankTxIndicator.DEBIT, + (b) => + eurBankIbans.includes(b.accountIban) && + b.creditDebitIndicator === BankTxIndicator.DEBIT && + b.instructedCurrency, ); const eurReceiverScryptExchangeTx = recentScryptExchangeTx.filter( (k) => k.type === ExchangeTxType.DEPOSIT && k.status === 'ok' && k.currency === 'EUR' && k.txId, @@ -417,25 +420,13 @@ export class LogJobService { (b) => eurBankIbans.includes(b.accountIban) && b.creditDebitIndicator === BankTxIndicator.CREDIT, ); - // sender and receiver data for Bank -> Scrypt - const { sender: recentChfYapealScryptTx, receiver: recentChfBankTxScrypt } = this.filterSenderPendingList( - chfSenderScryptBankTx, - chfReceiverScryptExchangeTx, - ); - const { sender: recentEurBankToScryptTx, receiver: recentEurBankTxScrypt } = this.filterSenderPendingList( - eurSenderScryptBankTx, - eurReceiverScryptExchangeTx, - ); + // Bank -> Scrypt: 1:1 matching, unmatched senders only + const recentChfYapealScryptTx = this.getUnmatchedSenders(chfSenderScryptBankTx, chfReceiverScryptExchangeTx); + const recentEurBankToScryptTx = this.getUnmatchedSenders(eurSenderScryptBankTx, eurReceiverScryptExchangeTx); - // sender and receiver data for Scrypt -> Bank - const { sender: recentChfScryptYapealTx, receiver: recentChfScryptBankTx } = this.filterSenderPendingList( - chfSenderScryptExchangeTx, - chfReceiverScryptBankTx, - ); - const { sender: recentEurScryptToBankTx, receiver: recentEurScryptBankTx } = this.filterSenderPendingList( - eurSenderScryptExchangeTx, - eurReceiverScryptBankTx, - ); + // Scrypt -> Bank: 1:1 matching, unmatched senders only + const recentChfScryptYapealTx = this.getUnmatchedSenders(chfSenderScryptExchangeTx, chfReceiverScryptBankTx); + const recentEurScryptToBankTx = this.getUnmatchedSenders(eurSenderScryptExchangeTx, eurReceiverScryptBankTx); // assetLog return assets.reduce((prev, curr) => { @@ -593,23 +584,18 @@ export class LogJobService { [...recentChfYapealScryptTx, ...recentEurBankToScryptTx], BankTxType.SCRYPT, ); - const pendingChfBankScryptMinusAmount = this.getPendingBankAmount( - [curr], - recentChfBankTxScrypt, - ExchangeTxType.DEPOSIT, - yapealChfBank.iban, - ); - const pendingEurBankScryptMinusAmount = isScryptEurAsset - ? this.getPendingBankAmount([curr], recentEurBankTxScrypt, ExchangeTxType.DEPOSIT) - : isEurBankAsset - ? 0 - : this.getPendingBankAmount([curr], recentEurBankTxScrypt, ExchangeTxType.DEPOSIT, yapealEurBank.iban); + // With 1:1 matching, matched receivers are already excluded from sender lists — no minus needed + const pendingChfBankScryptMinusAmount = 0; + const pendingEurBankScryptMinusAmount = 0; - // unfiltered lists + // unfiltered lists (1:1 matching) const pendingBankScryptPlusAmountUnfiltered = isScryptEurAsset ? this.getPendingBankAmount( eurBankAssets, - eurSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.bankTxId), + this.getUnmatchedSenders( + eurSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.bankTxId), + eurReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.exchangeTxId), + ), BankTxType.SCRYPT, ) : isEurBankAsset @@ -617,31 +603,19 @@ export class LogJobService { : this.getPendingBankAmount( [curr], [ - ...chfSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.chf?.bankTxId), - ...eurSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.bankTxId), + ...this.getUnmatchedSenders( + chfSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.chf?.bankTxId), + chfReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.chf?.exchangeTxId), + ), + ...this.getUnmatchedSenders( + eurSenderScryptBankTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.bankTxId), + eurReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.exchangeTxId), + ), ], BankTxType.SCRYPT, ); - const pendingChfBankScryptMinusAmountUnfiltered = this.getPendingBankAmount( - [curr], - chfReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.chf?.exchangeTxId), - ExchangeTxType.DEPOSIT, - yapealChfBank.iban, - ); - const pendingEurBankScryptMinusAmountUnfiltered = isScryptEurAsset - ? this.getPendingBankAmount( - [curr], - eurReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.exchangeTxId), - ExchangeTxType.DEPOSIT, - ) - : isEurBankAsset - ? 0 - : this.getPendingBankAmount( - [curr], - eurReceiverScryptExchangeTx.filter((t) => t.id >= financeLogPairIds?.toScrypt?.eur?.exchangeTxId), - ExchangeTxType.DEPOSIT, - yapealEurBank.iban, - ); + const pendingChfBankScryptMinusAmountUnfiltered = 0; + const pendingEurBankScryptMinusAmountUnfiltered = 0; // Scrypt to Bank // @@ -657,17 +631,16 @@ export class LogJobService { : isEurBankAsset ? 0 : this.getPendingBankAmount([curr], recentEurScryptToBankTx, ExchangeTxType.WITHDRAWAL, yapealEurBank.iban); - const pendingScryptBankMinusAmount = isScryptEurAsset - ? this.getPendingBankAmount(eurBankAssets, recentEurScryptBankTx, BankTxType.SCRYPT) - : isEurBankAsset - ? 0 - : this.getPendingBankAmount([curr], [...recentChfScryptBankTx, ...recentEurScryptBankTx], BankTxType.SCRYPT); + const pendingScryptBankMinusAmount = 0; - // unfiltered lists + // unfiltered lists (1:1 matching) const pendingChfScryptBankPlusAmountUnfiltered = financeLogPairIds?.fromScrypt?.chf?.exchangeTxId ? this.getPendingBankAmount( [curr], - chfSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.chf.exchangeTxId), + this.getUnmatchedSenders( + chfSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.chf.exchangeTxId), + chfReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.chf.bankTxId), + ), ExchangeTxType.WITHDRAWAL, yapealChfBank.iban, ) @@ -676,7 +649,10 @@ export class LogJobService { ? financeLogPairIds?.fromScrypt?.eur?.exchangeTxId ? this.getPendingBankAmount( [curr], - eurSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.exchangeTxId), + this.getUnmatchedSenders( + eurSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.exchangeTxId), + eurReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.bankTxId), + ), ExchangeTxType.WITHDRAWAL, ) : 0 @@ -685,31 +661,15 @@ export class LogJobService { : financeLogPairIds?.fromScrypt?.eur?.exchangeTxId ? this.getPendingBankAmount( [curr], - eurSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.exchangeTxId), + this.getUnmatchedSenders( + eurSenderScryptExchangeTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.exchangeTxId), + eurReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.bankTxId), + ), ExchangeTxType.WITHDRAWAL, yapealEurBank.iban, ) : 0; - const pendingScryptBankMinusAmountUnfiltered = isScryptEurAsset - ? financeLogPairIds?.fromScrypt?.eur?.bankTxId - ? this.getPendingBankAmount( - eurBankAssets, - eurReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.bankTxId), - BankTxType.SCRYPT, - ) - : 0 - : isEurBankAsset - ? 0 - : financeLogPairIds?.fromScrypt?.chf?.bankTxId || financeLogPairIds?.fromScrypt?.eur?.bankTxId - ? this.getPendingBankAmount( - [curr], - [ - ...chfReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.chf.bankTxId), - ...eurReceiverScryptBankTx.filter((t) => t.id >= financeLogPairIds.fromScrypt.eur.bankTxId), - ], - BankTxType.SCRYPT, - ) - : 0; + const pendingScryptBankMinusAmountUnfiltered = 0; const fromKrakenUnfiltered = pendingChfKrakenYapealPlusAmountUnfiltered + @@ -1097,6 +1057,40 @@ export class LogJobService { ); } + public getUnmatchedSenders( + senderTx: (BankTx | ExchangeTx)[], + receiverTx: (BankTx | ExchangeTx)[], + ): (BankTx | ExchangeTx)[] { + const before21Days = Util.daysBefore(21); + const recentSenders = senderTx.filter((s) => s.created > before21Days); + + if (!recentSenders.length || !receiverTx.length) return [...recentSenders]; + + const sortedSenders = [...recentSenders].sort((a, b) => a.id - b.id); + const sortedReceivers = [...receiverTx].sort((a, b) => a.id - b.id); + const matchedSenderIds = new Set(); + + for (const receiver of sortedReceivers) { + const receiverAmount = receiver instanceof BankTx ? receiver.instructedAmount : receiver.amount; + + const match = sortedSenders.find((s) => { + if (matchedSenderIds.has(s.id)) return false; + + const senderAmount = s instanceof BankTx ? s.instructedAmount : s.amount; + const senderDate = s instanceof BankTx ? s.valueDate : s.created; + const daysDiff = Math.abs(Util.daysDiff(senderDate, receiver.created)); + + return s instanceof BankTx + ? senderAmount === receiverAmount && daysDiff <= 5 && receiver.created > s.created + : senderAmount === receiverAmount && receiver.created > s.created; + }); + + if (match) matchedSenderIds.add(match.id); + } + + return sortedSenders.filter((s) => !matchedSenderIds.has(s.id)); + } + public filterSenderPendingList( senderTx: (BankTx | ExchangeTx)[], receiverTx: (BankTx | ExchangeTx)[] | undefined,