@dizzlkheinz/ynab-mcpb 0.12.2 → 0.15.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/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +3 -0
- package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +3 -0
- package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +1 -0
- package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +5 -0
- package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +1569 -0
- package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +3 -0
- package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +2832 -0
- package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +2709 -0
- package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +2832 -0
- package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +2832 -0
- package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +2709 -0
- package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +1 -0
- package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +5217 -0
- package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +2594 -0
- package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +2594 -0
- package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +231 -0
- package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +2590 -0
- package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +5195 -0
- package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +286 -0
- package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +218 -0
- package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +180 -0
- package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +3 -0
- package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +1 -0
- package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +747 -0
- package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +1 -0
- package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +2594 -0
- package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +2594 -0
- package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +144 -0
- package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +416 -0
- package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +2590 -0
- package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +2590 -0
- package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +2590 -0
- package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +790 -0
- package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +766 -0
- package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +790 -0
- package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +2594 -0
- package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +1000 -0
- package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +3489 -0
- package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +766 -0
- package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +2594 -0
- package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +2456 -0
- package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +2594 -0
- package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +18 -0
- package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +48 -0
- package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +1 -0
- package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +1 -0
- package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +1 -0
- package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +1 -0
- package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +1271 -0
- package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +1570 -0
- package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +2590 -0
- package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +3 -0
- package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +3299 -0
- package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +3299 -0
- package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +1882 -0
- package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +2594 -0
- package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +1 -0
- package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +170 -0
- package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +1 -0
- package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +3 -0
- package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +1 -0
- package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +1 -0
- package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +3 -0
- package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +1 -0
- package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +1 -0
- package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +5 -0
- package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +1 -0
- package/.github/workflows/ci-tests.yml +6 -2
- package/.github/workflows/publish.yml +3 -3
- package/.github/workflows/release.yml +4 -0
- package/CHANGELOG.md +89 -1
- package/NUL +1 -1
- package/README.md +36 -10
- package/dist/bundle/index.cjs +65 -42
- package/dist/index.js +9 -20
- package/dist/server/YNABMCPServer.d.ts +2 -1
- package/dist/server/YNABMCPServer.js +61 -27
- package/dist/server/cacheKeys.d.ts +8 -0
- package/dist/server/cacheKeys.js +8 -0
- package/dist/server/config.d.ts +22 -3
- package/dist/server/config.js +16 -17
- package/dist/server/errorHandler.d.ts +2 -0
- package/dist/server/errorHandler.js +49 -5
- package/dist/server/securityMiddleware.js +3 -6
- package/dist/server/toolRegistry.js +8 -10
- package/dist/tools/accountTools.js +4 -3
- package/dist/tools/categoryTools.js +8 -7
- package/dist/tools/monthTools.js +2 -1
- package/dist/tools/payeeTools.js +2 -1
- package/dist/tools/reconcileAdapter.js +10 -5
- package/dist/tools/reconciliation/analyzer.d.ts +4 -2
- package/dist/tools/reconciliation/analyzer.js +120 -404
- package/dist/tools/reconciliation/csvParser.d.ts +51 -0
- package/dist/tools/reconciliation/csvParser.js +413 -0
- package/dist/tools/reconciliation/executor.d.ts +8 -0
- package/dist/tools/reconciliation/executor.js +277 -50
- package/dist/tools/reconciliation/index.d.ts +7 -7
- package/dist/tools/reconciliation/index.js +115 -39
- package/dist/tools/reconciliation/matcher.d.ts +24 -3
- package/dist/tools/reconciliation/matcher.js +175 -133
- package/dist/tools/reconciliation/recommendationEngine.js +22 -18
- package/dist/tools/reconciliation/reportFormatter.js +9 -8
- package/dist/tools/reconciliation/signDetector.d.ts +2 -0
- package/dist/tools/reconciliation/signDetector.js +54 -0
- package/dist/tools/reconciliation/types.d.ts +20 -34
- package/dist/tools/reconciliation/types.js +1 -7
- package/dist/tools/reconciliation/ynabAdapter.d.ts +4 -0
- package/dist/tools/reconciliation/ynabAdapter.js +15 -0
- package/dist/tools/transactionTools.d.ts +3 -17
- package/dist/tools/transactionTools.js +5 -17
- package/dist/types/reconciliation.d.ts +24 -0
- package/dist/types/reconciliation.js +1 -0
- package/dist/utils/baseError.d.ts +3 -0
- package/dist/utils/baseError.js +7 -0
- package/dist/utils/errors.d.ts +13 -0
- package/dist/utils/errors.js +15 -0
- package/dist/utils/validationError.d.ts +3 -0
- package/dist/utils/validationError.js +3 -0
- package/docs/guides/ARCHITECTURE.md +12 -129
- package/docs/plans/2025-11-20-reloadable-config-token-validation.md +93 -0
- package/docs/plans/2025-11-21-fix-transaction-cached-property.md +362 -0
- package/docs/plans/2025-11-21-reconciliation-error-handling.md +90 -0
- package/docs/plans/2025-11-21-v014-hardening.md +153 -0
- package/docs/plans/reconciliation-v2-redesign.md +1571 -0
- package/package.json +8 -2
- package/scripts/run-throttled-integration-tests.js +9 -3
- package/scripts/test-recommendations.ts +1 -1
- package/src/__tests__/performance.test.ts +12 -5
- package/src/__tests__/testUtils.ts +62 -5
- package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +129 -0
- package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +53 -0
- package/src/__tests__/workflows.e2e.test.ts +33 -0
- package/src/index.ts +8 -31
- package/src/server/YNABMCPServer.ts +81 -42
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +10 -12
- package/src/server/__tests__/YNABMCPServer.test.ts +27 -15
- package/src/server/__tests__/config.test.ts +76 -152
- package/src/server/__tests__/server-startup.integration.test.ts +42 -14
- package/src/server/__tests__/toolRegistry.test.ts +1 -1
- package/src/server/cacheKeys.ts +8 -0
- package/src/server/config.ts +20 -38
- package/src/server/errorHandler.ts +52 -5
- package/src/server/securityMiddleware.ts +3 -7
- package/src/server/toolRegistry.ts +14 -10
- package/src/tools/__tests__/categoryTools.test.ts +37 -19
- package/src/tools/__tests__/transactionTools.test.ts +58 -2
- package/src/tools/accountTools.ts +8 -3
- package/src/tools/categoryTools.ts +12 -7
- package/src/tools/monthTools.ts +7 -1
- package/src/tools/payeeTools.ts +7 -1
- package/src/tools/reconcileAdapter.ts +10 -5
- package/src/tools/reconciliation/__tests__/adapter.test.ts +28 -22
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +114 -180
- package/src/tools/reconciliation/__tests__/csvParser.test.ts +87 -0
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +26 -6
- package/src/tools/reconciliation/__tests__/executor.test.ts +133 -60
- package/src/tools/reconciliation/__tests__/matcher.test.ts +68 -54
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +37 -30
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +6 -5
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +30 -11
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +50 -15
- package/src/tools/reconciliation/__tests__/signDetector.test.ts +211 -0
- package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +61 -0
- package/src/tools/reconciliation/analyzer.ts +174 -545
- package/src/tools/reconciliation/csvParser.ts +617 -0
- package/src/tools/reconciliation/executor.ts +344 -58
- package/src/tools/reconciliation/index.ts +141 -48
- package/src/tools/reconciliation/matcher.ts +234 -214
- package/src/tools/reconciliation/recommendationEngine.ts +23 -19
- package/src/tools/reconciliation/reportFormatter.ts +16 -11
- package/src/tools/reconciliation/signDetector.ts +117 -0
- package/src/tools/reconciliation/types.ts +39 -61
- package/src/tools/reconciliation/ynabAdapter.ts +33 -0
- package/src/tools/schemas/outputs/utilityOutputs.ts +1 -1
- package/src/tools/transactionTools.ts +7 -18
- package/src/types/reconciliation.ts +49 -0
- package/src/utils/baseError.ts +7 -0
- package/src/utils/errors.ts +21 -0
- package/src/utils/validationError.ts +3 -0
- package/temp-recon.ts +126 -0
- package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +3662 -0
- package/test_mcp_tools.mjs +75 -0
- package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +0 -8
- package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +0 -3
- package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +0 -3
- package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +0 -1
- package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +0 -12
- package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +0 -1
- package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +0 -1
- package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +0 -5
- package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +0 -1453
- package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +0 -8
- package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +0 -9
- package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +0 -1
- package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +0 -5
- package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +0 -81
- package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +0 -610
- package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +0 -1
- package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +0 -1
- package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +0 -1
- package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +0 -3
- package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +0 -8
- package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +0 -5
- package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +0 -1
- package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +0 -9
- package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +0 -1
- package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +0 -13
- package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +0 -14
- package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +0 -5
- package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +0 -5
- package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +0 -5
- package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +0 -3
- package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +0 -1
- package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +0 -1
- package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +0 -5
- package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +0 -5
- package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +0 -8
- package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +0 -8
- package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +0 -5
- package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +0 -1
- package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +0 -1
- package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +0 -1
- package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +0 -1
- package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +0 -1
- package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +0 -5
- package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +0 -1
- package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +0 -1
- package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +0 -1
- package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +0 -3
- package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +0 -8
- package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +0 -1
- package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +0 -1
- package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +0 -8
- package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +0 -3
- package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +0 -5
- package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +0 -1
- package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +0 -1
- package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +0 -5
- package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +0 -1
- package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +0 -14
- package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +0 -1
- package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +0 -5
- package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +0 -5
- package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +0 -11
- package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +0 -1
- package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +0 -8
- package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +0 -1
- package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +0 -3
- package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +0 -3
- package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +0 -8
- package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +0 -1
- package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +0 -5
- package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +0 -1
- package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +0 -1
- package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +0 -1
- package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +0 -1
- package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +0 -3
- package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +0 -8
- package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +0 -1
- package/.gemini/settings.json +0 -8
- package/ADOS-2-Module-1-Complete-Manual.md +0 -757
- package/WARP.md +0 -245
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { YNABAPIError } from '../../server/errorHandler.js';
|
|
3
|
+
import { toMilli, toMoneyValue, addMilli } from '../../utils/money.js';
|
|
3
4
|
import { generateCorrelationKey, correlateResults, toCorrelationPayload, } from '../transactionTools.js';
|
|
4
5
|
const MONEY_EPSILON_MILLI = 100;
|
|
5
6
|
const DEFAULT_TOLERANCE_CENTS = 1;
|
|
6
7
|
const CENTS_TO_MILLI = 10;
|
|
7
8
|
const MAX_BULK_CREATE_CHUNK = 100;
|
|
9
|
+
const MAX_BULK_UPDATE_CHUNK = 100;
|
|
10
|
+
const BATCH_DELAY_MS = 200;
|
|
11
|
+
const MAX_MEMO_LENGTH = 500;
|
|
8
12
|
function chunkArray(array, size) {
|
|
9
13
|
if (size <= 0) {
|
|
10
14
|
throw new Error('chunk size must be positive');
|
|
@@ -15,6 +19,16 @@ function chunkArray(array, size) {
|
|
|
15
19
|
}
|
|
16
20
|
return chunks;
|
|
17
21
|
}
|
|
22
|
+
function sleep(ms) {
|
|
23
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24
|
+
}
|
|
25
|
+
function truncateMemo(memo) {
|
|
26
|
+
if (!memo)
|
|
27
|
+
return 'Auto-reconciled from bank statement';
|
|
28
|
+
if (memo.length <= MAX_MEMO_LENGTH)
|
|
29
|
+
return memo;
|
|
30
|
+
return memo.substring(0, MAX_MEMO_LENGTH - 3) + '...';
|
|
31
|
+
}
|
|
18
32
|
function generateBulkImportId(accountId, date, amountMilli, payee) {
|
|
19
33
|
const normalizedPayee = (payee ?? '').trim().toLowerCase();
|
|
20
34
|
const raw = `${accountId}|${date}|${amountMilli}|${normalizedPayee}`;
|
|
@@ -74,13 +88,13 @@ export async function executeReconciliation(options) {
|
|
|
74
88
|
let bulkOperationDetails;
|
|
75
89
|
if (params.auto_create_transactions && !balanceAligned) {
|
|
76
90
|
const buildPreparedEntry = (bankTxn) => {
|
|
77
|
-
const amountMilli =
|
|
91
|
+
const amountMilli = bankTxn.amount;
|
|
78
92
|
const saveTransaction = {
|
|
79
93
|
account_id: accountId,
|
|
80
94
|
amount: amountMilli,
|
|
81
95
|
date: bankTxn.date,
|
|
82
96
|
payee_name: bankTxn.payee ?? undefined,
|
|
83
|
-
memo: bankTxn.memo
|
|
97
|
+
memo: truncateMemo(bankTxn.memo),
|
|
84
98
|
cleared: 'cleared',
|
|
85
99
|
approved: true,
|
|
86
100
|
import_id: generateBulkImportId(accountId, bankTxn.date, amountMilli, bankTxn.payee),
|
|
@@ -139,22 +153,27 @@ export async function executeReconciliation(options) {
|
|
|
139
153
|
recordAlignmentIfNeeded(trigger);
|
|
140
154
|
}
|
|
141
155
|
catch (error) {
|
|
156
|
+
const ynabError = normalizeYnabError(error);
|
|
142
157
|
if (bulkOperationDetails) {
|
|
143
158
|
bulkOperationDetails.transaction_failures += 1;
|
|
144
159
|
}
|
|
145
|
-
const failureReason =
|
|
160
|
+
const failureReason = ynabError.message || 'Unknown error occurred';
|
|
161
|
+
const statusSuffix = ynabError.status ? ` (HTTP ${ynabError.status})` : '';
|
|
146
162
|
const failureAction = {
|
|
147
163
|
type: 'create_transaction_failed',
|
|
148
164
|
transaction: entry.saveTransaction,
|
|
149
165
|
reason: options.fallbackError
|
|
150
|
-
? `Bulk fallback failed for ${entry.bankTransaction.payee ?? 'Unknown'} (${failureReason})`
|
|
151
|
-
: `Failed to create transaction ${entry.bankTransaction.payee ?? 'Unknown'} (${failureReason})`,
|
|
166
|
+
? `Bulk fallback failed for ${entry.bankTransaction.payee ?? 'Unknown'} (${failureReason}${statusSuffix})`
|
|
167
|
+
: `Failed to create transaction ${entry.bankTransaction.payee ?? 'Unknown'} (${failureReason}${statusSuffix})`,
|
|
152
168
|
correlation_key: entry.correlationKey,
|
|
153
169
|
};
|
|
154
170
|
if (options.chunkIndex !== undefined) {
|
|
155
171
|
failureAction.bulk_chunk_index = options.chunkIndex;
|
|
156
172
|
}
|
|
157
173
|
actions_taken.push(failureAction);
|
|
174
|
+
if (shouldPropagateYnabError(ynabError)) {
|
|
175
|
+
throw attachStatusToError(ynabError, error);
|
|
176
|
+
}
|
|
158
177
|
}
|
|
159
178
|
}
|
|
160
179
|
if (bulkOperationDetails && options.fallbackError && sequentialAttempts > 0) {
|
|
@@ -280,15 +299,21 @@ export async function executeReconciliation(options) {
|
|
|
280
299
|
bulkOperationDetails.bulk_successes += 1;
|
|
281
300
|
}
|
|
282
301
|
catch (error) {
|
|
283
|
-
|
|
302
|
+
const ynabError = normalizeYnabError(error);
|
|
303
|
+
const failureReason = ynabError.message || 'unknown error';
|
|
284
304
|
bulkOperationDetails.bulk_chunk_failures += 1;
|
|
305
|
+
if (shouldPropagateYnabError(ynabError)) {
|
|
306
|
+
bulkOperationDetails.transaction_failures += chunk.length;
|
|
307
|
+
throw attachStatusToError(ynabError, error);
|
|
308
|
+
}
|
|
309
|
+
bulkOperationDetails.sequential_fallbacks += 1;
|
|
285
310
|
actions_taken.push({
|
|
286
311
|
type: 'bulk_create_fallback',
|
|
287
312
|
transaction: null,
|
|
288
|
-
reason: `Bulk chunk #${chunkIndex} failed (${
|
|
313
|
+
reason: `Bulk chunk #${chunkIndex} failed (${failureReason}${ynabError.status ? ` (HTTP ${ynabError.status})` : ''}) - falling back to sequential creation`,
|
|
289
314
|
bulk_chunk_index: chunkIndex,
|
|
290
315
|
});
|
|
291
|
-
await processSequentialEntries(chunk, { chunkIndex, fallbackError:
|
|
316
|
+
await processSequentialEntries(chunk, { chunkIndex, fallbackError: ynabError });
|
|
292
317
|
}
|
|
293
318
|
}
|
|
294
319
|
}
|
|
@@ -306,13 +331,16 @@ export async function executeReconciliation(options) {
|
|
|
306
331
|
const flags = computeUpdateFlags(match, params);
|
|
307
332
|
if (!flags.needsClearedUpdate && !flags.needsDateUpdate)
|
|
308
333
|
continue;
|
|
309
|
-
if (!match.
|
|
334
|
+
if (!match.ynabTransaction)
|
|
310
335
|
continue;
|
|
311
336
|
const updatePayload = {
|
|
312
|
-
id: match.
|
|
337
|
+
id: match.ynabTransaction.id,
|
|
313
338
|
};
|
|
339
|
+
if (match.ynabTransaction.memo) {
|
|
340
|
+
updatePayload.memo = truncateMemo(match.ynabTransaction.memo);
|
|
341
|
+
}
|
|
314
342
|
if (flags.needsDateUpdate) {
|
|
315
|
-
updatePayload.date = match.
|
|
343
|
+
updatePayload.date = match.bankTransaction.date;
|
|
316
344
|
}
|
|
317
345
|
if (flags.needsClearedUpdate) {
|
|
318
346
|
updatePayload.cleared = 'cleared';
|
|
@@ -324,15 +352,15 @@ export async function executeReconciliation(options) {
|
|
|
324
352
|
actions_taken.push({
|
|
325
353
|
type: 'update_transaction',
|
|
326
354
|
transaction: {
|
|
327
|
-
transaction_id: match.
|
|
328
|
-
new_date: flags.needsDateUpdate ? match.
|
|
355
|
+
transaction_id: match.ynabTransaction.id,
|
|
356
|
+
new_date: flags.needsDateUpdate ? match.bankTransaction.date : undefined,
|
|
329
357
|
cleared: flags.needsClearedUpdate ? 'cleared' : undefined,
|
|
330
358
|
},
|
|
331
359
|
reason: `Would update transaction: ${updateReason(match, flags, currencyCode)}`,
|
|
332
360
|
});
|
|
333
361
|
if (flags.needsClearedUpdate) {
|
|
334
|
-
applyClearedDelta(match.
|
|
335
|
-
if (recordAlignmentIfNeeded(`clearing ${match.
|
|
362
|
+
applyClearedDelta(match.ynabTransaction.amount);
|
|
363
|
+
if (recordAlignmentIfNeeded(`clearing ${match.ynabTransaction.id ?? 'transaction'} (dry run)`)) {
|
|
336
364
|
break;
|
|
337
365
|
}
|
|
338
366
|
}
|
|
@@ -342,34 +370,75 @@ export async function executeReconciliation(options) {
|
|
|
342
370
|
if (flags.needsDateUpdate)
|
|
343
371
|
summary.dates_adjusted += 1;
|
|
344
372
|
if (flags.needsClearedUpdate) {
|
|
345
|
-
applyClearedDelta(match.
|
|
346
|
-
if (recordAlignmentIfNeeded(`clearing ${match.
|
|
373
|
+
applyClearedDelta(match.ynabTransaction.amount);
|
|
374
|
+
if (recordAlignmentIfNeeded(`clearing ${match.ynabTransaction.id}`)) {
|
|
347
375
|
break;
|
|
348
376
|
}
|
|
349
377
|
}
|
|
350
378
|
}
|
|
351
379
|
}
|
|
352
380
|
if (!params.dry_run && transactionsToUpdate.length > 0) {
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
381
|
+
const updateChunks = chunkArray(transactionsToUpdate, MAX_BULK_UPDATE_CHUNK);
|
|
382
|
+
for (let chunkIdx = 0; chunkIdx < updateChunks.length; chunkIdx++) {
|
|
383
|
+
const chunk = updateChunks[chunkIdx];
|
|
384
|
+
try {
|
|
385
|
+
const response = await ynabAPI.transactions.updateTransactions(budgetId, {
|
|
386
|
+
transactions: chunk,
|
|
387
|
+
});
|
|
388
|
+
const updatedTransactions = response.data.transactions ?? [];
|
|
389
|
+
summary.transactions_updated += updatedTransactions.length;
|
|
390
|
+
for (const updatedTransaction of updatedTransactions) {
|
|
391
|
+
const match = orderedAutoMatches.find((m) => m.ynabTransaction?.id === updatedTransaction.id);
|
|
392
|
+
const flags = match
|
|
393
|
+
? computeUpdateFlags(match, params)
|
|
394
|
+
: { needsClearedUpdate: false, needsDateUpdate: false };
|
|
395
|
+
actions_taken.push({
|
|
396
|
+
type: 'update_transaction',
|
|
397
|
+
transaction: updatedTransaction,
|
|
398
|
+
reason: `Updated transaction: ${match ? updateReason(match, flags, currencyCode) : 'cleared'}`,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
accountSnapshotDirty = true;
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
const ynabError = normalizeYnabError(error);
|
|
405
|
+
const failureReason = ynabError.message || 'Unknown error occurred';
|
|
406
|
+
const statusSuffix = ynabError.status ? ` (HTTP ${ynabError.status})` : '';
|
|
407
|
+
actions_taken.push({
|
|
408
|
+
type: 'batch_update_failed',
|
|
409
|
+
transaction: null,
|
|
410
|
+
reason: `Failed to update chunk ${chunkIdx + 1}/${updateChunks.length} (${chunk.length} transaction(s)): ${failureReason}${statusSuffix}`,
|
|
411
|
+
});
|
|
412
|
+
if (shouldPropagateYnabError(ynabError)) {
|
|
413
|
+
throw attachStatusToError(ynabError, error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (chunkIdx < updateChunks.length - 1) {
|
|
417
|
+
await sleep(BATCH_DELAY_MS);
|
|
418
|
+
}
|
|
368
419
|
}
|
|
369
|
-
accountSnapshotDirty = true;
|
|
370
420
|
}
|
|
371
421
|
}
|
|
372
422
|
const shouldRunSanityPass = params.auto_unclear_missing && !balanceAligned;
|
|
423
|
+
actions_taken.push({
|
|
424
|
+
type: 'diagnostic_step3_entry',
|
|
425
|
+
transaction: null,
|
|
426
|
+
reason: `STEP 3 diagnostics: auto_unclear_missing=${params.auto_unclear_missing}, balanceAligned=${balanceAligned}, shouldRunSanityPass=${shouldRunSanityPass}, orderedUnmatchedYNAB.length=${orderedUnmatchedYNAB.length}`,
|
|
427
|
+
});
|
|
428
|
+
if (orderedUnmatchedYNAB.length > 0) {
|
|
429
|
+
const unmatchedDetails = orderedUnmatchedYNAB.slice(0, 10).map((t) => ({
|
|
430
|
+
id: t.id,
|
|
431
|
+
date: t.date,
|
|
432
|
+
cleared: t.cleared,
|
|
433
|
+
amount: formatDisplay(t.amount, currencyCode),
|
|
434
|
+
payee: t.payee ?? 'Unknown',
|
|
435
|
+
}));
|
|
436
|
+
actions_taken.push({
|
|
437
|
+
type: 'diagnostic_unmatched_ynab',
|
|
438
|
+
transaction: { unmatched_transactions: unmatchedDetails },
|
|
439
|
+
reason: `First ${Math.min(10, orderedUnmatchedYNAB.length)} unmatched YNAB transactions (cleared status and amounts)`,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
373
442
|
if (shouldRunSanityPass) {
|
|
374
443
|
const transactionsToUnclear = [];
|
|
375
444
|
for (const ynabTxn of orderedUnmatchedYNAB) {
|
|
@@ -401,19 +470,97 @@ export async function executeReconciliation(options) {
|
|
|
401
470
|
}
|
|
402
471
|
}
|
|
403
472
|
if (!params.dry_run && transactionsToUnclear.length > 0) {
|
|
404
|
-
const
|
|
405
|
-
|
|
473
|
+
const unclearChunks = chunkArray(transactionsToUnclear, MAX_BULK_UPDATE_CHUNK);
|
|
474
|
+
for (let chunkIdx = 0; chunkIdx < unclearChunks.length; chunkIdx++) {
|
|
475
|
+
const chunk = unclearChunks[chunkIdx];
|
|
476
|
+
try {
|
|
477
|
+
const response = await ynabAPI.transactions.updateTransactions(budgetId, {
|
|
478
|
+
transactions: chunk,
|
|
479
|
+
});
|
|
480
|
+
const updatedTransactions = response.data.transactions ?? [];
|
|
481
|
+
summary.transactions_updated += updatedTransactions.length;
|
|
482
|
+
for (const updatedTransaction of updatedTransactions) {
|
|
483
|
+
actions_taken.push({
|
|
484
|
+
type: 'update_transaction',
|
|
485
|
+
transaction: updatedTransaction,
|
|
486
|
+
reason: `Marked transaction ${updatedTransaction.id} as uncleared - not found on statement`,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
accountSnapshotDirty = true;
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
const ynabError = normalizeYnabError(error);
|
|
493
|
+
const failureReason = ynabError.message || 'Unknown error occurred';
|
|
494
|
+
const statusSuffix = ynabError.status ? ` (HTTP ${ynabError.status})` : '';
|
|
495
|
+
actions_taken.push({
|
|
496
|
+
type: 'batch_unclear_failed',
|
|
497
|
+
transaction: null,
|
|
498
|
+
reason: `Failed to unclear chunk ${chunkIdx + 1}/${unclearChunks.length} (${chunk.length} transaction(s)): ${failureReason}${statusSuffix}`,
|
|
499
|
+
});
|
|
500
|
+
if (shouldPropagateYnabError(ynabError)) {
|
|
501
|
+
throw attachStatusToError(ynabError, error);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (chunkIdx < unclearChunks.length - 1) {
|
|
505
|
+
await sleep(BATCH_DELAY_MS);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (balanceAligned && !params.dry_run) {
|
|
511
|
+
const transactionsToReconcile = [];
|
|
512
|
+
for (const match of orderedAutoMatches) {
|
|
513
|
+
if (!match.ynabTransaction)
|
|
514
|
+
continue;
|
|
515
|
+
if (match.ynabTransaction.cleared === 'reconciled')
|
|
516
|
+
continue;
|
|
517
|
+
transactionsToReconcile.push({
|
|
518
|
+
id: match.ynabTransaction.id,
|
|
519
|
+
cleared: 'reconciled',
|
|
406
520
|
});
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
521
|
+
}
|
|
522
|
+
if (transactionsToReconcile.length > 0) {
|
|
523
|
+
const reconcileChunks = chunkArray(transactionsToReconcile, MAX_BULK_UPDATE_CHUNK);
|
|
524
|
+
for (let chunkIdx = 0; chunkIdx < reconcileChunks.length; chunkIdx++) {
|
|
525
|
+
const chunk = reconcileChunks[chunkIdx];
|
|
526
|
+
try {
|
|
527
|
+
const response = await ynabAPI.transactions.updateTransactions(budgetId, {
|
|
528
|
+
transactions: chunk,
|
|
529
|
+
});
|
|
530
|
+
const reconciledTransactions = response.data.transactions ?? [];
|
|
531
|
+
summary.transactions_updated += reconciledTransactions.length;
|
|
532
|
+
for (const reconciledTransaction of reconciledTransactions) {
|
|
533
|
+
const match = orderedAutoMatches.find((m) => m.ynabTransaction?.id === reconciledTransaction.id);
|
|
534
|
+
actions_taken.push({
|
|
535
|
+
type: 'update_transaction',
|
|
536
|
+
transaction: reconciledTransaction,
|
|
537
|
+
reason: `Marked as reconciled: ${match?.bankTransaction.payee ?? 'transaction'} (${formatDisplay(reconciledTransaction.amount, currencyCode)})`,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
accountSnapshotDirty = true;
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
const ynabError = normalizeYnabError(error);
|
|
544
|
+
const failureReason = ynabError.message || 'Unknown error occurred';
|
|
545
|
+
const statusSuffix = ynabError.status ? ` (HTTP ${ynabError.status})` : '';
|
|
546
|
+
actions_taken.push({
|
|
547
|
+
type: 'batch_reconcile_failed',
|
|
548
|
+
transaction: null,
|
|
549
|
+
reason: `Failed to reconcile chunk ${chunkIdx + 1}/${reconcileChunks.length} (${chunk.length} transaction(s)): ${failureReason}${statusSuffix}`,
|
|
550
|
+
});
|
|
551
|
+
if (shouldPropagateYnabError(ynabError)) {
|
|
552
|
+
throw attachStatusToError(ynabError, error);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (chunkIdx < reconcileChunks.length - 1) {
|
|
556
|
+
await sleep(BATCH_DELAY_MS);
|
|
557
|
+
}
|
|
415
558
|
}
|
|
416
|
-
|
|
559
|
+
actions_taken.push({
|
|
560
|
+
type: 'reconciliation_complete',
|
|
561
|
+
transaction: null,
|
|
562
|
+
reason: `Marked ${transactionsToReconcile.length} matched transaction(s) as reconciled - balance aligned within tolerance`,
|
|
563
|
+
});
|
|
417
564
|
}
|
|
418
565
|
}
|
|
419
566
|
let balance_reconciliation;
|
|
@@ -456,12 +603,92 @@ export async function executeReconciliation(options) {
|
|
|
456
603
|
}
|
|
457
604
|
return result;
|
|
458
605
|
}
|
|
606
|
+
const FATAL_YNAB_STATUS_CODES = new Set([400, 401, 403, 404, 429, 500, 503]);
|
|
607
|
+
export function normalizeYnabError(error) {
|
|
608
|
+
const parseStatus = (value) => {
|
|
609
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
610
|
+
return value;
|
|
611
|
+
if (typeof value === 'string') {
|
|
612
|
+
const numeric = Number(value);
|
|
613
|
+
if (Number.isFinite(numeric))
|
|
614
|
+
return numeric;
|
|
615
|
+
}
|
|
616
|
+
return undefined;
|
|
617
|
+
};
|
|
618
|
+
if (error instanceof Error) {
|
|
619
|
+
const status = parseStatus(error.status) ??
|
|
620
|
+
parseStatus(error.response?.status);
|
|
621
|
+
const detailSource = error.detail;
|
|
622
|
+
const detail = typeof detailSource === 'string' && detailSource.trim().length > 0 ? detailSource : undefined;
|
|
623
|
+
const result = {
|
|
624
|
+
name: error.name,
|
|
625
|
+
message: error.message || 'Unknown error occurred',
|
|
626
|
+
};
|
|
627
|
+
if (status !== undefined)
|
|
628
|
+
result.status = status;
|
|
629
|
+
if (detail !== undefined)
|
|
630
|
+
result.detail = detail;
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
if (error && typeof error === 'object') {
|
|
634
|
+
const errObj = error.error ?? error;
|
|
635
|
+
const status = parseStatus(errObj.id ?? errObj.status);
|
|
636
|
+
const detailCandidate = errObj.detail ??
|
|
637
|
+
errObj.message ??
|
|
638
|
+
errObj.name;
|
|
639
|
+
const detail = typeof detailCandidate === 'string' && detailCandidate.trim().length > 0
|
|
640
|
+
? detailCandidate
|
|
641
|
+
: undefined;
|
|
642
|
+
const message = detail ??
|
|
643
|
+
(typeof errObj === 'string' && errObj.trim().length > 0 ? errObj : 'Unknown error occurred');
|
|
644
|
+
const name = typeof errObj.name === 'string'
|
|
645
|
+
? errObj.name
|
|
646
|
+
: undefined;
|
|
647
|
+
const result = { message };
|
|
648
|
+
if (status !== undefined)
|
|
649
|
+
result.status = status;
|
|
650
|
+
if (name !== undefined)
|
|
651
|
+
result.name = name;
|
|
652
|
+
if (detail !== undefined)
|
|
653
|
+
result.detail = detail;
|
|
654
|
+
return result;
|
|
655
|
+
}
|
|
656
|
+
if (typeof error === 'string') {
|
|
657
|
+
return { message: error };
|
|
658
|
+
}
|
|
659
|
+
return { message: 'Unknown error occurred' };
|
|
660
|
+
}
|
|
661
|
+
export function shouldPropagateYnabError(error) {
|
|
662
|
+
return error.status !== undefined && FATAL_YNAB_STATUS_CODES.has(error.status);
|
|
663
|
+
}
|
|
664
|
+
function attachStatusToError(error, originalError) {
|
|
665
|
+
const message = error.message || 'YNAB API error';
|
|
666
|
+
const isKnownCode = error.status === 400 ||
|
|
667
|
+
error.status === 401 ||
|
|
668
|
+
error.status === 403 ||
|
|
669
|
+
error.status === 404 ||
|
|
670
|
+
error.status === 429 ||
|
|
671
|
+
error.status === 500;
|
|
672
|
+
if (isKnownCode) {
|
|
673
|
+
return new YNABAPIError(error.status, message, originalError);
|
|
674
|
+
}
|
|
675
|
+
const statusFragment = error.status ? ` (HTTP ${error.status})` : '';
|
|
676
|
+
const detailFragment = error.detail && !message.includes(error.detail) ? ` (${error.detail})` : '';
|
|
677
|
+
const err = new Error(`${message}${statusFragment}${detailFragment}`);
|
|
678
|
+
if (error.status !== undefined) {
|
|
679
|
+
err.status = error.status;
|
|
680
|
+
}
|
|
681
|
+
if (error.name) {
|
|
682
|
+
err.name = error.name;
|
|
683
|
+
}
|
|
684
|
+
return err;
|
|
685
|
+
}
|
|
459
686
|
function formatDisplay(amount, currency) {
|
|
460
|
-
return
|
|
687
|
+
return toMoneyValue(amount, currency).value_display;
|
|
461
688
|
}
|
|
462
689
|
function computeUpdateFlags(match, params) {
|
|
463
|
-
const ynabTxn = match.
|
|
464
|
-
const bankTxn = match.
|
|
690
|
+
const ynabTxn = match.ynabTransaction;
|
|
691
|
+
const bankTxn = match.bankTransaction;
|
|
465
692
|
if (!ynabTxn) {
|
|
466
693
|
return { needsClearedUpdate: false, needsDateUpdate: false };
|
|
467
694
|
}
|
|
@@ -475,7 +702,7 @@ function updateReason(match, flags, _currency) {
|
|
|
475
702
|
parts.push('marked as cleared');
|
|
476
703
|
}
|
|
477
704
|
if (flags.needsDateUpdate) {
|
|
478
|
-
parts.push(`date adjusted to ${match.
|
|
705
|
+
parts.push(`date adjusted to ${match.bankTransaction.date}`);
|
|
479
706
|
}
|
|
480
707
|
return parts.join(', ');
|
|
481
708
|
}
|
|
@@ -596,7 +823,7 @@ function sortByDateDescending(items) {
|
|
|
596
823
|
return [...items].sort((a, b) => compareDates(b.date, a.date));
|
|
597
824
|
}
|
|
598
825
|
function sortMatchesByBankDateDescending(matches) {
|
|
599
|
-
return [...matches].sort((a, b) => compareDates(b.
|
|
826
|
+
return [...matches].sort((a, b) => compareDates(b.bankTransaction.date, a.bankTransaction.date));
|
|
600
827
|
}
|
|
601
828
|
function compareDates(dateA, dateB) {
|
|
602
829
|
return toChronoValue(dateA) - toChronoValue(dateB);
|
|
@@ -11,16 +11,16 @@ export declare const ReconcileAccountSchema: z.ZodObject<{
|
|
|
11
11
|
account_id: z.ZodString;
|
|
12
12
|
csv_file_path: z.ZodOptional<z.ZodString>;
|
|
13
13
|
csv_data: z.ZodOptional<z.ZodString>;
|
|
14
|
-
csv_format: z.
|
|
15
|
-
date_column: z.
|
|
14
|
+
csv_format: z.ZodOptional<z.ZodObject<{
|
|
15
|
+
date_column: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
16
16
|
amount_column: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
17
17
|
debit_column: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
18
18
|
credit_column: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
19
|
-
description_column: z.
|
|
20
|
-
date_format: z.
|
|
21
|
-
has_header: z.
|
|
22
|
-
delimiter: z.
|
|
23
|
-
}, z.core.$strict
|
|
19
|
+
description_column: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
20
|
+
date_format: z.ZodOptional<z.ZodString>;
|
|
21
|
+
has_header: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
delimiter: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, z.core.$strict>>;
|
|
24
24
|
statement_balance: z.ZodNumber;
|
|
25
25
|
statement_start_date: z.ZodOptional<z.ZodString>;
|
|
26
26
|
statement_end_date: z.ZodOptional<z.ZodString>;
|