@actual-app/api 25.12.0-nightly.20251107 → 25.12.0-nightly.20251109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,7 +34,8 @@ export declare class CategoryTemplateContext {
34
34
  private limitCheck;
35
35
  private limitHold;
36
36
  readonly previouslyBudgeted: number;
37
- protected constructor(templates: Template[], category: CategoryEntity, month: string, fromLastMonth: number, budgeted: number, hideDecimal?: boolean);
37
+ private currency;
38
+ protected constructor(templates: Template[], category: CategoryEntity, month: string, fromLastMonth: number, budgeted: number, currencyCode: string, hideDecimal?: boolean);
38
39
  private runGoal;
39
40
  static checkByAndScheduleAndSpend(templates: Template[], month: string): Promise<void>;
40
41
  static checkPercentage(templates: Template[]): Promise<void>;
@@ -42,7 +43,7 @@ export declare class CategoryTemplateContext {
42
43
  private checkSpend;
43
44
  private checkGoal;
44
45
  private removeFraction;
45
- static runSimple(template: SimpleTemplate, limit: number): number;
46
+ static runSimple(template: SimpleTemplate, templateContext: CategoryTemplateContext): number;
46
47
  static runCopy(template: CopyTemplate, templateContext: CategoryTemplateContext): Promise<number>;
47
48
  static runPeriodic(template: PeriodicTemplate, templateContext: CategoryTemplateContext): number;
48
49
  static runSpend(template: SpendTemplate, templateContext: CategoryTemplateContext): Promise<number>;
@@ -0,0 +1,11 @@
1
+ import { NumberFormats } from './util';
2
+ export type Currency = {
3
+ code: string;
4
+ symbol: string;
5
+ name: string;
6
+ decimalPlaces: number;
7
+ numberFormat: NumberFormats;
8
+ symbolFirst: boolean;
9
+ };
10
+ export declare const currencies: Currency[];
11
+ export declare function getCurrency(code: string): Currency;
@@ -41,11 +41,21 @@ export type SpendingWidget = AbstractWidget<'spending-card', {
41
41
  export type CustomReportWidget = AbstractWidget<'custom-report', {
42
42
  id: string;
43
43
  }>;
44
+ export type CrossoverWidget = AbstractWidget<'crossover-card', {
45
+ name?: string;
46
+ expenseCategoryIds?: string[];
47
+ incomeAccountIds?: string[];
48
+ timeFrame?: TimeFrame;
49
+ safeWithdrawalRate?: number;
50
+ estimatedReturn?: number | null;
51
+ projectionType?: 'trend' | 'hampel';
52
+ showHiddenCategories?: boolean;
53
+ } | null>;
44
54
  export type MarkdownWidget = AbstractWidget<'markdown-card', {
45
55
  content: string;
46
56
  text_align?: 'left' | 'right' | 'center';
47
57
  }>;
48
- type SpecializedWidget = NetWorthWidget | CashFlowWidget | SpendingWidget | MarkdownWidget | SummaryWidget | CalendarWidget | FormulaWidget;
58
+ type SpecializedWidget = NetWorthWidget | CashFlowWidget | SpendingWidget | CrossoverWidget | MarkdownWidget | SummaryWidget | CalendarWidget | FormulaWidget;
49
59
  export type Widget = SpecializedWidget | CustomReportWidget;
50
60
  export type NewWidget = Omit<Widget, 'id' | 'tombstone'>;
51
61
  export type ExportImportCustomReportWidget = Omit<CustomReportWidget, 'id' | 'meta' | 'tombstone'> & {
@@ -1,4 +1,4 @@
1
- export type FeatureFlag = 'goalTemplatesEnabled' | 'goalTemplatesUIEnabled' | 'actionTemplating' | 'formulaMode' | 'currency' | 'plugins';
1
+ export type FeatureFlag = 'goalTemplatesEnabled' | 'goalTemplatesUIEnabled' | 'actionTemplating' | 'formulaMode' | 'currency' | 'crossoverReport' | 'plugins';
2
2
  /**
3
3
  * Cross-device preferences. These sync across devices when they are changed.
4
4
  */
@@ -108908,7 +108908,7 @@ async function reconcileTransactions(acctId, transactions, isBankSyncAccount = f
108908
108908
  category: existing.category || trans.category || null,
108909
108909
  imported_payee: trans.imported_payee || null,
108910
108910
  notes: existing.notes || trans.notes || null,
108911
- cleared: trans.cleared ?? existing.cleared,
108911
+ cleared: existing.cleared || trans.cleared || false,
108912
108912
  raw_synced_data: existing.raw_synced_data ?? trans.raw_synced_data ?? null
108913
108913
  };
108914
108914
  const fieldsToMarkUpdated = Object.keys(updates).filter((k) => {
@@ -112125,6 +112125,45 @@ async function getCategoryTemplates() {
112125
112125
  }
112126
112126
  return templates;
112127
112127
  }
112128
+ const currencies = [
112129
+ { code: "", name: "None", symbol: "", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112130
+ { code: "AED", name: "UAE Dirham", symbol: "د.إ", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: false },
112131
+ { code: "ARS", name: "Argentinian Peso", symbol: "Arg$", decimalPlaces: 2, numberFormat: "dot-comma", symbolFirst: true },
112132
+ { code: "AUD", name: "Australian Dollar", symbol: "A$", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112133
+ { code: "BRL", name: "Brazilian Real", symbol: "R$", decimalPlaces: 2, numberFormat: "dot-comma", symbolFirst: true },
112134
+ { code: "CAD", name: "Canadian Dollar", symbol: "CA$", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112135
+ { code: "CHF", name: "Swiss Franc", symbol: "Fr.", decimalPlaces: 2, numberFormat: "apostrophe-dot", symbolFirst: true },
112136
+ { code: "CNY", name: "Yuan Renminbi", symbol: "¥", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112137
+ { code: "CRC", name: "Costa Rican Colón", symbol: "₡", decimalPlaces: 2, numberFormat: "space-comma", symbolFirst: true },
112138
+ { code: "DKK", name: "Danish Krone", symbol: "kr", decimalPlaces: 2, numberFormat: "dot-comma", symbolFirst: false },
112139
+ { code: "EGP", name: "Egyptian Pound", symbol: "ج.م", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: false },
112140
+ { code: "EUR", name: "Euro", symbol: "€", decimalPlaces: 2, numberFormat: "dot-comma", symbolFirst: false },
112141
+ { code: "GBP", name: "Pound Sterling", symbol: "£", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112142
+ { code: "GTQ", name: "Guatemalan Quetzal", symbol: "Q", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112143
+ { code: "HKD", name: "Hong Kong Dollar", symbol: "HK$", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112144
+ { code: "INR", name: "Indian Rupee", symbol: "₹", decimalPlaces: 2, numberFormat: "comma-dot-in", symbolFirst: true },
112145
+ { code: "JMD", name: "Jamaican Dollar", symbol: "J$", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112146
+ { code: "JPY", name: "Japanese Yen", symbol: "¥", decimalPlaces: 0, numberFormat: "comma-dot", symbolFirst: true },
112147
+ { code: "LKR", name: "Sri Lankan Rupee", symbol: "Rs.", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112148
+ { code: "MDL", name: "Moldovan Leu", symbol: "L", decimalPlaces: 2, numberFormat: "dot-comma", symbolFirst: false },
112149
+ { code: "PHP", name: "Philippine Peso", symbol: "₱", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112150
+ { code: "PLN", name: "Polish Złoty", symbol: "zł", decimalPlaces: 2, numberFormat: "space-comma", symbolFirst: false },
112151
+ { code: "QAR", name: "Qatari Riyal", symbol: "ر.ق", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: false },
112152
+ { code: "RON", name: "Romanian Leu", symbol: "lei", decimalPlaces: 2, numberFormat: "dot-comma", symbolFirst: false },
112153
+ { code: "RSD", name: "Serbian Dinar", symbol: "дин", decimalPlaces: 2, numberFormat: "dot-comma", symbolFirst: false },
112154
+ { code: "RUB", name: "Russian Ruble", symbol: "₽", decimalPlaces: 2, numberFormat: "space-comma", symbolFirst: false },
112155
+ { code: "SAR", name: "Saudi Riyal", symbol: "ر.س", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: false },
112156
+ { code: "SEK", name: "Swedish Krona", symbol: "kr", decimalPlaces: 2, numberFormat: "space-comma", symbolFirst: false },
112157
+ { code: "SGD", name: "Singapore Dollar", symbol: "S$", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112158
+ { code: "THB", name: "Thai Baht", symbol: "฿", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112159
+ { code: "TRY", name: "Turkish Lira", symbol: "₺", decimalPlaces: 2, numberFormat: "dot-comma", symbolFirst: true },
112160
+ { code: "UAH", name: "Ukrainian Hryvnia", symbol: "₴", decimalPlaces: 2, numberFormat: "space-comma", symbolFirst: false },
112161
+ { code: "USD", name: "US Dollar", symbol: "$", decimalPlaces: 2, numberFormat: "comma-dot", symbolFirst: true },
112162
+ { code: "UZS", name: "Uzbek Soum", symbol: "UZS", decimalPlaces: 2, numberFormat: "space-comma", symbolFirst: false }
112163
+ ];
112164
+ function getCurrency(code) {
112165
+ return currencies.find((c) => c.code === code) || currencies[0];
112166
+ }
112128
112167
  var isArguments$1;
112129
112168
  var hasRequiredIsArguments$2;
112130
112169
  function requireIsArguments$2() {
@@ -119644,7 +119683,9 @@ class CategoryTemplateContext {
119644
119683
  await CategoryTemplateContext.checkByAndScheduleAndSpend(templates, month);
119645
119684
  await CategoryTemplateContext.checkPercentage(templates);
119646
119685
  const hideDecimal = await aqlQuery(q("preferences").filter({ id: "hideFraction" }).select("*"));
119647
- return new CategoryTemplateContext(templates, category, month, fromLastMonth, budgeted, hideDecimal.data.length > 0 ? hideDecimal.data[0].value === "true" : false);
119686
+ const currencyPref = await aqlQuery(q("preferences").filter({ id: "defaultCurrencyCode" }).select("*"));
119687
+ const currencyCode = currencyPref.data.length > 0 ? currencyPref.data[0].value : "";
119688
+ return new CategoryTemplateContext(templates, category, month, fromLastMonth, budgeted, currencyCode, hideDecimal.data.length > 0 ? hideDecimal.data[0].value === "true" : false);
119648
119689
  }
119649
119690
  isGoalOnly() {
119650
119691
  return this.templates.length === 0 && this.remainder.length === 0 && this.goals.length > 0;
@@ -119688,7 +119729,7 @@ class CategoryTemplateContext {
119688
119729
  let newBudget = 0;
119689
119730
  switch (template.type) {
119690
119731
  case "simple": {
119691
- newBudget = CategoryTemplateContext.runSimple(template, this.limitAmount);
119732
+ newBudget = CategoryTemplateContext.runSimple(template, this);
119692
119733
  break;
119693
119734
  }
119694
119735
  case "copy": {
@@ -119785,7 +119826,7 @@ class CategoryTemplateContext {
119785
119826
  longGoal: this.isLongGoal
119786
119827
  };
119787
119828
  }
119788
- constructor(templates, category, month, fromLastMonth, budgeted, hideDecimal = false) {
119829
+ constructor(templates, category, month, fromLastMonth, budgeted, currencyCode, hideDecimal = false) {
119789
119830
  this.templates = [];
119790
119831
  this.remainder = [];
119791
119832
  this.goals = [];
@@ -119811,6 +119852,7 @@ class CategoryTemplateContext {
119811
119852
  this.month = month;
119812
119853
  this.fromLastMonth = fromLastMonth;
119813
119854
  this.previouslyBudgeted = budgeted;
119855
+ this.currency = getCurrency(currencyCode);
119814
119856
  this.hideDecimal = hideDecimal;
119815
119857
  if (templates) {
119816
119858
  templates.forEach((t2) => {
@@ -119837,7 +119879,7 @@ class CategoryTemplateContext {
119837
119879
  if (this.isGoalOnly())
119838
119880
  this.toBudgetAmount = this.previouslyBudgeted;
119839
119881
  this.isLongGoal = true;
119840
- this.goalAmount = amountToInteger(this.goals[0].amount);
119882
+ this.goalAmount = amountToInteger(this.goals[0].amount, this.currency.decimalPlaces);
119841
119883
  return;
119842
119884
  }
119843
119885
  this.goalAmount = this.fullAmount;
@@ -119901,12 +119943,15 @@ class CategoryTemplateContext {
119901
119943
  }
119902
119944
  if (limitDef.period === "daily") {
119903
119945
  const numDays2 = differenceInCalendarDays(addMonths(this.month, 1), this.month);
119904
- this.limitAmount += amountToInteger(limitDef.amount) * numDays2;
119946
+ this.limitAmount += amountToInteger(limitDef.amount, this.currency.decimalPlaces) * numDays2;
119905
119947
  }
119906
119948
  else if (limitDef.period === "weekly") {
119949
+ if (!limitDef.start) {
119950
+ throw new Error("Weekly limit requires a start date (YYYY-MM-DD)");
119951
+ }
119907
119952
  const nextMonth$1 = nextMonth(this.month);
119908
119953
  let week2 = limitDef.start;
119909
- const baseLimit = amountToInteger(limitDef.amount);
119954
+ const baseLimit = amountToInteger(limitDef.amount, this.currency.decimalPlaces);
119910
119955
  while (week2 < nextMonth$1) {
119911
119956
  if (week2 >= this.month) {
119912
119957
  this.limitAmount += baseLimit;
@@ -119915,7 +119960,7 @@ class CategoryTemplateContext {
119915
119960
  }
119916
119961
  }
119917
119962
  else if (limitDef.period === "monthly") {
119918
- this.limitAmount = amountToInteger(limitDef.amount);
119963
+ this.limitAmount = amountToInteger(limitDef.amount, this.currency.decimalPlaces);
119919
119964
  }
119920
119965
  else {
119921
119966
  throw new Error("Invalid limit period. Check template syntax");
@@ -119949,16 +119994,16 @@ class CategoryTemplateContext {
119949
119994
  }
119950
119995
  }
119951
119996
  removeFraction(amount) {
119952
- return amountToInteger(Math.round(integerToAmount(amount)));
119997
+ return amountToInteger(Math.round(integerToAmount(amount, this.currency.decimalPlaces)), this.currency.decimalPlaces);
119953
119998
  }
119954
119999
  //-----------------------------------------------------------------------------
119955
120000
  // Processor Functions
119956
- static runSimple(template, limit) {
120001
+ static runSimple(template, templateContext) {
119957
120002
  if (template.monthly != null) {
119958
- return amountToInteger(template.monthly);
120003
+ return amountToInteger(template.monthly, templateContext.currency.decimalPlaces);
119959
120004
  }
119960
120005
  else {
119961
- return limit;
120006
+ return templateContext.limitAmount;
119962
120007
  }
119963
120008
  }
119964
120009
  static async runCopy(template, templateContext) {
@@ -119967,7 +120012,7 @@ class CategoryTemplateContext {
119967
120012
  }
119968
120013
  static runPeriodic(template, templateContext) {
119969
120014
  let toBudget = 0;
119970
- const amount = amountToInteger(template.amount);
120015
+ const amount = amountToInteger(template.amount, templateContext.currency.decimalPlaces);
119971
120016
  const period = template.period.period;
119972
120017
  const numPeriods = template.period.amount;
119973
120018
  let date = template.starting;
@@ -120026,7 +120071,7 @@ class CategoryTemplateContext {
120026
120071
  }
120027
120072
  }
120028
120073
  const numMonths = differenceInCalendarMonths(toMonth, templateContext.month);
120029
- const target = amountToInteger(template.amount);
120074
+ const target = amountToInteger(template.amount, templateContext.currency.decimalPlaces);
120030
120075
  if (numMonths < 0) {
120031
120076
  return 0;
120032
120077
  }
@@ -120091,13 +120136,13 @@ class CategoryTemplateContext {
120091
120136
  const period = savedInfo[i].period;
120092
120137
  let amount;
120093
120138
  if (numMonths > shortNumMonths && period) {
120094
- amount = Math.round(amountToInteger(template.amount) / period * (period - numMonths + shortNumMonths));
120139
+ amount = Math.round(amountToInteger(template.amount, templateContext.currency.decimalPlaces) / period * (period - numMonths + shortNumMonths));
120095
120140
  }
120096
120141
  else if (numMonths > shortNumMonths) {
120097
- amount = Math.round(amountToInteger(template.amount) / (numMonths + 1) * (shortNumMonths + 1));
120142
+ amount = Math.round(amountToInteger(template.amount, templateContext.currency.decimalPlaces) / (numMonths + 1) * (shortNumMonths + 1));
120098
120143
  }
120099
120144
  else {
120100
- amount = amountToInteger(template.amount);
120145
+ amount = amountToInteger(template.amount, templateContext.currency.decimalPlaces);
120101
120146
  }
120102
120147
  totalNeeded += amount;
120103
120148
  }
@@ -120119,13 +120164,13 @@ async function storeTemplates({ categoriesWithTemplates, source }) {
120119
120164
  async function applyTemplate({ month }) {
120120
120165
  await storeNoteTemplates();
120121
120166
  const categoryTemplates = await getTemplates();
120122
- const ret = await processTemplate(month, false, categoryTemplates);
120167
+ const ret = await processTemplate(month, false, categoryTemplates, []);
120123
120168
  return ret;
120124
120169
  }
120125
120170
  async function overwriteTemplate({ month }) {
120126
120171
  await storeNoteTemplates();
120127
120172
  const categoryTemplates = await getTemplates();
120128
- const ret = await processTemplate(month, true, categoryTemplates);
120173
+ const ret = await processTemplate(month, true, categoryTemplates, []);
120129
120174
  return ret;
120130
120175
  }
120131
120176
  async function applyMultipleCategoryTemplates({ month, categoryIds }) {
@@ -125214,6 +125259,7 @@ const exportModel = {
125214
125259
  "net-worth-card",
125215
125260
  "cash-flow-card",
125216
125261
  "spending-card",
125262
+ "crossover-card",
125217
125263
  "custom-report",
125218
125264
  "markdown-card",
125219
125265
  "summary-card",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actual-app/api",
3
- "version": "25.12.0-nightly.20251107",
3
+ "version": "25.12.0-nightly.20251109",
4
4
  "license": "MIT",
5
5
  "description": "An API for Actual",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actual-app/api",
3
- "version": "25.12.0-nightly.20251107",
3
+ "version": "25.12.0-nightly.20251109",
4
4
  "license": "MIT",
5
5
  "description": "An API for Actual",
6
6
  "engines": {