@tasenor/common-node 1.9.16
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/.eslintrc.js +4 -0
- package/LICENSE +21 -0
- package/dist/tasenor-common-node/src/cli.d.ts +81 -0
- package/dist/tasenor-common-node/src/cli.js +242 -0
- package/dist/tasenor-common-node/src/cli.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/account.d.ts +12 -0
- package/dist/tasenor-common-node/src/commands/account.js +58 -0
- package/dist/tasenor-common-node/src/commands/account.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/balance.d.ts +11 -0
- package/dist/tasenor-common-node/src/commands/balance.js +117 -0
- package/dist/tasenor-common-node/src/commands/balance.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/db.d.ts +14 -0
- package/dist/tasenor-common-node/src/commands/db.js +69 -0
- package/dist/tasenor-common-node/src/commands/db.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/entry.d.ts +13 -0
- package/dist/tasenor-common-node/src/commands/entry.js +106 -0
- package/dist/tasenor-common-node/src/commands/entry.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/import.d.ts +17 -0
- package/dist/tasenor-common-node/src/commands/import.js +140 -0
- package/dist/tasenor-common-node/src/commands/import.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/importer.d.ts +13 -0
- package/dist/tasenor-common-node/src/commands/importer.js +71 -0
- package/dist/tasenor-common-node/src/commands/importer.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/index.d.ts +191 -0
- package/dist/tasenor-common-node/src/commands/index.js +482 -0
- package/dist/tasenor-common-node/src/commands/index.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/period.d.ts +12 -0
- package/dist/tasenor-common-node/src/commands/period.js +48 -0
- package/dist/tasenor-common-node/src/commands/period.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/plugin.d.ts +15 -0
- package/dist/tasenor-common-node/src/commands/plugin.js +78 -0
- package/dist/tasenor-common-node/src/commands/plugin.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/report.d.ts +11 -0
- package/dist/tasenor-common-node/src/commands/report.js +96 -0
- package/dist/tasenor-common-node/src/commands/report.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/settings.d.ts +10 -0
- package/dist/tasenor-common-node/src/commands/settings.js +64 -0
- package/dist/tasenor-common-node/src/commands/settings.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/stock.d.ts +8 -0
- package/dist/tasenor-common-node/src/commands/stock.js +73 -0
- package/dist/tasenor-common-node/src/commands/stock.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/tag.d.ts +13 -0
- package/dist/tasenor-common-node/src/commands/tag.js +89 -0
- package/dist/tasenor-common-node/src/commands/tag.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/tx.d.ts +12 -0
- package/dist/tasenor-common-node/src/commands/tx.js +81 -0
- package/dist/tasenor-common-node/src/commands/tx.js.map +1 -0
- package/dist/tasenor-common-node/src/commands/user.d.ts +12 -0
- package/dist/tasenor-common-node/src/commands/user.js +52 -0
- package/dist/tasenor-common-node/src/commands/user.js.map +1 -0
- package/dist/tasenor-common-node/src/database/BookkeeperImporter.d.ts +77 -0
- package/dist/tasenor-common-node/src/database/BookkeeperImporter.js +343 -0
- package/dist/tasenor-common-node/src/database/BookkeeperImporter.js.map +1 -0
- package/dist/tasenor-common-node/src/database/DB.d.ts +51 -0
- package/dist/tasenor-common-node/src/database/DB.js +354 -0
- package/dist/tasenor-common-node/src/database/DB.js.map +1 -0
- package/dist/tasenor-common-node/src/database/index.d.ts +7 -0
- package/dist/tasenor-common-node/src/database/index.js +8 -0
- package/dist/tasenor-common-node/src/database/index.js.map +1 -0
- package/dist/tasenor-common-node/src/doccer.d.ts +29 -0
- package/dist/tasenor-common-node/src/doccer.js +30 -0
- package/dist/tasenor-common-node/src/doccer.js.map +1 -0
- package/dist/tasenor-common-node/src/error.d.ts +30 -0
- package/dist/tasenor-common-node/src/error.js +35 -0
- package/dist/tasenor-common-node/src/error.js.map +1 -0
- package/dist/tasenor-common-node/src/export/Exporter.d.ts +69 -0
- package/dist/tasenor-common-node/src/export/Exporter.js +123 -0
- package/dist/tasenor-common-node/src/export/Exporter.js.map +1 -0
- package/dist/tasenor-common-node/src/export/TasenorExporter.d.ts +55 -0
- package/dist/tasenor-common-node/src/export/TasenorExporter.js +135 -0
- package/dist/tasenor-common-node/src/export/TasenorExporter.js.map +1 -0
- package/dist/tasenor-common-node/src/export/TilitinExporter.d.ts +71 -0
- package/dist/tasenor-common-node/src/export/TilitinExporter.js +290 -0
- package/dist/tasenor-common-node/src/export/TilitinExporter.js.map +1 -0
- package/dist/tasenor-common-node/src/export/index.d.ts +8 -0
- package/dist/tasenor-common-node/src/export/index.js +9 -0
- package/dist/tasenor-common-node/src/export/index.js.map +1 -0
- package/dist/tasenor-common-node/src/import/TextFileProcessHandler.d.ts +104 -0
- package/dist/tasenor-common-node/src/import/TextFileProcessHandler.js +354 -0
- package/dist/tasenor-common-node/src/import/TextFileProcessHandler.js.map +1 -0
- package/dist/tasenor-common-node/src/import/TransactionImportConnector.d.ts +38 -0
- package/dist/tasenor-common-node/src/import/TransactionImportConnector.js +27 -0
- package/dist/tasenor-common-node/src/import/TransactionImportConnector.js.map +1 -0
- package/dist/tasenor-common-node/src/import/TransactionImportHandler.d.ts +173 -0
- package/dist/tasenor-common-node/src/import/TransactionImportHandler.js +733 -0
- package/dist/tasenor-common-node/src/import/TransactionImportHandler.js.map +1 -0
- package/dist/tasenor-common-node/src/import/TransactionRules.d.ts +238 -0
- package/dist/tasenor-common-node/src/import/TransactionRules.js +522 -0
- package/dist/tasenor-common-node/src/import/TransactionRules.js.map +1 -0
- package/dist/tasenor-common-node/src/import/TransactionUI.d.ts +181 -0
- package/dist/tasenor-common-node/src/import/TransactionUI.js +482 -0
- package/dist/tasenor-common-node/src/import/TransactionUI.js.map +1 -0
- package/dist/tasenor-common-node/src/import/TransferAnalyzer.d.ts +324 -0
- package/dist/tasenor-common-node/src/import/TransferAnalyzer.js +1379 -0
- package/dist/tasenor-common-node/src/import/TransferAnalyzer.js.map +1 -0
- package/dist/tasenor-common-node/src/import/index.d.ts +11 -0
- package/dist/tasenor-common-node/src/import/index.js +12 -0
- package/dist/tasenor-common-node/src/import/index.js.map +1 -0
- package/dist/tasenor-common-node/src/index.d.ts +12 -0
- package/dist/tasenor-common-node/src/index.js +13 -0
- package/dist/tasenor-common-node/src/index.js.map +1 -0
- package/dist/tasenor-common-node/src/net/crypto.d.ts +33 -0
- package/dist/tasenor-common-node/src/net/crypto.js +63 -0
- package/dist/tasenor-common-node/src/net/crypto.js.map +1 -0
- package/dist/tasenor-common-node/src/net/git.d.ts +49 -0
- package/dist/tasenor-common-node/src/net/git.js +137 -0
- package/dist/tasenor-common-node/src/net/git.js.map +1 -0
- package/dist/tasenor-common-node/src/net/index.d.ts +10 -0
- package/dist/tasenor-common-node/src/net/index.js +11 -0
- package/dist/tasenor-common-node/src/net/index.js.map +1 -0
- package/dist/tasenor-common-node/src/net/middleware.d.ts +61 -0
- package/dist/tasenor-common-node/src/net/middleware.js +220 -0
- package/dist/tasenor-common-node/src/net/middleware.js.map +1 -0
- package/dist/tasenor-common-node/src/net/tokens.d.ts +50 -0
- package/dist/tasenor-common-node/src/net/tokens.js +141 -0
- package/dist/tasenor-common-node/src/net/tokens.js.map +1 -0
- package/dist/tasenor-common-node/src/net/vault.d.ts +67 -0
- package/dist/tasenor-common-node/src/net/vault.js +145 -0
- package/dist/tasenor-common-node/src/net/vault.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/BackendPlugin.d.ts +91 -0
- package/dist/tasenor-common-node/src/plugins/BackendPlugin.js +165 -0
- package/dist/tasenor-common-node/src/plugins/BackendPlugin.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/DataPlugin.d.ts +13 -0
- package/dist/tasenor-common-node/src/plugins/DataPlugin.js +26 -0
- package/dist/tasenor-common-node/src/plugins/DataPlugin.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/ImportPlugin.d.ts +188 -0
- package/dist/tasenor-common-node/src/plugins/ImportPlugin.js +204 -0
- package/dist/tasenor-common-node/src/plugins/ImportPlugin.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/ReportPlugin.d.ts +132 -0
- package/dist/tasenor-common-node/src/plugins/ReportPlugin.js +393 -0
- package/dist/tasenor-common-node/src/plugins/ReportPlugin.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/SchemePlugin.d.ts +34 -0
- package/dist/tasenor-common-node/src/plugins/SchemePlugin.js +47 -0
- package/dist/tasenor-common-node/src/plugins/SchemePlugin.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/ServicePlugin.d.ts +80 -0
- package/dist/tasenor-common-node/src/plugins/ServicePlugin.js +168 -0
- package/dist/tasenor-common-node/src/plugins/ServicePlugin.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/ToolPlugin.d.ts +27 -0
- package/dist/tasenor-common-node/src/plugins/ToolPlugin.js +37 -0
- package/dist/tasenor-common-node/src/plugins/ToolPlugin.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/index.d.ts +13 -0
- package/dist/tasenor-common-node/src/plugins/index.js +14 -0
- package/dist/tasenor-common-node/src/plugins/index.js.map +1 -0
- package/dist/tasenor-common-node/src/plugins/plugins.d.ts +101 -0
- package/dist/tasenor-common-node/src/plugins/plugins.js +292 -0
- package/dist/tasenor-common-node/src/plugins/plugins.js.map +1 -0
- package/dist/tasenor-common-node/src/process/Process.d.ts +108 -0
- package/dist/tasenor-common-node/src/process/Process.js +335 -0
- package/dist/tasenor-common-node/src/process/Process.js.map +1 -0
- package/dist/tasenor-common-node/src/process/ProcessConnector.d.ts +24 -0
- package/dist/tasenor-common-node/src/process/ProcessConnector.js +28 -0
- package/dist/tasenor-common-node/src/process/ProcessConnector.js.map +1 -0
- package/dist/tasenor-common-node/src/process/ProcessFile.d.ts +69 -0
- package/dist/tasenor-common-node/src/process/ProcessFile.js +145 -0
- package/dist/tasenor-common-node/src/process/ProcessFile.js.map +1 -0
- package/dist/tasenor-common-node/src/process/ProcessHandler.d.ts +60 -0
- package/dist/tasenor-common-node/src/process/ProcessHandler.js +73 -0
- package/dist/tasenor-common-node/src/process/ProcessHandler.js.map +1 -0
- package/dist/tasenor-common-node/src/process/ProcessStep.d.ts +52 -0
- package/dist/tasenor-common-node/src/process/ProcessStep.js +78 -0
- package/dist/tasenor-common-node/src/process/ProcessStep.js.map +1 -0
- package/dist/tasenor-common-node/src/process/ProcessingSystem.d.ts +60 -0
- package/dist/tasenor-common-node/src/process/ProcessingSystem.js +182 -0
- package/dist/tasenor-common-node/src/process/ProcessingSystem.js.map +1 -0
- package/dist/tasenor-common-node/src/process/index.d.ts +11 -0
- package/dist/tasenor-common-node/src/process/index.js +12 -0
- package/dist/tasenor-common-node/src/process/index.js.map +1 -0
- package/dist/tasenor-common-node/src/reports/conversions.d.ts +8 -0
- package/dist/tasenor-common-node/src/reports/conversions.js +47 -0
- package/dist/tasenor-common-node/src/reports/conversions.js.map +1 -0
- package/dist/tasenor-common-node/src/reports/index.d.ts +6 -0
- package/dist/tasenor-common-node/src/reports/index.js +7 -0
- package/dist/tasenor-common-node/src/reports/index.js.map +1 -0
- package/dist/tasenor-common-node/src/server/ISPDemoServer.d.ts +43 -0
- package/dist/tasenor-common-node/src/server/ISPDemoServer.js +112 -0
- package/dist/tasenor-common-node/src/server/ISPDemoServer.js.map +1 -0
- package/dist/tasenor-common-node/src/server/api.d.ts +15 -0
- package/dist/tasenor-common-node/src/server/api.js +27 -0
- package/dist/tasenor-common-node/src/server/api.js.map +1 -0
- package/dist/tasenor-common-node/src/server/index.d.ts +7 -0
- package/dist/tasenor-common-node/src/server/index.js +8 -0
- package/dist/tasenor-common-node/src/server/index.js.map +1 -0
- package/dist/tasenor-common-node/src/server/router.d.ts +5 -0
- package/dist/tasenor-common-node/src/server/router.js +37 -0
- package/dist/tasenor-common-node/src/server/router.js.map +1 -0
- package/dist/tasenor-common-node/src/system.d.ts +27 -0
- package/dist/tasenor-common-node/src/system.js +95 -0
- package/dist/tasenor-common-node/src/system.js.map +1 -0
- package/dist/tasenor-common-node/src/testing/ProcessingSystemMock.d.ts +21 -0
- package/dist/tasenor-common-node/src/testing/ProcessingSystemMock.js +33 -0
- package/dist/tasenor-common-node/src/testing/ProcessingSystemMock.js.map +1 -0
- package/dist/tasenor-common-node/src/testing/UnitTestImportConnector.d.ts +24 -0
- package/dist/tasenor-common-node/src/testing/UnitTestImportConnector.js +68 -0
- package/dist/tasenor-common-node/src/testing/UnitTestImportConnector.js.map +1 -0
- package/dist/tasenor-common-node/src/testing/UnitTester.d.ts +64 -0
- package/dist/tasenor-common-node/src/testing/UnitTester.js +199 -0
- package/dist/tasenor-common-node/src/testing/UnitTester.js.map +1 -0
- package/dist/tasenor-common-node/src/testing/index.d.ts +4 -0
- package/dist/tasenor-common-node/src/testing/index.js +5 -0
- package/dist/tasenor-common-node/src/testing/index.js.map +1 -0
- package/dist/tasenor-common-node/src/testing/test-handlers.d.ts +13 -0
- package/dist/tasenor-common-node/src/testing/test-handlers.js +52 -0
- package/dist/tasenor-common-node/src/testing/test-handlers.js.map +1 -0
- package/dist/tasenor-common-node/tests/TransactionRules.spec.d.ts +1 -0
- package/dist/tasenor-common-node/tests/TransactionRules.spec.js +64 -0
- package/dist/tasenor-common-node/tests/TransactionRules.spec.js.map +1 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-account-address.spec.d.ts +1 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-account-address.spec.js +80 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-account-address.spec.js.map +1 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-buying-and-selling.spec.d.ts +1 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-buying-and-selling.spec.js +342 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-buying-and-selling.spec.js.map +1 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-loans.spec.d.ts +1 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-loans.spec.js +174 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-loans.spec.js.map +1 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-multiple-null-amounts.spec.d.ts +1 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-multiple-null-amounts.spec.js +175 -0
- package/dist/tasenor-common-node/tests/TransferAnalyzer-multiple-null-amounts.spec.js.map +1 -0
- package/dist/tasenor-common-node/tests/password.spec.d.ts +1 -0
- package/dist/tasenor-common-node/tests/password.spec.js +8 -0
- package/dist/tasenor-common-node/tests/password.spec.js.map +1 -0
- package/dist/tasenor-common-node/tests/tokens.spec.d.ts +1 -0
- package/dist/tasenor-common-node/tests/tokens.spec.js +49 -0
- package/dist/tasenor-common-node/tests/tokens.spec.js.map +1 -0
- package/dist/tasenor-common-node/tests/vault.spec.d.ts +1 -0
- package/dist/tasenor-common-node/tests/vault.spec.js +19 -0
- package/dist/tasenor-common-node/tests/vault.spec.js.map +1 -0
- package/dist/tasenor-common-plugins/src/CoinbaseImport/backend/CoinbaseHandler.d.ts +11 -0
- package/dist/tasenor-common-plugins/src/CoinbaseImport/backend/CoinbaseHandler.js +30 -0
- package/dist/tasenor-common-plugins/src/CoinbaseImport/backend/CoinbaseHandler.js.map +1 -0
- package/dist/tasenor-common-plugins/src/IncomeAndExpenses/backend/index.d.ts +5 -0
- package/dist/tasenor-common-plugins/src/IncomeAndExpenses/backend/index.js +350 -0
- package/dist/tasenor-common-plugins/src/IncomeAndExpenses/backend/index.js.map +1 -0
- package/dist/tasenor-common-plugins/src/KrakenImport/backend/KrakenHandler.d.ts +23 -0
- package/dist/tasenor-common-plugins/src/KrakenImport/backend/KrakenHandler.js +83 -0
- package/dist/tasenor-common-plugins/src/KrakenImport/backend/KrakenHandler.js.map +1 -0
- package/dist/tasenor-common-plugins/src/LynxImport/backend/LynxHandler.d.ts +28 -0
- package/dist/tasenor-common-plugins/src/LynxImport/backend/LynxHandler.js +340 -0
- package/dist/tasenor-common-plugins/src/LynxImport/backend/LynxHandler.js.map +1 -0
- package/dist/tasenor-common-plugins/src/NordeaImport/backend/NordeaHandler.d.ts +11 -0
- package/dist/tasenor-common-plugins/src/NordeaImport/backend/NordeaHandler.js +39 -0
- package/dist/tasenor-common-plugins/src/NordeaImport/backend/NordeaHandler.js.map +1 -0
- package/dist/tasenor-common-plugins/src/NordnetImport/backend/NordnetHandler.d.ts +17 -0
- package/dist/tasenor-common-plugins/src/NordnetImport/backend/NordnetHandler.js +66 -0
- package/dist/tasenor-common-plugins/src/NordnetImport/backend/NordnetHandler.js.map +1 -0
- package/dist/tasenor-common-plugins/src/TITOImport/backend/TITOHandler.d.ts +13 -0
- package/dist/tasenor-common-plugins/src/TITOImport/backend/TITOHandler.js +241 -0
- package/dist/tasenor-common-plugins/src/TITOImport/backend/TITOHandler.js.map +1 -0
- package/jest.config.js +1 -0
- package/package.json +62 -0
- package/src/cli.ts +267 -0
- package/src/commands/account.ts +69 -0
- package/src/commands/balance.ts +131 -0
- package/src/commands/db.ts +84 -0
- package/src/commands/entry.ts +117 -0
- package/src/commands/import.ts +160 -0
- package/src/commands/importer.ts +84 -0
- package/src/commands/index.ts +534 -0
- package/src/commands/period.ts +59 -0
- package/src/commands/plugin.ts +95 -0
- package/src/commands/report.ts +113 -0
- package/src/commands/settings.ts +75 -0
- package/src/commands/stock.ts +80 -0
- package/src/commands/tag.ts +102 -0
- package/src/commands/tx.ts +93 -0
- package/src/commands/user.ts +65 -0
- package/src/database/BookkeeperImporter.ts +358 -0
- package/src/database/DB.ts +396 -0
- package/src/database/index.ts +7 -0
- package/src/doccer.ts +29 -0
- package/src/error.ts +32 -0
- package/src/export/Exporter.ts +136 -0
- package/src/export/TasenorExporter.ts +144 -0
- package/src/export/TilitinExporter.ts +302 -0
- package/src/export/index.ts +8 -0
- package/src/import/TextFileProcessHandler.ts +384 -0
- package/src/import/TransactionImportConnector.ts +65 -0
- package/src/import/TransactionImportHandler.ts +819 -0
- package/src/import/TransactionRules.ts +570 -0
- package/src/import/TransactionUI.ts +520 -0
- package/src/import/TransferAnalyzer.ts +1450 -0
- package/src/import/index.ts +11 -0
- package/src/index.ts +12 -0
- package/src/net/crypto.ts +69 -0
- package/src/net/git.ts +151 -0
- package/src/net/index.ts +10 -0
- package/src/net/middleware.ts +261 -0
- package/src/net/tokens.ts +140 -0
- package/src/net/vault.ts +161 -0
- package/src/plugins/BackendPlugin.ts +188 -0
- package/src/plugins/DataPlugin.ts +29 -0
- package/src/plugins/ImportPlugin.ts +211 -0
- package/src/plugins/ReportPlugin.ts +443 -0
- package/src/plugins/SchemePlugin.ts +56 -0
- package/src/plugins/ServicePlugin.ts +188 -0
- package/src/plugins/ToolPlugin.ts +44 -0
- package/src/plugins/index.ts +13 -0
- package/src/plugins/plugins.ts +345 -0
- package/src/process/Process.ts +368 -0
- package/src/process/ProcessConnector.ts +45 -0
- package/src/process/ProcessFile.ts +169 -0
- package/src/process/ProcessHandler.ts +94 -0
- package/src/process/ProcessStep.ts +100 -0
- package/src/process/ProcessingSystem.ts +202 -0
- package/src/process/index.ts +11 -0
- package/src/reports/conversions.ts +52 -0
- package/src/reports/index.ts +6 -0
- package/src/server/ISPDemoServer.ts +122 -0
- package/src/server/api.ts +37 -0
- package/src/server/index.ts +7 -0
- package/src/server/router.ts +60 -0
- package/src/system.ts +96 -0
- package/src/testing/ProcessingSystemMock.ts +45 -0
- package/src/testing/UnitTestImportConnector.ts +86 -0
- package/src/testing/UnitTester.ts +231 -0
- package/src/testing/index.ts +4 -0
- package/src/testing/test-handlers.ts +55 -0
- package/tests/TransactionRules.spec.ts +73 -0
- package/tests/TransferAnalyzer-account-address.spec.ts +87 -0
- package/tests/TransferAnalyzer-buying-and-selling.spec.ts +354 -0
- package/tests/TransferAnalyzer-loans.spec.ts +197 -0
- package/tests/TransferAnalyzer-multiple-null-amounts.spec.ts +181 -0
- package/tests/password.spec.ts +8 -0
- package/tests/tokens.spec.ts +52 -0
- package/tests/vault.spec.ts +20 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import { TransactionApplyResults, debug, realNegative, realPositive, NO_SEGMENT, num, Directions, less, mergeTags, log } from '@dataplug/tasenor-common';
|
|
3
|
+
import { TransferAnalyzer } from './TransferAnalyzer';
|
|
4
|
+
import hash from 'object-hash';
|
|
5
|
+
import { TransactionUI } from './TransactionUI';
|
|
6
|
+
import { TransactionRules } from './TransactionRules';
|
|
7
|
+
import { isTransactionImportConnector } from './TransactionImportConnector';
|
|
8
|
+
import { TextFileProcessHandler } from './TextFileProcessHandler';
|
|
9
|
+
import { BadState, InvalidFile, NotImplemented, SystemError } from '../error';
|
|
10
|
+
import clone from 'clone';
|
|
11
|
+
/**
|
|
12
|
+
* Core functionality for all transaction import handlers.
|
|
13
|
+
*/
|
|
14
|
+
export class TransactionImportHandler extends TextFileProcessHandler {
|
|
15
|
+
UI;
|
|
16
|
+
rules;
|
|
17
|
+
analyzer;
|
|
18
|
+
constructor(name) {
|
|
19
|
+
super(name);
|
|
20
|
+
this.UI = new TransactionUI(this);
|
|
21
|
+
this.rules = new TransactionRules(this);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* By default, we don't support multifile.
|
|
25
|
+
* @param file
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
canAppend(file) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get a single account balance.
|
|
33
|
+
* @param addr
|
|
34
|
+
*/
|
|
35
|
+
getBalance(addr) {
|
|
36
|
+
if (!this.analyzer) {
|
|
37
|
+
throw new Error(`Cannot access balance for ${addr} when no analyzer instantiated.`);
|
|
38
|
+
}
|
|
39
|
+
return this.analyzer.getBalance(addr);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the translation for the text to the currently configured language.
|
|
43
|
+
* @param text
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
async getTranslation(text, language) {
|
|
47
|
+
if (!language) {
|
|
48
|
+
throw new SystemError('Language is compulsory setting for importing, if there are unknowns to ask from UI.');
|
|
49
|
+
}
|
|
50
|
+
return this.system.getTranslation(text, language);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get the account having matching asset in their code.
|
|
54
|
+
* @param asset
|
|
55
|
+
* @returns
|
|
56
|
+
*/
|
|
57
|
+
getAccountCanditates(addr, config) {
|
|
58
|
+
return this.system.connector.getAccountCanditates(addr, config);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Construct grouping for the line data with columns defined using sub class that can generate unique ID per transaction.
|
|
62
|
+
* @param state
|
|
63
|
+
*/
|
|
64
|
+
async groupingById(state) {
|
|
65
|
+
state.segments = {};
|
|
66
|
+
for (const fileName of Object.keys(state.files)) {
|
|
67
|
+
// Collect segments from lines.
|
|
68
|
+
for (let n = 0; n < state.files[fileName].lines.length; n++) {
|
|
69
|
+
const line = state.files[fileName].lines[n];
|
|
70
|
+
if (!line.columns || Object.keys(line.columns).length === 0) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const id = this.segmentId(line);
|
|
74
|
+
if (!id || !state.segments) {
|
|
75
|
+
throw new InvalidFile(`The segment ID for ${JSON.stringify(line)} was not found by ${this.constructor.name}.`);
|
|
76
|
+
}
|
|
77
|
+
if (id === NO_SEGMENT) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
state.segments[id] = state.segments[id] || { id, time: undefined, lines: [] };
|
|
81
|
+
state.segments[id].lines.push({ number: n, file: fileName });
|
|
82
|
+
line.segmentId = id;
|
|
83
|
+
}
|
|
84
|
+
// Calculate time stamps for each segment.
|
|
85
|
+
if (!state.segments) {
|
|
86
|
+
throw new InvalidFile('This cannot happen.');
|
|
87
|
+
}
|
|
88
|
+
Object.values(state.segments).forEach((segment) => {
|
|
89
|
+
const stamps = new Set();
|
|
90
|
+
segment.lines.forEach(segmentLine => {
|
|
91
|
+
const line = state.files[segmentLine.file].lines[segmentLine.number];
|
|
92
|
+
const time = this.time(line);
|
|
93
|
+
if (time) {
|
|
94
|
+
stamps.add(time.getTime());
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
if (stamps.size === 0) {
|
|
98
|
+
throw new InvalidFile(`Was not able to find timestamps for lines ${JSON.stringify(segment.lines)}.`);
|
|
99
|
+
}
|
|
100
|
+
if (stamps.size > 1) {
|
|
101
|
+
throw new InvalidFile(`Found more than one (${stamps.size}) canditate for timestamp (${[...stamps]}) from lines ${JSON.stringify(segment.lines)}.`);
|
|
102
|
+
}
|
|
103
|
+
segment.time = new Date([...stamps][0]);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Default parser for file data.
|
|
110
|
+
*/
|
|
111
|
+
async parse(state, config = {}) {
|
|
112
|
+
switch (this.importOptions.parser) {
|
|
113
|
+
case 'csv':
|
|
114
|
+
if (this.importOptions.csv === undefined) {
|
|
115
|
+
throw new SystemError('No CSV options defined.');
|
|
116
|
+
}
|
|
117
|
+
return this.parseCSV(state, this.importOptions.csv);
|
|
118
|
+
case 'custom':
|
|
119
|
+
if (this.importOptions.custom === undefined) {
|
|
120
|
+
throw new SystemError('No custom options defined.');
|
|
121
|
+
}
|
|
122
|
+
return this.parseCustom(state, this.importOptions.custom);
|
|
123
|
+
default:
|
|
124
|
+
throw new SystemError(`Parser '${this.importOptions.parser}' is not implemented.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Default segmentation is parsing CSV and then grouping by segment ID constructed for each line.
|
|
129
|
+
* @param state
|
|
130
|
+
* @param files
|
|
131
|
+
* @returns
|
|
132
|
+
*/
|
|
133
|
+
async segmentationCSV(process, state, files) {
|
|
134
|
+
const parsed = await this.parse(state, process.config);
|
|
135
|
+
const newState = await this.groupingById(parsed);
|
|
136
|
+
this.debugSegmentation(newState);
|
|
137
|
+
return newState;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Hook to do some post proccessing for segmentation process. Collects standard fields.
|
|
141
|
+
* @param state
|
|
142
|
+
* @returns
|
|
143
|
+
*/
|
|
144
|
+
async segmentationPostProcess(state) {
|
|
145
|
+
const shared = {};
|
|
146
|
+
for (const fileName of Object.keys(state.files)) {
|
|
147
|
+
// Build standard fields.
|
|
148
|
+
const { textField, totalAmountField } = this.importOptions;
|
|
149
|
+
for (let n = 0; n < state.files[fileName].lines.length; n++) {
|
|
150
|
+
const { columns, segmentId } = state.files[fileName].lines[n];
|
|
151
|
+
for (const name of this.importOptions.requiredFields) {
|
|
152
|
+
if (columns[name] === undefined) {
|
|
153
|
+
columns[name] = '';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const name of this.importOptions.numericFields) {
|
|
157
|
+
if (columns[name] !== undefined) {
|
|
158
|
+
// TODO: We need to allow numeric values as well. Might need some syntax fixing here and there.
|
|
159
|
+
columns[name] = (columns[name] === '' ? 0 : num(columns[name]));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (this.importOptions.sharedFields) {
|
|
163
|
+
for (const name of this.importOptions.sharedFields) {
|
|
164
|
+
if (columns[name] !== undefined) {
|
|
165
|
+
shared[segmentId] = shared[segmentId] || {};
|
|
166
|
+
if (shared[segmentId][name] === undefined) {
|
|
167
|
+
shared[segmentId][name] = columns[name];
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
throw new Error(`No handling implemented when shared field '${name}' is found from more than one line (${JSON.stringify(shared[segmentId][name])} and ${JSON.stringify(columns[name])}).`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (textField) {
|
|
176
|
+
columns._textField = columns[textField];
|
|
177
|
+
}
|
|
178
|
+
if (totalAmountField) {
|
|
179
|
+
columns._totalAmountField = columns[totalAmountField];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Add shared fields.
|
|
184
|
+
for (const fileName of Object.keys(state.files)) {
|
|
185
|
+
for (let n = 0; n < state.files[fileName].lines.length; n++) {
|
|
186
|
+
const { columns, segmentId } = state.files[fileName].lines[n];
|
|
187
|
+
if (shared[segmentId]) {
|
|
188
|
+
Object.assign(columns, shared[segmentId]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return state;
|
|
193
|
+
}
|
|
194
|
+
async segmentation(process, state, files) {
|
|
195
|
+
const result = await this.segmentationPostProcess(await this.segmentationCSV(process, state, files));
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Helper to dump segmentation results.
|
|
200
|
+
*/
|
|
201
|
+
debugSegmentation(state) {
|
|
202
|
+
if (state.files) {
|
|
203
|
+
Object.keys(state.files).forEach(fileName => {
|
|
204
|
+
debug('SEGMENTATION', `Segmentation of ${fileName}`);
|
|
205
|
+
debug('SEGMENTATION', state.files[fileName].lines.filter(line => Object.keys(line.columns).length > 0));
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Construct a hash for a text line usable as unique segment ID.
|
|
211
|
+
* @param line
|
|
212
|
+
*/
|
|
213
|
+
hash(line, columns = undefined) {
|
|
214
|
+
// Trim spaces away before calculating hash.
|
|
215
|
+
if (columns === undefined) {
|
|
216
|
+
columns = Object.keys(line.columns);
|
|
217
|
+
}
|
|
218
|
+
const obj = columns.map(c => [c, line.columns[c]]).filter(entry => entry[1] !== undefined).reduce((prev, cur) => ({ ...prev, [cur[0]]: `${cur[1]}`.trim() }), {});
|
|
219
|
+
return hash.sha1(obj);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Segmentation by ID can use this function to group lines by their ID. By default the hash is used.
|
|
223
|
+
* @param line
|
|
224
|
+
*/
|
|
225
|
+
segmentId(line, columns = undefined) {
|
|
226
|
+
if (columns === undefined) {
|
|
227
|
+
columns = Object.keys(line.columns);
|
|
228
|
+
}
|
|
229
|
+
if (line.columns && Object.keys(columns).length) {
|
|
230
|
+
return this.hash(line, columns);
|
|
231
|
+
}
|
|
232
|
+
return NO_SEGMENT;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Find out the timestamp from the line data if any.
|
|
236
|
+
* @param line
|
|
237
|
+
*/
|
|
238
|
+
time(line) {
|
|
239
|
+
throw new NotImplemented(`Import class ${this.constructor.name} does not implement time().`);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Default classification constructs lines belonging to each segment and asks subclass to classify them.
|
|
243
|
+
*
|
|
244
|
+
* @param state
|
|
245
|
+
* @param files
|
|
246
|
+
* @returns
|
|
247
|
+
*/
|
|
248
|
+
async classification(process, state, files) {
|
|
249
|
+
const newState = {
|
|
250
|
+
stage: 'classified',
|
|
251
|
+
files: state.files,
|
|
252
|
+
segments: state.segments,
|
|
253
|
+
result: {}
|
|
254
|
+
};
|
|
255
|
+
if (state.segments) {
|
|
256
|
+
// Handle segments by date.
|
|
257
|
+
for (const segment of this.sortSegments(state.segments)) {
|
|
258
|
+
const lines = segment.lines.map(fileRef => state.files[fileRef.file].lines[fileRef.number]);
|
|
259
|
+
const result = await this.classifyLines(lines, process.config, state.segments[segment.id]);
|
|
260
|
+
if (newState.result) { // Needed for compiler.
|
|
261
|
+
newState.result[segment.id] = [result];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
this.debugClassification(newState);
|
|
266
|
+
return newState;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Helper to dump classification results.
|
|
270
|
+
*/
|
|
271
|
+
debugClassification(state) {
|
|
272
|
+
if (state.result) {
|
|
273
|
+
Object.keys(state.result).forEach(segmentId => {
|
|
274
|
+
if (state.result && state.result[segmentId]) {
|
|
275
|
+
debug('CLASSIFICATION', `Classification of ${segmentId}`);
|
|
276
|
+
debug('CLASSIFICATION', state.result[segmentId]);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* By default, use rules to classify.
|
|
283
|
+
* @param lines
|
|
284
|
+
*/
|
|
285
|
+
async classifyLines(lines, config, segment) {
|
|
286
|
+
return await this.rules.classifyLines(lines, config, segment);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Collect lines related to the segment.
|
|
290
|
+
* @param state
|
|
291
|
+
* @param segmentId
|
|
292
|
+
*/
|
|
293
|
+
getLines(state, segmentId) {
|
|
294
|
+
if (state.segments && state.segments[segmentId]) {
|
|
295
|
+
const segment = state.segments[segmentId];
|
|
296
|
+
const lines = segment.lines.map(line => state.files[line.file].lines[line.number]);
|
|
297
|
+
return lines;
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Check if all accounts are configured and if not, construct query UI for it.
|
|
303
|
+
* @param state
|
|
304
|
+
* @returns
|
|
305
|
+
*/
|
|
306
|
+
async needInputForAnalysis(state, config) {
|
|
307
|
+
if (!state.result || !state.segments) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
const missing = new Set();
|
|
311
|
+
// Use fresh analyzer to avoid messing stock bookkeeping.
|
|
312
|
+
const analyzer = new TransferAnalyzer(this, config, state);
|
|
313
|
+
for (const [segmentId, result] of Object.entries(state.result)) {
|
|
314
|
+
const segment = state.segments[segmentId];
|
|
315
|
+
const items = result;
|
|
316
|
+
// Check if we have accounts.
|
|
317
|
+
for (const transfer of items) {
|
|
318
|
+
for (const acc of await analyzer.collectAccounts(segment, transfer, { findMissing: true })) {
|
|
319
|
+
missing.add(acc);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Find out if some of the missing accounts are actual defined as UI query or perhaps already answered.
|
|
323
|
+
for (const address of missing) {
|
|
324
|
+
if (config.answers) {
|
|
325
|
+
const answers = config.answers;
|
|
326
|
+
if ((segmentId in answers) &&
|
|
327
|
+
(`account.${address}` in answers[segmentId]) &&
|
|
328
|
+
(answers[segmentId][`account.${address}`] !== undefined)) {
|
|
329
|
+
missing.delete(address);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const [reason, type, asset] = address.split('.');
|
|
334
|
+
const query = await analyzer.getAccountQuery(reason, type, asset);
|
|
335
|
+
const lines = this.getLines(state, segmentId);
|
|
336
|
+
if (!lines) {
|
|
337
|
+
throw new Error(`Failed to collect lines for segment ${segmentId}.`);
|
|
338
|
+
}
|
|
339
|
+
if (query) {
|
|
340
|
+
const description = await this.UI.describeLines(lines, config.language);
|
|
341
|
+
const question = await this.UI.query(`answer.${segmentId}.account.${address}`, query, [], config.language);
|
|
342
|
+
return new Directions({
|
|
343
|
+
type: 'ui',
|
|
344
|
+
element: {
|
|
345
|
+
type: 'flat',
|
|
346
|
+
elements: [description, question]
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!missing.size) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
log(`Need to configure some accounts: ${[...missing].join(', ')}`);
|
|
356
|
+
return this.directionsForMissingAccounts(missing, config);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Study configured accounts and missing accounts and construct appropriate UI query for accounts.
|
|
360
|
+
* @param missing
|
|
361
|
+
* @param config
|
|
362
|
+
* @returns
|
|
363
|
+
*/
|
|
364
|
+
async directionsForMissingAccounts(missing, config) {
|
|
365
|
+
// Collect account settings from config.
|
|
366
|
+
const configured = Object.keys(config).filter(key => /^account\.\w+\.\w+\./.test(key));
|
|
367
|
+
// Get reason + type pair grouping and accounts for each group.
|
|
368
|
+
const pairs = {};
|
|
369
|
+
for (const address of configured) {
|
|
370
|
+
const [, reason, type, asset] = address.split('.');
|
|
371
|
+
if (asset !== '*') {
|
|
372
|
+
pairs[`${reason}.${type}`] = pairs[`${reason}.${type}`] || new Set();
|
|
373
|
+
pairs[`${reason}.${type}`].add(`${reason}.${type}.${asset}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
for (const address of missing) {
|
|
377
|
+
const [reason, type, asset] = address.split('.');
|
|
378
|
+
pairs[`${reason}.${type}`] = pairs[`${reason}.${type}`] || new Set();
|
|
379
|
+
pairs[`${reason}.${type}`].add(`${reason}.${type}.${asset}`);
|
|
380
|
+
}
|
|
381
|
+
// Check groups and construct query either for single account or grouped accounts.
|
|
382
|
+
const elements = [];
|
|
383
|
+
for (const addresses of Object.values(pairs)) {
|
|
384
|
+
if (addresses.size === 1) {
|
|
385
|
+
if (missing.has([...addresses][0])) {
|
|
386
|
+
elements.push(await this.UI.account(config, [...addresses][0]));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
let count = 0;
|
|
391
|
+
for (const address of addresses) {
|
|
392
|
+
if (missing.has(address))
|
|
393
|
+
count++;
|
|
394
|
+
}
|
|
395
|
+
if (count) {
|
|
396
|
+
elements.push(await this.UI.accountGroup(config, [...addresses]));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (elements.length === 0) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
elements.push(await this.UI.submit('Continue', 1, config.language));
|
|
404
|
+
return new Directions({
|
|
405
|
+
type: 'ui',
|
|
406
|
+
element: {
|
|
407
|
+
type: 'flat',
|
|
408
|
+
elements
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Insert custom segments based on answer collection, if necessary.
|
|
414
|
+
*/
|
|
415
|
+
async createCustomSegments(state, config) {
|
|
416
|
+
const newState = clone(state);
|
|
417
|
+
if (!newState.result) {
|
|
418
|
+
newState.result = {};
|
|
419
|
+
}
|
|
420
|
+
if (!newState.segments) {
|
|
421
|
+
newState.segments = {};
|
|
422
|
+
}
|
|
423
|
+
if ('answers' in config && '' in config.answers) {
|
|
424
|
+
const answers = config.answers;
|
|
425
|
+
const renamed = await this.getTranslation('note-renamed', config.language);
|
|
426
|
+
const oldName = await this.getTranslation('note-old-name', config.language);
|
|
427
|
+
const newName = await this.getTranslation('note-new-name', config.language);
|
|
428
|
+
if ('' in answers && answers['']) {
|
|
429
|
+
for (const rename of answers['']['asset-renaming'] || []) {
|
|
430
|
+
const transfers = [
|
|
431
|
+
{
|
|
432
|
+
reason: 'trade',
|
|
433
|
+
type: rename.type,
|
|
434
|
+
asset: rename.old,
|
|
435
|
+
data: {
|
|
436
|
+
notes: [renamed, oldName]
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
reason: 'trade',
|
|
441
|
+
type: rename.type,
|
|
442
|
+
asset: rename.new,
|
|
443
|
+
data: {
|
|
444
|
+
notes: [renamed, newName]
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
];
|
|
448
|
+
const segment = {
|
|
449
|
+
id: `rename-${rename.type}-${rename.old}-${rename.new}`,
|
|
450
|
+
time: new Date(`${rename.date}T00:00:00.000Z`),
|
|
451
|
+
lines: []
|
|
452
|
+
};
|
|
453
|
+
const td = {
|
|
454
|
+
type: 'transfers',
|
|
455
|
+
transfers
|
|
456
|
+
};
|
|
457
|
+
newState.segments[segment.id] = segment;
|
|
458
|
+
newState.result[segment.id] = [td];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return newState;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Sort the segments by their date.
|
|
466
|
+
* @param segments
|
|
467
|
+
* @returns
|
|
468
|
+
*/
|
|
469
|
+
sortSegments(segments) {
|
|
470
|
+
const time = (entry) => {
|
|
471
|
+
return (typeof entry.time === 'string') ? new Date(entry.time).getTime() : entry.time.getTime();
|
|
472
|
+
};
|
|
473
|
+
return Object.values(segments).sort((a, b) => time(a) - time(b));
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Convert transfers to the actual transactions with account numbers.
|
|
477
|
+
* @param state
|
|
478
|
+
* @param files
|
|
479
|
+
*/
|
|
480
|
+
async analysis(process, state, files, config) {
|
|
481
|
+
// Insert custom segments to the state.
|
|
482
|
+
state = await this.createCustomSegments(state, config);
|
|
483
|
+
this.analyzer = new TransferAnalyzer(this, config, state);
|
|
484
|
+
if (state.result && state.segments) {
|
|
485
|
+
// Sort segments by timestamp and find the first and the last.
|
|
486
|
+
const segments = this.sortSegments(state.segments);
|
|
487
|
+
let firstTimeStamp;
|
|
488
|
+
if (segments.length) {
|
|
489
|
+
// Look for the first and last valid time stamp.
|
|
490
|
+
const confStartDate = config.firstDate ? new Date(`${config.firstDate}T00:00:00.000Z`) : null;
|
|
491
|
+
for (let i = 0; i < segments.length; i++) {
|
|
492
|
+
const segmentTime = typeof segments[i].time === 'string' ? new Date(segments[i].time) : segments[i].time;
|
|
493
|
+
if (!confStartDate || segmentTime >= confStartDate) {
|
|
494
|
+
firstTimeStamp = segmentTime;
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (!firstTimeStamp) {
|
|
499
|
+
throw new Error(`Unable to find any valid time stamps after ${confStartDate}.`);
|
|
500
|
+
}
|
|
501
|
+
await this.analyzer.initialize(firstTimeStamp);
|
|
502
|
+
}
|
|
503
|
+
// Prepare loan account information.
|
|
504
|
+
const debtAccounts = {};
|
|
505
|
+
this.analyzer.getBalances().filter(balance => balance.mayTakeLoan).forEach(balance => {
|
|
506
|
+
debtAccounts[balance.account] = balance;
|
|
507
|
+
});
|
|
508
|
+
// Analyze each segment in chronological order.
|
|
509
|
+
for (const segment of segments) {
|
|
510
|
+
const txDesc = state.result[segment.id];
|
|
511
|
+
if (!txDesc) {
|
|
512
|
+
throw new BadState(`Cannot find results for segment ${segment.id} during analysis (${JSON.stringify(segment)})`);
|
|
513
|
+
}
|
|
514
|
+
for (let i = 0; i < txDesc.length; i++) {
|
|
515
|
+
txDesc[i] = await this.analyze(txDesc[i], segment, config, state, debtAccounts);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const newState = {
|
|
520
|
+
...state,
|
|
521
|
+
stage: 'analyzed'
|
|
522
|
+
};
|
|
523
|
+
this.debugAnalysis(newState);
|
|
524
|
+
return newState;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Analyze and construct transaction details from a transaction description.
|
|
528
|
+
* @param txs
|
|
529
|
+
*/
|
|
530
|
+
async analyze(txs, segment, config, state, debtAccounts) {
|
|
531
|
+
if (!this.analyzer) {
|
|
532
|
+
throw new SystemError('Calling analyze() without setting up analyzer.');
|
|
533
|
+
}
|
|
534
|
+
let result;
|
|
535
|
+
switch (txs.type) {
|
|
536
|
+
case 'transfers':
|
|
537
|
+
result = await this.analyzer.analyze(txs, segment, config);
|
|
538
|
+
return await this.checkForLoan(result, debtAccounts);
|
|
539
|
+
default:
|
|
540
|
+
throw new NotImplemented(`Cannot analyze yet type '${txs.type}' in ${this.constructor.name}.`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Check if the resulting transactions needs to be recorded to loan account.
|
|
545
|
+
*/
|
|
546
|
+
async checkForLoan(result, debtAccounts) {
|
|
547
|
+
if (!this.analyzer)
|
|
548
|
+
throw new Error('No analyzer. Internal error.');
|
|
549
|
+
for (const tx of result.transactions || []) {
|
|
550
|
+
for (const entry of tx.entries) {
|
|
551
|
+
if (entry.account in debtAccounts) {
|
|
552
|
+
// Find loan account if defined.
|
|
553
|
+
const balance = debtAccounts[entry.account];
|
|
554
|
+
const [loanReason, loanType, loanAsset] = balance.debtAddress.split('.');
|
|
555
|
+
const loanAccount = await this.analyzer.getAccount(loanReason, loanType, loanAsset);
|
|
556
|
+
if (balance.account === loanAccount) {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
const accountBalance = this.analyzer.getBalance(balance.address) || 0;
|
|
560
|
+
const debtBalance = this.analyzer.getBalance(balance.debtAddress) || 0;
|
|
561
|
+
// Take more loan.
|
|
562
|
+
if (realNegative(accountBalance) && realNegative(entry.amount)) {
|
|
563
|
+
this.analyzer.revertBalance(entry);
|
|
564
|
+
const originalBalance = this.analyzer.getBalance(balance.address) || 0;
|
|
565
|
+
// Only partial loan needed.
|
|
566
|
+
if (realPositive(originalBalance)) {
|
|
567
|
+
const loanEntry = {
|
|
568
|
+
account: loanAccount || '0',
|
|
569
|
+
amount: -(-entry.amount - originalBalance),
|
|
570
|
+
description: entry.description
|
|
571
|
+
};
|
|
572
|
+
entry.amount = -originalBalance;
|
|
573
|
+
// Add tags if any.
|
|
574
|
+
loanEntry.description = mergeTags(loanEntry.description, await this.analyzer.getTagsForAddr(balance.debtAddress) || []);
|
|
575
|
+
tx.entries.push(loanEntry);
|
|
576
|
+
this.analyzer.applyBalance(entry);
|
|
577
|
+
this.analyzer.applyBalance(loanEntry);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
// Full loan needed.
|
|
581
|
+
entry.account = loanAccount || '0';
|
|
582
|
+
this.analyzer.applyBalance(entry);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
// Pay back loan.
|
|
586
|
+
if (realNegative(debtBalance) && realPositive(entry.amount)) {
|
|
587
|
+
this.analyzer.revertBalance(entry);
|
|
588
|
+
// Getting more than full payment.
|
|
589
|
+
if (less(-debtBalance, entry.amount)) {
|
|
590
|
+
const loanEntry = {
|
|
591
|
+
account: loanAccount || '0',
|
|
592
|
+
amount: -debtBalance,
|
|
593
|
+
description: entry.description
|
|
594
|
+
};
|
|
595
|
+
entry.amount -= -debtBalance;
|
|
596
|
+
// Add tags if any.
|
|
597
|
+
loanEntry.description = mergeTags(loanEntry.description, await this.analyzer.getTagsForAddr(balance.debtAddress) || []);
|
|
598
|
+
tx.entries.push(loanEntry);
|
|
599
|
+
this.analyzer.applyBalance(entry);
|
|
600
|
+
this.analyzer.applyBalance(loanEntry);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
// Partial payment.
|
|
604
|
+
entry.account = loanAccount || '0';
|
|
605
|
+
this.analyzer.applyBalance(entry);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Dump analysis results.
|
|
615
|
+
* @param state
|
|
616
|
+
*/
|
|
617
|
+
debugAnalysis(state) {
|
|
618
|
+
if (state.result !== undefined) {
|
|
619
|
+
Object.keys(state.result).forEach(segmentId => {
|
|
620
|
+
debug('ANALYSIS', `Analyzed ${segmentId}`);
|
|
621
|
+
if (state.result && segmentId in state.result) {
|
|
622
|
+
for (const result of state.result[segmentId]) {
|
|
623
|
+
debug('ANALYSIS', result.transfers);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Apply the result using the connector.
|
|
631
|
+
* @param state
|
|
632
|
+
* @param files
|
|
633
|
+
* @returns
|
|
634
|
+
*/
|
|
635
|
+
async execution(process, state, files) {
|
|
636
|
+
const output = new TransactionApplyResults();
|
|
637
|
+
if (state.result) {
|
|
638
|
+
// Initialize all execution results.
|
|
639
|
+
for (const segmentId of Object.keys(state.result)) {
|
|
640
|
+
const result = state.result[segmentId];
|
|
641
|
+
for (const res of result) {
|
|
642
|
+
if (res.transactions) {
|
|
643
|
+
for (const tx of res.transactions) {
|
|
644
|
+
if (!tx.executionResult) {
|
|
645
|
+
tx.executionResult = 'not done';
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// Apply everything segment by segment.
|
|
652
|
+
for (const segmentId of Object.keys(state.result)) {
|
|
653
|
+
debug('EXECUTION', `Execution of segment ${segmentId}`);
|
|
654
|
+
const result = state.result[segmentId];
|
|
655
|
+
for (const res of result) {
|
|
656
|
+
debug('EXECUTION', res.transactions);
|
|
657
|
+
const hasOld = await this.system.connector.resultExists(process.id, res);
|
|
658
|
+
if (hasOld) {
|
|
659
|
+
const allow = await this.UI.getBoolean(process.config, 'allowIdenticalTx', 'Allow creation of identical transactions that has been already created.');
|
|
660
|
+
if (!allow) {
|
|
661
|
+
for (const tx of res.transactions || []) {
|
|
662
|
+
tx.executionResult = 'duplicate';
|
|
663
|
+
output.duplicate(tx);
|
|
664
|
+
}
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
log(`Duplicate transaction created ${JSON.stringify(res.transactions)} since allowed in settings.`);
|
|
668
|
+
}
|
|
669
|
+
const applied = await this.system.connector.applyResult(process.id, res);
|
|
670
|
+
output.add(applied);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// Remove stock data.
|
|
675
|
+
this.analyzer = null;
|
|
676
|
+
return {
|
|
677
|
+
...state,
|
|
678
|
+
output: output.toJSON(),
|
|
679
|
+
stage: 'executed'
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Ask VAT from connector.
|
|
684
|
+
* @param time
|
|
685
|
+
* @param reason
|
|
686
|
+
* @param asset
|
|
687
|
+
* @param currency
|
|
688
|
+
*/
|
|
689
|
+
async getVAT(time, transfer, currency) {
|
|
690
|
+
const connector = this.system.connector;
|
|
691
|
+
return connector.getVAT(time, transfer, currency);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Find the rate in the default currency for the asset.
|
|
695
|
+
* If there is information about rates inside the files, this function could be overridden and
|
|
696
|
+
* used for digging actual values. Those values can be collected during parse() call.
|
|
697
|
+
* @param time
|
|
698
|
+
* @param type
|
|
699
|
+
* @param asset
|
|
700
|
+
*/
|
|
701
|
+
async getRate(time, type, asset, currency, exchange) {
|
|
702
|
+
if (!isTransactionImportConnector(this.system.connector)) {
|
|
703
|
+
throw new SystemError('Connector used is not a transaction import connector.');
|
|
704
|
+
}
|
|
705
|
+
return this.system.connector.getRate(time, type, asset, currency, exchange);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Remove transactions created.
|
|
709
|
+
*/
|
|
710
|
+
async rollback(process, state) {
|
|
711
|
+
const success = await this.system.connector.rollback(process.id);
|
|
712
|
+
if (!success) {
|
|
713
|
+
throw new SystemError('Rollback failed.');
|
|
714
|
+
}
|
|
715
|
+
if (state.result) {
|
|
716
|
+
for (const segmentId of Object.keys(state.result)) {
|
|
717
|
+
const result = state.result[segmentId];
|
|
718
|
+
for (const res of result) {
|
|
719
|
+
if (res.transactions) {
|
|
720
|
+
for (const tx of res.transactions) {
|
|
721
|
+
tx.executionResult = 'reverted';
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
...state,
|
|
729
|
+
stage: 'rolledback'
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
//# sourceMappingURL=TransactionImportHandler.js.map
|