Skip to content

Commit 9f56fe0

Browse files
committed
feat(config): add targets/accounts mapping and update reconciliation docs
1 parent 6060752 commit 9f56fe0

7 files changed

Lines changed: 578 additions & 250 deletions

File tree

README.md

Lines changed: 177 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ This project provides an importer from Israeli banks (via [israeli-bank-scrapers
1717

1818
4. **Reconciliation:** Optional reconciliation to adjust account balances automatically.
1919

20-
5. **Concurrent Processing:** Uses a queue (via [p-queue](https://www.npmjs.com/package/p-queue)) to manage scraping tasks concurrently.
20+
5. **Credit Card / Multi-Account Mapping (Targets):** Supports mapping multiple scraped accounts/cards into one Actual account, or mapping each scraped card into its own Actual account (via `targets` and `accounts`).
21+
22+
6. **Concurrent Processing:** Uses a queue (via [p-queue](https://www.npmjs.com/package/p-queue)) to manage scraping tasks concurrently.
2123

2224
## Installation
2325

@@ -42,25 +44,176 @@ services:
4244
4345
## Configuration
4446
45-
The application configuration is defined using JSON and validated against a schema. The key configuration file is `config.json` and its schema is described in `config.schema.json`.
47+
The application configuration is defined using JSON and validated against a schema.
48+
The main configuration file is `config.json`.
49+
50+
The configuration has **two independent top-level sections**:
51+
1. `actual`: Configures the Actual Budget connection.
52+
2. `banks`: Configures bank scrapers and account mappings.
53+
54+
---
55+
56+
### 1) `actual` configuration
57+
58+
This section configures the connection to your Actual Budget server and budget.
59+
It is **always required**, regardless of how you configure banks or targets.
60+
61+
```json
62+
{
63+
"actual": {
64+
"init": {
65+
"dataDir": "./data",
66+
"password": "your_actual_password",
67+
"serverURL": "https://your-actual-server.com"
68+
},
69+
"budget": {
70+
"syncId": "your_sync_id",
71+
"password": "your_budget_password"
72+
}
73+
}
74+
}
75+
```
76+
77+
Nothing in this block changes when using `targets`, credit cards, or multi-account mappings.
78+
79+
---
80+
81+
### 2) `banks` configuration
82+
83+
The `banks` section defines:
84+
- Which banks to scrape
85+
- The credentials for each bank
86+
- How scraped accounts/cards are mapped into Actual accounts
87+
88+
Each bank entry includes the credentials required by `israeli-bank-scrapers`
89+
(e.g. `userCode`, `username`, `password`, etc.) and supports **multiple mapping modes**.
4690

47-
### Configuration Structure
91+
#### Using `targets` (recommended)
4892

49-
- **actual:**
50-
Contains settings for the Actual API integration:
51-
- `init`: Initialization parameters (e.g., server URL, password).
52-
- `budget`: Contains properties like `syncId` and `password` for synchronizing budgets.
93+
A single bank scrape (for example `visaCal`) may return **multiple accounts/cards**.
94+
Different users model these differently in Actual, so the importer supports `targets`.
5395

54-
- **banks:**
55-
Defines bank-specific settings for each supported bank. Each entry typically requires:
56-
- `actualAccountId`: The account identifier in Actual.
57-
- `password`: The bank account password.
58-
- Additional properties (e.g., `userCode`, `username`, or other bank-specific credentials) as required.
59-
- `reconcile` (optional): A flag to enable balance reconciliation.
96+
Each **target** represents:
97+
- One Actual account
98+
- One or more scraped accounts/cards that feed into it
6099

61-
Make sure your `config.json` follows the schema defined in `config.schema.json`.
100+
For each target:
101+
- Imported transactions = concatenation of transactions from selected cards
102+
- Reconciliation (if enabled) = sum of balances of selected cards
103+
(only cards with a valid numeric balance are included)
62104

63-
Example snippet:
105+
---
106+
107+
#### Example A: One Actual account for all VisaCal cards (consolidated)
108+
109+
```json
110+
{
111+
"actual": {
112+
"init": {
113+
"dataDir": "./data",
114+
"password": "your_actual_password",
115+
"serverURL": "https://your-actual-server.com"
116+
},
117+
"budget": {
118+
"syncId": "your_sync_id",
119+
"password": "your_budget_password"
120+
}
121+
},
122+
"banks": {
123+
"visaCal": {
124+
"username": "bank_username",
125+
"password": "bank_password",
126+
"targets": [
127+
{
128+
"actualAccountId": "actual-creditcards-all",
129+
"reconcile": true,
130+
"accounts": "all"
131+
}
132+
]
133+
}
134+
}
135+
}
136+
```
137+
138+
---
139+
140+
#### Example B: One Actual account per VisaCal card (separate accounts)
141+
142+
```json
143+
{
144+
"actual": {
145+
"init": {
146+
"dataDir": "./data",
147+
"password": "your_actual_password",
148+
"serverURL": "https://your-actual-server.com"
149+
},
150+
"budget": {
151+
"syncId": "your_sync_id",
152+
"password": "your_budget_password"
153+
}
154+
},
155+
"banks": {
156+
"visaCal": {
157+
"username": "bank_username",
158+
"password": "bank_password",
159+
"targets": [
160+
{
161+
"actualAccountId": "actual-card-8538",
162+
"reconcile": true,
163+
"accounts": ["8538"]
164+
},
165+
{
166+
"actualAccountId": "actual-card-7697",
167+
"reconcile": true,
168+
"accounts": ["7697"]
169+
}
170+
]
171+
}
172+
}
173+
}
174+
```
175+
176+
---
177+
178+
#### Example C: Grouped cards into a single Actual account (subset)
179+
180+
```json
181+
{
182+
"actual": {
183+
"init": {
184+
"dataDir": "./data",
185+
"password": "your_actual_password",
186+
"serverURL": "https://your-actual-server.com"
187+
},
188+
"budget": {
189+
"syncId": "your_sync_id",
190+
"password": "your_budget_password"
191+
}
192+
},
193+
"banks": {
194+
"visaCal": {
195+
"username": "bank_username",
196+
"password": "bank_password",
197+
"targets": [
198+
{
199+
"actualAccountId": "actual-cal-primary",
200+
"reconcile": true,
201+
"accounts": ["8538", "7697"]
202+
}
203+
]
204+
}
205+
}
206+
}
207+
```
208+
209+
---
210+
211+
## Legacy configuration (single Actual account per bank)
212+
213+
This configuration style is **fully supported for backward compatibility**,
214+
but does **not** allow fine-grained control over multiple cards/accounts.
215+
216+
It maps all scraped accounts from the bank into a single Actual account.
64217

65218
```json
66219
{
@@ -87,11 +240,19 @@ Example snippet:
87240
"username": "bank_username",
88241
"password": "bank_password"
89242
}
90-
// Additional bank configurations go here...
91243
}
92244
}
93245
```
94246

247+
---
248+
249+
## Notes
250+
251+
- The `actual` block is **always required** and independent of bank configuration.
252+
- `targets` are optional but strongly recommended for credit-card providers.
253+
- Duplicate transactions are prevented using a stable `imported_id`.
254+
- Credit card balances are often negative; reconciliation uses the values as returned by the bank.
255+
95256
## License
96257

97258
This project is open-source. Please see the [LICENSE](./LICENSE) file for licensing details.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@semantic-release/npm": "^13.1.2",
1717
"@semantic-release/release-notes-generator": "^14.1.0",
1818
"@types/lodash": "^4.17.21",
19-
"@types/papaparse": "^5.5.0",
19+
"@types/papaparse": "^5.5.2",
2020
"bun-types": "latest",
2121
"papaparse": "^5.5.3",
2222
"semantic-release": "^25.0.2",
@@ -25,14 +25,14 @@
2525
},
2626
"packageManager": "yarn@4.12.0",
2727
"dependencies": {
28-
"@actual-app/api": "^25.11.0",
28+
"@actual-app/api": "^25.12.0",
29+
"@tomerh2001/israeli-bank-scrapers": "latest",
2930
"cronstrue": "^3.9.0",
30-
"israeli-bank-scrapers": "^6.2.5",
3131
"lodash": "^4.17.21",
3232
"moment": "^2.30.1",
3333
"mute-stdout": "^2.0.0",
3434
"node-cron": "^4.2.1",
3535
"p-queue": "^9.0.1",
36-
"tsx": "^4.20.6"
36+
"tsx": "^4.21.0"
3737
}
3838
}

src/config.d.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,48 @@ export type ConfigActualBudget = {
1818

1919
export type ConfigBanks = Partial<Record<CompanyTypes, ConfigBank>>;
2020

21-
export type ConfigBank = ScraperCredentials & {
21+
/**
22+
* A single "import target" inside Actual.
23+
* One target maps one Actual account to one or more scraped accounts/cards.
24+
*/
25+
export type ConfigBankTarget = {
26+
/**
27+
* Actual Budget account ID to import into and (optionally) reconcile against.
28+
*/
2229
actualAccountId: string;
30+
31+
/**
32+
* If true, create/update a reconciliation transaction to match the scraped balance.
33+
*/
34+
reconcile?: boolean;
35+
36+
/**
37+
* Which scraped accounts (by accountNumber) should be included in this target.
38+
* - "all": include all scraped accounts with usable data (final selection logic lives in code).
39+
* - string[]: include only those accountNumbers.
40+
*
41+
* If omitted, default behavior should match legacy behavior:
42+
* - treat as "all" for import, and for reconciliation use the first usable balance
43+
* (you'll refine this in the implementation files).
44+
*/
45+
accounts?: 'all' | string[];
46+
};
47+
48+
/**
49+
* Bank config remains compatible with existing configs:
50+
* - Legacy: actualAccountId + reconcile at top level
51+
* - New: targets[]
52+
*/
53+
export type ConfigBank = ScraperCredentials & {
54+
/**
55+
* New preferred configuration: one bank can have multiple import targets.
56+
*/
57+
targets?: ConfigBankTarget[];
58+
59+
/**
60+
* Legacy single-target fields (backward compatible).
61+
* If targets is provided, these should be ignored by runtime logic.
62+
*/
63+
actualAccountId?: string;
2364
reconcile?: boolean;
2465
};

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/* eslint-disable no-await-in-loop */
55

66
import process from 'node:process';
7-
import {type CompanyTypes} from 'israeli-bank-scrapers';
7+
import {type CompanyTypes} from '@tomerh2001/israeli-bank-scrapers';
88
import _ from 'lodash';
99
import actual from '@actual-app/api';
1010
import Queue from 'p-queue';

src/utils.d.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import type {ScraperCredentials, CompanyTypes} from 'israeli-bank-scrapers';
2-
import type actual from '@actual-app/api';
3-
import {type ConfigBank} from '../config.js';
1+
import type {CompanyTypes} from 'israeli-bank-scrapers';
2+
import type {ConfigBank} from '../config.js';
43

54
export type ScrapeTransactionsContext = {
65
companyId: CompanyTypes;
76
bank: ConfigBank;
8-
};
7+
};

0 commit comments

Comments
 (0)