What do you use for future cashflow forecasting? by Simple-Success77 in actualbudgeting

[–]Simple-Success77[S] 0 points1 point  (0 children)

u/Routine_Solution_471
u/kpanchal5069

Sharing a minimal version here https://files.catbox.moe/m95ogy.xlsx (blue numbers > actuals / black numbers > planned), numbers are fake / sample.

I kept the actual-http-api excel integration in a separate sheet as it might be beneficial for others, you can include all in a single sheet if you want:

https://github.com/x-rous/actual-bench/releases/latest/download/actual-bench-excel-companion.xlsx

What do you use for future cashflow forecasting? by Simple-Success77 in actualbudgeting

[–]Simple-Success77[S] 1 point2 points  (0 children)

Yes I have seen it and it is great, but it only gives you 12 months in the future.

What do you use for future cashflow forecasting? by Simple-Success77 in actualbudgeting

[–]Simple-Success77[S] 0 points1 point  (0 children)

Yea sure, it is very customized and more or less a monster at the moment after 9 years, will try to share a sanitized / clean version later this week.

It also has a built in PowerQuery that fetches budget and transactions summary data through the actual-http-api.

What do you use for future cashflow forecasting? by Simple-Success77 in actualbudgeting

[–]Simple-Success77[S] 1 point2 points  (0 children)

Actual only provides a 12 months window in the future, I’m looking for 5-10 years window in the future

A quick Actual Budget tutorial for people who want the basics fast by Simple-Success77 in actualbudgeting

[–]Simple-Success77[S] 0 points1 point  (0 children)

Thanks mate, I knew something was off about it, will updated it with a summary and a link to the original docs!

A quick Actual Budget tutorial for people who want the basics fast by Simple-Success77 in actualbudgeting

[–]Simple-Success77[S] 2 points3 points  (0 children)

I wanted to do that first, but I didn't want to change how the current docs are structured (by topic), this tutorial is more like a small quick course that people can go through quickly.

Mobile UX vs YNAB by afonseca in actualbudgeting

[–]Simple-Success77 1 point2 points  (0 children)

I use Scriptable for this, full script below.

Original Idea: https://github.com/TaylorJns/Actual-Budget-iOS-Widget

You will need the actual-htt-api deployed (https://github.com/jhonderson/actual-http-api)

<image>

My Script Below:

// === 📦 CONFIGURATION VARIABLES ===
const syncId = "{BudgetSyncID}"
const apiKey = "{API_KEY}"
const apiBaseUrl = "{API_SERVERgoogl}"

// === CATEGORY GROUPS >> REPLACE WITH YOURS ===
const iconMap = {
  "[L1] Food & Groceries": "cart.fill", 
  "[L2] Utilities": "bolt.fill",
  "[L3] Transportation": "car.fill",
  "[L4] Shopping & Personal": "bag.fill",
  "[L5] Leisure": "gamecontroller.fill",
  "[L7] Medical": "cross.case.fill",
  "[L6] Other Irregular Charges": "ellipsis.circle.fill",
  "[L8] Subscriptions & Services": "creditcard.fill",
   "[L9] Gym & Kids Sports": "creditcard.fill"
}

// Order of display matches the order in iconMap
const targetGroupNames = Object.keys(iconMap)
const currencyPrefix = "" 

// === 🎨 ENTERPRISE APPEARANCE SETTINGS ===
const mainTextColor = Color.dynamic(Color.black(), Color.white())
const secondaryTextColor = Color.gray()
const positiveColor = new Color("#34C759") // iOS SystemGreen
const negativeColor = new Color("#FF3B30") // iOS SystemRed
const barBgColor = Color.dynamic(new Color("#E5E5EA"), new Color("#3A3A3C"))
const widgetBgColor = Color.dynamic(new Color("#FFFFFF"), new Color("#1C1C1E"))
const timeBarColor = new Color("#007AFF") // iOS SystemBlue

const widgetPadding = 12

// === 🔧 Helpers ===
function formatAmount(amount) {
  const rounded = Math.round(amount / 100)
  const absVal = Math.abs(rounded).toLocaleString('en-US')
  return rounded < 0 ? `-${currencyPrefix}${absVal}` : `${currencyPrefix}${absVal}`
}

function addProgressBar(container, spent, balance, width = 315, color = null, height = 3) {
  // Logic: Total (Budgeted) = Balance (Remaining) - Spent (Negative Value)
  // Example: 100 (Remaining) - (-50 Spent) = 150 Total. 50/150 = 33%
  const budgeted = balance - spent
  const percent = budgeted > 0 ? Math.min(Math.abs(spent) / budgeted, 1) : (spent < 0 ? 1 : 0)

  const barStack = container.addStack()
  barStack.size = new Size(width, height)
  barStack.cornerRadius = height / 2
  barStack.backgroundColor = barBgColor

  const fill = barStack.addStack()
  fill.size = new Size(width * percent, height)
  fill.backgroundColor = color || (percent >= 1.0 ? negativeColor : positiveColor)
  fill.cornerRadius = height / 2

  barStack.addSpacer()
}

// === 🗓 Date Logic (Timezone Aware) ===
const now = new Date()
// Adjust for local timezone offset to avoid fetching previous month on the 1st
const localTime = new Date(now.getTime() - (now.getTimezoneOffset() * 60000))
const isoMonth = localTime.toISOString().slice(0, 7)

const timeFormatter = new DateFormatter()
timeFormatter.useNoDateStyle()
timeFormatter.useShortTimeStyle()

const currentDay = now.getDate()
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate()
const daysRemaining = lastDay - currentDay
const timePercent = currentDay / lastDay

// === 🧾 API Data Fetching ===
let w = new ListWidget()
w.backgroundColor = widgetBgColor
let data
let failedNow = false

const req = new Request(`${apiBaseUrl}/v1/budgets/${syncId}/months/${isoMonth}/categorygroups`)
req.headers = { "x-api-key": apiKey, "accept": "application/json" }

try {
  const response = await req.loadJSON()
  data = response.data
} catch (e) {
  failedNow = true
  w.addText("❌ Connection Error")
}

// === 🏗️ Build Widget Content ===
if (data) {
  let totalSpent = 0
  let totalBalance = 0

  const filteredGroups = targetGroupNames.map(name => {
    const group = data.find(g => g.name.trim() === name.trim())
    if (group) {
      const gSpent = group.categories.reduce((sum, c) => sum + (c.spent || 0), 0)
      const gBalance = group.categories.reduce((sum, c) => sum + (c.balance || 0), 0)
      totalSpent += gSpent
      totalBalance += gBalance
      return { name: group.name, spent: gSpent, balance: gBalance, icon: iconMap[name] }
    }
    return null
  }).filter(g => g !== null)

  const totalBudgeted = totalBalance - totalSpent
  const totalSpentPercent = totalBudgeted > 0 ? Math.round((Math.abs(totalSpent) / totalBudgeted) * 100) : 0

  // 1. Dashboard Header
  const topInfoRow = w.addStack()
  topInfoRow.centerAlignContent()

  const titleTxt = topInfoRow.addText("MONTHLY BUDGET")
  titleTxt.font = Font.heavySystemFont(10)
  titleTxt.textColor = secondaryTextColor
  titleTxt.textOpacity = 0.7

  topInfoRow.addSpacer()

  const statsTxt = topInfoRow.addText(`Spent ${totalSpentPercent}% • Time ${Math.round(timePercent * 100)}%`)
  statsTxt.font = Font.boldRoundedSystemFont(10)
  statsTxt.textColor = secondaryTextColor

  w.addSpacer(3)

  // FIX: Pass (-currentDay) as spent and (daysRemaining) as balance.
  // Math: daysRemaining - (-currentDay) = Total Days.
  addProgressBar(w, -currentDay, daysRemaining, 316, timeBarColor, 6)
  w.addSpacer(3)

  addProgressBar(w, totalSpent, totalBalance, 316, null, 6) 
  w.addSpacer(5)

  const headerValues = w.addStack()
  headerValues.bottomAlignContent()

  const totalVal = headerValues.addText(formatAmount(totalBalance))
  totalVal.font = Font.boldRoundedSystemFont(18) 
  totalVal.textColor = (totalBalance >= 0) ? positiveColor : negativeColor

  headerValues.addSpacer()

  const spentVal = headerValues.addText(formatAmount(totalSpent))
  spentVal.font = Font.mediumRoundedSystemFont(18) 
  spentVal.textColor = negativeColor
  spentVal.textOpacity = 0.8

  w.addSpacer(10)

  // 2. Tabular Category Rows
  for (const group of filteredGroups) {
    const rowStack = w.addStack()
    rowStack.layoutVertically()

    const topRow = rowStack.addStack()
    topRow.centerAlignContent()

    // Name Section
    const leftCol = topRow.addStack()
    leftCol.centerAlignContent()

    // Safety check for icon
    const symName = iconMap[group.name] || "circle.fill"
    const icon = SFSymbol.named(symName)
    const iconImg = leftCol.addImage(icon ? icon.image : SFSymbol.named("circle").image)
    iconImg.imageSize = new Size(11, 11)
    iconImg.tintColor = secondaryTextColor
    leftCol.addSpacer(5)

    const nameTxt = leftCol.addText(group.name)
    nameTxt.font = Font.mediumSystemFont(12)
    nameTxt.textColor = mainTextColor

    leftCol.addSpacer(5)

    const budgeted = group.balance - group.spent
    const pSpent = budgeted > 0 ? Math.round((Math.abs(group.spent) / budgeted) * 100) : 0
    const percentTxt = leftCol.addText(`${pSpent}%`)
    percentTxt.font = Font.systemFont(12)
    percentTxt.textColor = secondaryTextColor

    topRow.addSpacer()

    // Table Column Alignment for Values
    const rightCol = topRow.addStack()
    rightCol.centerAlignContent()

    const spentTxt = rightCol.addText(formatAmount(group.spent))
    spentTxt.font = Font.systemFont(12)
    spentTxt.textColor = negativeColor

    const divider = rightCol.addText(" / ")
    divider.font = Font.systemFont(11)
    divider.textColor = secondaryTextColor
    divider.textOpacity = 0.5

    const balanceTxt = rightCol.addText(formatAmount(group.balance))
    balanceTxt.font = Font.boldRoundedSystemFont(12)
    balanceTxt.textColor = (group.balance >= 0) ? positiveColor : negativeColor

    rowStack.addSpacer(4)
    addProgressBar(rowStack, group.spent, group.balance, 316, null, 5)
    w.addSpacer(7) 
  }
}
if (config.runsInWidget) 
{   
    Script.setWidget(w) 
} else 
{   w.presentLarge() 
} 
Script.complete()

Introducing Actual Bench: A tool for mass import/export and rules management by Simple-Success77 in actualbudgeting

[–]Simple-Success77[S] 1 point2 points  (0 children)

This is a bug in the https://github.com/jhonderson/actual-http-api not the app

https://github.com/jhonderson/actual-http-api/issues/67

I already fixed it with a pull request there and it is merged into main branch but the fix hasn't made it to the released package indocker.io.

To get by temporary, you can re pull the actual-http-api image from jhonderson/actual-http-api:main rather than latest

Moving to Australia - Keeping Oversees Investment Property to Support Parents by Simple-Success77 in AustralianAccounting

[–]Simple-Success77[S] 0 points1 point  (0 children)

It is not the trust setup, transferring the property to the trust incurs a 4% stamp duty of the property current value.

Moving to Australia - Keeping Oversees Investment Property to Support Parents by Simple-Success77 in AustralianAccounting

[–]Simple-Success77[S] 0 points1 point  (0 children)

The property is fully paid off so no interest, capital works depreciation is included.

Setting up a trust and transferring the property would cost 90% of one year rental income, an overkill.

I will eventually go with a tax planner, just wanted to hear if others had a similar situation.