israeli-banks-actual-budget-importer 1.5.2 → 1.6.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/.yarnrc.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  nodeLinker: node-modules
2
2
 
3
- yarnPath: .yarn/releases/yarn-4.9.2.cjs
3
+ yarnPath: .yarn/releases/yarn-4.12.0.cjs
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # [1.6.0](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/compare/v1.5.3...v1.6.0) (2025-12-02)
2
+
3
+
4
+ ### Features
5
+
6
+ * enhance reconciliation process ([5ebf9a6](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/commit/5ebf9a6453aa8db1e6f23fd81d114fbef5c85e5c))
7
+
8
+ ## [1.5.3](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/compare/v1.5.2...v1.5.3) (2025-11-24)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * update Node.js version to 24 and adjust file permissions ([a4a2658](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/commit/a4a2658537057f4a5a8a4c1888791b94ec987577))
14
+ * Updated packages to latest version ([91c3f6b](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/commit/91c3f6bf57cbbc2a9f17925d13a393ebd9746510))
15
+
1
16
  ## [1.5.2](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/compare/v1.5.1...v1.5.2) (2025-06-09)
2
17
 
3
18
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.2",
2
+ "version": "1.6.0",
3
3
  "name": "israeli-banks-actual-budget-importer",
4
4
  "module": "index.ts",
5
5
  "type": "module",
@@ -12,27 +12,27 @@
12
12
  "@semantic-release/changelog": "^6.0.3",
13
13
  "@semantic-release/commit-analyzer": "^13.0.1",
14
14
  "@semantic-release/git": "^10.0.1",
15
- "@semantic-release/github": "^11.0.3",
16
- "@semantic-release/npm": "^12.0.1",
17
- "@semantic-release/release-notes-generator": "^14.0.3",
18
- "@types/lodash": "^4.17.17",
19
- "@types/papaparse": "^5.3.16",
15
+ "@semantic-release/github": "^12.0.2",
16
+ "@semantic-release/npm": "^13.1.2",
17
+ "@semantic-release/release-notes-generator": "^14.1.0",
18
+ "@types/lodash": "^4.17.21",
19
+ "@types/papaparse": "^5.5.0",
20
20
  "bun-types": "latest",
21
21
  "papaparse": "^5.5.3",
22
- "semantic-release": "^24.2.5",
23
- "typescript": "^5.8.3",
24
- "xo": "^1.1.0"
22
+ "semantic-release": "^25.0.2",
23
+ "typescript": "^5.9.3",
24
+ "xo": "^1.2.3"
25
25
  },
26
- "packageManager": "yarn@4.9.2",
26
+ "packageManager": "yarn@4.12.0",
27
27
  "dependencies": {
28
- "@actual-app/api": "^25.6.1",
29
- "cronstrue": "^2.61.0",
30
- "israeli-bank-scrapers": "^6.1.3",
28
+ "@actual-app/api": "^25.11.0",
29
+ "cronstrue": "^3.9.0",
30
+ "israeli-bank-scrapers": "^6.2.5",
31
31
  "lodash": "^4.17.21",
32
32
  "moment": "^2.30.1",
33
33
  "mute-stdout": "^2.0.0",
34
- "node-cron": "^4.1.0",
35
- "p-queue": "^8.1.0",
36
- "tsx": "^4.19.4"
34
+ "node-cron": "^4.2.1",
35
+ "p-queue": "^9.0.1",
36
+ "tsx": "^4.20.6"
37
37
  }
38
38
  }
package/src/config.d.ts CHANGED
@@ -16,9 +16,7 @@ export type ConfigActualBudget = {
16
16
  password: string;
17
17
  };
18
18
 
19
- export type ConfigBanks = Partial<{
20
- [key in CompanyTypes]: ConfigBank;
21
- }>;
19
+ export type ConfigBanks = Partial<Record<CompanyTypes, ConfigBank>>;
22
20
 
23
21
  export type ConfigBank = ScraperCredentials & {
24
22
  actualAccountId: string;
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  /* eslint-disable unicorn/no-process-exit */
3
3
 
4
4
  /* eslint-disable no-await-in-loop */
5
- /* eslint-disable n/file-extension-in-import */
6
5
 
7
6
  import process from 'node:process';
8
7
  import {type CompanyTypes} from 'israeli-bank-scrapers';
package/src/utils.d.ts CHANGED
File without changes
package/src/utils.ts CHANGED
@@ -1,10 +1,8 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
1
2
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
3
  /* eslint-disable @typescript-eslint/naming-convention */
3
4
  /* eslint-disable @typescript-eslint/no-unsafe-call */
4
5
 
5
- /* eslint-disable import/extensions */
6
- /* eslint-disable n/file-extension-in-import */
7
-
8
6
  import process from 'node:process';
9
7
  import {createScraper, type ScraperCredentials} from 'israeli-bank-scrapers';
10
8
  import _ from 'lodash';
@@ -77,27 +75,68 @@ export async function scrapeAndImportTransactions({companyId, bank}: ScrapeTrans
77
75
 
78
76
  const currentBalance = actual.utils.integerToAmount(await actual.getAccountBalance(bank.actualAccountId));
79
77
  const balanceDiff = accountBalance - currentBalance;
78
+
79
+ // Use a stable imported_id per account so we can find and update/delete the same
80
+ // reconciliation transaction instead of creating a new one every run.
81
+ const reconciliationImportedId = `reconciliation-${bank.actualAccountId}`;
82
+
83
+ // Fetch all transactions for this account and look for an existing reconciliation.
84
+ // Use a wide date range so we always find it if it exists.
85
+ const allAccountTxns: TransactionEntity[] = await actual.getTransactions(
86
+ bank.actualAccountId,
87
+ '2000-01-01',
88
+ moment().add(1, 'year').format('YYYY-MM-DD'),
89
+ );
90
+
91
+ const existingReconciliation = allAccountTxns.find(txn => txn.imported_id === reconciliationImportedId);
92
+
93
+ // If balances are already in sync, remove any existing reconciliation and exit.
80
94
  if (balanceDiff === 0) {
95
+ if (existingReconciliation) {
96
+ stdout.mute();
97
+ await actual.deleteTransaction(existingReconciliation.id);
98
+ stdout.unmute();
99
+ log('RECONCILIATION_REMOVED');
100
+ }
101
+
81
102
  return;
82
103
  }
83
104
 
84
- log('RECONCILIATION', {from: currentBalance, to: accountBalance, diff: balanceDiff});
105
+ log('RECONCILIATION', {
106
+ from: currentBalance,
107
+ to: accountBalance,
108
+ diff: balanceDiff,
109
+ });
85
110
 
86
- stdout.mute();
87
- const reconciliationResult = await actual.importTransactions(bank.actualAccountId, [{
111
+ const reconciliationTxn = {
112
+ account: bank.actualAccountId,
88
113
  date: moment().format('YYYY-MM-DD'),
89
114
  amount: actual.utils.amountToInteger(balanceDiff),
90
- payee: null,
115
+ payee: undefined,
91
116
  imported_payee: 'Reconciliation',
92
117
  notes: `Reconciliation from ${currentBalance.toLocaleString()} to ${accountBalance.toLocaleString()}`,
93
- imported_id: `reconciliation-${moment().format('YYYY-MM-DD HH:mm:ss')}`,
94
- }]);
95
- stdout.unmute();
118
+ imported_id: reconciliationImportedId,
119
+ };
96
120
 
97
- if (_.isEmpty(reconciliationResult)) {
98
- console.error('Reconciliation errors', reconciliationResult.errors);
121
+ stdout.mute();
122
+ if (existingReconciliation) {
123
+ // Update the single reconciliation transaction
124
+ await actual.updateTransaction(existingReconciliation.id, reconciliationTxn);
125
+ stdout.unmute();
126
+ log('RECONCILIATION_UPDATED', {transactionId: existingReconciliation.id});
99
127
  } else {
100
- log('RECONCILIATION_ADDED', {transactions: reconciliationResult.added.length});
128
+ // Create the reconciliation transaction for the first time
129
+ const reconciliationResult = await actual.importTransactions(
130
+ bank.actualAccountId,
131
+ [reconciliationTxn],
132
+ );
133
+ stdout.unmute();
134
+
135
+ if (!reconciliationResult || _.isEmpty(reconciliationResult.added)) {
136
+ console.error('Reconciliation errors', reconciliationResult?.errors);
137
+ } else {
138
+ log('RECONCILIATION_ADDED', {transactions: reconciliationResult.added.length});
139
+ }
101
140
  }
102
141
  } catch (error) {
103
142
  console.error('Error', companyId, error);
@@ -105,4 +144,3 @@ export async function scrapeAndImportTransactions({companyId, bank}: ScrapeTrans
105
144
  log('DONE');
106
145
  }
107
146
  }
108
-