@actual-app/core 26.3.0

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.
Files changed (358) hide show
  1. package/.swcrc +11 -0
  2. package/bin/build-browser +40 -0
  3. package/bin/copy-migrations +9 -0
  4. package/db.sqlite +0 -0
  5. package/default-db.sqlite +0 -0
  6. package/migrations/.force-copy-windows +0 -0
  7. package/migrations/1548957970627_remove-db-version.sql +5 -0
  8. package/migrations/1550601598648_payees.sql +23 -0
  9. package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
  10. package/migrations/1561751833510_indexes.sql +7 -0
  11. package/migrations/1567699552727_budget.sql +38 -0
  12. package/migrations/1582384163573_cleared.sql +6 -0
  13. package/migrations/1597756566448_rules.sql +10 -0
  14. package/migrations/1608652596043_parent_field.sql +13 -0
  15. package/migrations/1608652596044_trans_views.sql +56 -0
  16. package/migrations/1612625548236_optimize.sql +7 -0
  17. package/migrations/1614782639336_trans_views2.sql +33 -0
  18. package/migrations/1615745967948_meta.sql +10 -0
  19. package/migrations/1616167010796_accounts_order.sql +5 -0
  20. package/migrations/1618975177358_schedules.sql +28 -0
  21. package/migrations/1632571489012_remove_cache.js +136 -0
  22. package/migrations/1679728867040_rules_conditions.sql +5 -0
  23. package/migrations/1681115033845_add_schedule_name.sql +5 -0
  24. package/migrations/1682974838138_remove_payee_rules.sql +5 -0
  25. package/migrations/1685007876842_add_category_hidden.sql +6 -0
  26. package/migrations/1686139660866_remove_account_type.sql +5 -0
  27. package/migrations/1688749527273_transaction_filters.sql +10 -0
  28. package/migrations/1688841238000_add_account_type.sql +5 -0
  29. package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
  30. package/migrations/1694438752000_add_goal_targets.sql +7 -0
  31. package/migrations/1697046240000_add_reconciled.sql +5 -0
  32. package/migrations/1704572023730_add_account_sync_source.sql +5 -0
  33. package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
  34. package/migrations/1707267033000_reports.sql +28 -0
  35. package/migrations/1712784523000_unhide_input_group.sql +8 -0
  36. package/migrations/1716359441000_include_current.sql +5 -0
  37. package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
  38. package/migrations/1720664867241_add_payee_favorite.sql +5 -0
  39. package/migrations/1720665000000_goal_context.sql +6 -0
  40. package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
  41. package/migrations/1722804019000_create_dashboard_table.js +69 -0
  42. package/migrations/1723665565000_prefs.js +59 -0
  43. package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
  44. package/migrations/1736640000000_custom_report_sorting.sql +7 -0
  45. package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
  46. package/migrations/1738491452000_sorting_rename.sql +13 -0
  47. package/migrations/1739139550000_bank_sync_page.sql +7 -0
  48. package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
  49. package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
  50. package/migrations/1749799110000_add_tags.sql +10 -0
  51. package/migrations/1749799110001_tags_tombstone.sql +5 -0
  52. package/migrations/1754611200000_add_category_template_settings.sql +5 -0
  53. package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
  54. package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
  55. package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
  56. package/migrations/1765518577215_multiple_dashboards.js +30 -0
  57. package/migrations/1768872504000_add_payee_locations.sql +21 -0
  58. package/package.json +128 -0
  59. package/src/mocks/arbitrary-schema.ts +162 -0
  60. package/src/mocks/budget.ts +901 -0
  61. package/src/mocks/files/8859-1.qfx +63 -0
  62. package/src/mocks/files/best.data-ever$.QFX +124 -0
  63. package/src/mocks/files/big.data.QiF +91 -0
  64. package/src/mocks/files/budgets/.commit-to-git +0 -0
  65. package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
  66. package/src/mocks/files/camt/camt.053.xml +463 -0
  67. package/src/mocks/files/credit-card.ofx +11 -0
  68. package/src/mocks/files/data-multi-decimal.ofx +64 -0
  69. package/src/mocks/files/data-payee-memo.ofx +75 -0
  70. package/src/mocks/files/data-payee-memo.qif +17 -0
  71. package/src/mocks/files/data.ofx +124 -0
  72. package/src/mocks/files/data.qfx +124 -0
  73. package/src/mocks/files/data.qif +91 -0
  74. package/src/mocks/files/default-budget-template/db.sqlite +0 -0
  75. package/src/mocks/files/default-budget-template/metadata.json +6 -0
  76. package/src/mocks/files/html-vals.qfx +17 -0
  77. package/src/mocks/index.ts +221 -0
  78. package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
  79. package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
  80. package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
  81. package/src/mocks/random.ts +16 -0
  82. package/src/mocks/setup.ts +180 -0
  83. package/src/mocks/spreadsheet.ts +101 -0
  84. package/src/mocks/util.ts +82 -0
  85. package/src/platform/client/connection/README.md +3 -0
  86. package/src/platform/client/connection/__mocks__/index.ts +67 -0
  87. package/src/platform/client/connection/index-types.ts +95 -0
  88. package/src/platform/client/connection/index.browser.ts +213 -0
  89. package/src/platform/client/connection/index.ts +155 -0
  90. package/src/platform/client/undo/index.ts +59 -0
  91. package/src/platform/exceptions/__mocks__/index.ts +7 -0
  92. package/src/platform/exceptions/index.ts +9 -0
  93. package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
  94. package/src/platform/server/asyncStorage/index-types.ts +35 -0
  95. package/src/platform/server/asyncStorage/index.api.ts +2 -0
  96. package/src/platform/server/asyncStorage/index.electron.ts +88 -0
  97. package/src/platform/server/asyncStorage/index.ts +126 -0
  98. package/src/platform/server/connection/README.md +3 -0
  99. package/src/platform/server/connection/__mocks__/index.ts +15 -0
  100. package/src/platform/server/connection/index-types.ts +20 -0
  101. package/src/platform/server/connection/index.api.ts +13 -0
  102. package/src/platform/server/connection/index.electron.ts +102 -0
  103. package/src/platform/server/connection/index.ts +154 -0
  104. package/src/platform/server/fetch/__mocks__/index.ts +3 -0
  105. package/src/platform/server/fetch/index.api.ts +1 -0
  106. package/src/platform/server/fetch/index.electron.ts +18 -0
  107. package/src/platform/server/fetch/index.ts +20 -0
  108. package/src/platform/server/fs/index.api.ts +198 -0
  109. package/src/platform/server/fs/index.electron.ts +208 -0
  110. package/src/platform/server/fs/index.test.ts +117 -0
  111. package/src/platform/server/fs/index.ts +416 -0
  112. package/src/platform/server/fs/path-join.api.ts +1 -0
  113. package/src/platform/server/fs/path-join.electron.ts +1 -0
  114. package/src/platform/server/fs/path-join.ts +97 -0
  115. package/src/platform/server/fs/shared.ts +33 -0
  116. package/src/platform/server/indexeddb/index.ts +115 -0
  117. package/src/platform/server/log/index.ts +43 -0
  118. package/src/platform/server/sqlite/index.api.ts +2 -0
  119. package/src/platform/server/sqlite/index.electron.ts +134 -0
  120. package/src/platform/server/sqlite/index.test.ts +108 -0
  121. package/src/platform/server/sqlite/index.ts +241 -0
  122. package/src/platform/server/sqlite/normalise.ts +9 -0
  123. package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
  124. package/src/platform/server/sqlite/unicodeLike.ts +31 -0
  125. package/src/server/__mocks__/post.ts +9 -0
  126. package/src/server/__snapshots__/main.test.ts.snap +199 -0
  127. package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
  128. package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
  129. package/src/server/accounts/app-bank-sync.test.ts +136 -0
  130. package/src/server/accounts/app.ts +1294 -0
  131. package/src/server/accounts/link.ts +25 -0
  132. package/src/server/accounts/payees.ts +36 -0
  133. package/src/server/accounts/sync.test.ts +679 -0
  134. package/src/server/accounts/sync.ts +1168 -0
  135. package/src/server/accounts/title/index.ts +60 -0
  136. package/src/server/accounts/title/lower-case.ts +93 -0
  137. package/src/server/accounts/title/specials.ts +21 -0
  138. package/src/server/admin/app.ts +241 -0
  139. package/src/server/api-models.ts +244 -0
  140. package/src/server/api.test.ts +36 -0
  141. package/src/server/api.ts +1030 -0
  142. package/src/server/app.ts +91 -0
  143. package/src/server/aql/compiler.test.ts +966 -0
  144. package/src/server/aql/compiler.ts +1222 -0
  145. package/src/server/aql/exec.test.ts +289 -0
  146. package/src/server/aql/exec.ts +128 -0
  147. package/src/server/aql/index.ts +41 -0
  148. package/src/server/aql/schema/executors.test.ts +420 -0
  149. package/src/server/aql/schema/executors.ts +345 -0
  150. package/src/server/aql/schema/index.test.ts +67 -0
  151. package/src/server/aql/schema/index.ts +409 -0
  152. package/src/server/aql/schema-helpers.test.ts +242 -0
  153. package/src/server/aql/schema-helpers.ts +208 -0
  154. package/src/server/aql/views.test.ts +62 -0
  155. package/src/server/aql/views.ts +57 -0
  156. package/src/server/auth/app.ts +387 -0
  157. package/src/server/bench.ts +29 -0
  158. package/src/server/budget/actions.ts +686 -0
  159. package/src/server/budget/app.ts +469 -0
  160. package/src/server/budget/base.test.ts +340 -0
  161. package/src/server/budget/base.ts +339 -0
  162. package/src/server/budget/category-template-context.test.ts +1658 -0
  163. package/src/server/budget/category-template-context.ts +862 -0
  164. package/src/server/budget/cleanup-template.pegjs +27 -0
  165. package/src/server/budget/cleanup-template.ts +408 -0
  166. package/src/server/budget/envelope.ts +403 -0
  167. package/src/server/budget/goal-template.pegjs +110 -0
  168. package/src/server/budget/goal-template.ts +309 -0
  169. package/src/server/budget/report.ts +308 -0
  170. package/src/server/budget/schedule-template.test.ts +184 -0
  171. package/src/server/budget/schedule-template.ts +351 -0
  172. package/src/server/budget/statements.ts +60 -0
  173. package/src/server/budget/template-notes.test.ts +393 -0
  174. package/src/server/budget/template-notes.ts +323 -0
  175. package/src/server/budget/util.ts +25 -0
  176. package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
  177. package/src/server/budgetfiles/app.ts +672 -0
  178. package/src/server/budgetfiles/backups.test.ts +79 -0
  179. package/src/server/budgetfiles/backups.ts +251 -0
  180. package/src/server/cloud-storage.ts +467 -0
  181. package/src/server/dashboard/app.ts +373 -0
  182. package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
  183. package/src/server/db/index.test.ts +300 -0
  184. package/src/server/db/index.ts +855 -0
  185. package/src/server/db/mappings.ts +59 -0
  186. package/src/server/db/sort.ts +58 -0
  187. package/src/server/db/types/index.ts +342 -0
  188. package/src/server/db/util.ts +36 -0
  189. package/src/server/encryption/app.ts +133 -0
  190. package/src/server/encryption/encryption-internals.api.ts +2 -0
  191. package/src/server/encryption/encryption-internals.electron.ts +89 -0
  192. package/src/server/encryption/encryption-internals.ts +109 -0
  193. package/src/server/encryption/encryption.test.ts +19 -0
  194. package/src/server/encryption/index.test.ts +19 -0
  195. package/src/server/encryption/index.ts +89 -0
  196. package/src/server/errors.ts +110 -0
  197. package/src/server/filters/app.ts +191 -0
  198. package/src/server/importers/actual.ts +49 -0
  199. package/src/server/importers/index.ts +58 -0
  200. package/src/server/importers/ynab4-types.ts +163 -0
  201. package/src/server/importers/ynab4.ts +470 -0
  202. package/src/server/importers/ynab5-types.ts +290 -0
  203. package/src/server/importers/ynab5.ts +1193 -0
  204. package/src/server/main-app.ts +25 -0
  205. package/src/server/main.test.ts +392 -0
  206. package/src/server/main.ts +336 -0
  207. package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
  208. package/src/server/migrate/cli.ts +100 -0
  209. package/src/server/migrate/migrations.test.ts +81 -0
  210. package/src/server/migrate/migrations.ts +192 -0
  211. package/src/server/models.ts +184 -0
  212. package/src/server/mutators.ts +139 -0
  213. package/src/server/notes/app.ts +18 -0
  214. package/src/server/payees/app.ts +351 -0
  215. package/src/server/polyfills.ts +26 -0
  216. package/src/server/post.ts +219 -0
  217. package/src/server/preferences/app.ts +249 -0
  218. package/src/server/prefs.ts +91 -0
  219. package/src/server/reports/app.ts +187 -0
  220. package/src/server/rules/action.ts +344 -0
  221. package/src/server/rules/app.ts +193 -0
  222. package/src/server/rules/condition.ts +436 -0
  223. package/src/server/rules/customFunctions.ts +61 -0
  224. package/src/server/rules/formula-action.test.ts +175 -0
  225. package/src/server/rules/handlebars-helpers.ts +131 -0
  226. package/src/server/rules/index.test.ts +1095 -0
  227. package/src/server/rules/index.ts +22 -0
  228. package/src/server/rules/rule-indexer.ts +89 -0
  229. package/src/server/rules/rule-utils.ts +274 -0
  230. package/src/server/rules/rule.ts +193 -0
  231. package/src/server/schedules/app.test.ts +502 -0
  232. package/src/server/schedules/app.ts +644 -0
  233. package/src/server/schedules/find-schedules.ts +391 -0
  234. package/src/server/server-config.ts +59 -0
  235. package/src/server/sheet.test.ts +101 -0
  236. package/src/server/sheet.ts +280 -0
  237. package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
  238. package/src/server/spreadsheet/app.ts +54 -0
  239. package/src/server/spreadsheet/globals.ts +13 -0
  240. package/src/server/spreadsheet/graph-data-structure.ts +165 -0
  241. package/src/server/spreadsheet/scratch +60 -0
  242. package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
  243. package/src/server/spreadsheet/spreadsheet.ts +523 -0
  244. package/src/server/spreadsheet/util.ts +15 -0
  245. package/src/server/sql/init.sql +88 -0
  246. package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
  247. package/src/server/sync/app.ts +29 -0
  248. package/src/server/sync/encoder.ts +129 -0
  249. package/src/server/sync/index.ts +820 -0
  250. package/src/server/sync/make-test-message.ts +19 -0
  251. package/src/server/sync/migrate.test.ts +169 -0
  252. package/src/server/sync/migrate.ts +48 -0
  253. package/src/server/sync/repair.ts +39 -0
  254. package/src/server/sync/reset.ts +91 -0
  255. package/src/server/sync/sync.property.test.ts +385 -0
  256. package/src/server/sync/sync.test.ts +349 -0
  257. package/src/server/sync/utils.ts +3 -0
  258. package/src/server/tags/app.ts +101 -0
  259. package/src/server/tests/mockData.json +9352 -0
  260. package/src/server/tests/mockSyncServer.ts +119 -0
  261. package/src/server/tools/app.ts +152 -0
  262. package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
  263. package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
  264. package/src/server/transactions/app.ts +136 -0
  265. package/src/server/transactions/export/export-to-csv.ts +132 -0
  266. package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
  267. package/src/server/transactions/import/ofx2json.test.ts +33 -0
  268. package/src/server/transactions/import/ofx2json.ts +157 -0
  269. package/src/server/transactions/import/parse-file.test.ts +224 -0
  270. package/src/server/transactions/import/parse-file.ts +286 -0
  271. package/src/server/transactions/import/qif2json.ts +110 -0
  272. package/src/server/transactions/import/xmlcamt2json.ts +168 -0
  273. package/src/server/transactions/index.ts +196 -0
  274. package/src/server/transactions/merge.test.ts +370 -0
  275. package/src/server/transactions/merge.ts +139 -0
  276. package/src/server/transactions/transaction-rules.test.ts +994 -0
  277. package/src/server/transactions/transaction-rules.ts +1038 -0
  278. package/src/server/transactions/transfer.test.ts +221 -0
  279. package/src/server/transactions/transfer.ts +173 -0
  280. package/src/server/undo.ts +271 -0
  281. package/src/server/update.ts +37 -0
  282. package/src/server/util/budget-name.ts +61 -0
  283. package/src/server/util/custom-sync-mapping.ts +48 -0
  284. package/src/server/util/rschedule.ts +9 -0
  285. package/src/shared/__mocks__/platform.ts +7 -0
  286. package/src/shared/__snapshots__/months.test.ts.snap +21 -0
  287. package/src/shared/arithmetic.test.ts +112 -0
  288. package/src/shared/arithmetic.ts +170 -0
  289. package/src/shared/async.test.ts +135 -0
  290. package/src/shared/async.ts +76 -0
  291. package/src/shared/constants.ts +5 -0
  292. package/src/shared/currencies.ts +70 -0
  293. package/src/shared/dashboard.ts +260 -0
  294. package/src/shared/environment.ts +18 -0
  295. package/src/shared/errors.ts +195 -0
  296. package/src/shared/locale.ts +27 -0
  297. package/src/shared/location-utils.test.ts +69 -0
  298. package/src/shared/location-utils.ts +49 -0
  299. package/src/shared/months.test.ts +5 -0
  300. package/src/shared/months.ts +485 -0
  301. package/src/shared/normalisation.ts +6 -0
  302. package/src/shared/platform.electron.ts +21 -0
  303. package/src/shared/platform.ts +20 -0
  304. package/src/shared/query.ts +176 -0
  305. package/src/shared/rules.test.ts +56 -0
  306. package/src/shared/rules.ts +371 -0
  307. package/src/shared/schedules.test.ts +570 -0
  308. package/src/shared/schedules.ts +560 -0
  309. package/src/shared/test-helpers.ts +156 -0
  310. package/src/shared/transactions.test.ts +275 -0
  311. package/src/shared/transactions.ts +433 -0
  312. package/src/shared/transfer.test.ts +75 -0
  313. package/src/shared/transfer.ts +16 -0
  314. package/src/shared/user.ts +4 -0
  315. package/src/shared/util.test.ts +240 -0
  316. package/src/shared/util.ts +633 -0
  317. package/src/types/api-handlers.ts +287 -0
  318. package/src/types/budget.ts +8 -0
  319. package/src/types/file.ts +47 -0
  320. package/src/types/handlers.ts +46 -0
  321. package/src/types/models/account.ts +24 -0
  322. package/src/types/models/bank-sync.ts +23 -0
  323. package/src/types/models/bank.ts +6 -0
  324. package/src/types/models/category-group.ts +11 -0
  325. package/src/types/models/category.ts +13 -0
  326. package/src/types/models/dashboard.ts +199 -0
  327. package/src/types/models/gocardless.ts +84 -0
  328. package/src/types/models/import-transaction.ts +56 -0
  329. package/src/types/models/index.ts +23 -0
  330. package/src/types/models/nearby-payee.ts +7 -0
  331. package/src/types/models/note.ts +4 -0
  332. package/src/types/models/openid.ts +8 -0
  333. package/src/types/models/payee-location.ts +8 -0
  334. package/src/types/models/payee.ts +10 -0
  335. package/src/types/models/pluggyai.ts +19 -0
  336. package/src/types/models/reports.ts +144 -0
  337. package/src/types/models/rule.ts +174 -0
  338. package/src/types/models/schedule.ts +49 -0
  339. package/src/types/models/simplefin.ts +28 -0
  340. package/src/types/models/tags.ts +6 -0
  341. package/src/types/models/templates.ts +135 -0
  342. package/src/types/models/transaction-filter.ts +9 -0
  343. package/src/types/models/transaction.ts +39 -0
  344. package/src/types/models/user-access.ts +10 -0
  345. package/src/types/models/user.ts +25 -0
  346. package/src/types/prefs.ts +167 -0
  347. package/src/types/server-events.ts +86 -0
  348. package/src/types/server-handlers.ts +27 -0
  349. package/src/types/util.ts +26 -0
  350. package/tsconfig.json +34 -0
  351. package/typings/pegjs.ts +1 -0
  352. package/typings/process-worker.ts +12 -0
  353. package/typings/vite-plugin-peggy-loader.ts +1 -0
  354. package/typings/window.ts +62 -0
  355. package/vite.config.ts +109 -0
  356. package/vite.desktop.config.ts +59 -0
  357. package/vitest.config.ts +43 -0
  358. package/vitest.web.config.ts +38 -0
@@ -0,0 +1,27 @@
1
+ // https://peggyjs.org
2
+
3
+ expr
4
+ = source
5
+ { return { group: null, type: 'source' }}
6
+ /
7
+ sink _? weight: weight?
8
+ { return { type: 'sink', weight: +weight || 1, group: null } }
9
+ /
10
+ group: sourcegroup _? source
11
+ {return {group: group || null, type: 'source'}}
12
+ /
13
+ group: sinkgroup? _? sink _? weight: weight?
14
+ { return { type: 'sink', weight: +weight || 1, group: group || null } }
15
+ /
16
+ group: sourcegroup
17
+ {return {group: group, type: null}}
18
+
19
+ source = 'source'
20
+ sink = 'sink'
21
+
22
+ _ 'space' = ' '+
23
+ d 'digit' = [0-9]
24
+
25
+ weight 'weight' = weight: $(d+) { return +weight }
26
+ sourcegroup 'Name'= $(string:(!" source" .)*)
27
+ sinkgroup 'Name' = $(string:(!" sink" .)*)
@@ -0,0 +1,408 @@
1
+ // @ts-strict-ignore
2
+ import * as monthUtils from '../../shared/months';
3
+ import * as db from '../db';
4
+
5
+ import { getSheetValue, setBudget, setGoal } from './actions';
6
+ import { parse } from './cleanup-template.pegjs';
7
+
8
+ type Notification = {
9
+ type?: 'message' | 'error' | 'warning' | undefined;
10
+ pre?: string | undefined;
11
+ title?: string | undefined;
12
+ message: string;
13
+ sticky?: boolean | undefined;
14
+ };
15
+
16
+ export function cleanupTemplate({ month }: { month: string }) {
17
+ return processCleanup(month);
18
+ }
19
+
20
+ async function applyGroupCleanups(
21
+ month: string,
22
+ sourceGroups,
23
+ sinkGroups,
24
+ generalGroups,
25
+ ) {
26
+ const sheetName = monthUtils.sheetForMonth(month);
27
+ const warnings = [];
28
+ const db_month = parseInt(month.replace('-', ''));
29
+ let groupLength = sourceGroups.length;
30
+ while (groupLength > 0) {
31
+ //function for each unique group
32
+ const groupName = sourceGroups[0].group;
33
+ const tempSourceGroups = sourceGroups.filter(c => c.group === groupName);
34
+ const sinkGroup = sinkGroups.filter(c => c.group === groupName);
35
+ const generalGroup = generalGroups.filter(c => c.group === groupName);
36
+ let total_weight = 0;
37
+ // We track how mouch amount was produced by all group sinks and only
38
+ // distribute this instead of the "to-budget" amount.
39
+ let available_amount = 0;
40
+
41
+ if (sinkGroup.length > 0 || generalGroup.length > 0) {
42
+ //only return group source funds to To Budget if there are corresponding sinking groups or underfunded included groups
43
+ for (let ii = 0; ii < tempSourceGroups.length; ii++) {
44
+ const balance = await getSheetValue(
45
+ sheetName,
46
+ `leftover-${tempSourceGroups[ii].category}`,
47
+ );
48
+ const budgeted = await getSheetValue(
49
+ sheetName,
50
+ `budget-${tempSourceGroups[ii].category}`,
51
+ );
52
+ await setBudget({
53
+ category: tempSourceGroups[ii].category,
54
+ month,
55
+ amount: budgeted - balance,
56
+ });
57
+ available_amount += balance;
58
+ }
59
+
60
+ //calculate total weight for sinking funds
61
+ for (let ii = 0; ii < sinkGroup.length; ii++) {
62
+ total_weight += sinkGroup[ii].weight;
63
+ }
64
+
65
+ //fill underfunded categories within the group first
66
+ for (let ii = 0; ii < generalGroup.length && available_amount > 0; ii++) {
67
+ const balance = await getSheetValue(
68
+ sheetName,
69
+ `leftover-${generalGroup[ii].category}`,
70
+ );
71
+ const budgeted = await getSheetValue(
72
+ sheetName,
73
+ `budget-${generalGroup[ii].category}`,
74
+ );
75
+ const to_budget = budgeted + Math.abs(balance);
76
+ const categoryId = generalGroup[ii].category;
77
+ let carryover = await db.first<Pick<db.DbZeroBudget, 'carryover'>>(
78
+ `SELECT carryover FROM zero_budgets WHERE month = ? and category = ?`,
79
+ [db_month, categoryId],
80
+ );
81
+
82
+ if (carryover === null) {
83
+ carryover = { carryover: 0 };
84
+ }
85
+
86
+ if (
87
+ // We have enough to fully cover the overspent.
88
+ balance < 0 &&
89
+ Math.abs(balance) <= available_amount &&
90
+ !generalGroup[ii].category.is_income &&
91
+ carryover.carryover === 0
92
+ ) {
93
+ await setBudget({
94
+ category: generalGroup[ii].category,
95
+ month,
96
+ amount: to_budget,
97
+ });
98
+ available_amount -= Math.abs(balance);
99
+ } else if (
100
+ // We can only cover this category partially.
101
+ balance < 0 &&
102
+ !generalGroup[ii].category.is_income &&
103
+ carryover.carryover === 0 &&
104
+ Math.abs(balance) > available_amount
105
+ ) {
106
+ await setBudget({
107
+ category: generalGroup[ii].category,
108
+ month,
109
+ amount: budgeted + available_amount,
110
+ });
111
+ available_amount = 0;
112
+ }
113
+ }
114
+ for (let ii = 0; ii < sinkGroup.length && available_amount > 0; ii++) {
115
+ const budgeted = await getSheetValue(
116
+ sheetName,
117
+ `budget-${sinkGroup[ii].category}`,
118
+ );
119
+ const to_budget =
120
+ budgeted +
121
+ Math.round((sinkGroup[ii].weight / total_weight) * available_amount);
122
+ await setBudget({
123
+ category: sinkGroup[ii].category,
124
+ month,
125
+ amount: to_budget,
126
+ });
127
+ }
128
+ } else {
129
+ warnings.push(groupName + ' has no matching sink categories.');
130
+ }
131
+ sourceGroups = sourceGroups.filter(c => c.group !== groupName);
132
+ groupLength = sourceGroups.length;
133
+ }
134
+ return warnings;
135
+ }
136
+
137
+ async function processCleanup(month: string): Promise<Notification> {
138
+ let num_sources = 0;
139
+ let num_sinks = 0;
140
+ let total_weight = 0;
141
+ const errors = [];
142
+ const warnings = [];
143
+ const sinkCategory = [];
144
+ const sourceWithRollover = [];
145
+ const db_month = parseInt(month.replace('-', ''));
146
+
147
+ const category_templates = await getCategoryTemplates();
148
+ const categories = await db.all<db.DbViewCategory>(
149
+ 'SELECT * FROM v_categories WHERE tombstone = 0',
150
+ );
151
+ const sheetName = monthUtils.sheetForMonth(month);
152
+ const groupSource = [];
153
+ const groupSink = [];
154
+ const groupGeneral = [];
155
+
156
+ //filter out category groups
157
+ for (let c = 0; c < categories.length; c++) {
158
+ const category = categories[c];
159
+ const template = category_templates[category.id];
160
+
161
+ //filter out source and sink groups for processing
162
+ if (template) {
163
+ if (
164
+ template.filter(t => t.type === 'source' && t.group !== null).length > 0
165
+ ) {
166
+ groupSource.push({
167
+ category: category.id,
168
+ group: template.filter(
169
+ t => t.type === 'source' && t.group !== null,
170
+ )[0].group,
171
+ });
172
+ }
173
+ if (
174
+ template.filter(t => t.type === 'sink' && t.group !== null).length > 0
175
+ ) {
176
+ //only supports 1 sink reference per category. Need more?
177
+ groupSink.push({
178
+ category: category.id,
179
+ group: template.filter(t => t.type === 'sink' && t.group !== null)[0]
180
+ .group,
181
+ weight: template.filter(t => t.type === 'sink' && t.group !== null)[0]
182
+ .weight,
183
+ });
184
+ }
185
+ if (
186
+ template.filter(t => t.type === null && t.group !== null).length > 0
187
+ ) {
188
+ groupGeneral.push({ category: category.id, group: template[0].group });
189
+ }
190
+ }
191
+ }
192
+ //run category groups
193
+ const newWarnings = await applyGroupCleanups(
194
+ month,
195
+ groupSource,
196
+ groupSink,
197
+ groupGeneral,
198
+ );
199
+ warnings.splice(1, 0, ...newWarnings);
200
+
201
+ for (let c = 0; c < categories.length; c++) {
202
+ const category = categories[c];
203
+ const template = category_templates[category.id];
204
+ if (template) {
205
+ if (
206
+ template.filter(t => t.type === 'source' && t.group === null).length > 0
207
+ ) {
208
+ const balance = await getSheetValue(
209
+ sheetName,
210
+ `leftover-${category.id}`,
211
+ );
212
+ const budgeted = await getSheetValue(
213
+ sheetName,
214
+ `budget-${category.id}`,
215
+ );
216
+ if (balance >= 0) {
217
+ // const spent = await getSheetValue(
218
+ // sheetName,
219
+ // `sum-amount-${category.id}`,
220
+ // );
221
+ await setBudget({
222
+ category: category.id,
223
+ month,
224
+ amount: budgeted - balance,
225
+ });
226
+ await setGoal({
227
+ category: category.id,
228
+ month,
229
+ goal: budgeted - balance,
230
+ long_goal: 0,
231
+ });
232
+ num_sources += 1;
233
+ } else {
234
+ warnings.push(category.name + ' does not have available funds.');
235
+ }
236
+ const carryover = await db.first<Pick<db.DbZeroBudget, 'carryover'>>(
237
+ `SELECT carryover FROM zero_budgets WHERE month = ? and category = ?`,
238
+ [db_month, category.id],
239
+ );
240
+ if (carryover !== null) {
241
+ //keep track of source categories with rollover enabled
242
+ if (carryover.carryover === 1) {
243
+ sourceWithRollover.push({ cat: category, temp: template });
244
+ }
245
+ }
246
+ }
247
+ if (
248
+ template.filter(t => t.type === 'sink' && t.group === null).length > 0
249
+ ) {
250
+ sinkCategory.push({ cat: category, temp: template });
251
+ num_sinks += 1;
252
+ total_weight += template.filter(w => w.type === 'sink')[0].weight;
253
+ }
254
+ }
255
+ }
256
+
257
+ //funds all underfunded categories first unless the overspending rollover is checked
258
+ for (let c = 0; c < categories.length; c++) {
259
+ const category = categories[c];
260
+ const budgetAvailable = await getSheetValue(sheetName, `to-budget`);
261
+ const balance = await getSheetValue(sheetName, `leftover-${category.id}`);
262
+ const budgeted = await getSheetValue(sheetName, `budget-${category.id}`);
263
+ const to_budget = budgeted + Math.abs(balance);
264
+ const categoryId = category.id;
265
+ let carryover = await db.first<Pick<db.DbZeroBudget, 'carryover'>>(
266
+ `SELECT carryover FROM zero_budgets WHERE month = ? and category = ?`,
267
+ [db_month, categoryId],
268
+ );
269
+
270
+ if (carryover === null) {
271
+ carryover = { carryover: 0 };
272
+ }
273
+
274
+ if (
275
+ balance < 0 &&
276
+ Math.abs(balance) <= budgetAvailable &&
277
+ !category.is_income &&
278
+ carryover.carryover === 0
279
+ ) {
280
+ await setBudget({
281
+ category: category.id,
282
+ month,
283
+ amount: to_budget,
284
+ });
285
+ } else if (
286
+ balance < 0 &&
287
+ !category.is_income &&
288
+ carryover.carryover === 0 &&
289
+ Math.abs(balance) > budgetAvailable
290
+ ) {
291
+ await setBudget({
292
+ category: category.id,
293
+ month,
294
+ amount: budgeted + budgetAvailable,
295
+ });
296
+ }
297
+ }
298
+
299
+ const budgetAvailable = await getSheetValue(sheetName, `to-budget`);
300
+ if (budgetAvailable < 0) {
301
+ warnings.push('Global: No funds are available to reallocate.');
302
+ }
303
+
304
+ //fill sinking categories
305
+ for (let c = 0; c < sinkCategory.length; c++) {
306
+ const budgeted = await getSheetValue(
307
+ sheetName,
308
+ `budget-${sinkCategory[c].cat.id}`,
309
+ );
310
+ const categoryId = sinkCategory[c].cat.id;
311
+ const weight = sinkCategory[c].temp.filter(w => w.type === 'sink')[0]
312
+ .weight;
313
+ let to_budget =
314
+ budgeted + Math.round((weight / total_weight) * budgetAvailable);
315
+ if (c === sinkCategory.length - 1) {
316
+ const currentBudgetAvailable = await getSheetValue(
317
+ sheetName,
318
+ `to-budget`,
319
+ );
320
+ if (to_budget > currentBudgetAvailable) {
321
+ to_budget = budgeted + currentBudgetAvailable;
322
+ }
323
+ }
324
+ await setBudget({
325
+ category: categoryId,
326
+ month,
327
+ amount: to_budget,
328
+ });
329
+ }
330
+
331
+ if (num_sources === 0) {
332
+ if (errors.length) {
333
+ return {
334
+ type: 'error',
335
+ sticky: true,
336
+ message: 'There were errors interpreting some templates:',
337
+ pre: errors.join('\n\n'),
338
+ };
339
+ } else if (warnings.length) {
340
+ return {
341
+ type: 'warning',
342
+ message: 'Global: Funds not available:',
343
+ pre: warnings.join('\n\n'),
344
+ };
345
+ } else {
346
+ return {
347
+ type: 'message',
348
+ message: 'All categories were up to date.',
349
+ };
350
+ }
351
+ } else {
352
+ const applied = `Successfully returned funds from ${num_sources} ${
353
+ num_sources === 1 ? 'source' : 'sources'
354
+ } and funded ${num_sinks} sinking ${num_sinks === 1 ? 'fund' : 'funds'}.`;
355
+ if (errors.length) {
356
+ return {
357
+ sticky: true,
358
+ message: `${applied} There were errors interpreting some templates:`,
359
+ pre: errors.join('\n\n'),
360
+ };
361
+ } else if (warnings.length) {
362
+ return {
363
+ type: 'warning',
364
+ message: 'Global: Funds not available:',
365
+ pre: warnings.join('\n\n'),
366
+ };
367
+ } else if (budgetAvailable === 0) {
368
+ return {
369
+ type: 'message',
370
+ message: 'All categories were up to date.',
371
+ };
372
+ } else {
373
+ return {
374
+ type: 'message',
375
+ message: applied,
376
+ };
377
+ }
378
+ }
379
+ }
380
+
381
+ const TEMPLATE_PREFIX = '#cleanup ';
382
+ async function getCategoryTemplates() {
383
+ const templates = {};
384
+
385
+ const notes = await db.all<db.DbNote>(
386
+ `SELECT * FROM notes WHERE lower(note) like '%${TEMPLATE_PREFIX}%'`,
387
+ );
388
+
389
+ for (let n = 0; n < notes.length; n++) {
390
+ const lines = notes[n].note.split('\n');
391
+ const template_lines = [];
392
+ for (let l = 0; l < lines.length; l++) {
393
+ const line = lines[l].trim();
394
+ if (!line.toLowerCase().startsWith(TEMPLATE_PREFIX)) continue;
395
+ const expression = line.slice(TEMPLATE_PREFIX.length);
396
+ try {
397
+ const parsed = parse(expression);
398
+ template_lines.push(parsed);
399
+ } catch (e) {
400
+ template_lines.push({ type: 'error', line, error: e });
401
+ }
402
+ }
403
+ if (template_lines.length) {
404
+ templates[notes[n].id] = template_lines;
405
+ }
406
+ }
407
+ return templates;
408
+ }