All files / src/queues/jobs updateBillsBudgetLimit.ts

6.12% Statements 3/49
0% Branches 0/8
0% Functions 0/8
6.52% Lines 3/46

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                  1x                                                                     1x   1x                                                                                                                    
import { BillsService, BudgetLimitStore, BudgetsService } from "@billos/firefly-iii-sdk"
import pino from "pino"
 
import { client } from "../../client"
import { env } from "../../config"
import { getDateNow } from "../../utils/date"
import { addJobToQueue } from "../utils"
import { SimpleJob } from "./BaseJob"
 
const logger = pino()
 
async function getTotalAmountOfBills(start: string, end: string): Promise<number> {
  const allBills = await BillsService.listBill({ client, query: { page: 1, limit: 50, start, end } })
  // Filtering inactive bills
  const bills = allBills.data.data.filter(({ attributes }) => attributes.active)
  const paidBills = bills.filter(({ attributes: { paid_dates } }) => paid_dates.length > 0)
  const unpaidBills = bills
    .filter(({ attributes: { paid_dates } }) => paid_dates.length === 0)
    .filter(({ attributes: { next_expected_match } }) => !!next_expected_match)
 
  const maximumUnpaidBill = unpaidBills.reduce((acc, bill) => acc + parseFloat(bill.attributes.amount_max), 0)
  let paidBillsValue = 0
  for (const bill of paidBills) {
    const {
      data: { data: transactions },
    } = await BillsService.listTransactionByBill({
      client,
      path: { id: bill.id },
      query: { page: 1, limit: 50, start, end },
    })
    for (const { attributes } of transactions) {
      for (const { amount } of attributes.transactions) {
        paidBillsValue += parseFloat(amount)
      }
    }
  }
  const total = paidBillsValue + maximumUnpaidBill
  logger.info("You have paid %d in bills", paidBillsValue)
  logger.info("You have at most %d in unpaid bills", maximumUnpaidBill)
  logger.info("Total bills value is at most %d", total)
  return total
}
 
export class UpdateBillsBudgetLimitJob extends SimpleJob {
  readonly id = "update-bills-budget-limit"
 
  override readonly startDelay = 15
 
  async run(): Promise<void> {
    if (!env.billsBudgetId) {
      logger.warn("Bills budget name is not set in environment variables, skipping updateBillsBudgetLimit job")
      return
    }
 
    // Get all budgets
    const start = getDateNow().startOf("month").toISODate()
    const end = getDateNow().endOf("month").toISODate()
 
    const total = await getTotalAmountOfBills(start, end)
 
    const { data: existingLimits } = await BudgetsService.listBudgetLimitByBudget({
      client,
      path: { id: env.billsBudgetId },
      query: { start, end },
    })
 
    if (existingLimits.data.length > 1) {
      throw new Error("There are more than one limit for the bills budget")
    }
 
    const body: BudgetLimitStore = {
      amount: total.toString(),
      budget_id: env.billsBudgetId,
      start,
      end,
      fire_webhooks: false,
    }
 
    if (existingLimits.data.length === 0) {
      logger.info("There are no limits for the bills budget, creating budget limit")
      await BudgetsService.storeBudgetLimit({ client, path: { id: env.billsBudgetId }, body })
      return
    }
 
    if (existingLimits.data[0].attributes.amount === body.amount) {
      logger.info("The bills budget limit is already up to date, no changes needed")
      return
    }
 
    const [limit] = existingLimits.data
    try {
      await BudgetsService.updateBudgetLimit({ client, path: { id: env.billsBudgetId, limitId: limit.id }, body })
    } catch (err) {
      logger.error({ err }, "Error updating bills budget limit:")
    }
    logger.info("Bills budget limit updated")
  }
 
  override async init(): Promise<void> {
    logger.info("Initializing UpdateBillsBudgetLimit job")
    await addJobToQueue(this)
    logger.info("UpdateBillsBudgetLimit job initialized")
  }
}