@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.
- package/.swcrc +11 -0
- package/bin/build-browser +40 -0
- package/bin/copy-migrations +9 -0
- package/db.sqlite +0 -0
- package/default-db.sqlite +0 -0
- package/migrations/.force-copy-windows +0 -0
- package/migrations/1548957970627_remove-db-version.sql +5 -0
- package/migrations/1550601598648_payees.sql +23 -0
- package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
- package/migrations/1561751833510_indexes.sql +7 -0
- package/migrations/1567699552727_budget.sql +38 -0
- package/migrations/1582384163573_cleared.sql +6 -0
- package/migrations/1597756566448_rules.sql +10 -0
- package/migrations/1608652596043_parent_field.sql +13 -0
- package/migrations/1608652596044_trans_views.sql +56 -0
- package/migrations/1612625548236_optimize.sql +7 -0
- package/migrations/1614782639336_trans_views2.sql +33 -0
- package/migrations/1615745967948_meta.sql +10 -0
- package/migrations/1616167010796_accounts_order.sql +5 -0
- package/migrations/1618975177358_schedules.sql +28 -0
- package/migrations/1632571489012_remove_cache.js +136 -0
- package/migrations/1679728867040_rules_conditions.sql +5 -0
- package/migrations/1681115033845_add_schedule_name.sql +5 -0
- package/migrations/1682974838138_remove_payee_rules.sql +5 -0
- package/migrations/1685007876842_add_category_hidden.sql +6 -0
- package/migrations/1686139660866_remove_account_type.sql +5 -0
- package/migrations/1688749527273_transaction_filters.sql +10 -0
- package/migrations/1688841238000_add_account_type.sql +5 -0
- package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
- package/migrations/1694438752000_add_goal_targets.sql +7 -0
- package/migrations/1697046240000_add_reconciled.sql +5 -0
- package/migrations/1704572023730_add_account_sync_source.sql +5 -0
- package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
- package/migrations/1707267033000_reports.sql +28 -0
- package/migrations/1712784523000_unhide_input_group.sql +8 -0
- package/migrations/1716359441000_include_current.sql +5 -0
- package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
- package/migrations/1720664867241_add_payee_favorite.sql +5 -0
- package/migrations/1720665000000_goal_context.sql +6 -0
- package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
- package/migrations/1722804019000_create_dashboard_table.js +69 -0
- package/migrations/1723665565000_prefs.js +59 -0
- package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
- package/migrations/1736640000000_custom_report_sorting.sql +7 -0
- package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
- package/migrations/1738491452000_sorting_rename.sql +13 -0
- package/migrations/1739139550000_bank_sync_page.sql +7 -0
- package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
- package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
- package/migrations/1749799110000_add_tags.sql +10 -0
- package/migrations/1749799110001_tags_tombstone.sql +5 -0
- package/migrations/1754611200000_add_category_template_settings.sql +5 -0
- package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
- package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
- package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
- package/migrations/1765518577215_multiple_dashboards.js +30 -0
- package/migrations/1768872504000_add_payee_locations.sql +21 -0
- package/package.json +128 -0
- package/src/mocks/arbitrary-schema.ts +162 -0
- package/src/mocks/budget.ts +901 -0
- package/src/mocks/files/8859-1.qfx +63 -0
- package/src/mocks/files/best.data-ever$.QFX +124 -0
- package/src/mocks/files/big.data.QiF +91 -0
- package/src/mocks/files/budgets/.commit-to-git +0 -0
- package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
- package/src/mocks/files/camt/camt.053.xml +463 -0
- package/src/mocks/files/credit-card.ofx +11 -0
- package/src/mocks/files/data-multi-decimal.ofx +64 -0
- package/src/mocks/files/data-payee-memo.ofx +75 -0
- package/src/mocks/files/data-payee-memo.qif +17 -0
- package/src/mocks/files/data.ofx +124 -0
- package/src/mocks/files/data.qfx +124 -0
- package/src/mocks/files/data.qif +91 -0
- package/src/mocks/files/default-budget-template/db.sqlite +0 -0
- package/src/mocks/files/default-budget-template/metadata.json +6 -0
- package/src/mocks/files/html-vals.qfx +17 -0
- package/src/mocks/index.ts +221 -0
- package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
- package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
- package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
- package/src/mocks/random.ts +16 -0
- package/src/mocks/setup.ts +180 -0
- package/src/mocks/spreadsheet.ts +101 -0
- package/src/mocks/util.ts +82 -0
- package/src/platform/client/connection/README.md +3 -0
- package/src/platform/client/connection/__mocks__/index.ts +67 -0
- package/src/platform/client/connection/index-types.ts +95 -0
- package/src/platform/client/connection/index.browser.ts +213 -0
- package/src/platform/client/connection/index.ts +155 -0
- package/src/platform/client/undo/index.ts +59 -0
- package/src/platform/exceptions/__mocks__/index.ts +7 -0
- package/src/platform/exceptions/index.ts +9 -0
- package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
- package/src/platform/server/asyncStorage/index-types.ts +35 -0
- package/src/platform/server/asyncStorage/index.api.ts +2 -0
- package/src/platform/server/asyncStorage/index.electron.ts +88 -0
- package/src/platform/server/asyncStorage/index.ts +126 -0
- package/src/platform/server/connection/README.md +3 -0
- package/src/platform/server/connection/__mocks__/index.ts +15 -0
- package/src/platform/server/connection/index-types.ts +20 -0
- package/src/platform/server/connection/index.api.ts +13 -0
- package/src/platform/server/connection/index.electron.ts +102 -0
- package/src/platform/server/connection/index.ts +154 -0
- package/src/platform/server/fetch/__mocks__/index.ts +3 -0
- package/src/platform/server/fetch/index.api.ts +1 -0
- package/src/platform/server/fetch/index.electron.ts +18 -0
- package/src/platform/server/fetch/index.ts +20 -0
- package/src/platform/server/fs/index.api.ts +198 -0
- package/src/platform/server/fs/index.electron.ts +208 -0
- package/src/platform/server/fs/index.test.ts +117 -0
- package/src/platform/server/fs/index.ts +416 -0
- package/src/platform/server/fs/path-join.api.ts +1 -0
- package/src/platform/server/fs/path-join.electron.ts +1 -0
- package/src/platform/server/fs/path-join.ts +97 -0
- package/src/platform/server/fs/shared.ts +33 -0
- package/src/platform/server/indexeddb/index.ts +115 -0
- package/src/platform/server/log/index.ts +43 -0
- package/src/platform/server/sqlite/index.api.ts +2 -0
- package/src/platform/server/sqlite/index.electron.ts +134 -0
- package/src/platform/server/sqlite/index.test.ts +108 -0
- package/src/platform/server/sqlite/index.ts +241 -0
- package/src/platform/server/sqlite/normalise.ts +9 -0
- package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
- package/src/platform/server/sqlite/unicodeLike.ts +31 -0
- package/src/server/__mocks__/post.ts +9 -0
- package/src/server/__snapshots__/main.test.ts.snap +199 -0
- package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
- package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
- package/src/server/accounts/app-bank-sync.test.ts +136 -0
- package/src/server/accounts/app.ts +1294 -0
- package/src/server/accounts/link.ts +25 -0
- package/src/server/accounts/payees.ts +36 -0
- package/src/server/accounts/sync.test.ts +679 -0
- package/src/server/accounts/sync.ts +1168 -0
- package/src/server/accounts/title/index.ts +60 -0
- package/src/server/accounts/title/lower-case.ts +93 -0
- package/src/server/accounts/title/specials.ts +21 -0
- package/src/server/admin/app.ts +241 -0
- package/src/server/api-models.ts +244 -0
- package/src/server/api.test.ts +36 -0
- package/src/server/api.ts +1030 -0
- package/src/server/app.ts +91 -0
- package/src/server/aql/compiler.test.ts +966 -0
- package/src/server/aql/compiler.ts +1222 -0
- package/src/server/aql/exec.test.ts +289 -0
- package/src/server/aql/exec.ts +128 -0
- package/src/server/aql/index.ts +41 -0
- package/src/server/aql/schema/executors.test.ts +420 -0
- package/src/server/aql/schema/executors.ts +345 -0
- package/src/server/aql/schema/index.test.ts +67 -0
- package/src/server/aql/schema/index.ts +409 -0
- package/src/server/aql/schema-helpers.test.ts +242 -0
- package/src/server/aql/schema-helpers.ts +208 -0
- package/src/server/aql/views.test.ts +62 -0
- package/src/server/aql/views.ts +57 -0
- package/src/server/auth/app.ts +387 -0
- package/src/server/bench.ts +29 -0
- package/src/server/budget/actions.ts +686 -0
- package/src/server/budget/app.ts +469 -0
- package/src/server/budget/base.test.ts +340 -0
- package/src/server/budget/base.ts +339 -0
- package/src/server/budget/category-template-context.test.ts +1658 -0
- package/src/server/budget/category-template-context.ts +862 -0
- package/src/server/budget/cleanup-template.pegjs +27 -0
- package/src/server/budget/cleanup-template.ts +408 -0
- package/src/server/budget/envelope.ts +403 -0
- package/src/server/budget/goal-template.pegjs +110 -0
- package/src/server/budget/goal-template.ts +309 -0
- package/src/server/budget/report.ts +308 -0
- package/src/server/budget/schedule-template.test.ts +184 -0
- package/src/server/budget/schedule-template.ts +351 -0
- package/src/server/budget/statements.ts +60 -0
- package/src/server/budget/template-notes.test.ts +393 -0
- package/src/server/budget/template-notes.ts +323 -0
- package/src/server/budget/util.ts +25 -0
- package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
- package/src/server/budgetfiles/app.ts +672 -0
- package/src/server/budgetfiles/backups.test.ts +79 -0
- package/src/server/budgetfiles/backups.ts +251 -0
- package/src/server/cloud-storage.ts +467 -0
- package/src/server/dashboard/app.ts +373 -0
- package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
- package/src/server/db/index.test.ts +300 -0
- package/src/server/db/index.ts +855 -0
- package/src/server/db/mappings.ts +59 -0
- package/src/server/db/sort.ts +58 -0
- package/src/server/db/types/index.ts +342 -0
- package/src/server/db/util.ts +36 -0
- package/src/server/encryption/app.ts +133 -0
- package/src/server/encryption/encryption-internals.api.ts +2 -0
- package/src/server/encryption/encryption-internals.electron.ts +89 -0
- package/src/server/encryption/encryption-internals.ts +109 -0
- package/src/server/encryption/encryption.test.ts +19 -0
- package/src/server/encryption/index.test.ts +19 -0
- package/src/server/encryption/index.ts +89 -0
- package/src/server/errors.ts +110 -0
- package/src/server/filters/app.ts +191 -0
- package/src/server/importers/actual.ts +49 -0
- package/src/server/importers/index.ts +58 -0
- package/src/server/importers/ynab4-types.ts +163 -0
- package/src/server/importers/ynab4.ts +470 -0
- package/src/server/importers/ynab5-types.ts +290 -0
- package/src/server/importers/ynab5.ts +1193 -0
- package/src/server/main-app.ts +25 -0
- package/src/server/main.test.ts +392 -0
- package/src/server/main.ts +336 -0
- package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
- package/src/server/migrate/cli.ts +100 -0
- package/src/server/migrate/migrations.test.ts +81 -0
- package/src/server/migrate/migrations.ts +192 -0
- package/src/server/models.ts +184 -0
- package/src/server/mutators.ts +139 -0
- package/src/server/notes/app.ts +18 -0
- package/src/server/payees/app.ts +351 -0
- package/src/server/polyfills.ts +26 -0
- package/src/server/post.ts +219 -0
- package/src/server/preferences/app.ts +249 -0
- package/src/server/prefs.ts +91 -0
- package/src/server/reports/app.ts +187 -0
- package/src/server/rules/action.ts +344 -0
- package/src/server/rules/app.ts +193 -0
- package/src/server/rules/condition.ts +436 -0
- package/src/server/rules/customFunctions.ts +61 -0
- package/src/server/rules/formula-action.test.ts +175 -0
- package/src/server/rules/handlebars-helpers.ts +131 -0
- package/src/server/rules/index.test.ts +1095 -0
- package/src/server/rules/index.ts +22 -0
- package/src/server/rules/rule-indexer.ts +89 -0
- package/src/server/rules/rule-utils.ts +274 -0
- package/src/server/rules/rule.ts +193 -0
- package/src/server/schedules/app.test.ts +502 -0
- package/src/server/schedules/app.ts +644 -0
- package/src/server/schedules/find-schedules.ts +391 -0
- package/src/server/server-config.ts +59 -0
- package/src/server/sheet.test.ts +101 -0
- package/src/server/sheet.ts +280 -0
- package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
- package/src/server/spreadsheet/app.ts +54 -0
- package/src/server/spreadsheet/globals.ts +13 -0
- package/src/server/spreadsheet/graph-data-structure.ts +165 -0
- package/src/server/spreadsheet/scratch +60 -0
- package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
- package/src/server/spreadsheet/spreadsheet.ts +523 -0
- package/src/server/spreadsheet/util.ts +15 -0
- package/src/server/sql/init.sql +88 -0
- package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
- package/src/server/sync/app.ts +29 -0
- package/src/server/sync/encoder.ts +129 -0
- package/src/server/sync/index.ts +820 -0
- package/src/server/sync/make-test-message.ts +19 -0
- package/src/server/sync/migrate.test.ts +169 -0
- package/src/server/sync/migrate.ts +48 -0
- package/src/server/sync/repair.ts +39 -0
- package/src/server/sync/reset.ts +91 -0
- package/src/server/sync/sync.property.test.ts +385 -0
- package/src/server/sync/sync.test.ts +349 -0
- package/src/server/sync/utils.ts +3 -0
- package/src/server/tags/app.ts +101 -0
- package/src/server/tests/mockData.json +9352 -0
- package/src/server/tests/mockSyncServer.ts +119 -0
- package/src/server/tools/app.ts +152 -0
- package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
- package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
- package/src/server/transactions/app.ts +136 -0
- package/src/server/transactions/export/export-to-csv.ts +132 -0
- package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
- package/src/server/transactions/import/ofx2json.test.ts +33 -0
- package/src/server/transactions/import/ofx2json.ts +157 -0
- package/src/server/transactions/import/parse-file.test.ts +224 -0
- package/src/server/transactions/import/parse-file.ts +286 -0
- package/src/server/transactions/import/qif2json.ts +110 -0
- package/src/server/transactions/import/xmlcamt2json.ts +168 -0
- package/src/server/transactions/index.ts +196 -0
- package/src/server/transactions/merge.test.ts +370 -0
- package/src/server/transactions/merge.ts +139 -0
- package/src/server/transactions/transaction-rules.test.ts +994 -0
- package/src/server/transactions/transaction-rules.ts +1038 -0
- package/src/server/transactions/transfer.test.ts +221 -0
- package/src/server/transactions/transfer.ts +173 -0
- package/src/server/undo.ts +271 -0
- package/src/server/update.ts +37 -0
- package/src/server/util/budget-name.ts +61 -0
- package/src/server/util/custom-sync-mapping.ts +48 -0
- package/src/server/util/rschedule.ts +9 -0
- package/src/shared/__mocks__/platform.ts +7 -0
- package/src/shared/__snapshots__/months.test.ts.snap +21 -0
- package/src/shared/arithmetic.test.ts +112 -0
- package/src/shared/arithmetic.ts +170 -0
- package/src/shared/async.test.ts +135 -0
- package/src/shared/async.ts +76 -0
- package/src/shared/constants.ts +5 -0
- package/src/shared/currencies.ts +70 -0
- package/src/shared/dashboard.ts +260 -0
- package/src/shared/environment.ts +18 -0
- package/src/shared/errors.ts +195 -0
- package/src/shared/locale.ts +27 -0
- package/src/shared/location-utils.test.ts +69 -0
- package/src/shared/location-utils.ts +49 -0
- package/src/shared/months.test.ts +5 -0
- package/src/shared/months.ts +485 -0
- package/src/shared/normalisation.ts +6 -0
- package/src/shared/platform.electron.ts +21 -0
- package/src/shared/platform.ts +20 -0
- package/src/shared/query.ts +176 -0
- package/src/shared/rules.test.ts +56 -0
- package/src/shared/rules.ts +371 -0
- package/src/shared/schedules.test.ts +570 -0
- package/src/shared/schedules.ts +560 -0
- package/src/shared/test-helpers.ts +156 -0
- package/src/shared/transactions.test.ts +275 -0
- package/src/shared/transactions.ts +433 -0
- package/src/shared/transfer.test.ts +75 -0
- package/src/shared/transfer.ts +16 -0
- package/src/shared/user.ts +4 -0
- package/src/shared/util.test.ts +240 -0
- package/src/shared/util.ts +633 -0
- package/src/types/api-handlers.ts +287 -0
- package/src/types/budget.ts +8 -0
- package/src/types/file.ts +47 -0
- package/src/types/handlers.ts +46 -0
- package/src/types/models/account.ts +24 -0
- package/src/types/models/bank-sync.ts +23 -0
- package/src/types/models/bank.ts +6 -0
- package/src/types/models/category-group.ts +11 -0
- package/src/types/models/category.ts +13 -0
- package/src/types/models/dashboard.ts +199 -0
- package/src/types/models/gocardless.ts +84 -0
- package/src/types/models/import-transaction.ts +56 -0
- package/src/types/models/index.ts +23 -0
- package/src/types/models/nearby-payee.ts +7 -0
- package/src/types/models/note.ts +4 -0
- package/src/types/models/openid.ts +8 -0
- package/src/types/models/payee-location.ts +8 -0
- package/src/types/models/payee.ts +10 -0
- package/src/types/models/pluggyai.ts +19 -0
- package/src/types/models/reports.ts +144 -0
- package/src/types/models/rule.ts +174 -0
- package/src/types/models/schedule.ts +49 -0
- package/src/types/models/simplefin.ts +28 -0
- package/src/types/models/tags.ts +6 -0
- package/src/types/models/templates.ts +135 -0
- package/src/types/models/transaction-filter.ts +9 -0
- package/src/types/models/transaction.ts +39 -0
- package/src/types/models/user-access.ts +10 -0
- package/src/types/models/user.ts +25 -0
- package/src/types/prefs.ts +167 -0
- package/src/types/server-events.ts +86 -0
- package/src/types/server-handlers.ts +27 -0
- package/src/types/util.ts +26 -0
- package/tsconfig.json +34 -0
- package/typings/pegjs.ts +1 -0
- package/typings/process-worker.ts +12 -0
- package/typings/vite-plugin-peggy-loader.ts +1 -0
- package/typings/window.ts +62 -0
- package/vite.config.ts +109 -0
- package/vite.desktop.config.ts +59 -0
- package/vitest.config.ts +43 -0
- package/vitest.web.config.ts +38 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import * as d from 'date-fns';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
|
|
5
|
+
import { captureBreadcrumb } from '../../platform/exceptions';
|
|
6
|
+
import * as connection from '../../platform/server/connection';
|
|
7
|
+
import { logger } from '../../platform/server/log';
|
|
8
|
+
import { currentDay, dayFromDate, parseDate } from '../../shared/months';
|
|
9
|
+
import { q } from '../../shared/query';
|
|
10
|
+
import {
|
|
11
|
+
extractScheduleConds,
|
|
12
|
+
getDateWithSkippedWeekend,
|
|
13
|
+
getHasTransactionsQuery,
|
|
14
|
+
getNextDate,
|
|
15
|
+
getScheduledAmount,
|
|
16
|
+
getStatus,
|
|
17
|
+
recurConfigToRSchedule,
|
|
18
|
+
} from '../../shared/schedules';
|
|
19
|
+
import type { RuleConditionEntity, ScheduleEntity } from '../../types/models';
|
|
20
|
+
import { addTransactions } from '../accounts/sync';
|
|
21
|
+
import { createApp } from '../app';
|
|
22
|
+
import { aqlQuery } from '../aql';
|
|
23
|
+
import * as db from '../db';
|
|
24
|
+
import { toDateRepr } from '../models';
|
|
25
|
+
import { mutator, runMutator } from '../mutators';
|
|
26
|
+
import * as prefs from '../prefs';
|
|
27
|
+
import { Rule } from '../rules';
|
|
28
|
+
import { addSyncListener, batchMessages } from '../sync';
|
|
29
|
+
import {
|
|
30
|
+
getRules,
|
|
31
|
+
insertRule,
|
|
32
|
+
ruleModel,
|
|
33
|
+
updateRule,
|
|
34
|
+
} from '../transactions/transaction-rules';
|
|
35
|
+
import { undoable } from '../undo';
|
|
36
|
+
import { RSchedule } from '../util/rschedule';
|
|
37
|
+
|
|
38
|
+
import { findSchedules } from './find-schedules';
|
|
39
|
+
|
|
40
|
+
// Utilities
|
|
41
|
+
|
|
42
|
+
function zip(arr1, arr2) {
|
|
43
|
+
const result = [];
|
|
44
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
45
|
+
result.push([arr1[i], arr2[i]]);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function areConditionValuesEqual(left, right) {
|
|
51
|
+
if (left === right) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (left == null || right == null) {
|
|
56
|
+
return left === right;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
60
|
+
return (
|
|
61
|
+
Array.isArray(left) &&
|
|
62
|
+
Array.isArray(right) &&
|
|
63
|
+
left.length === right.length &&
|
|
64
|
+
left.every((value, index) => areConditionValuesEqual(value, right[index]))
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof left === 'object' && typeof right === 'object') {
|
|
69
|
+
const leftKeys = Object.keys(left).sort();
|
|
70
|
+
const rightKeys = Object.keys(right).sort();
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
leftKeys.length === rightKeys.length &&
|
|
74
|
+
leftKeys.every((key, index) => {
|
|
75
|
+
const rightKey = rightKeys[index];
|
|
76
|
+
return (
|
|
77
|
+
key === rightKey &&
|
|
78
|
+
areConditionValuesEqual(left[key], right[rightKey])
|
|
79
|
+
);
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function areScheduleConditionsEqual(
|
|
88
|
+
left?: RuleConditionEntity,
|
|
89
|
+
right?: RuleConditionEntity,
|
|
90
|
+
) {
|
|
91
|
+
if (left == null || right == null) {
|
|
92
|
+
return left === right;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { type: _leftType, ...leftCondition } = left;
|
|
96
|
+
const { type: _rightType, ...rightCondition } = right;
|
|
97
|
+
|
|
98
|
+
return areConditionValuesEqual(leftCondition, rightCondition);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function updateConditions(conditions, newConditions) {
|
|
102
|
+
const scheduleConds = extractScheduleConds(conditions);
|
|
103
|
+
const newScheduleConds = extractScheduleConds(newConditions);
|
|
104
|
+
|
|
105
|
+
const replacements = zip(
|
|
106
|
+
Object.values(scheduleConds),
|
|
107
|
+
Object.values(newScheduleConds),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const updated = conditions.map(cond => {
|
|
111
|
+
const r = replacements.find(r => cond === r[0]);
|
|
112
|
+
return r && r[1] ? r[1] : cond;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const added = replacements
|
|
116
|
+
.filter(x => x[0] == null && x[1] != null)
|
|
117
|
+
.map(x => x[1]);
|
|
118
|
+
|
|
119
|
+
return updated.concat(added);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function getRuleForSchedule(id: string | null): Promise<Rule> {
|
|
123
|
+
if (id == null) {
|
|
124
|
+
throw new Error('Schedule not attached to a rule');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { data: ruleId } = await aqlQuery(
|
|
128
|
+
q('schedules').filter({ id }).calculate('rule'),
|
|
129
|
+
);
|
|
130
|
+
return getRules().find(rule => rule.id === ruleId);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function fixRuleForSchedule(id) {
|
|
134
|
+
const { data: ruleId } = await aqlQuery(
|
|
135
|
+
q('schedules').filter({ id }).calculate('rule'),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (ruleId) {
|
|
139
|
+
// Take the bad rule out of the system so it never causes problems
|
|
140
|
+
// in the future
|
|
141
|
+
await db.delete_('rules', ruleId);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const newId = await insertRule({
|
|
145
|
+
stage: null,
|
|
146
|
+
conditionsOp: 'and',
|
|
147
|
+
conditions: [
|
|
148
|
+
{ op: 'isapprox', field: 'date', value: currentDay() },
|
|
149
|
+
{ op: 'isapprox', field: 'amount', value: 0 },
|
|
150
|
+
],
|
|
151
|
+
actions: [{ op: 'link-schedule', value: id }],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await db.updateWithSchema('schedules', { id, rule: newId });
|
|
155
|
+
|
|
156
|
+
return getRules().find(rule => rule.id === newId);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function setNextDate({
|
|
160
|
+
id,
|
|
161
|
+
start,
|
|
162
|
+
conditions,
|
|
163
|
+
reset,
|
|
164
|
+
skipRequested,
|
|
165
|
+
}: {
|
|
166
|
+
id: string;
|
|
167
|
+
start?;
|
|
168
|
+
conditions?;
|
|
169
|
+
reset?: boolean;
|
|
170
|
+
skipRequested?: boolean;
|
|
171
|
+
}) {
|
|
172
|
+
if (conditions == null) {
|
|
173
|
+
const rule = await getRuleForSchedule(id);
|
|
174
|
+
if (rule == null) {
|
|
175
|
+
throw new Error('No rule found for schedule');
|
|
176
|
+
}
|
|
177
|
+
conditions = rule.serialize().conditions;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const { date: dateCond } = extractScheduleConds(conditions);
|
|
181
|
+
|
|
182
|
+
let { data: nextDate } = await aqlQuery(
|
|
183
|
+
q('schedules').filter({ id }).calculate('next_date'),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
if (skipRequested === true) {
|
|
187
|
+
const skipWeekend: boolean = dateCond.value?.skipWeekend;
|
|
188
|
+
const weekendSolveMode: string = dateCond.value?.weekendSolveMode;
|
|
189
|
+
|
|
190
|
+
if (weekendSolveMode === 'before' && skipWeekend === true) {
|
|
191
|
+
const parsedNextDate = parseDate(nextDate);
|
|
192
|
+
if (d.isFriday(parsedNextDate) || d.isWeekend(parsedNextDate)) {
|
|
193
|
+
// nextDate is on weekend or friday, moving to monday
|
|
194
|
+
// so getNextDate and getDateWithSkippedWeekend
|
|
195
|
+
// don't push the date back to Friday, thus causing
|
|
196
|
+
// `(newNextDate !== nextDate) ` to be false and not updating the next date
|
|
197
|
+
nextDate = dayFromDate(d.nextMonday(parsedNextDate));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Only do this if a date condition exists
|
|
203
|
+
if (dateCond) {
|
|
204
|
+
const newNextDate = getNextDate(
|
|
205
|
+
dateCond,
|
|
206
|
+
start ? start(nextDate) : new Date(),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (newNextDate !== nextDate) {
|
|
210
|
+
// Our `update` functon requires the id of the item and we don't
|
|
211
|
+
// have it, so we need to query it
|
|
212
|
+
const nd = await db.first<
|
|
213
|
+
Pick<db.DbScheduleNextDate, 'id' | 'base_next_date_ts'>
|
|
214
|
+
>(
|
|
215
|
+
'SELECT id, base_next_date_ts FROM schedules_next_date WHERE schedule_id = ?',
|
|
216
|
+
[id],
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
await db.update(
|
|
220
|
+
'schedules_next_date',
|
|
221
|
+
reset
|
|
222
|
+
? {
|
|
223
|
+
id: nd.id,
|
|
224
|
+
base_next_date: toDateRepr(newNextDate),
|
|
225
|
+
base_next_date_ts: Date.now(),
|
|
226
|
+
}
|
|
227
|
+
: {
|
|
228
|
+
id: nd.id,
|
|
229
|
+
local_next_date: toDateRepr(newNextDate),
|
|
230
|
+
local_next_date_ts: nd.base_next_date_ts,
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Methods
|
|
238
|
+
|
|
239
|
+
async function checkIfScheduleExists(name, scheduleId) {
|
|
240
|
+
const idForName = await db.first<Pick<db.DbSchedule, 'id'>>(
|
|
241
|
+
'SELECT id from schedules WHERE tombstone = 0 AND name = ?',
|
|
242
|
+
[name],
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (idForName == null) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (scheduleId) {
|
|
249
|
+
return idForName['id'] !== scheduleId;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function createSchedule({
|
|
255
|
+
schedule = null,
|
|
256
|
+
conditions = [],
|
|
257
|
+
} = {}): Promise<ScheduleEntity['id']> {
|
|
258
|
+
const scheduleId = schedule?.id || uuidv4();
|
|
259
|
+
|
|
260
|
+
const { date: dateCond } = extractScheduleConds(conditions);
|
|
261
|
+
if (dateCond == null) {
|
|
262
|
+
throw new Error('A date condition is required to create a schedule');
|
|
263
|
+
}
|
|
264
|
+
if (dateCond.value == null) {
|
|
265
|
+
throw new Error('Date is required');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const nextDate = getNextDate(dateCond);
|
|
269
|
+
const nextDateRepr = nextDate ? toDateRepr(nextDate) : null;
|
|
270
|
+
if (schedule) {
|
|
271
|
+
if (schedule.name) {
|
|
272
|
+
if (await checkIfScheduleExists(schedule.name, scheduleId)) {
|
|
273
|
+
throw new Error('Cannot create schedules with the same name');
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
schedule.name = null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Create the rule here based on the info
|
|
281
|
+
const ruleId = await insertRule({
|
|
282
|
+
stage: null,
|
|
283
|
+
conditionsOp: 'and',
|
|
284
|
+
conditions,
|
|
285
|
+
actions: [{ op: 'link-schedule', value: scheduleId }],
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
await db.insertWithUUID('schedules_next_date', {
|
|
290
|
+
schedule_id: scheduleId,
|
|
291
|
+
local_next_date: nextDateRepr,
|
|
292
|
+
local_next_date_ts: now,
|
|
293
|
+
base_next_date: nextDateRepr,
|
|
294
|
+
base_next_date_ts: now,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
await db.insertWithSchema('schedules', {
|
|
298
|
+
...schedule,
|
|
299
|
+
id: scheduleId,
|
|
300
|
+
rule: ruleId,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
return scheduleId;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// TODO: don't allow deleting rules that link schedules
|
|
307
|
+
|
|
308
|
+
export async function updateSchedule({
|
|
309
|
+
schedule,
|
|
310
|
+
conditions,
|
|
311
|
+
resetNextDate,
|
|
312
|
+
}: {
|
|
313
|
+
schedule: Partial<ScheduleEntity> & Pick<ScheduleEntity, 'id'>;
|
|
314
|
+
conditions?: RuleConditionEntity[];
|
|
315
|
+
resetNextDate?: boolean;
|
|
316
|
+
}) {
|
|
317
|
+
if (schedule.rule) {
|
|
318
|
+
throw new Error('You cannot change the rule of a schedule');
|
|
319
|
+
}
|
|
320
|
+
let rule;
|
|
321
|
+
|
|
322
|
+
// This must be outside the `batchMessages` call because we change
|
|
323
|
+
// and then read data
|
|
324
|
+
if (conditions) {
|
|
325
|
+
const { date: dateCond } = extractScheduleConds(conditions);
|
|
326
|
+
if (dateCond && dateCond.value == null) {
|
|
327
|
+
throw new Error('Date is required');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// We need to get the full rule to merge in the updated
|
|
331
|
+
// conditions
|
|
332
|
+
rule = await getRuleForSchedule(schedule.id);
|
|
333
|
+
|
|
334
|
+
if (rule == null) {
|
|
335
|
+
// In the edge case that a rule gets corrupted (either by a bug in
|
|
336
|
+
// the system or user messing with their data), don't crash. We
|
|
337
|
+
// generate a new rule because schedules have to have a rule
|
|
338
|
+
// attached to them.
|
|
339
|
+
rule = await fixRuleForSchedule(schedule.id);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
await batchMessages(async () => {
|
|
344
|
+
if (conditions) {
|
|
345
|
+
const oldConditions = rule.serialize().conditions;
|
|
346
|
+
const newConditions = updateConditions(oldConditions, conditions);
|
|
347
|
+
|
|
348
|
+
await updateRule({ id: rule.id, conditions: newConditions });
|
|
349
|
+
|
|
350
|
+
// Annoyingly, sometimes it has `type` and sometimes it doesn't
|
|
351
|
+
const stripType = ({ type: _type, ...fields }) => fields;
|
|
352
|
+
|
|
353
|
+
// Update `next_date` if the user forced it, or if the account
|
|
354
|
+
// or date changed. We check account because we don't update
|
|
355
|
+
// schedules automatically for closed account, and the user
|
|
356
|
+
// might switch accounts from a closed one
|
|
357
|
+
if (
|
|
358
|
+
resetNextDate ||
|
|
359
|
+
!areScheduleConditionsEqual(
|
|
360
|
+
oldConditions.find(c => c.field === 'account'),
|
|
361
|
+
newConditions.find(c => c.field === 'account'),
|
|
362
|
+
) ||
|
|
363
|
+
!areConditionValuesEqual(
|
|
364
|
+
stripType(oldConditions.find(c => c.field === 'date') || {}),
|
|
365
|
+
stripType(newConditions.find(c => c.field === 'date') || {}),
|
|
366
|
+
)
|
|
367
|
+
) {
|
|
368
|
+
await setNextDate({
|
|
369
|
+
id: schedule.id,
|
|
370
|
+
conditions: newConditions,
|
|
371
|
+
reset: true,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
} else if (resetNextDate) {
|
|
375
|
+
await setNextDate({ id: schedule.id, reset: true });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
await db.updateWithSchema('schedules', schedule);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
return schedule.id;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export async function deleteSchedule({ id }) {
|
|
385
|
+
const { data: ruleId } = await aqlQuery(
|
|
386
|
+
q('schedules').filter({ id }).calculate('rule'),
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
await batchMessages(async () => {
|
|
390
|
+
await db.delete_('rules', ruleId);
|
|
391
|
+
await db.delete_('schedules', id);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export async function skipNextDate({ id }) {
|
|
396
|
+
return setNextDate({
|
|
397
|
+
id,
|
|
398
|
+
start: nextDate => {
|
|
399
|
+
return d.addDays(parseDate(nextDate), 1);
|
|
400
|
+
},
|
|
401
|
+
skipRequested: true,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function discoverSchedules() {
|
|
406
|
+
return findSchedules();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function getUpcomingDates({ config, count }) {
|
|
410
|
+
const rules = recurConfigToRSchedule(config);
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const schedule = new RSchedule({ rrules: rules });
|
|
414
|
+
|
|
415
|
+
return schedule
|
|
416
|
+
.occurrences({ start: d.startOfDay(new Date()), take: count })
|
|
417
|
+
.toArray()
|
|
418
|
+
.map(date =>
|
|
419
|
+
config.skipWeekend
|
|
420
|
+
? getDateWithSkippedWeekend(date.date, config.weekendSolveMode)
|
|
421
|
+
: date.date,
|
|
422
|
+
)
|
|
423
|
+
.map(date => dayFromDate(date));
|
|
424
|
+
} catch (err) {
|
|
425
|
+
captureBreadcrumb(config);
|
|
426
|
+
throw err;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Services
|
|
431
|
+
|
|
432
|
+
function onRuleUpdate(rule) {
|
|
433
|
+
const { actions, conditions } =
|
|
434
|
+
rule instanceof Rule ? rule.serialize() : ruleModel.toJS(rule);
|
|
435
|
+
|
|
436
|
+
if (actions && actions.find(a => a.op === 'link-schedule')) {
|
|
437
|
+
const scheduleId = actions.find(a => a.op === 'link-schedule').value;
|
|
438
|
+
|
|
439
|
+
if (scheduleId) {
|
|
440
|
+
const conds = extractScheduleConds(conditions);
|
|
441
|
+
|
|
442
|
+
const payeeIdx = conditions.findIndex(c => c === conds.payee);
|
|
443
|
+
const accountIdx = conditions.findIndex(c => c === conds.account);
|
|
444
|
+
const amountIdx = conditions.findIndex(c => c === conds.amount);
|
|
445
|
+
const dateIdx = conditions.findIndex(c => c === conds.date);
|
|
446
|
+
|
|
447
|
+
db.runQuery(
|
|
448
|
+
'INSERT OR REPLACE INTO schedules_json_paths (schedule_id, payee, account, amount, date) VALUES (?, ?, ?, ?, ?)',
|
|
449
|
+
[
|
|
450
|
+
scheduleId,
|
|
451
|
+
payeeIdx === -1 ? null : `$[${payeeIdx}]`,
|
|
452
|
+
accountIdx === -1 ? null : `$[${accountIdx}]`,
|
|
453
|
+
amountIdx === -1 ? null : `$[${amountIdx}]`,
|
|
454
|
+
dateIdx === -1 ? null : `$[${dateIdx}]`,
|
|
455
|
+
],
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function trackJSONPaths() {
|
|
462
|
+
// Populate the table
|
|
463
|
+
db.transaction(() => {
|
|
464
|
+
getRules().forEach(rule => {
|
|
465
|
+
onRuleUpdate(rule);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
return addSyncListener(onApplySync);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function onApplySync(oldValues, newValues) {
|
|
473
|
+
newValues.forEach((items, table) => {
|
|
474
|
+
if (table === 'rules') {
|
|
475
|
+
items.forEach(newValue => {
|
|
476
|
+
onRuleUpdate(newValue);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// This is the service that move schedules forward automatically and
|
|
483
|
+
// posts transactions
|
|
484
|
+
|
|
485
|
+
async function postTransactionForSchedule({
|
|
486
|
+
id,
|
|
487
|
+
today,
|
|
488
|
+
}: {
|
|
489
|
+
id: string;
|
|
490
|
+
today?: boolean;
|
|
491
|
+
}) {
|
|
492
|
+
const { data } = await aqlQuery(q('schedules').filter({ id }).select('*'));
|
|
493
|
+
const schedule = data[0];
|
|
494
|
+
if (schedule == null || schedule._account == null) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const transaction = {
|
|
499
|
+
payee: schedule._payee,
|
|
500
|
+
account: schedule._account,
|
|
501
|
+
amount: getScheduledAmount(schedule._amount),
|
|
502
|
+
date: today ? currentDay() : schedule.next_date,
|
|
503
|
+
schedule: schedule.id,
|
|
504
|
+
cleared: false,
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
if (transaction.account) {
|
|
508
|
+
await addTransactions(transaction.account, [transaction]);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// TODO: make this sequential
|
|
513
|
+
|
|
514
|
+
async function advanceSchedulesService(syncSuccess) {
|
|
515
|
+
// Move all paid schedules
|
|
516
|
+
const { data: schedules } = await aqlQuery(
|
|
517
|
+
q('schedules')
|
|
518
|
+
.filter({ completed: false, '_account.closed': false })
|
|
519
|
+
.select('*'),
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
const { data: hasTransData } = await aqlQuery(
|
|
523
|
+
getHasTransactionsQuery(schedules),
|
|
524
|
+
);
|
|
525
|
+
const hasTrans = new Set(
|
|
526
|
+
hasTransData.filter(Boolean).map(row => row.schedule),
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
const failedToPost = [];
|
|
530
|
+
let didPost = false;
|
|
531
|
+
|
|
532
|
+
const { data: upcomingLength } = await aqlQuery(
|
|
533
|
+
q('preferences')
|
|
534
|
+
.filter({ id: 'upcomingScheduledTransactionLength' })
|
|
535
|
+
.select('value'),
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
for (const schedule of schedules) {
|
|
539
|
+
const status = getStatus(
|
|
540
|
+
schedule.next_date,
|
|
541
|
+
schedule.completed,
|
|
542
|
+
hasTrans.has(schedule.id),
|
|
543
|
+
upcomingLength[0]?.value ?? '7',
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
if (status === 'paid') {
|
|
547
|
+
if (schedule._date) {
|
|
548
|
+
// Move forward recurring schedules
|
|
549
|
+
if (schedule._date.frequency) {
|
|
550
|
+
try {
|
|
551
|
+
await setNextDate({ id: schedule.id });
|
|
552
|
+
} catch {
|
|
553
|
+
// This might error if the rule is corrupted and it can't
|
|
554
|
+
// find the rule
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
if (schedule._date < currentDay()) {
|
|
558
|
+
// Complete any single schedules
|
|
559
|
+
await updateSchedule({
|
|
560
|
+
schedule: { id: schedule.id, completed: true },
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
} else if (
|
|
566
|
+
(status === 'due' || status === 'missed') &&
|
|
567
|
+
schedule.posts_transaction &&
|
|
568
|
+
schedule._account
|
|
569
|
+
) {
|
|
570
|
+
// Automatically create a transaction for due schedules
|
|
571
|
+
if (syncSuccess) {
|
|
572
|
+
await postTransactionForSchedule({ id: schedule.id });
|
|
573
|
+
|
|
574
|
+
didPost = true;
|
|
575
|
+
} else {
|
|
576
|
+
failedToPost.push(schedule._payee);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (failedToPost.length > 0) {
|
|
582
|
+
connection.send('schedules-offline');
|
|
583
|
+
} else if (didPost) {
|
|
584
|
+
// This forces a full refresh of transactions because it
|
|
585
|
+
// simulates them coming in from a full sync. This not a
|
|
586
|
+
// great API right now, but I think generally the approach
|
|
587
|
+
// is sane to treat them as external sync events.
|
|
588
|
+
connection.send('sync-event', {
|
|
589
|
+
type: 'success',
|
|
590
|
+
tables: ['transactions'],
|
|
591
|
+
syncDisabled: false,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export type SchedulesHandlers = {
|
|
597
|
+
'schedule/create': typeof createSchedule;
|
|
598
|
+
'schedule/update': typeof updateSchedule;
|
|
599
|
+
'schedule/delete': typeof deleteSchedule;
|
|
600
|
+
'schedule/skip-next-date': typeof skipNextDate;
|
|
601
|
+
'schedule/post-transaction': typeof postTransactionForSchedule;
|
|
602
|
+
'schedule/force-run-service': typeof advanceSchedulesService;
|
|
603
|
+
'schedule/discover': typeof discoverSchedules;
|
|
604
|
+
'schedule/get-upcoming-dates': typeof getUpcomingDates;
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// Expose functions to the client
|
|
608
|
+
export const app = createApp<SchedulesHandlers>();
|
|
609
|
+
|
|
610
|
+
app.method('schedule/create', mutator(undoable(createSchedule)));
|
|
611
|
+
app.method('schedule/update', mutator(undoable(updateSchedule)));
|
|
612
|
+
app.method('schedule/delete', mutator(undoable(deleteSchedule)));
|
|
613
|
+
app.method('schedule/skip-next-date', mutator(undoable(skipNextDate)));
|
|
614
|
+
app.method(
|
|
615
|
+
'schedule/post-transaction',
|
|
616
|
+
mutator(undoable(postTransactionForSchedule)),
|
|
617
|
+
);
|
|
618
|
+
app.method(
|
|
619
|
+
'schedule/force-run-service',
|
|
620
|
+
mutator(() => advanceSchedulesService(true)),
|
|
621
|
+
);
|
|
622
|
+
app.method('schedule/discover', discoverSchedules);
|
|
623
|
+
app.method('schedule/get-upcoming-dates', getUpcomingDates);
|
|
624
|
+
|
|
625
|
+
app.service(trackJSONPaths);
|
|
626
|
+
|
|
627
|
+
app.events.on('sync', ({ type }) => {
|
|
628
|
+
const completeEvent =
|
|
629
|
+
type === 'success' || type === 'error' || type === 'unauthorized';
|
|
630
|
+
|
|
631
|
+
if (completeEvent && prefs.getPrefs()) {
|
|
632
|
+
if (!db.getDatabase()) {
|
|
633
|
+
logger.info('database is not available, skipping schedule service');
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const { lastScheduleRun } = prefs.getPrefs();
|
|
638
|
+
if (lastScheduleRun !== currentDay()) {
|
|
639
|
+
void runMutator(() => advanceSchedulesService(type === 'success'));
|
|
640
|
+
|
|
641
|
+
void prefs.savePrefs({ lastScheduleRun: currentDay() });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
});
|