All files / src/queues/jobs linkPaypalTransactions.ts

7.31% Statements 3/41
0% Branches 0/18
0% Functions 0/4
7.31% Lines 3/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                    1x     1x   1x                                                                                                                                                                                                          
import { TransactionsService, TransactionTypeProperty } from "@billos/firefly-iii-sdk"
import { DateTime } from "luxon"
import pino from "pino"
 
import { client, paypalClient } from "../../client"
import { env } from "../../config"
import { getDateNow } from "../../utils/date"
import { addJobToQueue } from "../utils"
import { SimpleJob } from "./BaseJob"
 
const logger = pino()
 
export class LinkPaypalTransactionsJob extends SimpleJob {
  readonly id = "link-paypal-transactions"
 
  override readonly startDelay = 35
 
  async run(): Promise<void> {
    if (!env.fireflyPaypalAccountToken) {
      logger.info("No PayPal account token found, skipping job")
      return
    }
    // StartDate and EndDate are today - 20 days to today
    const start = getDateNow().minus({ days: 20 }).toISODate()
    const end = getDateNow().toISODate()
 
    // This function will retrieve the Paypal transactions that do not have the tag "Linked"
    const {
      data: { data },
    } = await TransactionsService.listTransaction({ client: paypalClient, query: { start, end, limit: 50, page: 1 } })
    const unlinkedPaypalTransactions = data.filter(
      ({ attributes: { transactions } }) =>
        !transactions[0].tags.includes("Linked") && transactions[0].type === TransactionTypeProperty.WITHDRAWAL,
    )
    logger.info("Found %d Unlinked Paypal transactions", unlinkedPaypalTransactions.length)
 
    if (unlinkedPaypalTransactions.length === 0) {
      logger.info("No unlinked Paypal transactions found, exiting job")
      return
    }
 
    const {
      data: { data: ffData },
    } = await TransactionsService.listTransaction({ client, query: { start, end, limit: 50, page: 1 } })
    // Filtering Firefly III transactions to only include those that do not have the tag "Linked" and have "PayPal" in the description
    const unlinkedFFTransactions = ffData.filter(
      ({ attributes: { transactions } }) => !transactions[0].tags.includes("Linked") && transactions[0].description.includes("PAYPAL"),
    )
 
    logger.info("Found %d Unlinked Firefly III transactions", unlinkedFFTransactions.length)
 
    if (unlinkedFFTransactions.length === 0) {
      logger.info("No unlinked Firefly III transactions found, exiting job")
      return
    }
 
    for (const paypalTransaction of unlinkedPaypalTransactions) {
      const [transaction] = paypalTransaction.attributes.transactions
      // It will retrieve the transactions that do not have the tag "Linked"
      if (transaction.tags.includes("Linked")) {
        continue
      }
      logger.info("Checking unlinked Paypal transaction %s - type: %s - %s", paypalTransaction.id, transaction.type, transaction.amount)
      // Getting the transactions from Firefly III each time to avoid having outdated data
      // Then it will try to find match the transaction with the Paypal transaction
      for (const {
        id,
        attributes: {
          transactions: [ffTransaction],
        },
      } of unlinkedFFTransactions) {
        //  - Amount should match
        if (ffTransaction.amount !== transaction.amount) {
          continue
        }
 
        // Date difference should be less than 5 days
        const ffTransactionDate = DateTime.fromISO(ffTransaction.date)
        const paypalTransactionDate = DateTime.fromISO(transaction.date)
        if (ffTransactionDate.diff(paypalTransactionDate, "days").days > 5) {
          continue
        }
 
        // Add Linked tag to both transactions
        // Add the destination_name of the Paypal transaction to the Firefly III transaction Notes
        logger.info("Linking paypal %s to Firefly III %s", transaction.destination_name, ffTransaction.description)
        await TransactionsService.updateTransaction({
          client: paypalClient,
          path: { id: paypalTransaction.id },
          body: {
            apply_rules: false,
            fire_webhooks: false,
            transactions: [{ tags: [...transaction.tags, "Linked"] }],
          },
        })
        await TransactionsService.updateTransaction({
          client,
          path: { id },
          body: {
            apply_rules: true,
            fire_webhooks: false,
            transactions: [{ tags: [...ffTransaction.tags, "Linked"], notes: transaction.destination_name }],
          },
        })
      }
    }
  }
 
  override async init(): Promise<void> {
    logger.info("Initializing LinkPaypalTransactions job")
    if (env.fireflyPaypalAccountToken) {
      await addJobToQueue(this)
    }
    logger.info("LinkPaypalTransactions job initialized")
  }
}