Compare commits
333 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 422f18443a | |||
| 0fbf185223 | |||
| 91cdffa9a6 | |||
| 89199eed8b | |||
| 1a65bb9db6 | |||
| 9772d9ca62 | |||
| 5ee93a5db1 | |||
| 85c4f686da | |||
| 1f066b0d1e | |||
| 38ddb7aaa3 | |||
| a22931f96b | |||
| dcee067aea | |||
| 302d118ae0 | |||
| 09eea96cdc | |||
| 205dea9e58 | |||
| 089eabb806 | |||
| dd63500202 | |||
| 13488efdaf | |||
| edcf33f49c | |||
| d601e01029 | |||
| 4d7c3650b5 | |||
| a0fd468309 | |||
| 0b7471879d | |||
| 282b74c95e | |||
| 5ce1dc973c | |||
| 7ac1e0b69f | |||
| 127bed1026 | |||
| d517a1862b | |||
| 8e5202b375 | |||
| 301fb58917 | |||
| aedebb1461 | |||
| 1336377598 | |||
| 3b58dcbc4d | |||
| 23a5f0a96f | |||
| b81d2ec63c | |||
| cabe365907 | |||
| 54f61ecb18 | |||
| 404cd62d7b | |||
| f0f3143605 | |||
| b729fdedca | |||
| 973cec2c6a | |||
| 6e61aba050 | |||
| 40a8deba12 | |||
| 0ba762ba6e | |||
| 732c256db2 | |||
| d2ce801277 | |||
| 4845fdedfd | |||
| f5a7e2e2d6 | |||
| a84f48ae8a | |||
| c4c9503e31 | |||
| 8c1f499ed8 | |||
| c6eb3cfb74 | |||
| d7a0d253c4 | |||
| 9d275a3051 | |||
| 8192a48bc5 | |||
| 247181830c | |||
| d5dfdc8c05 | |||
| d95fcd8b00 | |||
| 40a366e68d | |||
| 593ae10783 | |||
| 75d9e11bab | |||
| 6d37d42e50 | |||
| f9e9c9285f | |||
| 314bf876f2 | |||
| 61c52cc888 | |||
| b42f226aba | |||
| 767b841866 | |||
| fd08666f49 | |||
| eb662681a1 | |||
| ef15eccc33 | |||
| e0286ff133 | |||
| 2baffe3f11 | |||
| 196657ee86 | |||
| b4c4aafc99 | |||
| b907a79223 | |||
| 0d213de580 | |||
| 2e97d699e7 | |||
| 22e4738b7a | |||
| 4b68641043 | |||
| 3a66a3d655 | |||
| 76d1d3aef3 | |||
| fe2aa5d28b | |||
| f474bbf09a | |||
| c4d02db879 | |||
| 75b36ec547 | |||
| 43b7aea76e | |||
| 13a4a47d40 | |||
| fd9f380922 | |||
| a5fdb9d6b7 | |||
| 983c65e4f8 | |||
| fa568056d3 | |||
| ea8b2812d4 | |||
| b6a2aea8fd | |||
| fa047bf303 | |||
| 4177ac3d46 | |||
| 7647f4f5b9 | |||
| bab03dbde1 | |||
| 85db6e96af | |||
| 548461ade0 | |||
| ecbf182173 | |||
| ab38d33e31 | |||
| 0020f4ede9 | |||
| b470cb63b7 | |||
| 32f2eaef3c | |||
| a7fc3c78eb | |||
| d42b3ecb5e | |||
| 2d4a603d11 | |||
| 7a369328b6 | |||
| 545667e502 | |||
| 8387a81a59 | |||
| 1e3087ccf0 | |||
| bee7772bfd | |||
| a8b6f72ee6 | |||
| 9484cf514d | |||
| 70958c00d3 | |||
| 9467335536 | |||
| f916fdff06 | |||
| ced346506e | |||
| e0cd96f87e | |||
| ed7e906903 | |||
| 3bb7f5abf4 | |||
| 5d801a2343 | |||
| 0d9e59dad9 | |||
| 5fd1396b5c | |||
| b3b9d9293b | |||
| 8b405e513f | |||
| 2bfcfbf03d | |||
| 10388a5ffa | |||
| a127a381cc | |||
| 4aa0dc20af | |||
| 012cc04107 | |||
| 25a84ad3af | |||
| c0036d230a | |||
| 869970a4ab | |||
| 42f8aa410c | |||
| 80e1223505 | |||
| fc9581580c | |||
| b0e6764bfe | |||
| 03fef81414 | |||
| 8dcb8648a5 | |||
| 50b4c96a99 | |||
| c9b894fdbe | |||
| a2f1d944ad | |||
| be4ec2bcce | |||
| c1a728c391 | |||
| 46ff0ecd3b | |||
| 8db69f64c8 | |||
| 8447dd7ae6 | |||
| 543cc5f656 | |||
| 9664bac47f | |||
| 42ae323568 | |||
| a357fb8136 | |||
| 3b487ca0d9 | |||
| 91e98f3126 | |||
| 7ecacaeb05 | |||
| 598ae9fa06 | |||
| 0803a5930f | |||
| 9aaf3284c0 | |||
| b27f9c12de | |||
| a730ebab8f | |||
| f7d0e2279a | |||
| e41dd1c1f8 | |||
| 98274ab864 | |||
| a3261acc82 | |||
| 7d9cfc4ced | |||
| b556efa510 | |||
| 4b72bfd76d | |||
| 0f532094ca | |||
| 7e48cca4ab | |||
| 98aa535193 | |||
| 48ef9acc19 | |||
| e304f4d3fa | |||
| 83a34ae322 | |||
| 43a6d1be0f | |||
| 89fb8a099e | |||
| 853b0d430e | |||
| 88b63d0222 | |||
| 618ad4cac2 | |||
| 9b4dd5600a | |||
| ca959fb9ce | |||
| ee9b281919 | |||
| 1a0630846d | |||
| 9585cbc8a9 | |||
| 19c0ca8191 | |||
| 3b0b95ac4a | |||
| 1691c320cc | |||
| caf88a9488 | |||
| b295b99d3d | |||
| 3cf1276fa7 | |||
| 5ae763273a | |||
| e39965e7b5 | |||
| af36fe9212 | |||
| 6eb7fa27f6 | |||
| 0dd0597c3b | |||
| f0a74a6108 | |||
| 6829eddde5 | |||
| 1c596c4a15 | |||
| ab88b0bf44 | |||
| d462d0164c | |||
| d4d1342c70 | |||
| a157c1961a | |||
| 9a037ace5a | |||
| c64b4502cb | |||
| dc41bf8e10 | |||
| 0ce66d9070 | |||
| 6e369f39a4 | |||
| fb25f589fb | |||
| 8651755d7a | |||
| 277da30339 | |||
| 2fb509beb2 | |||
| 6634d5b791 | |||
| 41739d97e7 | |||
| 43bc04012d | |||
| 43154832b6 | |||
| 91a00cb5b3 | |||
| 526d7e50ec | |||
| cc0996e0d2 | |||
| 8be5e8aa1d | |||
| 022dd3303b | |||
| 2865635013 | |||
| c276f261f9 | |||
| ee7e98bb00 | |||
| 554ce37475 | |||
| 1938d972ff | |||
| 630859bc25 | |||
| 8ea8a9fe2a | |||
| f5e4d82efc | |||
| 958515b9e0 | |||
| 5131e3d6e3 | |||
| b5a18c86dc | |||
| 2f3e26dbe5 | |||
| 3313ccf051 | |||
| 2ada077b38 | |||
| 31c36f0edf | |||
| e74d290016 | |||
| 28337ae228 | |||
| e252378898 | |||
| 1cc0cd7ae6 | |||
| 088e9a339d | |||
| b009c7b6e5 | |||
| 6bb69b0c27 | |||
| 842683da25 | |||
| d39816bb9f | |||
| e856aefd7b | |||
| f54c4998ef | |||
| 59a138d417 | |||
| 0dc2825e5d | |||
| 76af5d946a | |||
| c35cbbda15 | |||
| ece58b60ec | |||
| d95e34a597 | |||
| a09d7b57f9 | |||
| a535fbcef1 | |||
| 931d5e8395 | |||
| b37450db15 | |||
| 2dd7fd30de | |||
| fb55cd1b33 | |||
| e9b4392163 | |||
| 861e4c036b | |||
| e825323bb0 | |||
| aebd65449b | |||
| 0a8f62741a | |||
| b1cefa5a34 | |||
| a12038e40c | |||
| b2fab42170 | |||
| e9c3001c28 | |||
| 44039438e0 | |||
| 372ea29edd | |||
| 1eb958a21b | |||
| 89dd306bb4 | |||
| c170cb42e6 | |||
| 78f3beaf2f | |||
| 482d025c90 | |||
| 11c943efef | |||
| 6debea6dbb | |||
| a7a8b9a2fb | |||
| 47cc046e60 | |||
| 5a9f4ec3b4 | |||
| 70aa19c623 | |||
| d2771f6fa9 | |||
| 5e4637c6ad | |||
| a9a7d28082 | |||
| ffce01c612 | |||
| 7a3ec9468f | |||
| 5850e0e298 | |||
| fdf6548cc9 | |||
| ee8aa2bb8e | |||
| eccea273e6 | |||
| e143c8f098 | |||
| 81226c3bb2 | |||
| c3db8cee2d | |||
| 9061fc3188 | |||
| b9539c4aba | |||
| deabe178df | |||
| a5320cf929 | |||
| 79842d9171 | |||
| 3daff44155 | |||
| cd2cce4268 | |||
| ad132d5637 | |||
| c8b3daa915 | |||
| 96561ec2be | |||
| 608411feab | |||
| 516e3a5613 | |||
| 2431152cec | |||
| 17f604b6aa | |||
| 3fe51dce63 | |||
| 2c454f001e | |||
| 4781cb34eb | |||
| 9faea14e36 | |||
| bd704a8c15 | |||
| bb9a19bcb2 | |||
| 9ff1334584 | |||
| de27c8e6c5 | |||
| ba278e47ff | |||
| 7d70859107 | |||
| 6430a52027 | |||
| 45be96cf68 | |||
| 837a62a534 | |||
| 707283fd66 | |||
| 10b9c09192 | |||
| ed8c5c96ac | |||
| 3ba91c590e | |||
| 44dc45de51 | |||
| 83bd68e7f4 | |||
| dafbc115c4 | |||
| 5de1e32cd8 | |||
| 8ae5c1ea99 | |||
| 29651f674a | |||
| b1dff5ef51 | |||
| bb0971ea17 | |||
| 8a020b666c | |||
| 8b34750426 | |||
| 32cb2b2354 |
@@ -3,13 +3,13 @@ name: Build backend file for windows
|
||||
inputs:
|
||||
go-version:
|
||||
required: false
|
||||
default: "1.25.3"
|
||||
default: "1.25.7"
|
||||
mingw-version:
|
||||
required: false
|
||||
default: "14.2.0"
|
||||
default: "15.2.0"
|
||||
mingw-revison:
|
||||
required: false
|
||||
default: "v12-rev2"
|
||||
default: "v13-rev1"
|
||||
release-build:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
+14
@@ -147,3 +147,17 @@ dist/
|
||||
|
||||
# Roo Code
|
||||
.roo/
|
||||
|
||||
# Binary and build files
|
||||
ezbookkeeping
|
||||
!**/ezbookkeeping/
|
||||
package/
|
||||
|
||||
# Environment variable files
|
||||
.env
|
||||
**/.env
|
||||
|
||||
# Other directories
|
||||
data/
|
||||
storage/
|
||||
log/
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
# Build backend binary file
|
||||
FROM golang:1.25.3-alpine3.22 AS be-builder
|
||||
FROM golang:1.25.7-alpine3.23 AS be-builder
|
||||
ARG RELEASE_BUILD
|
||||
ARG BUILD_PIPELINE
|
||||
ARG BUILD_UNIXTIME
|
||||
@@ -19,7 +19,7 @@ RUN apk add git gcc g++ libc-dev
|
||||
RUN ./build.sh backend
|
||||
|
||||
# Build frontend files
|
||||
FROM --platform=$BUILDPLATFORM node:24.10.0-alpine3.22 AS fe-builder
|
||||
FROM --platform=$BUILDPLATFORM node:24.14.0-alpine3.23 AS fe-builder
|
||||
ARG RELEASE_BUILD
|
||||
ARG BUILD_PIPELINE
|
||||
ARG BUILD_UNIXTIME
|
||||
@@ -35,7 +35,7 @@ RUN apk add git
|
||||
RUN ./build.sh frontend
|
||||
|
||||
# Package docker image
|
||||
FROM alpine:3.22.2
|
||||
FROM alpine:3.23.3
|
||||
LABEL maintainer="MaysWind <i@mayswind.net>"
|
||||
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
|
||||
RUN apk --no-cache add tzdata
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2025 MaysWind (i@mayswind.net)
|
||||
Copyright (c) 2020-2026 MaysWind (i@mayswind.net)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[](https://trendshift.io/repositories/12917)
|
||||
|
||||
## Introduction
|
||||
ezBookkeeping is a lightweight, self-hosted personal finance app with a user-friendly interface and powerful bookkeeping features. It's easy to deploy, and you can start it with just one single Docker command. Designed to be resource-efficient and highly scalable, it can run smoothly on devices as small as a Raspberry Pi, or scale up to NAS, MicroServers, and even large cluster environments.
|
||||
ezBookkeeping is a lightweight, self-hosted personal finance app with a user-friendly interface and powerful bookkeeping features. It helps you record daily transactions, import data from various sources, and quickly search and filter your bills. You can analyze historical data using built-in charts or perform custom queries with your own chart dimensions to better understand spending patterns and financial trends. ezBookkeeping is easy to deploy, and you can start it with just one single Docker command. Designed to be resource-efficient, it runs smoothly on devices such as Raspberry Pi, NAS, and MicroServers.
|
||||
|
||||
ezBookkeeping offers tailored interfaces for both mobile and desktop devices. With support for PWA (Progressive Web Apps), you can even [add it to your mobile home screen](https://raw.githubusercontent.com/wiki/mayswind/ezbookkeeping/img/mobile/add_to_home_screen.gif) and use it like a native app.
|
||||
|
||||
@@ -21,9 +21,9 @@ Live Demo: [https://ezbookkeeping-demo.mayswind.net](https://ezbookkeeping-demo.
|
||||
- **Open Source & Self-Hosted**
|
||||
- Built for privacy and control
|
||||
- **Lightweight & Fast**
|
||||
- Optimized for performance, runs smoothly even on low-resource environments
|
||||
- Minimal resource usage, runs smoothly even on low-resource devices
|
||||
- **Easy Installation**
|
||||
- Docker-ready
|
||||
- Docker support
|
||||
- Supports SQLite, MySQL, PostgreSQL
|
||||
- Cross-platform (Windows, macOS, Linux)
|
||||
- Works on x86, amd64, ARM architectures
|
||||
@@ -33,24 +33,28 @@ Live Demo: [https://ezbookkeeping-demo.mayswind.net](https://ezbookkeeping-demo.
|
||||
- Dark mode
|
||||
- **AI-Powered Features**
|
||||
- Receipt image recognition
|
||||
- Supports MCP (Model Context Protocol) for AI integration
|
||||
- MCP (Model Context Protocol) support for AI integration
|
||||
- API command-line script tools for AI integration
|
||||
- **Powerful Bookkeeping**
|
||||
- Two-level accounts and categories
|
||||
- Attach images to transactions
|
||||
- Image attachments for transactions
|
||||
- Location tracking with maps
|
||||
- Recurring transactions
|
||||
- Advanced filtering, search, visualization, and analysis
|
||||
- **Localization & Globalization**
|
||||
- Scheduled transactions
|
||||
- Advanced filtering, search, visualization and analysis
|
||||
- **Localization & Internationalization**
|
||||
- Multi-language and multi-currency support
|
||||
- Automatic exchange rates
|
||||
- Multi-timezone awareness
|
||||
- Custom formats for dates, numbers, and currencies
|
||||
- Multiple exchange rate sources with automatic updates
|
||||
- Multi-timezone support
|
||||
- Custom formats for dates, numbers and currencies
|
||||
- **Security**
|
||||
- Two-factor authentication (2FA)
|
||||
- OIDC external authentication
|
||||
- Login rate limiting
|
||||
- Application lock (PIN code / WebAuthn)
|
||||
- **Data Import/Export**
|
||||
- Supports CSV, OFX, QFX, QIF, IIF, Camt.053, MT940, GnuCash, Firefly III, Beancount, and more
|
||||
- **Data Import & Export**
|
||||
- Supports CSV, OFX, QFX, QIF, IIF, Camt.052, Camt.053, MT940, GnuCash, Firefly III, Beancount and more
|
||||
|
||||
For a full list of features, visit the [Full Feature List](https://ezbookkeeping.mayswind.net/comparison/).
|
||||
|
||||
## Screenshots
|
||||
### Desktop Version
|
||||
@@ -112,16 +116,16 @@ You can also build a Docker image. Make sure you have [Docker](https://www.docke
|
||||
## Contributing
|
||||
We welcome contributions of all kinds.
|
||||
|
||||
Found a bug? [Submit an issue](https://github.com/mayswind/ezbookkeeping/issues)
|
||||
If you find a bug, please [submit an issue](https://github.com/mayswind/ezbookkeeping/issues) on GitHub.
|
||||
|
||||
Want to contribute code? Feel free to fork and send a pull request.
|
||||
If you would like to contribute code, you can fork the repository and open a pull request.
|
||||
|
||||
Contributions of all kinds — bug reports, feature suggestions, documentation improvements, or code — are highly appreciated.
|
||||
Improvements to documentation, feature suggestions, and other forms of feedback are also appreciated.
|
||||
|
||||
Check out our [Contributor Graph](https://github.com/mayswind/ezbookkeeping/graphs/contributors) to see the amazing people who've already helped.
|
||||
You can view existing contributors on the [Contributor Graph](https://github.com/mayswind/ezbookkeeping/graphs/contributors).
|
||||
|
||||
## Translating
|
||||
Help make ezBookkeeping accessible to users around the world. If you want to contribute a translation, please refer to our [translation guide](https://ezbookkeeping.mayswind.net/translating).
|
||||
Help make ezBookkeeping accessible to users around the world. We welcome help to improve existing translations or add new ones. If you would like to contribute a translation, please refer to the [translation guide](https://ezbookkeeping.mayswind.net/translating).
|
||||
|
||||
Currently available translations:
|
||||
|
||||
@@ -129,22 +133,24 @@ Currently available translations:
|
||||
| --- | --- | --- |
|
||||
| de | Deutsch | [@chrgm](https://github.com/chrgm) |
|
||||
| en | English | / |
|
||||
| es | Español | [@Miguelonlonlon](https://github.com/Miguelonlonlon) |
|
||||
| es | Español | [@Miguelonlonlon](https://github.com/Miguelonlonlon), [@abrugues](https://github.com/abrugues), [@AndresTeller](https://github.com/AndresTeller), [@diegofercri](https://github.com/diegofercri) |
|
||||
| fr | Français | [@brieucdlf](https://github.com/brieucdlf) |
|
||||
| it | Italiano | [@waron97](https://github.com/waron97) |
|
||||
| ja | 日本語 | [@tkymmm](https://github.com/tkymmm) |
|
||||
| kn | ಕನ್ನಡ | [@Darshanbm05](https://github.com/Darshanbm05) |
|
||||
| ko | 한국어 | [@overworks](https://github.com/overworks) |
|
||||
| nl | Nederlands | [@automagic](https://github.com/automagics) |
|
||||
| pt-BR | Português (Brasil) | [@thecodergus](https://github.com/thecodergus) |
|
||||
| ru | Русский | [@artegoser](https://github.com/artegoser) |
|
||||
| nl | Nederlands | [@automagics](https://github.com/automagics) |
|
||||
| pt-BR | Português (Brasil) | [@thecodergus](https://github.com/thecodergus), [@balaios](https://github.com/balaios) |
|
||||
| ru | Русский | [@artegoser](https://github.com/artegoser), [@dshemin](https://github.com/dshemin) |
|
||||
| sl | Slovenščina | [@thehijacker](https://github.com/thehijacker) |
|
||||
| ta | தமிழ் | [@hhharsha36](https://github.com/hhharsha36) |
|
||||
| th | ไทย | [@natthavat28](https://github.com/natthavat28) |
|
||||
| tr | Türkçe | [@aydnykn](https://github.com/aydnykn) |
|
||||
| uk | Українська | [@nktlitvinenko](https://github.com/nktlitvinenko) |
|
||||
| vi | Tiếng Việt | [@f97](https://github.com/f97) |
|
||||
| zh-Hans | 中文 (简体) | / |
|
||||
| zh-Hant | 中文 (繁體) | / |
|
||||
|
||||
Don't see your language? Help us add it.
|
||||
|
||||
## Documentation
|
||||
1. [English](https://ezbookkeeping.mayswind.net)
|
||||
1. [中文 (简体)](https://ezbookkeeping.mayswind.net/zh_Hans)
|
||||
|
||||
@@ -101,6 +101,14 @@ func updateAllDatabaseTablesStructure(c *core.CliContext) error {
|
||||
|
||||
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] transaction category table maintained successfully")
|
||||
|
||||
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionTagGroup))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] transaction tag group table maintained successfully")
|
||||
|
||||
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionTag))
|
||||
|
||||
if err != nil {
|
||||
@@ -157,5 +165,13 @@ func updateAllDatabaseTablesStructure(c *core.CliContext) error {
|
||||
|
||||
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] user external auth table maintained successfully")
|
||||
|
||||
err = datastore.Container.UserDataStore.SyncStructs(new(models.InsightsExplorer))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] insights explorer table maintained successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -195,9 +195,25 @@ func getConfigWithoutSensitiveData(config *settings.Config) *settings.Config {
|
||||
clonedConfig.ReceiptImageRecognitionLLMConfig.OpenAICompatibleAPIKey = "****"
|
||||
}
|
||||
|
||||
if clonedConfig.ReceiptImageRecognitionLLMConfig.AnthropicCompatibleAPIKey != "" {
|
||||
clonedConfig.ReceiptImageRecognitionLLMConfig.AnthropicCompatibleAPIKey = "****"
|
||||
}
|
||||
|
||||
if clonedConfig.ReceiptImageRecognitionLLMConfig.AnthropicAPIKey != "" {
|
||||
clonedConfig.ReceiptImageRecognitionLLMConfig.AnthropicAPIKey = "****"
|
||||
}
|
||||
|
||||
if clonedConfig.ReceiptImageRecognitionLLMConfig.OpenRouterAPIKey != "" {
|
||||
clonedConfig.ReceiptImageRecognitionLLMConfig.OpenRouterAPIKey = "****"
|
||||
}
|
||||
|
||||
if clonedConfig.ReceiptImageRecognitionLLMConfig.LMStudioToken != "" {
|
||||
clonedConfig.ReceiptImageRecognitionLLMConfig.LMStudioToken = "****"
|
||||
}
|
||||
|
||||
if clonedConfig.ReceiptImageRecognitionLLMConfig.GoogleAIAPIKey != "" {
|
||||
clonedConfig.ReceiptImageRecognitionLLMConfig.GoogleAIAPIKey = "****"
|
||||
}
|
||||
}
|
||||
|
||||
if clonedConfig.OAuth2ClientSecret != "" {
|
||||
|
||||
+21
-1
@@ -116,6 +116,7 @@ func startWebServer(c *core.CliContext) error {
|
||||
_ = v.RegisterValidation("validCurrency", validators.ValidCurrency)
|
||||
_ = v.RegisterValidation("validHexRGBColor", validators.ValidHexRGBColor)
|
||||
_ = v.RegisterValidation("validAmountFilter", validators.ValidAmountFilter)
|
||||
_ = v.RegisterValidation("validTagFilter", validators.ValidTagFilter)
|
||||
_ = v.RegisterValidation("validFiscalYearStart", validators.ValidateFiscalYearStart)
|
||||
}
|
||||
|
||||
@@ -315,6 +316,7 @@ func startWebServer(c *core.CliContext) error {
|
||||
|
||||
apiV1Route := apiRoute.Group("/v1")
|
||||
apiV1Route.Use(bindMiddleware(middlewares.JWTAuthorization(config)))
|
||||
apiV1Route.Use(bindMiddleware(middlewares.APITokenIpLimit(config)))
|
||||
{
|
||||
// Tokens
|
||||
apiV1Route.GET("/tokens/list.json", bindApi(api.Tokens.TokenListHandler))
|
||||
@@ -382,6 +384,7 @@ func startWebServer(c *core.CliContext) error {
|
||||
apiV1Route.GET("/transactions/count.json", bindApi(api.Transactions.TransactionCountHandler))
|
||||
apiV1Route.GET("/transactions/list.json", bindApi(api.Transactions.TransactionListHandler))
|
||||
apiV1Route.GET("/transactions/list/by_month.json", bindApi(api.Transactions.TransactionMonthListHandler))
|
||||
apiV1Route.GET("/transactions/list/all.json", bindApi(api.Transactions.TransactionListAllHandler))
|
||||
apiV1Route.GET("/transactions/reconciliation_statements.json", bindApi(api.Transactions.TransactionReconciliationStatementHandler))
|
||||
apiV1Route.GET("/transactions/statistics.json", bindApi(api.Transactions.TransactionStatisticsHandler))
|
||||
apiV1Route.GET("/transactions/statistics/trends.json", bindApi(api.Transactions.TransactionStatisticsTrendsHandler))
|
||||
@@ -394,7 +397,7 @@ func startWebServer(c *core.CliContext) error {
|
||||
apiV1Route.POST("/transactions/delete.json", bindApi(api.Transactions.TransactionDeleteHandler))
|
||||
|
||||
if config.EnableDataImport {
|
||||
apiV1Route.POST("/transactions/parse_dsv_file.json", bindApi(api.Transactions.TransactionParseImportDsvFileDataHandler))
|
||||
apiV1Route.POST("/transactions/parse_custom_file.json", bindApi(api.Transactions.TransactionParseImportCustomFileDataHandler))
|
||||
apiV1Route.POST("/transactions/parse_import.json", bindApi(api.Transactions.TransactionParseImportFileHandler))
|
||||
apiV1Route.POST("/transactions/import.json", bindApi(api.Transactions.TransactionImportHandler))
|
||||
apiV1Route.GET("/transactions/import/process.json", bindApi(api.Transactions.TransactionImportProcessHandler))
|
||||
@@ -416,6 +419,14 @@ func startWebServer(c *core.CliContext) error {
|
||||
apiV1Route.POST("/transaction/categories/move.json", bindApi(api.TransactionCategories.CategoryMoveHandler))
|
||||
apiV1Route.POST("/transaction/categories/delete.json", bindApi(api.TransactionCategories.CategoryDeleteHandler))
|
||||
|
||||
// Transaction Tag Groups
|
||||
apiV1Route.GET("/transaction/tags/groups/list.json", bindApi(api.TransactionTagGroups.TagGroupListHandler))
|
||||
apiV1Route.GET("/transaction/tags/groups/get.json", bindApi(api.TransactionTagGroups.TagGroupGetHandler))
|
||||
apiV1Route.POST("/transaction/tags/groups/add.json", bindApi(api.TransactionTagGroups.TagGroupCreateHandler))
|
||||
apiV1Route.POST("/transaction/tags/groups/modify.json", bindApi(api.TransactionTagGroups.TagGroupModifyHandler))
|
||||
apiV1Route.POST("/transaction/tags/groups/move.json", bindApi(api.TransactionTagGroups.TagGroupMoveHandler))
|
||||
apiV1Route.POST("/transaction/tags/groups/delete.json", bindApi(api.TransactionTagGroups.TagGroupDeleteHandler))
|
||||
|
||||
// Transaction Tags
|
||||
apiV1Route.GET("/transaction/tags/list.json", bindApi(api.TransactionTags.TagListHandler))
|
||||
apiV1Route.GET("/transaction/tags/get.json", bindApi(api.TransactionTags.TagGetHandler))
|
||||
@@ -435,6 +446,15 @@ func startWebServer(c *core.CliContext) error {
|
||||
apiV1Route.POST("/transaction/templates/move.json", bindApi(api.TransactionTemplates.TemplateMoveHandler))
|
||||
apiV1Route.POST("/transaction/templates/delete.json", bindApi(api.TransactionTemplates.TemplateDeleteHandler))
|
||||
|
||||
// Insights Explorers
|
||||
apiV1Route.GET("/insights/explorers/list.json", bindApi(api.InsightsExplorers.InsightsExplorerListHandler))
|
||||
apiV1Route.GET("/insights/explorers/get.json", bindApi(api.InsightsExplorers.InsightsExplorerGetHandler))
|
||||
apiV1Route.POST("/insights/explorers/add.json", bindApi(api.InsightsExplorers.InsightsExplorerCreateHandler))
|
||||
apiV1Route.POST("/insights/explorers/modify.json", bindApi(api.InsightsExplorers.InsightsExplorerModifyHandler))
|
||||
apiV1Route.POST("/insights/explorers/hide.json", bindApi(api.InsightsExplorers.InsightsExplorerHideHandler))
|
||||
apiV1Route.POST("/insights/explorers/move.json", bindApi(api.InsightsExplorers.InsightsExplorerMoveHandler))
|
||||
apiV1Route.POST("/insights/explorers/delete.json", bindApi(api.InsightsExplorers.InsightsExplorerDeleteHandler))
|
||||
|
||||
// Large Language Models
|
||||
if config.ReceiptImageRecognitionLLMConfig != nil && config.ReceiptImageRecognitionLLMConfig.LLMProvider != "" {
|
||||
if config.TransactionFromAIImageRecognition {
|
||||
|
||||
+38
-7
@@ -1,7 +1,4 @@
|
||||
[global]
|
||||
# Application instance name
|
||||
app_name = ezBookkeeping
|
||||
|
||||
# Either "production", "development"
|
||||
mode = production
|
||||
|
||||
@@ -18,7 +15,7 @@ http_port = 8080
|
||||
# The domain name used to access ezBookkeeping
|
||||
domain = localhost
|
||||
|
||||
# The full url used to access ezBookkeeping in browser
|
||||
# The full url used to access ezBookkeeping in browser, supports placeholders: %(protocol)s, %(domain)s, %(http_port)s
|
||||
root_url = %(protocol)s://%(domain)s:%(http_port)s/
|
||||
|
||||
# https certification and its key file
|
||||
@@ -172,7 +169,7 @@ transaction_from_ai_image_recognition = false
|
||||
max_ai_recognition_picture_size = 10485760
|
||||
|
||||
[llm_image_recognition]
|
||||
# Large Language Model (LLM) provider for receipt image recognition, supports the following types: "openai", "openai_compatible", "openrouter", "ollama", "google_ai"
|
||||
# Large Language Model (LLM) provider for receipt image recognition, supports the following types: "openai", "openai_compatible", "anthropic", "anthropic_compatible", "openrouter", "ollama", "lm_studio", "google_ai"
|
||||
llm_provider =
|
||||
|
||||
# For "openai" llm provider only, OpenAI API secret key, please visit https://platform.openai.com/api-keys for more information
|
||||
@@ -190,6 +187,30 @@ openai_compatible_api_key =
|
||||
# For "openai_compatible" llm provider only, receipt image recognition model for creating transactions from images
|
||||
openai_compatible_model_id =
|
||||
|
||||
# For "anthropic" llm provider only, Anthropic API key, please visit https://platform.claude.com/settings/keys for more information
|
||||
anthropic_api_key =
|
||||
|
||||
# For "anthropic" llm provider only, receipt image recognition model for creating transactions from images
|
||||
anthropic_model_id =
|
||||
|
||||
# For "anthropic" llm provider only, maximum allowed number of generated tokens for creating transactions from images, default is 1024
|
||||
anthropic_max_tokens = 1024
|
||||
|
||||
# For "anthropic_compatible" llm provider only, Anthropic compatible API base url, e.g. "https://api.anthropic.com/v1/"
|
||||
anthropic_compatible_base_url =
|
||||
|
||||
# For "anthropic_compatible" llm provider only, Anthropic compatible API version, e.g. "2023-06-01". If the LLM service does not require API versioning, leave it blank
|
||||
anthropic_compatible_api_version =
|
||||
|
||||
# For "anthropic_compatible" llm provider only, Anthropic compatible API secret key
|
||||
anthropic_compatible_api_key =
|
||||
|
||||
# For "anthropic_compatible" llm provider only, receipt image recognition model for creating transactions from images
|
||||
anthropic_compatible_model_id =
|
||||
|
||||
# For "anthropic_compatible" llm provider only, maximum allowed number of generated tokens for creating transactions from images, default is 1024
|
||||
anthropic_compatible_max_tokens = 1024
|
||||
|
||||
# For "openrouter" llm provider only, OpenRouter API key, please visit https://openrouter.ai/settings/keys for more information
|
||||
openrouter_api_key =
|
||||
|
||||
@@ -202,6 +223,15 @@ ollama_server_url =
|
||||
# For "ollama" llm provider only, receipt image recognition model for creating transactions from images
|
||||
ollama_model_id =
|
||||
|
||||
# For "lm_studio" llm provider only, LM Studio server url, e.g. "http://127.0.0.1:1234/"
|
||||
lm_studio_server_url =
|
||||
|
||||
# For "lm_studio" llm provider only, LM Studio API token, if "require authentication" is not enabled in LM Studio, leave it blank
|
||||
lm_studio_token =
|
||||
|
||||
# For "lm_studio" llm provider only, receipt image recognition model for creating transactions from images
|
||||
lm_studio_model_id =
|
||||
|
||||
# For "google_ai" llm provider only, Google AI Studio API key, please visit https://aistudio.google.com/apikey for more information
|
||||
google_ai_api_key =
|
||||
|
||||
@@ -266,6 +296,9 @@ password_reset_token_expired_time = 3600
|
||||
# Set to true to enable API token generation
|
||||
enable_api_token = false
|
||||
|
||||
# Allowed remote IPs for using the API token, a comma-separated list of allowed remote IPs (asterisk * for any addresses, e.g. 192.168.1.* means any IPs in the 192.168.1.x subnet), leave blank to allow all remote IPs
|
||||
api_token_allowed_remote_ips =
|
||||
|
||||
# Maximum count of password / token check failures (0 - 4294967295) per IP per minute (use the above duplicate checker), default is 5, set to 0 to disable
|
||||
max_failures_per_ip_per_minute = 5
|
||||
|
||||
@@ -496,7 +529,6 @@ custom_map_tile_server_default_zoom_level = 14
|
||||
|
||||
[exchange_rates]
|
||||
# Exchange rates data source, supports the following types:
|
||||
# "reserve_bank_of_australia": https://www.rba.gov.au/statistics/frequency/exchange-rates.html
|
||||
# "bank_of_canada": https://www.bankofcanada.ca/rates/exchange/daily-exchange-rates/
|
||||
# "czech_national_bank": https://www.cnb.cz/en/financial-markets/foreign-exchange-market/central-bank-exchange-rate-fixing/central-bank-exchange-rate-fixing/
|
||||
# "danmarks_national_bank": https://www.nationalbanken.dk/en/what-we-do/stable-prices-monetary-policy-and-the-danish-economy/exchange-rates
|
||||
@@ -512,7 +544,6 @@ custom_map_tile_server_default_zoom_level = 14
|
||||
# "swiss_national_bank": https://www.snb.ch/en/the-snb/mandates-goals/statistics/statistics-pub/current_interest_exchange_rates
|
||||
# "national_bank_of_ukraine": https://bank.gov.ua/ua/markets/exchangerates
|
||||
# "central_bank_of_uzbekistan": https://cbu.uz/en/arkhiv-kursov-valyut/
|
||||
# "international_monetary_fund": https://www.imf.org/external/np/fin/data/param_rms_mth.aspx
|
||||
# "user_custom": users set their own exchange rates data in the UI
|
||||
data_source = euro_central_bank
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"code": [
|
||||
"jiangshengwu",
|
||||
"vigdail",
|
||||
"f97",
|
||||
"Miguelonlonlon",
|
||||
"seb26",
|
||||
"nktlitvinenko",
|
||||
"lvdou-bing",
|
||||
"dshemin",
|
||||
"lucdsouza",
|
||||
"OuIChien",
|
||||
"RasterCrow"
|
||||
],
|
||||
"translators": {
|
||||
"de": [
|
||||
"chrgm"
|
||||
],
|
||||
"en": [],
|
||||
"es": [
|
||||
"Miguelonlonlon",
|
||||
"abrugues",
|
||||
"AndresTeller",
|
||||
"diegofercri"
|
||||
],
|
||||
"fr": [
|
||||
"brieucdlf"
|
||||
],
|
||||
"it": [
|
||||
"waron97"
|
||||
],
|
||||
"ja": [
|
||||
"tkymmm"
|
||||
],
|
||||
"kn": [
|
||||
"Darshanbm05"
|
||||
],
|
||||
"ko": [
|
||||
"overworks"
|
||||
],
|
||||
"nl": [
|
||||
"automagics"
|
||||
],
|
||||
"pt-BR": [
|
||||
"thecodergus",
|
||||
"balaios"
|
||||
],
|
||||
"ru": [
|
||||
"artegoser",
|
||||
"dshemin"
|
||||
],
|
||||
"sl": [
|
||||
"thehijacker"
|
||||
],
|
||||
"ta": [
|
||||
"hhharsha36"
|
||||
],
|
||||
"th": [
|
||||
"natthavat28"
|
||||
],
|
||||
"tr": [
|
||||
"aydnykn"
|
||||
],
|
||||
"uk": [
|
||||
"nktlitvinenko"
|
||||
],
|
||||
"vi": [
|
||||
"f97"
|
||||
],
|
||||
"zh-Hans": [],
|
||||
"zh-Hant": []
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/cmd"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -26,9 +26,9 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
settings.Version = Version
|
||||
settings.CommitHash = CommitHash
|
||||
settings.BuildTime = BuildUnixTime
|
||||
core.Version = Version
|
||||
core.CommitHash = CommitHash
|
||||
core.BuildTime = BuildUnixTime
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: "ezBookkeeping",
|
||||
|
||||
@@ -4,31 +4,31 @@ go 1.25
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.1.0
|
||||
github.com/coreos/go-oidc/v3 v3.16.0
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/extrame/xls v0.0.2-0.20200426124601-4a6cf263071b
|
||||
github.com/gin-contrib/cache v1.4.1
|
||||
github.com/gin-contrib/gzip v1.2.5
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/go-co-op/gocron/v2 v2.17.0
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/go-co-op/gocron/v2 v2.19.1
|
||||
github.com/go-playground/validator/v10 v10.30.1
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/invopop/jsonschema v0.13.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/minio/minio-go/v7 v7.0.95
|
||||
github.com/lib/pq v1.11.1
|
||||
github.com/mattn/go-sqlite3 v1.14.33
|
||||
github.com/minio/minio-go/v7 v7.0.98
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/urfave/cli/v3 v3.5.0
|
||||
github.com/urfave/cli/v3 v3.6.2
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/xuri/excelize/v2 v2.10.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/oauth2 v0.32.0
|
||||
golang.org/x/text v0.30.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/oauth2 v0.34.0
|
||||
golang.org/x/text v0.33.0
|
||||
gopkg.in/ini.v1 v1.67.1
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.11
|
||||
@@ -52,7 +52,7 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/extrame/goyymmdd v0.0.0-20210114090516-7cc815f00d1a // indirect
|
||||
github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
@@ -65,13 +65,14 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/memcachier/mc/v3 v3.0.3 // indirect
|
||||
github.com/minio/crc64nvme v1.0.2 // indirect
|
||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -90,18 +91,19 @@ require (
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/tealeg/xlsx v1.0.5 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -28,8 +28,8 @@ github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
|
||||
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -43,8 +43,8 @@ github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7/go.mod h1:GPpMrAfHdb8
|
||||
github.com/extrame/xls v0.0.2-0.20200426124601-4a6cf263071b h1:jqW/h4gcXYEB6kVf6iuxjU9ONWA0ugUB94TP9UNmgdg=
|
||||
github.com/extrame/xls v0.0.2-0.20200426124601-4a6cf263071b/go.mod h1:iACcgahst7BboCpIMSpnFs4SKyU9ZjsvZBfNbUxZOJI=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gin-contrib/cache v1.4.1 h1:HcLwLfw7p+FasNp5VAnFbbBj9SzB4bDtswvon7wYSg4=
|
||||
github.com/gin-contrib/cache v1.4.1/go.mod h1:tykDV+FgItJHYEO0eCasuRYsZKPPyb4BYhAjuTlG6RM=
|
||||
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
|
||||
@@ -53,8 +53,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-co-op/gocron/v2 v2.17.0 h1:e/oj6fcAM8vOOKZxv2Cgfmjo+s8AXC46po5ZPtaSea4=
|
||||
github.com/go-co-op/gocron/v2 v2.17.0/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI=
|
||||
github.com/go-co-op/gocron/v2 v2.19.1 h1:B4iLeA0NB/2iO3EKQ7NfKn5KsQgZfjb2fkvoZJU3yBI=
|
||||
github.com/go-co-op/gocron/v2 v2.19.1/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
@@ -63,16 +63,16 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
@@ -90,34 +90,36 @@ github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7X
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI=
|
||||
github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
|
||||
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4=
|
||||
github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug=
|
||||
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
|
||||
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
|
||||
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
|
||||
github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0=
|
||||
github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -153,16 +155,18 @@ github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62/go.mod h1:65XQgovT
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
@@ -171,14 +175,14 @@ github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
||||
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
|
||||
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
|
||||
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/urfave/cli/v3 v3.5.0 h1:qCuFMmdayTF3zmjG8TSsoBzrDqszNrklYg2x3g4MSgw=
|
||||
github.com/urfave/cli/v3 v3.5.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
|
||||
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
@@ -188,32 +192,33 @@ github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstf
|
||||
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
@@ -221,8 +226,8 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
|
||||
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
|
||||
Generated
+2458
-1974
File diff suppressed because it is too large
Load Diff
+25
-24
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ezbookkeeping",
|
||||
"version": "1.2.0",
|
||||
"version": "1.4.0",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -20,62 +20,63 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@vuepic/vue-datepicker": "^11.0.3",
|
||||
"axios": "^1.12.2",
|
||||
"@vuepic/vue-datepicker": "^12.1.0",
|
||||
"axios": "^1.13.4",
|
||||
"cbor-js": "^0.1.0",
|
||||
"chardet": "^2.1.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dom7": "^4.0.6",
|
||||
"echarts": "^6.0.0",
|
||||
"framework7": "^8.3.4",
|
||||
"framework7": "^9.0.3",
|
||||
"framework7-icons": "^5.0.5",
|
||||
"framework7-vue": "^8.3.4",
|
||||
"framework7-vue": "^9.0.3",
|
||||
"jalaali-js": "^1.2.8",
|
||||
"leaflet": "^1.9.4",
|
||||
"line-awesome": "^1.3.0",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.6.0",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia": "^3.0.4",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"skeleton-elements": "^4.0.1",
|
||||
"swiper": "^10.2.0",
|
||||
"swiper": "^12.1.0",
|
||||
"ua-parser-js": "^1.0.39",
|
||||
"vue": "^3.5.22",
|
||||
"vue": "^3.5.27",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-router": "^4.6.3",
|
||||
"vue-i18n": "^11.2.8",
|
||||
"vue-router": "^5.0.2",
|
||||
"vue3-perfect-scrollbar": "^2.0.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuetify": "^3.10.7"
|
||||
"vuetify": "^3.11.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.2.0",
|
||||
"@tsconfig/node24": "^24.0.1",
|
||||
"@tsconfig/node24": "^24.0.4",
|
||||
"@types/cbor-js": "^0.1.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/git-rev-sync": "^2.0.2",
|
||||
"@types/jalaali-js": "^1.2.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^24.9.1",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
"@vue/eslint-config-typescript": "^14.6.0",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint-plugin-vue": "^10.5.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-vue": "^10.7.0",
|
||||
"git-rev-sync": "^3.0.2",
|
||||
"jest": "^30.2.0",
|
||||
"postcss-preset-env": "^10.4.0",
|
||||
"sass": "^1.93.2",
|
||||
"ts-jest": "^29.4.5",
|
||||
"postcss-preset-env": "^11.1.3",
|
||||
"sass": "^1.97.3",
|
||||
"ts-jest": "^29.4.6",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.12",
|
||||
"vite-plugin-checker": "^0.11.0",
|
||||
"vite-plugin-pwa": "^1.1.0",
|
||||
"vite-plugin-vuetify": "^2.1.2",
|
||||
"vue-tsc": "^3.1.2"
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-checker": "^0.12.0",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vite-plugin-vuetify": "^2.1.3",
|
||||
"vue-tsc": "^3.2.4"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 5 Chrome versions",
|
||||
|
||||
+27
-16
@@ -150,10 +150,10 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[accounts.AccountCreateHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[accounts.AccountCreateHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error
|
||||
}
|
||||
}
|
||||
|
||||
err = a.accounts.CreateAccounts(c, mainAccount, accountCreateReq.BalanceTime, childrenAccounts, childrenAccountBalanceTimes, utcOffset)
|
||||
err = a.accounts.CreateAccounts(c, mainAccount, accountCreateReq.BalanceTime, childrenAccounts, childrenAccountBalanceTimes, clientTimezone)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[accounts.AccountCreateHandler] failed to create account \"id:%d\" for user \"uid:%d\", because %s", mainAccount.AccountId, uid, err.Error())
|
||||
@@ -315,10 +315,10 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
||||
return nil, errs.ErrAccountIdInvalid
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[accounts.AccountModifyHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[accounts.AccountModifyHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -437,6 +437,17 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
||||
toUpdateAccount := a.getToUpdateAccount(uid, &accountModifyReq, mainAccount, false)
|
||||
|
||||
if toUpdateAccount != nil {
|
||||
if toUpdateAccount.Category != mainAccount.Category {
|
||||
maxOrderId, err := a.accounts.GetMaxDisplayOrder(c, uid, toUpdateAccount.Category)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[accounts.AccountModifyHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
toUpdateAccount.DisplayOrder = maxOrderId + 1
|
||||
}
|
||||
|
||||
anythingUpdate = true
|
||||
toUpdateAccounts = append(toUpdateAccounts, toUpdateAccount)
|
||||
}
|
||||
@@ -521,7 +532,7 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
||||
}
|
||||
}
|
||||
|
||||
err = a.accounts.ModifyAccounts(c, mainAccount, toUpdateAccounts, toAddAccounts, toAddAccountBalanceTimes, toDeleteAccountIds, utcOffset)
|
||||
err = a.accounts.ModifyAccounts(c, mainAccount, toUpdateAccounts, toAddAccounts, toAddAccountBalanceTimes, toDeleteAccountIds, clientTimezone)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[accounts.AccountModifyHandler] failed to update account \"id:%d\" for user \"uid:%d\", because %s", accountModifyReq.Id, uid, err.Error())
|
||||
@@ -542,7 +553,6 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
||||
|
||||
account.Type = oldAccount.Type
|
||||
account.ParentAccountId = oldAccount.ParentAccountId
|
||||
account.DisplayOrder = oldAccount.DisplayOrder
|
||||
account.Currency = oldAccount.Currency
|
||||
account.Balance = oldAccount.Balance
|
||||
|
||||
@@ -762,15 +772,16 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
|
||||
}
|
||||
|
||||
newAccount := &models.Account{
|
||||
AccountId: oldAccount.AccountId,
|
||||
Uid: uid,
|
||||
Name: accountModifyReq.Name,
|
||||
Category: accountModifyReq.Category,
|
||||
Icon: accountModifyReq.Icon,
|
||||
Color: accountModifyReq.Color,
|
||||
Comment: accountModifyReq.Comment,
|
||||
Extend: newAccountExtend,
|
||||
Hidden: accountModifyReq.Hidden,
|
||||
AccountId: oldAccount.AccountId,
|
||||
Uid: uid,
|
||||
Name: accountModifyReq.Name,
|
||||
DisplayOrder: oldAccount.DisplayOrder,
|
||||
Category: accountModifyReq.Category,
|
||||
Icon: accountModifyReq.Icon,
|
||||
Color: accountModifyReq.Color,
|
||||
Comment: accountModifyReq.Comment,
|
||||
Extend: newAccountExtend,
|
||||
Hidden: accountModifyReq.Hidden,
|
||||
}
|
||||
|
||||
if newAccount.Name != oldAccount.Name ||
|
||||
|
||||
+47
-23
@@ -28,9 +28,11 @@ type DataManagementsApi struct {
|
||||
transactions *services.TransactionService
|
||||
categories *services.TransactionCategoryService
|
||||
tags *services.TransactionTagService
|
||||
tagGroups *services.TransactionTagGroupService
|
||||
pictures *services.TransactionPictureService
|
||||
templates *services.TransactionTemplateService
|
||||
userCustomExchangeRates *services.UserCustomExchangeRatesService
|
||||
insightsExploreres *services.InsightsExplorerService
|
||||
}
|
||||
|
||||
// Initialize a data management api singleton instance
|
||||
@@ -45,9 +47,11 @@ var (
|
||||
transactions: services.Transactions,
|
||||
categories: services.TransactionCategories,
|
||||
tags: services.TransactionTags,
|
||||
tagGroups: services.TransactionTagGroups,
|
||||
pictures: services.TransactionPictures,
|
||||
templates: services.TransactionTemplates,
|
||||
userCustomExchangeRates: services.UserCustomExchangeRates,
|
||||
insightsExploreres: services.InsightsExplorers,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -99,6 +103,13 @@ func (a *DataManagementsApi) DataStatisticsHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
totalInsightsExplorerCount, err := a.insightsExploreres.GetTotalInsightsExplorersCountByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.DataStatisticsHandler] failed to get total insights explorer count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
totalTransactionTemplateCount, err := a.templates.GetTotalNormalTemplateCountByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
@@ -119,6 +130,7 @@ func (a *DataManagementsApi) DataStatisticsHandler(c *core.WebContext) (any, *er
|
||||
TotalTransactionTagCount: totalTransactionTagCount,
|
||||
TotalTransactionCount: totalTransactionCount,
|
||||
TotalTransactionPictureCount: totalTransactionPictureCount,
|
||||
TotalInsightsExplorerCount: totalInsightsExplorerCount,
|
||||
TotalTransactionTemplateCount: totalTransactionTemplateCount,
|
||||
TotalScheduledTransactionCount: totalScheduledTransactionCount,
|
||||
}
|
||||
@@ -183,6 +195,13 @@ func (a *DataManagementsApi) ClearAllDataHandler(c *core.WebContext) (any, *errs
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.tagGroups.DeleteAllTagGroups(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearAllDataHandler] failed to delete all transaction tag groups, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.userCustomExchangeRates.DeleteAllCustomExchangeRates(c, uid)
|
||||
|
||||
if err != nil {
|
||||
@@ -190,6 +209,13 @@ func (a *DataManagementsApi) ClearAllDataHandler(c *core.WebContext) (any, *errs
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.insightsExploreres.DeleteAllInsightsExplorers(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearAllDataHandler] failed to delete all insights explorers, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[data_managements.ClearAllDataHandler] user \"uid:%d\" has cleared all data", uid)
|
||||
return true, nil
|
||||
}
|
||||
@@ -298,17 +324,15 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
||||
err := c.ShouldBindQuery(&exportTransactionDataReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[data_managements.ExportDataHandler] parse request failed, because %s", err.Error())
|
||||
log.Warnf(c, "[data_managements.getExportedFileContent] parse request failed, because %s", err.Error())
|
||||
return nil, "", errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
timezone := time.Local
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[data_managements.ExportDataHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
} else {
|
||||
timezone = time.FixedZone("Client Timezone", int(utcOffset)*60)
|
||||
log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone, because %s", err.Error())
|
||||
clientTimezone = time.Local
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
@@ -316,7 +340,7 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.Warnf(c, "[data_managements.ExportDataHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Warnf(c, "[data_managements.getExportedFileContent] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
||||
}
|
||||
|
||||
return nil, "", errs.ErrUserNotFound
|
||||
@@ -329,28 +353,28 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ExportDataHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[data_managements.getExportedFileContent] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, "", errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
categories, err := a.categories.GetAllCategoriesByUid(c, uid, 0, -1)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ExportDataHandler] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[data_managements.getExportedFileContent] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, "", errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
tags, err := a.tags.GetAllTagsByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ExportDataHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[data_managements.getExportedFileContent] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, "", errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
tagIndexes, err := a.tags.GetAllTagIdsMapOfAllTransactions(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ExportDataHandler] failed to get tag index for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[data_managements.getExportedFileContent] failed to get tag index for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, "", errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
@@ -361,25 +385,25 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
||||
allAccountIds, err := a.accounts.GetAccountOrSubAccountIds(c, exportTransactionDataReq.AccountIds, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[data_managements.ExportDataHandler] get account error, because %s", err.Error())
|
||||
log.Warnf(c, "[data_managements.getExportedFileContent] get account error, because %s", err.Error())
|
||||
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
allCategoryIds, err := a.categories.GetCategoryOrSubCategoryIds(c, exportTransactionDataReq.CategoryIds, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[data_managements.ExportDataHandler] get transaction category error, because %s", err.Error())
|
||||
log.Warnf(c, "[data_managements.getExportedFileContent] get transaction category error, because %s", err.Error())
|
||||
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
var allTagIds []int64
|
||||
noTags := exportTransactionDataReq.TagIds == "none"
|
||||
noTags := exportTransactionDataReq.TagFilter == models.TransactionNoTagFilterValue
|
||||
var tagFilters []*models.TransactionTagFilter
|
||||
|
||||
if !noTags {
|
||||
allTagIds, err = a.tags.GetTagIds(exportTransactionDataReq.TagIds)
|
||||
tagFilters, err = models.ParseTransactionTagFilter(exportTransactionDataReq.TagFilter)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[data_managements.ExportDataHandler] get transaction tag ids error, because %s", err.Error())
|
||||
log.Warnf(c, "[data_managements.getExportedFileContent] parse transaction tag filters error, because %s", err.Error())
|
||||
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
@@ -395,10 +419,10 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
||||
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(exportTransactionDataReq.MinTime)
|
||||
}
|
||||
|
||||
allTransactions, err := a.transactions.GetAllSpecifiedTransactions(c, uid, maxTransactionTime, minTransactionTime, exportTransactionDataReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, exportTransactionDataReq.TagFilterType, exportTransactionDataReq.AmountFilter, exportTransactionDataReq.Keyword, pageCountForDataExport, true)
|
||||
allTransactions, err := a.transactions.GetAllSpecifiedTransactions(c, uid, maxTransactionTime, minTransactionTime, exportTransactionDataReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, exportTransactionDataReq.AmountFilter, exportTransactionDataReq.Keyword, pageCountForDataExport, true)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ExportDataHandler] failed to all transactions user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[data_managements.getExportedFileContent] failed to all transactions user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, "", errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
@@ -411,17 +435,17 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
||||
result, err := dataExporter.ToExportedContent(c, uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexes)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ExportDataHandler] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[data_managements.getExportedFileContent] failed to get exported data for \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
fileName := a.getFileName(user, timezone, fileType)
|
||||
fileName := a.getFileName(user, clientTimezone, fileType)
|
||||
|
||||
return result, fileName, nil
|
||||
}
|
||||
|
||||
func (a *DataManagementsApi) getFileName(user *models.User, timezone *time.Location, fileExtension string) string {
|
||||
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone)
|
||||
func (a *DataManagementsApi) getFileName(user *models.User, clientTimezone *time.Location, fileExtension string) string {
|
||||
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), clientTimezone)
|
||||
currentTime = strings.Replace(currentTime, "-", "_", -1)
|
||||
currentTime = strings.Replace(currentTime, " ", "_", -1)
|
||||
currentTime = strings.Replace(currentTime, ":", "_", -1)
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||
)
|
||||
|
||||
// InsightsExplorersApi represents insights explorers api
|
||||
type InsightsExplorersApi struct {
|
||||
insightsExploreres *services.InsightsExplorerService
|
||||
}
|
||||
|
||||
// Initialize a insights explorers api singleton instance
|
||||
var (
|
||||
InsightsExplorers = &InsightsExplorersApi{
|
||||
insightsExploreres: services.InsightsExplorers,
|
||||
}
|
||||
)
|
||||
|
||||
// InsightsExplorerListHandler returns insights explorer list of current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerListHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
explorers, err := a.insightsExploreres.GetAllInsightsExplorerNamesByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerListHandler] failed to get insights explorers for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
explorerResps := make(models.InsightsExplorerInfoResponseSlice, len(explorers))
|
||||
|
||||
for i := 0; i < len(explorers); i++ {
|
||||
explorerResps[i], err = explorers[i].ToInsightsExplorerInfoResponse()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerListHandler] failed to get insights explorer response for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(explorerResps)
|
||||
|
||||
return explorerResps, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerGetHandler returns one specific insights explorer of current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerGetHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerGetReq models.InsightsExplorerGetRequest
|
||||
err := c.ShouldBindQuery(&explorerGetReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerGetHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
explorer, err := a.insightsExploreres.GetInsightsExplorerByExplorerId(c, uid, explorerGetReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerGetHandler] failed to get insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerGetReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
explorerResp, err := explorer.ToInsightsExplorerInfoResponse()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerGetHandler] failed to get insights explorer response for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
return explorerResp, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerCreateHandler saves a new insights explorer by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerCreateHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerCreateReq models.InsightsExplorerCreateRequest
|
||||
err := c.ShouldBindJSON(&explorerCreateReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerCreateHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
maxOrderId, err := a.insightsExploreres.GetMaxDisplayOrder(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
explorer, err := a.createNewInsightsExplorerModel(uid, &explorerCreateReq, maxOrderId+1)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerCreateHandler] failed to parse insights explorer data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
err = a.insightsExploreres.CreateInsightsExplorer(c, explorer)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerCreateHandler] failed to create insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorer.ExplorerId, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerCreateHandler] user \"uid:%d\" has created a new insights explorer \"id:%d\" successfully", uid, explorer.ExplorerId)
|
||||
|
||||
explorerResp, err := explorer.ToInsightsExplorerInfoResponse()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerCreateHandler] failed to get insights explorer response for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
return explorerResp, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerModifyHandler saves an existed insights explorer by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerModifyHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerModifyReq models.InsightsExplorerModifyRequest
|
||||
err := c.ShouldBindJSON(&explorerModifyReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerModifyHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
explorer, err := a.insightsExploreres.GetInsightsExplorerByExplorerId(c, uid, explorerModifyReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerModifyHandler] failed to get insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
newData, err := json.Marshal(explorerModifyReq.Data)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerModifyHandler] failed to parse insights explorer data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
newExplorer := &models.InsightsExplorer{
|
||||
ExplorerId: explorer.ExplorerId,
|
||||
Uid: uid,
|
||||
Name: explorerModifyReq.Name,
|
||||
Data: string(newData),
|
||||
}
|
||||
|
||||
if newExplorer.Name == explorer.Name && newExplorer.Data == explorer.Data {
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
err = a.insightsExploreres.ModifyInsightsExplorer(c, newExplorer)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerModifyHandler] failed to update insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerModifyHandler] user \"uid:%d\" has updated insights explorer \"id:%d\" successfully", uid, explorerModifyReq.Id)
|
||||
|
||||
explorer.Name = newExplorer.Name
|
||||
explorer.Data = newExplorer.Data
|
||||
explorerResp, err := explorer.ToInsightsExplorerInfoResponse()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerModifyHandler] failed to get insights explorer response for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
return explorerResp, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerHideHandler hides a insights explorer by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerHideHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerHideReq models.InsightsExplorerHideRequest
|
||||
err := c.ShouldBindJSON(&explorerHideReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerHideHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.insightsExploreres.HideInsightsExplorer(c, uid, []int64{explorerHideReq.Id}, explorerHideReq.Hidden)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerHideHandler] failed to hide insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerHideReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerHideHandler] user \"uid:%d\" has hidden insights explorer \"id:%d\"", uid, explorerHideReq.Id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerMoveHandler moves display order of existed insights explorers by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerMoveHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerMoveReq models.InsightsExplorerMoveRequest
|
||||
err := c.ShouldBindJSON(&explorerMoveReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerMoveHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
explorers := make([]*models.InsightsExplorer, len(explorerMoveReq.NewDisplayOrders))
|
||||
|
||||
for i := 0; i < len(explorerMoveReq.NewDisplayOrders); i++ {
|
||||
newDisplayOrder := explorerMoveReq.NewDisplayOrders[i]
|
||||
explorer := &models.InsightsExplorer{
|
||||
Uid: uid,
|
||||
ExplorerId: newDisplayOrder.Id,
|
||||
DisplayOrder: newDisplayOrder.DisplayOrder,
|
||||
}
|
||||
|
||||
explorers[i] = explorer
|
||||
}
|
||||
|
||||
err = a.insightsExploreres.ModifyInsightsExplorerDisplayOrders(c, uid, explorers)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerMoveHandler] failed to move insights explorers for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerMoveHandler] user \"uid:%d\" has moved insights explorers", uid)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerDeleteHandler deletes an existed insights explorer by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerDeleteHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerDeleteReq models.InsightsExplorerDeleteRequest
|
||||
err := c.ShouldBindJSON(&explorerDeleteReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerDeleteHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.insightsExploreres.DeleteInsightsExplorer(c, uid, explorerDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerDeleteHandler] failed to delete insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerDeleteReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerDeleteHandler] user \"uid:%d\" has deleted insights explorer \"id:%d\"", uid, explorerDeleteReq.Id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *InsightsExplorersApi) createNewInsightsExplorerModel(uid int64, explorerCreateReq *models.InsightsExplorerCreateRequest, order int32) (*models.InsightsExplorer, error) {
|
||||
data, err := json.Marshal(explorerCreateReq.Data)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.InsightsExplorer{
|
||||
Uid: uid,
|
||||
Name: explorerCreateReq.Name,
|
||||
Data: string(data),
|
||||
DisplayOrder: order,
|
||||
}, nil
|
||||
}
|
||||
+2
-3
@@ -3,7 +3,6 @@ package api
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
// HealthsApi represents health api
|
||||
@@ -18,8 +17,8 @@ var (
|
||||
func (a *HealthsApi) HealthStatusHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
result := make(map[string]string)
|
||||
|
||||
result["version"] = settings.Version
|
||||
result["commit"] = settings.CommitHash
|
||||
result["version"] = core.Version
|
||||
result["commit"] = core.CommitHash
|
||||
result["status"] = "ok"
|
||||
|
||||
return result, nil
|
||||
|
||||
@@ -47,14 +47,13 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
|
||||
return nil, errs.ErrLargeLanguageModelProviderNotEnabled
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[large_language_models.RecognizeReceiptImageHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[large_language_models.RecognizeReceiptImageHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
timezone := time.FixedZone("Client Timezone", int(utcOffset)*60)
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
@@ -192,11 +191,12 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
|
||||
systemPrompt, err := templates.GetTemplate(templates.SYSTEM_PROMPT_RECEIPT_IMAGE_RECOGNITION)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[large_language_models.RecognizeReceiptImageHandler] failed to get system prompt template for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
systemPromptParams := map[string]any{
|
||||
"CurrentDateTime": utils.FormatUnixTimeToLongDateTime(time.Now().Unix(), timezone),
|
||||
"CurrentDateTime": utils.FormatUnixTimeToLongDateTime(time.Now().Unix(), clientTimezone),
|
||||
"AllExpenseCategoryNames": strings.Join(expenseCategoryNames, "\n"),
|
||||
"AllIncomeCategoryNames": strings.Join(incomeCategoryNames, "\n"),
|
||||
"AllTransferCategoryNames": strings.Join(transferCategoryNames, "\n"),
|
||||
@@ -208,6 +208,7 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
|
||||
err = systemPrompt.Execute(&bodyBuffer, systemPromptParams)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[large_language_models.RecognizeReceiptImageHandler] failed to get final system prompt from template for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
@@ -222,6 +223,7 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
|
||||
llmResponse, err := llm.Container.GetJsonResponseByReceiptImageRecognitionModel(c, c.GetCurrentUid(), a.CurrentConfig(), llmRequest)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[large_language_models.RecognizeReceiptImageHandler] failed to get llm response user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
@@ -236,10 +238,10 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
return a.parseRecognizedReceiptImageResponse(c, uid, utcOffset, result, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return a.parseRecognizedReceiptImageResponse(c, uid, clientTimezone, result, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
func (a *LargeLanguageModelsApi) parseRecognizedReceiptImageResponse(c *core.WebContext, uid int64, utcOffset int16, recognizedResult *models.RecognizedReceiptImageResult, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (*models.RecognizedReceiptImageResponse, *errs.Error) {
|
||||
func (a *LargeLanguageModelsApi) parseRecognizedReceiptImageResponse(c *core.WebContext, uid int64, clientTimezone *time.Location, recognizedResult *models.RecognizedReceiptImageResult, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (*models.RecognizedReceiptImageResponse, *errs.Error) {
|
||||
recognizedReceiptImageResponse := &models.RecognizedReceiptImageResponse{
|
||||
Type: models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
@@ -288,7 +290,7 @@ func (a *LargeLanguageModelsApi) parseRecognizedReceiptImageResponse(c *core.Web
|
||||
|
||||
if len(recognizedResult.Time) > 0 {
|
||||
longDateTime := a.getLongDateTime(recognizedResult.Time)
|
||||
timestamp, err := utils.ParseFromLongDateTime(longDateTime, utcOffset)
|
||||
timestamp, err := utils.ParseFromLongDateTimeInTimeZone(longDateTime, clientTimezone)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[large_language_models.parseRecognizedReceiptImageResponse] recoginzed time \"%s\" is invalid", recognizedResult.Time)
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/{z}/{x}/{y}.png" // https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
@@ -25,6 +26,8 @@ const tianDiTuMapAnnotationUrlFormat = "https://t0.tianditu.gov.cn/cva_w/wmts?SE
|
||||
// MapImageProxy represents map image proxy
|
||||
type MapImageProxy struct {
|
||||
ApiUsingConfig
|
||||
mutex sync.Mutex
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
// Initialize a map image proxy singleton instance
|
||||
@@ -36,6 +39,18 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (p *MapImageProxy) initializeHttpTransport() {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if p.transport != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.transport = http.DefaultTransport.(*http.Transport).Clone()
|
||||
httpclient.SetProxyUrl(p.transport, p.CurrentConfig().MapProxy)
|
||||
}
|
||||
|
||||
// MapTileImageProxyHandler returns map tile image
|
||||
func (p *MapImageProxy) MapTileImageProxyHandler(c *core.WebContext) (*httputil.ReverseProxy, *errs.Error) {
|
||||
return p.mapImageProxyHandler(c, func(c *core.WebContext, mapProvider string) (string, *errs.Error) {
|
||||
@@ -109,8 +124,9 @@ func (p *MapImageProxy) mapImageProxyHandler(c *core.WebContext, fn func(c *core
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
utils.SetProxyUrl(transport, p.CurrentConfig().MapProxy)
|
||||
if p.transport == nil {
|
||||
p.initializeHttpTransport()
|
||||
}
|
||||
|
||||
director := func(req *http.Request) {
|
||||
imageRawUrl := targetUrl
|
||||
@@ -126,7 +142,7 @@ func (p *MapImageProxy) mapImageProxyHandler(c *core.WebContext, fn func(c *core
|
||||
}
|
||||
|
||||
return &httputil.ReverseProxy{
|
||||
Transport: transport,
|
||||
Transport: p.transport,
|
||||
Director: director,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
const mcpServerName = "ezBookkeeping-mcp"
|
||||
const mcpServerName = core.ApplicationName + "-mcp"
|
||||
|
||||
// ModelContextProtocolAPI represents model context protocol api
|
||||
type ModelContextProtocolAPI struct {
|
||||
@@ -102,8 +102,8 @@ func (a *ModelContextProtocolAPI) InitializeHandler(c *core.WebContext, jsonRPCR
|
||||
},
|
||||
ServerInfo: &mcp.MCPImplementation{
|
||||
Name: mcpServerName,
|
||||
Title: a.CurrentConfig().AppName,
|
||||
Version: settings.Version,
|
||||
Title: core.ApplicationName,
|
||||
Version: core.Version,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
||||
)
|
||||
|
||||
const oauth2CallbackPageUrlSuccessFormat = "%sdesktop/#/oauth2_callback?platform=%s&provider=%s&token=%s"
|
||||
const oauth2CallbackPageUrlNeedVerifyFormat = "%sdesktop/#/oauth2_callback?platform=%s&provider=%s&userName=%s&token=%s"
|
||||
const oauth2CallbackPageUrlFailedFormat = "%sdesktop/#/oauth2_callback?errorCode=%d&errorMessage=%s"
|
||||
const oauth2CallbackPageUrlErrorMessageFormat = "%sdesktop/#/oauth2_callback?errorMessage=%s"
|
||||
const oauth2CallbackPageUrlSuccessFormat = "%sdesktop#/oauth2_callback?platform=%s&provider=%s&token=%s"
|
||||
const oauth2CallbackPageUrlNeedVerifyFormat = "%sdesktop#/oauth2_callback?platform=%s&provider=%s&userName=%s&token=%s"
|
||||
const oauth2CallbackPageUrlFailedFormat = "%sdesktop#/oauth2_callback?errorCode=%d&errorMessage=%s"
|
||||
const oauth2CallbackPageUrlErrorMessageFormat = "%sdesktop#/oauth2_callback?errorMessage=%s"
|
||||
|
||||
// OAuth2AuthenticationApi represents OAuth 2.0 authorization api
|
||||
type OAuth2AuthenticationApi struct {
|
||||
@@ -208,9 +208,20 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrCannotRetrieveUserInfo)
|
||||
}
|
||||
|
||||
if oauth2UserInfo.UserName == "" || oauth2UserInfo.Email == "" {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] invalid oauth 2.0 user info, userName: %s, email: %s", oauth2UserInfo.UserName, oauth2UserInfo.Email)
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrCannotRetrieveUserInfo)
|
||||
log.Infof(c, "[oauth2_authentications.CallbackHandler] oauth 2.0 user info, userName: %s, email: %s", oauth2UserInfo.UserName, oauth2UserInfo.Email)
|
||||
|
||||
if oauth2UserInfo.UserName == "" && oauth2UserInfo.Email == "" {
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrOAuth2UserNameAndEmailEmpty)
|
||||
}
|
||||
|
||||
if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierEmail && oauth2UserInfo.Email == "" {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] invalid oauth 2.0 user info, email is empty")
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrOAuth2EmailEmpty)
|
||||
}
|
||||
|
||||
if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername && oauth2UserInfo.UserName == "" {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] invalid oauth 2.0 user info, userName is empty")
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrOAuth2UserNameEmpty)
|
||||
}
|
||||
|
||||
userExternalAuthType := oauth2.GetExternalUserAuthType()
|
||||
@@ -221,7 +232,7 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
||||
} else if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername {
|
||||
userExternalAuth, err = a.userExternalAuths.GetUserExternalAuthByExternalUserName(c, oauth2UserInfo.UserName, userExternalAuthType)
|
||||
} else {
|
||||
userExternalAuth, err = a.userExternalAuths.GetUserExternalAuthByExternalEmail(c, oauth2UserInfo.Email, userExternalAuthType)
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrNotSupported)
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, errs.ErrUserExternalAuthNotFound) {
|
||||
@@ -257,7 +268,7 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
||||
} else if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername {
|
||||
user, err = a.users.GetUserByUsername(c, oauth2UserInfo.UserName)
|
||||
} else {
|
||||
user, err = a.users.GetUserByEmail(c, oauth2UserInfo.Email)
|
||||
err = errs.ErrNotSupported
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, errs.ErrUserNotFound) {
|
||||
@@ -267,6 +278,14 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
||||
}
|
||||
|
||||
if user == nil && a.CurrentConfig().EnableUserRegister && a.CurrentConfig().OAuth2AutoRegister {
|
||||
if oauth2UserInfo.UserName == "" {
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrOAuth2UserNameEmptyCannotRegister)
|
||||
}
|
||||
|
||||
if oauth2UserInfo.Email == "" {
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrOAuth2EmailEmptyCannotRegister)
|
||||
}
|
||||
|
||||
userName := strings.TrimSpace(oauth2UserInfo.UserName)
|
||||
email := strings.TrimSpace(oauth2UserInfo.Email)
|
||||
nickName := strings.TrimSpace(oauth2UserInfo.NickName)
|
||||
|
||||
+4
-5
@@ -3,7 +3,6 @@ package api
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
// SystemsApi represents system api
|
||||
@@ -18,11 +17,11 @@ var (
|
||||
func (a *SystemsApi) VersionHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
result := make(map[string]string)
|
||||
|
||||
result["version"] = settings.Version
|
||||
result["commitHash"] = settings.CommitHash
|
||||
result["version"] = core.Version
|
||||
result["commitHash"] = core.CommitHash
|
||||
|
||||
if settings.BuildTime != "" {
|
||||
result["buildTime"] = settings.BuildTime
|
||||
if core.BuildTime != "" {
|
||||
result["buildTime"] = core.BuildTime
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
+4
-4
@@ -69,10 +69,10 @@ func (a *TokensApi) TokenListHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
tokenResp.IsCurrent = true
|
||||
}
|
||||
|
||||
if token.TokenType == core.USER_TOKEN_TYPE_API && token.UserAgent != services.TokenUserAgentCreatedViaCli {
|
||||
tokenResp.UserAgent = services.TokenUserAgentForAPI
|
||||
} else if token.TokenType == core.USER_TOKEN_TYPE_MCP && token.UserAgent != services.TokenUserAgentCreatedViaCli {
|
||||
tokenResp.UserAgent = services.TokenUserAgentForMCP
|
||||
if token.TokenType == core.USER_TOKEN_TYPE_API && token.UserAgent != core.TokenUserAgentCreatedViaCli {
|
||||
tokenResp.UserAgent = core.TokenUserAgentForAPI
|
||||
} else if token.TokenType == core.USER_TOKEN_TYPE_MCP && token.UserAgent != core.TokenUserAgentCreatedViaCli {
|
||||
tokenResp.UserAgent = core.TokenUserAgentForMCP
|
||||
}
|
||||
|
||||
tokenResps[i] = tokenResp
|
||||
|
||||
@@ -214,6 +214,7 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.WebContext) (an
|
||||
Uid: uid,
|
||||
ParentCategoryId: categoryModifyReq.ParentId,
|
||||
Name: categoryModifyReq.Name,
|
||||
DisplayOrder: category.DisplayOrder,
|
||||
Icon: categoryModifyReq.Icon,
|
||||
Color: categoryModifyReq.Color,
|
||||
Comment: categoryModifyReq.Comment,
|
||||
@@ -259,6 +260,15 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.WebContext) (an
|
||||
if toPrimaryCategory.ParentCategoryId != models.LevelOneTransactionCategoryParentId {
|
||||
return nil, errs.Or(err, errs.ErrNotAllowUseSecondaryTransactionAsPrimaryCategory)
|
||||
}
|
||||
|
||||
maxOrderId, err := a.categories.GetMaxSubCategoryDisplayOrder(c, uid, category.Type, newCategory.ParentCategoryId)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_categories.CategoryModifyHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
newCategory.DisplayOrder = maxOrderId + 1
|
||||
}
|
||||
|
||||
err = a.categories.ModifyCategory(c, newCategory)
|
||||
@@ -271,7 +281,6 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.WebContext) (an
|
||||
log.Infof(c, "[transaction_categories.CategoryModifyHandler] user \"uid:%d\" has updated category \"id:%d\" successfully", uid, categoryModifyReq.Id)
|
||||
|
||||
newCategory.Type = category.Type
|
||||
newCategory.DisplayOrder = category.DisplayOrder
|
||||
categoryResp := newCategory.ToTransactionCategoryInfoResponse()
|
||||
|
||||
return categoryResp, nil
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||
)
|
||||
|
||||
// TransactionTagGroupsApi represents transaction tag group api
|
||||
type TransactionTagGroupsApi struct {
|
||||
tagGroups *services.TransactionTagGroupService
|
||||
}
|
||||
|
||||
// Initialize a transaction tag group api singleton instance
|
||||
var (
|
||||
TransactionTagGroups = &TransactionTagGroupsApi{
|
||||
tagGroups: services.TransactionTagGroups,
|
||||
}
|
||||
)
|
||||
|
||||
// TagGroupListHandler returns transaction tag group list of current user
|
||||
func (a *TransactionTagGroupsApi) TagGroupListHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
tagGroups, err := a.tagGroups.GetAllTagGroupsByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tag_groups.TagGroupListHandler] failed to get tag groups for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
tagGroupResps := make(models.TransactionTagGroupInfoResponseSlice, len(tagGroups))
|
||||
|
||||
for i := 0; i < len(tagGroups); i++ {
|
||||
tagGroupResps[i] = tagGroups[i].ToTransactionTagGroupInfoResponse()
|
||||
}
|
||||
|
||||
sort.Sort(tagGroupResps)
|
||||
|
||||
return tagGroupResps, nil
|
||||
}
|
||||
|
||||
// TagGroupGetHandler returns one specific transaction tag group of current user
|
||||
func (a *TransactionTagGroupsApi) TagGroupGetHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var tagGroupGetReq models.TransactionTagGroupGetRequest
|
||||
err := c.ShouldBindQuery(&tagGroupGetReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transaction_tag_groups.TagGroupGetHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
tagGroup, err := a.tagGroups.GetTagGroupByTagGroupId(c, uid, tagGroupGetReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tag_groups.TagGroupGetHandler] failed to get tag group \"id:%d\" for user \"uid:%d\", because %s", tagGroupGetReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
tagGroupResp := tagGroup.ToTransactionTagGroupInfoResponse()
|
||||
|
||||
return tagGroupResp, nil
|
||||
}
|
||||
|
||||
// TagGroupCreateHandler saves a new transaction tag group by request parameters for current user
|
||||
func (a *TransactionTagGroupsApi) TagGroupCreateHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var tagGroupCreateReq models.TransactionTagGroupCreateRequest
|
||||
err := c.ShouldBindJSON(&tagGroupCreateReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transaction_tag_groups.TagGroupCreateHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
maxOrderId, err := a.tagGroups.GetMaxDisplayOrder(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tag_groups.TagGroupCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
tagGroup := a.createNewTagGroupModel(uid, &tagGroupCreateReq, maxOrderId+1)
|
||||
|
||||
err = a.tagGroups.CreateTagGroup(c, tagGroup)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tag_groups.TagGroupCreateHandler] failed to create tag group \"id:%d\" for user \"uid:%d\", because %s", tagGroup.TagGroupId, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[transaction_tag_groups.TagGroupCreateHandler] user \"uid:%d\" has created a new tag group \"id:%d\" successfully", uid, tagGroup.TagGroupId)
|
||||
|
||||
tagGroupResp := tagGroup.ToTransactionTagGroupInfoResponse()
|
||||
|
||||
return tagGroupResp, nil
|
||||
}
|
||||
|
||||
// TagGroupModifyHandler saves an existed transaction tag group by request parameters for current user
|
||||
func (a *TransactionTagGroupsApi) TagGroupModifyHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var tagGroupModifyReq models.TransactionTagGroupModifyRequest
|
||||
err := c.ShouldBindJSON(&tagGroupModifyReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transaction_tag_groups.TagGroupModifyHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
tagGroup, err := a.tagGroups.GetTagGroupByTagGroupId(c, uid, tagGroupModifyReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tag_groups.TagGroupModifyHandler] failed to get tag group \"id:%d\" for user \"uid:%d\", because %s", tagGroupModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
newTagGroup := &models.TransactionTagGroup{
|
||||
TagGroupId: tagGroup.TagGroupId,
|
||||
Uid: uid,
|
||||
Name: tagGroupModifyReq.Name,
|
||||
}
|
||||
|
||||
if newTagGroup.Name == tagGroup.Name {
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
err = a.tagGroups.ModifyTagGroup(c, newTagGroup)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tag_groups.TagGroupModifyHandler] failed to update tag group \"id:%d\" for user \"uid:%d\", because %s", tagGroupModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[transaction_tag_groups.TagGroupModifyHandler] user \"uid:%d\" has updated tag group \"id:%d\" successfully", uid, tagGroupModifyReq.Id)
|
||||
|
||||
tagGroup.Name = newTagGroup.Name
|
||||
tagGroupResp := tagGroup.ToTransactionTagGroupInfoResponse()
|
||||
|
||||
return tagGroupResp, nil
|
||||
}
|
||||
|
||||
// TagGroupMoveHandler moves display order of existed transaction tag groups by request parameters for current user
|
||||
func (a *TransactionTagGroupsApi) TagGroupMoveHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var tagGroupMoveReq models.TransactionTagGroupMoveRequest
|
||||
err := c.ShouldBindJSON(&tagGroupMoveReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transaction_tag_groups.TagGroupMoveHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
tagGroups := make([]*models.TransactionTagGroup, len(tagGroupMoveReq.NewDisplayOrders))
|
||||
|
||||
for i := 0; i < len(tagGroupMoveReq.NewDisplayOrders); i++ {
|
||||
newDisplayOrder := tagGroupMoveReq.NewDisplayOrders[i]
|
||||
tagGroup := &models.TransactionTagGroup{
|
||||
Uid: uid,
|
||||
TagGroupId: newDisplayOrder.Id,
|
||||
DisplayOrder: newDisplayOrder.DisplayOrder,
|
||||
}
|
||||
|
||||
tagGroups[i] = tagGroup
|
||||
}
|
||||
|
||||
err = a.tagGroups.ModifyTagGroupDisplayOrders(c, uid, tagGroups)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tag_groups.TagGroupMoveHandler] failed to move tag groups for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[transaction_tag_groups.TagGroupMoveHandler] user \"uid:%d\" has moved tag groups", uid)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TagGroupDeleteHandler deletes an existed transaction tag group by request parameters for current user
|
||||
func (a *TransactionTagGroupsApi) TagGroupDeleteHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var tagGroupDeleteReq models.TransactionTagGroupDeleteRequest
|
||||
err := c.ShouldBindJSON(&tagGroupDeleteReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transaction_tag_groups.TagGroupDeleteHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.tagGroups.DeleteTagGroup(c, uid, tagGroupDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tag_groups.TagGroupDeleteHandler] failed to delete tag group \"id:%d\" for user \"uid:%d\", because %s", tagGroupDeleteReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[transaction_tag_groups.TagGroupDeleteHandler] user \"uid:%d\" has deleted tag group \"id:%d\"", uid, tagGroupDeleteReq.Id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *TransactionTagGroupsApi) createNewTagGroupModel(uid int64, tagGroupCreateReq *models.TransactionTagGroupCreateRequest, order int32) *models.TransactionTagGroup {
|
||||
return &models.TransactionTagGroup{
|
||||
Uid: uid,
|
||||
Name: tagGroupCreateReq.Name,
|
||||
DisplayOrder: order,
|
||||
}
|
||||
}
|
||||
+80
-10
@@ -12,13 +12,15 @@ import (
|
||||
|
||||
// TransactionTagsApi represents transaction tag api
|
||||
type TransactionTagsApi struct {
|
||||
tags *services.TransactionTagService
|
||||
tags *services.TransactionTagService
|
||||
tagGroups *services.TransactionTagGroupService
|
||||
}
|
||||
|
||||
// Initialize a transaction tag api singleton instance
|
||||
var (
|
||||
TransactionTags = &TransactionTagsApi{
|
||||
tags: services.TransactionTags,
|
||||
tags: services.TransactionTags,
|
||||
tagGroups: services.TransactionTagGroups,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -78,7 +80,21 @@ func (a *TransactionTagsApi) TagCreateHandler(c *core.WebContext) (any, *errs.Er
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
maxOrderId, err := a.tags.GetMaxDisplayOrder(c, uid)
|
||||
if tagCreateReq.GroupId > 0 {
|
||||
tagGroup, err := a.tagGroups.GetTagGroupByTagGroupId(c, uid, tagCreateReq.GroupId)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tags.TagCreateHandler] failed to get tag group \"id:%d\" for user \"uid:%d\", because %s", tagCreateReq.GroupId, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
if tagGroup == nil {
|
||||
log.Warnf(c, "[transaction_tags.TagCreateHandler] the tag group \"id:%d\" does not exist for user \"uid:%d\"", tagCreateReq.GroupId, uid)
|
||||
return nil, errs.ErrTransactionTagGroupNotFound
|
||||
}
|
||||
}
|
||||
|
||||
maxOrderId, err := a.tags.GetMaxDisplayOrder(c, uid, tagCreateReq.GroupId)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tags.TagCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -111,9 +127,30 @@ func (a *TransactionTagsApi) TagCreateBatchHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(tagCreateBatchReq.Tags); i++ {
|
||||
if tagCreateBatchReq.Tags[i].GroupId != tagCreateBatchReq.GroupId {
|
||||
log.Warnf(c, "[transaction_tags.TagCreateBatchHandler] the group id \"%d\" of tag#%d is inconsistent with the batch group id \"%d\"", tagCreateBatchReq.Tags[i].GroupId, i, tagCreateBatchReq.GroupId)
|
||||
return nil, errs.ErrTransactionTagGroupIdInvalid
|
||||
}
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
maxOrderId, err := a.tags.GetMaxDisplayOrder(c, uid)
|
||||
if tagCreateBatchReq.GroupId > 0 {
|
||||
tagGroup, err := a.tagGroups.GetTagGroupByTagGroupId(c, uid, tagCreateBatchReq.GroupId)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tags.TagCreateBatchHandler] failed to get tag group \"id:%d\" for user \"uid:%d\", because %s", tagCreateBatchReq.GroupId, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
if tagGroup == nil {
|
||||
log.Warnf(c, "[transaction_tags.TagCreateBatchHandler] the tag group \"id:%d\" does not exist for user \"uid:%d\"", tagCreateBatchReq.GroupId, uid)
|
||||
return nil, errs.ErrTransactionTagGroupNotFound
|
||||
}
|
||||
}
|
||||
|
||||
maxOrderId, err := a.tags.GetMaxDisplayOrder(c, uid, tagCreateBatchReq.GroupId)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tags.TagCreateBatchHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -160,17 +197,46 @@ func (a *TransactionTagsApi) TagModifyHandler(c *core.WebContext) (any, *errs.Er
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
newTag := &models.TransactionTag{
|
||||
TagId: tag.TagId,
|
||||
Uid: uid,
|
||||
Name: tagModifyReq.Name,
|
||||
if tagModifyReq.GroupId != tag.TagGroupId && tagModifyReq.GroupId > 0 {
|
||||
tagGroup, err := a.tagGroups.GetTagGroupByTagGroupId(c, uid, tagModifyReq.GroupId)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tags.TagModifyHandler] failed to get tag group \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.GroupId, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
if tagGroup == nil {
|
||||
log.Warnf(c, "[transaction_tags.TagModifyHandler] the tag group \"id:%d\" does not exist for user \"uid:%d\"", tagModifyReq.GroupId, uid)
|
||||
return nil, errs.ErrTransactionTagGroupNotFound
|
||||
}
|
||||
}
|
||||
|
||||
if newTag.Name == tag.Name {
|
||||
newTag := &models.TransactionTag{
|
||||
TagId: tag.TagId,
|
||||
Uid: uid,
|
||||
Name: tagModifyReq.Name,
|
||||
TagGroupId: tagModifyReq.GroupId,
|
||||
DisplayOrder: tag.DisplayOrder,
|
||||
}
|
||||
|
||||
tagNameChanged := newTag.Name != tag.Name
|
||||
|
||||
if !tagNameChanged && newTag.TagGroupId == tag.TagGroupId {
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
err = a.tags.ModifyTag(c, newTag)
|
||||
if newTag.TagGroupId != tag.TagGroupId {
|
||||
maxOrderId, err := a.tags.GetMaxDisplayOrder(c, uid, newTag.TagGroupId)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tags.TagModifyHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
newTag.DisplayOrder = maxOrderId + 1
|
||||
}
|
||||
|
||||
err = a.tags.ModifyTag(c, newTag, tagNameChanged)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transaction_tags.TagModifyHandler] failed to update tag \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.Id, uid, err.Error())
|
||||
@@ -180,6 +246,8 @@ func (a *TransactionTagsApi) TagModifyHandler(c *core.WebContext) (any, *errs.Er
|
||||
log.Infof(c, "[transaction_tags.TagModifyHandler] user \"uid:%d\" has updated tag \"id:%d\" successfully", uid, tagModifyReq.Id)
|
||||
|
||||
tag.Name = newTag.Name
|
||||
tag.TagGroupId = newTag.TagGroupId
|
||||
tag.DisplayOrder = newTag.DisplayOrder
|
||||
tagResp := tag.ToTransactionTagInfoResponse()
|
||||
|
||||
return tagResp, nil
|
||||
@@ -268,6 +336,7 @@ func (a *TransactionTagsApi) createNewTagModel(uid int64, tagCreateReq *models.T
|
||||
return &models.TransactionTag{
|
||||
Uid: uid,
|
||||
Name: tagCreateReq.Name,
|
||||
TagGroupId: tagCreateReq.GroupId,
|
||||
DisplayOrder: order,
|
||||
}
|
||||
}
|
||||
@@ -278,6 +347,7 @@ func (a *TransactionTagsApi) createNewTagModels(uid int64, tagCreateBatchReq *mo
|
||||
for i := 0; i < len(tagCreateBatchReq.Tags); i++ {
|
||||
tagCreateReq := tagCreateBatchReq.Tags[i]
|
||||
tag := a.createNewTagModel(uid, tagCreateReq, order+int32(i))
|
||||
tag.TagGroupId = tagCreateBatchReq.GroupId
|
||||
tags[i] = tag
|
||||
}
|
||||
|
||||
|
||||
+318
-102
@@ -4,8 +4,10 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
|
||||
@@ -83,19 +85,19 @@ func (a *TransactionsApi) TransactionCountHandler(c *core.WebContext) (any, *err
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
var allTagIds []int64
|
||||
noTags := transactionCountReq.TagIds == "none"
|
||||
noTags := transactionCountReq.TagFilter == models.TransactionNoTagFilterValue
|
||||
var tagFilters []*models.TransactionTagFilter
|
||||
|
||||
if !noTags {
|
||||
allTagIds, err = a.transactionTags.GetTagIds(transactionCountReq.TagIds)
|
||||
tagFilters, err = models.ParseTransactionTagFilter(transactionCountReq.TagFilter)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionCountHandler] get transaction tag ids error, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionCountHandler] parse transaction filters error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionCountReq.TagFilterType, transactionCountReq.AmountFilter, transactionCountReq.Keyword)
|
||||
totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionCountReq.AmountFilter, transactionCountReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -119,10 +121,10 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionListHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionListHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -151,14 +153,14 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
var allTagIds []int64
|
||||
noTags := transactionListReq.TagIds == "none"
|
||||
noTags := transactionListReq.TagFilter == models.TransactionNoTagFilterValue
|
||||
var tagFilters []*models.TransactionTagFilter
|
||||
|
||||
if !noTags {
|
||||
allTagIds, err = a.transactionTags.GetTagIds(transactionListReq.TagIds)
|
||||
tagFilters, err = models.ParseTransactionTagFilter(transactionListReq.TagFilter)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionListHandler] get transaction tag ids error, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionListHandler] parse transaction tag filters error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
@@ -166,7 +168,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
|
||||
var totalCount int64
|
||||
|
||||
if transactionListReq.WithCount {
|
||||
totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.TagFilterType, transactionListReq.AmountFilter, transactionListReq.Keyword)
|
||||
totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -174,7 +176,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
|
||||
}
|
||||
}
|
||||
|
||||
transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.TagFilterType, transactionListReq.AmountFilter, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true)
|
||||
transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionListHandler] failed to get transactions earlier than \"%d\" for user \"uid:%d\", because %s", transactionListReq.MaxTime, uid, err.Error())
|
||||
@@ -190,7 +192,15 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
|
||||
transactions = transactions[:transactionListReq.Count]
|
||||
}
|
||||
|
||||
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
|
||||
accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, err := a.getTransactionEssentialDataByTransactionIds(c, user, transactions, transactionListReq.WithPictures, transactionListReq.TrimCategory, transactionListReq.TrimTag)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionListHandler] failed to get essential data for assembling transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactions = a.filterTransactions(c, uid, transactions, accountMap)
|
||||
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, clientTimezone, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -222,10 +232,10 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any,
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionMonthListHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionMonthListHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -254,26 +264,34 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any,
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
var allTagIds []int64
|
||||
noTags := transactionListReq.TagIds == "none"
|
||||
noTags := transactionListReq.TagFilter == models.TransactionNoTagFilterValue
|
||||
var tagFilters []*models.TransactionTagFilter
|
||||
|
||||
if !noTags {
|
||||
allTagIds, err = a.transactionTags.GetTagIds(transactionListReq.TagIds)
|
||||
tagFilters, err = models.ParseTransactionTagFilter(transactionListReq.TagFilter)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionMonthListHandler] get transaction tag ids error, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionMonthListHandler] parse transaction tag filters error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.TagFilterType, transactionListReq.AmountFilter, transactionListReq.Keyword)
|
||||
transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to get transactions in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
|
||||
accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, err := a.getTransactionEssentialDataByTransactionIds(c, user, transactions, transactionListReq.WithPictures, transactionListReq.TrimCategory, transactionListReq.TrimTag)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to get essential data for assembling transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactions = a.filterTransactions(c, uid, transactions, accountMap)
|
||||
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, clientTimezone, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -288,6 +306,106 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any,
|
||||
return transactionResps, nil
|
||||
}
|
||||
|
||||
// TransactionListAllHandler returns all transaction list of current user
|
||||
func (a *TransactionsApi) TransactionListAllHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var transactionAllListReq models.TransactionAllListRequest
|
||||
err := c.ShouldBindQuery(&transactionAllListReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionListAllHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionListAllHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.Errorf(c, "[transactions.TransactionListAllHandler] failed to get user, because %s", err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
allAccountIds, err := a.accounts.GetAccountOrSubAccountIds(c, transactionAllListReq.AccountIds, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionListAllHandler] get account error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
allCategoryIds, err := a.transactionCategories.GetCategoryOrSubCategoryIds(c, transactionAllListReq.CategoryIds, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionListAllHandler] get transaction category error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
noTags := transactionAllListReq.TagFilter == models.TransactionNoTagFilterValue
|
||||
var tagFilters []*models.TransactionTagFilter
|
||||
|
||||
if !noTags {
|
||||
tagFilters, err = models.ParseTransactionTagFilter(transactionAllListReq.TagFilter)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionListAllHandler] parse transaction tag filters error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
maxTransactionTime := int64(math.MaxInt64)
|
||||
minTransactionTime := int64(0)
|
||||
|
||||
if transactionAllListReq.EndTime > 0 {
|
||||
maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(transactionAllListReq.EndTime)
|
||||
}
|
||||
|
||||
if transactionAllListReq.StartTime > 0 {
|
||||
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(transactionAllListReq.StartTime)
|
||||
}
|
||||
|
||||
allTransactions, err := a.transactions.GetAllSpecifiedTransactions(c, uid, maxTransactionTime, minTransactionTime, transactionAllListReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionAllListReq.AmountFilter, transactionAllListReq.Keyword, pageCountForDataExport, true)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionListAllHandler] failed to get all transactions for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
var accountMap map[int64]*models.Account
|
||||
var categoryMap map[int64]*models.TransactionCategory
|
||||
var tagMap map[int64]*models.TransactionTag
|
||||
var allTransactionTagIds map[int64][]int64
|
||||
var pictureInfoMap map[int64][]*models.TransactionPictureInfo
|
||||
|
||||
if minTransactionTime == 0 && maxTransactionTime == math.MaxInt64 && len(allCategoryIds) < 1 && len(allAccountIds) < 1 && len(tagFilters) < 1 && transactionAllListReq.AmountFilter == "" && transactionAllListReq.Keyword == "" {
|
||||
accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, err = a.getTransactionAllEssentialData(c, user, transactionAllListReq.WithPictures, transactionAllListReq.TrimCategory, transactionAllListReq.TrimTag)
|
||||
} else {
|
||||
accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, err = a.getTransactionEssentialDataByTransactionIds(c, user, allTransactions, transactionAllListReq.WithPictures, transactionAllListReq.TrimCategory, transactionAllListReq.TrimTag)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionListAllHandler] failed to get essential data for assembling transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
allTransactions = a.filterTransactions(c, uid, allTransactions, accountMap)
|
||||
transactionResult, err := a.getTransactionResponseListResult(c, user, allTransactions, accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, clientTimezone, transactionAllListReq.WithPictures, transactionAllListReq.TrimAccount, transactionAllListReq.TrimCategory, transactionAllListReq.TrimTag)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionListAllHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
return transactionResult, nil
|
||||
}
|
||||
|
||||
// TransactionReconciliationStatementHandler returns transaction reconciliation statement list of current user
|
||||
func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var reconciliationStatementRequest models.TransactionReconciliationStatementRequest
|
||||
@@ -298,10 +416,10 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionReconciliationStatementHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionReconciliationStatementHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -357,7 +475,24 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC
|
||||
transactionAccountBalanceMap[transactionWithBalance.RelatedId] = transactionWithBalance
|
||||
}
|
||||
|
||||
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, false, true, true, true)
|
||||
allAccountIds := make([]int64, 0, len(transactions)*2)
|
||||
|
||||
for i := 0; i < len(transactions); i++ {
|
||||
allAccountIds = append(allAccountIds, transactions[i].AccountId)
|
||||
|
||||
if transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN || transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||
allAccountIds = append(allAccountIds, transactions[i].RelatedAccountId)
|
||||
}
|
||||
}
|
||||
|
||||
allAccounts, err := a.accounts.GetAccountsByAccountIds(c, uid, utils.ToUniqueInt64Slice(allAccountIds))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to get essential data for assembling transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, allAccounts, nil, nil, nil, nil, clientTimezone, false, true, true, true)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -406,27 +541,27 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.WebContext) (any,
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
var allTagIds []int64
|
||||
noTags := statisticReq.TagIds == "none"
|
||||
noTags := statisticReq.TagFilter == models.TransactionNoTagFilterValue
|
||||
var tagFilters []*models.TransactionTagFilter
|
||||
|
||||
if !noTags {
|
||||
allTagIds, err = a.transactionTags.GetTagIds(statisticReq.TagIds)
|
||||
tagFilters, err = models.ParseTransactionTagFilter(statisticReq.TagFilter)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsHandler] get transaction tag ids error, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsHandler] parse transaction tag filters error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalInflowAndOutflow(c, uid, statisticReq.StartTime, statisticReq.EndTime, allTagIds, noTags, statisticReq.TagFilterType, statisticReq.Keyword, utcOffset, statisticReq.UseTransactionTimezone)
|
||||
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalInflowAndOutflow(c, uid, statisticReq.StartTime, statisticReq.EndTime, tagFilters, noTags, statisticReq.Keyword, clientTimezone, statisticReq.UseTransactionTimezone)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -467,10 +602,10 @@ func (a *TransactionsApi) TransactionStatisticsTrendsHandler(c *core.WebContext)
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsTrendsHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsTrendsHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -481,20 +616,20 @@ func (a *TransactionsApi) TransactionStatisticsTrendsHandler(c *core.WebContext)
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
var allTagIds []int64
|
||||
noTags := statisticTrendsReq.TagIds == "none"
|
||||
noTags := statisticTrendsReq.TagFilter == models.TransactionNoTagFilterValue
|
||||
var tagFilters []*models.TransactionTagFilter
|
||||
|
||||
if !noTags {
|
||||
allTagIds, err = a.transactionTags.GetTagIds(statisticTrendsReq.TagIds)
|
||||
tagFilters, err = models.ParseTransactionTagFilter(statisticTrendsReq.TagFilter)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsTrendsHandler] get transaction tag ids error, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsTrendsHandler] parse transaction tag filters error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
allMonthlyTotalAmounts, err := a.transactions.GetAccountsAndCategoriesMonthlyInflowAndOutflow(c, uid, startYear, startMonth, endYear, endMonth, allTagIds, noTags, statisticTrendsReq.TagFilterType, statisticTrendsReq.Keyword, utcOffset, statisticTrendsReq.UseTransactionTimezone)
|
||||
allMonthlyTotalAmounts, err := a.transactions.GetAccountsAndCategoriesMonthlyInflowAndOutflow(c, uid, startYear, startMonth, endYear, endMonth, tagFilters, noTags, statisticTrendsReq.Keyword, clientTimezone, statisticTrendsReq.UseTransactionTimezone)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionStatisticsTrendsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -542,10 +677,10 @@ func (a *TransactionsApi) TransactionStatisticsAssetTrendsHandler(c *core.WebCon
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -563,7 +698,7 @@ func (a *TransactionsApi) TransactionStatisticsAssetTrendsHandler(c *core.WebCon
|
||||
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(statisticAssetTrendsReq.StartTime)
|
||||
}
|
||||
|
||||
accountDailyBalances, err := a.transactions.GetAllAccountsDailyOpeningAndClosingBalance(c, uid, maxTransactionTime, minTransactionTime, utcOffset)
|
||||
accountDailyBalances, err := a.transactions.GetAllAccountsDailyOpeningAndClosingBalance(c, uid, maxTransactionTime, minTransactionTime, clientTimezone)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] failed to get transactions from \"%d\" to \"%d\" for user \"uid:%d\", because %s", statisticAssetTrendsReq.StartTime, statisticAssetTrendsReq.EndTime, uid, err.Error())
|
||||
@@ -643,10 +778,10 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.WebContext) (any, *e
|
||||
}
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionAmountsHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionAmountsHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -665,7 +800,7 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.WebContext) (any, *e
|
||||
for i := 0; i < len(requestItems); i++ {
|
||||
requestItem := requestItems[i]
|
||||
|
||||
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime, excludeAccountIds, excludeCategoryIds, utcOffset, transactionAmountsReq.UseTransactionTimezone)
|
||||
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime, excludeAccountIds, excludeCategoryIds, clientTimezone, transactionAmountsReq.UseTransactionTimezone)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionAmountsHandler] failed to get transaction amounts item for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -746,10 +881,10 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionGetHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionGetHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -835,7 +970,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
|
||||
}
|
||||
}
|
||||
|
||||
transactionEditable := transaction.IsEditable(user, utcOffset, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId])
|
||||
transactionEditable := transaction.IsEditable(user, clientTimezone, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId])
|
||||
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
|
||||
transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable)
|
||||
|
||||
@@ -876,6 +1011,13 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionCreateHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
tagIds, err := utils.StringArrayToInt64Array(transactionCreateReq.TagIds)
|
||||
|
||||
if err != nil {
|
||||
@@ -933,7 +1075,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
|
||||
}
|
||||
|
||||
transaction := a.createNewTransactionModel(uid, &transactionCreateReq, c.ClientIP())
|
||||
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset)
|
||||
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
|
||||
|
||||
if !transactionEditable {
|
||||
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
|
||||
@@ -1006,6 +1148,13 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionModifyHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
tagIds, err := utils.StringArrayToInt64Array(transactionModifyReq.TagIds)
|
||||
|
||||
if err != nil {
|
||||
@@ -1119,8 +1268,8 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transaction.TimezoneUtcOffset)
|
||||
newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, transactionModifyReq.UtcOffset)
|
||||
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
|
||||
newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, clientTimezone)
|
||||
|
||||
if !transactionEditable || !newTransactionEditable {
|
||||
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
|
||||
@@ -1256,10 +1405,10 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionDeleteHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionDeleteHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -1286,7 +1435,7 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, utcOffset)
|
||||
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
|
||||
|
||||
if !transactionEditable {
|
||||
return nil, errs.ErrCannotDeleteTransactionWithThisTransactionTime
|
||||
@@ -1303,13 +1452,13 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *er
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TransactionParseImportDsvFileDataHandler returns the parsed file data by request parameters for current user
|
||||
func (a *TransactionsApi) TransactionParseImportDsvFileDataHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
// TransactionParseImportCustomFileDataHandler returns the parsed file data by request parameters for current user
|
||||
func (a *TransactionsApi) TransactionParseImportCustomFileDataHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
form, err := c.MultipartForm()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionParseImportDsvFileDataHandler] failed to get multi-part form data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[transactions.TransactionParseImportCustomFileDataHandler] failed to get multi-part form data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrParameterInvalid
|
||||
}
|
||||
|
||||
@@ -1321,18 +1470,18 @@ func (a *TransactionsApi) TransactionParseImportDsvFileDataHandler(c *core.WebCo
|
||||
|
||||
fileType := fileTypes[0]
|
||||
|
||||
if !converters.IsCustomDelimiterSeparatedValuesFileType(fileType) {
|
||||
if !converters.IsCustomFileFormatFileType(fileType) {
|
||||
return nil, errs.Or(err, errs.ErrImportFileTypeNotSupported)
|
||||
}
|
||||
|
||||
fileEncodings := form.Value["fileEncoding"]
|
||||
fileEncoding := ""
|
||||
|
||||
if len(fileEncodings) < 1 || fileEncodings[0] == "" {
|
||||
return nil, errs.ErrImportFileEncodingIsEmpty
|
||||
if len(fileEncodings) > 0 {
|
||||
fileEncoding = fileEncodings[0]
|
||||
}
|
||||
|
||||
fileEncoding := fileEncodings[0]
|
||||
dataParser, err := converters.CreateNewDelimiterSeparatedValuesDataParser(fileType, fileEncoding)
|
||||
dataParser, err := converters.CreateNewCustomFileFormatTransactionDataParser(fileType, fileEncoding)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.Or(err, errs.ErrImportFileTypeNotSupported)
|
||||
@@ -1341,24 +1490,24 @@ func (a *TransactionsApi) TransactionParseImportDsvFileDataHandler(c *core.WebCo
|
||||
importFiles := form.File["file"]
|
||||
|
||||
if len(importFiles) < 1 {
|
||||
log.Warnf(c, "[transactions.TransactionParseImportDsvFileDataHandler] there is no import file in request for user \"uid:%d\"", uid)
|
||||
log.Warnf(c, "[transactions.TransactionParseImportCustomFileDataHandler] there is no import file in request for user \"uid:%d\"", uid)
|
||||
return nil, errs.ErrNoFilesUpload
|
||||
}
|
||||
|
||||
if importFiles[0].Size < 1 {
|
||||
log.Warnf(c, "[transactions.TransactionParseImportDsvFileDataHandler] the size of import file in request is zero for user \"uid:%d\"", uid)
|
||||
log.Warnf(c, "[transactions.TransactionParseImportCustomFileDataHandler] the size of import file in request is zero for user \"uid:%d\"", uid)
|
||||
return nil, errs.ErrUploadedFileEmpty
|
||||
}
|
||||
|
||||
if importFiles[0].Size > int64(a.CurrentConfig().MaxImportFileSize) {
|
||||
log.Warnf(c, "[transactions.TransactionParseImportDsvFileDataHandler] the upload file size \"%d\" exceeds the maximum size \"%d\" of import file for user \"uid:%d\"", importFiles[0].Size, a.CurrentConfig().MaxImportFileSize, uid)
|
||||
log.Warnf(c, "[transactions.TransactionParseImportCustomFileDataHandler] the upload file size \"%d\" exceeds the maximum size \"%d\" of import file for user \"uid:%d\"", importFiles[0].Size, a.CurrentConfig().MaxImportFileSize, uid)
|
||||
return nil, errs.ErrExceedMaxUploadFileSize
|
||||
}
|
||||
|
||||
importFile, err := importFiles[0].Open()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionParseImportDsvFileDataHandler] failed to get import file from request for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[transactions.TransactionParseImportCustomFileDataHandler] failed to get import file from request for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
@@ -1366,14 +1515,14 @@ func (a *TransactionsApi) TransactionParseImportDsvFileDataHandler(c *core.WebCo
|
||||
fileData, err := io.ReadAll(importFile)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionParseImportDsvFileDataHandler] failed to read import file data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[transactions.TransactionParseImportCustomFileDataHandler] failed to read import file data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
allLines, err := dataParser.ParseDsvFileLines(c, fileData)
|
||||
allLines, err := dataParser.ParseDataLines(c, fileData)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionParseImportDsvFileDataHandler] failed to parse import file data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Errorf(c, "[transactions.TransactionParseImportCustomFileDataHandler] failed to parse import file data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
@@ -1390,10 +1539,10 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
|
||||
return nil, errs.ErrParameterInvalid
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionParseImportFileHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
log.Warnf(c, "[transactions.TransactionParseImportFileHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
@@ -1405,17 +1554,25 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
|
||||
|
||||
fileType := fileTypes[0]
|
||||
|
||||
textualOptions := form.Value["options"]
|
||||
textualOption := ""
|
||||
|
||||
if len(textualOptions) > 0 {
|
||||
textualOption = textualOptions[0]
|
||||
}
|
||||
|
||||
additionalOptions := converter.ParseImporterOptions(textualOption)
|
||||
|
||||
var dataImporter converter.TransactionDataImporter
|
||||
|
||||
if converters.IsCustomDelimiterSeparatedValuesFileType(fileType) {
|
||||
if converters.IsCustomFileFormatFileType(fileType) {
|
||||
fileEncodings := form.Value["fileEncoding"]
|
||||
fileEncoding := ""
|
||||
|
||||
if len(fileEncodings) < 1 || fileEncodings[0] == "" {
|
||||
return nil, errs.ErrImportFileEncodingIsEmpty
|
||||
if len(fileEncodings) > 0 {
|
||||
fileEncoding = fileEncodings[0]
|
||||
}
|
||||
|
||||
fileEncoding := fileEncodings[0]
|
||||
|
||||
columnMappings := form.Value["columnMapping"]
|
||||
|
||||
if len(columnMappings) < 1 || columnMappings[0] == "" {
|
||||
@@ -1499,7 +1656,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
|
||||
transactionTagSeparator = transactionTagSeparators[0]
|
||||
}
|
||||
|
||||
dataImporter, err = converters.CreateNewDelimiterSeparatedValuesDataImporter(fileType, fileEncoding, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormats[0], timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoLocationSeparator, geoLocationOrder, transactionTagSeparator)
|
||||
dataImporter, err = converters.CreateNewCustomTransactionDataImporter(fileType, fileEncoding, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormats[0], timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoLocationSeparator, geoLocationOrder, transactionTagSeparator)
|
||||
} else {
|
||||
dataImporter, err = converters.GetTransactionDataImporter(fileType)
|
||||
}
|
||||
@@ -1581,7 +1738,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
|
||||
|
||||
tagMap := a.transactionTags.GetVisibleTagNameMapByList(tags)
|
||||
|
||||
parsedTransactions, _, _, _, _, _, err := dataImporter.ParseImportedData(c, user, fileData, utcOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
parsedTransactions, _, _, _, _, _, err := dataImporter.ParseImportedData(c, user, fileData, clientTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionParseImportFileHandler] failed to parse imported data for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
@@ -1612,6 +1769,13 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
clientTimezone, err := c.GetClientTimezone()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionImportHandler] cannot get client timezone, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
if a.CurrentConfig().EnableDuplicateSubmissionsCheck && transactionImportReq.ClientSessionId != "" {
|
||||
@@ -1697,7 +1861,7 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
|
||||
for i := 0; i < len(transactionImportReq.Transactions); i++ {
|
||||
transactionCreateReq := transactionImportReq.Transactions[i]
|
||||
transaction := a.createNewTransactionModel(uid, transactionCreateReq, c.ClientIP())
|
||||
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset)
|
||||
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
|
||||
|
||||
if !transactionEditable {
|
||||
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
|
||||
@@ -1814,7 +1978,61 @@ func (a *TransactionsApi) getTransactionTagInfoResponses(tagIds []int64, allTran
|
||||
return allTags
|
||||
}
|
||||
|
||||
func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, user *models.User, transactions []*models.Transaction, utcOffset int16, withPictures bool, trimAccount bool, trimCategory bool, trimTag bool) (models.TransactionInfoResponseSlice, error) {
|
||||
func (a *TransactionsApi) getTransactionAllEssentialData(c *core.WebContext, user *models.User, withPictures bool, trimCategory bool, trimTag bool) (accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTransactionTagIds map[int64][]int64, pictureInfoMap map[int64][]*models.TransactionPictureInfo, err error) {
|
||||
uid := user.Uid
|
||||
allAccounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionAllEssentialData] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
accountMap = a.accounts.GetAccountMapByList(allAccounts)
|
||||
|
||||
allTagIndexes, err := a.transactionTags.GetAllTagIdsOfAllTransactions(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionAllEssentialData] failed to get all transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
allTransactionTagIds = a.transactionTags.GetGroupedTransactionTagIds(allTagIndexes)
|
||||
|
||||
if !trimCategory {
|
||||
allCategories, err := a.transactionCategories.GetAllCategoriesByUid(c, uid, 0, -1)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionAllEssentialData] failed to get all transactions categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
categoryMap = a.transactionCategories.GetCategoryMapByList(allCategories)
|
||||
}
|
||||
|
||||
if !trimTag {
|
||||
allTags, err := a.transactionTags.GetAllTagsByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionAllEssentialData] failed to get all transactions tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
tagMap = a.transactionTags.GetTagMapByList(allTags)
|
||||
}
|
||||
|
||||
if withPictures && a.CurrentConfig().EnableTransactionPictures {
|
||||
pictureInfoMap, err = a.transactionPictures.GetAllPictureInfosOfAllTransactions(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionAllEssentialData] failed to get all transactions pictures for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, nil
|
||||
}
|
||||
|
||||
func (a *TransactionsApi) getTransactionEssentialDataByTransactionIds(c *core.WebContext, user *models.User, transactions []*models.Transaction, withPictures bool, trimCategory bool, trimTag bool) (accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTransactionTagIds map[int64][]int64, pictureInfoMap map[int64][]*models.TransactionPictureInfo, err error) {
|
||||
uid := user.Uid
|
||||
transactionIds := make([]int64, len(transactions))
|
||||
accountIds := make([]int64, 0, len(transactions)*2)
|
||||
@@ -1837,32 +2055,26 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u
|
||||
categoryIds = append(categoryIds, transactions[i].CategoryId)
|
||||
}
|
||||
|
||||
allAccounts, err := a.accounts.GetAccountsByAccountIds(c, uid, utils.ToUniqueInt64Slice(accountIds))
|
||||
accountMap, err = a.accounts.GetAccountsByAccountIds(c, uid, utils.ToUniqueInt64Slice(accountIds))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, err
|
||||
log.Errorf(c, "[transactions.getTransactionEssentialDataByTransactionIds] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
transactions = a.filterTransactions(c, uid, transactions, allAccounts)
|
||||
|
||||
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(c, uid, transactionIds)
|
||||
allTransactionTagIds, err = a.transactionTags.GetAllTagIdsOfTransactions(c, uid, transactionIds)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, err
|
||||
log.Errorf(c, "[transactions.getTransactionEssentialDataByTransactionIds] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
var categoryMap map[int64]*models.TransactionCategory
|
||||
var tagMap map[int64]*models.TransactionTag
|
||||
var pictureInfoMap map[int64][]*models.TransactionPictureInfo
|
||||
|
||||
if !trimCategory {
|
||||
categoryMap, err = a.transactionCategories.GetCategoriesByCategoryIds(c, uid, utils.ToUniqueInt64Slice(categoryIds))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get transactions categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, err
|
||||
log.Errorf(c, "[transactions.getTransactionEssentialDataByTransactionIds] failed to get transactions categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1870,8 +2082,8 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u
|
||||
tagMap, err = a.transactionTags.GetTagsByTagIds(c, uid, utils.ToUniqueInt64Slice(a.transactionTags.GetTransactionTagIds(allTransactionTagIds)))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, err
|
||||
log.Errorf(c, "[transactions.getTransactionEssentialDataByTransactionIds] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1879,11 +2091,15 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u
|
||||
pictureInfoMap, err = a.transactionPictures.GetPictureInfosByTransactionIds(c, uid, utils.ToUniqueInt64Slice(a.transactions.GetTransactionIds(transactions)))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get transactions pictures for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, err
|
||||
log.Errorf(c, "[transactions.getTransactionEssentialDataByTransactionIds] failed to get transactions pictures for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, nil
|
||||
}
|
||||
|
||||
func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, user *models.User, transactions []*models.Transaction, allAccounts map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTransactionTagIds map[int64][]int64, pictureInfoMap map[int64][]*models.TransactionPictureInfo, clientTimezone *time.Location, withPictures bool, trimAccount bool, trimCategory bool, trimTag bool) (models.TransactionInfoResponseSlice, error) {
|
||||
result := make(models.TransactionInfoResponseSlice, len(transactions))
|
||||
|
||||
for i := 0; i < len(transactions); i++ {
|
||||
@@ -1893,7 +2109,7 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u
|
||||
transaction = a.transactions.GetRelatedTransferTransaction(transaction)
|
||||
}
|
||||
|
||||
transactionEditable := transaction.IsEditable(user, utcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId])
|
||||
transactionEditable := transaction.IsEditable(user, clientTimezone, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId])
|
||||
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
|
||||
result[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable)
|
||||
|
||||
@@ -1907,17 +2123,17 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u
|
||||
}
|
||||
}
|
||||
|
||||
if !trimCategory {
|
||||
if !trimCategory && categoryMap != nil {
|
||||
if category := categoryMap[transaction.CategoryId]; category != nil {
|
||||
result[i].Category = category.ToTransactionCategoryInfoResponse()
|
||||
}
|
||||
}
|
||||
|
||||
if !trimTag {
|
||||
if !trimTag && tagMap != nil && transactionTagIds != nil {
|
||||
result[i].Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap)
|
||||
}
|
||||
|
||||
if withPictures && a.CurrentConfig().EnableTransactionPictures {
|
||||
if withPictures && a.CurrentConfig().EnableTransactionPictures && pictureInfoMap != nil {
|
||||
pictureInfos, exists := pictureInfoMap[transaction.TransactionId]
|
||||
|
||||
if exists {
|
||||
|
||||
@@ -85,7 +85,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.WebCo
|
||||
return nil, errs.ErrNotPermittedToPerformThisAction
|
||||
}
|
||||
|
||||
key, err := a.twoFactorAuthorizations.GenerateTwoFactorSecret(c, user)
|
||||
key, err := a.twoFactorAuthorizations.GenerateTwoFactorSecret(c, user, c.GetClientLocale())
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to generate two-factor secret, because %s", err.Error())
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider/oidc"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// OAuth2Container contains the current OAuth 2.0 authentication provider
|
||||
@@ -67,7 +67,7 @@ func InitializeOAuth2Provider(config *settings.Config) error {
|
||||
|
||||
Container.current = oauth2Provider
|
||||
Container.usePKCE = config.OAuth2UsePKCE
|
||||
Container.oauth2HttpClient = utils.NewHttpClient(config.OAuth2RequestTimeout, config.OAuth2Proxy, config.OAuth2SkipTLSVerify, settings.GetUserAgent())
|
||||
Container.oauth2HttpClient = httpclient.NewHttpClient(config.OAuth2RequestTimeout, config.OAuth2Proxy, config.OAuth2SkipTLSVerify, core.GetOutgoingUserAgent(), config.EnableDebugLog)
|
||||
Container.externalUserAuthType = externalUserAuthType
|
||||
|
||||
return nil
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
@@ -59,6 +60,11 @@ func (p *CommonOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
||||
}
|
||||
|
||||
oauth2Client := oauth2.NewClient(c, oauth2.StaticTokenSource(oauth2Token))
|
||||
|
||||
req = req.WithContext(httpclient.CustomHttpResponseLog(c, func(data []byte) {
|
||||
log.Debugf(c, "[common_oauth2_provider.GetUserInfo] response is %s", data)
|
||||
}))
|
||||
|
||||
resp, err := oauth2Client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
@@ -69,8 +75,6 @@ func (p *CommonOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
log.Debugf(c, "[common_oauth2_provider.GetUserInfo] response is %s", body)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Errorf(c, "[common_oauth2_provider.GetUserInfo] failed to get user info response, because response code is %d", resp.StatusCode)
|
||||
return nil, errs.ErrFailedToRequestRemoteApi
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
@@ -61,6 +62,11 @@ func (p *GithubOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
||||
}
|
||||
|
||||
oauth2Client := oauth2.NewClient(c, oauth2.StaticTokenSource(oauth2Token))
|
||||
|
||||
req = req.WithContext(httpclient.CustomHttpResponseLog(c, func(data []byte) {
|
||||
log.Debugf(c, "[github_oauth2_provider.GetUserInfo] user profile response is %s", data)
|
||||
}))
|
||||
|
||||
resp, err := oauth2Client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
@@ -71,8 +77,6 @@ func (p *GithubOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
log.Debugf(c, "[github_oauth2_provider.GetUserInfo] user profile response is %s", body)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Errorf(c, "[github_oauth2_provider.GetUserInfo] failed to get user info response, because response code is %d", resp.StatusCode)
|
||||
return nil, errs.ErrFailedToRequestRemoteApi
|
||||
@@ -92,6 +96,10 @@ func (p *GithubOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
||||
return nil, errs.ErrFailedToRequestRemoteApi
|
||||
}
|
||||
|
||||
req = req.WithContext(httpclient.CustomHttpResponseLog(c, func(data []byte) {
|
||||
log.Debugf(c, "[github_oauth2_provider.GetUserInfo] user emails response is %s", data)
|
||||
}))
|
||||
|
||||
resp, err = oauth2Client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
@@ -102,8 +110,6 @@ func (p *GithubOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
||||
defer resp.Body.Close()
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
|
||||
log.Debugf(c, "[github_oauth2_provider.GetUserInfo] user emails response is %s", body)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Errorf(c, "[github_oauth2_provider.GetUserInfo] failed to get user emails response, because response code is %d", resp.StatusCode)
|
||||
return nil, errs.ErrFailedToRequestRemoteApi
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
// OIDCClaims represents OIDC claims
|
||||
type OIDCClaims struct {
|
||||
PreferredUserName string `json:"preferred_username"`
|
||||
UserName string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
@@ -92,7 +94,9 @@ func (p *OIDCProvider) GetUserInfo(c core.Context, oauth2Token *oauth2.Token) (*
|
||||
nickName := claims.Name
|
||||
|
||||
if userName == "" || email == "" || nickName == "" {
|
||||
userInfo, err := p.oidcProvider.UserInfo(c, oauth2.StaticTokenSource(oauth2Token))
|
||||
userInfo, err := p.oidcProvider.UserInfo(httpclient.CustomHttpResponseLog(c, func(data []byte) {
|
||||
log.Debugf(c, "[oidc_provider.GetUserInfo] response is %s", data)
|
||||
}), oauth2.StaticTokenSource(oauth2Token))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[oidc_provider.GetUserInfo] failed to get user info, because %s", err.Error())
|
||||
@@ -110,6 +114,10 @@ func (p *OIDCProvider) GetUserInfo(c core.Context, oauth2Token *oauth2.Token) (*
|
||||
userName = claims.PreferredUserName
|
||||
}
|
||||
|
||||
if userName == "" {
|
||||
userName = claims.UserName
|
||||
}
|
||||
|
||||
if email == "" {
|
||||
email = claims.Email
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
@@ -149,7 +150,7 @@ func (l *UserDataCli) ModifyUserPassword(c *core.CliContext, username string, pa
|
||||
Password: password,
|
||||
}
|
||||
|
||||
_, _, err = l.users.UpdateUser(c, userNew, false)
|
||||
err = l.users.UpdateUserPassword(c, userNew)
|
||||
|
||||
if err != nil {
|
||||
log.CliErrorf(c, "[user_data.ModifyUserPassword] failed to update user \"%s\" password, because %s", user.Username, err.Error())
|
||||
@@ -818,7 +819,7 @@ func (l *UserDataCli) ImportTransaction(c *core.CliContext, username string, fil
|
||||
return err
|
||||
}
|
||||
|
||||
parsedTransactions, newAccounts, newSubExpenseCategories, newSubIncomeCategories, newSubTransferCategories, newTags, err := dataImporter.ParseImportedData(c, user, data, utils.GetTimezoneOffsetMinutes(time.Local), accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
parsedTransactions, newAccounts, newSubExpenseCategories, newSubIncomeCategories, newSubTransferCategories, newTags, err := dataImporter.ParseImportedData(c, user, data, time.Local, converter.DefaultImporterOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
|
||||
if err != nil {
|
||||
log.CliErrorf(c, "[user_data.ImportTransaction] failed to parse imported data for \"%s\", because %s", username, err.Error())
|
||||
|
||||
@@ -2,6 +2,7 @@ package alipay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
@@ -53,7 +54,7 @@ type alipayTransactionDataCsvFileImporter struct {
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the alipay transaction csv data
|
||||
func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
enc := simplifiedchinese.GB18030
|
||||
reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder())
|
||||
|
||||
@@ -83,5 +84,5 @@ func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Contex
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, alipayTransactionSupportedColumns, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(alipayTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -35,7 +36,7 @@ func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 4, len(allNewTransactions))
|
||||
@@ -94,7 +95,7 @@ func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -112,7 +113,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||
@@ -132,7 +133,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||
@@ -144,7 +145,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_ParseInvestmentRefundTransaction(t *testing.T) {
|
||||
converter := AlipayAppTransactionDataCsvFileImporter
|
||||
importer := AlipayAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -163,7 +164,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvestmentRefundTransaction
|
||||
"2024-09-01 01:00:00,Test Account2,xxx-买入,不计收支,0.01,Test Account,退款成功,\n" +
|
||||
"2024-09-01 02:00:00,Test Account2,xxx-买入退款,不计收支,0.01,Test Account,退款成功,\n")
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(allNewTransactions))
|
||||
@@ -184,7 +185,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvestmentRefundTransaction
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -201,7 +202,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
data2, err := simplifiedchinese.GB18030.NewEncoder().String("支付宝交易记录明细查询\n" +
|
||||
@@ -213,12 +214,12 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -235,12 +236,12 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -258,7 +259,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -274,7 +275,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -290,7 +291,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -307,7 +308,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -324,7 +325,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data5), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -341,7 +342,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data6), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -358,7 +359,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data7), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data7), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -367,7 +368,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
|
||||
converter := AlipayAppTransactionDataCsvFileImporter
|
||||
importer := AlipayAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -388,7 +389,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
|
||||
"2024-09-01 23:59:59,Test Category3,充值-普通充值,不计收支,0.05,交易成功,\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 3, len(allNewTransactions))
|
||||
@@ -407,7 +408,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_ParseRelatedAccount(t *testing.T) {
|
||||
converter := AlipayAppTransactionDataCsvFileImporter
|
||||
importer := AlipayAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -434,7 +435,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRelatedAccount(t *testing.T
|
||||
"2024-09-01 08:00:00,Test Account4,信用卡还款,不计收支,0.01,Test Account,还款成功,repayment,\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 9, len(allNewTransactions))
|
||||
@@ -529,7 +530,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRelatedAccount(t *testing.T
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -546,7 +547,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -561,7 +562,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -569,7 +570,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_SkipClosedIncomeOrTransferTransaction(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -586,12 +587,12 @@ func TestAlipayCsvFileImporterParseImportedData_SkipClosedIncomeOrTransferTransa
|
||||
"2024-09-01 23:59:59 ,充值-普通充值 ,0.05 ,不计收支 ,交易关闭 ,\n" +
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_SkipUnknownProductTransferTransaction(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -607,12 +608,12 @@ func TestAlipayCsvFileImporterParseImportedData_SkipUnknownProductTransferTransa
|
||||
"2024-09-01 23:59:59 ,xxxx ,0.05 ,不计收支 ,交易成功 ,\n" +
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -628,12 +629,12 @@ func TestAlipayCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *
|
||||
"2024-09-01 01:23:45 ,xxxx ,0.12 ,收入 ,xxxx ,\n" +
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -647,15 +648,15 @@ func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T)
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -671,7 +672,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
||||
"金额(元),收/支 ,交易状态 ,\n" +
|
||||
"0.12 ,收入 ,交易成功 ,\n" +
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
@@ -682,7 +683,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
||||
"交易创建时间 ,收/支 ,交易状态 ,\n" +
|
||||
"2024-09-01 12:34:56 ,收入 ,交易成功 ,\n" +
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Status Column
|
||||
@@ -693,7 +694,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
||||
"交易创建时间 ,金额(元),收/支 ,\n" +
|
||||
"2024-09-01 12:34:56 ,0.12 ,收入 ,\n" +
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Type Column
|
||||
@@ -704,12 +705,12 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
||||
"交易创建时间 ,金额(元),交易状态 ,\n" +
|
||||
"2024-09-01 12:34:56 ,0.12 ,交易成功 ,\n" +
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
func TestAlipayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
||||
converter := AlipayWebTransactionDataCsvFileImporter
|
||||
importer := AlipayWebTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -723,6 +724,6 @@ func TestAlipayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T)
|
||||
"---------------------------------交易记录明细列表------------------------------------\n" +
|
||||
"交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" +
|
||||
"------------------------------------------------------------------------------------\n")
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
const alipayTransactionDataStatusSuccessName = "交易成功"
|
||||
const alipayTransactionDataStatusPaymentSuccessName = "支付成功"
|
||||
const alipayTransactionDataStatusPendingGoodsReceiptConfirmationName = "等待确认收货"
|
||||
const alipayTransactionDataStatusRepaymentSuccessName = "还款成功"
|
||||
const alipayTransactionDataStatusClosedName = "交易关闭"
|
||||
const alipayTransactionDataStatusRefundSuccessName = "退款成功"
|
||||
@@ -46,6 +47,7 @@ func (p *alipayTransactionDataRowParser) Parse(ctx core.Context, user *models.Us
|
||||
|
||||
if dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusSuccessName &&
|
||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusPaymentSuccessName &&
|
||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusPendingGoodsReceiptConfirmationName &&
|
||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusRepaymentSuccessName &&
|
||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusClosedName &&
|
||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusRefundSuccessName &&
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package beancount
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -24,7 +26,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the Beancount transaction data
|
||||
func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
beancountDataReader, err := createNewBeancountDataReader(ctx, data)
|
||||
|
||||
if err != nil {
|
||||
@@ -45,5 +47,5 @@ func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
|
||||
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(beancountTransactionTypeNameMapping, "", "", BEANCOUNT_TRANSACTION_TAG_SEPARATOR)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package beancount
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -12,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -20,7 +22,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 *\n"+
|
||||
" Equity:Opening-Balances -123.45 CNY\n"+
|
||||
" Assets:TestAccount 123.45 CNY\n"+
|
||||
@@ -32,7 +34,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testi
|
||||
" Expenses:TestCategory2 1.00 CNY\n"+
|
||||
"2024-09-04 *\n"+
|
||||
" Assets:TestAccount -0.05 CNY\n"+
|
||||
" Assets:TestAccount2 0.05 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount2 0.05 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -91,7 +93,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testi
|
||||
}
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -99,7 +101,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *test
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 *\n"+
|
||||
" Assets:TestAccount 123.45 CNY\n"+
|
||||
" Equity:Opening-Balances -123.45 CNY\n"+
|
||||
@@ -111,7 +113,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *test
|
||||
" Assets:TestAccount -1.00 CNY\n"+
|
||||
"2024-09-04 *\n"+
|
||||
" Assets:TestAccount2 0.05 CNY\n"+
|
||||
" Assets:TestAccount -0.05 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount -0.05 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -170,7 +172,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *test
|
||||
}
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -178,15 +180,15 @@ func TestBeancountTransactionDataFileParseImportedData_ParseInvalidTime(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024/09/01 *\n"+
|
||||
" Equity:Opening-Balances -123.45 CNY\n"+
|
||||
" Assets:TestAccount 123.45 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount 123.45 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -194,10 +196,10 @@ func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||
" Assets:TestAccount -0.12 USD\n"+
|
||||
" Assets:TestAccount2 0.84 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount2 0.84 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -222,7 +224,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *tes
|
||||
}
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -230,21 +232,21 @@ func TestBeancountTransactionDataFileParseImportedData_ParseInvalidAmount(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 *\n"+
|
||||
" Equity:Opening-Balances -abc CNY\n"+
|
||||
" Assets:TestAccount abc CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount abc CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 *\n"+
|
||||
" Equity:Opening-Balances -1/0 CNY\n"+
|
||||
" Assets:TestAccount 1/0 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount 1/0 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -252,13 +254,13 @@ func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 * \"foo bar\t#test\n\"\n"+
|
||||
" Equity:Opening-Balances -123.45 CNY\n"+
|
||||
" Assets:TestAccount 123.45 CNY\n"+
|
||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||
" Income:TestCategory -0.12 CNY\n"+
|
||||
" Assets:TestAccount 0.12 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount 0.12 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -269,7 +271,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testi
|
||||
}
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_InvalidTransaction(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -277,33 +279,33 @@ func TestBeancountTransactionDataFileParseImportedData_InvalidTransaction(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||
" Assets:TestAccount 0.11 CNY\n"+
|
||||
" Assets:TestAccount2 0.11 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount2 0.11 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||
" Expenses:TestCategory -0.11 CNY\n"+
|
||||
" Expenses:TestCategory2 0.11 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Expenses:TestCategory2 0.11 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||
" Income:TestCategory -0.11 CNY\n"+
|
||||
" Income:TestCategory2 0.11 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Income:TestCategory2 0.11 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||
" Equity:TestCategory -0.11 CNY\n"+
|
||||
" Equity:TestCategory2 0.11 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Equity:TestCategory2 0.11 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message)
|
||||
}
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_NotSupportedToParseSplitTransaction(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -311,16 +313,16 @@ func TestBeancountTransactionDataFileParseImportedData_NotSupportedToParseSplitT
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||
" Assets:TestAccount -0.23 CNY\n"+
|
||||
" Assets:TestAccount2 0.11 CNY\n"+
|
||||
" Assets:TestAccount3 0.12 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount3 0.12 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||
}
|
||||
|
||||
func TestBeancountTransactionDataFileParseImportedData_MissingTransactionRequiredData(t *testing.T) {
|
||||
converter := BeancountTransactionDataImporter
|
||||
importer := BeancountTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -329,30 +331,30 @@ func TestBeancountTransactionDataFileParseImportedData_MissingTransactionRequire
|
||||
}
|
||||
|
||||
// Missing Transaction Time
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"* \"narration\"\n"+
|
||||
" Equity:Opening-Balances -123.45 CNY\n"+
|
||||
" Assets:TestAccount 123.45 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount 123.45 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
|
||||
// Missing Account Name
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 * \"narration\"\n"+
|
||||
" Equity:Opening-Balances -123.45 CNY\n"+
|
||||
" 123.45 CNY\n"), 0, nil, nil, nil, nil, nil)
|
||||
" 123.45 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
|
||||
|
||||
// Missing Amount
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 * \"narration\"\n"+
|
||||
" Equity:Opening-Balances\n"+
|
||||
" Assets:TestAccount\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
|
||||
|
||||
// Missing Commodity
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 * \"narration\"\n"+
|
||||
" Equity:Opening-Balances -123.45\n"+
|
||||
" Assets:TestAccount 123.45\n"), 0, nil, nil, nil, nil, nil)
|
||||
" Assets:TestAccount 123.45\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
|
||||
}
|
||||
|
||||
@@ -9,11 +9,20 @@ const (
|
||||
CAMT_INDICATOR_DEBIT camtCreditDebitIndicator = "DBIT"
|
||||
)
|
||||
|
||||
type camt052File struct {
|
||||
XMLName xml.Name `xml:"Document"`
|
||||
BankToCustomerAccountReport *camtBankToCustomerAccountReport `xml:"BkToCstmrAcctRpt"`
|
||||
}
|
||||
|
||||
type camt053File struct {
|
||||
XMLName xml.Name `xml:"Document"`
|
||||
BankToCustomerStatement *camtBankToCustomerStatement `xml:"BkToCstmrStmt"`
|
||||
}
|
||||
|
||||
type camtBankToCustomerAccountReport struct {
|
||||
Statements []*camtStatement `xml:"Rpt"`
|
||||
}
|
||||
|
||||
type camtBankToCustomerStatement struct {
|
||||
Statements []*camtStatement `xml:"Stmt"`
|
||||
}
|
||||
|
||||
@@ -10,11 +10,30 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
// camt052FileReader defines the structure of camt.052 file reader
|
||||
type camt052FileReader struct {
|
||||
xmlDecoder *xml.Decoder
|
||||
}
|
||||
|
||||
// camt053FileReader defines the structure of camt.053 file reader
|
||||
type camt053FileReader struct {
|
||||
xmlDecoder *xml.Decoder
|
||||
}
|
||||
|
||||
// read returns the imported camt.052 data
|
||||
// Reference: https://www.iso20022.org/message-set/1196/download
|
||||
func (r *camt052FileReader) read(ctx core.Context) (*camt052File, error) {
|
||||
file := &camt052File{}
|
||||
|
||||
err := r.xmlDecoder.Decode(&file)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// read returns the imported camt.053 data
|
||||
// Reference: https://www.iso20022.org/message-set/1196/download
|
||||
func (r *camt053FileReader) read(ctx core.Context) (*camt053File, error) {
|
||||
@@ -29,6 +48,19 @@ func (r *camt053FileReader) read(ctx core.Context) (*camt053File, error) {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func createNewCamt052FileReader(data []byte) (*camt052FileReader, error) {
|
||||
if len(data) > 5 && data[0] == 0x3C && data[1] == 0x3F && data[2] == 0x78 && data[3] == 0x6D && data[4] == 0x6C { // <?xml
|
||||
xmlDecoder := xml.NewDecoder(bytes.NewReader(data))
|
||||
xmlDecoder.CharsetReader = charset.NewReaderLabel
|
||||
|
||||
return &camt052FileReader{
|
||||
xmlDecoder: xmlDecoder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, errs.ErrInvalidXmlFile
|
||||
}
|
||||
|
||||
func createNewCamt053FileReader(data []byte) (*camt053FileReader, error) {
|
||||
if len(data) > 5 && data[0] == 0x3C && data[1] == 0x3F && data[2] == 0x78 && data[3] == 0x6D && data[4] == 0x6C { // <?xml
|
||||
xmlDecoder := xml.NewDecoder(bytes.NewReader(data))
|
||||
|
||||
@@ -231,7 +231,7 @@ func (t *camtStatementTransactionDataRowIterator) parseTransaction(ctx core.Cont
|
||||
}
|
||||
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location())
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
|
||||
} else if entry.BookingDate != nil && entry.BookingDate.Date != "" {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = fmt.Sprintf("%s 00:00:00", entry.BookingDate.Date)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = datatable.TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE
|
||||
@@ -303,12 +303,12 @@ func (t *camtStatementTransactionDataRowIterator) parseTransaction(ctx core.Cont
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func createNewCamtStatementTransactionDataTable(file *camt053File) (*camtStatementTransactionDataTable, error) {
|
||||
if file == nil || file.BankToCustomerStatement == nil || len(file.BankToCustomerStatement.Statements) == 0 {
|
||||
func createNewCamtStatementTransactionDataTable(camtStatements []*camtStatement) (*camtStatementTransactionDataTable, error) {
|
||||
if len(camtStatements) == 0 {
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
return &camtStatementTransactionDataTable{
|
||||
allStatements: file.BankToCustomerStatement.Statements,
|
||||
allStatements: camtStatements,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package camt
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
@@ -13,17 +16,51 @@ var camtTransactionTypeNameMapping = map[models.TransactionType]string{
|
||||
models.TRANSACTION_TYPE_TRANSFER: utils.IntToString(int(models.TRANSACTION_TYPE_TRANSFER)),
|
||||
}
|
||||
|
||||
// camt052TransactionDataImporter defines the structure of camt.052 file importer for transaction data
|
||||
type camt052TransactionDataImporter struct {
|
||||
}
|
||||
|
||||
// camt053TransactionDataImporter defines the structure of camt.053 file importer for transaction data
|
||||
type camt053TransactionDataImporter struct {
|
||||
}
|
||||
|
||||
// Initialize a camt.053 transaction data importer singleton instance
|
||||
// Initialize camt.052 and camt.053 transaction data importer singleton instances
|
||||
var (
|
||||
Camt052TransactionDataImporter = &camt052TransactionDataImporter{}
|
||||
Camt053TransactionDataImporter = &camt053TransactionDataImporter{}
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the camt.052 file transaction data
|
||||
func (c *camt052TransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
camt052DataReader, err := createNewCamt052FileReader(data)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
camt052Data, err := camt052DataReader.read(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
if camt052Data.BankToCustomerAccountReport == nil || camt052Data.BankToCustomerAccountReport.Statements == nil {
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
transactionDataTable, err := createNewCamtStatementTransactionDataTable(camt052Data.BankToCustomerAccountReport.Statements)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(camtTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the camt.053 file transaction data
|
||||
func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
camt053DataReader, err := createNewCamt053FileReader(data)
|
||||
|
||||
if err != nil {
|
||||
@@ -36,7 +73,11 @@ func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, use
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
transactionDataTable, err := createNewCamtStatementTransactionDataTable(camt053Data)
|
||||
if camt053Data.BankToCustomerStatement == nil || camt053Data.BankToCustomerStatement.Statements == nil {
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
transactionDataTable, err := createNewCamtStatementTransactionDataTable(camt053Data.BankToCustomerStatement.Statements)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
@@ -44,5 +85,5 @@ func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, use
|
||||
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(camtTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,19 @@ package camt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := Camt053TransactionDataImporter
|
||||
func TestCamt052TransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
importer := Camt052TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -20,7 +22,110 @@ func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.052.001.02">
|
||||
<BkToCstmrAcctRpt>
|
||||
<Rpt>
|
||||
<Acct>
|
||||
<Id>
|
||||
<IBAN>123</IBAN>
|
||||
</Id>
|
||||
<Ccy>CNY</Ccy>
|
||||
</Acct>
|
||||
<Ntry>
|
||||
<BookgDt>
|
||||
<DtTm>2024-09-01T01:23:45+08:00</DtTm>
|
||||
</BookgDt>
|
||||
<CdtDbtInd>CRDT</CdtDbtInd>
|
||||
<Amt Ccy="CNY">123.45</Amt>
|
||||
</Ntry>
|
||||
<Ntry>
|
||||
<BookgDt>
|
||||
<DtTm>2024-09-01T12:34:56+08:00</DtTm>
|
||||
</BookgDt>
|
||||
<CdtDbtInd>DBIT</CdtDbtInd>
|
||||
<Amt Ccy="CNY">0.12</Amt>
|
||||
</Ntry>
|
||||
</Rpt>
|
||||
<Rpt>
|
||||
<Acct>
|
||||
<Id>
|
||||
<Othr>
|
||||
<Id>456</Id>
|
||||
</Othr>
|
||||
</Id>
|
||||
<Ccy>USD</Ccy>
|
||||
</Acct>
|
||||
<Ntry>
|
||||
<BookgDt>
|
||||
<DtTm>2024-09-01T23:59:59+08:00</DtTm>
|
||||
</BookgDt>
|
||||
<CdtDbtInd>CRDT</CdtDbtInd>
|
||||
<Amt Ccy="USD">1.23</Amt>
|
||||
</Ntry>
|
||||
</Rpt>
|
||||
</BkToCstmrAcctRpt>
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 3, len(allNewTransactions))
|
||||
assert.Equal(t, 2, len(allNewAccounts))
|
||||
assert.Equal(t, 1, len(allNewSubExpenseCategories))
|
||||
assert.Equal(t, 1, len(allNewSubIncomeCategories))
|
||||
assert.Equal(t, 0, len(allNewSubTransferCategories))
|
||||
assert.Equal(t, 0, len(allNewTags))
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1725125025), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "123", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "CNY", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, "", allNewTransactions[0].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[1].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[1].Type)
|
||||
assert.Equal(t, int64(1725165296), utils.GetUnixTimeFromTransactionTime(allNewTransactions[1].TransactionTime))
|
||||
assert.Equal(t, int64(12), allNewTransactions[1].Amount)
|
||||
assert.Equal(t, "123", allNewTransactions[1].OriginalSourceAccountName)
|
||||
assert.Equal(t, "CNY", allNewTransactions[1].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, "", allNewTransactions[1].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[2].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[2].Type)
|
||||
assert.Equal(t, int64(1725206399), utils.GetUnixTimeFromTransactionTime(allNewTransactions[2].TransactionTime))
|
||||
assert.Equal(t, int64(123), allNewTransactions[2].Amount)
|
||||
assert.Equal(t, "456", allNewTransactions[2].OriginalSourceAccountName)
|
||||
assert.Equal(t, "USD", allNewTransactions[2].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, "", allNewTransactions[2].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[0].Uid)
|
||||
assert.Equal(t, "123", allNewAccounts[0].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[0].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[1].Uid)
|
||||
assert.Equal(t, "456", allNewAccounts[1].Name)
|
||||
assert.Equal(t, "USD", allNewAccounts[1].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewSubExpenseCategories[0].Uid)
|
||||
assert.Equal(t, "", allNewSubExpenseCategories[0].Name)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewSubIncomeCategories[0].Uid)
|
||||
assert.Equal(t, "", allNewSubIncomeCategories[0].Name)
|
||||
}
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
importer := Camt053TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -64,7 +169,7 @@ func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -115,7 +220,7 @@ func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing
|
||||
}
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t *testing.T) {
|
||||
converter := Camt053TransactionDataImporter
|
||||
importer := Camt053TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -123,7 +228,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -157,7 +262,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(allNewTransactions))
|
||||
@@ -168,7 +273,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t
|
||||
}
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *testing.T) {
|
||||
converter := Camt053TransactionDataImporter
|
||||
importer := Camt053TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -176,7 +281,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -196,10 +301,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -219,10 +324,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -242,10 +347,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -265,12 +370,12 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmountAndCurrency(t *testing.T) {
|
||||
converter := Camt053TransactionDataImporter
|
||||
importer := Camt053TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -278,7 +383,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -314,7 +419,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(allNewTransactions))
|
||||
@@ -323,7 +428,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
||||
assert.Equal(t, "USD", allNewTransactions[1].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, int64(10023), allNewTransactions[1].Amount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -365,7 +470,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(allNewTransactions))
|
||||
@@ -374,7 +479,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
||||
assert.Equal(t, "USD", allNewTransactions[1].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, int64(9999), allNewTransactions[1].Amount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -403,14 +508,14 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -430,7 +535,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -439,7 +544,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
||||
}
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmountAndCurrency(t *testing.T) {
|
||||
converter := Camt053TransactionDataImporter
|
||||
importer := Camt053TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -447,7 +552,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -467,10 +572,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -498,10 +603,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -529,12 +634,12 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := Camt053TransactionDataImporter
|
||||
importer := Camt053TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -542,7 +647,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -572,13 +677,13 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "Test Transaction", allNewTransactions[0].Comment)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -607,13 +712,13 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "Test Line 1\nTest Line 2", allNewTransactions[0].Comment)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -634,7 +739,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -642,7 +747,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
||||
}
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testing.T) {
|
||||
converter := Camt053TransactionDataImporter
|
||||
importer := Camt053TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -650,7 +755,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -664,12 +769,12 @@ func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testi
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingAccountData.Message)
|
||||
}
|
||||
|
||||
func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
||||
converter := Camt053TransactionDataImporter
|
||||
importer := Camt053TransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -677,7 +782,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -694,10 +799,10 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -716,10 +821,10 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -738,10 +843,10 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||
<BkToCstmrStmt>
|
||||
@@ -760,6 +865,6 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
||||
</Ntry>
|
||||
</Stmt>
|
||||
</BkToCstmrStmt>
|
||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
||||
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -28,10 +28,11 @@ func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context
|
||||
}
|
||||
|
||||
dataRowMap := make(map[datatable.TransactionDataTableColumn]string, 15)
|
||||
transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime)
|
||||
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||
|
||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionTimeZone)
|
||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(transactionUnixTime, transactionTimeZone)
|
||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionUnixTime, transactionTimeZone)
|
||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataTableBuilder.ReplaceDelimiters(c.getDisplayTransactionTypeName(transaction.Type))
|
||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_CATEGORY] = c.getExportedTransactionCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
||||
|
||||
@@ -3,6 +3,7 @@ package converter
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
@@ -29,7 +30,7 @@ type DataTableTransactionDataImporter struct {
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported transaction data
|
||||
func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, dataTable datatable.TransactionDataTable, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, dataTable datatable.TransactionDataTable, defaultTimezone *time.Location, additionalOptions TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
if dataTable.TransactionRowCount() < 1 {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_importer.ParseImportedData] cannot parse import data for user \"uid:%d\", because data table row count is less 1", user.Uid)
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile
|
||||
@@ -94,7 +95,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
continue
|
||||
}
|
||||
|
||||
timezoneOffset := defaultTimezoneOffset
|
||||
timezone := defaultTimezone
|
||||
|
||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) &&
|
||||
dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) != datatable.TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE {
|
||||
@@ -105,10 +106,10 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrTransactionTimeZoneInvalid
|
||||
}
|
||||
|
||||
timezoneOffset = utils.GetTimezoneOffsetMinutes(transactionTimezone)
|
||||
timezone = transactionTimezone
|
||||
}
|
||||
|
||||
transactionTime, err := utils.ParseFromLongDateTime(dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME), timezoneOffset)
|
||||
transactionTime, err := utils.ParseFromLongDateTimeInTimeZone(dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME), timezone)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_importer.ParseImportedData] cannot parse time \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME), dataRowIndex, user.Uid, err.Error())
|
||||
@@ -303,6 +304,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
|
||||
var tagIds []string
|
||||
var tagNames []string
|
||||
tagNamesMap := make(map[string]bool)
|
||||
|
||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TAGS) {
|
||||
var tagNameItems []string
|
||||
@@ -320,19 +322,39 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
continue
|
||||
}
|
||||
|
||||
tag, exists := tagMap[tagName]
|
||||
allNewTags, tagIds, tagNames = c.addTag(user, tagName, tagNamesMap, tagMap, allNewTags, tagIds, tagNames)
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
tag = c.createNewTransactionTagModel(user.Uid, tagName)
|
||||
allNewTags = append(allNewTags, tag)
|
||||
tagMap[tagName] = tag
|
||||
}
|
||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_PAYEE) && additionalOptions.IsPayeeAsTag() {
|
||||
payee := dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_PAYEE)
|
||||
|
||||
if tag != nil {
|
||||
tagIds = append(tagIds, utils.Int64ToString(tag.TagId))
|
||||
}
|
||||
if payee != "" {
|
||||
allNewTags, tagIds, tagNames = c.addTag(user, payee, tagNamesMap, tagMap, allNewTags, tagIds, tagNames)
|
||||
}
|
||||
}
|
||||
|
||||
tagNames = append(tagNames, tagName)
|
||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_MEMBER) && additionalOptions.IsMemberAsTag() {
|
||||
member := dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_MEMBER)
|
||||
|
||||
if member != "" {
|
||||
allNewTags, tagIds, tagNames = c.addTag(user, member, tagNamesMap, tagMap, allNewTags, tagIds, tagNames)
|
||||
}
|
||||
}
|
||||
|
||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_PROJECT) && additionalOptions.IsProjectAsTag() {
|
||||
project := dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_PROJECT)
|
||||
|
||||
if project != "" {
|
||||
allNewTags, tagIds, tagNames = c.addTag(user, project, tagNamesMap, tagMap, allNewTags, tagIds, tagNames)
|
||||
}
|
||||
}
|
||||
|
||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_MERCHANT) && additionalOptions.IsMerchantAsTag() {
|
||||
merchant := dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_MERCHANT)
|
||||
|
||||
if merchant != "" {
|
||||
allNewTags, tagIds, tagNames = c.addTag(user, merchant, tagNamesMap, tagMap, allNewTags, tagIds, tagNames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,13 +364,17 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
description = dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_DESCRIPTION)
|
||||
}
|
||||
|
||||
if description == "" && additionalOptions.IsPayeeAsDescription() && dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_PAYEE) {
|
||||
description = dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_PAYEE)
|
||||
}
|
||||
|
||||
transaction := &models.ImportTransaction{
|
||||
Transaction: &models.Transaction{
|
||||
Uid: user.Uid,
|
||||
Type: transactionDbType,
|
||||
CategoryId: categoryId,
|
||||
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
|
||||
TimezoneUtcOffset: timezoneOffset,
|
||||
TimezoneUtcOffset: utils.GetTimezoneOffsetMinutes(transactionTime.Unix(), timezone),
|
||||
AccountId: account.AccountId,
|
||||
Amount: amount,
|
||||
HideAmount: false,
|
||||
@@ -459,6 +485,27 @@ func (c *DataTableTransactionDataImporter) getTransactionCategory(categories map
|
||||
return subCategory, exists
|
||||
}
|
||||
|
||||
func (c *DataTableTransactionDataImporter) addTag(user *models.User, tagName string, tagNamesMap map[string]bool, tagMap map[string]*models.TransactionTag, allNewTags []*models.TransactionTag, tagIds []string, tagNames []string) ([]*models.TransactionTag, []string, []string) {
|
||||
if tagName != "" && !tagNamesMap[tagName] {
|
||||
tag, exists := tagMap[tagName]
|
||||
|
||||
if !exists {
|
||||
tag = c.createNewTransactionTagModel(user.Uid, tagName)
|
||||
allNewTags = append(allNewTags, tag)
|
||||
tagMap[tagName] = tag
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
tagIds = append(tagIds, utils.Int64ToString(tag.TagId))
|
||||
}
|
||||
|
||||
tagNames = append(tagNames, tagName)
|
||||
tagNamesMap[tagName] = true
|
||||
}
|
||||
|
||||
return allNewTags, tagIds, tagNames
|
||||
}
|
||||
|
||||
func (c *DataTableTransactionDataImporter) createNewAccountModel(uid int64, accountName string, currency string) *models.Account {
|
||||
return &models.Account{
|
||||
Uid: uid,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
@@ -14,7 +16,7 @@ type TransactionDataExporter interface {
|
||||
// TransactionDataImporter defines the structure of transaction data importer
|
||||
type TransactionDataImporter interface {
|
||||
// ParseImportedData returns the imported data
|
||||
ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error)
|
||||
ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error)
|
||||
}
|
||||
|
||||
// TransactionDataConverter defines the structure of transaction data converter
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package converter
|
||||
|
||||
import "strings"
|
||||
|
||||
// TransactionDataImporterOptions defines the options for transaction data importer
|
||||
type TransactionDataImporterOptions struct {
|
||||
payeeAsTag bool
|
||||
payeeAsDescription bool
|
||||
memberAsTag bool
|
||||
projectAsTag bool
|
||||
merchantAsTag bool
|
||||
}
|
||||
|
||||
// DefaultImporterOptions provides the default options for transaction data importer
|
||||
var DefaultImporterOptions = TransactionDataImporterOptions{
|
||||
payeeAsTag: false,
|
||||
payeeAsDescription: false,
|
||||
memberAsTag: false,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
|
||||
// IsPayeeAsTag returns whether to import payee as tag
|
||||
func (o TransactionDataImporterOptions) IsPayeeAsTag() bool {
|
||||
return o.payeeAsTag
|
||||
}
|
||||
|
||||
// IsPayeeAsDescription returns whether to import payee as description
|
||||
func (o TransactionDataImporterOptions) IsPayeeAsDescription() bool {
|
||||
return o.payeeAsDescription
|
||||
}
|
||||
|
||||
// IsMemberAsTag returns whether to import member as tag
|
||||
func (o TransactionDataImporterOptions) IsMemberAsTag() bool {
|
||||
return o.memberAsTag
|
||||
}
|
||||
|
||||
// IsProjectAsTag returns whether to import project as tag
|
||||
func (o TransactionDataImporterOptions) IsProjectAsTag() bool {
|
||||
return o.projectAsTag
|
||||
}
|
||||
|
||||
// IsMerchantAsTag returns whether to import merchant as tag
|
||||
func (o TransactionDataImporterOptions) IsMerchantAsTag() bool {
|
||||
return o.merchantAsTag
|
||||
}
|
||||
|
||||
// WithPayeeAsTag sets the option to import payee as tag
|
||||
func (o TransactionDataImporterOptions) WithPayeeAsTag() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.payeeAsTag = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// WithPayeeAsDescription sets the option to import payee as description
|
||||
func (o TransactionDataImporterOptions) WithPayeeAsDescription() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.payeeAsDescription = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// WithMemberAsTag sets the option to import member as tag
|
||||
func (o TransactionDataImporterOptions) WithMemberAsTag() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.memberAsTag = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// WithProjectAsTag sets the option to import project as tag
|
||||
func (o TransactionDataImporterOptions) WithProjectAsTag() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.projectAsTag = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// WithMerchantAsTag sets the option to import merchant as tag
|
||||
func (o TransactionDataImporterOptions) WithMerchantAsTag() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.merchantAsTag = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// Clone creates a copy of the options instance
|
||||
func (o TransactionDataImporterOptions) Clone() TransactionDataImporterOptions {
|
||||
return TransactionDataImporterOptions{
|
||||
payeeAsTag: o.payeeAsTag,
|
||||
payeeAsDescription: o.payeeAsDescription,
|
||||
memberAsTag: o.memberAsTag,
|
||||
projectAsTag: o.projectAsTag,
|
||||
merchantAsTag: o.merchantAsTag,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseImporterOptions parses the textual options to the instance
|
||||
func ParseImporterOptions(s string) TransactionDataImporterOptions {
|
||||
options := TransactionDataImporterOptions{}
|
||||
|
||||
if s == "" {
|
||||
return options
|
||||
}
|
||||
|
||||
for _, option := range strings.Split(s, ",") {
|
||||
switch option {
|
||||
case "payeeAsTag":
|
||||
options.payeeAsTag = true
|
||||
case "payeeAsDescription":
|
||||
options.payeeAsDescription = true
|
||||
case "memberAsTag":
|
||||
options.memberAsTag = true
|
||||
case "projectAsTag":
|
||||
options.projectAsTag = true
|
||||
case "merchantAsTag":
|
||||
options.merchantAsTag = true
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseImporterOptions(t *testing.T) {
|
||||
actualValue := ParseImporterOptions("payeeAsTag,memberAsTag")
|
||||
expectedValue := TransactionDataImporterOptions{
|
||||
payeeAsTag: true,
|
||||
memberAsTag: true,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, true, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, true, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, false, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMerchantAsTag())
|
||||
|
||||
actualValue = ParseImporterOptions("")
|
||||
expectedValue = TransactionDataImporterOptions{
|
||||
payeeAsTag: false,
|
||||
memberAsTag: false,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, false, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, false, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMerchantAsTag())
|
||||
}
|
||||
|
||||
func TestParseImporterOptions_WithAllOptions(t *testing.T) {
|
||||
actualValue := ParseImporterOptions("payeeAsTag,payeeAsDescription,memberAsTag,projectAsTag,merchantAsTag")
|
||||
expectedValue := TransactionDataImporterOptions{
|
||||
payeeAsTag: true,
|
||||
payeeAsDescription: true,
|
||||
memberAsTag: true,
|
||||
projectAsTag: true,
|
||||
merchantAsTag: true,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, true, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, true, actualValue.IsPayeeAsDescription())
|
||||
assert.Equal(t, true, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, true, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, true, actualValue.IsMerchantAsTag())
|
||||
}
|
||||
|
||||
func TestParseImporterOptions_WithInvalidOptions(t *testing.T) {
|
||||
actualValue := ParseImporterOptions("invalidOption,payeeAsTag,memberAsTag")
|
||||
expectedValue := TransactionDataImporterOptions{
|
||||
payeeAsTag: true,
|
||||
memberAsTag: true,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, true, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, true, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, false, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMerchantAsTag())
|
||||
|
||||
actualValue = ParseImporterOptions("invalidOption")
|
||||
expectedValue = TransactionDataImporterOptions{
|
||||
payeeAsTag: false,
|
||||
memberAsTag: false,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, false, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, false, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMerchantAsTag())
|
||||
}
|
||||
|
||||
func TestParseImporterOptions_Clone(t *testing.T) {
|
||||
original := TransactionDataImporterOptions{
|
||||
payeeAsTag: true,
|
||||
payeeAsDescription: false,
|
||||
memberAsTag: false,
|
||||
projectAsTag: true,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
|
||||
cloned := original.Clone()
|
||||
assert.Equal(t, original, cloned)
|
||||
|
||||
// Modify cloned options and verify original options are not affected
|
||||
cloned.payeeAsTag = false
|
||||
cloned.payeeAsDescription = true
|
||||
cloned.memberAsTag = true
|
||||
|
||||
assert.Equal(t, true, original.payeeAsTag)
|
||||
assert.Equal(t, false, original.payeeAsDescription)
|
||||
assert.Equal(t, false, original.memberAsTag)
|
||||
assert.Equal(t, true, original.projectAsTag)
|
||||
assert.Equal(t, false, original.merchantAsTag)
|
||||
|
||||
assert.Equal(t, false, cloned.payeeAsTag)
|
||||
assert.Equal(t, true, cloned.payeeAsDescription)
|
||||
assert.Equal(t, true, cloned.memberAsTag)
|
||||
assert.Equal(t, true, cloned.projectAsTag)
|
||||
assert.Equal(t, false, cloned.merchantAsTag)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package custom
|
||||
|
||||
import "github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
|
||||
// CustomTransactionDataParser represents the parser for custom transaction data files
|
||||
type CustomTransactionDataParser interface {
|
||||
ParseDataLines(ctx core.Context, data []byte) ([][]string, error)
|
||||
}
|
||||
+71
-63
@@ -1,10 +1,11 @@
|
||||
package dsv
|
||||
package custom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/encoding/traditionalchinese"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/encoding/unicode/utf32"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
@@ -28,59 +30,61 @@ import (
|
||||
var supportedFileTypeSeparators = map[string]rune{
|
||||
"custom_csv": ',',
|
||||
"custom_tsv": '\t',
|
||||
"custom_ssv": ';',
|
||||
}
|
||||
|
||||
var supportedFileEncodings = map[string]encoding.Encoding{
|
||||
"utf-8": unicode.UTF8, // UTF-8
|
||||
"utf-8-bom": unicode.UTF8BOM, // UTF-8 with BOM
|
||||
"utf-16le": unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), // UTF-16 Little Endian
|
||||
"utf-16be": unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM), // UTF-16 Big Endian
|
||||
"cp437": charmap.CodePage437, // OEM United States (CP-437)
|
||||
"cp863": charmap.CodePage863, // OEM Canadian French (CP-863)
|
||||
"cp037": charmap.CodePage037, // IBM EBCDIC US/Canada (CP-037)
|
||||
"cp1047": charmap.CodePage1047, // IBM EBCDIC Open Systems (CP-1047)
|
||||
"cp1140": charmap.CodePage1140, // IBM EBCDIC US/Canada with Euro (CP-1140)
|
||||
"iso-8859-1": charmap.ISO8859_1, // Western European (ISO-8859-1)
|
||||
"cp850": charmap.CodePage850, // Western European (CP-850)
|
||||
"cp858": charmap.CodePage858, // Western European with Euro (CP-858)
|
||||
"windows-1252": charmap.Windows1252, // Western European (Windows-1252)
|
||||
"iso-8859-15": charmap.ISO8859_15, // Western European (ISO-8859-15)
|
||||
"iso-8859-4": charmap.ISO8859_4, // North European (ISO-8859-4)
|
||||
"iso-8859-10": charmap.ISO8859_10, // North European (ISO-8859-10)
|
||||
"cp865": charmap.CodePage865, // North European (CP-865)
|
||||
"iso-8859-2": charmap.ISO8859_2, // Central European (ISO-8859-2)
|
||||
"cp852": charmap.CodePage852, // Central European (CP-852)
|
||||
"windows-1250": charmap.Windows1250, // Central European (Windows-1250)
|
||||
"iso-8859-14": charmap.ISO8859_14, // Celtic (ISO-8859-14)
|
||||
"iso-8859-3": charmap.ISO8859_3, // South European (ISO-8859-3)
|
||||
"cp860": charmap.CodePage860, // Portuguese (CP-860)
|
||||
"iso-8859-7": charmap.ISO8859_7, // Greek (ISO-8859-7)
|
||||
"windows-1253": charmap.Windows1253, // Greek (Windows-1253)
|
||||
"iso-8859-9": charmap.ISO8859_9, // Turkish (ISO-8859-9)
|
||||
"windows-1254": charmap.Windows1254, // Turkish (Windows-1254)
|
||||
"iso-8859-13": charmap.ISO8859_13, // Baltic (ISO-8859-13)
|
||||
"windows-1257": charmap.Windows1257, // Baltic (Windows-1257)
|
||||
"iso-8859-16": charmap.ISO8859_16, // South-Eastern European (ISO-8859-16)
|
||||
"iso-8859-5": charmap.ISO8859_5, // Cyrillic (ISO-8859-5)
|
||||
"cp855": charmap.CodePage855, // Cyrillic (CP-855)
|
||||
"cp866": charmap.CodePage866, // Cyrillic (CP-866)
|
||||
"windows-1251": charmap.Windows1251, // Cyrillic (Windows-1251)
|
||||
"koi8r": charmap.KOI8R, // Cyrillic (KOI8-R)
|
||||
"koi8u": charmap.KOI8U, // Cyrillic (KOI8-U)
|
||||
"iso-8859-6": charmap.ISO8859_6, // Arabic (ISO-8859-6)
|
||||
"windows-1256": charmap.Windows1256, // Arabic (Windows-1256)
|
||||
"iso-8859-8": charmap.ISO8859_8, // Hebrew (ISO-8859-8)
|
||||
"cp862": charmap.CodePage862, // Hebrew (CP-862)
|
||||
"windows-1255": charmap.Windows1255, // Hebrew (Windows-1255)
|
||||
"windows-874": charmap.Windows874, // Thai (Windows-874)
|
||||
"windows-1258": charmap.Windows1258, // Vietnamese (Windows-1258)
|
||||
"gb18030": simplifiedchinese.GB18030, // Chinese (Simplified, GB18030)
|
||||
"gbk": simplifiedchinese.GBK, // Chinese (Simplified, GBK)
|
||||
"big5": traditionalchinese.Big5, // Chinese (Traditional, Big5)
|
||||
"euc-kr": korean.EUCKR, // Korean (EUC-KR)
|
||||
"euc-jp": japanese.EUCJP, // Japanese (EUC-JP)
|
||||
"iso-2022-jp": japanese.ISO2022JP, // Japanese (ISO-2022-JP)
|
||||
"shift_jis": japanese.ShiftJIS, // Japanese (Shift JIS)
|
||||
"utf-8": unicode.UTF8BOM, // UTF-8
|
||||
"utf-16le": unicode.UTF16(unicode.LittleEndian, unicode.UseBOM), // UTF-16 Little Endian
|
||||
"utf-16be": unicode.UTF16(unicode.BigEndian, unicode.UseBOM), // UTF-16 Big Endian
|
||||
"utf-32le": utf32.UTF32(utf32.LittleEndian, utf32.UseBOM), // UTF-32 Little Endian
|
||||
"utf-32be": utf32.UTF32(utf32.BigEndian, utf32.UseBOM), // UTF-32 Big Endian
|
||||
"cp437": charmap.CodePage437, // OEM United States (CP-437)
|
||||
"cp863": charmap.CodePage863, // OEM Canadian French (CP-863)
|
||||
"cp037": charmap.CodePage037, // IBM EBCDIC US/Canada (CP-037)
|
||||
"cp1047": charmap.CodePage1047, // IBM EBCDIC Open Systems (CP-1047)
|
||||
"cp1140": charmap.CodePage1140, // IBM EBCDIC US/Canada with Euro (CP-1140)
|
||||
"iso-8859-1": charmap.ISO8859_1, // Western European (ISO-8859-1)
|
||||
"cp850": charmap.CodePage850, // Western European (CP-850)
|
||||
"cp858": charmap.CodePage858, // Western European with Euro (CP-858)
|
||||
"windows-1252": charmap.Windows1252, // Western European (Windows-1252)
|
||||
"iso-8859-15": charmap.ISO8859_15, // Western European (ISO-8859-15)
|
||||
"iso-8859-4": charmap.ISO8859_4, // North European (ISO-8859-4)
|
||||
"iso-8859-10": charmap.ISO8859_10, // North European (ISO-8859-10)
|
||||
"cp865": charmap.CodePage865, // North European (CP-865)
|
||||
"iso-8859-2": charmap.ISO8859_2, // Central European (ISO-8859-2)
|
||||
"cp852": charmap.CodePage852, // Central European (CP-852)
|
||||
"windows-1250": charmap.Windows1250, // Central European (Windows-1250)
|
||||
"iso-8859-14": charmap.ISO8859_14, // Celtic (ISO-8859-14)
|
||||
"iso-8859-3": charmap.ISO8859_3, // South European (ISO-8859-3)
|
||||
"cp860": charmap.CodePage860, // Portuguese (CP-860)
|
||||
"iso-8859-7": charmap.ISO8859_7, // Greek (ISO-8859-7)
|
||||
"windows-1253": charmap.Windows1253, // Greek (Windows-1253)
|
||||
"iso-8859-9": charmap.ISO8859_9, // Turkish (ISO-8859-9)
|
||||
"windows-1254": charmap.Windows1254, // Turkish (Windows-1254)
|
||||
"iso-8859-13": charmap.ISO8859_13, // Baltic (ISO-8859-13)
|
||||
"windows-1257": charmap.Windows1257, // Baltic (Windows-1257)
|
||||
"iso-8859-16": charmap.ISO8859_16, // South-Eastern European (ISO-8859-16)
|
||||
"iso-8859-5": charmap.ISO8859_5, // Cyrillic (ISO-8859-5)
|
||||
"cp855": charmap.CodePage855, // Cyrillic (CP-855)
|
||||
"cp866": charmap.CodePage866, // Cyrillic (CP-866)
|
||||
"windows-1251": charmap.Windows1251, // Cyrillic (Windows-1251)
|
||||
"koi8r": charmap.KOI8R, // Cyrillic (KOI8-R)
|
||||
"koi8u": charmap.KOI8U, // Cyrillic (KOI8-U)
|
||||
"iso-8859-6": charmap.ISO8859_6, // Arabic (ISO-8859-6)
|
||||
"windows-1256": charmap.Windows1256, // Arabic (Windows-1256)
|
||||
"iso-8859-8": charmap.ISO8859_8, // Hebrew (ISO-8859-8)
|
||||
"cp862": charmap.CodePage862, // Hebrew (CP-862)
|
||||
"windows-1255": charmap.Windows1255, // Hebrew (Windows-1255)
|
||||
"windows-874": charmap.Windows874, // Thai (Windows-874)
|
||||
"windows-1258": charmap.Windows1258, // Vietnamese (Windows-1258)
|
||||
"gb18030": simplifiedchinese.GB18030, // Chinese (Simplified, GB18030)
|
||||
"gbk": simplifiedchinese.GBK, // Chinese (Simplified, GBK)
|
||||
"big5": traditionalchinese.Big5, // Chinese (Traditional, Big5)
|
||||
"euc-kr": korean.EUCKR, // Korean (EUC-KR)
|
||||
"euc-jp": japanese.EUCJP, // Japanese (EUC-JP)
|
||||
"iso-2022-jp": japanese.ISO2022JP, // Japanese (ISO-2022-JP)
|
||||
"shift_jis": japanese.ShiftJIS, // Japanese (Shift JIS)
|
||||
}
|
||||
|
||||
var customTransactionTypeNameMapping = map[models.TransactionType]string{
|
||||
@@ -90,10 +94,6 @@ var customTransactionTypeNameMapping = map[models.TransactionType]string{
|
||||
models.TRANSACTION_TYPE_TRANSFER: utils.IntToString(int(models.TRANSACTION_TYPE_TRANSFER)),
|
||||
}
|
||||
|
||||
type CustomTransactionDataDsvFileParser interface {
|
||||
ParseDsvFileLines(ctx core.Context, data []byte) ([][]string, error)
|
||||
}
|
||||
|
||||
// customTransactionDataDsvFileImporter defines the structure of custom dsv importer for transaction data
|
||||
type customTransactionDataDsvFileImporter struct {
|
||||
fileEncoding encoding.Encoding
|
||||
@@ -110,8 +110,8 @@ type customTransactionDataDsvFileImporter struct {
|
||||
transactionTagSeparator string
|
||||
}
|
||||
|
||||
// ParseDsvFileLines returns the parsed file lines for specified the dsv file data
|
||||
func (c *customTransactionDataDsvFileImporter) ParseDsvFileLines(ctx core.Context, data []byte) ([][]string, error) {
|
||||
// ParseDataLines returns the parsed file lines for specified the dsv file data
|
||||
func (c *customTransactionDataDsvFileImporter) ParseDataLines(ctx core.Context, data []byte) ([][]string, error) {
|
||||
reader := transform.NewReader(bytes.NewReader(data), c.fileEncoding.NewDecoder())
|
||||
csvReader := csv.NewReader(reader)
|
||||
csvReader.Comma = c.separator
|
||||
@@ -127,7 +127,7 @@ func (c *customTransactionDataDsvFileImporter) ParseDsvFileLines(ctx core.Contex
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[custom_transaction_data_dsv_file_importer.ParseDsvFileLines] cannot parse dsv data, because %s", err.Error())
|
||||
log.Errorf(ctx, "[custom_transaction_data_dsv_file_importer.ParseDataLines] cannot parse dsv data, because %s", err.Error())
|
||||
return nil, errs.ErrInvalidCSVFile
|
||||
}
|
||||
|
||||
@@ -146,8 +146,8 @@ func (c *customTransactionDataDsvFileImporter) ParseDsvFileLines(ctx core.Contex
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the custom transaction dsv data
|
||||
func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
allLines, err := c.ParseDsvFileLines(ctx, data)
|
||||
func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
allLines, err := c.ParseDataLines(ctx, data)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
@@ -157,7 +157,7 @@ func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Contex
|
||||
transactionDataTable := CreateNewCustomPlainTextDataTable(dataTable, c.columnIndexMapping, c.transactionTypeNameMapping, c.timeFormat, c.timezoneFormat, c.amountDecimalSeparator, c.amountDigitGroupingSymbol)
|
||||
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(customTransactionTypeNameMapping, c.geoLocationSeparator, c.geoLocationOrder, c.transactionTagSeparator)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
// IsDelimiterSeparatedValuesFileType returns whether the file type is the delimiter-separated values file type
|
||||
@@ -166,14 +166,18 @@ func IsDelimiterSeparatedValuesFileType(fileType string) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
// CreateNewCustomTransactionDataDsvFileParser returns a new custom dsv parser for transaction data
|
||||
func CreateNewCustomTransactionDataDsvFileParser(fileType string, fileEncoding string) (CustomTransactionDataDsvFileParser, error) {
|
||||
// CreateNewCustomTransactionDataDsvFileParser returns a new custom transaction data parser
|
||||
func CreateNewCustomTransactionDataDsvFileParser(fileType string, fileEncoding string) (CustomTransactionDataParser, error) {
|
||||
separator, exists := supportedFileTypeSeparators[fileType]
|
||||
|
||||
if !exists {
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
|
||||
if fileEncoding == "" {
|
||||
return nil, errs.ErrImportFileEncodingIsEmpty
|
||||
}
|
||||
|
||||
enc, exists := supportedFileEncodings[fileEncoding]
|
||||
|
||||
if !exists {
|
||||
@@ -194,6 +198,10 @@ func CreateNewCustomTransactionDataDsvFileImporter(fileType string, fileEncoding
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
|
||||
if fileEncoding == "" {
|
||||
return nil, errs.ErrImportFileEncodingIsEmpty
|
||||
}
|
||||
|
||||
enc, exists := supportedFileEncodings[fileEncoding]
|
||||
|
||||
if !exists {
|
||||
+199
-119
@@ -1,10 +1,12 @@
|
||||
package dsv
|
||||
package custom
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
@@ -15,19 +17,21 @@ import (
|
||||
func TestIsDelimiterSeparatedValuesFileType(t *testing.T) {
|
||||
assert.True(t, IsDelimiterSeparatedValuesFileType("custom_csv"))
|
||||
assert.True(t, IsDelimiterSeparatedValuesFileType("custom_tsv"))
|
||||
assert.True(t, IsDelimiterSeparatedValuesFileType("custom_ssv"))
|
||||
|
||||
assert.False(t, IsDelimiterSeparatedValuesFileType("dsv"))
|
||||
assert.False(t, IsDelimiterSeparatedValuesFileType("csv"))
|
||||
assert.False(t, IsDelimiterSeparatedValuesFileType("tsv"))
|
||||
assert.False(t, IsDelimiterSeparatedValuesFileType("ssv"))
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataDsvFileParser_ParseDsvFileLines(t *testing.T) {
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileParser("custom_csv", "utf-8")
|
||||
func TestCustomTransactionDataDsvFileParser_ParseDataLines(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileParser("custom_csv", "utf-8")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
allLines, err := converter.ParseDsvFileLines(context, []byte(
|
||||
allLines, err := importer.ParseDataLines(context, []byte(
|
||||
"2024-09-01 00:00:00,B,123.45\n"+
|
||||
"2024-09-01 01:23:45,I,0.12\n"))
|
||||
assert.Nil(t, err)
|
||||
@@ -44,10 +48,10 @@ func TestCustomTransactionDataDsvFileParser_ParseDsvFileLines(t *testing.T) {
|
||||
assert.Equal(t, "I", allLines[1][1])
|
||||
assert.Equal(t, "0.12", allLines[1][2])
|
||||
|
||||
converter, err = CreateNewCustomTransactionDataDsvFileParser("custom_tsv", "utf-8")
|
||||
importer, err = CreateNewCustomTransactionDataDsvFileParser("custom_tsv", "utf-8")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allLines, err = converter.ParseDsvFileLines(context, []byte(
|
||||
allLines, err = importer.ParseDataLines(context, []byte(
|
||||
"2024-09-01 12:34:56\tE\t1.00\n"+
|
||||
"2024-09-01 23:59:59\tT\t0.05"))
|
||||
assert.Nil(t, err)
|
||||
@@ -63,6 +67,26 @@ func TestCustomTransactionDataDsvFileParser_ParseDsvFileLines(t *testing.T) {
|
||||
assert.Equal(t, "2024-09-01 23:59:59", allLines[1][0])
|
||||
assert.Equal(t, "T", allLines[1][1])
|
||||
assert.Equal(t, "0.05", allLines[1][2])
|
||||
|
||||
importer, err = CreateNewCustomTransactionDataDsvFileParser("custom_ssv", "utf-8")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allLines, err = importer.ParseDataLines(context, []byte(
|
||||
"2024-09-01 12:34:56;E;1.00\n"+
|
||||
"2024-09-01 23:59:59;T;0.05"))
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(allLines))
|
||||
|
||||
assert.Equal(t, 3, len(allLines[0]))
|
||||
assert.Equal(t, "2024-09-01 12:34:56", allLines[0][0])
|
||||
assert.Equal(t, "E", allLines[0][1])
|
||||
assert.Equal(t, "1.00", allLines[0][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[1]))
|
||||
assert.Equal(t, "2024-09-01 23:59:59", allLines[1][0])
|
||||
assert.Equal(t, "T", allLines[1][1])
|
||||
assert.Equal(t, "0.05", allLines[1][2])
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataDsvFileImporter_MinimumValidData(t *testing.T) {
|
||||
@@ -77,7 +101,7 @@ func TestCustomTransactionDataDsvFileImporter_MinimumValidData(t *testing.T) {
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", ".", "", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", ".", "", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -87,11 +111,11 @@ func TestCustomTransactionDataDsvFileImporter_MinimumValidData(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,B,123.45\n"+
|
||||
"2024-09-01 01:23:45,I,0.12\n"+
|
||||
"2024-09-01 12:34:56,E,1.00\n"+
|
||||
"2024-09-01 23:59:59,T,0.05"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 23:59:59,T,0.05"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -168,7 +192,7 @@ func TestCustomTransactionDataDsvFileImporter_WithAllSupportedColumns(t *testing
|
||||
"Expense": models.TRANSACTION_TYPE_EXPENSE,
|
||||
"Transfer": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, true, "YYYY-MM-DD HH:mm:ss", "", ".", "", " ", "lonlat", ";")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, true, "YYYY-MM-DD HH:mm:ss", "", ".", "", " ", "lonlat", ";")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -178,12 +202,12 @@ func TestCustomTransactionDataDsvFileImporter_WithAllSupportedColumns(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"\"Time\",\"Timezone\",\"Type\",\"Category\",\"Sub Category\",\"Account\",\"Account Currency\",\"Amount\",\"Account2\",\"Account2 Currency\",\"Account2 Amount\",\"Geographic Location\",\"Tags\",\"Description\"\n"+
|
||||
"\"2024-09-01 00:00:00\",\"+08:00\",\"Balance Modification\",\"\",\"\",\"Test Account\",\"CNY\",\"123.45\",\"\",\"\",\"\",\"\",\"\",\"\"\n"+
|
||||
"\"2024-09-01 01:23:45\",\"+08:00\",\"Income\",\"Test Category\",\"Test Sub Category\",\"Test Account\",\"CNY\",\"0.12\",\"\",\"\",\"\",\"123.450000 45.670000\",\"Test Tag;Test Tag2\",\"Hello World\"\n"+
|
||||
"\"2024-09-01 12:34:56\",\"+00:00\",\"Expense\",\"Test Category2\",\"Test Sub Category2\",\"Test Account\",\"CNY\",\"1.00\",\"\",\"\",\"\",\"\",\"Test Tag\",\"Foo#Bar\"\n"+
|
||||
"\"2024-09-01 23:59:59\",\"-05:00\",\"Transfer\",\"Test Category3\",\"Test Sub Category3\",\"Test Account\",\"CNY\",\"0.05\",\"Test Account2\",\"USD\",\"0.35\",\"\",\"Test Tag2\",\"foo\tbar\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"2024-09-01 23:59:59\",\"-05:00\",\"Transfer\",\"Test Category3\",\"Test Sub Category3\",\"Test Account\",\"CNY\",\"0.05\",\"Test Account2\",\"USD\",\"0.35\",\"\",\"Test Tag2\",\"foo\tbar\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -261,7 +285,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTime(t *testing.T) {
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -271,12 +295,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTime(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01T12:34:56,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01T12:34:56,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"09/01/2024 12:34:56,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"09/01/2024 12:34:56,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -292,7 +316,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTransactionWithoutType(t *tes
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -302,8 +326,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseTransactionWithoutType(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,A,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,A,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
@@ -316,7 +340,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidType(t *testing.T) {
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"B": 0,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -326,8 +350,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidType(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,B,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,B,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -340,7 +364,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone(t *testing.T
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ssZ", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ssZ", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -350,20 +374,20 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone(t *testing.T
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56-10:00,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56-10:00,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56+00:00,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56+00:00,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56+12:45,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56+12:45,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
@@ -378,7 +402,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone2(t *testing.
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ssZZ", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ssZZ", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -388,20 +412,20 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone2(t *testing.
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56-1000,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56-1000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56+0000,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56+0000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56+1245,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56+1245,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
@@ -417,7 +441,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone(t *testing.T) {
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -427,20 +451,20 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,-10:00,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,-10:00,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,+00:00,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,+00:00,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,+12:45,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,+12:45,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
@@ -456,7 +480,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone2(t *testing.T)
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "ZZ", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "ZZ", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -466,20 +490,20 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone2(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,-1000,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,-1000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,+0000,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,+0000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,+1245,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,+1245,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
@@ -495,7 +519,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezoneFormat(t *test
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "z", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "z", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -505,8 +529,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezoneFormat(t *test
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,CST,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,CST,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrImportFileTransactionTimezoneFormatInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -520,7 +544,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone(t *testing.T)
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -530,12 +554,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,-0700,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,-0700,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -549,7 +573,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone2(t *testing.T
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "ZZ", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "ZZ", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -559,12 +583,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone2(t *testing.T
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,0700,E,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,0700,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -577,7 +601,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithCustomFormat(t *tes
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ",", ".", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ",", ".", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -587,8 +611,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithCustomFormat(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56\tE\t1.234,56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(123456), allNewTransactions[0].Amount)
|
||||
@@ -603,7 +627,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", ",", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", ",", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -613,8 +637,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56\tE\t1.234,56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -627,7 +651,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ",", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ",", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -637,8 +661,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56\tE\t1.234,56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -655,7 +679,7 @@ func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T)
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -665,11 +689,11 @@ func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,B,,123.45\n"+
|
||||
"2024-09-01 01:23:45,I,Test Category,0.12\n"+
|
||||
"2024-09-01 12:34:56,E,Test Category2,1.00\n"+
|
||||
"2024-09-01 23:59:59,T,Test Category3,0.05"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 23:59:59,T,Test Category3,0.05"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -724,7 +748,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAccountCurrency(t *testi
|
||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -734,9 +758,9 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAccountCurrency(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,T,Test Account,USD,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 12:34:56,T,Test Account,USD,1.23,Test Account2,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -767,7 +791,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAccountCurrency(t *tes
|
||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -777,14 +801,14 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAccountCurrency(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,T,Test Account,CNY,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 12:34:56,T,Test Account,CNY,1.23,Test Account2,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,T,Test Account2,CNY,1.23,Test Account,EUR,1.10"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 12:34:56,T,Test Account2,CNY,1.23,Test Account,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -803,7 +827,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseNotSupportedCurrency(t *testi
|
||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -813,12 +837,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseNotSupportedCurrency(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 01:23:45,B,Test Account,XXX,123.45,,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 01:23:45,B,Test Account,XXX,123.45,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 01:23:45,T,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 01:23:45,T,Test Account,USD,123.45,Test Account2,XXX,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -835,7 +859,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -845,11 +869,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,B,123.45000000,\n"+
|
||||
"2024-09-01 01:23:45,I,0.12000000,\n"+
|
||||
"2024-09-01 12:34:56,E,1.00000000,\n"+
|
||||
"2024-09-01 23:59:59,T,0.05000000,0.35000000"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 23:59:59,T,0.05000000,0.35000000"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -873,6 +897,62 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
|
||||
assert.Equal(t, int64(35), allNewTransactions[3].RelatedAccountAmount)
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingSymbol(t *testing.T) {
|
||||
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2,
|
||||
}
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", " ", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
// normal space
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,E,1 234,\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(123400), allNewTransactions[0].Amount)
|
||||
|
||||
// no-break space (NBSP)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,E,1 234,\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(123400), allNewTransactions[0].Amount)
|
||||
|
||||
// narrow no-break space (NNBSP)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,E,1 234,\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(123400), allNewTransactions[0].Amount)
|
||||
|
||||
// figure space
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,E,1 234,\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(123400), allNewTransactions[0].Amount)
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
|
||||
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||
@@ -886,7 +966,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -896,12 +976,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,Test Account,123 45,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,Test Account,123 45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,T,Test Account,123.45,Test Account2,123 45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,T,Test Account,123.45,Test Account2,123 45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -917,7 +997,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseNoAmount2(t *testing.T) {
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -927,15 +1007,15 @@ func TestCustomTransactionDataDsvFileImporter_ParseNoAmount2(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,Test Account,123.45,"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,Test Account,123.45,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,T,Test Account,123.45,Test Account2"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,T,Test Account,123.45,Test Account2"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
@@ -952,7 +1032,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidGeographicLocation(t *te
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", ";", "lonlat", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", ";", "lonlat", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -962,8 +1042,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidGeographicLocation(t *te
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,123.45,123.45;45.56"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,123.45,123.45;45.56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -981,7 +1061,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidGeographicLocation(t *
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", " ", "lonlat", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", " ", "lonlat", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -991,15 +1071,15 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidGeographicLocation(t *
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,123.45,,,1"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,123.45,,,1"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,123.45,a b"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,E,123.45,a b"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -1013,7 +1093,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTag(t *testing.T) {
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", ";")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", ";")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -1023,8 +1103,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseTag(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -1053,7 +1133,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTagWithoutSeparator(t *testin
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -1063,8 +1143,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseTagWithoutSeparator(t *testin
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -1084,7 +1164,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseDescription(t *testing.T) {
|
||||
transactionTypeMapping := map[string]models.TransactionType{
|
||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||
}
|
||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "", "")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
@@ -1094,8 +1174,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseDescription(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,T,123.45,foo bar\t#test"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"2024-09-01 12:34:56,T,123.45,foo bar\t#test"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -0,0 +1,137 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
csvconverter "github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/excel"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
const customOOXMLExcelFileType = "custom_xlsx"
|
||||
const customMSCFBExcelFileType = "custom_xls"
|
||||
|
||||
// customTransactionDataExcelFileImporter defines the structure of custom excel importer for transaction data
|
||||
type customTransactionDataExcelFileImporter struct {
|
||||
fileType string
|
||||
columnIndexMapping map[datatable.TransactionDataTableColumn]int
|
||||
transactionTypeNameMapping map[string]models.TransactionType
|
||||
hasHeaderLine bool
|
||||
timeFormat string
|
||||
timezoneFormat string
|
||||
amountDecimalSeparator string
|
||||
amountDigitGroupingSymbol string
|
||||
geoLocationSeparator string
|
||||
geoLocationOrder converter.TransactionGeoLocationOrder
|
||||
transactionTagSeparator string
|
||||
}
|
||||
|
||||
// ParseDataLines returns the parsed file lines for specified the excel file data
|
||||
func (c *customTransactionDataExcelFileImporter) ParseDataLines(ctx core.Context, data []byte) ([][]string, error) {
|
||||
var excelDataTable datatable.BasicDataTable
|
||||
var err error
|
||||
|
||||
if c.fileType == customOOXMLExcelFileType {
|
||||
excelDataTable, err = excel.CreateNewExcelOOXMLFileBasicDataTable(data, false)
|
||||
} else if c.fileType == customMSCFBExcelFileType {
|
||||
excelDataTable, err = excel.CreateNewExcelMSCFBFileBasicDataTable(data, false)
|
||||
} else {
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iterator := excelDataTable.DataRowIterator()
|
||||
allLines := make([][]string, 0)
|
||||
|
||||
for iterator.HasNext() {
|
||||
row := iterator.Next()
|
||||
items := make([]string, row.ColumnCount())
|
||||
|
||||
for i := 0; i < row.ColumnCount(); i++ {
|
||||
items[i] = strings.Trim(row.GetData(i), " ")
|
||||
}
|
||||
|
||||
allLines = append(allLines, items)
|
||||
}
|
||||
|
||||
return allLines, nil
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the custom transaction dsv data
|
||||
func (c *customTransactionDataExcelFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
allLines, err := c.ParseDataLines(ctx, data)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataTable := csvconverter.CreateNewCustomCsvBasicDataTable(allLines, c.hasHeaderLine)
|
||||
transactionDataTable := CreateNewCustomPlainTextDataTable(dataTable, c.columnIndexMapping, c.transactionTypeNameMapping, c.timeFormat, c.timezoneFormat, c.amountDecimalSeparator, c.amountDigitGroupingSymbol)
|
||||
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(customTransactionTypeNameMapping, c.geoLocationSeparator, c.geoLocationOrder, c.transactionTagSeparator)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
// IsCustomExcelFileType returns whether the file type is the custom excel file type
|
||||
func IsCustomExcelFileType(fileType string) bool {
|
||||
return fileType == customOOXMLExcelFileType || fileType == customMSCFBExcelFileType
|
||||
}
|
||||
|
||||
// CreateNewCustomTransactionDataExcelFileParser returns a new custom transaction data parser
|
||||
func CreateNewCustomTransactionDataExcelFileParser(fileType string) (CustomTransactionDataParser, error) {
|
||||
if fileType != customOOXMLExcelFileType && fileType != customMSCFBExcelFileType {
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
|
||||
return &customTransactionDataExcelFileImporter{
|
||||
fileType: fileType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateNewCustomTransactionDataExcelFileImporter returns a new custom excel importer for transaction data
|
||||
func CreateNewCustomTransactionDataExcelFileImporter(fileType string, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, hasHeaderLine bool, timeFormat string, timezoneFormat string, amountDecimalSeparator string, amountDigitGroupingSymbol string, geoLocationSeparator string, geoLocationOrder string, transactionTagSeparator string) (converter.TransactionDataImporter, error) {
|
||||
if fileType != customOOXMLExcelFileType && fileType != customMSCFBExcelFileType {
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
|
||||
if geoLocationOrder == "" {
|
||||
geoLocationOrder = string(converter.TRANSACTION_GEO_LOCATION_ORDER_LONGITUDE_LATITUDE)
|
||||
} else if geoLocationOrder != string(converter.TRANSACTION_GEO_LOCATION_ORDER_LONGITUDE_LATITUDE) &&
|
||||
geoLocationOrder != string(converter.TRANSACTION_GEO_LOCATION_ORDER_LATITUDE_LONGITUDE) {
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
|
||||
if _, exists := columnIndexMapping[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME]; !exists {
|
||||
return nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||
}
|
||||
|
||||
if _, exists := columnIndexMapping[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE]; !exists {
|
||||
return nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||
}
|
||||
|
||||
if _, exists := columnIndexMapping[datatable.TRANSACTION_DATA_TABLE_AMOUNT]; !exists {
|
||||
return nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||
}
|
||||
|
||||
return &customTransactionDataExcelFileImporter{
|
||||
fileType: fileType,
|
||||
columnIndexMapping: columnIndexMapping,
|
||||
transactionTypeNameMapping: transactionTypeNameMapping,
|
||||
hasHeaderLine: hasHeaderLine,
|
||||
timeFormat: timeFormat,
|
||||
timezoneFormat: timezoneFormat,
|
||||
amountDecimalSeparator: amountDecimalSeparator,
|
||||
amountDigitGroupingSymbol: amountDigitGroupingSymbol,
|
||||
geoLocationSeparator: geoLocationSeparator,
|
||||
geoLocationOrder: converter.TransactionGeoLocationOrder(geoLocationOrder),
|
||||
transactionTagSeparator: transactionTagSeparator,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsCustomExcelFileType(t *testing.T) {
|
||||
assert.True(t, IsCustomExcelFileType("custom_xlsx"))
|
||||
assert.True(t, IsCustomExcelFileType("custom_xls"))
|
||||
|
||||
assert.False(t, IsCustomExcelFileType("xlsx"))
|
||||
assert.False(t, IsCustomExcelFileType("xls"))
|
||||
assert.False(t, IsCustomExcelFileType("excel"))
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataParser_ParseOOXMLExcelDataLines_EmptyData(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataExcelFileParser("custom_xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allLines, err := importer.ParseDataLines(context, testdata)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(allLines))
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataParser_ParseOOXMLExcelDataLines_SingleSheet(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataExcelFileParser("custom_xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allLines, err := importer.ParseDataLines(context, testdata)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 3, len(allLines))
|
||||
|
||||
assert.Equal(t, 3, len(allLines[0]))
|
||||
assert.Equal(t, "A1", allLines[0][0])
|
||||
assert.Equal(t, "B1", allLines[0][1])
|
||||
assert.Equal(t, "C1", allLines[0][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[1]))
|
||||
assert.Equal(t, "A2", allLines[1][0])
|
||||
assert.Equal(t, "B2", allLines[1][1])
|
||||
assert.Equal(t, "C2", allLines[1][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[2]))
|
||||
assert.Equal(t, "A3", allLines[2][0])
|
||||
assert.Equal(t, "B3", allLines[2][1])
|
||||
assert.Equal(t, "C3", allLines[2][2])
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataParser_ParseOOXMLExcelDataLines_MultipleSheet(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataExcelFileParser("custom_xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allLines, err := importer.ParseDataLines(context, testdata)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 9, len(allLines))
|
||||
|
||||
assert.Equal(t, 3, len(allLines[0]))
|
||||
assert.Equal(t, "A1", allLines[0][0])
|
||||
assert.Equal(t, "B1", allLines[0][1])
|
||||
assert.Equal(t, "C1", allLines[0][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[1]))
|
||||
assert.Equal(t, "1-A2", allLines[1][0])
|
||||
assert.Equal(t, "1-B2", allLines[1][1])
|
||||
assert.Equal(t, "1-C2", allLines[1][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[2]))
|
||||
assert.Equal(t, "1-A3", allLines[2][0])
|
||||
assert.Equal(t, "1-B3", allLines[2][1])
|
||||
assert.Equal(t, "1-C3", allLines[2][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[3]))
|
||||
assert.Equal(t, "A1", allLines[3][0])
|
||||
assert.Equal(t, "B1", allLines[3][1])
|
||||
assert.Equal(t, "C1", allLines[3][2])
|
||||
|
||||
assert.Equal(t, 2, len(allLines[4]))
|
||||
assert.Equal(t, "3-A2", allLines[4][0])
|
||||
assert.Equal(t, "3-B2", allLines[4][1])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[5]))
|
||||
assert.Equal(t, "A1", allLines[5][0])
|
||||
assert.Equal(t, "B1", allLines[5][1])
|
||||
assert.Equal(t, "C1", allLines[5][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[6]))
|
||||
assert.Equal(t, "A1", allLines[6][0])
|
||||
assert.Equal(t, "B1", allLines[6][1])
|
||||
assert.Equal(t, "C1", allLines[6][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[7]))
|
||||
assert.Equal(t, "5-A2", allLines[7][0])
|
||||
assert.Equal(t, "5-B2", allLines[7][1])
|
||||
assert.Equal(t, "5-C2", allLines[7][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[8]))
|
||||
assert.Equal(t, "5-A3", allLines[8][0])
|
||||
assert.Equal(t, "5-B3", allLines[8][1])
|
||||
assert.Equal(t, "5-C3", allLines[8][2])
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataParser_ParseOOXMLExcelDataLines_MultipleSheetWithDifferentColumnCount(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataExcelFileParser("custom_xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_with_different_header_row_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = importer.ParseDataLines(context, testdata)
|
||||
assert.EqualError(t, err, errs.ErrFieldsInMultiTableAreDifferent.Message)
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataParser_ParseMSCFBExcelDataLines_EmptyData(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataExcelFileParser("custom_xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allLines, err := importer.ParseDataLines(context, testdata)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(allLines))
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataParser_ParseMSCFBExcelDataLines_SingleSheet(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataExcelFileParser("custom_xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allLines, err := importer.ParseDataLines(context, testdata)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 3, len(allLines))
|
||||
|
||||
assert.Equal(t, 3, len(allLines[0]))
|
||||
assert.Equal(t, "A1", allLines[0][0])
|
||||
assert.Equal(t, "B1", allLines[0][1])
|
||||
assert.Equal(t, "C1", allLines[0][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[1]))
|
||||
assert.Equal(t, "A2", allLines[1][0])
|
||||
assert.Equal(t, "B2", allLines[1][1])
|
||||
assert.Equal(t, "C2", allLines[1][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[2]))
|
||||
assert.Equal(t, "A3", allLines[2][0])
|
||||
assert.Equal(t, "B3", allLines[2][1])
|
||||
assert.Equal(t, "C3", allLines[2][2])
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataParser_ParseMSCFBExcelDataLines_MultipleSheet(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataExcelFileParser("custom_xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allLines, err := importer.ParseDataLines(context, testdata)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 9, len(allLines))
|
||||
|
||||
assert.Equal(t, 3, len(allLines[0]))
|
||||
assert.Equal(t, "A1", allLines[0][0])
|
||||
assert.Equal(t, "B1", allLines[0][1])
|
||||
assert.Equal(t, "C1", allLines[0][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[1]))
|
||||
assert.Equal(t, "1-A2", allLines[1][0])
|
||||
assert.Equal(t, "1-B2", allLines[1][1])
|
||||
assert.Equal(t, "1-C2", allLines[1][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[2]))
|
||||
assert.Equal(t, "1-A3", allLines[2][0])
|
||||
assert.Equal(t, "1-B3", allLines[2][1])
|
||||
assert.Equal(t, "1-C3", allLines[2][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[3]))
|
||||
assert.Equal(t, "A1", allLines[3][0])
|
||||
assert.Equal(t, "B1", allLines[3][1])
|
||||
assert.Equal(t, "C1", allLines[3][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[4]))
|
||||
assert.Equal(t, "3-A2", allLines[4][0])
|
||||
assert.Equal(t, "3-B2", allLines[4][1])
|
||||
assert.Equal(t, "", allLines[4][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[5]))
|
||||
assert.Equal(t, "A1", allLines[5][0])
|
||||
assert.Equal(t, "B1", allLines[5][1])
|
||||
assert.Equal(t, "C1", allLines[5][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[6]))
|
||||
assert.Equal(t, "A1", allLines[6][0])
|
||||
assert.Equal(t, "B1", allLines[6][1])
|
||||
assert.Equal(t, "C1", allLines[6][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[7]))
|
||||
assert.Equal(t, "5-A2", allLines[7][0])
|
||||
assert.Equal(t, "5-B2", allLines[7][1])
|
||||
assert.Equal(t, "5-C2", allLines[7][2])
|
||||
|
||||
assert.Equal(t, 3, len(allLines[8]))
|
||||
assert.Equal(t, "5-A3", allLines[8][0])
|
||||
assert.Equal(t, "5-B3", allLines[8][1])
|
||||
assert.Equal(t, "5-C3", allLines[8][2])
|
||||
}
|
||||
|
||||
func TestCustomTransactionDataParser_ParseMSCFBExcelDataLines_MultipleSheetWithDifferentColumnCount(t *testing.T) {
|
||||
importer, err := CreateNewCustomTransactionDataExcelFileParser("custom_xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
context := core.NewNullContext()
|
||||
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_with_different_header_row_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = importer.ParseDataLines(context, testdata)
|
||||
assert.EqualError(t, err, errs.ErrFieldsInMultiTableAreDifferent.Message)
|
||||
}
|
||||
+23
-2
@@ -1,4 +1,4 @@
|
||||
package dsv
|
||||
package custom
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -107,6 +107,7 @@ func (t *customPlainTextDataRowIterator) Next(ctx core.Context, user *models.Use
|
||||
|
||||
func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user *models.User, row datatable.BasicDataTableRow) (map[datatable.TransactionDataTableColumn]string, bool, error) {
|
||||
rowData := make(map[datatable.TransactionDataTableColumn]string, len(t.transactionDataTable.columnIndexMapping))
|
||||
var transactionTime *time.Time = nil
|
||||
|
||||
for column, columnIndex := range t.transactionDataTable.columnIndexMapping {
|
||||
if columnIndex < 0 || columnIndex >= row.ColumnCount() {
|
||||
@@ -144,10 +145,11 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
return nil, false, errs.ErrTransactionTimeInvalid
|
||||
}
|
||||
|
||||
transactionTime = &dateTime
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
|
||||
|
||||
if t.transactionDataTable.timeFormatIncludeTimezone {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location())
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +166,19 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
|
||||
timezone = timezone[:3] + ":" + timezone[3:]
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = timezone
|
||||
} else if t.transactionDataTable.timezoneFormat == "zzz" { // IANA Timezone Name
|
||||
timezoneName := rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE]
|
||||
timezone, err := time.LoadLocation(timezoneName)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, errs.ErrTransactionTimeZoneInvalid
|
||||
}
|
||||
|
||||
if transactionTime == nil {
|
||||
return nil, false, errs.ErrTransactionTimeInvalid
|
||||
}
|
||||
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionTime.Unix(), timezone)
|
||||
} else {
|
||||
return nil, false, errs.ErrImportFileTransactionTimezoneFormatInvalid
|
||||
}
|
||||
@@ -215,6 +230,12 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
func (t *customPlainTextDataRowIterator) parseAmount(ctx core.Context, amountValue string) (string, error) {
|
||||
if t.transactionDataTable.amountDigitGroupingSymbol != "" {
|
||||
amountValue = strings.ReplaceAll(amountValue, t.transactionDataTable.amountDigitGroupingSymbol, "")
|
||||
|
||||
if t.transactionDataTable.amountDigitGroupingSymbol == " " {
|
||||
amountValue = strings.ReplaceAll(amountValue, "\u00A0", "") // No-Break Space (NBSP)
|
||||
amountValue = strings.ReplaceAll(amountValue, "\u202F", "") // Narrow No-Break Space (NNBSP)
|
||||
amountValue = strings.ReplaceAll(amountValue, "\u2007", "") // Figure Space
|
||||
}
|
||||
}
|
||||
|
||||
if t.transactionDataTable.amountDecimalSeparator != "" && t.transactionDataTable.amountDecimalSeparator != "." {
|
||||
@@ -72,6 +72,10 @@ const (
|
||||
TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION TransactionDataTableColumn = 12
|
||||
TRANSACTION_DATA_TABLE_TAGS TransactionDataTableColumn = 13
|
||||
TRANSACTION_DATA_TABLE_DESCRIPTION TransactionDataTableColumn = 14
|
||||
TRANSACTION_DATA_TABLE_PAYEE TransactionDataTableColumn = 101
|
||||
TRANSACTION_DATA_TABLE_MEMBER TransactionDataTableColumn = 102
|
||||
TRANSACTION_DATA_TABLE_PROJECT TransactionDataTableColumn = 103
|
||||
TRANSACTION_DATA_TABLE_MERCHANT TransactionDataTableColumn = 104
|
||||
)
|
||||
|
||||
// TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE represents the constant for timezone not available
|
||||
|
||||
@@ -35,7 +35,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the transaction json data
|
||||
func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
var importRequest models.ImportTransactionRequest
|
||||
|
||||
if err := json.Unmarshal(data, &importRequest); err != nil {
|
||||
@@ -55,7 +55,7 @@ func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context,
|
||||
ezbookkeepingTagSeparator,
|
||||
)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
func (c *defaultTransactionDataJsonImporter) createNewDefaultTransactionDataTable(importRequest models.ImportTransactionRequest) (datatable.TransactionDataTable, error) {
|
||||
@@ -75,10 +75,11 @@ func (c *defaultTransactionDataJsonImporter) createNewDefaultTransactionDataTabl
|
||||
}
|
||||
|
||||
timezone := time.FixedZone("Transaction Timezone", utcOffset*60)
|
||||
timezoneOffset := utils.FormatTimezoneOffset(time.Now().Unix(), timezone)
|
||||
|
||||
row := make(map[datatable.TransactionDataTableColumn]string, len(allJsonDataSupportedColumns))
|
||||
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = transaction.Time
|
||||
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(timezone)
|
||||
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = timezoneOffset
|
||||
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = transaction.Type
|
||||
row[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = transaction.CategoryName
|
||||
row[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = transaction.SourceAccountName
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package _default
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
@@ -84,7 +86,7 @@ func (c *defaultTransactionDataPlainTextConverter) ToExportedContent(ctx core.Co
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the transaction plain text data
|
||||
func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
dataTable, err := createNewDefaultPlainTextDataTable(
|
||||
string(data),
|
||||
c.columnSeparator,
|
||||
@@ -104,5 +106,5 @@ func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Co
|
||||
ezbookkeepingTagSeparator,
|
||||
)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package _default
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -12,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterToExportedContent(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
exporter := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
transactions := make([]*models.Transaction, 3)
|
||||
@@ -119,14 +121,14 @@ func TestDefaultTransactionDataCSVFileConverterToExportedContent(t *testing.T) {
|
||||
"2024-09-01 12:34:56,+08:00,Income,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,123.450000 45.670000,Test Tag;Test Tag2,Hello World\n" +
|
||||
"2024-09-01 12:34:56,+00:00,Expense,Test Category2,Test Sub Category2,Test Account,CNY,-0.10,,,,,Test Tag,Foo#Bar\n" +
|
||||
"2024-09-01 12:34:56,-05:00,Transfer,Test Category3,Test Sub Category3,Test Account,CNY,123.45,Test Account2,USD,17.35,,Test Tag2,T\te s t test\n"
|
||||
actualContent, err := converter.ToExportedContent(context, 123, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
||||
actualContent, err := exporter.ToExportedContent(context, 123, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expectedContent, string(actualContent))
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -134,11 +136,11 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MinimumValidDat
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,\n"+
|
||||
"2024-09-01 01:23:45,Income,Test Category,Test Account,0.12,,\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category2,Test Account,1.00,,\n"+
|
||||
"2024-09-01 23:59:59,Transfer,Test Category3,Test Account,0.05,Test Account2,0.05"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 23:59:59,Transfer,Test Category3,Test Account,0.05,Test Account2,0.05"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -197,7 +199,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MinimumValidDat
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -205,17 +207,17 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTim
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01T12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01T12:34:56,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"09/01/2024 12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"09/01/2024 12:34:56,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -223,13 +225,13 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTyp
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Type,Test Category,Test Account,123.45,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Type,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -237,27 +239,27 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidTimez
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,-10:00,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,-10:00,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,+00:00,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,+00:00,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,+12:45,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,+12:45,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTimezone(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -265,13 +267,13 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTim
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Asia/Shanghai,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Asia/Shanghai,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -279,9 +281,9 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidAccou
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category2,Test Account,USD,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 12:34:56,Transfer,Test Category2,Test Account,USD,1.23,Test Account2,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -298,7 +300,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidAccou
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -306,19 +308,19 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAcc
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account,CNY,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account,CNY,1.23,Test Account2,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account2,CNY,1.23,Test Account,EUR,1.10"), 0, nil, nil, nil, nil, nil)
|
||||
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account2,CNY,1.23,Test Account,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -326,17 +328,17 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNotSupport
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,XXX,123.45,,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,XXX,123.45,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Transfer,Test Category,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Transfer,Test Category,Test Account,USD,123.45,Test Account2,XXX,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -344,17 +346,17 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAmo
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123 45,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123 45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2,123 45"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2,123 45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -362,15 +364,15 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
@@ -378,7 +380,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidGeographicLocation(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -386,8 +388,8 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidGeogr
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,123.45 45.56"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,123.45 45.56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -396,7 +398,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidGeogr
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidGeographicLocation(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -404,24 +406,24 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidGeo
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,a b"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,a b"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1 "), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1 "), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message)
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseTag(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -429,8 +431,8 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseTag(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Tags\n"+
|
||||
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,,foo;;bar.;#test;hello\tworld;;"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Tags\n"+
|
||||
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,,foo;;bar.;#test;hello\tworld;;"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -450,7 +452,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseTag(t *tes
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -458,8 +460,8 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseDescriptio
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Description\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,foo bar\t#test"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Description\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,foo bar\t#test"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -467,7 +469,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseDescriptio
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -475,12 +477,12 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingFileHead
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := DefaultTransactionDataCSVFileConverter
|
||||
importer := DefaultTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -489,32 +491,32 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingRequired
|
||||
}
|
||||
|
||||
// Missing Time Column
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Type Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Sub Category Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Account,CNY,123.45,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Account Name Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,CNY,123.45,,,,,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,CNY,123.45,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,,,,,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Account2 Name Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func (t *ExcelMSCFBFileBasicDataTable) DataRowIterator() datatable.BasicDataTabl
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
func (r *ExcelMSCFBFileBasicDataTableRow) ColumnCount() int {
|
||||
row := r.sheet.Row(r.rowIndex)
|
||||
return row.LastCol() + 1
|
||||
return row.LastCol()
|
||||
}
|
||||
|
||||
// GetData returns the data in the specified column index
|
||||
@@ -195,7 +195,10 @@ func CreateNewExcelMSCFBFileBasicDataTable(data []byte, hasTitleLine bool) (data
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
for j := 0; j <= row.LastCol(); j++ {
|
||||
// row.LastCol() returns "colMac" in the "Row" struct, that is an unsigned integer that specifies the one-based index of the last column.
|
||||
// But row.FirstCol() returns "colMic" in the "Row" struct, that is an unsigned integer that specifies the zero-based index of the first column.
|
||||
// Reference: https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/4aab09eb-49ed-4d01-a3b1-1d726247d3c2
|
||||
for j := 0; j < row.LastCol(); j++ {
|
||||
headerItem := row.Col(j)
|
||||
|
||||
if headerItem == "" {
|
||||
@@ -205,7 +208,7 @@ func CreateNewExcelMSCFBFileBasicDataTable(data []byte, hasTitleLine bool) (data
|
||||
firstRowItems = append(firstRowItems, headerItem)
|
||||
}
|
||||
} else {
|
||||
for j := 0; j <= min(row.LastCol(), len(firstRowItems)-1); j++ {
|
||||
for j := 0; j < min(row.LastCol(), len(firstRowItems)); j++ {
|
||||
headerItem := row.Col(j)
|
||||
|
||||
if headerItem != firstRowItems[j] {
|
||||
|
||||
@@ -300,10 +300,10 @@ func TestExcelMSCFBFileBasicDataRowColumnCount(t *testing.T) {
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
assert.EqualValues(t, 4, row1.ColumnCount())
|
||||
assert.EqualValues(t, 3, row1.ColumnCount())
|
||||
|
||||
row2 := iterator.Next()
|
||||
assert.EqualValues(t, 4, row2.ColumnCount())
|
||||
assert.EqualValues(t, 3, row2.ColumnCount())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataRowGetData(t *testing.T) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package feidee
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
@@ -27,6 +28,9 @@ const feideeMymoneyAppTransactionAccountCurrencyColumnName = "账户币种"
|
||||
const feideeMymoneyAppTransactionAmountColumnName = "金额"
|
||||
const feideeMymoneyAppTransactionDescriptionColumnName = "备注"
|
||||
const feideeMymoneyAppTransactionRelatedIdColumnName = "关联Id"
|
||||
const feideeMymoneyAppTransactionMemberColumnName = "成员"
|
||||
const feideeMymoneyAppTransactionProjectColumnName = "项目"
|
||||
const feideeMymoneyAppTransactionMerchantColumnName = "商家"
|
||||
|
||||
const feideeMymoneyAppTransactionTypeModifyBalanceText = "余额变更"
|
||||
const feideeMymoneyAppTransactionTypeModifyOutstandingBalanceText = "负债变更"
|
||||
@@ -44,6 +48,9 @@ var feideeMymoneyAppDataColumnNameMapping = map[datatable.TransactionDataTableCo
|
||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY: feideeMymoneyAppTransactionAccountCurrencyColumnName,
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: feideeMymoneyAppTransactionAmountColumnName,
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: feideeMymoneyAppTransactionDescriptionColumnName,
|
||||
datatable.TRANSACTION_DATA_TABLE_MEMBER: feideeMymoneyAppTransactionMemberColumnName,
|
||||
datatable.TRANSACTION_DATA_TABLE_PROJECT: feideeMymoneyAppTransactionProjectColumnName,
|
||||
datatable.TRANSACTION_DATA_TABLE_MERCHANT: feideeMymoneyAppTransactionMerchantColumnName,
|
||||
}
|
||||
|
||||
// feideeMymoneyAppTransactionDataCsvFileImporter defines the structure of feidee mymoney app csv importer for transaction data
|
||||
@@ -55,7 +62,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the feidee mymoney app transaction csv data
|
||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
fallback := unicode.UTF8.NewDecoder()
|
||||
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
||||
|
||||
@@ -91,7 +98,7 @@ func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx c
|
||||
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(feideeMymoneyTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) createNewFeideeMymoneyAppTransactionDataTable(ctx core.Context, commonDataTable datatable.CommonDataTable) (datatable.TransactionDataTable, error) {
|
||||
@@ -123,6 +130,18 @@ func (c *feideeMymoneyAppTransactionDataCsvFileImporter) createNewFeideeMymoneyA
|
||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_DESCRIPTION)
|
||||
}
|
||||
|
||||
if commonDataTable.HasColumn(feideeMymoneyAppTransactionMemberColumnName) {
|
||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_MEMBER)
|
||||
}
|
||||
|
||||
if commonDataTable.HasColumn(feideeMymoneyAppTransactionProjectColumnName) {
|
||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_PROJECT)
|
||||
}
|
||||
|
||||
if commonDataTable.HasColumn(feideeMymoneyAppTransactionMerchantColumnName) {
|
||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_MERCHANT)
|
||||
}
|
||||
|
||||
transactionRowParser := createFeideeMymoneyTransactionDataRowParser()
|
||||
transactionDataTable := datatable.CreateNewWritableTransactionDataTableWithRowParser(newColumns, transactionRowParser)
|
||||
transferTransactionsMap := make(map[string]map[datatable.TransactionDataTableColumn]string, 0)
|
||||
|
||||
+96
-66
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -21,7 +22,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 01:00:00\",\"\",\"Test Account2\",\"-0.12\",\"\",\"\"\n"+
|
||||
@@ -30,7 +31,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testi
|
||||
"\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account2\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\"\n"+
|
||||
"\"转出\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account2\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转出\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account2\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -110,7 +111,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testi
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceModification(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -118,10 +119,10 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceMo
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"负债变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"+
|
||||
"\"负债变更\",\"2024-09-01 01:00:00\",\"\",\"Test Account2\",\"-0.12\",\"\",\"\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
"\"负债变更\",\"2024-09-01 01:00:00\",\"\",\"Test Account2\",\"-0.12\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -160,7 +161,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceMo
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -168,19 +169,19 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidTime(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"收入\",\"2024-09-01T12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"收入\",\"2024-09-01T12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"收入\",\"09/01/2024 12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"收入\",\"09/01/2024 12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -188,14 +189,14 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidType(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"Type\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"Type\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -203,11 +204,11 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+
|
||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"USD\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -224,7 +225,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -232,23 +233,23 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAccountCurren
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+
|
||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"CNY\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+
|
||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"CNY\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -256,26 +257,26 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseNotSupportedCurrency
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"XXX\",\"123.45\",\"\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"XXX\",\"123.45\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"USD\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"XXX\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"XXX\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"XXX\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"USD\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"USD\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -283,31 +284,31 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAmount(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"123 45\",\"\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"123 45\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"负债变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"123 45\",\"\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"负债变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"123 45\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123 45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123 45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123 45\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -315,18 +316,18 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseDescription(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"Test\n"+
|
||||
"A new line break\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"A new line break\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "Test\nA new line break", allNewTransactions[0].Comment)
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_InvalidRelatedId(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_WithAdditionalOptions(t *testing.T) {
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -334,41 +335,70 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_InvalidRelatedId(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\",\"成员\",\"项目\",\"商家\"\n"+
|
||||
"\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\",\"test1\",\"test2\",\"test3\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, 0, len(allNewTransactions[0].OriginalTagNames))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\",\"成员\",\"项目\",\"商家\"\n"+
|
||||
"\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\",\"test1\",\"test2\",\"test3\""), time.UTC, converter.DefaultImporterOptions.WithMemberAsTag().WithProjectAsTag().WithMerchantAsTag(), nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, 3, len(allNewTransactions[0].OriginalTagNames))
|
||||
assert.Contains(t, allNewTransactions[0].OriginalTagNames, "test1")
|
||||
assert.Contains(t, allNewTransactions[0].OriginalTagNames, "test2")
|
||||
assert.Contains(t, allNewTransactions[0].OriginalTagNames, "test3")
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_InvalidRelatedId(t *testing.T) {
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrRelatedIdCannotBeBlank.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"转入\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrRelatedIdCannotBeBlank.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
||||
"\"转入\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\""), 0, nil, nil, nil, nil, nil)
|
||||
"\"转入\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrFoundRecordNotHasRelatedRecord.Message)
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
}
|
||||
|
||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -377,38 +407,38 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingRequiredColumn(t *
|
||||
}
|
||||
|
||||
// Missing Time Column
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
"\"余额变更\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Type Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"2024-09-01 00:00:00\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
"\"2024-09-01 00:00:00\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Sub Category Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"Test Account\",\"123.45\",\"\",\"\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"Test Account\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Account Name Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"123.45\",\"\",\"\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"备注\",\"关联Id\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"\",\"\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Related ID Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\"\n"+
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
+7
-2
@@ -1,6 +1,8 @@
|
||||
package feidee
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/excel"
|
||||
@@ -18,6 +20,9 @@ var feideeMymoneyElecloudDataColumnNameMapping = map[datatable.TransactionDataTa
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: "金额",
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: "备注",
|
||||
datatable.TRANSACTION_DATA_TABLE_MEMBER: "成员",
|
||||
datatable.TRANSACTION_DATA_TABLE_PROJECT: "项目",
|
||||
datatable.TRANSACTION_DATA_TABLE_MERCHANT: "商家",
|
||||
}
|
||||
|
||||
// feideeMymoneyElecloudTransactionDataXlsxFileImporter defines the structure of feidee mymoney (elecloud) xlsx importer for transaction data
|
||||
@@ -31,7 +36,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the feidee mymoney (elecloud) transaction xlsx data
|
||||
func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
dataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, true)
|
||||
|
||||
if err != nil {
|
||||
@@ -42,5 +47,5 @@ func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyElecloudDataColumnNameMapping, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewSimpleImporter(feideeMymoneyElecloudTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
+3
-2
@@ -7,13 +7,14 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
func TestFeideeMymoneyElecloudTransactionDataXlsxImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := FeideeMymoneyElecloudTransactionDataXlsxFileImporter
|
||||
importer := FeideeMymoneyElecloudTransactionDataXlsxFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -24,7 +25,7 @@ func TestFeideeMymoneyElecloudTransactionDataXlsxImporterParseImportedData_Minim
|
||||
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_elecloud_test_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, testdata, 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, testdata, time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 7, len(allNewTransactions))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package feidee
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/excel"
|
||||
@@ -17,6 +19,9 @@ var feideeMymoneyWebDataColumnNameMapping = map[datatable.TransactionDataTableCo
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: "金额",
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: "备注",
|
||||
datatable.TRANSACTION_DATA_TABLE_MEMBER: "成员",
|
||||
datatable.TRANSACTION_DATA_TABLE_PROJECT: "项目",
|
||||
datatable.TRANSACTION_DATA_TABLE_MERCHANT: "商家",
|
||||
}
|
||||
|
||||
// feideeMymoneyWebTransactionDataXlsFileImporter defines the structure of feidee mymoney (web) xls importer for transaction data
|
||||
@@ -30,7 +35,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the feidee mymoney (web) transaction xls data
|
||||
func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
dataTable, err := excel.CreateNewExcelMSCFBFileBasicDataTable(data, true)
|
||||
|
||||
if err != nil {
|
||||
@@ -41,5 +46,5 @@ func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx c
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyWebDataColumnNameMapping, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(feideeMymoneyTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
func TestFeideeMymoneyTransactionDataXlsImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := FeideeMymoneyWebTransactionDataXlsFileImporter
|
||||
importer := FeideeMymoneyWebTransactionDataXlsFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -24,7 +25,7 @@ func TestFeideeMymoneyTransactionDataXlsImporterParseImportedData_MinimumValidDa
|
||||
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_test_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, testdata, 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, testdata, time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 7, len(allNewTransactions))
|
||||
|
||||
@@ -2,6 +2,7 @@ package fireflyIII
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
@@ -40,7 +41,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the firefly III transaction csv data
|
||||
func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
reader := bytes.NewReader(data)
|
||||
dataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, true)
|
||||
|
||||
@@ -52,5 +53,5 @@ func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Co
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(fireflyIIITransactionTypeNameMapping, "", "", ",")
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,19 @@ package fireflyIII
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -20,11 +22,11 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MinimumValidData(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
|
||||
"Deposit,0.12,2024-09-01T01:23:45+08:00,\"A revenue account\",\"Test Account\",\"Test Category\"\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category2\"\n"+
|
||||
"Transfer,0.05,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category3\""), 0, nil, nil, nil, nil, nil)
|
||||
"Transfer,0.05,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category3\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -82,8 +84,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MinimumValidData(t *testing
|
||||
assert.Equal(t, "Test Category3", allNewSubTransferCategories[0].Name)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -91,17 +93,17 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidTime(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01 12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01 12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -109,13 +111,13 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidType(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Type,123.45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Type,123.45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseAccountNameAsCategoryName(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseAccountNameAsCategoryName(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -123,23 +125,23 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseAccountNameAsCategoryN
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "A expense account", allNewTransactions[0].OriginalCategoryName)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Deposit,10.00,2024-09-01T12:34:56+08:00,\"A revenue account\",\"Test Account\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Deposit,10.00,2024-09-01T12:34:56+08:00,\"A revenue account\",\"Test Account\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "A revenue account", allNewTransactions[0].OriginalCategoryName)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -147,27 +149,27 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidTimezone(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56-10:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56-10:00,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+00:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+00:00,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+12:45,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+12:45,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -175,9 +177,9 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidAccountCurrency(t
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
|
||||
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category2\""), 0, nil, nil, nil, nil, nil)
|
||||
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category2\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -193,8 +195,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidAccountCurrency(t
|
||||
assert.Equal(t, "EUR", allNewAccounts[1].Currency)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndCurrency(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidForeignAmountAndCurrency(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -202,8 +204,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,15.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,15.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -213,8 +215,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -223,8 +225,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,,\"Test Account\",\"Test Account2\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -232,8 +234,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -241,19 +243,19 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAccountCurrency
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
|
||||
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account\",\"Test Account2\",\"Test Category3\""), 0, nil, nil, nil, nil, nil)
|
||||
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account\",\"Test Account2\",\"Test Category3\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
|
||||
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account2\",\"Test Account\",\"Test Category3\""), 0, nil, nil, nil, nil, nil)
|
||||
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account2\",\"Test Account\",\"Test Category3\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -261,17 +263,17 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseNotSupportedCurrency(t
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,XXX,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,XXX,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,123.45,123.45,2024-09-01T23:59:59+08:00,USD,XXX,\"Test Account\",\"Test Account2\",\"Test Category2\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,123.45,123.45,2024-09-01T23:59:59+08:00,USD,XXX,\"Test Account\",\"Test Account2\",\"Test Category2\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -279,17 +281,17 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAmount(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123 45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123 45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,source_name,destination_name,category\n"+
|
||||
"Transfer,123.45,123 45,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category2\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,source_name,destination_name,category\n"+
|
||||
"Transfer,123.45,123 45,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category2\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -297,16 +299,16 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseDescription(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,description,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123.45,\"foo bar\t#test\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,description,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123.45,\"foo bar\t#test\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseTags(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_ParseTags(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -314,8 +316,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseTags(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte("type,amount,tags,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123.45,\"tag1,tag2,tag3\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte("type,amount,tags,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123.45,\"tag1,tag2,tag3\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -328,8 +330,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseTags(t *testing.T) {
|
||||
assert.Equal(t, "tag3", allNewTags[2].Name)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -337,12 +339,12 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MissingFileHeader(t *testin
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
func TestFireFlyIIICsvFileimporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
importer := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -351,32 +353,32 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MissingRequiredColumn(t *te
|
||||
}
|
||||
|
||||
// Missing Time Column
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Type Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("amount,date,source_name,destination_name,category\n"+
|
||||
"123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("amount,date,source_name,destination_name,category\n"+
|
||||
"123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Sub Category Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Account Name Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,date,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,date,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Account2 Name Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (p *fireflyIIITransactionDataRowParser) Parse(data map[datatable.Transactio
|
||||
}
|
||||
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location())
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
|
||||
}
|
||||
|
||||
// trim trailing zero in decimal
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package gnucash
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -24,7 +26,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the gnucash transaction data
|
||||
func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
gnucashDataReader, err := createNewGnuCashDatabaseReader(data)
|
||||
|
||||
if err != nil {
|
||||
@@ -45,5 +47,5 @@ func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, use
|
||||
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(gnucashTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -185,7 +187,7 @@ const gnucashCommonValidDataCaseFooter = "</gnc:book>\n" +
|
||||
"</gnc-v2>\n"
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -193,14 +195,14 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidData(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(gnucashMinimumValidDataCase), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(gnucashMinimumValidDataCase), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_GzippedMinimumValidData(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -217,14 +219,14 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_GzippedMinimumValidData
|
||||
assert.Nil(t, err)
|
||||
|
||||
gzippedData := buffer.Bytes()
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, gzippedData, 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, gzippedData, time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithReversedSplitOrder(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -232,7 +234,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithRev
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"+
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"+
|
||||
"<gnc-v2\n"+
|
||||
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
||||
" xmlns:act=\"http://www.gnucash.org/XML/act\"\n"+
|
||||
@@ -356,14 +358,14 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithRev
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
"</gnc:book>\n"+
|
||||
"</gnc-v2>\n"), 0, nil, nil, nil, nil, nil)
|
||||
"</gnc-v2>\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -371,7 +373,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -388,10 +390,10 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -408,12 +410,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -421,7 +423,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -438,12 +440,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -460,14 +462,14 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -475,7 +477,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurren
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"+
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"+
|
||||
"<gnc-v2\n"+
|
||||
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
||||
" xmlns:act=\"http://www.gnucash.org/XML/act\"\n"+
|
||||
@@ -557,7 +559,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurren
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
"</gnc:book>\n"+
|
||||
"</gnc-v2>\n"), 0, nil, nil, nil, nil, nil)
|
||||
"</gnc-v2>\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -574,7 +576,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurren
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -582,7 +584,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -599,13 +601,13 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1234500), allNewTransactions[0].Amount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -622,7 +624,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -630,7 +632,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -638,7 +640,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -655,10 +657,10 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -675,10 +677,10 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -695,12 +697,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -708,7 +710,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -726,7 +728,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *tes
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -734,7 +736,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *tes
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransaction(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -742,7 +744,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransacti
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -755,12 +757,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransacti
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSplitTransaction(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -768,7 +770,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSpli
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:account version=\"2.0.0\">\n"+
|
||||
" <act:name>Test Category2</act:name>\n"+
|
||||
@@ -805,12 +807,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSpli
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredNode(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -819,7 +821,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredN
|
||||
}
|
||||
|
||||
// Missing Account Currency Node
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"+
|
||||
"<gnc-v2\n"+
|
||||
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
||||
@@ -872,12 +874,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredN
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
||||
converter := GnuCashTransactionDataImporter
|
||||
importer := GnuCashTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -886,7 +888,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
|
||||
}
|
||||
|
||||
// Missing Transaction Time Node
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:splits>\n"+
|
||||
@@ -900,22 +902,22 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
|
||||
|
||||
// Missing Transaction Splits Node
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
" <ts:date>2024-09-01 00:00:00 +0000</ts:date>\n"+
|
||||
" </trn:date-posted>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidGnuCashFile.Message)
|
||||
|
||||
// Missing Transaction Split Quantity Node
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -930,11 +932,11 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
// Missing Transaction Split Account Node
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
gnucashCommonValidDataCaseHeader+
|
||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||
" <trn:date-posted>\n"+
|
||||
@@ -950,7 +952,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
|
||||
" </trn:split>\n"+
|
||||
" </trn:splits>\n"+
|
||||
"</gnc:transaction>\n"+
|
||||
gnucashCommonValidDataCaseFooter), 0, nil, nil, nil, nil, nil)
|
||||
gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingAccountData.Message)
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func (t *gnucashTransactionDataRowIterator) parseTransaction(ctx core.Context, u
|
||||
}
|
||||
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location())
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
|
||||
|
||||
if len(gnucashTransaction.Splits) == 2 {
|
||||
splitData1 := gnucashTransaction.Splits[0]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package iif
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -23,7 +25,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the intuit interchange format (iif) data
|
||||
func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
iifDataReader := createNewIifDataReader(data)
|
||||
accountDatasets, transactionDatasets, err := iifDataReader.read(ctx)
|
||||
|
||||
@@ -39,5 +41,5 @@ func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, use
|
||||
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(iifTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package iif
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -12,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -20,7 +22,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
||||
"ACCNT\tTest Account\tBANK\n"+
|
||||
"ACCNT\tTest Account2\tBANK\n"+
|
||||
@@ -49,7 +51,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
"ENDTRNS\t\t\t\t\n"+
|
||||
"TRNS\tCREDIT CARD\t09/07/2024\tTest Category2\t34.56\n"+
|
||||
"SPL\tCREDIT CARD\t09/07/2024\tTest Account2\t-34.56\n"+
|
||||
"ENDTRNS\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -132,7 +134,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountData(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -140,13 +142,13 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountD
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Category\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -162,7 +164,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountD
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -170,7 +172,7 @@ func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
||||
"ACCNT\tTest Account3\tBANK\n"+
|
||||
"ACCNT\tTest Account4\tBANK\n"+
|
||||
@@ -202,7 +204,7 @@ func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
||||
"ENDTRNS\t\t\t\t\n"+
|
||||
"!ACCNT\tTEST\tNAME\tACCNTTYPE\n"+
|
||||
"ACCNT\t\tTest Category\tINC\n"+
|
||||
"ACCNT\t\tTest Category2\tEXP\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ACCNT\t\tTest Category2\tEXP\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -247,7 +249,7 @@ func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -255,7 +257,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, allNewSubExpenseCategories, allNewSubIncomeCategories, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, allNewSubExpenseCategories, allNewSubIncomeCategories, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
||||
"ACCNT\tTest Parent Category:Test Category\tINC\n"+
|
||||
"ACCNT\tTest Parent Category2:Test Category2\tEXP\n"+
|
||||
@@ -267,7 +269,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/02/2024\tTest Account2\t-123.45\n"+
|
||||
"SPL\t09/02/2024\tTest Parent Category2:Test Category2\t123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -299,7 +301,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -307,7 +309,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
@@ -322,7 +324,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t2024/9/4\tTest Account\t123.45\n"+
|
||||
"SPL\t2024/9/4\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -334,7 +336,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTime(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -342,7 +344,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTim
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
@@ -354,7 +356,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTim
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t9/3/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t9/3/2024\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -365,7 +367,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTim
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYearFormatTime(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -373,7 +375,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYear
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
@@ -385,7 +387,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYear
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t24/9/3\tTest Account\t123.45\n"+
|
||||
"SPL\t24/9/3\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -396,7 +398,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYear
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -404,36 +406,36 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09-01-2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09-01-2024\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t2024-09-01\tTest Account\t123.45\n"+
|
||||
"SPL\t2024-09-01\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t9/24\tTest Account\t123.45\n"+
|
||||
"SPL\t9/24\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparator(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -441,13 +443,13 @@ func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t9/01/2024\tTest Account\t123,456.78\n"+
|
||||
"SPL\t9/01/2024\tTest Account2\t-123,456.78\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -456,7 +458,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -464,27 +466,27 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123 45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123 45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -492,25 +494,25 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!ENDTRNS\t\t\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t\t-123.45\t\n"+
|
||||
"ENDTRNS\t\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!ENDTRNS\t\t\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\tTest\t123.45\t\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t\t-123.45\t\n"+
|
||||
"ENDTRNS\t\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -518,7 +520,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -526,7 +528,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testin
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
||||
"ACCNT\tTest Category\tINC\n"+
|
||||
"ACCNT\tTest Category2\tEXP\n"+
|
||||
@@ -552,7 +554,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testin
|
||||
"TRNS\t09/05/2024\tTest Category2\t100.00\n"+
|
||||
"SPL\t09/05/2024\tTest Account3\t-40.00\n"+
|
||||
"SPL\t09/05/2024\tTest Account4\t-60.00\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -651,7 +653,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testin
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescription(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -659,21 +661,21 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescriptio
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!ENDTRNS\t\t\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t\t-100.00\t\"foo\ttest#bar\"\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\t\t-23.45\t\n"+
|
||||
"ENDTRNS\t\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(allNewTransactions))
|
||||
assert.Equal(t, "foo\ttest#bar", allNewTransactions[0].Comment)
|
||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[1].Comment)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!ENDTRNS\t\t\t\t\t\n"+
|
||||
@@ -681,7 +683,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescriptio
|
||||
"SPL\t09/01/2024\tTest Account2\t\t-100.00\t\"test\"\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\tfoo\t-12.34\t\n"+
|
||||
"SPL\t09/01/2024\tTest Account4\t\t-11.11\t\n"+
|
||||
"ENDTRNS\t\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(allNewTransactions))
|
||||
@@ -691,7 +693,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescriptio
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -700,52 +702,52 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
|
||||
}
|
||||
|
||||
// Opening balance transaction
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tTRNSTYPE\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tTRNSTYPE\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\t\n"+
|
||||
"TRNS\tBEGINBALCHECK\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\tBEGINBALCHECK\t09/01/2024\tTest Account2\t-100.00\n"+
|
||||
"SPL\tBEGINBALCHECK\t09/01/2024\tTest Account3\t-23.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||
|
||||
// Transaction with invalid amount
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123 45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-100.00\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
// Transaction split data with invalid amount
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-100 00\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
// Transaction amount not equal to sum of split data amount
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.00\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-100.00\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -754,75 +756,75 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
|
||||
}
|
||||
|
||||
//Missing Transaction Line
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Transaction And Split Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Split Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Transaction End Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"), 0, nil, nil, nil, nil, nil)
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Transaction End Line (following is another header)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
||||
"ACCNT\tTest Account\tBANK\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ACCNT\tTest Account\tBANK\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Invalid Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||
"TEST\t\t\t\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Repeat Transaction Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Repeat Transaction End Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\t\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\t\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
@@ -830,12 +832,12 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -844,49 +846,49 @@ func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T
|
||||
}
|
||||
|
||||
// Missing All Sample Lines
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Transaction Sample Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"!ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Split Sample Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"!ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Transaction End Sample Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"), 0, nil, nil, nil, nil, nil)
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Transaction End Sample Line (following is data line)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Invalid Sample Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!TEST\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"!ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
importer := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -895,32 +897,32 @@ func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testin
|
||||
}
|
||||
|
||||
// Missing Date Column
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tACCNT\tAMOUNT\t\n"+
|
||||
"!SPL\tACCNT\tAMOUNT\t\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\tTest Account\t123.45\n"+
|
||||
"SPL\tTest Account2\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Account Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tAMOUNT\t\n"+
|
||||
"!SPL\tDATE\tAMOUNT\t\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\t123.45\n"+
|
||||
"SPL\t09/01/2024\t-123.45\n"+
|
||||
"ENDTRNS\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\t\n"+
|
||||
"!SPL\tDATE\tACCNT\t\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\n"+
|
||||
"ENDTRNS\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
"ENDTRNS\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package jdcom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
@@ -27,7 +28,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the jd.com finance transaction csv data
|
||||
func (c *jdComFinanceTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *jdComFinanceTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
fallback := unicode.UTF8.NewDecoder()
|
||||
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
||||
|
||||
@@ -60,5 +61,5 @@ func (c *jdComFinanceTransactionDataCsvFileImporter) ParseImportedData(ctx core.
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, jdComFinanceTransactionSupportedColumns, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(jdComFinanceTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -30,7 +31,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MinimumValidData(t *testin
|
||||
"2025-09-01 12:34:56,xxx,xxx,123.45,银行卡,交易成功,支出,其他网购\n" +
|
||||
"2025-09-01 23:59:59,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n" +
|
||||
"2025-09-02 23:59:59,xxx,京东余额提现,0.03,银行卡,交易成功,不计收支,余额\n"
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 4, len(allNewTransactions))
|
||||
@@ -93,7 +94,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MinimumValidData(t *testin
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -110,7 +111,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *
|
||||
"2025-09-01 02:34:56,xxx,xxx,0.12(已全额退款),银行卡,交易成功,不计收支\n" +
|
||||
"2025-09-02 01:23:45,xxx,xxx,3.45,银行卡,退款成功,不计收支\n" +
|
||||
"2025-09-02 02:34:56,xxx,xxx,123.45(已退款3.45),银行卡,交易成功,支出\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||
@@ -139,7 +140,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -153,7 +154,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testin
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01T01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
data2 := "导出信息:\n" +
|
||||
@@ -162,12 +163,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testin
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"09/01/2025 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -181,12 +182,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidType(t *testin
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,转账\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -200,12 +201,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidAmount(t *test
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,¥0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -220,7 +221,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -235,7 +236,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,京东余额提现,0.05,银行卡,交易成功,不计收支,余额\n"
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -252,7 +253,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
||||
"2025-09-01 01:23:45,xxx,京东小金库-转入,0.05,余额,交易成功,不计收支,小金库\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -269,7 +270,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
||||
"2025-09-01 01:23:45,xxx,京东小金库-转出,0.05,余额,交易成功,不计收支,小金库\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -286,7 +287,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
||||
"2025-09-01 01:23:45,xxx,价保退款,0.05,银行卡,交易成功,不计收支,其他\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data5), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -302,7 +303,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
||||
"2025-09-01 01:23:45,xxx,白条主动还款,0.05,银行卡,交易成功,不计收支,白条\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data6), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -312,7 +313,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -326,7 +327,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,,0.12,银行卡,交易成功,支出\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -338,7 +339,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\"foo\"\"bar,\ntest\"\n"
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment)
|
||||
|
||||
@@ -348,13 +349,13 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\n"
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -368,12 +369,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownStatusTransacti
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,xxxx,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownMemoTransferTransaction(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -387,12 +388,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownMemoTransferTra
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,不计收支\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -402,15 +403,15 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingFileHeader(t *testi
|
||||
|
||||
data := "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -425,7 +426,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
||||
"\n" +
|
||||
"商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"xxx,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
|
||||
// Missing Merchant Name Column
|
||||
@@ -435,7 +436,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
||||
"\n" +
|
||||
"交易时间,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Transaction Memo Column
|
||||
@@ -445,7 +446,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
||||
"\n" +
|
||||
"交易时间,商户名称,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
@@ -455,7 +456,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Related Account Column
|
||||
@@ -465,7 +466,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data5), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Status Column
|
||||
@@ -475,7 +476,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data6), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Type Column
|
||||
@@ -485,12 +486,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data7), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data7), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -503,6 +504,6 @@ func TestJDComFinanceCsvFileImporterParseImportedData_NoTransactionData(t *testi
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package mt
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -22,7 +24,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the mt940 file statement data
|
||||
func (c *mt940TransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *mt940TransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
mt940DataReader := createNewMT940FileReader(data)
|
||||
mt940Data, err := mt940DataReader.read(ctx)
|
||||
|
||||
@@ -38,5 +40,5 @@ func (c *mt940TransactionDataFileImporter) ParseImportedData(ctx core.Context, u
|
||||
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(mt940TransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package mt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -12,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := MT940TransactionDataFileImporter
|
||||
importer := MT940TransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -20,7 +22,7 @@ func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||
:20:123456789
|
||||
:25:12345678
|
||||
@@ -31,7 +33,7 @@ func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T
|
||||
:61:2506020603D234,56NTRFFOOBAR
|
||||
:86:Transaction 2
|
||||
:62F:C250601CNY123,45
|
||||
-}`), 0, nil, nil, nil, nil, nil)
|
||||
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -70,7 +72,7 @@ func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T
|
||||
}
|
||||
|
||||
func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAndCurrency(t *testing.T) {
|
||||
converter := MT940TransactionDataFileImporter
|
||||
importer := MT940TransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -78,7 +80,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAn
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||
:20:123456789
|
||||
:25:12345678
|
||||
@@ -91,7 +93,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAn
|
||||
:61:250603C1,NTRFTEST
|
||||
:86:Transaction 3
|
||||
:62F:C250601CNY123,45
|
||||
-}`), 0, nil, nil, nil, nil, nil)
|
||||
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(allNewTransactions))
|
||||
@@ -101,7 +103,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAn
|
||||
}
|
||||
|
||||
func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmountAndCurrency(t *testing.T) {
|
||||
converter := MT940TransactionDataFileImporter
|
||||
importer := MT940TransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -109,7 +111,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||
:20:123456789
|
||||
:25:12345678
|
||||
@@ -117,10 +119,10 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
|
||||
:60F:C250601CNY123,45
|
||||
:61:2506010602C123 45NTRFTEST
|
||||
:62F:C250601CNY123,45
|
||||
-}`), 0, nil, nil, nil, nil, nil)
|
||||
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidMT940File.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||
:20:123456789
|
||||
:25:12345678
|
||||
@@ -128,12 +130,12 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
|
||||
:60F:C250601CNY123,45
|
||||
:61:2506010602C12.45NTRFTEST
|
||||
:62F:C250601CNY123,45
|
||||
-}`), 0, nil, nil, nil, nil, nil)
|
||||
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidMT940File.Message)
|
||||
}
|
||||
|
||||
func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testing.T) {
|
||||
converter := MT940TransactionDataFileImporter
|
||||
importer := MT940TransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -141,7 +143,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||
:20:123456789
|
||||
:25:12345678
|
||||
@@ -156,7 +158,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testi
|
||||
:61:250604RD123,45NTRFTEST
|
||||
:86:Transaction 4
|
||||
:62F:C250601CNY123,45
|
||||
-}`), 0, nil, nil, nil, nil, nil)
|
||||
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, len(allNewTransactions))
|
||||
@@ -167,7 +169,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testi
|
||||
}
|
||||
|
||||
func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := MT940TransactionDataFileImporter
|
||||
importer := MT940TransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -175,7 +177,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||
:20:123456789
|
||||
:25:12345678
|
||||
@@ -186,7 +188,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T
|
||||
Part 2
|
||||
Part 3
|
||||
:62F:C250601CNY123,45
|
||||
-}`), 0, nil, nil, nil, nil, nil)
|
||||
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -194,7 +196,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T
|
||||
}
|
||||
|
||||
func TestMT940TransactionDataFileParseImportedData_MissingRequiredField(t *testing.T) {
|
||||
converter := MT940TransactionDataFileImporter
|
||||
importer := MT940TransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -203,12 +205,12 @@ func TestMT940TransactionDataFileParseImportedData_MissingRequiredField(t *testi
|
||||
}
|
||||
|
||||
// Missing opening balance and closing balance
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||
:20:123456789
|
||||
:28C:123/1
|
||||
:61:250601C123,45NTRFTEST
|
||||
:86:Transaction 1
|
||||
-}`), 0, nil, nil, nil, nil, nil)
|
||||
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
const ofx1USAsciiEncoding = "usascii"
|
||||
const ofx1UnicodeEncoding = "unicode"
|
||||
const ofx1UTF8Encoding = "utf8" // non-standard ofx 1.x encoding, used by some banks (https://github.com/mayswind/ezbookkeeping/issues/48)
|
||||
const ofx1UTF8Encoding = "utf-8" // non-standard ofx 1.x encoding, used by some banks (https://github.com/mayswind/ezbookkeeping/issues/48)
|
||||
const ofx1SGMLDataFormat = "OFXSGML"
|
||||
|
||||
var ofx2HeaderPattern = regexp.MustCompile("<\\?OFX( +[A-Z]+=\"[^=]*\")* *\\?>")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package ofx
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -23,7 +25,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the open financial exchange (ofx) file transaction data
|
||||
func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
ofxDataReader, err := createNewOFXFileReader(ctx, data)
|
||||
|
||||
if err != nil {
|
||||
@@ -44,5 +46,5 @@ func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *m
|
||||
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(ofxTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package ofx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -12,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -20,7 +22,7 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -76,7 +78,7 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
" </CCSTMTRS>\n"+
|
||||
" </CCSTMTTRNRS>\n"+
|
||||
" </CREDITCARDMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -160,7 +162,7 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -168,7 +170,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -210,7 +212,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
||||
" </CCSTMTRS>\n"+
|
||||
" </CCSTMTTRNRS>\n"+
|
||||
" </CREDITCARDMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -243,7 +245,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -251,7 +253,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -295,7 +297,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -310,7 +312,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -318,7 +320,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -337,10 +339,10 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -359,10 +361,10 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -381,10 +383,10 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -403,12 +405,12 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -416,7 +418,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -435,7 +437,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -444,7 +446,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -452,7 +454,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -471,12 +473,12 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -484,7 +486,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -504,7 +506,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -512,7 +514,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -520,7 +522,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -541,13 +543,13 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -567,13 +569,13 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -595,7 +597,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -603,7 +605,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -612,7 +614,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testi
|
||||
}
|
||||
|
||||
// Missing Posted Date Node
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -628,12 +630,12 @@ func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testi
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingAccountData.Message)
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -642,7 +644,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.
|
||||
}
|
||||
|
||||
// Missing Default Currency Node
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -660,12 +662,12 @@ func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
||||
converter := OFXTransactionDataImporter
|
||||
importer := OFXTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -674,7 +676,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
|
||||
}
|
||||
|
||||
// Missing Posted Date Node
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -692,11 +694,11 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
|
||||
|
||||
// Missing Transaction Type Node
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -714,11 +716,11 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||
|
||||
// Missing Amount Node
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"<OFX>\n"+
|
||||
" <BANKMSGSRSV1>\n"+
|
||||
" <STMTTRNRS>\n"+
|
||||
@@ -736,6 +738,6 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
|
||||
" </STMTRS>\n"+
|
||||
" </STMTTRNRS>\n"+
|
||||
" </BANKMSGSRSV1>\n"+
|
||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
||||
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package qif
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -35,7 +37,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the quicken interchange format (qif) transaction data
|
||||
func (c *qifTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *qifTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
qifDataReader := createNewQifDataReader(data)
|
||||
qifData, err := qifDataReader.read(ctx)
|
||||
|
||||
@@ -51,5 +53,5 @@ func (c *qifTransactionDataImporter) ParseImportedData(ctx core.Context, user *m
|
||||
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(qifTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package qif
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -12,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -20,7 +22,7 @@ func TestQIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T123.45\n"+
|
||||
@@ -42,7 +44,7 @@ func TestQIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
"D2024-09-05\n"+
|
||||
"T0.06\n"+
|
||||
"L[Test Account2]\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -113,7 +115,7 @@ func TestQIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseYearMonthDayDateFormatTime(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -121,7 +123,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseYearMonthDayDateFormatTime
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
@@ -137,7 +139,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseYearMonthDayDateFormatTime
|
||||
"^\n"+
|
||||
"D2024'9.5\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -151,7 +153,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseYearMonthDayDateFormatTime
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseMonthDayYearDateFormatTime(t *testing.T) {
|
||||
converter := QifMonthDayYearTransactionDataImporter
|
||||
importer := QifMonthDayYearTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -159,7 +161,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseMonthDayYearDateFormatTime
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D09-01-2024\n"+
|
||||
"T-123.45\n"+
|
||||
@@ -175,7 +177,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseMonthDayYearDateFormatTime
|
||||
"^\n"+
|
||||
"D9.5'2024\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -189,7 +191,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseMonthDayYearDateFormatTime
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseDayYearMonthDateFormatTime(t *testing.T) {
|
||||
converter := QifDayMonthYearTransactionDataImporter
|
||||
importer := QifDayMonthYearTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -197,7 +199,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDayYearMonthDateFormatTime
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D01-09-2024\n"+
|
||||
"T-123.45\n"+
|
||||
@@ -213,7 +215,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDayYearMonthDateFormatTime
|
||||
"^\n"+
|
||||
"D5'9.2024\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -227,7 +229,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDayYearMonthDateFormatTime
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseShortYearMonthDayDateFormatTime(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -235,7 +237,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseShortYearMonthDayDateForma
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D24-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
@@ -251,7 +253,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseShortYearMonthDayDateForma
|
||||
"^\n"+
|
||||
"D24'9.5\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -265,7 +267,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseShortYearMonthDayDateForma
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -273,16 +275,16 @@ func TestQIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024 09 01\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparator(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -290,11 +292,11 @@ func TestQIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123,456.78\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -304,7 +306,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -312,16 +314,16 @@ func TestQIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123 45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -329,11 +331,11 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Cash\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -342,11 +344,11 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
|
||||
assert.Equal(t, int64(1725148800), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:CCard\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -355,11 +357,11 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
|
||||
assert.Equal(t, int64(1725148800), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Oth A\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -368,11 +370,11 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
|
||||
assert.Equal(t, int64(1725148800), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Oth L\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -383,7 +385,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseAccount(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -391,14 +393,14 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccount(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Account\n"+
|
||||
"NTest Account\n"+
|
||||
"^\n"+
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -412,7 +414,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccount(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseAmountWithLeadingPlusSign(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -420,11 +422,11 @@ func TestQIFTransactionDataFileParseImportedData_ParseAmountWithLeadingPlusSign(
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T+123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -432,7 +434,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAmountWithLeadingPlusSign(
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseSubCategory(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -440,12 +442,12 @@ func TestQIFTransactionDataFileParseImportedData_ParseSubCategory(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, allNewSubExpenseCategories, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, allNewSubExpenseCategories, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"LTest Category:Sub Category\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -458,7 +460,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseSubCategory(t *testing.T)
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -466,7 +468,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
@@ -476,7 +478,24 @@ func TestQIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
"D2024-09-02\n"+
|
||||
"T-234.56\n"+
|
||||
"PTest2\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(allNewTransactions))
|
||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
||||
assert.Equal(t, "", allNewTransactions[1].Comment)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"PTest\n"+
|
||||
"Mfoo bar\t#test\n"+
|
||||
"^\n"+
|
||||
"D2024-09-02\n"+
|
||||
"T-234.56\n"+
|
||||
"PTest2\n"+
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions.WithPayeeAsDescription(), nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(allNewTransactions))
|
||||
@@ -484,8 +503,41 @@ func TestQIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
assert.Equal(t, "Test2", allNewTransactions[1].Comment)
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_WithAdditionalOptions(t *testing.T) {
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"PTest2\n"+
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, 0, len(allNewTransactions[0].OriginalTagNames))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"T-123.45\n"+
|
||||
"PTest2\n"+
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions.WithPayeeAsTag(), nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, 1, len(allNewTransactions[0].OriginalTagNames))
|
||||
assert.Equal(t, "Test2", allNewTransactions[0].OriginalTagNames[0])
|
||||
}
|
||||
|
||||
func TestQIFTransactionDataFileParseImportedData_MissingRequiredFields(t *testing.T) {
|
||||
converter := QifYearMonthDayTransactionDataImporter
|
||||
importer := QifYearMonthDayTransactionDataImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -494,16 +546,16 @@ func TestQIFTransactionDataFileParseImportedData_MissingRequiredFields(t *testin
|
||||
}
|
||||
|
||||
// Missing Time Field
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"T-123.45\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
|
||||
|
||||
// Missing Amount Field
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||
"!Type:Bank\n"+
|
||||
"D2024-09-01\n"+
|
||||
"^\n"), 0, nil, nil, nil, nil, nil)
|
||||
"^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ var qifTransactionSupportedColumns = map[datatable.TransactionDataTableColumn]bo
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_PAYEE: true,
|
||||
}
|
||||
|
||||
// qifDateFormatType represents the quicken interchange format (qif) date format type
|
||||
@@ -184,8 +185,10 @@ func (t *qifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
|
||||
if qifTransaction.Memo != "" {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = qifTransaction.Memo
|
||||
} else if qifTransaction.Payee != "" && qifTransaction.Payee != qifOpeningBalancePayeeText {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = qifTransaction.Payee
|
||||
}
|
||||
|
||||
if qifTransaction.Payee != "" && qifTransaction.Payee != qifOpeningBalancePayeeText {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_PAYEE] = qifTransaction.Payee
|
||||
}
|
||||
|
||||
return data, nil
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/beancount"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/camt"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/custom"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/default"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/dsv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/feidee"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/fireflyIII"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/gnucash"
|
||||
@@ -52,6 +52,8 @@ func GetTransactionDataImporter(fileType string) (converter.TransactionDataImpor
|
||||
return qif.QifDayMonthYearTransactionDataImporter, nil
|
||||
} else if fileType == "iif" {
|
||||
return iif.IifTransactionDataFileImporter, nil
|
||||
} else if fileType == "camt052" {
|
||||
return camt.Camt052TransactionDataImporter, nil
|
||||
} else if fileType == "camt053" {
|
||||
return camt.Camt053TransactionDataImporter, nil
|
||||
} else if fileType == "mt940" {
|
||||
@@ -83,17 +85,29 @@ func GetTransactionDataImporter(fileType string) (converter.TransactionDataImpor
|
||||
}
|
||||
}
|
||||
|
||||
// IsCustomDelimiterSeparatedValuesFileType returns whether the file type is the delimiter-separated values file type
|
||||
func IsCustomDelimiterSeparatedValuesFileType(fileType string) bool {
|
||||
return dsv.IsDelimiterSeparatedValuesFileType(fileType)
|
||||
// IsCustomFileFormatFileType returns whether the file type is the custom file format
|
||||
func IsCustomFileFormatFileType(fileType string) bool {
|
||||
return custom.IsDelimiterSeparatedValuesFileType(fileType) || custom.IsCustomExcelFileType(fileType)
|
||||
}
|
||||
|
||||
// CreateNewDelimiterSeparatedValuesDataParser returns a new delimiter-separated values data parser according to the file type and encoding
|
||||
func CreateNewDelimiterSeparatedValuesDataParser(fileType string, fileEncoding string) (dsv.CustomTransactionDataDsvFileParser, error) {
|
||||
return dsv.CreateNewCustomTransactionDataDsvFileParser(fileType, fileEncoding)
|
||||
// CreateNewCustomFileFormatTransactionDataParser returns a new custom transaction data parser according to the file type and encoding
|
||||
func CreateNewCustomFileFormatTransactionDataParser(fileType string, fileEncoding string) (custom.CustomTransactionDataParser, error) {
|
||||
if custom.IsDelimiterSeparatedValuesFileType(fileType) {
|
||||
return custom.CreateNewCustomTransactionDataDsvFileParser(fileType, fileEncoding)
|
||||
} else if custom.IsCustomExcelFileType(fileType) {
|
||||
return custom.CreateNewCustomTransactionDataExcelFileParser(fileType)
|
||||
} else {
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewDelimiterSeparatedValuesDataImporter returns a new delimiter-separated values data importer according to the file type and encoding
|
||||
func CreateNewDelimiterSeparatedValuesDataImporter(fileType string, fileEncoding string, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, hasHeaderLine bool, timeFormat string, timezoneFormat string, amountDecimalSeparator string, amountDigitGroupingSymbol string, geoLocationSeparator string, geoLocationOrder string, transactionTagSeparator string) (converter.TransactionDataImporter, error) {
|
||||
return dsv.CreateNewCustomTransactionDataDsvFileImporter(fileType, fileEncoding, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormat, timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoLocationSeparator, geoLocationOrder, transactionTagSeparator)
|
||||
// CreateNewCustomTransactionDataImporter returns a new custom transaction data importer according to the file type and encoding
|
||||
func CreateNewCustomTransactionDataImporter(fileType string, fileEncoding string, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, hasHeaderLine bool, timeFormat string, timezoneFormat string, amountDecimalSeparator string, amountDigitGroupingSymbol string, geoLocationSeparator string, geoLocationOrder string, transactionTagSeparator string) (converter.TransactionDataImporter, error) {
|
||||
if custom.IsDelimiterSeparatedValuesFileType(fileType) {
|
||||
return custom.CreateNewCustomTransactionDataDsvFileImporter(fileType, fileEncoding, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormat, timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoLocationSeparator, geoLocationOrder, transactionTagSeparator)
|
||||
} else if custom.IsCustomExcelFileType(fileType) {
|
||||
return custom.CreateNewCustomTransactionDataExcelFileImporter(fileType, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormat, timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoLocationSeparator, geoLocationOrder, transactionTagSeparator)
|
||||
} else {
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package wechat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
@@ -27,7 +28,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the wechat pay transaction csv data
|
||||
func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
fallback := unicode.UTF8.NewDecoder()
|
||||
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
||||
|
||||
@@ -58,5 +59,5 @@ func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Con
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, wechatPayTransactionSupportedColumns, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(wechatPayTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -31,7 +32,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T
|
||||
"2024-09-01 12:34:56,商户消费,支出,¥123.45,支付成功\n" +
|
||||
"2024-09-01 23:59:59,零钱充值,/,¥0.05,充值完成\n" +
|
||||
"2024-09-02 23:59:59,零钱提现,/,¥0.03,提现已到账\n"
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 4, len(allNewTransactions))
|
||||
@@ -93,7 +94,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -108,7 +109,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseRefundTransaction(t *tes
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),当前状态\n" +
|
||||
"2024-09-01 01:23:45,xxx-退款,收入,¥0.12,已全额退款\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||
@@ -120,7 +121,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseRefundTransaction(t *tes
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -135,7 +136,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),当前状态\n" +
|
||||
"2024-09-01T01:23:45,二维码收款,收入,¥0.12,已收钱\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
data2 := "微信支付账单明细,,,,\n" +
|
||||
@@ -145,12 +146,12 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),当前状态\n" +
|
||||
"09/01/2024 12:34:56,二维码收款,收入,¥0.12,已收钱\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -165,12 +166,35 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),当前状态\n" +
|
||||
"2024-09-01T01:23:45,xxx,,¥0.12,支付成功\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_ParseAmountWithThousandSeparator(t *testing.T) {
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data1 := "微信支付账单明细,,,,\n" +
|
||||
"微信昵称:[xxx],,,,\n" +
|
||||
"起始时间:[2024-01-01 00:00:00] 终止时间:[2024-09-01 23:59:59],,,,\n" +
|
||||
",,,,\n" +
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),支付方式,当前状态\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,收入,\"¥1,234.56\",/,已收钱\n"
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(123456), allNewTransactions[0].Amount)
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -185,12 +209,12 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),当前状态\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,收入,¥,已收钱\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -206,7 +230,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),支付方式,当前状态\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,收入,¥0.12,/,已收钱\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -222,7 +246,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
|
||||
"2024-09-01 01:23:45,xxx-退款,收入,¥0.12,test,已全额退款\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -238,7 +262,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
|
||||
"2024-09-01 23:59:59,零钱充值,/,¥0.05,test,充值完成\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -255,7 +279,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
|
||||
"2024-09-02 23:59:59,零钱提现,/,¥0.03,test,提现已到账\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -272,7 +296,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
|
||||
"2024-09-03 23:59:59,信用卡还款,/,¥0.01,零钱,支付成功\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data5), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -281,7 +305,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -296,7 +320,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),当前状态,备注\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,收入,¥0.12,已收钱,\"/\"\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -309,7 +333,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,商品,收/支,金额(元),当前状态,备注\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,Test,收入,¥0.12,已收钱,\"foo\"\"bar,\ntest\"\n"
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment)
|
||||
|
||||
@@ -320,13 +344,13 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,商品,收/支,金额(元),当前状态,备注\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,Test,收入,¥0.12,已收钱,\"\"\n"
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_SkipUnknownTransferTransaction(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -341,12 +365,12 @@ func TestWeChatPayCsvFileImporterParseImportedData_SkipUnknownTransferTransactio
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),当前状态\n" +
|
||||
"2024-09-01 23:59:59,/,/,¥0.05,充值完成\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -356,15 +380,15 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.
|
||||
|
||||
data := "交易时间,交易类型,收/支,金额(元),当前状态\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,收入,¥0.12,已收钱\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -380,7 +404,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易类型,收/支,金额(元),当前状态\n" +
|
||||
"二维码收款,收入,¥0.12,已收钱\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Category Column
|
||||
@@ -391,7 +415,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,收/支,金额(元),当前状态\n" +
|
||||
"2024-09-01 01:23:45,收入,¥0.12,已收钱\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Type Column
|
||||
@@ -402,7 +426,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,金额(元),当前状态\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,¥0.12,已收钱\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
@@ -413,7 +437,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,当前状态\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,收入,已收钱\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Status Column
|
||||
@@ -424,12 +448,12 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元)\n" +
|
||||
"2024-09-01 01:23:45,二维码收款,收入,¥0.12\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data5), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
func TestWeChatPayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
||||
converter := WeChatPayTransactionDataCsvFileImporter
|
||||
importer := WeChatPayTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
@@ -443,6 +467,6 @@ func TestWeChatPayCsvFileImporterParseImportedData_NoTransactionData(t *testing.
|
||||
",,,,\n" +
|
||||
"----------------------微信支付账单明细列表--------------------,,,,\n" +
|
||||
"交易时间,交易类型,收/支,金额(元),当前状态\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (p *weChatPayTransactionDataRowParser) Parse(ctx core.Context, user *models
|
||||
}
|
||||
|
||||
if p.hasOriginalColumn(wechatPayTransactionAmountColumnName) {
|
||||
amount, success := utils.ParseFirstConsecutiveNumber(dataRow.GetData(wechatPayTransactionAmountColumnName))
|
||||
amount, success := utils.ParseFirstConsecutiveNumber(strings.ReplaceAll(dataRow.GetData(wechatPayTransactionAmountColumnName), ",", ""))
|
||||
|
||||
if !success {
|
||||
log.Errorf(ctx, "[wechat_pay_transaction_data_row_parser.Parse] cannot parse amount \"%s\" of transaction in row \"%s\"", dataRow.GetData(wechatPayTransactionAmountColumnName), rowId)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/excel"
|
||||
@@ -21,7 +23,7 @@ var (
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the wechat pay transaction csv data
|
||||
func (c *wechatPayTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *wechatPayTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
xlsxDataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, false)
|
||||
|
||||
if err != nil {
|
||||
@@ -49,5 +51,5 @@ func (c *wechatPayTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Co
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, wechatPayTransactionSupportedColumns, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(wechatPayTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package core
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ApplicationName represents the application name
|
||||
const ApplicationName = "ezBookkeeping"
|
||||
|
||||
// Version, CommitHash and BuildTime are set at build
|
||||
var (
|
||||
Version string
|
||||
CommitHash string
|
||||
BuildTime string
|
||||
)
|
||||
|
||||
func GetOutgoingUserAgent() string {
|
||||
if Version == "" {
|
||||
return ApplicationName
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", ApplicationName, Version)
|
||||
}
|
||||
+38
-7
@@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
@@ -25,6 +26,9 @@ const RemoteClientPortHeader = "X-Real-Port"
|
||||
// ClientTimezoneOffsetHeaderName represents the header name of client timezone offset
|
||||
const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset"
|
||||
|
||||
// ClientTimezoneNameHeaderName represents the header name of client timezone name
|
||||
const ClientTimezoneNameHeaderName = "X-Timezone-Name"
|
||||
|
||||
const tokenHeaderName = "Authorization"
|
||||
const tokenHeaderValuePrefix = "bearer "
|
||||
const tokenQueryStringParam = "token"
|
||||
@@ -183,16 +187,24 @@ func (c *WebContext) GetClientLocale() string {
|
||||
return value
|
||||
}
|
||||
|
||||
// GetClientTimezoneOffset returns the client timezone offset
|
||||
func (c *WebContext) GetClientTimezoneOffset() (int16, error) {
|
||||
value := c.GetHeader(ClientTimezoneOffsetHeaderName)
|
||||
offset, err := strconv.Atoi(value)
|
||||
func (c *WebContext) GetClientTimezone() (*time.Location, error) {
|
||||
timezoneName := c.getClientTimezoneName()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
if timezoneName != "" {
|
||||
location, err := time.LoadLocation(timezoneName)
|
||||
|
||||
if err == nil && location != nil {
|
||||
return location, nil
|
||||
}
|
||||
}
|
||||
|
||||
return int16(offset), nil
|
||||
utcOffset, err := c.getClientTimezoneOffset()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return time.FixedZone("Client Fixed Timezone", int(utcOffset)*60), nil
|
||||
}
|
||||
|
||||
// SetResponseError sets the response error
|
||||
@@ -211,6 +223,25 @@ func (c *WebContext) GetResponseError() *errs.Error {
|
||||
return err.(*errs.Error)
|
||||
}
|
||||
|
||||
// GetClientTimezoneOffset returns the client timezone offset
|
||||
func (c *WebContext) getClientTimezoneOffset() (int16, error) {
|
||||
value := c.GetHeader(ClientTimezoneOffsetHeaderName)
|
||||
offset, err := strconv.Atoi(value)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int16(offset), nil
|
||||
}
|
||||
|
||||
// GetClientTimezoneName returns the client timezone name
|
||||
func (c *WebContext) getClientTimezoneName() string {
|
||||
value := c.GetHeader(ClientTimezoneNameHeaderName)
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// WrapWebContext returns a context wrapped by this file
|
||||
func WrapWebContext(ginCtx *gin.Context) *WebContext {
|
||||
return &WebContext{
|
||||
|
||||
@@ -6,6 +6,15 @@ import (
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// TokenUserAgentCreatedViaCli is the user agent of token created via cli
|
||||
const TokenUserAgentCreatedViaCli = ApplicationName + " Cli"
|
||||
|
||||
// TokenUserAgentForAPI is the user agent for API token
|
||||
const TokenUserAgentForAPI = ApplicationName + " API"
|
||||
|
||||
// TokenUserAgentForMCP is the user agent for MCP token
|
||||
const TokenUserAgentForMCP = ApplicationName + " MCP"
|
||||
|
||||
// TokenType represents token type
|
||||
type TokenType byte
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ const (
|
||||
NormalSubcategoryLargeLanguageModel = 15
|
||||
NormalSubcategoryUserExternalAuth = 16
|
||||
NormalSubcategoryOAuth2 = 17
|
||||
NormalSubcategoryInsightsExplorer = 18
|
||||
NormalSubcategoryTagGroup = 19
|
||||
)
|
||||
|
||||
// Error represents the specific error returned to user
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package errs
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Error codes related to insights explorers
|
||||
var (
|
||||
ErrInsightsExplorerIdInvalid = NewNormalError(NormalSubcategoryInsightsExplorer, 0, http.StatusBadRequest, "explorer id is invalid")
|
||||
ErrInsightsExplorerNotFound = NewNormalError(NormalSubcategoryInsightsExplorer, 1, http.StatusBadRequest, "explorer not found")
|
||||
ErrInsightsExplorerDataInvalid = NewNormalError(NormalSubcategoryInsightsExplorer, 2, http.StatusBadRequest, "explorer data is invalid")
|
||||
)
|
||||
@@ -94,3 +94,8 @@ func GetParameterInvalidHexRGBColorMessage(field string) string {
|
||||
func GetParameterInvalidAmountFilterMessage(field string) string {
|
||||
return fmt.Sprintf("parameter \"%s\" is invalid amount filter", field)
|
||||
}
|
||||
|
||||
// GetParameterInvalidTagFilterMessage returns specific error message for invalid tag filter parameter error
|
||||
func GetParameterInvalidTagFilterMessage(field string) string {
|
||||
return fmt.Sprintf("parameter \"%s\" is invalid tag filter", field)
|
||||
}
|
||||
|
||||
@@ -17,4 +17,9 @@ var (
|
||||
ErrInvalidOAuth2Token = NewNormalError(NormalSubcategoryOAuth2, 8, http.StatusBadRequest, "invalid oauth2 token")
|
||||
ErrCannotRetrieveUserInfo = NewNormalError(NormalSubcategoryOAuth2, 9, http.StatusBadRequest, "cannot retrieve user info from oauth2 provider")
|
||||
ErrOAuth2UserAlreadyBoundToAnotherUser = NewNormalError(NormalSubcategoryOAuth2, 10, http.StatusBadRequest, "oauth2 user already bound to another user")
|
||||
ErrOAuth2UserNameAndEmailEmpty = NewNormalError(NormalSubcategoryOAuth2, 11, http.StatusBadRequest, "user name and email from oauth2 provider are both empty")
|
||||
ErrOAuth2UserNameEmpty = NewNormalError(NormalSubcategoryOAuth2, 12, http.StatusBadRequest, "user name from oauth2 provider is empty")
|
||||
ErrOAuth2EmailEmpty = NewNormalError(NormalSubcategoryOAuth2, 13, http.StatusBadRequest, "email from oauth2 provider is empty")
|
||||
ErrOAuth2UserNameEmptyCannotRegister = NewNormalError(NormalSubcategoryOAuth2, 14, http.StatusBadRequest, "user name from oauth2 provider is empty, cannot register new user")
|
||||
ErrOAuth2EmailEmptyCannotRegister = NewNormalError(NormalSubcategoryOAuth2, 15, http.StatusBadRequest, "email from oauth2 provider is empty, cannot register new user")
|
||||
)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package errs
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Error codes related to transaction tag groups
|
||||
var (
|
||||
ErrTransactionTagGroupIdInvalid = NewNormalError(NormalSubcategoryTagGroup, 0, http.StatusBadRequest, "transaction tag group id is invalid")
|
||||
ErrTransactionTagGroupNotFound = NewNormalError(NormalSubcategoryTagGroup, 1, http.StatusBadRequest, "transaction tag group not found")
|
||||
ErrTransactionTagGroupInUseCannotBeDeleted = NewNormalError(NormalSubcategoryTagGroup, 2, http.StatusBadRequest, "transaction tag group is in use and cannot be deleted")
|
||||
)
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// HttpExchangeRatesDataSource defines the structure of http exchange rates data source
|
||||
@@ -41,6 +41,10 @@ func (e *CommonHttpExchangeRatesDataProvider) GetLatestExchangeRates(c core.Cont
|
||||
|
||||
for i := 0; i < len(requests); i++ {
|
||||
req := requests[i]
|
||||
req = req.WithContext(httpclient.CustomHttpResponseLog(c, func(data []byte) {
|
||||
log.Debugf(c, "[common_http_exchange_rates_data_provider.GetLatestExchangeRates] response#%d is %s", i, data)
|
||||
}))
|
||||
|
||||
resp, err := e.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
@@ -51,8 +55,6 @@ func (e *CommonHttpExchangeRatesDataProvider) GetLatestExchangeRates(c core.Cont
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
log.Debugf(c, "[common_http_exchange_rates_data_provider.GetLatestExchangeRates] response#%d is %s", i, body)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Errorf(c, "[common_http_exchange_rates_data_provider.GetLatestExchangeRates] failed to get latest exchange rate data response for user \"uid:%d\", because response code is %d", uid, resp.StatusCode)
|
||||
return nil, errs.ErrFailedToRequestRemoteApi
|
||||
@@ -106,6 +108,6 @@ func (e *CommonHttpExchangeRatesDataProvider) GetLatestExchangeRates(c core.Cont
|
||||
func newCommonHttpExchangeRatesDataProvider(config *settings.Config, dataSource HttpExchangeRatesDataSource) *CommonHttpExchangeRatesDataProvider {
|
||||
return &CommonHttpExchangeRatesDataProvider{
|
||||
dataSource: dataSource,
|
||||
httpClient: utils.NewHttpClient(config.ExchangeRatesRequestTimeout, config.ExchangeRatesProxy, config.ExchangeRatesSkipTLSVerify, settings.GetUserAgent()),
|
||||
httpClient: httpclient.NewHttpClient(config.ExchangeRatesRequestTimeout, config.ExchangeRatesProxy, config.ExchangeRatesSkipTLSVerify, core.GetOutgoingUserAgent(), config.EnableDebugLog),
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user