mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-23 11:14:26 +08:00
Compare commits
430 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9f60ab9d4 | |||
| d1525ad017 | |||
| 22e7ea0438 | |||
| 39afd0a638 | |||
| 1f7411a002 | |||
| a739498ef7 | |||
| e6bb128cda | |||
| b9855faf38 | |||
| ea9fce9bae | |||
| 7557c1014d | |||
| aee8757ad5 | |||
| d0f76fea22 | |||
| e6c6d02112 | |||
| 563bef69cf | |||
| 11f2c9fff7 | |||
| 9e0275a11a | |||
| 231d3210cb | |||
| 75d801f775 | |||
| de132dd7fd | |||
| 39ee47e05a | |||
| 0038321781 | |||
| 7c67d30222 | |||
| da2f1ef410 | |||
| 416e7cece1 | |||
| 1d5102a015 | |||
| 1e38d1b18b | |||
| bab7a0041b | |||
| e38ba2ea0a | |||
| 9d25914411 | |||
| bb2068f4db | |||
| e4e74304b6 | |||
| de885c963d | |||
| 1428ce921c | |||
| 60477f7f27 | |||
| 9dcbf1aa7e | |||
| 1d5a6562f3 | |||
| 0c427e9857 | |||
| 0b984b3c20 | |||
| b87a39464e | |||
| 0d2b3196e6 | |||
| e172e040f9 | |||
| 629dbeeaa4 | |||
| 53e515fb92 | |||
| 9c87436a36 | |||
| f56b5c471d | |||
| cd8f746745 | |||
| d39494a78e | |||
| e69df56874 | |||
| 808cb98002 | |||
| 892404924c | |||
| 374132fe7c | |||
| 5d8e709070 | |||
| 124962a4f4 | |||
| b9b210c591 | |||
| 6a4ab4c145 | |||
| e89aa10137 | |||
| d73704af66 | |||
| bf7fe0c583 | |||
| a6e252c30d | |||
| 1e4bb73874 | |||
| 8f01469a41 | |||
| 7a821abbb6 | |||
| 02d8b132f5 | |||
| c64c60c6a0 | |||
| 50472d437a | |||
| 53702e68d8 | |||
| 36d82254d6 | |||
| 36529abf08 | |||
| c0641b1db5 | |||
| c2d7bcc5f1 | |||
| 4af0797051 | |||
| 63ec0e4424 | |||
| c828db4988 | |||
| d7151bc7ab | |||
| 0222f61da6 | |||
| ff1158be00 | |||
| f214b7db88 | |||
| d605a8f4ec | |||
| 5d333a4e74 | |||
| fb35756601 | |||
| 721384b9cc | |||
| f2b633cc7b | |||
| 448fc760c0 | |||
| a604737c7c | |||
| d44798bf0f | |||
| fcedb3147d | |||
| cd59c4e6a5 | |||
| fe0187ac2c | |||
| b4c31fc9d0 | |||
| ae7ee274d5 | |||
| ee45d89730 | |||
| 5359e3c1fb | |||
| 97fb73ad43 | |||
| ce0c9ec65e | |||
| ed084e1ce0 | |||
| ec84065f73 | |||
| 2e8aedcfa6 | |||
| 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 |
@@ -10,11 +10,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
|
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
|
||||||
@@ -24,10 +24,10 @@ jobs:
|
|||||||
type=raw,value=latest
|
type=raw,value=latest
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Set up the environment
|
- name: Set up the environment
|
||||||
id: setup
|
id: setup
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
|
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
|
||||||
@@ -24,10 +24,10 @@ jobs:
|
|||||||
type=sha,format=short,prefix=SNAPSHOT-
|
type=sha,format=short,prefix=SNAPSHOT-
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Set up the environment
|
- name: Set up the environment
|
||||||
id: setup
|
id: setup
|
||||||
|
|||||||
@@ -3,81 +3,78 @@ name: Build docker image and package for linux
|
|||||||
inputs:
|
inputs:
|
||||||
release-build:
|
release-build:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "Whether to build the linux package in release mode. If set to '1', the package will be built in release mode. Otherwise, it will be built in development mode."
|
||||||
build-unix-time:
|
build-unix-time:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "The unix time to use for building the linux package. The value should be a string representing the unix time in seconds."
|
||||||
build-date:
|
build-date:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "The date to use for building the linux package. The value should be a string representing the date in the format of 'YYYYMMDD'."
|
||||||
check-3rd-api:
|
check-3rd-api:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "Whether to run integration tests that call third party APIs. If set to '1', the tests will be run. Otherwise, the tests will be skipped."
|
||||||
skip-tests:
|
skip-tests:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "Whether to skip tests when building the linux package. If set to '1', the tests will be skipped. Otherwise, the tests will be run."
|
||||||
platform:
|
platform:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The platform to build the linux package for. The value should be in the format of 'os/arch[/variant]'. For example, 'linux/amd64', 'linux/arm64/v8', 'linux/arm/v7', or 'linux/arm/v6'."
|
||||||
platform-name:
|
platform-name:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The name of the platform to build the linux package for. The value should be a string that can be used in file names. For example, 'linux-amd64', 'linux-arm64', 'linux-armv7', or 'linux-armv6'."
|
||||||
docker-push:
|
docker-push:
|
||||||
required: true
|
required: true
|
||||||
type: boolean
|
description: "Whether to push the built docker image to the registry. If set to 'true', the image will be pushed. Otherwise, it will not be pushed."
|
||||||
docker-image-name:
|
docker-image-name:
|
||||||
required: true
|
required: false
|
||||||
type: string
|
description: "The repository name of the docker image to build. This is required if 'docker-push' is set to 'true'."
|
||||||
docker-username:
|
docker-username:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "Username for logging in to the docker registry. This is required if 'docker-push' is set to 'true'."
|
||||||
docker-password:
|
docker-password:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "Password for logging in to the docker registry. This is required if 'docker-push' is set to 'true'."
|
||||||
docker-bake-meta-file-path:
|
docker-bake-meta-file-path:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The file path to the docker bake meta file."
|
||||||
docker-bake-meta-artifact-name:
|
docker-bake-meta-artifact-name:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The name of the artifact that contains the docker bake meta file."
|
||||||
docker-bake-digests-file-path:
|
docker-bake-digests-file-path:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The file path to save the docker bake digests file. The file will be created with the name of the digest under this path."
|
||||||
docker-bake-digests-artifact-name-prefix:
|
docker-bake-digests-artifact-name-prefix:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The prefix for the docker bake digests artifact name."
|
||||||
package-file-name-prefix:
|
package-file-name-prefix:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The prefix for the linux package file name."
|
||||||
package-artifact-name-prefix:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Download docker bake meta artifact
|
- name: Download docker bake meta artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.docker-bake-meta-artifact-name }}
|
name: ${{ inputs.docker-bake-meta-artifact-name }}
|
||||||
path: ${{ runner.temp }}
|
path: ${{ runner.temp }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
if: ${{ inputs.docker-username != '' && inputs.docker-password != '' }}
|
if: ${{ inputs.docker-username != '' && inputs.docker-password != '' }}
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ inputs.docker-username }}
|
username: ${{ inputs.docker-username }}
|
||||||
password: ${{ inputs.docker-password }}
|
password: ${{ inputs.docker-password }}
|
||||||
|
|
||||||
- name: Build docker for ${{ inputs.platform-name }}
|
- name: Build docker for ${{ inputs.platform-name }}
|
||||||
id: bake
|
id: bake
|
||||||
uses: docker/bake-action@v6
|
uses: docker/bake-action@v7
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./docker-bake.hcl
|
./docker-bake.hcl
|
||||||
@@ -110,15 +107,15 @@ runs:
|
|||||||
tar -czf ${{ runner.temp }}/${{ inputs.package-file-name-prefix }}-${{ inputs.platform-name }}.tar.gz *
|
tar -czf ${{ runner.temp }}/${{ inputs.package-file-name-prefix }}-${{ inputs.platform-name }}.tar.gz *
|
||||||
|
|
||||||
- name: Upload ${{ inputs.platform-name }} digests artifact
|
- name: Upload ${{ inputs.platform-name }} digests artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.docker-bake-digests-artifact-name-prefix }}-${{ inputs.platform-name }}
|
name: ${{ inputs.docker-bake-digests-artifact-name-prefix }}-${{ inputs.platform-name }}
|
||||||
path: ${{ inputs.docker-bake-digests-file-path }}/*
|
path: ${{ inputs.docker-bake-digests-file-path }}/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload artifact for ${{ inputs.platform-name }}
|
- name: Upload artifact for ${{ inputs.platform-name }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.package-artifact-name-prefix }}-${{ inputs.platform-name }}
|
archive: false
|
||||||
path: ${{ runner.temp }}/${{ inputs.package-file-name-prefix }}-${{ inputs.platform-name }}.tar.gz
|
path: ${{ runner.temp }}/${{ inputs.package-file-name-prefix }}-${{ inputs.platform-name }}.tar.gz
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
name: Build backend file for macOS
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
go-version:
|
||||||
|
required: false
|
||||||
|
description: "The Go version to use for building the macOS backend. The version should be in the format of 'x.y.z'."
|
||||||
|
default: "1.26.2"
|
||||||
|
release-build:
|
||||||
|
required: false
|
||||||
|
description: "Whether to build the macOS backend in release mode. If set to '1', the backend will be built in release mode. Otherwise, it will be built in development mode."
|
||||||
|
build-unix-time:
|
||||||
|
required: false
|
||||||
|
description: "The unix time to use for building the macOS backend. The value should be a string representing the unix time in seconds."
|
||||||
|
build-date:
|
||||||
|
required: false
|
||||||
|
description: "The date to use for building the macOS backend. The value should be a string representing the date in the format of 'YYYYMMDD'."
|
||||||
|
check-3rd-api:
|
||||||
|
required: false
|
||||||
|
description: "Whether to run integration tests that call third party APIs. If set to '1', the tests will be run. Otherwise, the tests will be skipped."
|
||||||
|
skip-tests:
|
||||||
|
required: false
|
||||||
|
description: "Whether to skip tests when building the macOS backend. If set to '1', the tests will be skipped. Otherwise, the tests will be run."
|
||||||
|
architecture:
|
||||||
|
required: true
|
||||||
|
description: "The name of the architecture to build the macOS package for."
|
||||||
|
backend-artifact-name-prefix:
|
||||||
|
required: true
|
||||||
|
description: "The prefix for the macOS backend artifact name."
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: ${{ inputs.go-version }}
|
||||||
|
|
||||||
|
- name: Build backend for macOS-${{ inputs.architecture }}
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
RELEASE_BUILD: "${{ inputs.release-build }}"
|
||||||
|
BUILD_PIPELINE: "1"
|
||||||
|
BUILD_UNIXTIME: "${{ inputs.build-unix-time }}"
|
||||||
|
BUILD_DATE: "${{ inputs.build-date }}"
|
||||||
|
CHECK_3RD_API: "${{ inputs.check-3rd-api }}"
|
||||||
|
SKIP_TESTS: "${{ inputs.skip-tests }}"
|
||||||
|
run: |
|
||||||
|
./build.sh backend
|
||||||
|
|
||||||
|
- name: Upload macOS-${{ inputs.architecture }} backend artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.backend-artifact-name-prefix }}-macos-${{ inputs.architecture }}
|
||||||
|
path: ezbookkeeping
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
name: Build packages for macOS
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
architecture:
|
||||||
|
required: true
|
||||||
|
description: "The name of the architecture to build the macOS package for."
|
||||||
|
package-file-name-prefix:
|
||||||
|
required: true
|
||||||
|
description: "The prefix for the macOS package file name."
|
||||||
|
backend-artifact-name-prefix:
|
||||||
|
required: true
|
||||||
|
description: "The prefix for the macOS backend artifact name."
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Download macOS-${{ inputs.architecture }} backend file
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.backend-artifact-name-prefix }}-macos-${{ inputs.architecture }}
|
||||||
|
path: ${{ runner.temp }}/backend
|
||||||
|
|
||||||
|
- name: Download linux-amd64 packaged files
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.package-file-name-prefix }}-linux-amd64.tar.gz
|
||||||
|
path: ${{ runner.temp }}/package
|
||||||
|
|
||||||
|
- name: Extract frontend files from linux-amd64 package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p package
|
||||||
|
tar -xzf ${{ runner.temp }}/package/${{ inputs.package-file-name-prefix }}-linux-amd64.tar.gz -C package
|
||||||
|
|
||||||
|
- name: Package macOS-${{ inputs.architecture }} package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p ezbookkeeping
|
||||||
|
mkdir -p ezbookkeeping/data
|
||||||
|
mkdir -p ezbookkeeping/storage
|
||||||
|
mkdir -p ezbookkeeping/log
|
||||||
|
cp ${{ runner.temp }}/backend/ezbookkeeping ezbookkeeping/
|
||||||
|
cp -R package/public ezbookkeeping/public
|
||||||
|
cp -R conf ezbookkeeping/conf
|
||||||
|
cp -R templates ezbookkeeping/templates
|
||||||
|
cp LICENSE ezbookkeeping/
|
||||||
|
cd ezbookkeeping
|
||||||
|
chmod +x ezbookkeeping
|
||||||
|
tar -czf ${{ runner.temp }}/${{ inputs.package-file-name-prefix }}-macos-${{ inputs.architecture }}.tar.gz *
|
||||||
|
cd ..
|
||||||
|
rm -rf ezbookkeeping
|
||||||
|
|
||||||
|
- name: Upload macOS-${{ inputs.architecture }} artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
archive: false
|
||||||
|
path: ${{ runner.temp }}/${{ inputs.package-file-name-prefix }}-macos-${{ inputs.architecture }}.tar.gz
|
||||||
|
if-no-files-found: error
|
||||||
@@ -3,31 +3,34 @@ name: Build backend file for windows
|
|||||||
inputs:
|
inputs:
|
||||||
go-version:
|
go-version:
|
||||||
required: false
|
required: false
|
||||||
default: "1.25.3"
|
description: "The Go version to use for building the windows backend. The version should be in the format of 'x.y.z'."
|
||||||
|
default: "1.26.2"
|
||||||
mingw-version:
|
mingw-version:
|
||||||
required: false
|
required: false
|
||||||
default: "14.2.0"
|
description: "The MinGW version to use for building the windows backend. The version should be in the format of 'x.y.z'."
|
||||||
|
default: "15.2.0"
|
||||||
mingw-revison:
|
mingw-revison:
|
||||||
required: false
|
required: false
|
||||||
default: "v12-rev2"
|
description: "The MinGW revision to use for building the windows backend. The revision should be in the format of 'vX-revY'."
|
||||||
|
default: "v13-rev1"
|
||||||
release-build:
|
release-build:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "Whether to build the windows backend in release mode. If set to '1', the backend will be built in release mode. Otherwise, it will be built in development mode."
|
||||||
build-unix-time:
|
build-unix-time:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "The unix time to use for building the windows backend. The value should be a string representing the unix time in seconds."
|
||||||
build-date:
|
build-date:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "The date to use for building the windows backend. The value should be a string representing the date in the format of 'YYYYMMDD'."
|
||||||
check-3rd-api:
|
check-3rd-api:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "Whether to run integration tests that call third party APIs. If set to '1', the tests will be run. Otherwise, the tests will be skipped."
|
||||||
skip-tests:
|
skip-tests:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
description: "Whether to skip tests when building the windows backend. If set to '1', the tests will be skipped. Otherwise, the tests will be run."
|
||||||
backend-artifact-name-prefix:
|
backend-artifact-name-prefix:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The prefix for the windows backend artifact name."
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
@@ -69,7 +72,7 @@ runs:
|
|||||||
.\build.ps1 backend
|
.\build.ps1 backend
|
||||||
|
|
||||||
- name: Upload windows backend artifact
|
- name: Upload windows backend artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.backend-artifact-name-prefix }}-windows-x64
|
name: ${{ inputs.backend-artifact-name-prefix }}-windows-x64
|
||||||
path: ezbookkeeping.exe
|
path: ezbookkeeping.exe
|
||||||
|
|||||||
@@ -3,27 +3,24 @@ name: Build packages for windows
|
|||||||
inputs:
|
inputs:
|
||||||
package-file-name-prefix:
|
package-file-name-prefix:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The prefix for the windows package file name."
|
||||||
package-artifact-name-prefix:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
backend-artifact-name-prefix:
|
backend-artifact-name-prefix:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The prefix for the windows backend artifact name."
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Download windows-x64 backend file
|
- name: Download windows-x64 backend file
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.backend-artifact-name-prefix }}-windows-x64
|
name: ${{ inputs.backend-artifact-name-prefix }}-windows-x64
|
||||||
path: ${{ runner.temp }}\backend
|
path: ${{ runner.temp }}\backend
|
||||||
|
|
||||||
- name: Download linux-amd64 packaged files
|
- name: Download linux-amd64 packaged files
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.package-artifact-name-prefix }}-linux-amd64
|
name: ${{ inputs.package-file-name-prefix }}-linux-amd64.tar.gz
|
||||||
path: ${{ runner.temp }}\package
|
path: ${{ runner.temp }}\package
|
||||||
|
|
||||||
- name: Extract frontend files from linux-amd64 package
|
- name: Extract frontend files from linux-amd64 package
|
||||||
@@ -50,8 +47,8 @@ runs:
|
|||||||
Remove-Item -Recurse -Force ezbookkeeping
|
Remove-Item -Recurse -Force ezbookkeeping
|
||||||
|
|
||||||
- name: Upload windows artifact
|
- name: Upload windows artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.package-artifact-name-prefix }}-windows-x64
|
archive: false
|
||||||
path: ${{ inputs.package-file-name-prefix }}-windows-x64.zip
|
path: ${{ inputs.package-file-name-prefix }}-windows-x64.zip
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|||||||
@@ -3,50 +3,50 @@ name: Push linux docker multi-arch image to registry
|
|||||||
inputs:
|
inputs:
|
||||||
docker-image-name:
|
docker-image-name:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The repository name of the docker image to build."
|
||||||
docker-username:
|
docker-username:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "Username for logging in to the docker registry."
|
||||||
docker-password:
|
docker-password:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "Password for logging in to the docker registry."
|
||||||
docker-bake-meta-file-path:
|
docker-bake-meta-file-path:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The file path to the docker bake meta file."
|
||||||
docker-bake-meta-artifact-name:
|
docker-bake-meta-artifact-name:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The name of the artifact that contains the docker bake meta file."
|
||||||
docker-bake-digests-file-path:
|
docker-bake-digests-file-path:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The file path to save the docker bake digests file. The file will be created with the name of the digest under this path."
|
||||||
docker-bake-digests-artifact-name-prefix:
|
docker-bake-digests-artifact-name-prefix:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The prefix for the docker bake digests artifact name."
|
||||||
docker-image-tags:
|
docker-image-tags:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
description: "The tags for the docker image to push."
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Download docker bake meta artifact
|
- name: Download docker bake meta artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.docker-bake-meta-artifact-name }}
|
name: ${{ inputs.docker-bake-meta-artifact-name }}
|
||||||
path: ${{ runner.temp }}
|
path: ${{ runner.temp }}
|
||||||
|
|
||||||
- name: Download digests artifact
|
- name: Download digests artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: ${{ inputs.docker-bake-digests-artifact-name-prefix }}-*
|
pattern: ${{ inputs.docker-bake-digests-artifact-name-prefix }}-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: ${{ inputs.docker-bake-digests-file-path }}
|
path: ${{ inputs.docker-bake-digests-file-path }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ inputs.docker-username }}
|
username: ${{ inputs.docker-username }}
|
||||||
password: ${{ inputs.docker-password }}
|
password: ${{ inputs.docker-password }}
|
||||||
|
|||||||
@@ -0,0 +1,352 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const FRONTEND_LOCALES_DIR = path.join(__dirname, '..', '..', 'src', 'locales');
|
||||||
|
const BACKEND_LOCALES_DIR = path.join(__dirname, '..', '..', 'pkg', 'locales');
|
||||||
|
const OUTPUT_DIR = process.argv[2] || path.join(__dirname, '..', '..', 'i18n-badge');
|
||||||
|
|
||||||
|
const DEFAULT_LANGUAGE_TAG = 'en';
|
||||||
|
|
||||||
|
const BACKEND_SKIP_STRUCTS = new Set([
|
||||||
|
'GlobalTextItems',
|
||||||
|
'DefaultTypes',
|
||||||
|
'DataConverterTextItems',
|
||||||
|
]);
|
||||||
|
|
||||||
|
function discoverFrontendLanguages() {
|
||||||
|
const indexPath = path.join(FRONTEND_LOCALES_DIR, 'index.ts');
|
||||||
|
const content = fs.readFileSync(indexPath, 'utf-8');
|
||||||
|
|
||||||
|
const importMap = {};
|
||||||
|
const importRegex = /import\s+(\w+)\s+from\s+['"]\.\/([\w_]+\.json)['"]/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = importRegex.exec(content)) !== null) {
|
||||||
|
importMap[match[1]] = match[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
const langRegex = /['"]([^'"]+)['"]\s*:\s*\{[^}]*content\s*:\s*(\w+)/g;
|
||||||
|
|
||||||
|
while ((match = langRegex.exec(content)) !== null) {
|
||||||
|
const tag = match[1];
|
||||||
|
const varName = match[2];
|
||||||
|
|
||||||
|
if (importMap[varName]) {
|
||||||
|
result[tag] = importMap[varName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function discoverBackendLanguages() {
|
||||||
|
const allLocalesPath = path.join(BACKEND_LOCALES_DIR, 'all_locales.go');
|
||||||
|
const content = fs.readFileSync(allLocalesPath, 'utf-8');
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
const entryRegex = /"([^"]+)"\s*:\s*\{[^}]*Content\s*:\s*(\w+)/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = entryRegex.exec(content)) !== null) {
|
||||||
|
const tag = match[1];
|
||||||
|
const fileName = tag.toLowerCase().replace(/-/g, '_') + '.go';
|
||||||
|
const filePath = path.join(BACKEND_LOCALES_DIR, fileName);
|
||||||
|
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
result[tag] = fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flattenJSON(obj, prefix) {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
const fullKey = prefix ? prefix + '.' + key : key;
|
||||||
|
|
||||||
|
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||||
|
Object.assign(result, flattenJSON(obj[key], fullKey));
|
||||||
|
} else {
|
||||||
|
result[fullKey] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSkipFrontendKey(key) {
|
||||||
|
if (key.startsWith('global.')) {
|
||||||
|
return true;
|
||||||
|
} else if (key.startsWith('default.')) {
|
||||||
|
return true;
|
||||||
|
} else if (key.startsWith('currency.')) {
|
||||||
|
if (key.startsWith('currency.unit.')) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (key.startsWith('mapprovider.')) {
|
||||||
|
return true;
|
||||||
|
} else if (key.startsWith('encoding.')) {
|
||||||
|
return true;
|
||||||
|
} else if (key.startsWith('document.')) {
|
||||||
|
if (key.startsWith('document.anchor.')) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFrontendAlwaysTranslatedKey(key) {
|
||||||
|
if (key.startsWith('language.')) {
|
||||||
|
return true;
|
||||||
|
} else if (key.startsWith('format.')) {
|
||||||
|
if (key.startsWith('format.misc.')) {
|
||||||
|
if (key === 'format.misc.multiTextJoinSeparator') {
|
||||||
|
return true;
|
||||||
|
} else if (key === 'format.misc.eachMonthDayInMonthDays') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (key.startsWith('datetime.')) {
|
||||||
|
return true;
|
||||||
|
} else if (key.startsWith('timezone.')) {
|
||||||
|
return true;
|
||||||
|
} else if (key.startsWith('currency.')) {
|
||||||
|
if (key === 'currency.name.EUR') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (key.startsWith('parameter.')) {
|
||||||
|
if (key === 'parameter.id') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (key === 'OK') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractGoStringFields(content) {
|
||||||
|
const fields = [];
|
||||||
|
const structBlockRegex = /(\w+):\s*&\w+\{([^}]*)\}/gs;
|
||||||
|
let blockMatch;
|
||||||
|
|
||||||
|
while ((blockMatch = structBlockRegex.exec(content)) !== null) {
|
||||||
|
const structName = blockMatch[1];
|
||||||
|
const blockBody = blockMatch[2];
|
||||||
|
const fieldRegex = /(\w+):\s+"((?:[^"\\]|\\.)*)"/g;
|
||||||
|
let fieldMatch;
|
||||||
|
|
||||||
|
while ((fieldMatch = fieldRegex.exec(blockBody)) !== null) {
|
||||||
|
fields.push({
|
||||||
|
struct: structName,
|
||||||
|
name: fieldMatch[1],
|
||||||
|
value: fieldMatch[2],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProgressColor(progress) {
|
||||||
|
if (progress >= 95) {
|
||||||
|
return 'brightgreen';
|
||||||
|
} else if (progress >= 90) {
|
||||||
|
return 'green';
|
||||||
|
} else if (progress >= 70) {
|
||||||
|
return 'yellowgreen';
|
||||||
|
} else if (progress >= 50) {
|
||||||
|
return 'yellow';
|
||||||
|
} else if (progress >= 20) {
|
||||||
|
return 'orange';
|
||||||
|
} else {
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const frontendLangs = discoverFrontendLanguages();
|
||||||
|
const backendLangs = discoverBackendLanguages();
|
||||||
|
const allTags = new Set([...Object.keys(frontendLangs), ...Object.keys(backendLangs)]);
|
||||||
|
|
||||||
|
console.log('Discovered ' + allTags.size + ' languages: ' + [...allTags].sort().join(', '));
|
||||||
|
|
||||||
|
const defaultFrontendJSON = JSON.parse(fs.readFileSync(path.join(FRONTEND_LOCALES_DIR, `${DEFAULT_LANGUAGE_TAG}.json`), 'utf-8'));
|
||||||
|
const defaultFrontendItemsMap = flattenJSON(defaultFrontendJSON, '');
|
||||||
|
const defaultFrontendKeys = Object.keys(defaultFrontendItemsMap);
|
||||||
|
const frontendTranslatableKeys = defaultFrontendKeys.filter(function (k) {
|
||||||
|
return !shouldSkipFrontendKey(k);
|
||||||
|
});
|
||||||
|
const frontendSkippedCount = defaultFrontendKeys.length - frontendTranslatableKeys.length;
|
||||||
|
const frontendTotal = frontendTranslatableKeys.length;
|
||||||
|
|
||||||
|
const defaultBackendContent = fs.readFileSync(path.join(BACKEND_LOCALES_DIR, `${DEFAULT_LANGUAGE_TAG}.go`), 'utf-8');
|
||||||
|
const defaultBackendItems = extractGoStringFields(defaultBackendContent);
|
||||||
|
const defaultBackendTranslatableItems = defaultBackendItems.filter(function (f) {
|
||||||
|
return !BACKEND_SKIP_STRUCTS.has(f.struct);
|
||||||
|
});
|
||||||
|
const backendSkippedCount = defaultBackendItems.length - defaultBackendTranslatableItems.length;
|
||||||
|
const backendTotal = defaultBackendTranslatableItems.length;
|
||||||
|
|
||||||
|
console.log('Frontend: ' + frontendTotal + ' translatable keys (' + frontendSkippedCount + ' excluded)');
|
||||||
|
console.log('Backend: ' + backendTotal + ' translatable fields (' + backendSkippedCount + ' excluded)');
|
||||||
|
|
||||||
|
const results = {};
|
||||||
|
const untranslatedKeys = {};
|
||||||
|
|
||||||
|
for (const tag of allTags) {
|
||||||
|
results[tag] = {
|
||||||
|
languageTag: tag,
|
||||||
|
frontendTranslated: 0,
|
||||||
|
frontendTotal: frontendTotal,
|
||||||
|
backendTranslated: 0,
|
||||||
|
backendTotal: backendTotal
|
||||||
|
};
|
||||||
|
untranslatedKeys[tag] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of Object.keys(frontendLangs)) {
|
||||||
|
if (tag === DEFAULT_LANGUAGE_TAG) {
|
||||||
|
results[tag].frontendTranslated = frontendTotal;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = frontendLangs[tag];
|
||||||
|
const filePath = path.join(FRONTEND_LOCALES_DIR, file);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
||||||
|
const kv = flattenJSON(json, '');
|
||||||
|
let translated = 0;
|
||||||
|
|
||||||
|
for (const key of frontendTranslatableKeys) {
|
||||||
|
if (kv[key] !== undefined && kv[key] !== '' && (kv[key] !== defaultFrontendItemsMap[key] || isFrontendAlwaysTranslatedKey(key))) {
|
||||||
|
translated++;
|
||||||
|
} else {
|
||||||
|
untranslatedKeys[tag].push({ source: path.join('src', 'locales', file), key: key, defaultValue: defaultFrontendItemsMap[key], value: kv[key] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results[tag].frontendTranslated = translated;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of Object.keys(backendLangs)) {
|
||||||
|
if (tag === DEFAULT_LANGUAGE_TAG) {
|
||||||
|
results[tag].backendTranslated = backendTotal;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = backendLangs[tag];
|
||||||
|
const filePath = path.join(BACKEND_LOCALES_DIR, file);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
const fields = extractGoStringFields(content).filter(function (f) {
|
||||||
|
return !BACKEND_SKIP_STRUCTS.has(f.struct);
|
||||||
|
});
|
||||||
|
let translated = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < defaultBackendTranslatableItems.length; i++) {
|
||||||
|
if (i < fields.length && fields[i].value !== defaultBackendTranslatableItems[i].value) {
|
||||||
|
translated++;
|
||||||
|
} else {
|
||||||
|
untranslatedKeys[tag].push({ source: path.join('pkg', 'locales', file), key: defaultBackendTranslatableItems[i].struct + '.' + defaultBackendTranslatableItems[i].name, defaultValue: defaultBackendTranslatableItems[i].value, value: (i < fields.length) ? fields[i].value : null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results[tag].backendTranslated = translated;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of Object.keys(results)) {
|
||||||
|
const r = results[tag];
|
||||||
|
const totalTranslated = r.frontendTranslated + r.backendTranslated;
|
||||||
|
const totalItems = r.frontendTotal + r.backendTotal;
|
||||||
|
r.totalProgress = Math.round((totalTranslated / totalItems) * 10000) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedResults = {};
|
||||||
|
var sortedTags = Object.keys(results).sort();
|
||||||
|
|
||||||
|
for (const tag of sortedTags) {
|
||||||
|
sortedResults[tag] = results[tag];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||||
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
var badgesDir = path.join(OUTPUT_DIR, 'badges');
|
||||||
|
|
||||||
|
if (!fs.existsSync(badgesDir)) {
|
||||||
|
fs.mkdirSync(badgesDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(OUTPUT_DIR, 'i18n-progress.json'),
|
||||||
|
JSON.stringify(sortedResults, null, 4) + '\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const tag of sortedTags) {
|
||||||
|
const data = sortedResults[tag];
|
||||||
|
const badge = {
|
||||||
|
schemaVersion: 1,
|
||||||
|
label: 'translation',
|
||||||
|
message: data.totalProgress + '%',
|
||||||
|
color: getProgressColor(data.totalProgress)
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(badgesDir, tag + '.json'),
|
||||||
|
JSON.stringify(badge, null, 4) + '\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var untranslatedDir = path.join(OUTPUT_DIR, 'untranslated');
|
||||||
|
|
||||||
|
if (!fs.existsSync(untranslatedDir)) {
|
||||||
|
fs.mkdirSync(untranslatedDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of sortedTags) {
|
||||||
|
const items = untranslatedKeys[tag] || [];
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(untranslatedDir, tag + '.json'),
|
||||||
|
JSON.stringify(items, null, 4) + '\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of sortedTags) {
|
||||||
|
const data = sortedResults[tag];
|
||||||
|
const missingCount = (untranslatedKeys[tag] || []).length;
|
||||||
|
console.log(tag + ': ' + data.totalProgress + '% (frontend: ' + data.frontendTranslated + '/' + data.frontendTotal + ', backend: ' + data.backendTranslated + '/' + data.backendTotal + ', untranslated: ' + missingCount + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\nResults written to ' + OUTPUT_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -18,15 +18,14 @@ jobs:
|
|||||||
ezbookkeeping-docker-digests-file-path: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_file_path }}
|
ezbookkeeping-docker-digests-file-path: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_file_path }}
|
||||||
ezbookkeeping-docker-digests-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_artifact_name_prefix }}
|
ezbookkeeping-docker-digests-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_artifact_name_prefix }}
|
||||||
ezbookkeeping-package-file-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_file_name_prefix }}
|
ezbookkeeping-package-file-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_file_name_prefix }}
|
||||||
ezbookkeeping-package-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_artifact_name_prefix }}
|
|
||||||
ezbookkeeping-backend-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_backend_artifact_name_prefix }}
|
ezbookkeeping-backend-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_backend_artifact_name_prefix }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ vars.DOCKER_IMAGE_NAME }}
|
${{ vars.DOCKER_IMAGE_NAME }}
|
||||||
@@ -43,7 +42,6 @@ jobs:
|
|||||||
echo "ezbookkeeping_docker_digests_file_path=${{ runner.temp }}/digests" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_docker_digests_file_path=${{ runner.temp }}/digests" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_docker_digests_artifact_name_prefix=ezbookkeeping-build-dev-digests-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_docker_digests_artifact_name_prefix=ezbookkeeping-build-dev-digests-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_package_file_name_prefix=ezbookkeeping-dev-package-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_package_file_name_prefix=ezbookkeeping-dev-package-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_package_artifact_name_prefix=ezbookkeeping-build-dev-package-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ezbookkeeping_backend_artifact_name_prefix=ezbookkeeping-build-dev-backend-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_backend_artifact_name_prefix=ezbookkeeping-build-dev-backend-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Rename docker bake meta file
|
- name: Rename docker bake meta file
|
||||||
@@ -51,7 +49,7 @@ jobs:
|
|||||||
mv "${{ steps.meta.outputs.bake-file }}" "${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}"
|
mv "${{ steps.meta.outputs.bake-file }}" "${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}"
|
||||||
|
|
||||||
- name: Upload docker bake meta artifact
|
- name: Upload docker bake meta artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_artifact_name }}
|
name: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_artifact_name }}
|
||||||
path: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}
|
path: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}
|
||||||
@@ -63,7 +61,7 @@ jobs:
|
|||||||
- setup
|
- setup
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-linux-docker-and-package
|
- uses: ./.github/actions/build-linux-docker-and-package
|
||||||
with:
|
with:
|
||||||
@@ -80,7 +78,6 @@ jobs:
|
|||||||
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
||||||
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
|
||||||
|
|
||||||
build-linux-docker-and-package-arm:
|
build-linux-docker-and-package-arm:
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
@@ -97,7 +94,7 @@ jobs:
|
|||||||
platform-name: linux-armv6
|
platform-name: linux-armv6
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-linux-docker-and-package
|
- uses: ./.github/actions/build-linux-docker-and-package
|
||||||
with:
|
with:
|
||||||
@@ -114,7 +111,6 @@ jobs:
|
|||||||
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
||||||
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
|
||||||
|
|
||||||
build-windows-backend:
|
build-windows-backend:
|
||||||
needs:
|
needs:
|
||||||
@@ -122,7 +118,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-windows-backend
|
- uses: ./.github/actions/build-windows-backend
|
||||||
with:
|
with:
|
||||||
@@ -132,6 +128,23 @@ jobs:
|
|||||||
skip-tests: ${{ vars.SKIP_TESTS }}
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
|
build-macos-backend:
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: ./.github/actions/build-macos-backend
|
||||||
|
with:
|
||||||
|
architecture: arm64
|
||||||
|
build-unix-time: ${{ needs.setup.outputs.build-unix-time }}
|
||||||
|
build-date: ${{ needs.setup.outputs.build-date }}
|
||||||
|
check-3rd-api: ${{ vars.CHECK_3RD_API }}
|
||||||
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
build-windows-package:
|
build-windows-package:
|
||||||
needs:
|
needs:
|
||||||
- setup
|
- setup
|
||||||
@@ -140,10 +153,25 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-windows-package
|
- uses: ./.github/actions/build-windows-package
|
||||||
with:
|
with:
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
|
build-macos-package:
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
- build-macos-backend
|
||||||
|
- build-linux-docker-and-package-x86
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: ./.github/actions/build-macos-package
|
||||||
|
with:
|
||||||
|
architecture: arm64
|
||||||
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ jobs:
|
|||||||
setup:
|
setup:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
|
docker-push: ${{ steps.variable.outputs.docker_push }}
|
||||||
build-unix-time: ${{ steps.variable.outputs.build_unix_time }}
|
build-unix-time: ${{ steps.variable.outputs.build_unix_time }}
|
||||||
build-date: ${{ steps.variable.outputs.build_date }}
|
build-date: ${{ steps.variable.outputs.build_date }}
|
||||||
docker-version: ${{ steps.meta.outputs.version }}
|
docker-version: ${{ steps.meta.outputs.version }}
|
||||||
@@ -19,15 +20,14 @@ jobs:
|
|||||||
ezbookkeeping-docker-digests-file-path: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_file_path }}
|
ezbookkeeping-docker-digests-file-path: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_file_path }}
|
||||||
ezbookkeeping-docker-digests-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_artifact_name_prefix }}
|
ezbookkeeping-docker-digests-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_artifact_name_prefix }}
|
||||||
ezbookkeeping-package-file-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_file_name_prefix }}
|
ezbookkeeping-package-file-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_file_name_prefix }}
|
||||||
ezbookkeeping-package-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_artifact_name_prefix }}
|
|
||||||
ezbookkeeping-backend-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_backend_artifact_name_prefix }}
|
ezbookkeeping-backend-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_backend_artifact_name_prefix }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ vars.DOCKER_IMAGE_NAME }}
|
${{ vars.DOCKER_IMAGE_NAME }}
|
||||||
@@ -39,6 +39,7 @@ jobs:
|
|||||||
- name: Set up variables
|
- name: Set up variables
|
||||||
id: variable
|
id: variable
|
||||||
run: |
|
run: |
|
||||||
|
echo "docker_push=${{ vars.DOCKER_IMAGE_NAME != '' && vars.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "build_unix_time=$(date '+%s')" >> "$GITHUB_OUTPUT"
|
echo "build_unix_time=$(date '+%s')" >> "$GITHUB_OUTPUT"
|
||||||
echo "build_date=$(date '+%Y%m%d')" >> "$GITHUB_OUTPUT"
|
echo "build_date=$(date '+%Y%m%d')" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_docker_bake_meta_file_path=${{ runner.temp }}/bake-meta.json" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_docker_bake_meta_file_path=${{ runner.temp }}/bake-meta.json" >> "$GITHUB_OUTPUT"
|
||||||
@@ -46,7 +47,6 @@ jobs:
|
|||||||
echo "ezbookkeeping_docker_digests_file_path=${{ runner.temp }}/digests" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_docker_digests_file_path=${{ runner.temp }}/digests" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_docker_digests_artifact_name_prefix=ezbookkeeping-build-release-digests-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_docker_digests_artifact_name_prefix=ezbookkeeping-build-release-digests-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_package_file_name_prefix=ezbookkeeping-${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_package_file_name_prefix=ezbookkeeping-${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_package_artifact_name_prefix=ezbookkeeping-build-release-package-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ezbookkeeping_backend_artifact_name_prefix=ezbookkeeping-build-release-backend-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_backend_artifact_name_prefix=ezbookkeeping-build-release-backend-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Rename docker bake meta file
|
- name: Rename docker bake meta file
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
mv "${{ steps.meta.outputs.bake-file }}" "${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}"
|
mv "${{ steps.meta.outputs.bake-file }}" "${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}"
|
||||||
|
|
||||||
- name: Upload docker bake meta artifact
|
- name: Upload docker bake meta artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_artifact_name }}
|
name: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_artifact_name }}
|
||||||
path: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}
|
path: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
- setup
|
- setup
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-linux-docker-and-package
|
- uses: ./.github/actions/build-linux-docker-and-package
|
||||||
with:
|
with:
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
skip-tests: ${{ vars.SKIP_TESTS }}
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
platform-name: linux-amd64
|
platform-name: linux-amd64
|
||||||
docker-push: true
|
docker-push: ${{ needs.setup.outputs.docker-push }}
|
||||||
docker-image-name: ${{ vars.DOCKER_IMAGE_NAME }}
|
docker-image-name: ${{ vars.DOCKER_IMAGE_NAME }}
|
||||||
docker-username: ${{ vars.DOCKER_USERNAME }}
|
docker-username: ${{ vars.DOCKER_USERNAME }}
|
||||||
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
@@ -86,7 +86,6 @@ jobs:
|
|||||||
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
||||||
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
|
||||||
|
|
||||||
build-linux-docker-and-package-arm:
|
build-linux-docker-and-package-arm:
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
@@ -103,7 +102,7 @@ jobs:
|
|||||||
platform-name: linux-armv6
|
platform-name: linux-armv6
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-linux-docker-and-package
|
- uses: ./.github/actions/build-linux-docker-and-package
|
||||||
with:
|
with:
|
||||||
@@ -114,7 +113,7 @@ jobs:
|
|||||||
skip-tests: ${{ vars.SKIP_TESTS }}
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
platform: ${{ matrix.platform }}
|
platform: ${{ matrix.platform }}
|
||||||
platform-name: ${{ matrix.platform-name }}
|
platform-name: ${{ matrix.platform-name }}
|
||||||
docker-push: true
|
docker-push: ${{ needs.setup.outputs.docker-push }}
|
||||||
docker-image-name: ${{ vars.DOCKER_IMAGE_NAME }}
|
docker-image-name: ${{ vars.DOCKER_IMAGE_NAME }}
|
||||||
docker-username: ${{ vars.DOCKER_USERNAME }}
|
docker-username: ${{ vars.DOCKER_USERNAME }}
|
||||||
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
@@ -123,9 +122,9 @@ jobs:
|
|||||||
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
||||||
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
|
||||||
|
|
||||||
push-linux-docker:
|
push-linux-docker:
|
||||||
|
if: ${{ needs.setup.outputs.docker-push == 'true' }}
|
||||||
needs:
|
needs:
|
||||||
- setup
|
- setup
|
||||||
- build-linux-docker-and-package-x86
|
- build-linux-docker-and-package-x86
|
||||||
@@ -133,7 +132,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/push-linux-docker
|
- uses: ./.github/actions/push-linux-docker
|
||||||
with:
|
with:
|
||||||
@@ -152,7 +151,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-windows-backend
|
- uses: ./.github/actions/build-windows-backend
|
||||||
with:
|
with:
|
||||||
@@ -163,6 +162,24 @@ jobs:
|
|||||||
skip-tests: ${{ vars.SKIP_TESTS }}
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
|
build-macos-backend:
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: ./.github/actions/build-macos-backend
|
||||||
|
with:
|
||||||
|
architecture: arm64
|
||||||
|
release-build: 1
|
||||||
|
build-unix-time: ${{ needs.setup.outputs.build-unix-time }}
|
||||||
|
build-date: ${{ needs.setup.outputs.build-date }}
|
||||||
|
check-3rd-api: ${{ vars.CHECK_3RD_API }}
|
||||||
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
build-windows-package:
|
build-windows-package:
|
||||||
needs:
|
needs:
|
||||||
- setup
|
- setup
|
||||||
@@ -171,12 +188,27 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-windows-package
|
- uses: ./.github/actions/build-windows-package
|
||||||
with:
|
with:
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
|
build-macos-package:
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
- build-macos-backend
|
||||||
|
- build-linux-docker-and-package-x86
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: ./.github/actions/build-macos-package
|
||||||
|
with:
|
||||||
|
architecture: arm64
|
||||||
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
publish-release:
|
publish-release:
|
||||||
@@ -186,17 +218,19 @@ jobs:
|
|||||||
- build-linux-docker-and-package-x86
|
- build-linux-docker-and-package-x86
|
||||||
- build-linux-docker-and-package-arm
|
- build-linux-docker-and-package-arm
|
||||||
- build-windows-package
|
- build-windows-package
|
||||||
|
- build-macos-package
|
||||||
- push-linux-docker
|
- push-linux-docker
|
||||||
steps:
|
steps:
|
||||||
- name: Download all packaged files
|
- name: Download all packaged files
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}-*
|
pattern: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
skip-decompress: true
|
||||||
path: release-files
|
path: release-files
|
||||||
|
|
||||||
- name: Publish Release ${{ github.ref_name }}
|
- name: Publish Release ${{ github.ref_name }}
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ github.ref_name }}
|
name: ${{ github.ref_name }}
|
||||||
tag_name: ${{ github.ref_name }}
|
tag_name: ${{ github.ref_name }}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ jobs:
|
|||||||
setup:
|
setup:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
|
docker-push: ${{ steps.variable.outputs.docker_push }}
|
||||||
build-unix-time: ${{ steps.variable.outputs.build_unix_time }}
|
build-unix-time: ${{ steps.variable.outputs.build_unix_time }}
|
||||||
build-date: ${{ steps.variable.outputs.build_date }}
|
build-date: ${{ steps.variable.outputs.build_date }}
|
||||||
docker-version: ${{ steps.meta.outputs.version }}
|
docker-version: ${{ steps.meta.outputs.version }}
|
||||||
@@ -19,15 +20,14 @@ jobs:
|
|||||||
ezbookkeeping-docker-digests-file-path: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_file_path }}
|
ezbookkeeping-docker-digests-file-path: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_file_path }}
|
||||||
ezbookkeeping-docker-digests-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_artifact_name_prefix }}
|
ezbookkeeping-docker-digests-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_docker_digests_artifact_name_prefix }}
|
||||||
ezbookkeeping-package-file-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_file_name_prefix }}
|
ezbookkeeping-package-file-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_file_name_prefix }}
|
||||||
ezbookkeeping-package-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_package_artifact_name_prefix }}
|
|
||||||
ezbookkeeping-backend-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_backend_artifact_name_prefix }}
|
ezbookkeeping-backend-artifact-name-prefix: ${{ steps.variable.outputs.ezbookkeeping_backend_artifact_name_prefix }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ vars.DOCKER_IMAGE_NAME }}
|
${{ vars.DOCKER_IMAGE_NAME }}
|
||||||
@@ -39,6 +39,7 @@ jobs:
|
|||||||
- name: Set up variables
|
- name: Set up variables
|
||||||
id: variable
|
id: variable
|
||||||
run: |
|
run: |
|
||||||
|
echo "docker_push=${{ vars.DOCKER_IMAGE_NAME != '' && vars.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "build_unix_time=$(date '+%s')" >> "$GITHUB_OUTPUT"
|
echo "build_unix_time=$(date '+%s')" >> "$GITHUB_OUTPUT"
|
||||||
echo "build_date=$(date '+%Y%m%d')" >> "$GITHUB_OUTPUT"
|
echo "build_date=$(date '+%Y%m%d')" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_docker_bake_meta_file_path=${{ runner.temp }}/bake-meta.json" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_docker_bake_meta_file_path=${{ runner.temp }}/bake-meta.json" >> "$GITHUB_OUTPUT"
|
||||||
@@ -46,7 +47,6 @@ jobs:
|
|||||||
echo "ezbookkeeping_docker_digests_file_path=${{ runner.temp }}/digests" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_docker_digests_file_path=${{ runner.temp }}/digests" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_docker_digests_artifact_name_prefix=ezbookkeeping-build-dev-digests-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_docker_digests_artifact_name_prefix=ezbookkeeping-build-dev-digests-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_package_file_name_prefix=ezbookkeeping-dev-package-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_package_file_name_prefix=ezbookkeeping-dev-package-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "ezbookkeeping_package_artifact_name_prefix=ezbookkeeping-build-dev-package-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ezbookkeeping_backend_artifact_name_prefix=ezbookkeeping-build-dev-backend-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
echo "ezbookkeeping_backend_artifact_name_prefix=ezbookkeeping-build-dev-backend-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Rename docker bake meta file
|
- name: Rename docker bake meta file
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
mv "${{ steps.meta.outputs.bake-file }}" "${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}"
|
mv "${{ steps.meta.outputs.bake-file }}" "${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}"
|
||||||
|
|
||||||
- name: Upload docker bake meta artifact
|
- name: Upload docker bake meta artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_artifact_name }}
|
name: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_artifact_name }}
|
||||||
path: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}
|
path: ${{ steps.variable.outputs.ezbookkeeping_docker_bake_meta_file_path }}
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
- setup
|
- setup
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-linux-docker-and-package
|
- uses: ./.github/actions/build-linux-docker-and-package
|
||||||
with:
|
with:
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
skip-tests: ${{ vars.SKIP_TESTS }}
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
platform-name: linux-amd64
|
platform-name: linux-amd64
|
||||||
docker-push: true
|
docker-push: ${{ needs.setup.outputs.docker-push }}
|
||||||
docker-image-name: ${{ vars.DOCKER_IMAGE_NAME }}
|
docker-image-name: ${{ vars.DOCKER_IMAGE_NAME }}
|
||||||
docker-username: ${{ vars.DOCKER_USERNAME }}
|
docker-username: ${{ vars.DOCKER_USERNAME }}
|
||||||
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
@@ -85,7 +85,6 @@ jobs:
|
|||||||
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
||||||
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
|
||||||
|
|
||||||
build-linux-docker-and-package-arm:
|
build-linux-docker-and-package-arm:
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
@@ -102,7 +101,7 @@ jobs:
|
|||||||
platform-name: linux-armv6
|
platform-name: linux-armv6
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-linux-docker-and-package
|
- uses: ./.github/actions/build-linux-docker-and-package
|
||||||
with:
|
with:
|
||||||
@@ -112,7 +111,7 @@ jobs:
|
|||||||
skip-tests: ${{ vars.SKIP_TESTS }}
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
platform: ${{ matrix.platform }}
|
platform: ${{ matrix.platform }}
|
||||||
platform-name: ${{ matrix.platform-name }}
|
platform-name: ${{ matrix.platform-name }}
|
||||||
docker-push: true
|
docker-push: ${{ needs.setup.outputs.docker-push }}
|
||||||
docker-image-name: ${{ vars.DOCKER_IMAGE_NAME }}
|
docker-image-name: ${{ vars.DOCKER_IMAGE_NAME }}
|
||||||
docker-username: ${{ vars.DOCKER_USERNAME }}
|
docker-username: ${{ vars.DOCKER_USERNAME }}
|
||||||
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
@@ -121,9 +120,9 @@ jobs:
|
|||||||
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
docker-bake-digests-file-path: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-file-path }}
|
||||||
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
docker-bake-digests-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-docker-digests-artifact-name-prefix }}
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
|
||||||
|
|
||||||
push-linux-docker:
|
push-linux-docker:
|
||||||
|
if: ${{ needs.setup.outputs.docker-push == 'true' }}
|
||||||
needs:
|
needs:
|
||||||
- setup
|
- setup
|
||||||
- build-linux-docker-and-package-x86
|
- build-linux-docker-and-package-x86
|
||||||
@@ -131,7 +130,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/push-linux-docker
|
- uses: ./.github/actions/push-linux-docker
|
||||||
with:
|
with:
|
||||||
@@ -150,7 +149,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-windows-backend
|
- uses: ./.github/actions/build-windows-backend
|
||||||
with:
|
with:
|
||||||
@@ -160,6 +159,23 @@ jobs:
|
|||||||
skip-tests: ${{ vars.SKIP_TESTS }}
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
|
build-macos-backend:
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: ./.github/actions/build-macos-backend
|
||||||
|
with:
|
||||||
|
architecture: arm64
|
||||||
|
build-unix-time: ${{ needs.setup.outputs.build-unix-time }}
|
||||||
|
build-date: ${{ needs.setup.outputs.build-date }}
|
||||||
|
check-3rd-api: ${{ vars.CHECK_3RD_API }}
|
||||||
|
skip-tests: ${{ vars.SKIP_TESTS }}
|
||||||
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
build-windows-package:
|
build-windows-package:
|
||||||
needs:
|
needs:
|
||||||
- setup
|
- setup
|
||||||
@@ -168,10 +184,25 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: ./.github/actions/build-windows-package
|
- uses: ./.github/actions/build-windows-package
|
||||||
with:
|
with:
|
||||||
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
package-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|
||||||
|
build-macos-package:
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
- build-macos-backend
|
||||||
|
- build-linux-docker-and-package-x86
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: ./.github/actions/build-macos-package
|
||||||
|
with:
|
||||||
|
architecture: arm64
|
||||||
|
package-file-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-package-file-name-prefix }}
|
||||||
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
backend-artifact-name-prefix: ${{ needs.setup.outputs.ezbookkeeping-backend-artifact-name-prefix }}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Update i18n Translation Progress Badges
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'src/locales/**'
|
||||||
|
- 'pkg/locales/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-i18n-progress:
|
||||||
|
if: ${{ vars.UPDATE_I18N_BADGE_REPO == 'true' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
|
||||||
|
- name: Update translation progress data
|
||||||
|
run: |
|
||||||
|
node .github/scripts/update-i18n-progress.js ${{ runner.temp }}/i18n-badge
|
||||||
|
|
||||||
|
- name: Checkout badge repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
repository: mayswind/ezbookkeeping-i18n-badge
|
||||||
|
token: ${{ secrets.I18N_BADGE_REPO_TOKEN }}
|
||||||
|
path: ezbookkeeping-i18n-badge
|
||||||
|
|
||||||
|
- name: Update badge data
|
||||||
|
run: |
|
||||||
|
rm -rf ezbookkeeping-i18n-badge/i18n-progress.json
|
||||||
|
cp ${{ runner.temp }}/i18n-badge/i18n-progress.json ezbookkeeping-i18n-badge/
|
||||||
|
mkdir -p ezbookkeeping-i18n-badge/badges
|
||||||
|
rm -rf ezbookkeeping-i18n-badge/badges/*
|
||||||
|
cp ${{ runner.temp }}/i18n-badge/badges/*.json ezbookkeeping-i18n-badge/badges/
|
||||||
|
mkdir -p ezbookkeeping-i18n-badge/untranslated
|
||||||
|
rm -rf ezbookkeeping-i18n-badge/untranslated/*
|
||||||
|
cp ${{ runner.temp }}/i18n-badge/untranslated/*.json ezbookkeeping-i18n-badge/untranslated/
|
||||||
|
|
||||||
|
- name: Commit and push
|
||||||
|
run: |
|
||||||
|
cd ezbookkeeping-i18n-badge
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add -A
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No changes to commit"
|
||||||
|
else
|
||||||
|
git commit -m "Update i18n progress data (${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})"
|
||||||
|
git push
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Purge GitHub camo image cache
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
CAMO_URLS=$(curl -s -H "Accept: application/vnd.github.html+json" -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/${{ github.repository }}/readme" | grep -oP 'https://camo\.githubusercontent\.com/[^"]+' | sort -u)
|
||||||
|
|
||||||
|
if [ -z "$CAMO_URLS" ]; then
|
||||||
|
echo "No camo URLs found, skipping cache purge"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for url in $CAMO_URLS; do
|
||||||
|
echo "Purging: $url"
|
||||||
|
curl -s -X PURGE "$url" > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Purged $(echo "$CAMO_URLS" | wc -l) camo URLs"
|
||||||
+14
@@ -147,3 +147,17 @@ dist/
|
|||||||
|
|
||||||
# Roo Code
|
# Roo Code
|
||||||
.roo/
|
.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
|
# Build backend binary file
|
||||||
FROM golang:1.25.3-alpine3.22 AS be-builder
|
FROM golang:1.26.2-alpine3.23 AS be-builder
|
||||||
ARG RELEASE_BUILD
|
ARG RELEASE_BUILD
|
||||||
ARG BUILD_PIPELINE
|
ARG BUILD_PIPELINE
|
||||||
ARG BUILD_UNIXTIME
|
ARG BUILD_UNIXTIME
|
||||||
@@ -19,7 +19,7 @@ RUN apk add git gcc g++ libc-dev
|
|||||||
RUN ./build.sh backend
|
RUN ./build.sh backend
|
||||||
|
|
||||||
# Build frontend files
|
# Build frontend files
|
||||||
FROM --platform=$BUILDPLATFORM node:24.10.0-alpine3.22 AS fe-builder
|
FROM --platform=$BUILDPLATFORM node:24.15.0-alpine3.23 AS fe-builder
|
||||||
ARG RELEASE_BUILD
|
ARG RELEASE_BUILD
|
||||||
ARG BUILD_PIPELINE
|
ARG BUILD_PIPELINE
|
||||||
ARG BUILD_UNIXTIME
|
ARG BUILD_UNIXTIME
|
||||||
@@ -35,7 +35,7 @@ RUN apk add git
|
|||||||
RUN ./build.sh frontend
|
RUN ./build.sh frontend
|
||||||
|
|
||||||
# Package docker image
|
# Package docker image
|
||||||
FROM alpine:3.22.2
|
FROM alpine:3.23.4
|
||||||
LABEL maintainer="MaysWind <i@mayswind.net>"
|
LABEL maintainer="MaysWind <i@mayswind.net>"
|
||||||
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
|
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
|
||||||
RUN apk --no-cache add tzdata
|
RUN apk --no-cache add tzdata
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
[](https://trendshift.io/repositories/12917)
|
[](https://trendshift.io/repositories/12917)
|
||||||
|
|
||||||
## Introduction
|
## 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.
|
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**
|
- **Open Source & Self-Hosted**
|
||||||
- Built for privacy and control
|
- Built for privacy and control
|
||||||
- **Lightweight & Fast**
|
- **Lightweight & Fast**
|
||||||
- Optimized for performance, runs smoothly even on low-resource environments
|
- Minimal resource usage, runs smoothly even on low-resource devices
|
||||||
- **Easy Installation**
|
- **Easy Installation**
|
||||||
- Docker-ready
|
- Docker support
|
||||||
- Supports SQLite, MySQL, PostgreSQL
|
- Supports SQLite, MySQL, PostgreSQL
|
||||||
- Cross-platform (Windows, macOS, Linux)
|
- Cross-platform (Windows, macOS, Linux)
|
||||||
- Works on x86, amd64, ARM architectures
|
- Works on x86, amd64, ARM architectures
|
||||||
@@ -33,24 +33,28 @@ Live Demo: [https://ezbookkeeping-demo.mayswind.net](https://ezbookkeeping-demo.
|
|||||||
- Dark mode
|
- Dark mode
|
||||||
- **AI-Powered Features**
|
- **AI-Powered Features**
|
||||||
- Receipt image recognition
|
- Receipt image recognition
|
||||||
- Supports MCP (Model Context Protocol) for AI integration
|
- MCP (Model Context Protocol) support for AI integration
|
||||||
|
- Agent Skill and API command-line script tools support for AI integration
|
||||||
- **Powerful Bookkeeping**
|
- **Powerful Bookkeeping**
|
||||||
- Two-level accounts and categories
|
- Two-level accounts and categories
|
||||||
- Attach images to transactions
|
- Image attachments for transactions
|
||||||
- Location tracking with maps
|
- Location tracking with maps
|
||||||
- Recurring transactions
|
- Scheduled transactions
|
||||||
- Advanced filtering, search, visualization, and analysis
|
- Advanced filtering, search, visualization and analysis
|
||||||
- **Localization & Globalization**
|
- **Localization & Internationalization**
|
||||||
- Multi-language and multi-currency support
|
- Multi-language and multi-currency support
|
||||||
- Automatic exchange rates
|
- Multiple exchange rate sources with automatic updates
|
||||||
- Multi-timezone awareness
|
- Multi-timezone support
|
||||||
- Custom formats for dates, numbers, and currencies
|
- Custom formats for dates, numbers and currencies
|
||||||
- **Security**
|
- **Security**
|
||||||
- Two-factor authentication (2FA)
|
- Two-factor authentication (2FA)
|
||||||
|
- OIDC external authentication
|
||||||
- Login rate limiting
|
- Login rate limiting
|
||||||
- Application lock (PIN code / WebAuthn)
|
- Application lock (PIN code / WebAuthn)
|
||||||
- **Data Import/Export**
|
- **Data Import & Export**
|
||||||
- Supports CSV, OFX, QFX, QIF, IIF, Camt.053, MT940, GnuCash, Firefly III, Beancount, and more
|
- 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/features/).
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
### Desktop Version
|
### Desktop Version
|
||||||
@@ -112,38 +116,40 @@ You can also build a Docker image. Make sure you have [Docker](https://www.docke
|
|||||||
## Contributing
|
## Contributing
|
||||||
We welcome contributions of all kinds.
|
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
|
## 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:
|
Currently available translations:
|
||||||
|
|
||||||
| Tag | Language | Contributors |
|
| Tag | Language | Progress | Contributors |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| de | Deutsch | [@chrgm](https://github.com/chrgm) |
|
| de | Deutsch | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/de.json) | [@chrgm](https://github.com/chrgm), [@1270o1](https://github.com/1270o1) |
|
||||||
| en | English | / |
|
| en | English | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/en.json) | / |
|
||||||
| es | Español | [@Miguelonlonlon](https://github.com/Miguelonlonlon) |
|
| es | Español | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/es.json) | [@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) |
|
| fr | Français | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/fr.json) | [@brieucdlf](https://github.com/brieucdlf) |
|
||||||
| it | Italiano | [@waron97](https://github.com/waron97) |
|
| it | Italiano | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/it.json) | [@waron97](https://github.com/waron97) |
|
||||||
| ja | 日本語 | [@tkymmm](https://github.com/tkymmm) |
|
| ja | 日本語 | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/ja.json) | [@tkymmm](https://github.com/tkymmm) |
|
||||||
| ko | 한국어 | [@overworks](https://github.com/overworks) |
|
| kn | ಕನ್ನಡ | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/kn.json) | [@Darshanbm05](https://github.com/Darshanbm05) |
|
||||||
| nl | Nederlands | [@automagic](https://github.com/automagics) |
|
| ko | 한국어 | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/ko.json) | [@overworks](https://github.com/overworks) |
|
||||||
| pt-BR | Português (Brasil) | [@thecodergus](https://github.com/thecodergus) |
|
| nl | Nederlands | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/nl.json) | [@automagics](https://github.com/automagics) |
|
||||||
| ru | Русский | [@artegoser](https://github.com/artegoser) |
|
| pt-BR | Português (Brasil) | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/pt-BR.json) | [@thecodergus](https://github.com/thecodergus), [@balaios](https://github.com/balaios) |
|
||||||
| th | ไทย | [@natthavat28](https://github.com/natthavat28) |
|
| ru | Русский | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/ru.json) | [@artegoser](https://github.com/artegoser), [@dshemin](https://github.com/dshemin) |
|
||||||
| uk | Українська | [@nktlitvinenko](https://github.com/nktlitvinenko) |
|
| sl | Slovenščina | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/sl.json) | [@thehijacker](https://github.com/thehijacker) |
|
||||||
| vi | Tiếng Việt | [@f97](https://github.com/f97) |
|
| ta | தமிழ் | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/ta.json) | [@hhharsha36](https://github.com/hhharsha36) |
|
||||||
| zh-Hans | 中文 (简体) | / |
|
| th | ไทย | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/th.json) | [@natthavat28](https://github.com/natthavat28) |
|
||||||
| zh-Hant | 中文 (繁體) | / |
|
| tr | Türkçe | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/tr.json) | [@aydnykn](https://github.com/aydnykn) |
|
||||||
|
| uk | Українська | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/uk.json) | [@nktlitvinenko](https://github.com/nktlitvinenko), [@grid-pilot](https://github.com/grid-pilot) |
|
||||||
Don't see your language? Help us add it.
|
| vi | Tiếng Việt | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/vi.json) | [@f97](https://github.com/f97) |
|
||||||
|
| zh-Hans | 中文 (简体) | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/zh-Hans.json) | / |
|
||||||
|
| zh-Hant | 中文 (繁體) | [](https://github.com/mayswind/ezBookkeeping-i18n-badge/blob/main/untranslated/zh-Hant.json) | / |
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
1. [English](https://ezbookkeeping.mayswind.net)
|
1. [English](https://ezbookkeeping.mayswind.net)
|
||||||
|
|||||||
@@ -160,6 +160,12 @@ build_backend() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
ld_static_link_flags=""
|
||||||
|
|
||||||
|
if [ "$(uname -s)" = "Linux" ]; then
|
||||||
|
ld_static_link_flags="-linkmode external -extldflags '-static'"
|
||||||
|
fi
|
||||||
|
|
||||||
backend_build_extra_arguments="-X main.Version=$VERSION"
|
backend_build_extra_arguments="-X main.Version=$VERSION"
|
||||||
backend_build_extra_arguments="$backend_build_extra_arguments -X main.CommitHash=$COMMIT_HASH"
|
backend_build_extra_arguments="$backend_build_extra_arguments -X main.CommitHash=$COMMIT_HASH"
|
||||||
|
|
||||||
@@ -169,7 +175,7 @@ build_backend() {
|
|||||||
|
|
||||||
echo "Building backend binary file ($RELEASE_TYPE)..."
|
echo "Building backend binary file ($RELEASE_TYPE)..."
|
||||||
|
|
||||||
CGO_ENABLED=1 go build -a -v -trimpath -ldflags "-w -s -linkmode external -extldflags '-static' $backend_build_extra_arguments" -o ezbookkeeping ezbookkeeping.go
|
CGO_ENABLED=1 go build -a -v -trimpath -ldflags "-w -s $ld_static_link_flags $backend_build_extra_arguments" -o ezbookkeeping ezbookkeeping.go
|
||||||
chmod +x ezbookkeeping
|
chmod +x ezbookkeeping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,14 @@ func updateAllDatabaseTablesStructure(c *core.CliContext) error {
|
|||||||
|
|
||||||
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] transaction category table maintained successfully")
|
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))
|
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionTag))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -157,5 +165,13 @@ func updateAllDatabaseTablesStructure(c *core.CliContext) error {
|
|||||||
|
|
||||||
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] user external auth table maintained successfully")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,9 +195,25 @@ func getConfigWithoutSensitiveData(config *settings.Config) *settings.Config {
|
|||||||
clonedConfig.ReceiptImageRecognitionLLMConfig.OpenAICompatibleAPIKey = "****"
|
clonedConfig.ReceiptImageRecognitionLLMConfig.OpenAICompatibleAPIKey = "****"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if clonedConfig.ReceiptImageRecognitionLLMConfig.AnthropicCompatibleAPIKey != "" {
|
||||||
|
clonedConfig.ReceiptImageRecognitionLLMConfig.AnthropicCompatibleAPIKey = "****"
|
||||||
|
}
|
||||||
|
|
||||||
|
if clonedConfig.ReceiptImageRecognitionLLMConfig.AnthropicAPIKey != "" {
|
||||||
|
clonedConfig.ReceiptImageRecognitionLLMConfig.AnthropicAPIKey = "****"
|
||||||
|
}
|
||||||
|
|
||||||
if clonedConfig.ReceiptImageRecognitionLLMConfig.OpenRouterAPIKey != "" {
|
if clonedConfig.ReceiptImageRecognitionLLMConfig.OpenRouterAPIKey != "" {
|
||||||
clonedConfig.ReceiptImageRecognitionLLMConfig.OpenRouterAPIKey = "****"
|
clonedConfig.ReceiptImageRecognitionLLMConfig.OpenRouterAPIKey = "****"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if clonedConfig.ReceiptImageRecognitionLLMConfig.LMStudioToken != "" {
|
||||||
|
clonedConfig.ReceiptImageRecognitionLLMConfig.LMStudioToken = "****"
|
||||||
|
}
|
||||||
|
|
||||||
|
if clonedConfig.ReceiptImageRecognitionLLMConfig.GoogleAIAPIKey != "" {
|
||||||
|
clonedConfig.ReceiptImageRecognitionLLMConfig.GoogleAIAPIKey = "****"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if clonedConfig.OAuth2ClientSecret != "" {
|
if clonedConfig.OAuth2ClientSecret != "" {
|
||||||
|
|||||||
@@ -953,6 +953,7 @@ func printUserInfo(user *models.User) {
|
|||||||
fmt.Printf("[Password] %s\n", user.Password)
|
fmt.Printf("[Password] %s\n", user.Password)
|
||||||
fmt.Printf("[Salt] %s\n", user.Salt)
|
fmt.Printf("[Salt] %s\n", user.Salt)
|
||||||
fmt.Printf("[DefaultAccountId] %d\n", user.DefaultAccountId)
|
fmt.Printf("[DefaultAccountId] %d\n", user.DefaultAccountId)
|
||||||
|
fmt.Printf("[UseLastReconciledTime] %t\n", user.UseLastReconciledTime)
|
||||||
fmt.Printf("[TransactionEditScope] %s (%d)\n", user.TransactionEditScope, user.TransactionEditScope)
|
fmt.Printf("[TransactionEditScope] %s (%d)\n", user.TransactionEditScope, user.TransactionEditScope)
|
||||||
fmt.Printf("[Language] %s\n", user.Language)
|
fmt.Printf("[Language] %s\n", user.Language)
|
||||||
fmt.Printf("[DefaultCurrency] %s\n", user.DefaultCurrency)
|
fmt.Printf("[DefaultCurrency] %s\n", user.DefaultCurrency)
|
||||||
|
|||||||
+28
-1
@@ -116,6 +116,7 @@ func startWebServer(c *core.CliContext) error {
|
|||||||
_ = v.RegisterValidation("validCurrency", validators.ValidCurrency)
|
_ = v.RegisterValidation("validCurrency", validators.ValidCurrency)
|
||||||
_ = v.RegisterValidation("validHexRGBColor", validators.ValidHexRGBColor)
|
_ = v.RegisterValidation("validHexRGBColor", validators.ValidHexRGBColor)
|
||||||
_ = v.RegisterValidation("validAmountFilter", validators.ValidAmountFilter)
|
_ = v.RegisterValidation("validAmountFilter", validators.ValidAmountFilter)
|
||||||
|
_ = v.RegisterValidation("validTagFilter", validators.ValidTagFilter)
|
||||||
_ = v.RegisterValidation("validFiscalYearStart", validators.ValidateFiscalYearStart)
|
_ = v.RegisterValidation("validFiscalYearStart", validators.ValidateFiscalYearStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,6 +316,7 @@ func startWebServer(c *core.CliContext) error {
|
|||||||
|
|
||||||
apiV1Route := apiRoute.Group("/v1")
|
apiV1Route := apiRoute.Group("/v1")
|
||||||
apiV1Route.Use(bindMiddleware(middlewares.JWTAuthorization(config)))
|
apiV1Route.Use(bindMiddleware(middlewares.JWTAuthorization(config)))
|
||||||
|
apiV1Route.Use(bindMiddleware(middlewares.APITokenIpLimit(config)))
|
||||||
{
|
{
|
||||||
// Tokens
|
// Tokens
|
||||||
apiV1Route.GET("/tokens/list.json", bindApi(api.Tokens.TokenListHandler))
|
apiV1Route.GET("/tokens/list.json", bindApi(api.Tokens.TokenListHandler))
|
||||||
@@ -373,6 +375,7 @@ func startWebServer(c *core.CliContext) error {
|
|||||||
apiV1Route.GET("/accounts/get.json", bindApi(api.Accounts.AccountGetHandler))
|
apiV1Route.GET("/accounts/get.json", bindApi(api.Accounts.AccountGetHandler))
|
||||||
apiV1Route.POST("/accounts/add.json", bindApi(api.Accounts.AccountCreateHandler))
|
apiV1Route.POST("/accounts/add.json", bindApi(api.Accounts.AccountCreateHandler))
|
||||||
apiV1Route.POST("/accounts/modify.json", bindApi(api.Accounts.AccountModifyHandler))
|
apiV1Route.POST("/accounts/modify.json", bindApi(api.Accounts.AccountModifyHandler))
|
||||||
|
apiV1Route.POST("/accounts/update/last_reconciled_time.json", bindApi(api.Accounts.AccountUpdateLastReconciledTimeHandler))
|
||||||
apiV1Route.POST("/accounts/hide.json", bindApi(api.Accounts.AccountHideHandler))
|
apiV1Route.POST("/accounts/hide.json", bindApi(api.Accounts.AccountHideHandler))
|
||||||
apiV1Route.POST("/accounts/move.json", bindApi(api.Accounts.AccountMoveHandler))
|
apiV1Route.POST("/accounts/move.json", bindApi(api.Accounts.AccountMoveHandler))
|
||||||
apiV1Route.POST("/accounts/delete.json", bindApi(api.Accounts.AccountDeleteHandler))
|
apiV1Route.POST("/accounts/delete.json", bindApi(api.Accounts.AccountDeleteHandler))
|
||||||
@@ -382,6 +385,7 @@ func startWebServer(c *core.CliContext) error {
|
|||||||
apiV1Route.GET("/transactions/count.json", bindApi(api.Transactions.TransactionCountHandler))
|
apiV1Route.GET("/transactions/count.json", bindApi(api.Transactions.TransactionCountHandler))
|
||||||
apiV1Route.GET("/transactions/list.json", bindApi(api.Transactions.TransactionListHandler))
|
apiV1Route.GET("/transactions/list.json", bindApi(api.Transactions.TransactionListHandler))
|
||||||
apiV1Route.GET("/transactions/list/by_month.json", bindApi(api.Transactions.TransactionMonthListHandler))
|
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/reconciliation_statements.json", bindApi(api.Transactions.TransactionReconciliationStatementHandler))
|
||||||
apiV1Route.GET("/transactions/statistics.json", bindApi(api.Transactions.TransactionStatisticsHandler))
|
apiV1Route.GET("/transactions/statistics.json", bindApi(api.Transactions.TransactionStatisticsHandler))
|
||||||
apiV1Route.GET("/transactions/statistics/trends.json", bindApi(api.Transactions.TransactionStatisticsTrendsHandler))
|
apiV1Route.GET("/transactions/statistics/trends.json", bindApi(api.Transactions.TransactionStatisticsTrendsHandler))
|
||||||
@@ -390,11 +394,17 @@ func startWebServer(c *core.CliContext) error {
|
|||||||
apiV1Route.GET("/transactions/get.json", bindApi(api.Transactions.TransactionGetHandler))
|
apiV1Route.GET("/transactions/get.json", bindApi(api.Transactions.TransactionGetHandler))
|
||||||
apiV1Route.POST("/transactions/add.json", bindApi(api.Transactions.TransactionCreateHandler))
|
apiV1Route.POST("/transactions/add.json", bindApi(api.Transactions.TransactionCreateHandler))
|
||||||
apiV1Route.POST("/transactions/modify.json", bindApi(api.Transactions.TransactionModifyHandler))
|
apiV1Route.POST("/transactions/modify.json", bindApi(api.Transactions.TransactionModifyHandler))
|
||||||
|
apiV1Route.POST("/transactions/batch_update/category.json", bindApi(api.Transactions.TransactionBatchUpdateCategoriesHandler))
|
||||||
|
apiV1Route.POST("/transactions/batch_update/account.json", bindApi(api.Transactions.TransactionBatchUpdateAccountsHandler))
|
||||||
|
apiV1Route.POST("/transactions/batch_update/tag/add.json", bindApi(api.Transactions.TransactionBatchAddTagsHandler))
|
||||||
|
apiV1Route.POST("/transactions/batch_update/tag/remove.json", bindApi(api.Transactions.TransactionBatchRemoveTagsHandler))
|
||||||
|
apiV1Route.POST("/transactions/batch_update/tag/clear.json", bindApi(api.Transactions.TransactionBatchClearTagsHandler))
|
||||||
apiV1Route.POST("/transactions/move/all.json", bindApi(api.Transactions.TransactionMoveAllBetweenAccountsHandler))
|
apiV1Route.POST("/transactions/move/all.json", bindApi(api.Transactions.TransactionMoveAllBetweenAccountsHandler))
|
||||||
apiV1Route.POST("/transactions/delete.json", bindApi(api.Transactions.TransactionDeleteHandler))
|
apiV1Route.POST("/transactions/delete.json", bindApi(api.Transactions.TransactionDeleteHandler))
|
||||||
|
apiV1Route.POST("/transactions/batch_delete.json", bindApi(api.Transactions.TransactionBatchDeleteHandler))
|
||||||
|
|
||||||
if config.EnableDataImport {
|
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/parse_import.json", bindApi(api.Transactions.TransactionParseImportFileHandler))
|
||||||
apiV1Route.POST("/transactions/import.json", bindApi(api.Transactions.TransactionImportHandler))
|
apiV1Route.POST("/transactions/import.json", bindApi(api.Transactions.TransactionImportHandler))
|
||||||
apiV1Route.GET("/transactions/import/process.json", bindApi(api.Transactions.TransactionImportProcessHandler))
|
apiV1Route.GET("/transactions/import/process.json", bindApi(api.Transactions.TransactionImportProcessHandler))
|
||||||
@@ -416,6 +426,14 @@ func startWebServer(c *core.CliContext) error {
|
|||||||
apiV1Route.POST("/transaction/categories/move.json", bindApi(api.TransactionCategories.CategoryMoveHandler))
|
apiV1Route.POST("/transaction/categories/move.json", bindApi(api.TransactionCategories.CategoryMoveHandler))
|
||||||
apiV1Route.POST("/transaction/categories/delete.json", bindApi(api.TransactionCategories.CategoryDeleteHandler))
|
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
|
// Transaction Tags
|
||||||
apiV1Route.GET("/transaction/tags/list.json", bindApi(api.TransactionTags.TagListHandler))
|
apiV1Route.GET("/transaction/tags/list.json", bindApi(api.TransactionTags.TagListHandler))
|
||||||
apiV1Route.GET("/transaction/tags/get.json", bindApi(api.TransactionTags.TagGetHandler))
|
apiV1Route.GET("/transaction/tags/get.json", bindApi(api.TransactionTags.TagGetHandler))
|
||||||
@@ -435,6 +453,15 @@ func startWebServer(c *core.CliContext) error {
|
|||||||
apiV1Route.POST("/transaction/templates/move.json", bindApi(api.TransactionTemplates.TemplateMoveHandler))
|
apiV1Route.POST("/transaction/templates/move.json", bindApi(api.TransactionTemplates.TemplateMoveHandler))
|
||||||
apiV1Route.POST("/transaction/templates/delete.json", bindApi(api.TransactionTemplates.TemplateDeleteHandler))
|
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
|
// Large Language Models
|
||||||
if config.ReceiptImageRecognitionLLMConfig != nil && config.ReceiptImageRecognitionLLMConfig.LLMProvider != "" {
|
if config.ReceiptImageRecognitionLLMConfig != nil && config.ReceiptImageRecognitionLLMConfig.LLMProvider != "" {
|
||||||
if config.TransactionFromAIImageRecognition {
|
if config.TransactionFromAIImageRecognition {
|
||||||
|
|||||||
+39
-7
@@ -1,7 +1,4 @@
|
|||||||
[global]
|
[global]
|
||||||
# Application instance name
|
|
||||||
app_name = ezBookkeeping
|
|
||||||
|
|
||||||
# Either "production", "development"
|
# Either "production", "development"
|
||||||
mode = production
|
mode = production
|
||||||
|
|
||||||
@@ -18,7 +15,7 @@ http_port = 8080
|
|||||||
# The domain name used to access ezBookkeeping
|
# The domain name used to access ezBookkeeping
|
||||||
domain = localhost
|
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/
|
root_url = %(protocol)s://%(domain)s:%(http_port)s/
|
||||||
|
|
||||||
# https certification and its key file
|
# https certification and its key file
|
||||||
@@ -172,7 +169,7 @@ transaction_from_ai_image_recognition = false
|
|||||||
max_ai_recognition_picture_size = 10485760
|
max_ai_recognition_picture_size = 10485760
|
||||||
|
|
||||||
[llm_image_recognition]
|
[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 =
|
llm_provider =
|
||||||
|
|
||||||
# For "openai" llm provider only, OpenAI API secret key, please visit https://platform.openai.com/api-keys for more information
|
# 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
|
# For "openai_compatible" llm provider only, receipt image recognition model for creating transactions from images
|
||||||
openai_compatible_model_id =
|
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
|
# For "openrouter" llm provider only, OpenRouter API key, please visit https://openrouter.ai/settings/keys for more information
|
||||||
openrouter_api_key =
|
openrouter_api_key =
|
||||||
|
|
||||||
@@ -202,6 +223,15 @@ ollama_server_url =
|
|||||||
# For "ollama" llm provider only, receipt image recognition model for creating transactions from images
|
# For "ollama" llm provider only, receipt image recognition model for creating transactions from images
|
||||||
ollama_model_id =
|
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
|
# 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 =
|
google_ai_api_key =
|
||||||
|
|
||||||
@@ -266,6 +296,9 @@ password_reset_token_expired_time = 3600
|
|||||||
# Set to true to enable API token generation
|
# Set to true to enable API token generation
|
||||||
enable_api_token = false
|
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
|
# 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
|
max_failures_per_ip_per_minute = 5
|
||||||
|
|
||||||
@@ -496,7 +529,6 @@ custom_map_tile_server_default_zoom_level = 14
|
|||||||
|
|
||||||
[exchange_rates]
|
[exchange_rates]
|
||||||
# Exchange rates data source, supports the following types:
|
# 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/
|
# "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/
|
# "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
|
# "danmarks_national_bank": https://www.nationalbanken.dk/en/what-we-do/stable-prices-monetary-policy-and-the-danish-economy/exchange-rates
|
||||||
@@ -504,6 +536,7 @@ custom_map_tile_server_default_zoom_level = 14
|
|||||||
# "national_bank_of_georgia": https://nbg.gov.ge/en/monetary-policy/currency
|
# "national_bank_of_georgia": https://nbg.gov.ge/en/monetary-policy/currency
|
||||||
# "central_bank_of_hungary": https://www.mnb.hu/en/arfolyamok
|
# "central_bank_of_hungary": https://www.mnb.hu/en/arfolyamok
|
||||||
# "bank_of_israel": https://www.boi.org.il/en/economic-roles/financial-markets/exchange-rates/
|
# "bank_of_israel": https://www.boi.org.il/en/economic-roles/financial-markets/exchange-rates/
|
||||||
|
# "national_bank_of_kazakhstan": https://nationalbank.kz/en/exchangerates/ezhednevnye-oficialnye-rynochnye-kursy-valyut
|
||||||
# "central_bank_of_myanmar": https://forex.cbm.gov.mm/index.php/fxrate
|
# "central_bank_of_myanmar": https://forex.cbm.gov.mm/index.php/fxrate
|
||||||
# "norges_bank": https://www.norges-bank.no/en/topics/Statistics/exchange_rates/
|
# "norges_bank": https://www.norges-bank.no/en/topics/Statistics/exchange_rates/
|
||||||
# "national_bank_of_poland": https://nbp.pl/en/statistic-and-financial-reporting/rates/
|
# "national_bank_of_poland": https://nbp.pl/en/statistic-and-financial-reporting/rates/
|
||||||
@@ -512,7 +545,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
|
# "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
|
# "national_bank_of_ukraine": https://bank.gov.ua/ua/markets/exchangerates
|
||||||
# "central_bank_of_uzbekistan": https://cbu.uz/en/arkhiv-kursov-valyut/
|
# "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
|
# "user_custom": users set their own exchange rates data in the UI
|
||||||
data_source = euro_central_bank
|
data_source = euro_central_bank
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"code": [
|
||||||
|
"jiangshengwu",
|
||||||
|
"vigdail",
|
||||||
|
"f97",
|
||||||
|
"Miguelonlonlon",
|
||||||
|
"seb26",
|
||||||
|
"nktlitvinenko",
|
||||||
|
"lvdou-bing",
|
||||||
|
"dshemin",
|
||||||
|
"lucdsouza",
|
||||||
|
"OuIChien",
|
||||||
|
"RasterCrow"
|
||||||
|
],
|
||||||
|
"translators": {
|
||||||
|
"de": [
|
||||||
|
"chrgm",
|
||||||
|
"1270o1"
|
||||||
|
],
|
||||||
|
"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",
|
||||||
|
"grid-pilot"
|
||||||
|
],
|
||||||
|
"vi": [
|
||||||
|
"f97"
|
||||||
|
],
|
||||||
|
"zh-Hans": [],
|
||||||
|
"zh-Hant": []
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/cmd"
|
"github.com/mayswind/ezbookkeeping/cmd"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,9 +26,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
settings.Version = Version
|
core.Version = Version
|
||||||
settings.CommitHash = CommitHash
|
core.CommitHash = CommitHash
|
||||||
settings.BuildTime = BuildUnixTime
|
core.BuildTime = BuildUnixTime
|
||||||
|
|
||||||
cmd := &cli.Command{
|
cmd := &cli.Command{
|
||||||
Name: "ezBookkeeping",
|
Name: "ezBookkeeping",
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
module github.com/mayswind/ezbookkeeping
|
module github.com/mayswind/ezbookkeeping
|
||||||
|
|
||||||
go 1.25
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boombuler/barcode v1.1.0
|
github.com/boombuler/barcode v1.1.0
|
||||||
github.com/coreos/go-oidc/v3 v3.16.0
|
github.com/coreos/go-oidc/v3 v3.18.0
|
||||||
github.com/extrame/xls v0.0.2-0.20200426124601-4a6cf263071b
|
github.com/extrame/xls v0.0.2-0.20200426124601-4a6cf263071b
|
||||||
github.com/gin-contrib/cache v1.4.1
|
github.com/gin-contrib/cache v1.4.4
|
||||||
github.com/gin-contrib/gzip v1.2.5
|
github.com/gin-contrib/gzip v1.2.6
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/go-co-op/gocron/v2 v2.17.0
|
github.com/go-co-op/gocron/v2 v2.21.1
|
||||||
github.com/go-playground/validator/v10 v10.28.0
|
github.com/go-playground/validator/v10 v10.30.2
|
||||||
github.com/go-sql-driver/mysql v1.9.3
|
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/invopop/jsonschema v0.13.0
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.12.3
|
||||||
github.com/mattn/go-sqlite3 v1.14.32
|
github.com/mattn/go-sqlite3 v1.14.42
|
||||||
github.com/minio/minio-go/v7 v7.0.95
|
github.com/minio/minio-go/v7 v7.0.100
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pquerna/otp v1.5.0
|
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/stretchr/testify v1.11.1
|
||||||
github.com/urfave/cli/v3 v3.5.0
|
github.com/urfave/cli/v3 v3.8.0
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||||
github.com/xuri/excelize/v2 v2.10.0
|
github.com/xuri/excelize/v2 v2.10.1
|
||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/net v0.46.0
|
golang.org/x/net v0.53.0
|
||||||
golang.org/x/oauth2 v0.32.0
|
golang.org/x/oauth2 v0.36.0
|
||||||
golang.org/x/text v0.30.0
|
golang.org/x/text v0.36.0
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.1
|
||||||
gopkg.in/mail.v2 v2.3.1
|
gopkg.in/mail.v2 v2.3.1
|
||||||
xorm.io/builder v0.3.13
|
xorm.io/builder v0.3.13
|
||||||
xorm.io/xorm v1.3.11
|
xorm.io/xorm v1.3.11
|
||||||
@@ -40,8 +40,8 @@ require (
|
|||||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||||
github.com/buger/jsonparser v1.1.1 // indirect
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.14.1 // indirect
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
@@ -52,26 +52,27 @@ require (
|
|||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/extrame/goyymmdd v0.0.0-20210114090516-7cc815f00d1a // indirect
|
github.com/extrame/goyymmdd v0.0.0-20210114090516-7cc815f00d1a // indirect
|
||||||
github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7 // 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.13 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-ini/ini v1.67.0 // indirect
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/gomodule/redigo v1.9.2 // indirect
|
github.com/gomodule/redigo v1.9.2 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.5 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // 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/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/memcachier/mc/v3 v3.0.3 // 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/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
@@ -79,29 +80,31 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/philhofer/fwd v1.2.0 // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.55.0 // indirect
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
github.com/richardlehane/mscfb v1.0.6 // indirect
|
||||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
github.com/richardlehane/msoleps v1.0.6 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
|
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
github.com/tealeg/xlsx v1.0.5 // indirect
|
github.com/tealeg/xlsx v1.0.5 // indirect
|
||||||
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
github.com/tiendc/go-deepcopy v1.7.2 // 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/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
github.com/xuri/efp v0.0.1 // indirect
|
github.com/xuri/efp v0.0.1 // indirect
|
||||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/arch v0.22.0 // indirect
|
golang.org/x/arch v0.22.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||||
golang.org/x/mod v0.29.0 // indirect
|
golang.org/x/mod v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
golang.org/x/tools v0.43.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M
|
|||||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
@@ -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 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
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/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.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A=
|
||||||
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -43,36 +43,36 @@ 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 h1:jqW/h4gcXYEB6kVf6iuxjU9ONWA0ugUB94TP9UNmgdg=
|
||||||
github.com/extrame/xls v0.0.2-0.20200426124601-4a6cf263071b/go.mod h1:iACcgahst7BboCpIMSpnFs4SKyU9ZjsvZBfNbUxZOJI=
|
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/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.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gin-contrib/cache v1.4.1 h1:HcLwLfw7p+FasNp5VAnFbbBj9SzB4bDtswvon7wYSg4=
|
github.com/gin-contrib/cache v1.4.4 h1:4Sasrroa8CrbRYQ3aEMutRJGhz7ujyPlKvAPmJdIx9U=
|
||||||
github.com/gin-contrib/cache v1.4.1/go.mod h1:tykDV+FgItJHYEO0eCasuRYsZKPPyb4BYhAjuTlG6RM=
|
github.com/gin-contrib/cache v1.4.4/go.mod h1:OfwzOu0CcBcQYgvc+wg7moQWFzmJCKqmo0NU7Wx3xyQ=
|
||||||
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
|
github.com/gin-contrib/gzip v1.2.6 h1:OtN8DplD5DNZCSLAnQ5HxRkD2qZ5VU+JhOrcfJrcRvg=
|
||||||
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
|
github.com/gin-contrib/gzip v1.2.6/go.mod h1:BQy8/+JApnRjAVUplSGZiVtD2k8GmIE2e9rYu/hLzzU=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
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-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.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||||
github.com/go-co-op/gocron/v2 v2.17.0 h1:e/oj6fcAM8vOOKZxv2Cgfmjo+s8AXC46po5ZPtaSea4=
|
github.com/go-co-op/gocron/v2 v2.21.1 h1:QYOK6iOQVCut+jDcs4zRdWRTBHRxRCEeeFi1TnAmgbU=
|
||||||
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.21.1/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
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-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
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.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
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/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 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
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.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
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/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.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
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/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
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.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
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.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 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
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/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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
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/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 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
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.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||||
github.com/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4=
|
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/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.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||||
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
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 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
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.100 h1:ShkWi8Tyj9RtU57OQB2HIXKz4bFgtVib0bbT1sbtLI8=
|
||||||
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
|
github.com/minio/minio-go/v7 v7.0.100/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -137,15 +139,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8=
|
||||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
|
||||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
|
||||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko=
|
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko=
|
||||||
@@ -153,67 +154,70 @@ 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 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
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/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.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
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.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.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.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.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.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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
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/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
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/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.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
|
||||||
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
||||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
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.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/urfave/cli/v3 v3.5.0 h1:qCuFMmdayTF3zmjG8TSsoBzrDqszNrklYg2x3g4MSgw=
|
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
||||||
github.com/urfave/cli/v3 v3.5.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
github.com/urfave/cli/v3 v3.8.0/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 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
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=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
|
github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0=
|
||||||
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
|
github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc=
|
||||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
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=
|
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
|
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.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 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
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.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sync v0.17.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-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
|
||||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
@@ -221,8 +225,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 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/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/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.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
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 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
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=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { type JestConfigWithTsJest, createDefaultEsmPreset } from 'ts-jest';
|
|
||||||
|
|
||||||
const presetConfig = createDefaultEsmPreset({
|
|
||||||
tsconfig: '<rootDir>/tsconfig.jest.json'
|
|
||||||
});
|
|
||||||
|
|
||||||
const config: JestConfigWithTsJest = {
|
|
||||||
...presetConfig,
|
|
||||||
clearMocks: true,
|
|
||||||
collectCoverage: false,
|
|
||||||
moduleNameMapper: {
|
|
||||||
"^@/(.*)$": "<rootDir>/src/$1"
|
|
||||||
},
|
|
||||||
testEnvironment: "node",
|
|
||||||
testMatch: [
|
|
||||||
"**/__tests__/**/*.[jt]s?(x)",
|
|
||||||
"!**/__tests__/*_gen.[jt]s?(x)"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
Generated
+3299
-4998
File diff suppressed because it is too large
Load Diff
+56
-57
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ezbookkeeping",
|
"name": "ezbookkeeping",
|
||||||
"version": "1.2.0",
|
"version": "1.5.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -16,66 +16,65 @@
|
|||||||
"build": "cross-env NODE_ENV=production vite build",
|
"build": "cross-env NODE_ENV=production vite build",
|
||||||
"serve:dist": "vite preview",
|
"serve:dist": "vite preview",
|
||||||
"lint": "vue-tsc --noEmit && eslint . --fix",
|
"lint": "vue-tsc --noEmit && eslint . --fix",
|
||||||
"test": "cross-env TS_NODE_PROJECT=\"./tsconfig.jest.json\" jest"
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@vuepic/vue-datepicker": "^11.0.3",
|
"@vuepic/vue-datepicker": "12.1.0",
|
||||||
"axios": "^1.12.2",
|
"axios": "1.15.2",
|
||||||
"cbor-js": "^0.1.0",
|
"cbor-js": "0.1.0",
|
||||||
"clipboard": "^2.0.11",
|
"chardet": "2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"clipboard": "2.0.11",
|
||||||
"dom7": "^4.0.6",
|
"crypto-js": "4.2.0",
|
||||||
"echarts": "^6.0.0",
|
"dom7": "4.0.6",
|
||||||
"framework7": "^8.3.4",
|
"echarts": "6.0.0",
|
||||||
"framework7-icons": "^5.0.5",
|
"framework7": "9.0.3",
|
||||||
"framework7-vue": "^8.3.4",
|
"framework7-icons": "5.0.5",
|
||||||
"jalaali-js": "^1.2.8",
|
"framework7-vue": "9.0.3",
|
||||||
"leaflet": "^1.9.4",
|
"jalaali-js": "1.2.8",
|
||||||
"line-awesome": "^1.3.0",
|
"leaflet": "1.9.4",
|
||||||
"moment": "^2.30.1",
|
"line-awesome": "1.3.0",
|
||||||
"moment-timezone": "^0.6.0",
|
"moment": "2.30.1",
|
||||||
"pinia": "^3.0.3",
|
"moment-timezone": "0.6.1",
|
||||||
"register-service-worker": "^1.7.2",
|
"pinia": "3.0.4",
|
||||||
"skeleton-elements": "^4.0.1",
|
"register-service-worker": "1.7.2",
|
||||||
"swiper": "^10.2.0",
|
"skeleton-elements": "4.0.1",
|
||||||
"ua-parser-js": "^1.0.39",
|
"swiper": "12.1.3",
|
||||||
"vue": "^3.5.22",
|
"ua-parser-js": "1.0.39",
|
||||||
"vue-echarts": "^8.0.1",
|
"vue": "3.5.33",
|
||||||
"vue-i18n": "^11.1.12",
|
"vue-echarts": "8.0.1",
|
||||||
"vue-router": "^4.6.3",
|
"vue-i18n": "11.3.2",
|
||||||
"vue3-perfect-scrollbar": "^2.0.0",
|
"vue-router": "5.0.6",
|
||||||
"vuedraggable": "^4.1.0",
|
"vue3-perfect-scrollbar": "2.0.0",
|
||||||
"vuetify": "^3.10.7"
|
"vuedraggable": "4.1.0",
|
||||||
|
"vuetify": "3.12.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "^30.2.0",
|
"@jest/globals": "30.3.0",
|
||||||
"@tsconfig/node24": "^24.0.1",
|
"@tsconfig/node24": "24.0.4",
|
||||||
"@types/cbor-js": "^0.1.1",
|
"@types/cbor-js": "0.1.1",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/git-rev-sync": "^2.0.2",
|
"@types/git-rev-sync": "2.0.2",
|
||||||
"@types/jalaali-js": "^1.2.0",
|
"@types/jalaali-js": "1.2.0",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/node": "25.6.0",
|
||||||
"@types/node": "^24.9.1",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@vitejs/plugin-vue": "6.0.6",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vue/eslint-config-typescript": "14.7.0",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/tsconfig": "0.9.1",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"cross-env": "10.1.0",
|
||||||
"cross-env": "^10.1.0",
|
"eslint": "10.2.1",
|
||||||
"eslint": "^9.38.0",
|
"eslint-plugin-vue": "10.9.0",
|
||||||
"eslint-plugin-vue": "^10.5.1",
|
"git-rev-sync": "3.0.2",
|
||||||
"git-rev-sync": "^3.0.2",
|
"postcss-preset-env": "11.2.1",
|
||||||
"jest": "^30.2.0",
|
"sass": "1.99.0",
|
||||||
"postcss-preset-env": "^10.4.0",
|
"ts-node": "10.9.2",
|
||||||
"sass": "^1.93.2",
|
"typescript": "6.0.3",
|
||||||
"ts-jest": "^29.4.5",
|
"vite": "7.3.2",
|
||||||
"ts-node": "^10.9.2",
|
"vite-plugin-checker": "0.13.0",
|
||||||
"typescript": "^5.9.3",
|
"vite-plugin-pwa": "1.2.0",
|
||||||
"vite": "^7.1.12",
|
"vite-plugin-vuetify": "2.1.3",
|
||||||
"vite-plugin-checker": "^0.11.0",
|
"vitest": "4.1.5",
|
||||||
"vite-plugin-pwa": "^1.1.0",
|
"vue-tsc": "3.2.7"
|
||||||
"vite-plugin-vuetify": "^2.1.2",
|
|
||||||
"vue-tsc": "^3.1.2"
|
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 5 Chrome versions",
|
"last 5 Chrome versions",
|
||||||
|
|||||||
+130
-28
@@ -19,6 +19,7 @@ type AccountsApi struct {
|
|||||||
ApiUsingConfig
|
ApiUsingConfig
|
||||||
ApiUsingDuplicateChecker
|
ApiUsingDuplicateChecker
|
||||||
accounts *services.AccountService
|
accounts *services.AccountService
|
||||||
|
users *services.UserService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize an account api singleton instance
|
// Initialize an account api singleton instance
|
||||||
@@ -34,6 +35,7 @@ var (
|
|||||||
container: duplicatechecker.Container,
|
container: duplicatechecker.Container,
|
||||||
},
|
},
|
||||||
accounts: services.Accounts,
|
accounts: services.Accounts,
|
||||||
|
users: services.Users,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -150,10 +152,10 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error
|
|||||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
utcOffset, err := c.GetClientTimezoneOffset()
|
clientTimezone, err := c.GetClientTimezone()
|
||||||
|
|
||||||
if err != nil {
|
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
|
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +280,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 {
|
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())
|
log.Errorf(c, "[accounts.AccountCreateHandler] failed to create account \"id:%d\" for user \"uid:%d\", because %s", mainAccount.AccountId, uid, err.Error())
|
||||||
@@ -315,10 +317,10 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
|||||||
return nil, errs.ErrAccountIdInvalid
|
return nil, errs.ErrAccountIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
utcOffset, err := c.GetClientTimezoneOffset()
|
clientTimezone, err := c.GetClientTimezone()
|
||||||
|
|
||||||
if err != nil {
|
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
|
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +335,16 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
|||||||
}
|
}
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
|
user, err := a.users.GetUserById(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsCustomError(err) {
|
||||||
|
log.Errorf(c, "[accounts.AccountModifyHandler] failed to get user, because %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errs.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountModifyReq.Id)
|
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountModifyReq.Id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -434,9 +446,24 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
|||||||
var toAddAccountBalanceTimes []int64
|
var toAddAccountBalanceTimes []int64
|
||||||
var toDeleteAccountIds []int64
|
var toDeleteAccountIds []int64
|
||||||
|
|
||||||
toUpdateAccount := a.getToUpdateAccount(uid, &accountModifyReq, mainAccount, false)
|
toUpdateAccount, err := a.getToUpdateAccount(user, &accountModifyReq, mainAccount, false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
if toUpdateAccount != nil {
|
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
|
anythingUpdate = true
|
||||||
toUpdateAccounts = append(toUpdateAccounts, toUpdateAccount)
|
toUpdateAccounts = append(toUpdateAccounts, toUpdateAccount)
|
||||||
}
|
}
|
||||||
@@ -472,7 +499,11 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
|||||||
toAddAccountBalanceTimes = append(toAddAccountBalanceTimes, 0)
|
toAddAccountBalanceTimes = append(toAddAccountBalanceTimes, 0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toUpdateSubAccount := a.getToUpdateAccount(uid, subAccountReq, accountMap[subAccountReq.Id], true)
|
toUpdateSubAccount, err := a.getToUpdateAccount(user, subAccountReq, accountMap[subAccountReq.Id], true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
if toUpdateSubAccount != nil {
|
if toUpdateSubAccount != nil {
|
||||||
anythingUpdate = true
|
anythingUpdate = true
|
||||||
@@ -521,7 +552,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 {
|
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())
|
log.Errorf(c, "[accounts.AccountModifyHandler] failed to update account \"id:%d\" for user \"uid:%d\", because %s", accountModifyReq.Id, uid, err.Error())
|
||||||
@@ -542,7 +573,6 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
|||||||
|
|
||||||
account.Type = oldAccount.Type
|
account.Type = oldAccount.Type
|
||||||
account.ParentAccountId = oldAccount.ParentAccountId
|
account.ParentAccountId = oldAccount.ParentAccountId
|
||||||
account.DisplayOrder = oldAccount.DisplayOrder
|
|
||||||
account.Currency = oldAccount.Currency
|
account.Currency = oldAccount.Currency
|
||||||
account.Balance = oldAccount.Balance
|
account.Balance = oldAccount.Balance
|
||||||
|
|
||||||
@@ -597,6 +627,69 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
|
|||||||
return accountResp, nil
|
return accountResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AccountUpdateLastReconciledTimeHandler updates last reconciled time of an existed account by request parameters for current user
|
||||||
|
func (a *AccountsApi) AccountUpdateLastReconciledTimeHandler(c *core.WebContext) (any, *errs.Error) {
|
||||||
|
var accountUpdateReq models.AccountUpdateLastReconciledTimeRequest
|
||||||
|
err := c.ShouldBindJSON(&accountUpdateReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf(c, "[accounts.AccountUpdateLastReconciledTimeHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountUpdateReq.Id <= 0 {
|
||||||
|
return nil, errs.ErrAccountIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
user, err := a.users.GetUserById(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsCustomError(err) {
|
||||||
|
log.Errorf(c, "[accounts.AccountUpdateLastReconciledTimeHandler] failed to get user, because %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errs.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.UseLastReconciledTime {
|
||||||
|
return nil, errs.ErrLastReconciledTimeIsNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := a.accounts.GetAccountByAccountId(c, uid, accountUpdateReq.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(c, "[accounts.AccountUpdateLastReconciledTimeHandler] failed to get account \"id:%d\" for user \"uid:%d\", because %s", accountUpdateReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
|
||||||
|
return nil, errs.ErrParentAccountCannotSetLastReconciledTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Extend == nil {
|
||||||
|
account.Extend = &models.AccountExtend{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Extend.LastReconciledTime != nil && accountUpdateReq.LastReconciledTime < *account.Extend.LastReconciledTime {
|
||||||
|
return nil, errs.ErrCannotSetLastReconciledTimeBeforeCurrent
|
||||||
|
} else if account.Extend.LastReconciledTime != nil && accountUpdateReq.LastReconciledTime == *account.Extend.LastReconciledTime {
|
||||||
|
return nil, errs.ErrNothingWillBeUpdated
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Extend.LastReconciledTime = &accountUpdateReq.LastReconciledTime
|
||||||
|
|
||||||
|
err = a.accounts.UpdateAccountExtend(c, uid, account)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(c, "[accounts.AccountUpdateLastReconciledTimeHandler] failed to update last reconciled time for account \"id:%d\" of user \"uid:%d\", because %s", account.AccountId, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof(c, "[accounts.AccountUpdateLastReconciledTimeHandler] user \"uid:%d\" has updated last reconciled time \"%d\" for account \"id:%d\"", uid, account.Extend.LastReconciledTime, account.AccountId)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AccountHideHandler hides an existed account by request parameters for current user
|
// AccountHideHandler hides an existed account by request parameters for current user
|
||||||
func (a *AccountsApi) AccountHideHandler(c *core.WebContext) (any, *errs.Error) {
|
func (a *AccountsApi) AccountHideHandler(c *core.WebContext) (any, *errs.Error) {
|
||||||
var accountHideReq models.AccountHideRequest
|
var accountHideReq models.AccountHideRequest
|
||||||
@@ -754,23 +847,25 @@ func (a *AccountsApi) createSubAccountModels(uid int64, accountCreateReq *models
|
|||||||
return childrenAccounts, childrenAccountBalanceTimes
|
return childrenAccounts, childrenAccountBalanceTimes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.AccountModifyRequest, oldAccount *models.Account, isSubAccount bool) *models.Account {
|
func (a *AccountsApi) getToUpdateAccount(user *models.User, accountModifyReq *models.AccountModifyRequest, oldAccount *models.Account, isSubAccount bool) (*models.Account, error) {
|
||||||
newAccountExtend := &models.AccountExtend{}
|
newAccountExtend := &models.AccountExtend{}
|
||||||
|
newAccountExtend.LastReconciledTime = accountModifyReq.LastReconciledTime
|
||||||
|
|
||||||
if !isSubAccount && accountModifyReq.Category == models.ACCOUNT_CATEGORY_CREDIT_CARD {
|
if !isSubAccount && accountModifyReq.Category == models.ACCOUNT_CATEGORY_CREDIT_CARD {
|
||||||
newAccountExtend.CreditCardStatementDate = &accountModifyReq.CreditCardStatementDate
|
newAccountExtend.CreditCardStatementDate = &accountModifyReq.CreditCardStatementDate
|
||||||
}
|
}
|
||||||
|
|
||||||
newAccount := &models.Account{
|
newAccount := &models.Account{
|
||||||
AccountId: oldAccount.AccountId,
|
AccountId: oldAccount.AccountId,
|
||||||
Uid: uid,
|
Uid: user.Uid,
|
||||||
Name: accountModifyReq.Name,
|
Name: accountModifyReq.Name,
|
||||||
Category: accountModifyReq.Category,
|
DisplayOrder: oldAccount.DisplayOrder,
|
||||||
Icon: accountModifyReq.Icon,
|
Category: accountModifyReq.Category,
|
||||||
Color: accountModifyReq.Color,
|
Icon: accountModifyReq.Icon,
|
||||||
Comment: accountModifyReq.Comment,
|
Color: accountModifyReq.Color,
|
||||||
Extend: newAccountExtend,
|
Comment: accountModifyReq.Comment,
|
||||||
Hidden: accountModifyReq.Hidden,
|
Extend: newAccountExtend,
|
||||||
|
Hidden: accountModifyReq.Hidden,
|
||||||
}
|
}
|
||||||
|
|
||||||
if newAccount.Name != oldAccount.Name ||
|
if newAccount.Name != oldAccount.Name ||
|
||||||
@@ -779,21 +874,28 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
|
|||||||
newAccount.Color != oldAccount.Color ||
|
newAccount.Color != oldAccount.Color ||
|
||||||
newAccount.Comment != oldAccount.Comment ||
|
newAccount.Comment != oldAccount.Comment ||
|
||||||
newAccount.Hidden != oldAccount.Hidden {
|
newAccount.Hidden != oldAccount.Hidden {
|
||||||
return newAccount
|
return newAccount, nil
|
||||||
}
|
|
||||||
|
|
||||||
if (newAccount.Extend != nil && oldAccount.Extend == nil) ||
|
|
||||||
(newAccount.Extend == nil && oldAccount.Extend != nil) {
|
|
||||||
return newAccount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oldAccountExtend := oldAccount.Extend
|
oldAccountExtend := oldAccount.Extend
|
||||||
|
|
||||||
if newAccountExtend.CreditCardStatementDate != oldAccountExtend.CreditCardStatementDate {
|
if (newAccountExtend.LastReconciledTime != nil && (oldAccountExtend == nil || oldAccountExtend.LastReconciledTime == nil)) ||
|
||||||
return newAccount
|
(newAccountExtend.LastReconciledTime == nil && oldAccountExtend != nil && oldAccountExtend.LastReconciledTime != nil) ||
|
||||||
|
(newAccountExtend.LastReconciledTime != nil && oldAccountExtend != nil && oldAccountExtend.LastReconciledTime != nil && *newAccountExtend.LastReconciledTime != *oldAccountExtend.LastReconciledTime) {
|
||||||
|
if !user.UseLastReconciledTime {
|
||||||
|
return nil, errs.ErrLastReconciledTimeIsNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return newAccount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if (newAccountExtend.CreditCardStatementDate != nil && (oldAccountExtend == nil || oldAccountExtend.CreditCardStatementDate == nil)) ||
|
||||||
|
(newAccountExtend.CreditCardStatementDate == nil && oldAccountExtend != nil && oldAccountExtend.CreditCardStatementDate != nil) ||
|
||||||
|
(newAccountExtend.CreditCardStatementDate != nil && oldAccountExtend != nil && oldAccountExtend.CreditCardStatementDate != nil && *newAccountExtend.CreditCardStatementDate != *oldAccountExtend.CreditCardStatementDate) {
|
||||||
|
return newAccount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AccountsApi) getToDeleteSubAccountIds(accountModifyReq *models.AccountModifyRequest, mainAccount *models.Account, accountAndSubAccounts []*models.Account) []int64 {
|
func (a *AccountsApi) getToDeleteSubAccountIds(accountModifyReq *models.AccountModifyRequest, mainAccount *models.Account, accountAndSubAccounts []*models.Account) []int64 {
|
||||||
|
|||||||
+47
-23
@@ -28,9 +28,11 @@ type DataManagementsApi struct {
|
|||||||
transactions *services.TransactionService
|
transactions *services.TransactionService
|
||||||
categories *services.TransactionCategoryService
|
categories *services.TransactionCategoryService
|
||||||
tags *services.TransactionTagService
|
tags *services.TransactionTagService
|
||||||
|
tagGroups *services.TransactionTagGroupService
|
||||||
pictures *services.TransactionPictureService
|
pictures *services.TransactionPictureService
|
||||||
templates *services.TransactionTemplateService
|
templates *services.TransactionTemplateService
|
||||||
userCustomExchangeRates *services.UserCustomExchangeRatesService
|
userCustomExchangeRates *services.UserCustomExchangeRatesService
|
||||||
|
insightsExploreres *services.InsightsExplorerService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a data management api singleton instance
|
// Initialize a data management api singleton instance
|
||||||
@@ -45,9 +47,11 @@ var (
|
|||||||
transactions: services.Transactions,
|
transactions: services.Transactions,
|
||||||
categories: services.TransactionCategories,
|
categories: services.TransactionCategories,
|
||||||
tags: services.TransactionTags,
|
tags: services.TransactionTags,
|
||||||
|
tagGroups: services.TransactionTagGroups,
|
||||||
pictures: services.TransactionPictures,
|
pictures: services.TransactionPictures,
|
||||||
templates: services.TransactionTemplates,
|
templates: services.TransactionTemplates,
|
||||||
userCustomExchangeRates: services.UserCustomExchangeRates,
|
userCustomExchangeRates: services.UserCustomExchangeRates,
|
||||||
|
insightsExploreres: services.InsightsExplorers,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,6 +103,13 @@ func (a *DataManagementsApi) DataStatisticsHandler(c *core.WebContext) (any, *er
|
|||||||
return nil, errs.ErrOperationFailed
|
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)
|
totalTransactionTemplateCount, err := a.templates.GetTotalNormalTemplateCountByUid(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -119,6 +130,7 @@ func (a *DataManagementsApi) DataStatisticsHandler(c *core.WebContext) (any, *er
|
|||||||
TotalTransactionTagCount: totalTransactionTagCount,
|
TotalTransactionTagCount: totalTransactionTagCount,
|
||||||
TotalTransactionCount: totalTransactionCount,
|
TotalTransactionCount: totalTransactionCount,
|
||||||
TotalTransactionPictureCount: totalTransactionPictureCount,
|
TotalTransactionPictureCount: totalTransactionPictureCount,
|
||||||
|
TotalInsightsExplorerCount: totalInsightsExplorerCount,
|
||||||
TotalTransactionTemplateCount: totalTransactionTemplateCount,
|
TotalTransactionTemplateCount: totalTransactionTemplateCount,
|
||||||
TotalScheduledTransactionCount: totalScheduledTransactionCount,
|
TotalScheduledTransactionCount: totalScheduledTransactionCount,
|
||||||
}
|
}
|
||||||
@@ -183,6 +195,13 @@ func (a *DataManagementsApi) ClearAllDataHandler(c *core.WebContext) (any, *errs
|
|||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
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)
|
err = a.userCustomExchangeRates.DeleteAllCustomExchangeRates(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -190,6 +209,13 @@ func (a *DataManagementsApi) ClearAllDataHandler(c *core.WebContext) (any, *errs
|
|||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
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)
|
log.Infof(c, "[data_managements.ClearAllDataHandler] user \"uid:%d\" has cleared all data", uid)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -298,17 +324,15 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
|||||||
err := c.ShouldBindQuery(&exportTransactionDataReq)
|
err := c.ShouldBindQuery(&exportTransactionDataReq)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
return nil, "", errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
timezone := time.Local
|
clientTimezone, err := c.GetClientTimezone()
|
||||||
utcOffset, err := c.GetClientTimezoneOffset()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf(c, "[data_managements.ExportDataHandler] cannot get client timezone offset, because %s", err.Error())
|
log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone, because %s", err.Error())
|
||||||
} else {
|
clientTimezone = time.Local
|
||||||
timezone = time.FixedZone("Client Timezone", int(utcOffset)*60)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
@@ -316,7 +340,7 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errs.IsCustomError(err) {
|
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
|
return nil, "", errs.ErrUserNotFound
|
||||||
@@ -329,28 +353,28 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
|||||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
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
|
return nil, "", errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
categories, err := a.categories.GetAllCategoriesByUid(c, uid, 0, -1)
|
categories, err := a.categories.GetAllCategoriesByUid(c, uid, 0, -1)
|
||||||
|
|
||||||
if err != nil {
|
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
|
return nil, "", errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, err := a.tags.GetAllTagsByUid(c, uid)
|
tags, err := a.tags.GetAllTagsByUid(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
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
|
return nil, "", errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
tagIndexes, err := a.tags.GetAllTagIdsMapOfAllTransactions(c, uid)
|
tagIndexes, err := a.tags.GetAllTagIdsMapOfAllTransactions(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
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
|
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)
|
allAccountIds, err := a.accounts.GetAccountOrSubAccountIds(c, exportTransactionDataReq.AccountIds, uid)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
allCategoryIds, err := a.categories.GetCategoryOrSubCategoryIds(c, exportTransactionDataReq.CategoryIds, uid)
|
allCategoryIds, err := a.categories.GetCategoryOrSubCategoryIds(c, exportTransactionDataReq.CategoryIds, uid)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
var allTagIds []int64
|
noTags := exportTransactionDataReq.TagFilter == models.TransactionNoTagFilterValue
|
||||||
noTags := exportTransactionDataReq.TagIds == "none"
|
var tagFilters []*models.TransactionTagFilter
|
||||||
|
|
||||||
if !noTags {
|
if !noTags {
|
||||||
allTagIds, err = a.tags.GetTagIds(exportTransactionDataReq.TagIds)
|
tagFilters, err = models.ParseTransactionTagFilter(exportTransactionDataReq.TagFilter)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,10 +419,10 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
|||||||
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(exportTransactionDataReq.MinTime)
|
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, false, pageCountForDataExport, true)
|
||||||
|
|
||||||
if err != nil {
|
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
|
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)
|
result, err := dataExporter.ToExportedContent(c, uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexes)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := a.getFileName(user, timezone, fileType)
|
fileName := a.getFileName(user, clientTimezone, fileType)
|
||||||
|
|
||||||
return result, fileName, nil
|
return result, fileName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *DataManagementsApi) getFileName(user *models.User, timezone *time.Location, fileExtension string) string {
|
func (a *DataManagementsApi) getFileName(user *models.User, clientTimezone *time.Location, fileExtension string) string {
|
||||||
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone)
|
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), clientTimezone)
|
||||||
currentTime = strings.Replace(currentTime, "-", "_", -1)
|
currentTime = strings.Replace(currentTime, "-", "_", -1)
|
||||||
currentTime = strings.Replace(currentTime, " ", "_", -1)
|
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
|
||||||
|
}
|
||||||
@@ -166,7 +166,7 @@ func (a *ForgetPasswordsApi) UserResetPasswordHandler(c *core.WebContext) (any,
|
|||||||
Password: request.Password,
|
Password: request.Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = a.users.UpdateUser(c, userNew, false)
|
_, _, err = a.users.UpdateUser(c, userNew, false, false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(c, "[forget_passwords.UserResetPasswordHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
log.Errorf(c, "[forget_passwords.UserResetPasswordHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
|||||||
+2
-3
@@ -3,7 +3,6 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HealthsApi represents health api
|
// HealthsApi represents health api
|
||||||
@@ -18,8 +17,8 @@ var (
|
|||||||
func (a *HealthsApi) HealthStatusHandler(c *core.WebContext) (any, *errs.Error) {
|
func (a *HealthsApi) HealthStatusHandler(c *core.WebContext) (any, *errs.Error) {
|
||||||
result := make(map[string]string)
|
result := make(map[string]string)
|
||||||
|
|
||||||
result["version"] = settings.Version
|
result["version"] = core.Version
|
||||||
result["commit"] = settings.CommitHash
|
result["commit"] = core.CommitHash
|
||||||
result["status"] = "ok"
|
result["status"] = "ok"
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|||||||
@@ -47,14 +47,13 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
|
|||||||
return nil, errs.ErrLargeLanguageModelProviderNotEnabled
|
return nil, errs.ErrLargeLanguageModelProviderNotEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
utcOffset, err := c.GetClientTimezoneOffset()
|
clientTimezone, err := c.GetClientTimezone()
|
||||||
|
|
||||||
if err != nil {
|
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
|
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
timezone := time.FixedZone("Client Timezone", int(utcOffset)*60)
|
|
||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
user, err := a.users.GetUserById(c, uid)
|
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)
|
systemPrompt, err := templates.GetTemplate(templates.SYSTEM_PROMPT_RECEIPT_IMAGE_RECOGNITION)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
systemPromptParams := map[string]any{
|
systemPromptParams := map[string]any{
|
||||||
"CurrentDateTime": utils.FormatUnixTimeToLongDateTime(time.Now().Unix(), timezone),
|
"CurrentDateTime": utils.FormatUnixTimeToLongDateTime(time.Now().Unix(), clientTimezone),
|
||||||
"AllExpenseCategoryNames": strings.Join(expenseCategoryNames, "\n"),
|
"AllExpenseCategoryNames": strings.Join(expenseCategoryNames, "\n"),
|
||||||
"AllIncomeCategoryNames": strings.Join(incomeCategoryNames, "\n"),
|
"AllIncomeCategoryNames": strings.Join(incomeCategoryNames, "\n"),
|
||||||
"AllTransferCategoryNames": strings.Join(transferCategoryNames, "\n"),
|
"AllTransferCategoryNames": strings.Join(transferCategoryNames, "\n"),
|
||||||
@@ -208,6 +208,7 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
|
|||||||
err = systemPrompt.Execute(&bodyBuffer, systemPromptParams)
|
err = systemPrompt.Execute(&bodyBuffer, systemPromptParams)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
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)
|
llmResponse, err := llm.Container.GetJsonResponseByReceiptImageRecognitionModel(c, c.GetCurrentUid(), a.CurrentConfig(), llmRequest)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
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 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{
|
recognizedReceiptImageResponse := &models.RecognizedReceiptImageResponse{
|
||||||
Type: models.TRANSACTION_TYPE_EXPENSE,
|
Type: models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
@@ -288,7 +290,7 @@ func (a *LargeLanguageModelsApi) parseRecognizedReceiptImageResponse(c *core.Web
|
|||||||
|
|
||||||
if len(recognizedResult.Time) > 0 {
|
if len(recognizedResult.Time) > 0 {
|
||||||
longDateTime := a.getLongDateTime(recognizedResult.Time)
|
longDateTime := a.getLongDateTime(recognizedResult.Time)
|
||||||
timestamp, err := utils.ParseFromLongDateTime(longDateTime, utcOffset)
|
timestamp, err := utils.ParseFromLongDateTimeInTimeZone(longDateTime, clientTimezone)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf(c, "[large_language_models.parseRecognizedReceiptImageResponse] recoginzed time \"%s\" is invalid", recognizedResult.Time)
|
log.Warnf(c, "[large_language_models.parseRecognizedReceiptImageResponse] recoginzed time \"%s\" is invalid", recognizedResult.Time)
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"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
|
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
|
// MapImageProxy represents map image proxy
|
||||||
type MapImageProxy struct {
|
type MapImageProxy struct {
|
||||||
ApiUsingConfig
|
ApiUsingConfig
|
||||||
|
mutex sync.Mutex
|
||||||
|
transport *http.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a map image proxy singleton instance
|
// 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
|
// MapTileImageProxyHandler returns map tile image
|
||||||
func (p *MapImageProxy) MapTileImageProxyHandler(c *core.WebContext) (*httputil.ReverseProxy, *errs.Error) {
|
func (p *MapImageProxy) MapTileImageProxyHandler(c *core.WebContext) (*httputil.ReverseProxy, *errs.Error) {
|
||||||
return p.mapImageProxyHandler(c, func(c *core.WebContext, mapProvider string) (string, *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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
if p.transport == nil {
|
||||||
utils.SetProxyUrl(transport, p.CurrentConfig().MapProxy)
|
p.initializeHttpTransport()
|
||||||
|
}
|
||||||
|
|
||||||
director := func(req *http.Request) {
|
director := func(req *http.Request) {
|
||||||
imageRawUrl := targetUrl
|
imageRawUrl := targetUrl
|
||||||
@@ -126,7 +142,7 @@ func (p *MapImageProxy) mapImageProxyHandler(c *core.WebContext, fn func(c *core
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &httputil.ReverseProxy{
|
return &httputil.ReverseProxy{
|
||||||
Transport: transport,
|
Transport: p.transport,
|
||||||
Director: director,
|
Director: director,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const mcpServerName = "ezBookkeeping-mcp"
|
const mcpServerName = core.ApplicationName + "-mcp"
|
||||||
|
|
||||||
// ModelContextProtocolAPI represents model context protocol api
|
// ModelContextProtocolAPI represents model context protocol api
|
||||||
type ModelContextProtocolAPI struct {
|
type ModelContextProtocolAPI struct {
|
||||||
@@ -102,8 +102,8 @@ func (a *ModelContextProtocolAPI) InitializeHandler(c *core.WebContext, jsonRPCR
|
|||||||
},
|
},
|
||||||
ServerInfo: &mcp.MCPImplementation{
|
ServerInfo: &mcp.MCPImplementation{
|
||||||
Name: mcpServerName,
|
Name: mcpServerName,
|
||||||
Title: a.CurrentConfig().AppName,
|
Title: core.ApplicationName,
|
||||||
Version: settings.Version,
|
Version: core.Version,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
const oauth2CallbackPageUrlSuccessFormat = "%sdesktop/#/oauth2_callback?platform=%s&provider=%s&token=%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 oauth2CallbackPageUrlNeedVerifyFormat = "%sdesktop#/oauth2_callback?platform=%s&provider=%s&userName=%s&token=%s"
|
||||||
const oauth2CallbackPageUrlFailedFormat = "%sdesktop/#/oauth2_callback?errorCode=%d&errorMessage=%s"
|
const oauth2CallbackPageUrlFailedFormat = "%sdesktop#/oauth2_callback?errorCode=%d&errorMessage=%s"
|
||||||
const oauth2CallbackPageUrlErrorMessageFormat = "%sdesktop/#/oauth2_callback?errorMessage=%s"
|
const oauth2CallbackPageUrlErrorMessageFormat = "%sdesktop#/oauth2_callback?errorMessage=%s"
|
||||||
|
|
||||||
// OAuth2AuthenticationApi represents OAuth 2.0 authorization api
|
// OAuth2AuthenticationApi represents OAuth 2.0 authorization api
|
||||||
type OAuth2AuthenticationApi struct {
|
type OAuth2AuthenticationApi struct {
|
||||||
@@ -208,9 +208,20 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
|||||||
return a.redirectToFailedCallbackPage(c, errs.ErrCannotRetrieveUserInfo)
|
return a.redirectToFailedCallbackPage(c, errs.ErrCannotRetrieveUserInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oauth2UserInfo.UserName == "" || oauth2UserInfo.Email == "" {
|
log.Infof(c, "[oauth2_authentications.CallbackHandler] oauth 2.0 user info, userName: %s, email: %s", 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)
|
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()
|
userExternalAuthType := oauth2.GetExternalUserAuthType()
|
||||||
@@ -221,7 +232,7 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
|||||||
} else if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername {
|
} else if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername {
|
||||||
userExternalAuth, err = a.userExternalAuths.GetUserExternalAuthByExternalUserName(c, oauth2UserInfo.UserName, userExternalAuthType)
|
userExternalAuth, err = a.userExternalAuths.GetUserExternalAuthByExternalUserName(c, oauth2UserInfo.UserName, userExternalAuthType)
|
||||||
} else {
|
} else {
|
||||||
userExternalAuth, err = a.userExternalAuths.GetUserExternalAuthByExternalEmail(c, oauth2UserInfo.Email, userExternalAuthType)
|
return a.redirectToFailedCallbackPage(c, errs.ErrNotSupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, errs.ErrUserExternalAuthNotFound) {
|
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 {
|
} else if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername {
|
||||||
user, err = a.users.GetUserByUsername(c, oauth2UserInfo.UserName)
|
user, err = a.users.GetUserByUsername(c, oauth2UserInfo.UserName)
|
||||||
} else {
|
} else {
|
||||||
user, err = a.users.GetUserByEmail(c, oauth2UserInfo.Email)
|
err = errs.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, errs.ErrUserNotFound) {
|
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 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)
|
userName := strings.TrimSpace(oauth2UserInfo.UserName)
|
||||||
email := strings.TrimSpace(oauth2UserInfo.Email)
|
email := strings.TrimSpace(oauth2UserInfo.Email)
|
||||||
nickName := strings.TrimSpace(oauth2UserInfo.NickName)
|
nickName := strings.TrimSpace(oauth2UserInfo.NickName)
|
||||||
|
|||||||
+4
-5
@@ -3,7 +3,6 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemsApi represents system api
|
// SystemsApi represents system api
|
||||||
@@ -18,11 +17,11 @@ var (
|
|||||||
func (a *SystemsApi) VersionHandler(c *core.WebContext) (any, *errs.Error) {
|
func (a *SystemsApi) VersionHandler(c *core.WebContext) (any, *errs.Error) {
|
||||||
result := make(map[string]string)
|
result := make(map[string]string)
|
||||||
|
|
||||||
result["version"] = settings.Version
|
result["version"] = core.Version
|
||||||
result["commitHash"] = settings.CommitHash
|
result["commitHash"] = core.CommitHash
|
||||||
|
|
||||||
if settings.BuildTime != "" {
|
if core.BuildTime != "" {
|
||||||
result["buildTime"] = settings.BuildTime
|
result["buildTime"] = core.BuildTime
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|||||||
+4
-4
@@ -69,10 +69,10 @@ func (a *TokensApi) TokenListHandler(c *core.WebContext) (any, *errs.Error) {
|
|||||||
tokenResp.IsCurrent = true
|
tokenResp.IsCurrent = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.TokenType == core.USER_TOKEN_TYPE_API && token.UserAgent != services.TokenUserAgentCreatedViaCli {
|
if token.TokenType == core.USER_TOKEN_TYPE_API && token.UserAgent != core.TokenUserAgentCreatedViaCli {
|
||||||
tokenResp.UserAgent = services.TokenUserAgentForAPI
|
tokenResp.UserAgent = core.TokenUserAgentForAPI
|
||||||
} else if token.TokenType == core.USER_TOKEN_TYPE_MCP && token.UserAgent != services.TokenUserAgentCreatedViaCli {
|
} else if token.TokenType == core.USER_TOKEN_TYPE_MCP && token.UserAgent != core.TokenUserAgentCreatedViaCli {
|
||||||
tokenResp.UserAgent = services.TokenUserAgentForMCP
|
tokenResp.UserAgent = core.TokenUserAgentForMCP
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenResps[i] = tokenResp
|
tokenResps[i] = tokenResp
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.WebContext) (an
|
|||||||
Uid: uid,
|
Uid: uid,
|
||||||
ParentCategoryId: categoryModifyReq.ParentId,
|
ParentCategoryId: categoryModifyReq.ParentId,
|
||||||
Name: categoryModifyReq.Name,
|
Name: categoryModifyReq.Name,
|
||||||
|
DisplayOrder: category.DisplayOrder,
|
||||||
Icon: categoryModifyReq.Icon,
|
Icon: categoryModifyReq.Icon,
|
||||||
Color: categoryModifyReq.Color,
|
Color: categoryModifyReq.Color,
|
||||||
Comment: categoryModifyReq.Comment,
|
Comment: categoryModifyReq.Comment,
|
||||||
@@ -259,6 +260,15 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.WebContext) (an
|
|||||||
if toPrimaryCategory.ParentCategoryId != models.LevelOneTransactionCategoryParentId {
|
if toPrimaryCategory.ParentCategoryId != models.LevelOneTransactionCategoryParentId {
|
||||||
return nil, errs.Or(err, errs.ErrNotAllowUseSecondaryTransactionAsPrimaryCategory)
|
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)
|
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)
|
log.Infof(c, "[transaction_categories.CategoryModifyHandler] user \"uid:%d\" has updated category \"id:%d\" successfully", uid, categoryModifyReq.Id)
|
||||||
|
|
||||||
newCategory.Type = category.Type
|
newCategory.Type = category.Type
|
||||||
newCategory.DisplayOrder = category.DisplayOrder
|
|
||||||
categoryResp := newCategory.ToTransactionCategoryInfoResponse()
|
categoryResp := newCategory.ToTransactionCategoryInfoResponse()
|
||||||
|
|
||||||
return categoryResp, nil
|
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
|
// TransactionTagsApi represents transaction tag api
|
||||||
type TransactionTagsApi struct {
|
type TransactionTagsApi struct {
|
||||||
tags *services.TransactionTagService
|
tags *services.TransactionTagService
|
||||||
|
tagGroups *services.TransactionTagGroupService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a transaction tag api singleton instance
|
// Initialize a transaction tag api singleton instance
|
||||||
var (
|
var (
|
||||||
TransactionTags = &TransactionTagsApi{
|
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()
|
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 {
|
if err != nil {
|
||||||
log.Errorf(c, "[transaction_tags.TagCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
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)
|
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()
|
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 {
|
if err != nil {
|
||||||
log.Errorf(c, "[transaction_tags.TagCreateBatchHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
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)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
newTag := &models.TransactionTag{
|
if tagModifyReq.GroupId != tag.TagGroupId && tagModifyReq.GroupId > 0 {
|
||||||
TagId: tag.TagId,
|
tagGroup, err := a.tagGroups.GetTagGroupByTagGroupId(c, uid, tagModifyReq.GroupId)
|
||||||
Uid: uid,
|
|
||||||
Name: tagModifyReq.Name,
|
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
|
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 {
|
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())
|
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)
|
log.Infof(c, "[transaction_tags.TagModifyHandler] user \"uid:%d\" has updated tag \"id:%d\" successfully", uid, tagModifyReq.Id)
|
||||||
|
|
||||||
tag.Name = newTag.Name
|
tag.Name = newTag.Name
|
||||||
|
tag.TagGroupId = newTag.TagGroupId
|
||||||
|
tag.DisplayOrder = newTag.DisplayOrder
|
||||||
tagResp := tag.ToTransactionTagInfoResponse()
|
tagResp := tag.ToTransactionTagInfoResponse()
|
||||||
|
|
||||||
return tagResp, nil
|
return tagResp, nil
|
||||||
@@ -268,6 +336,7 @@ func (a *TransactionTagsApi) createNewTagModel(uid int64, tagCreateReq *models.T
|
|||||||
return &models.TransactionTag{
|
return &models.TransactionTag{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
Name: tagCreateReq.Name,
|
Name: tagCreateReq.Name,
|
||||||
|
TagGroupId: tagCreateReq.GroupId,
|
||||||
DisplayOrder: order,
|
DisplayOrder: order,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,6 +347,7 @@ func (a *TransactionTagsApi) createNewTagModels(uid int64, tagCreateBatchReq *mo
|
|||||||
for i := 0; i < len(tagCreateBatchReq.Tags); i++ {
|
for i := 0; i < len(tagCreateBatchReq.Tags); i++ {
|
||||||
tagCreateReq := tagCreateBatchReq.Tags[i]
|
tagCreateReq := tagCreateBatchReq.Tags[i]
|
||||||
tag := a.createNewTagModel(uid, tagCreateReq, order+int32(i))
|
tag := a.createNewTagModel(uid, tagCreateReq, order+int32(i))
|
||||||
|
tag.TagGroupId = tagCreateBatchReq.GroupId
|
||||||
tags[i] = tag
|
tags[i] = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1093
-106
File diff suppressed because it is too large
Load Diff
@@ -85,7 +85,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.WebCo
|
|||||||
return nil, errs.ErrNotPermittedToPerformThisAction
|
return nil, errs.ErrNotPermittedToPerformThisAction
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := a.twoFactorAuthorizations.GenerateTwoFactorSecret(c, user)
|
key, err := a.twoFactorAuthorizations.GenerateTwoFactorSecret(c, user, c.GetClientLocale())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to generate two-factor secret, because %s", err.Error())
|
log.Errorf(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to generate two-factor secret, because %s", err.Error())
|
||||||
|
|||||||
+12
-1
@@ -256,6 +256,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
|
|||||||
userUpdateReq.Nickname = strings.TrimSpace(userUpdateReq.Nickname)
|
userUpdateReq.Nickname = strings.TrimSpace(userUpdateReq.Nickname)
|
||||||
|
|
||||||
modifyProfileBasicInfo := false
|
modifyProfileBasicInfo := false
|
||||||
|
modifyUseLastReconciledTime := false
|
||||||
anythingUpdate := false
|
anythingUpdate := false
|
||||||
userNew := &models.User{
|
userNew := &models.User{
|
||||||
Uid: user.Uid,
|
Uid: user.Uid,
|
||||||
@@ -317,6 +318,16 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
|
|||||||
anythingUpdate = true
|
anythingUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if userUpdateReq.UseLastReconciledTime != nil && *userUpdateReq.UseLastReconciledTime != user.UseLastReconciledTime {
|
||||||
|
user.UseLastReconciledTime = *userUpdateReq.UseLastReconciledTime
|
||||||
|
userNew.UseLastReconciledTime = *userUpdateReq.UseLastReconciledTime
|
||||||
|
modifyProfileBasicInfo = true
|
||||||
|
modifyUseLastReconciledTime = true
|
||||||
|
anythingUpdate = true
|
||||||
|
} else {
|
||||||
|
modifyUseLastReconciledTime = false
|
||||||
|
}
|
||||||
|
|
||||||
if userUpdateReq.TransactionEditScope != nil && *userUpdateReq.TransactionEditScope != user.TransactionEditScope {
|
if userUpdateReq.TransactionEditScope != nil && *userUpdateReq.TransactionEditScope != user.TransactionEditScope {
|
||||||
user.TransactionEditScope = *userUpdateReq.TransactionEditScope
|
user.TransactionEditScope = *userUpdateReq.TransactionEditScope
|
||||||
userNew.TransactionEditScope = *userUpdateReq.TransactionEditScope
|
userNew.TransactionEditScope = *userUpdateReq.TransactionEditScope
|
||||||
@@ -531,7 +542,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
|
|||||||
return nil, errs.ErrNothingWillBeUpdated
|
return nil, errs.ErrNothingWillBeUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
keyProfileUpdated, emailSetToUnverified, err := a.users.UpdateUser(c, userNew, modifyUserLanguage)
|
keyProfileUpdated, emailSetToUnverified, err := a.users.UpdateUser(c, userNew, modifyUserLanguage, modifyUseLastReconciledTime)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(c, "[users.UserUpdateProfileHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
log.Errorf(c, "[users.UserUpdateProfileHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider/oidc"
|
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider/oidc"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OAuth2Container contains the current OAuth 2.0 authentication provider
|
// OAuth2Container contains the current OAuth 2.0 authentication provider
|
||||||
@@ -67,7 +67,7 @@ func InitializeOAuth2Provider(config *settings.Config) error {
|
|||||||
|
|
||||||
Container.current = oauth2Provider
|
Container.current = oauth2Provider
|
||||||
Container.usePKCE = config.OAuth2UsePKCE
|
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
|
Container.externalUserAuthType = externalUserAuthType
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"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))
|
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)
|
resp, err := oauth2Client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,8 +75,6 @@ func (p *CommonOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
log.Debugf(c, "[common_oauth2_provider.GetUserInfo] response is %s", body)
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Errorf(c, "[common_oauth2_provider.GetUserInfo] failed to get user info response, because response code is %d", resp.StatusCode)
|
log.Errorf(c, "[common_oauth2_provider.GetUserInfo] failed to get user info response, because response code is %d", resp.StatusCode)
|
||||||
return nil, errs.ErrFailedToRequestRemoteApi
|
return nil, errs.ErrFailedToRequestRemoteApi
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"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))
|
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)
|
resp, err := oauth2Client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,8 +77,6 @@ func (p *GithubOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
log.Debugf(c, "[github_oauth2_provider.GetUserInfo] user profile response is %s", body)
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Errorf(c, "[github_oauth2_provider.GetUserInfo] failed to get user info response, because response code is %d", resp.StatusCode)
|
log.Errorf(c, "[github_oauth2_provider.GetUserInfo] failed to get user info response, because response code is %d", resp.StatusCode)
|
||||||
return nil, errs.ErrFailedToRequestRemoteApi
|
return nil, errs.ErrFailedToRequestRemoteApi
|
||||||
@@ -92,6 +96,10 @@ func (p *GithubOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
|||||||
return nil, errs.ErrFailedToRequestRemoteApi
|
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)
|
resp, err = oauth2Client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,8 +110,6 @@ func (p *GithubOAuth2Provider) GetUserInfo(c core.Context, oauth2Token *oauth2.T
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err = io.ReadAll(resp.Body)
|
body, err = io.ReadAll(resp.Body)
|
||||||
|
|
||||||
log.Debugf(c, "[github_oauth2_provider.GetUserInfo] user emails response is %s", body)
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Errorf(c, "[github_oauth2_provider.GetUserInfo] failed to get user emails response, because response code is %d", resp.StatusCode)
|
log.Errorf(c, "[github_oauth2_provider.GetUserInfo] failed to get user emails response, because response code is %d", resp.StatusCode)
|
||||||
return nil, errs.ErrFailedToRequestRemoteApi
|
return nil, errs.ErrFailedToRequestRemoteApi
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
"github.com/mayswind/ezbookkeeping/pkg/auth/oauth2/provider"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/httpclient"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
)
|
)
|
||||||
@@ -18,6 +19,7 @@ import (
|
|||||||
// OIDCClaims represents OIDC claims
|
// OIDCClaims represents OIDC claims
|
||||||
type OIDCClaims struct {
|
type OIDCClaims struct {
|
||||||
PreferredUserName string `json:"preferred_username"`
|
PreferredUserName string `json:"preferred_username"`
|
||||||
|
UserName string `json:"username"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
@@ -92,7 +94,9 @@ func (p *OIDCProvider) GetUserInfo(c core.Context, oauth2Token *oauth2.Token) (*
|
|||||||
nickName := claims.Name
|
nickName := claims.Name
|
||||||
|
|
||||||
if userName == "" || email == "" || nickName == "" {
|
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 {
|
if err != nil {
|
||||||
log.Errorf(c, "[oidc_provider.GetUserInfo] failed to get user info, because %s", err.Error())
|
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
|
userName = claims.PreferredUserName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if userName == "" {
|
||||||
|
userName = claims.UserName
|
||||||
|
}
|
||||||
|
|
||||||
if email == "" {
|
if email == "" {
|
||||||
email = claims.Email
|
email = claims.Email
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters"
|
"github.com/mayswind/ezbookkeeping/pkg/converters"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
@@ -149,7 +150,7 @@ func (l *UserDataCli) ModifyUserPassword(c *core.CliContext, username string, pa
|
|||||||
Password: password,
|
Password: password,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = l.users.UpdateUser(c, userNew, false)
|
err = l.users.UpdateUserPassword(c, userNew)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.CliErrorf(c, "[user_data.ModifyUserPassword] failed to update user \"%s\" password, because %s", user.Username, err.Error())
|
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
|
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 {
|
if err != nil {
|
||||||
log.CliErrorf(c, "[user_data.ImportTransaction] failed to parse imported data for \"%s\", because %s", username, err.Error())
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/text/encoding/simplifiedchinese"
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
@@ -53,7 +54,7 @@ type alipayTransactionDataCsvFileImporter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseImportedData returns the imported data by parsing the alipay transaction csv data
|
// 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
|
enc := simplifiedchinese.GB18030
|
||||||
reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder())
|
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)
|
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, alipayTransactionSupportedColumns, transactionRowParser)
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(alipayTransactionTypeNameMapping)
|
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"
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -35,7 +36,7 @@ func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 4, len(allNewTransactions))
|
assert.Equal(t, 4, len(allNewTransactions))
|
||||||
@@ -94,7 +95,7 @@ func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -112,7 +113,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||||
@@ -132,7 +133,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||||
@@ -144,7 +145,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseInvestmentRefundTransaction(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseInvestmentRefundTransaction(t *testing.T) {
|
||||||
converter := AlipayAppTransactionDataCsvFileImporter
|
importer := AlipayAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
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 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")
|
"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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 2, len(allNewTransactions))
|
assert.Equal(t, 2, len(allNewTransactions))
|
||||||
@@ -184,7 +185,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvestmentRefundTransaction
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -201,7 +202,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
data2, err := simplifiedchinese.GB18030.NewEncoder().String("支付宝交易记录明细查询\n" +
|
data2, err := simplifiedchinese.GB18030.NewEncoder().String("支付宝交易记录明细查询\n" +
|
||||||
@@ -213,12 +214,12 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -235,12 +236,12 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -258,7 +259,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -274,7 +275,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -290,7 +291,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -307,7 +308,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -324,7 +325,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -341,7 +342,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -358,7 +359,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -367,7 +368,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
|
||||||
converter := AlipayAppTransactionDataCsvFileImporter
|
importer := AlipayAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -388,7 +389,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
|
|||||||
"2024-09-01 23:59:59,Test Category3,充值-普通充值,不计收支,0.05,交易成功,\n")
|
"2024-09-01 23:59:59,Test Category3,充值-普通充值,不计收支,0.05,交易成功,\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 3, len(allNewTransactions))
|
assert.Equal(t, 3, len(allNewTransactions))
|
||||||
@@ -407,7 +408,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseRelatedAccount(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseRelatedAccount(t *testing.T) {
|
||||||
converter := AlipayAppTransactionDataCsvFileImporter
|
importer := AlipayAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
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")
|
"2024-09-01 08:00:00,Test Account4,信用卡还款,不计收支,0.01,Test Account,还款成功,repayment,\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 9, len(allNewTransactions))
|
assert.Equal(t, 9, len(allNewTransactions))
|
||||||
@@ -529,7 +530,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRelatedAccount(t *testing.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -546,7 +547,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -561,7 +562,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -569,7 +570,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_SkipClosedIncomeOrTransferTransaction(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_SkipClosedIncomeOrTransferTransaction(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -586,12 +587,12 @@ func TestAlipayCsvFileImporterParseImportedData_SkipClosedIncomeOrTransferTransa
|
|||||||
"2024-09-01 23:59:59 ,充值-普通充值 ,0.05 ,不计收支 ,交易关闭 ,\n" +
|
"2024-09-01 23:59:59 ,充值-普通充值 ,0.05 ,不计收支 ,交易关闭 ,\n" +
|
||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_SkipUnknownProductTransferTransaction(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_SkipUnknownProductTransferTransaction(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -607,12 +608,12 @@ func TestAlipayCsvFileImporterParseImportedData_SkipUnknownProductTransferTransa
|
|||||||
"2024-09-01 23:59:59 ,xxxx ,0.05 ,不计收支 ,交易成功 ,\n" +
|
"2024-09-01 23:59:59 ,xxxx ,0.05 ,不计收支 ,交易成功 ,\n" +
|
||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -628,12 +629,12 @@ func TestAlipayCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *
|
|||||||
"2024-09-01 01:23:45 ,xxxx ,0.12 ,收入 ,xxxx ,\n" +
|
"2024-09-01 01:23:45 ,xxxx ,0.12 ,收入 ,xxxx ,\n" +
|
||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -647,15 +648,15 @@ func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T)
|
|||||||
"------------------------------------------------------------------------------------\n")
|
"------------------------------------------------------------------------------------\n")
|
||||||
assert.Nil(t, err)
|
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)
|
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)
|
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -671,7 +672,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
|||||||
"金额(元),收/支 ,交易状态 ,\n" +
|
"金额(元),收/支 ,交易状态 ,\n" +
|
||||||
"0.12 ,收入 ,交易成功 ,\n" +
|
"0.12 ,收入 ,交易成功 ,\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.ErrMissingRequiredFieldInHeaderRow.Message)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Amount Column
|
// Missing Amount Column
|
||||||
@@ -682,7 +683,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
|||||||
"交易创建时间 ,收/支 ,交易状态 ,\n" +
|
"交易创建时间 ,收/支 ,交易状态 ,\n" +
|
||||||
"2024-09-01 12:34:56 ,收入 ,交易成功 ,\n" +
|
"2024-09-01 12:34:56 ,收入 ,交易成功 ,\n" +
|
||||||
"------------------------------------------------------------------------------------\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Status Column
|
// Missing Status Column
|
||||||
@@ -693,7 +694,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
|||||||
"交易创建时间 ,金额(元),收/支 ,\n" +
|
"交易创建时间 ,金额(元),收/支 ,\n" +
|
||||||
"2024-09-01 12:34:56 ,0.12 ,收入 ,\n" +
|
"2024-09-01 12:34:56 ,0.12 ,收入 ,\n" +
|
||||||
"------------------------------------------------------------------------------------\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Type Column
|
// Missing Type Column
|
||||||
@@ -704,12 +705,12 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
|||||||
"交易创建时间 ,金额(元),交易状态 ,\n" +
|
"交易创建时间 ,金额(元),交易状态 ,\n" +
|
||||||
"2024-09-01 12:34:56 ,0.12 ,交易成功 ,\n" +
|
"2024-09-01 12:34:56 ,0.12 ,交易成功 ,\n" +
|
||||||
"------------------------------------------------------------------------------------\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
importer := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -723,6 +724,6 @@ func TestAlipayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T)
|
|||||||
"---------------------------------交易记录明细列表------------------------------------\n" +
|
"---------------------------------交易记录明细列表------------------------------------\n" +
|
||||||
"交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" +
|
"交易创建时间 ,金额(元),收/支 ,交易状态 ,\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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
const alipayTransactionDataStatusSuccessName = "交易成功"
|
const alipayTransactionDataStatusSuccessName = "交易成功"
|
||||||
const alipayTransactionDataStatusPaymentSuccessName = "支付成功"
|
const alipayTransactionDataStatusPaymentSuccessName = "支付成功"
|
||||||
|
const alipayTransactionDataStatusPendingGoodsReceiptConfirmationName = "等待确认收货"
|
||||||
const alipayTransactionDataStatusRepaymentSuccessName = "还款成功"
|
const alipayTransactionDataStatusRepaymentSuccessName = "还款成功"
|
||||||
const alipayTransactionDataStatusClosedName = "交易关闭"
|
const alipayTransactionDataStatusClosedName = "交易关闭"
|
||||||
const alipayTransactionDataStatusRefundSuccessName = "退款成功"
|
const alipayTransactionDataStatusRefundSuccessName = "退款成功"
|
||||||
@@ -46,6 +47,7 @@ func (p *alipayTransactionDataRowParser) Parse(ctx core.Context, user *models.Us
|
|||||||
|
|
||||||
if dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusSuccessName &&
|
if dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusSuccessName &&
|
||||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusPaymentSuccessName &&
|
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusPaymentSuccessName &&
|
||||||
|
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusPendingGoodsReceiptConfirmationName &&
|
||||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusRepaymentSuccessName &&
|
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusRepaymentSuccessName &&
|
||||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusClosedName &&
|
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusClosedName &&
|
||||||
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusRefundSuccessName &&
|
dataRow.GetData(p.columns.statusColumnName) != alipayTransactionDataStatusRefundSuccessName &&
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package beancount
|
package beancount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -24,7 +26,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ParseImportedData returns the imported data by parsing the Beancount transaction data
|
// 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)
|
beancountDataReader, err := createNewBeancountDataReader(ctx, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -45,5 +47,5 @@ func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
|||||||
|
|
||||||
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(beancountTransactionTypeNameMapping, "", "", BEANCOUNT_TRANSACTION_TAG_SEPARATOR)
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -12,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -20,7 +22,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"2024-09-01 *\n"+
|
||||||
" Equity:Opening-Balances -123.45 CNY\n"+
|
" Equity:Opening-Balances -123.45 CNY\n"+
|
||||||
" Assets:TestAccount 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"+
|
" Expenses:TestCategory2 1.00 CNY\n"+
|
||||||
"2024-09-04 *\n"+
|
"2024-09-04 *\n"+
|
||||||
" Assets:TestAccount -0.05 CNY\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -91,7 +93,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -99,7 +101,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *test
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"2024-09-01 *\n"+
|
||||||
" Assets:TestAccount 123.45 CNY\n"+
|
" Assets:TestAccount 123.45 CNY\n"+
|
||||||
" Equity:Opening-Balances -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"+
|
" Assets:TestAccount -1.00 CNY\n"+
|
||||||
"2024-09-04 *\n"+
|
"2024-09-04 *\n"+
|
||||||
" Assets:TestAccount2 0.05 CNY\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -170,7 +172,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *test
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -178,15 +180,15 @@ func TestBeancountTransactionDataFileParseImportedData_ParseInvalidTime(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024/09/01 *\n"+
|
"2024/09/01 *\n"+
|
||||||
" Equity:Opening-Balances -123.45 CNY\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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -194,10 +196,10 @@ func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"2024-09-01 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||||
" Assets:TestAccount -0.12 USD\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -222,7 +224,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -230,21 +232,21 @@ func TestBeancountTransactionDataFileParseImportedData_ParseInvalidAmount(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 *\n"+
|
"2024-09-01 *\n"+
|
||||||
" Equity:Opening-Balances -abc CNY\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 *\n"+
|
"2024-09-01 *\n"+
|
||||||
" Equity:Opening-Balances -1/0 CNY\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -252,13 +254,13 @@ func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"2024-09-01 * \"foo bar\t#test\n\"\n"+
|
||||||
" Equity:Opening-Balances -123.45 CNY\n"+
|
" Equity:Opening-Balances -123.45 CNY\n"+
|
||||||
" Assets:TestAccount 123.45 CNY\n"+
|
" Assets:TestAccount 123.45 CNY\n"+
|
||||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||||
" Income:TestCategory -0.12 CNY\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -269,7 +271,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_InvalidTransaction(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_InvalidTransaction(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -277,33 +279,33 @@ func TestBeancountTransactionDataFileParseImportedData_InvalidTransaction(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||||
" Assets:TestAccount 0.11 CNY\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)
|
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"+
|
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||||
" Expenses:TestCategory -0.11 CNY\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)
|
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"+
|
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||||
" Income:TestCategory -0.11 CNY\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)
|
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"+
|
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||||
" Equity:TestCategory -0.11 CNY\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)
|
assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_NotSupportedToParseSplitTransaction(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_NotSupportedToParseSplitTransaction(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -311,16 +313,16 @@ func TestBeancountTransactionDataFileParseImportedData_NotSupportedToParseSplitT
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
|
||||||
" Assets:TestAccount -0.23 CNY\n"+
|
" Assets:TestAccount -0.23 CNY\n"+
|
||||||
" Assets:TestAccount2 0.11 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)
|
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBeancountTransactionDataFileParseImportedData_MissingTransactionRequiredData(t *testing.T) {
|
func TestBeancountTransactionDataFileParseImportedData_MissingTransactionRequiredData(t *testing.T) {
|
||||||
converter := BeancountTransactionDataImporter
|
importer := BeancountTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -329,30 +331,30 @@ func TestBeancountTransactionDataFileParseImportedData_MissingTransactionRequire
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Transaction Time
|
// Missing Transaction Time
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"* \"narration\"\n"+
|
"* \"narration\"\n"+
|
||||||
" Equity:Opening-Balances -123.45 CNY\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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
|
|
||||||
// Missing Account Name
|
// Missing Account Name
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 * \"narration\"\n"+
|
"2024-09-01 * \"narration\"\n"+
|
||||||
" Equity:Opening-Balances -123.45 CNY\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)
|
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
|
||||||
|
|
||||||
// Missing Amount
|
// Missing Amount
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 * \"narration\"\n"+
|
"2024-09-01 * \"narration\"\n"+
|
||||||
" Equity:Opening-Balances\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)
|
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
|
||||||
|
|
||||||
// Missing Commodity
|
// Missing Commodity
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 * \"narration\"\n"+
|
"2024-09-01 * \"narration\"\n"+
|
||||||
" Equity:Opening-Balances -123.45\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)
|
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,20 @@ const (
|
|||||||
CAMT_INDICATOR_DEBIT camtCreditDebitIndicator = "DBIT"
|
CAMT_INDICATOR_DEBIT camtCreditDebitIndicator = "DBIT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type camt052File struct {
|
||||||
|
XMLName xml.Name `xml:"Document"`
|
||||||
|
BankToCustomerAccountReport *camtBankToCustomerAccountReport `xml:"BkToCstmrAcctRpt"`
|
||||||
|
}
|
||||||
|
|
||||||
type camt053File struct {
|
type camt053File struct {
|
||||||
XMLName xml.Name `xml:"Document"`
|
XMLName xml.Name `xml:"Document"`
|
||||||
BankToCustomerStatement *camtBankToCustomerStatement `xml:"BkToCstmrStmt"`
|
BankToCustomerStatement *camtBankToCustomerStatement `xml:"BkToCstmrStmt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type camtBankToCustomerAccountReport struct {
|
||||||
|
Statements []*camtStatement `xml:"Rpt"`
|
||||||
|
}
|
||||||
|
|
||||||
type camtBankToCustomerStatement struct {
|
type camtBankToCustomerStatement struct {
|
||||||
Statements []*camtStatement `xml:"Stmt"`
|
Statements []*camtStatement `xml:"Stmt"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,30 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"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
|
// camt053FileReader defines the structure of camt.053 file reader
|
||||||
type camt053FileReader struct {
|
type camt053FileReader struct {
|
||||||
xmlDecoder *xml.Decoder
|
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
|
// read returns the imported camt.053 data
|
||||||
// Reference: https://www.iso20022.org/message-set/1196/download
|
// Reference: https://www.iso20022.org/message-set/1196/download
|
||||||
func (r *camt053FileReader) read(ctx core.Context) (*camt053File, error) {
|
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
|
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) {
|
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
|
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 := 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_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 != "" {
|
} 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_TIME] = fmt.Sprintf("%s 00:00:00", entry.BookingDate.Date)
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = datatable.TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE
|
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
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNewCamtStatementTransactionDataTable(file *camt053File) (*camtStatementTransactionDataTable, error) {
|
func createNewCamtStatementTransactionDataTable(camtStatements []*camtStatement) (*camtStatementTransactionDataTable, error) {
|
||||||
if file == nil || file.BankToCustomerStatement == nil || len(file.BankToCustomerStatement.Statements) == 0 {
|
if len(camtStatements) == 0 {
|
||||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||||
}
|
}
|
||||||
|
|
||||||
return &camtStatementTransactionDataTable{
|
return &camtStatementTransactionDataTable{
|
||||||
allStatements: file.BankToCustomerStatement.Statements,
|
allStatements: camtStatements,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package camt
|
package camt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"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)),
|
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
|
// camt053TransactionDataImporter defines the structure of camt.053 file importer for transaction data
|
||||||
type camt053TransactionDataImporter struct {
|
type camt053TransactionDataImporter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a camt.053 transaction data importer singleton instance
|
// Initialize camt.052 and camt.053 transaction data importer singleton instances
|
||||||
var (
|
var (
|
||||||
|
Camt052TransactionDataImporter = &camt052TransactionDataImporter{}
|
||||||
Camt053TransactionDataImporter = &camt053TransactionDataImporter{}
|
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
|
// 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)
|
camt053DataReader, err := createNewCamt053FileReader(data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -36,7 +73,11 @@ func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, use
|
|||||||
return nil, nil, nil, nil, nil, nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, err
|
||||||
@@ -44,5 +85,5 @@ func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, use
|
|||||||
|
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(camtTransactionTypeNameMapping)
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
func TestCamt052TransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := Camt053TransactionDataImporter
|
importer := Camt052TransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -20,7 +22,110 @@ func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing
|
|||||||
DefaultCurrency: "CNY",
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -64,7 +169,7 @@ func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -115,7 +220,7 @@ func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t *testing.T) {
|
func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t *testing.T) {
|
||||||
converter := Camt053TransactionDataImporter
|
importer := Camt053TransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -123,7 +228,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -157,7 +262,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 3, len(allNewTransactions))
|
assert.Equal(t, 3, len(allNewTransactions))
|
||||||
@@ -168,7 +273,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *testing.T) {
|
func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *testing.T) {
|
||||||
converter := Camt053TransactionDataImporter
|
importer := Camt053TransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -176,7 +281,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -196,10 +301,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -219,10 +324,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -242,10 +347,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -265,12 +370,12 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmountAndCurrency(t *testing.T) {
|
func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmountAndCurrency(t *testing.T) {
|
||||||
converter := Camt053TransactionDataImporter
|
importer := Camt053TransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -278,7 +383,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -314,7 +419,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(allNewTransactions))
|
assert.Equal(t, 2, len(allNewTransactions))
|
||||||
@@ -323,7 +428,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
|||||||
assert.Equal(t, "USD", allNewTransactions[1].OriginalSourceAccountCurrency)
|
assert.Equal(t, "USD", allNewTransactions[1].OriginalSourceAccountCurrency)
|
||||||
assert.Equal(t, int64(10023), allNewTransactions[1].Amount)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -365,7 +470,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(allNewTransactions))
|
assert.Equal(t, 2, len(allNewTransactions))
|
||||||
@@ -374,7 +479,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
|||||||
assert.Equal(t, "USD", allNewTransactions[1].OriginalSourceAccountCurrency)
|
assert.Equal(t, "USD", allNewTransactions[1].OriginalSourceAccountCurrency)
|
||||||
assert.Equal(t, int64(9999), allNewTransactions[1].Amount)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -403,14 +508,14 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -430,7 +535,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -439,7 +544,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmountAndCurrency(t *testing.T) {
|
func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmountAndCurrency(t *testing.T) {
|
||||||
converter := Camt053TransactionDataImporter
|
importer := Camt053TransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -447,7 +552,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -467,10 +572,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -498,10 +603,10 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -529,12 +634,12 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := Camt053TransactionDataImporter
|
importer := Camt053TransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -542,7 +647,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -572,13 +677,13 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "Test Transaction", allNewTransactions[0].Comment)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -607,13 +712,13 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "Test Line 1\nTest Line 2", allNewTransactions[0].Comment)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -634,7 +739,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</BkToCstmrStmt>
|
||||||
</Document>`), 0, nil, nil, nil, nil, nil)
|
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -642,7 +747,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testing.T) {
|
func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testing.T) {
|
||||||
converter := Camt053TransactionDataImporter
|
importer := Camt053TransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -650,7 +755,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -664,12 +769,12 @@ func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testi
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
assert.EqualError(t, err, errs.ErrMissingAccountData.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
||||||
converter := Camt053TransactionDataImporter
|
importer := Camt053TransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -677,7 +782,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -694,10 +799,10 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -716,10 +821,10 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -738,10 +843,10 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
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"?>
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
|
||||||
<BkToCstmrStmt>
|
<BkToCstmrStmt>
|
||||||
@@ -760,6 +865,6 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
|
|||||||
</Ntry>
|
</Ntry>
|
||||||
</Stmt>
|
</Stmt>
|
||||||
</BkToCstmrStmt>
|
</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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,29 +20,51 @@ type DataTableTransactionDataExporter struct {
|
|||||||
|
|
||||||
// BuildExportedContent writes the exported transaction data to the data table builder
|
// BuildExportedContent writes the exported transaction data to the data table builder
|
||||||
func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context, dataTableBuilder datatable.TransactionDataTableBuilder, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) error {
|
func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context, dataTableBuilder datatable.TransactionDataTableBuilder, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) error {
|
||||||
|
existsTransferOutTransactions := make(map[int64]bool)
|
||||||
|
|
||||||
for i := 0; i < len(transactions); i++ {
|
for i := 0; i < len(transactions); i++ {
|
||||||
transaction := transactions[i]
|
transaction := transactions[i]
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||||
|
existsTransferOutTransactions[transaction.TransactionId] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(transactions); i++ {
|
||||||
|
transaction := transactions[i]
|
||||||
|
|
||||||
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN && existsTransferOutTransactions[transaction.RelatedId] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dataRowMap := make(map[datatable.TransactionDataTableColumn]string, 15)
|
dataRowMap := make(map[datatable.TransactionDataTableColumn]string, 15)
|
||||||
|
transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime)
|
||||||
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
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_TIME] = utils.FormatUnixTimeToLongDateTime(transactionUnixTime, transactionTimeZone)
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(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_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_CATEGORY] = c.getExportedTransactionCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.AccountId, accountMap)
|
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.AccountId, accountMap)
|
if transaction.Type != models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(transaction.Amount)
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.AccountId, accountMap)
|
||||||
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.AccountId, accountMap)
|
||||||
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(transaction.Amount)
|
||||||
|
} else { // if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
||||||
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
||||||
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(transaction.RelatedAccountAmount)
|
||||||
|
}
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(transaction.RelatedAccountAmount)
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(transaction.RelatedAccountAmount)
|
||||||
|
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.AccountId, accountMap)
|
||||||
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.AccountId, accountMap)
|
||||||
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(transaction.Amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
dataRowMap[datatable.TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION] = c.getExportedGeographicLocation(transaction)
|
dataRowMap[datatable.TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION] = c.getExportedGeographicLocation(transaction)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package converter
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
@@ -29,7 +30,7 @@ type DataTableTransactionDataImporter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseImportedData returns the imported transaction data
|
// 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 {
|
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)
|
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
|
return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile
|
||||||
@@ -94,7 +95,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
timezoneOffset := defaultTimezoneOffset
|
timezone := defaultTimezone
|
||||||
|
|
||||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) &&
|
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) &&
|
||||||
dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) != datatable.TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE {
|
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
|
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 {
|
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())
|
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 tagIds []string
|
||||||
var tagNames []string
|
var tagNames []string
|
||||||
|
tagNamesMap := make(map[string]bool)
|
||||||
|
|
||||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TAGS) {
|
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TAGS) {
|
||||||
var tagNameItems []string
|
var tagNameItems []string
|
||||||
@@ -320,19 +322,39 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tag, exists := tagMap[tagName]
|
allNewTags, tagIds, tagNames = c.addTag(user, tagName, tagNamesMap, tagMap, allNewTags, tagIds, tagNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !exists {
|
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_PAYEE) && additionalOptions.IsPayeeAsTag() {
|
||||||
tag = c.createNewTransactionTagModel(user.Uid, tagName)
|
payee := dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_PAYEE)
|
||||||
allNewTags = append(allNewTags, tag)
|
|
||||||
tagMap[tagName] = tag
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag != nil {
|
if payee != "" {
|
||||||
tagIds = append(tagIds, utils.Int64ToString(tag.TagId))
|
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)
|
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.ImportTransaction{
|
||||||
Transaction: &models.Transaction{
|
Transaction: &models.Transaction{
|
||||||
Uid: user.Uid,
|
Uid: user.Uid,
|
||||||
Type: transactionDbType,
|
Type: transactionDbType,
|
||||||
CategoryId: categoryId,
|
CategoryId: categoryId,
|
||||||
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
|
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
|
||||||
TimezoneUtcOffset: timezoneOffset,
|
TimezoneUtcOffset: utils.GetTimezoneOffsetMinutes(transactionTime.Unix(), timezone),
|
||||||
AccountId: account.AccountId,
|
AccountId: account.AccountId,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
HideAmount: false,
|
HideAmount: false,
|
||||||
@@ -357,7 +383,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
|||||||
Comment: description,
|
Comment: description,
|
||||||
GeoLongitude: geoLongitude,
|
GeoLongitude: geoLongitude,
|
||||||
GeoLatitude: geoLatitude,
|
GeoLatitude: geoLatitude,
|
||||||
CreatedIp: "127.0.0.1",
|
CreatedIp: ctx.ClientIP(),
|
||||||
},
|
},
|
||||||
TagIds: tagIds,
|
TagIds: tagIds,
|
||||||
OriginalCategoryName: subCategoryName,
|
OriginalCategoryName: subCategoryName,
|
||||||
@@ -459,6 +485,27 @@ func (c *DataTableTransactionDataImporter) getTransactionCategory(categories map
|
|||||||
return subCategory, exists
|
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 {
|
func (c *DataTableTransactionDataImporter) createNewAccountModel(uid int64, accountName string, currency string) *models.Account {
|
||||||
return &models.Account{
|
return &models.Account{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package converter
|
package converter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
)
|
)
|
||||||
@@ -14,7 +16,7 @@ type TransactionDataExporter interface {
|
|||||||
// TransactionDataImporter defines the structure of transaction data importer
|
// TransactionDataImporter defines the structure of transaction data importer
|
||||||
type TransactionDataImporter interface {
|
type TransactionDataImporter interface {
|
||||||
// ParseImportedData returns the imported data
|
// 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
|
// 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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/text/encoding"
|
"golang.org/x/text/encoding"
|
||||||
"golang.org/x/text/encoding/charmap"
|
"golang.org/x/text/encoding/charmap"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"golang.org/x/text/encoding/simplifiedchinese"
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
"golang.org/x/text/encoding/traditionalchinese"
|
"golang.org/x/text/encoding/traditionalchinese"
|
||||||
"golang.org/x/text/encoding/unicode"
|
"golang.org/x/text/encoding/unicode"
|
||||||
|
"golang.org/x/text/encoding/unicode/utf32"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
@@ -28,59 +30,61 @@ import (
|
|||||||
var supportedFileTypeSeparators = map[string]rune{
|
var supportedFileTypeSeparators = map[string]rune{
|
||||||
"custom_csv": ',',
|
"custom_csv": ',',
|
||||||
"custom_tsv": '\t',
|
"custom_tsv": '\t',
|
||||||
|
"custom_ssv": ';',
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportedFileEncodings = map[string]encoding.Encoding{
|
var supportedFileEncodings = map[string]encoding.Encoding{
|
||||||
"utf-8": unicode.UTF8, // UTF-8
|
"utf-8": unicode.UTF8BOM, // UTF-8
|
||||||
"utf-8-bom": unicode.UTF8BOM, // UTF-8 with BOM
|
"utf-16le": unicode.UTF16(unicode.LittleEndian, unicode.UseBOM), // UTF-16 Little Endian
|
||||||
"utf-16le": unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), // UTF-16 Little Endian
|
"utf-16be": unicode.UTF16(unicode.BigEndian, unicode.UseBOM), // UTF-16 Big Endian
|
||||||
"utf-16be": unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM), // UTF-16 Big Endian
|
"utf-32le": utf32.UTF32(utf32.LittleEndian, utf32.UseBOM), // UTF-32 Little Endian
|
||||||
"cp437": charmap.CodePage437, // OEM United States (CP-437)
|
"utf-32be": utf32.UTF32(utf32.BigEndian, utf32.UseBOM), // UTF-32 Big Endian
|
||||||
"cp863": charmap.CodePage863, // OEM Canadian French (CP-863)
|
"cp437": charmap.CodePage437, // OEM United States (CP-437)
|
||||||
"cp037": charmap.CodePage037, // IBM EBCDIC US/Canada (CP-037)
|
"cp863": charmap.CodePage863, // OEM Canadian French (CP-863)
|
||||||
"cp1047": charmap.CodePage1047, // IBM EBCDIC Open Systems (CP-1047)
|
"cp037": charmap.CodePage037, // IBM EBCDIC US/Canada (CP-037)
|
||||||
"cp1140": charmap.CodePage1140, // IBM EBCDIC US/Canada with Euro (CP-1140)
|
"cp1047": charmap.CodePage1047, // IBM EBCDIC Open Systems (CP-1047)
|
||||||
"iso-8859-1": charmap.ISO8859_1, // Western European (ISO-8859-1)
|
"cp1140": charmap.CodePage1140, // IBM EBCDIC US/Canada with Euro (CP-1140)
|
||||||
"cp850": charmap.CodePage850, // Western European (CP-850)
|
"iso-8859-1": charmap.ISO8859_1, // Western European (ISO-8859-1)
|
||||||
"cp858": charmap.CodePage858, // Western European with Euro (CP-858)
|
"cp850": charmap.CodePage850, // Western European (CP-850)
|
||||||
"windows-1252": charmap.Windows1252, // Western European (Windows-1252)
|
"cp858": charmap.CodePage858, // Western European with Euro (CP-858)
|
||||||
"iso-8859-15": charmap.ISO8859_15, // Western European (ISO-8859-15)
|
"windows-1252": charmap.Windows1252, // Western European (Windows-1252)
|
||||||
"iso-8859-4": charmap.ISO8859_4, // North European (ISO-8859-4)
|
"iso-8859-15": charmap.ISO8859_15, // Western European (ISO-8859-15)
|
||||||
"iso-8859-10": charmap.ISO8859_10, // North European (ISO-8859-10)
|
"iso-8859-4": charmap.ISO8859_4, // North European (ISO-8859-4)
|
||||||
"cp865": charmap.CodePage865, // North European (CP-865)
|
"iso-8859-10": charmap.ISO8859_10, // North European (ISO-8859-10)
|
||||||
"iso-8859-2": charmap.ISO8859_2, // Central European (ISO-8859-2)
|
"cp865": charmap.CodePage865, // North European (CP-865)
|
||||||
"cp852": charmap.CodePage852, // Central European (CP-852)
|
"iso-8859-2": charmap.ISO8859_2, // Central European (ISO-8859-2)
|
||||||
"windows-1250": charmap.Windows1250, // Central European (Windows-1250)
|
"cp852": charmap.CodePage852, // Central European (CP-852)
|
||||||
"iso-8859-14": charmap.ISO8859_14, // Celtic (ISO-8859-14)
|
"windows-1250": charmap.Windows1250, // Central European (Windows-1250)
|
||||||
"iso-8859-3": charmap.ISO8859_3, // South European (ISO-8859-3)
|
"iso-8859-14": charmap.ISO8859_14, // Celtic (ISO-8859-14)
|
||||||
"cp860": charmap.CodePage860, // Portuguese (CP-860)
|
"iso-8859-3": charmap.ISO8859_3, // South European (ISO-8859-3)
|
||||||
"iso-8859-7": charmap.ISO8859_7, // Greek (ISO-8859-7)
|
"cp860": charmap.CodePage860, // Portuguese (CP-860)
|
||||||
"windows-1253": charmap.Windows1253, // Greek (Windows-1253)
|
"iso-8859-7": charmap.ISO8859_7, // Greek (ISO-8859-7)
|
||||||
"iso-8859-9": charmap.ISO8859_9, // Turkish (ISO-8859-9)
|
"windows-1253": charmap.Windows1253, // Greek (Windows-1253)
|
||||||
"windows-1254": charmap.Windows1254, // Turkish (Windows-1254)
|
"iso-8859-9": charmap.ISO8859_9, // Turkish (ISO-8859-9)
|
||||||
"iso-8859-13": charmap.ISO8859_13, // Baltic (ISO-8859-13)
|
"windows-1254": charmap.Windows1254, // Turkish (Windows-1254)
|
||||||
"windows-1257": charmap.Windows1257, // Baltic (Windows-1257)
|
"iso-8859-13": charmap.ISO8859_13, // Baltic (ISO-8859-13)
|
||||||
"iso-8859-16": charmap.ISO8859_16, // South-Eastern European (ISO-8859-16)
|
"windows-1257": charmap.Windows1257, // Baltic (Windows-1257)
|
||||||
"iso-8859-5": charmap.ISO8859_5, // Cyrillic (ISO-8859-5)
|
"iso-8859-16": charmap.ISO8859_16, // South-Eastern European (ISO-8859-16)
|
||||||
"cp855": charmap.CodePage855, // Cyrillic (CP-855)
|
"iso-8859-5": charmap.ISO8859_5, // Cyrillic (ISO-8859-5)
|
||||||
"cp866": charmap.CodePage866, // Cyrillic (CP-866)
|
"cp855": charmap.CodePage855, // Cyrillic (CP-855)
|
||||||
"windows-1251": charmap.Windows1251, // Cyrillic (Windows-1251)
|
"cp866": charmap.CodePage866, // Cyrillic (CP-866)
|
||||||
"koi8r": charmap.KOI8R, // Cyrillic (KOI8-R)
|
"windows-1251": charmap.Windows1251, // Cyrillic (Windows-1251)
|
||||||
"koi8u": charmap.KOI8U, // Cyrillic (KOI8-U)
|
"koi8r": charmap.KOI8R, // Cyrillic (KOI8-R)
|
||||||
"iso-8859-6": charmap.ISO8859_6, // Arabic (ISO-8859-6)
|
"koi8u": charmap.KOI8U, // Cyrillic (KOI8-U)
|
||||||
"windows-1256": charmap.Windows1256, // Arabic (Windows-1256)
|
"iso-8859-6": charmap.ISO8859_6, // Arabic (ISO-8859-6)
|
||||||
"iso-8859-8": charmap.ISO8859_8, // Hebrew (ISO-8859-8)
|
"windows-1256": charmap.Windows1256, // Arabic (Windows-1256)
|
||||||
"cp862": charmap.CodePage862, // Hebrew (CP-862)
|
"iso-8859-8": charmap.ISO8859_8, // Hebrew (ISO-8859-8)
|
||||||
"windows-1255": charmap.Windows1255, // Hebrew (Windows-1255)
|
"cp862": charmap.CodePage862, // Hebrew (CP-862)
|
||||||
"windows-874": charmap.Windows874, // Thai (Windows-874)
|
"windows-1255": charmap.Windows1255, // Hebrew (Windows-1255)
|
||||||
"windows-1258": charmap.Windows1258, // Vietnamese (Windows-1258)
|
"windows-874": charmap.Windows874, // Thai (Windows-874)
|
||||||
"gb18030": simplifiedchinese.GB18030, // Chinese (Simplified, GB18030)
|
"windows-1258": charmap.Windows1258, // Vietnamese (Windows-1258)
|
||||||
"gbk": simplifiedchinese.GBK, // Chinese (Simplified, GBK)
|
"gb18030": simplifiedchinese.GB18030, // Chinese (Simplified, GB18030)
|
||||||
"big5": traditionalchinese.Big5, // Chinese (Traditional, Big5)
|
"gbk": simplifiedchinese.GBK, // Chinese (Simplified, GBK)
|
||||||
"euc-kr": korean.EUCKR, // Korean (EUC-KR)
|
"big5": traditionalchinese.Big5, // Chinese (Traditional, Big5)
|
||||||
"euc-jp": japanese.EUCJP, // Japanese (EUC-JP)
|
"euc-kr": korean.EUCKR, // Korean (EUC-KR)
|
||||||
"iso-2022-jp": japanese.ISO2022JP, // Japanese (ISO-2022-JP)
|
"euc-jp": japanese.EUCJP, // Japanese (EUC-JP)
|
||||||
"shift_jis": japanese.ShiftJIS, // Japanese (Shift JIS)
|
"iso-2022-jp": japanese.ISO2022JP, // Japanese (ISO-2022-JP)
|
||||||
|
"shift_jis": japanese.ShiftJIS, // Japanese (Shift JIS)
|
||||||
}
|
}
|
||||||
|
|
||||||
var customTransactionTypeNameMapping = map[models.TransactionType]string{
|
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)),
|
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
|
// customTransactionDataDsvFileImporter defines the structure of custom dsv importer for transaction data
|
||||||
type customTransactionDataDsvFileImporter struct {
|
type customTransactionDataDsvFileImporter struct {
|
||||||
fileEncoding encoding.Encoding
|
fileEncoding encoding.Encoding
|
||||||
@@ -110,8 +110,8 @@ type customTransactionDataDsvFileImporter struct {
|
|||||||
transactionTagSeparator string
|
transactionTagSeparator string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDsvFileLines returns the parsed file lines for specified the dsv file data
|
// ParseDataLines returns the parsed file lines for specified the dsv file data
|
||||||
func (c *customTransactionDataDsvFileImporter) ParseDsvFileLines(ctx core.Context, data []byte) ([][]string, error) {
|
func (c *customTransactionDataDsvFileImporter) ParseDataLines(ctx core.Context, data []byte) ([][]string, error) {
|
||||||
reader := transform.NewReader(bytes.NewReader(data), c.fileEncoding.NewDecoder())
|
reader := transform.NewReader(bytes.NewReader(data), c.fileEncoding.NewDecoder())
|
||||||
csvReader := csv.NewReader(reader)
|
csvReader := csv.NewReader(reader)
|
||||||
csvReader.Comma = c.separator
|
csvReader.Comma = c.separator
|
||||||
@@ -127,7 +127,7 @@ func (c *customTransactionDataDsvFileImporter) ParseDsvFileLines(ctx core.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
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
|
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
|
// 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) {
|
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.ParseDsvFileLines(ctx, data)
|
allLines, err := c.ParseDataLines(ctx, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, err
|
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)
|
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)
|
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
|
// IsDelimiterSeparatedValuesFileType returns whether the file type is the delimiter-separated values file type
|
||||||
@@ -166,14 +166,18 @@ func IsDelimiterSeparatedValuesFileType(fileType string) bool {
|
|||||||
return exists
|
return exists
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNewCustomTransactionDataDsvFileParser returns a new custom dsv parser for transaction data
|
// CreateNewCustomTransactionDataDsvFileParser returns a new custom transaction data parser
|
||||||
func CreateNewCustomTransactionDataDsvFileParser(fileType string, fileEncoding string) (CustomTransactionDataDsvFileParser, error) {
|
func CreateNewCustomTransactionDataDsvFileParser(fileType string, fileEncoding string) (CustomTransactionDataParser, error) {
|
||||||
separator, exists := supportedFileTypeSeparators[fileType]
|
separator, exists := supportedFileTypeSeparators[fileType]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, errs.ErrImportFileTypeNotSupported
|
return nil, errs.ErrImportFileTypeNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fileEncoding == "" {
|
||||||
|
return nil, errs.ErrImportFileEncodingIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
enc, exists := supportedFileEncodings[fileEncoding]
|
enc, exists := supportedFileEncodings[fileEncoding]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
@@ -194,6 +198,10 @@ func CreateNewCustomTransactionDataDsvFileImporter(fileType string, fileEncoding
|
|||||||
return nil, errs.ErrImportFileTypeNotSupported
|
return nil, errs.ErrImportFileTypeNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fileEncoding == "" {
|
||||||
|
return nil, errs.ErrImportFileEncodingIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
enc, exists := supportedFileEncodings[fileEncoding]
|
enc, exists := supportedFileEncodings[fileEncoding]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
+199
-119
@@ -1,10 +1,12 @@
|
|||||||
package dsv
|
package custom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
@@ -15,19 +17,21 @@ import (
|
|||||||
func TestIsDelimiterSeparatedValuesFileType(t *testing.T) {
|
func TestIsDelimiterSeparatedValuesFileType(t *testing.T) {
|
||||||
assert.True(t, IsDelimiterSeparatedValuesFileType("custom_csv"))
|
assert.True(t, IsDelimiterSeparatedValuesFileType("custom_csv"))
|
||||||
assert.True(t, IsDelimiterSeparatedValuesFileType("custom_tsv"))
|
assert.True(t, IsDelimiterSeparatedValuesFileType("custom_tsv"))
|
||||||
|
assert.True(t, IsDelimiterSeparatedValuesFileType("custom_ssv"))
|
||||||
|
|
||||||
assert.False(t, IsDelimiterSeparatedValuesFileType("dsv"))
|
assert.False(t, IsDelimiterSeparatedValuesFileType("dsv"))
|
||||||
assert.False(t, IsDelimiterSeparatedValuesFileType("csv"))
|
assert.False(t, IsDelimiterSeparatedValuesFileType("csv"))
|
||||||
assert.False(t, IsDelimiterSeparatedValuesFileType("tsv"))
|
assert.False(t, IsDelimiterSeparatedValuesFileType("tsv"))
|
||||||
|
assert.False(t, IsDelimiterSeparatedValuesFileType("ssv"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCustomTransactionDataDsvFileParser_ParseDsvFileLines(t *testing.T) {
|
func TestCustomTransactionDataDsvFileParser_ParseDataLines(t *testing.T) {
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileParser("custom_csv", "utf-8")
|
importer, err := CreateNewCustomTransactionDataDsvFileParser("custom_csv", "utf-8")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
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 00:00:00,B,123.45\n"+
|
||||||
"2024-09-01 01:23:45,I,0.12\n"))
|
"2024-09-01 01:23:45,I,0.12\n"))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -44,10 +48,10 @@ func TestCustomTransactionDataDsvFileParser_ParseDsvFileLines(t *testing.T) {
|
|||||||
assert.Equal(t, "I", allLines[1][1])
|
assert.Equal(t, "I", allLines[1][1])
|
||||||
assert.Equal(t, "0.12", allLines[1][2])
|
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)
|
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 12:34:56\tE\t1.00\n"+
|
||||||
"2024-09-01 23:59:59\tT\t0.05"))
|
"2024-09-01 23:59:59\tT\t0.05"))
|
||||||
assert.Nil(t, err)
|
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, "2024-09-01 23:59:59", allLines[1][0])
|
||||||
assert.Equal(t, "T", allLines[1][1])
|
assert.Equal(t, "T", allLines[1][1])
|
||||||
assert.Equal(t, "0.05", allLines[1][2])
|
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) {
|
func TestCustomTransactionDataDsvFileImporter_MinimumValidData(t *testing.T) {
|
||||||
@@ -77,7 +101,7 @@ func TestCustomTransactionDataDsvFileImporter_MinimumValidData(t *testing.T) {
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -87,11 +111,11 @@ func TestCustomTransactionDataDsvFileImporter_MinimumValidData(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
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 00:00:00,B,123.45\n"+
|
||||||
"2024-09-01 01:23:45,I,0.12\n"+
|
"2024-09-01 01:23:45,I,0.12\n"+
|
||||||
"2024-09-01 12:34:56,E,1.00\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -168,7 +192,7 @@ func TestCustomTransactionDataDsvFileImporter_WithAllSupportedColumns(t *testing
|
|||||||
"Expense": models.TRANSACTION_TYPE_EXPENSE,
|
"Expense": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"Transfer": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -178,12 +202,12 @@ func TestCustomTransactionDataDsvFileImporter_WithAllSupportedColumns(t *testing
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"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 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 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 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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -261,7 +285,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTime(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -271,12 +295,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTime(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01T12:34:56,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01T12:34:56,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"09/01/2024 12:34:56,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +316,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTransactionWithoutType(t *tes
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -302,8 +326,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseTransactionWithoutType(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,A,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +340,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidType(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"B": 0,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -326,8 +350,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidType(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,B,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +364,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone(t *testing.T
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -350,20 +374,20 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone(t *testing.T
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56-10:00,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56+00:00,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56+12:45,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
@@ -378,7 +402,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone2(t *testing.
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -388,20 +412,20 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone2(t *testing.
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56-1000,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01 12:34:56-1000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56+0000,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01 12:34:56+0000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56+1245,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01 12:34:56+1245,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
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{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -427,20 +451,20 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,-10:00,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,+00:00,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,+12:45,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
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{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -466,20 +490,20 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone2(t *testing.T)
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,-1000,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01 12:34:56,-1000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,+0000,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01 12:34:56,+0000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,+1245,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01 12:34:56,+1245,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
@@ -495,7 +519,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezoneFormat(t *test
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -505,8 +529,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezoneFormat(t *test
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,CST,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrImportFileTransactionTimezoneFormatInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,7 +544,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone(t *testing.T)
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -530,12 +554,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone(t *testing.T)
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,-0700,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +573,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone2(t *testing.T
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -559,12 +583,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone2(t *testing.T
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,0700,E,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +601,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithCustomFormat(t *tes
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -587,8 +611,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithCustomFormat(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01 12:34:56\tE\t1.234,56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(123456), allNewTransactions[0].Amount)
|
assert.Equal(t, int64(123456), allNewTransactions[0].Amount)
|
||||||
@@ -603,7 +627,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -613,8 +637,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,7 +651,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -637,8 +661,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,7 +679,7 @@ func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T)
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -665,11 +689,11 @@ func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T)
|
|||||||
DefaultCurrency: "CNY",
|
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 00:00:00,B,,123.45\n"+
|
||||||
"2024-09-01 01:23:45,I,Test Category,0.12\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 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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -724,7 +748,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAccountCurrency(t *testi
|
|||||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -734,9 +758,9 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAccountCurrency(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
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 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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -767,7 +791,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAccountCurrency(t *tes
|
|||||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -777,14 +801,14 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAccountCurrency(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
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 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)
|
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 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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,7 +827,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseNotSupportedCurrency(t *testi
|
|||||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -813,12 +837,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseNotSupportedCurrency(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 01:23:45,B,Test Account,XXX,123.45,,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
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,T,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,7 +859,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -845,11 +869,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
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 00:00:00,B,123.45000000,\n"+
|
||||||
"2024-09-01 01:23:45,I,0.12000000,\n"+
|
"2024-09-01 01:23:45,I,0.12000000,\n"+
|
||||||
"2024-09-01 12:34:56,E,1.00000000,\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -873,6 +897,62 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
|
|||||||
assert.Equal(t, int64(35), allNewTransactions[3].RelatedAccountAmount)
|
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) {
|
func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
|
||||||
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||||
@@ -886,7 +966,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -896,12 +976,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,E,Test Account,123 45,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.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)
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -917,7 +997,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseNoAmount2(t *testing.T) {
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -927,15 +1007,15 @@ func TestCustomTransactionDataDsvFileImporter_ParseNoAmount2(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,E,Test Account,123.45,"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||||
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
|
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,T,Test Account,123.45,Test Account2"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||||
@@ -952,7 +1032,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidGeographicLocation(t *te
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -962,8 +1042,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidGeographicLocation(t *te
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,E,123.45,123.45;45.56"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -981,7 +1061,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidGeographicLocation(t *
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -991,15 +1071,15 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidGeographicLocation(t *
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,E,123.45,,,1"), 0, nil, nil, nil, nil, nil)
|
"2024-09-01 12:34:56,E,123.45,,,1"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
||||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
|
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,E,123.45,a b"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,7 +1093,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTag(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -1023,8 +1103,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseTag(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -1053,7 +1133,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTagWithoutSeparator(t *testin
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -1063,8 +1143,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseTagWithoutSeparator(t *testin
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -1084,7 +1164,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseDescription(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -1094,8 +1174,8 @@ func TestCustomTransactionDataDsvFileImporter_ParseDescription(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"2024-09-01 12:34:56,T,123.45,foo bar\t#test"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
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 (
|
import (
|
||||||
"strings"
|
"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) {
|
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))
|
rowData := make(map[datatable.TransactionDataTableColumn]string, len(t.transactionDataTable.columnIndexMapping))
|
||||||
|
var transactionTime *time.Time = nil
|
||||||
|
|
||||||
for column, columnIndex := range t.transactionDataTable.columnIndexMapping {
|
for column, columnIndex := range t.transactionDataTable.columnIndexMapping {
|
||||||
if columnIndex < 0 || columnIndex >= row.ColumnCount() {
|
if columnIndex < 0 || columnIndex >= row.ColumnCount() {
|
||||||
@@ -144,10 +145,11 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user
|
|||||||
return nil, false, errs.ErrTransactionTimeInvalid
|
return nil, false, errs.ErrTransactionTimeInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transactionTime = &dateTime
|
||||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
|
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
|
||||||
|
|
||||||
if t.transactionDataTable.timeFormatIncludeTimezone {
|
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:]
|
timezone = timezone[:3] + ":" + timezone[3:]
|
||||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = timezone
|
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 {
|
} else {
|
||||||
return nil, false, errs.ErrImportFileTransactionTimezoneFormatInvalid
|
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) {
|
func (t *customPlainTextDataRowIterator) parseAmount(ctx core.Context, amountValue string) (string, error) {
|
||||||
if t.transactionDataTable.amountDigitGroupingSymbol != "" {
|
if t.transactionDataTable.amountDigitGroupingSymbol != "" {
|
||||||
amountValue = strings.ReplaceAll(amountValue, 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 != "." {
|
if t.transactionDataTable.amountDecimalSeparator != "" && t.transactionDataTable.amountDecimalSeparator != "." {
|
||||||
@@ -72,6 +72,10 @@ const (
|
|||||||
TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION TransactionDataTableColumn = 12
|
TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION TransactionDataTableColumn = 12
|
||||||
TRANSACTION_DATA_TABLE_TAGS TransactionDataTableColumn = 13
|
TRANSACTION_DATA_TABLE_TAGS TransactionDataTableColumn = 13
|
||||||
TRANSACTION_DATA_TABLE_DESCRIPTION TransactionDataTableColumn = 14
|
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
|
// 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
|
// 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
|
var importRequest models.ImportTransactionRequest
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &importRequest); err != nil {
|
if err := json.Unmarshal(data, &importRequest); err != nil {
|
||||||
@@ -55,7 +55,7 @@ func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context,
|
|||||||
ezbookkeepingTagSeparator,
|
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) {
|
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)
|
timezone := time.FixedZone("Transaction Timezone", utcOffset*60)
|
||||||
|
timezoneOffset := utils.FormatTimezoneOffset(time.Now().Unix(), timezone)
|
||||||
|
|
||||||
row := make(map[datatable.TransactionDataTableColumn]string, len(allJsonDataSupportedColumns))
|
row := make(map[datatable.TransactionDataTableColumn]string, len(allJsonDataSupportedColumns))
|
||||||
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = transaction.Time
|
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_TRANSACTION_TYPE] = transaction.Type
|
||||||
row[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = transaction.CategoryName
|
row[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = transaction.CategoryName
|
||||||
row[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = transaction.SourceAccountName
|
row[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = transaction.SourceAccountName
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package _default
|
package _default
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"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
|
// 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(
|
dataTable, err := createNewDefaultPlainTextDataTable(
|
||||||
string(data),
|
string(data),
|
||||||
c.columnSeparator,
|
c.columnSeparator,
|
||||||
@@ -104,5 +106,5 @@ func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Co
|
|||||||
ezbookkeepingTagSeparator,
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -12,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterToExportedContent(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterToExportedContent(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
exporter := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
transactions := make([]*models.Transaction, 3)
|
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,+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,+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"
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, expectedContent, string(actualContent))
|
assert.Equal(t, expectedContent, string(actualContent))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MinimumValidData(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -134,11 +136,11 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MinimumValidDat
|
|||||||
DefaultCurrency: "CNY",
|
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 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 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 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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -197,7 +199,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MinimumValidDat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -205,17 +207,17 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTim
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, _, _, 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,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, _, _, 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,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -223,13 +225,13 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTyp
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, _, _, 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,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidTimezone(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -237,27 +239,27 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidTimez
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
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,,"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
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"+
|
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,,"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
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"+
|
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,,"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTimezone(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTimezone(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -265,13 +267,13 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTim
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, _, _, 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,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -279,9 +281,9 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidAccou
|
|||||||
DefaultCurrency: "CNY",
|
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 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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -298,7 +300,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidAccou
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -306,19 +308,19 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAcc
|
|||||||
DefaultCurrency: "CNY",
|
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 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)
|
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 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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -326,17 +328,17 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNotSupport
|
|||||||
DefaultCurrency: "CNY",
|
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,XXX,123.45,,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
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,Transfer,Test Category,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -344,17 +346,17 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAmo
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, _, _, 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,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -362,15 +364,15 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
|
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,"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||||
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
|
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
|
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"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||||
@@ -378,7 +380,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidGeographicLocation(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidGeographicLocation(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -386,8 +388,8 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidGeogr
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
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"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -396,7 +398,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidGeogr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidGeographicLocation(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidGeographicLocation(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -404,24 +406,24 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidGeo
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
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"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
||||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
|
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"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"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)
|
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"+
|
_, _, _, _, _, _, 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 "), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseTag(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseTag(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -429,8 +431,8 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseTag(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Tags\n"+
|
_, _, _, _, _, 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;;"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -450,7 +452,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseTag(t *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseDescription(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -458,8 +460,8 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseDescriptio
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Description\n"+
|
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"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -467,7 +469,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseDescriptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingFileHeader(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -475,12 +477,12 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingFileHead
|
|||||||
DefaultCurrency: "CNY",
|
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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||||
converter := DefaultTransactionDataCSVFileConverter
|
importer := DefaultTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -489,32 +491,32 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingRequired
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Time Column
|
// 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"+
|
_, _, _, _, _, _, 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,,,,,,"), 0, nil, nil, nil, nil, nil)
|
"+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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Type Column
|
// 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"+
|
_, _, _, _, _, _, 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,,,,,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Sub Category Column
|
// 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"+
|
_, _, _, _, _, _, 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,,,,,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Account Name Column
|
// 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"+
|
_, _, _, _, _, _, 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,,,,,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Amount Column
|
// 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"+
|
_, _, _, _, _, _, 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,,,,,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Account2 Name Column
|
// 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"+
|
_, _, _, _, _, _, 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,,,,,"), 0, nil, nil, nil, nil, nil)
|
"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)
|
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
|
// ColumnCount returns the total count of column in this data row
|
||||||
func (r *ExcelMSCFBFileBasicDataTableRow) ColumnCount() int {
|
func (r *ExcelMSCFBFileBasicDataTableRow) ColumnCount() int {
|
||||||
row := r.sheet.Row(r.rowIndex)
|
row := r.sheet.Row(r.rowIndex)
|
||||||
return row.LastCol() + 1
|
return row.LastCol()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetData returns the data in the specified column index
|
// GetData returns the data in the specified column index
|
||||||
@@ -195,7 +195,10 @@ func CreateNewExcelMSCFBFileBasicDataTable(data []byte, hasTitleLine bool) (data
|
|||||||
}
|
}
|
||||||
|
|
||||||
if i == 0 {
|
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)
|
headerItem := row.Col(j)
|
||||||
|
|
||||||
if headerItem == "" {
|
if headerItem == "" {
|
||||||
@@ -205,7 +208,7 @@ func CreateNewExcelMSCFBFileBasicDataTable(data []byte, hasTitleLine bool) (data
|
|||||||
firstRowItems = append(firstRowItems, headerItem)
|
firstRowItems = append(firstRowItems, headerItem)
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
headerItem := row.Col(j)
|
||||||
|
|
||||||
if headerItem != firstRowItems[j] {
|
if headerItem != firstRowItems[j] {
|
||||||
|
|||||||
@@ -300,10 +300,10 @@ func TestExcelMSCFBFileBasicDataRowColumnCount(t *testing.T) {
|
|||||||
iterator := datatable.DataRowIterator()
|
iterator := datatable.DataRowIterator()
|
||||||
|
|
||||||
row1 := iterator.Next()
|
row1 := iterator.Next()
|
||||||
assert.EqualValues(t, 4, row1.ColumnCount())
|
assert.EqualValues(t, 3, row1.ColumnCount())
|
||||||
|
|
||||||
row2 := iterator.Next()
|
row2 := iterator.Next()
|
||||||
assert.EqualValues(t, 4, row2.ColumnCount())
|
assert.EqualValues(t, 3, row2.ColumnCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcelMSCFBFileBasicDataRowGetData(t *testing.T) {
|
func TestExcelMSCFBFileBasicDataRowGetData(t *testing.T) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package feidee
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/text/encoding/unicode"
|
"golang.org/x/text/encoding/unicode"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
@@ -27,6 +28,9 @@ const feideeMymoneyAppTransactionAccountCurrencyColumnName = "账户币种"
|
|||||||
const feideeMymoneyAppTransactionAmountColumnName = "金额"
|
const feideeMymoneyAppTransactionAmountColumnName = "金额"
|
||||||
const feideeMymoneyAppTransactionDescriptionColumnName = "备注"
|
const feideeMymoneyAppTransactionDescriptionColumnName = "备注"
|
||||||
const feideeMymoneyAppTransactionRelatedIdColumnName = "关联Id"
|
const feideeMymoneyAppTransactionRelatedIdColumnName = "关联Id"
|
||||||
|
const feideeMymoneyAppTransactionMemberColumnName = "成员"
|
||||||
|
const feideeMymoneyAppTransactionProjectColumnName = "项目"
|
||||||
|
const feideeMymoneyAppTransactionMerchantColumnName = "商家"
|
||||||
|
|
||||||
const feideeMymoneyAppTransactionTypeModifyBalanceText = "余额变更"
|
const feideeMymoneyAppTransactionTypeModifyBalanceText = "余额变更"
|
||||||
const feideeMymoneyAppTransactionTypeModifyOutstandingBalanceText = "负债变更"
|
const feideeMymoneyAppTransactionTypeModifyOutstandingBalanceText = "负债变更"
|
||||||
@@ -44,6 +48,9 @@ var feideeMymoneyAppDataColumnNameMapping = map[datatable.TransactionDataTableCo
|
|||||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY: feideeMymoneyAppTransactionAccountCurrencyColumnName,
|
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY: feideeMymoneyAppTransactionAccountCurrencyColumnName,
|
||||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: feideeMymoneyAppTransactionAmountColumnName,
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: feideeMymoneyAppTransactionAmountColumnName,
|
||||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: feideeMymoneyAppTransactionDescriptionColumnName,
|
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
|
// 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
|
// 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()
|
fallback := unicode.UTF8.NewDecoder()
|
||||||
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
||||||
|
|
||||||
@@ -91,7 +98,7 @@ func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx c
|
|||||||
|
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(feideeMymoneyTransactionTypeNameMapping)
|
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) {
|
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)
|
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()
|
transactionRowParser := createFeideeMymoneyTransactionDataRowParser()
|
||||||
transactionDataTable := datatable.CreateNewWritableTransactionDataTableWithRowParser(newColumns, transactionRowParser)
|
transactionDataTable := datatable.CreateNewWritableTransactionDataTableWithRowParser(newColumns, transactionRowParser)
|
||||||
transferTransactionsMap := make(map[string]map[datatable.TransactionDataTableColumn]string, 0)
|
transferTransactionsMap := make(map[string]map[datatable.TransactionDataTableColumn]string, 0)
|
||||||
|
|||||||
+96
-66
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -21,7 +22,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||||
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"+
|
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"+
|
||||||
"\"余额变更\",\"2024-09-01 01:00:00\",\"\",\"Test Account2\",\"-0.12\",\"\",\"\"\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 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-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 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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceModification(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceModification(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -118,10 +119,10 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceMo
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||||
"\"负债变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -160,7 +161,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -168,19 +169,19 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidTime(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -188,14 +189,14 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidType(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\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)
|
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -203,11 +204,11 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\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 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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -224,7 +225,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -232,23 +233,23 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAccountCurren
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\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 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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||||
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\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 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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -256,26 +257,26 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseNotSupportedCurrency
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联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 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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联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 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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -283,31 +284,31 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAmount(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
||||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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 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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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 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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -315,18 +316,18 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseDescription(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||||
"\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"Test\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "Test\nA new line break", allNewTransactions[0].Comment)
|
assert.Equal(t, "Test\nA new line break", allNewTransactions[0].Comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_InvalidRelatedId(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_WithAdditionalOptions(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -334,41 +335,70 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_InvalidRelatedId(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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)
|
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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
||||||
"\"转出\",\"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 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)
|
assert.EqualError(t, err, errs.ErrFoundRecordNotHasRelatedRecord.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1,
|
Uid: 1,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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.ErrInvalidFileHeader.Message)
|
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)
|
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||||
converter := FeideeMymoneyAppTransactionDataCsvFileImporter
|
importer := FeideeMymoneyAppTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -377,38 +407,38 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingRequiredColumn(t *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Time Column
|
// 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"+
|
"\"交易类型\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Type Column
|
// 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"+
|
"\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Sub Category Column
|
// 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"+
|
"\"交易类型\",\"日期\",\"账户\",\"金额\",\"备注\",\"关联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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Account Name Column
|
// 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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"金额\",\"备注\",\"关联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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Amount Column
|
// 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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"备注\",\"关联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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Related ID Column
|
// 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"+
|
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\"\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-2
@@ -1,6 +1,8 @@
|
|||||||
package feidee
|
package feidee
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/excel"
|
"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_AMOUNT: "金额",
|
||||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
|
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
|
||||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: "备注",
|
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
|
// 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
|
// 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)
|
dataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -42,5 +47,5 @@ func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData
|
|||||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyElecloudDataColumnNameMapping, transactionRowParser)
|
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyElecloudDataColumnNameMapping, transactionRowParser)
|
||||||
dataTableImporter := converter.CreateNewSimpleImporter(feideeMymoneyElecloudTransactionTypeNameMapping)
|
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/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFeideeMymoneyElecloudTransactionDataXlsxImporterParseImportedData_MinimumValidData(t *testing.T) {
|
func TestFeideeMymoneyElecloudTransactionDataXlsxImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := FeideeMymoneyElecloudTransactionDataXlsxFileImporter
|
importer := FeideeMymoneyElecloudTransactionDataXlsxFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -24,7 +25,7 @@ func TestFeideeMymoneyElecloudTransactionDataXlsxImporterParseImportedData_Minim
|
|||||||
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_elecloud_test_file.xlsx")
|
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_elecloud_test_file.xlsx")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 7, len(allNewTransactions))
|
assert.Equal(t, 7, len(allNewTransactions))
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package feidee
|
package feidee
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/excel"
|
"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_AMOUNT: "金额",
|
||||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
|
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
|
||||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: "备注",
|
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
|
// 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
|
// 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)
|
dataTable, err := excel.CreateNewExcelMSCFBFileBasicDataTable(data, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -41,5 +46,5 @@ func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx c
|
|||||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyWebDataColumnNameMapping, transactionRowParser)
|
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyWebDataColumnNameMapping, transactionRowParser)
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(feideeMymoneyTransactionTypeNameMapping)
|
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/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFeideeMymoneyTransactionDataXlsImporterParseImportedData_MinimumValidData(t *testing.T) {
|
func TestFeideeMymoneyTransactionDataXlsImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := FeideeMymoneyWebTransactionDataXlsFileImporter
|
importer := FeideeMymoneyWebTransactionDataXlsFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -24,7 +25,7 @@ func TestFeideeMymoneyTransactionDataXlsImporterParseImportedData_MinimumValidDa
|
|||||||
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_test_file.xls")
|
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_test_file.xls")
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 7, len(allNewTransactions))
|
assert.Equal(t, 7, len(allNewTransactions))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fireflyIII
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
"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
|
// 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)
|
reader := bytes.NewReader(data)
|
||||||
dataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, true)
|
dataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, true)
|
||||||
|
|
||||||
@@ -52,5 +53,5 @@ func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Co
|
|||||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser)
|
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser)
|
||||||
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(fireflyIIITransactionTypeNameMapping, "", "", ",")
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_MinimumValidData(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -20,11 +22,11 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MinimumValidData(t *testing
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"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"+
|
"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"+
|
"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -82,8 +84,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MinimumValidData(t *testing
|
|||||||
assert.Equal(t, "Test Category3", allNewSubTransferCategories[0].Name)
|
assert.Equal(t, "Test Category3", allNewSubTransferCategories[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -91,17 +93,17 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidTime(t *testing
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
_, _, _, _, _, _, 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\""), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
_, _, _, _, _, _, 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\""), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -109,13 +111,13 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidType(t *testing
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
_, _, _, _, _, _, 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\""), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseAccountNameAsCategoryName(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseAccountNameAsCategoryName(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -123,23 +125,23 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseAccountNameAsCategoryN
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
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\",\"\""), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "A expense account", allNewTransactions[0].OriginalCategoryName)
|
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"+
|
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\",\"\""), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "A revenue account", allNewTransactions[0].OriginalCategoryName)
|
assert.Equal(t, "A revenue account", allNewTransactions[0].OriginalCategoryName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidTimezone(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -147,27 +149,27 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidTimezone(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
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\""), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
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"+
|
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\""), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
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"+
|
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\""), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -175,9 +177,9 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidAccountCurrency(t
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -193,8 +195,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidAccountCurrency(t
|
|||||||
assert.Equal(t, "EUR", allNewAccounts[1].Currency)
|
assert.Equal(t, "EUR", allNewAccounts[1].Currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndCurrency(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidForeignAmountAndCurrency(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -202,8 +204,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
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\""), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -213,8 +215,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
|||||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||||
assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
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"+
|
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\""), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -223,8 +225,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
|||||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||||
assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
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"+
|
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\""), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -232,8 +234,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
|||||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
assert.Equal(t, "USD", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -241,19 +243,19 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAccountCurrency
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"\"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)
|
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"+
|
"\"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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -261,17 +263,17 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseNotSupportedCurrency(t
|
|||||||
DefaultCurrency: "CNY",
|
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,XXX,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
"\"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)
|
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"+
|
||||||
"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)
|
"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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -279,17 +281,17 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAmount(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,source_name,destination_name,category\n"+
|
_, _, _, _, _, _, 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\""), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseDescription(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -297,16 +299,16 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseDescription(t *testing
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,description,date,source_name,destination_name,category\n"+
|
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"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseTags(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_ParseTags(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -314,8 +316,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseTags(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte("type,amount,tags,date,source_name,destination_name,category\n"+
|
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"), 0, nil, nil, nil, nil, nil)
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -328,8 +330,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseTags(t *testing.T) {
|
|||||||
assert.Equal(t, "tag3", allNewTags[2].Name)
|
assert.Equal(t, "tag3", allNewTags[2].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_MissingFileHeader(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -337,12 +339,12 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MissingFileHeader(t *testin
|
|||||||
DefaultCurrency: "CNY",
|
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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireFlyIIICsvFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
func TestFireFlyIIICsvFileimporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||||
converter := FireflyIIITransactionDataCsvFileImporter
|
importer := FireflyIIITransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -351,32 +353,32 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MissingRequiredColumn(t *te
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Time Column
|
// Missing Time Column
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,source_name,destination_name,category\n"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"\"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Type Column
|
// Missing Type Column
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("amount,date,source_name,destination_name,category\n"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Sub Category Column
|
// Missing Sub Category Column
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name\n"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"\"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Account Name Column
|
// Missing Account Name Column
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,destination_name,category\n"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"\"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Amount Column
|
// Missing Amount Column
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,date,source_name,destination_name,category\n"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"\"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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Account2 Name Column
|
// Missing Account2 Name Column
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,category\n"+
|
_, _, _, _, _, _, 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"), 0, nil, nil, nil, nil, nil)
|
"\"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)
|
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_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
|
// trim trailing zero in decimal
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package gnucash
|
package gnucash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -24,7 +26,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ParseImportedData returns the imported data by parsing the gnucash transaction data
|
// 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)
|
gnucashDataReader, err := createNewGnuCashDatabaseReader(data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -45,5 +47,5 @@ func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, use
|
|||||||
|
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(gnucashTransactionTypeNameMapping)
|
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"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -185,7 +187,7 @@ const gnucashCommonValidDataCaseFooter = "</gnc:book>\n" +
|
|||||||
"</gnc-v2>\n"
|
"</gnc-v2>\n"
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidData(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -193,14 +195,14 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidData(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
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)
|
assert.Nil(t, err)
|
||||||
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_GzippedMinimumValidData(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_GzippedMinimumValidData(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -217,14 +219,14 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_GzippedMinimumValidData
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
gzippedData := buffer.Bytes()
|
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)
|
assert.Nil(t, err)
|
||||||
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithReversedSplitOrder(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithReversedSplitOrder(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -232,7 +234,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithRev
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"<gnc-v2\n"+
|
||||||
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
||||||
" xmlns:act=\"http://www.gnucash.org/XML/act\"\n"+
|
" xmlns:act=\"http://www.gnucash.org/XML/act\"\n"+
|
||||||
@@ -356,14 +358,14 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithRev
|
|||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\n"+
|
"</gnc:transaction>\n"+
|
||||||
"</gnc:book>\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)
|
assert.Nil(t, err)
|
||||||
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -371,7 +373,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -388,10 +390,10 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -408,12 +410,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -421,7 +423,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -438,12 +440,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
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+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -460,14 +462,14 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -475,7 +477,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurren
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"<gnc-v2\n"+
|
||||||
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
||||||
" xmlns:act=\"http://www.gnucash.org/XML/act\"\n"+
|
" xmlns:act=\"http://www.gnucash.org/XML/act\"\n"+
|
||||||
@@ -557,7 +559,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurren
|
|||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\n"+
|
"</gnc:transaction>\n"+
|
||||||
"</gnc:book>\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -574,7 +576,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurren
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -582,7 +584,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -599,13 +601,13 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, int64(1234500), allNewTransactions[0].Amount)
|
assert.Equal(t, int64(1234500), allNewTransactions[0].Amount)
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -622,7 +624,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -630,7 +632,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -638,7 +640,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -655,10 +657,10 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -675,10 +677,10 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -695,12 +697,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -708,7 +710,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -726,7 +728,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *tes
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -734,7 +736,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransaction(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransaction(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -742,7 +744,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransacti
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -755,12 +757,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransacti
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSplitTransaction(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSplitTransaction(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -768,7 +770,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSpli
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:account version=\"2.0.0\">\n"+
|
"<gnc:account version=\"2.0.0\">\n"+
|
||||||
" <act:name>Test Category2</act:name>\n"+
|
" <act:name>Test Category2</act:name>\n"+
|
||||||
@@ -805,12 +807,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSpli
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredNode(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredNode(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -819,7 +821,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredN
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Account Currency Node
|
// Missing Account Currency Node
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"+
|
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"+
|
||||||
"<gnc-v2\n"+
|
"<gnc-v2\n"+
|
||||||
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
" xmlns:gnc=\"http://www.gnucash.org/XML/gnc\"\n"+
|
||||||
@@ -872,12 +874,12 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredN
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
||||||
converter := GnuCashTransactionDataImporter
|
importer := GnuCashTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -886,7 +888,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Transaction Time Node
|
// Missing Transaction Time Node
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:splits>\n"+
|
" <trn:splits>\n"+
|
||||||
@@ -900,22 +902,22 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
|
||||||
|
|
||||||
// Missing Transaction Splits Node
|
// Missing Transaction Splits Node
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
" <ts:date>2024-09-01 00:00:00 +0000</ts:date>\n"+
|
" <ts:date>2024-09-01 00:00:00 +0000</ts:date>\n"+
|
||||||
" </trn:date-posted>\n"+
|
" </trn:date-posted>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrInvalidGnuCashFile.Message)
|
||||||
|
|
||||||
// Missing Transaction Split Quantity Node
|
// Missing Transaction Split Quantity Node
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -930,11 +932,11 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
// Missing Transaction Split Account Node
|
// Missing Transaction Split Account Node
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
gnucashCommonValidDataCaseHeader+
|
gnucashCommonValidDataCaseHeader+
|
||||||
"<gnc:transaction version=\"2.0.0\">\n"+
|
"<gnc:transaction version=\"2.0.0\">\n"+
|
||||||
" <trn:date-posted>\n"+
|
" <trn:date-posted>\n"+
|
||||||
@@ -950,7 +952,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
|
|||||||
" </trn:split>\n"+
|
" </trn:split>\n"+
|
||||||
" </trn:splits>\n"+
|
" </trn:splits>\n"+
|
||||||
"</gnc:transaction>\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)
|
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_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 {
|
if len(gnucashTransaction.Splits) == 2 {
|
||||||
splitData1 := gnucashTransaction.Splits[0]
|
splitData1 := gnucashTransaction.Splits[0]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package iif
|
package iif
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -23,7 +25,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ParseImportedData returns the imported data by parsing the intuit interchange format (iif) data
|
// 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)
|
iifDataReader := createNewIifDataReader(data)
|
||||||
accountDatasets, transactionDatasets, err := iifDataReader.read(ctx)
|
accountDatasets, transactionDatasets, err := iifDataReader.read(ctx)
|
||||||
|
|
||||||
@@ -39,5 +41,5 @@ func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, use
|
|||||||
|
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(iifTransactionTypeNameMapping)
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -12,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -20,7 +22,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
|||||||
DefaultCurrency: "CNY",
|
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\tNAME\tACCNTTYPE\n"+
|
||||||
"ACCNT\tTest Account\tBANK\n"+
|
"ACCNT\tTest Account\tBANK\n"+
|
||||||
"ACCNT\tTest Account2\tBANK\n"+
|
"ACCNT\tTest Account2\tBANK\n"+
|
||||||
@@ -49,7 +51,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
|||||||
"ENDTRNS\t\t\t\t\n"+
|
"ENDTRNS\t\t\t\t\n"+
|
||||||
"TRNS\tCREDIT CARD\t09/07/2024\tTest Category2\t34.56\n"+
|
"TRNS\tCREDIT CARD\t09/07/2024\tTest Category2\t34.56\n"+
|
||||||
"SPL\tCREDIT CARD\t09/07/2024\tTest Account2\t-34.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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -132,7 +134,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountData(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountData(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -140,13 +142,13 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountD
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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 Category\t-123.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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -162,7 +164,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountD
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -170,7 +172,7 @@ func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
||||||
"ACCNT\tTest Account3\tBANK\n"+
|
"ACCNT\tTest Account3\tBANK\n"+
|
||||||
"ACCNT\tTest Account4\tBANK\n"+
|
"ACCNT\tTest Account4\tBANK\n"+
|
||||||
@@ -202,7 +204,7 @@ func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
|||||||
"ENDTRNS\t\t\t\t\n"+
|
"ENDTRNS\t\t\t\t\n"+
|
||||||
"!ACCNT\tTEST\tNAME\tACCNTTYPE\n"+
|
"!ACCNT\tTEST\tNAME\tACCNTTYPE\n"+
|
||||||
"ACCNT\t\tTest Category\tINC\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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -247,7 +249,7 @@ func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -255,7 +257,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *
|
|||||||
DefaultCurrency: "CNY",
|
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\tNAME\tACCNTTYPE\n"+
|
||||||
"ACCNT\tTest Parent Category:Test Category\tINC\n"+
|
"ACCNT\tTest Parent Category:Test Category\tINC\n"+
|
||||||
"ACCNT\tTest Parent Category2:Test Category2\tEXP\n"+
|
"ACCNT\tTest Parent Category2:Test Category2\tEXP\n"+
|
||||||
@@ -267,7 +269,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *
|
|||||||
"ENDTRNS\t\t\t\n"+
|
"ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t09/02/2024\tTest Account2\t-123.45\n"+
|
"TRNS\t09/02/2024\tTest Account2\t-123.45\n"+
|
||||||
"SPL\t09/02/2024\tTest Parent Category2:Test Category2\t123.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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -299,7 +301,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -307,7 +309,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
@@ -322,7 +324,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *
|
|||||||
"ENDTRNS\t\t\t\n"+
|
"ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t2024/9/4\tTest Account\t123.45\n"+
|
"TRNS\t2024/9/4\tTest Account\t123.45\n"+
|
||||||
"SPL\t2024/9/4\tTest Account2\t-123.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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -334,7 +336,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTime(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTime(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -342,7 +344,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTim
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
@@ -354,7 +356,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTim
|
|||||||
"ENDTRNS\t\t\t\n"+
|
"ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t9/3/2024\tTest Account\t123.45\n"+
|
"TRNS\t9/3/2024\tTest Account\t123.45\n"+
|
||||||
"SPL\t9/3/2024\tTest Account2\t-123.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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -365,7 +367,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTim
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYearFormatTime(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYearFormatTime(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -373,7 +375,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYear
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
@@ -385,7 +387,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYear
|
|||||||
"ENDTRNS\t\t\t\n"+
|
"ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t24/9/3\tTest Account\t123.45\n"+
|
"TRNS\t24/9/3\tTest Account\t123.45\n"+
|
||||||
"SPL\t24/9/3\tTest Account2\t-123.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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -396,7 +398,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYear
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -404,36 +406,36 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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"+
|
"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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t2024-09-01\tTest Account\t123.45\n"+
|
"TRNS\t2024-09-01\tTest Account\t123.45\n"+
|
||||||
"SPL\t2024-09-01\tTest Account2\t-123.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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t9/24\tTest Account\t123.45\n"+
|
"TRNS\t9/24\tTest Account\t123.45\n"+
|
||||||
"SPL\t9/24\tTest Account2\t-123.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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparator(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparator(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -441,13 +443,13 @@ func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t9/01/2024\tTest Account\t123,456.78\n"+
|
"TRNS\t9/01/2024\tTest Account\t123,456.78\n"+
|
||||||
"SPL\t9/01/2024\tTest Account2\t-123,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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -456,7 +458,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -464,27 +466,27 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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"+
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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"+
|
"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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -492,25 +494,25 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||||
"!ENDTRNS\t\t\t\t\t\n"+
|
"!ENDTRNS\t\t\t\t\t\n"+
|
||||||
"TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\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"+
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
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"+
|
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||||
"!ENDTRNS\t\t\t\t\t\n"+
|
"!ENDTRNS\t\t\t\t\t\n"+
|
||||||
"TRNS\t09/01/2024\tTest Account\tTest\t123.45\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"+
|
"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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -518,7 +520,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -526,7 +528,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testin
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
||||||
"ACCNT\tTest Category\tINC\n"+
|
"ACCNT\tTest Category\tINC\n"+
|
||||||
"ACCNT\tTest Category2\tEXP\n"+
|
"ACCNT\tTest Category2\tEXP\n"+
|
||||||
@@ -552,7 +554,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testin
|
|||||||
"TRNS\t09/05/2024\tTest Category2\t100.00\n"+
|
"TRNS\t09/05/2024\tTest Category2\t100.00\n"+
|
||||||
"SPL\t09/05/2024\tTest Account3\t-40.00\n"+
|
"SPL\t09/05/2024\tTest Account3\t-40.00\n"+
|
||||||
"SPL\t09/05/2024\tTest Account4\t-60.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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -651,7 +653,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescription(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescription(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -659,21 +661,21 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescriptio
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||||
"!ENDTRNS\t\t\t\t\t\n"+
|
"!ENDTRNS\t\t\t\t\t\n"+
|
||||||
"TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\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 Account2\t\t-100.00\t\"foo\ttest#bar\"\n"+
|
||||||
"SPL\t09/01/2024\tTest Account3\t\t-23.45\t\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.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(allNewTransactions))
|
assert.Equal(t, 2, len(allNewTransactions))
|
||||||
assert.Equal(t, "foo\ttest#bar", allNewTransactions[0].Comment)
|
assert.Equal(t, "foo\ttest#bar", allNewTransactions[0].Comment)
|
||||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[1].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"+
|
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||||
"!ENDTRNS\t\t\t\t\t\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 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 Account3\tfoo\t-12.34\t\n"+
|
||||||
"SPL\t09/01/2024\tTest Account4\t\t-11.11\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.Nil(t, err)
|
||||||
assert.Equal(t, 3, len(allNewTransactions))
|
assert.Equal(t, 3, len(allNewTransactions))
|
||||||
@@ -691,7 +693,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescriptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -700,52 +702,52 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Opening balance transaction
|
// Opening balance transaction
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tTRNSTYPE\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tTRNSTYPE\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tTRNSTYPE\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tTRNSTYPE\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\t\n"+
|
"!ENDTRNS\t\t\t\t\n"+
|
||||||
"TRNS\tBEGINBALCHECK\t09/01/2024\tTest Account\t123.45\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 Account2\t-100.00\n"+
|
||||||
"SPL\tBEGINBALCHECK\t09/01/2024\tTest Account3\t-23.45\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)
|
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||||
|
|
||||||
// Transaction with invalid amount
|
// Transaction with invalid amount
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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-100.00\n"+
|
"SPL\t09/01/2024\tTest Account2\t-100.00\n"+
|
||||||
"SPL\t09/01/2024\tTest Account3\t-23.45\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
// Transaction split data with invalid amount
|
// Transaction split data with invalid amount
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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-100 00\n"+
|
"SPL\t09/01/2024\tTest Account2\t-100 00\n"+
|
||||||
"SPL\t09/01/2024\tTest Account3\t-23.45\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
|
||||||
// Transaction amount not equal to sum of split data amount
|
// 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"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t09/01/2024\tTest Account\t123.00\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 Account2\t-100.00\n"+
|
||||||
"SPL\t09/01/2024\tTest Account3\t-23.45\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)
|
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -754,75 +756,75 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Missing Transaction Line
|
//Missing Transaction Line
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"SPL\t09/01/2024\tTest Account2\t-123.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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Missing Transaction And Split Line
|
// Missing Transaction And Split Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Missing Split Line
|
// Missing Split Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t09/01/2024\tTest Account\t123.45\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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Missing Transaction End Line
|
// Missing Transaction End Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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"), 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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Missing Transaction End Line (following is another header)
|
// 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"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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"+
|
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||||
"!ACCNT\tNAME\tACCNTTYPE\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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Invalid Line
|
// Invalid Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\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"+
|
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||||
"TEST\t\t\t\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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Repeat Transaction Line
|
// Repeat Transaction Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
"TRNS\t09/01/2024\tTest Account\t123.45\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"+
|
"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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Repeat Transaction End Line
|
// Repeat Transaction End Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\t\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\t\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\t\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\t\n"+
|
||||||
"!ENDTRNS\t\t\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"+
|
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||||
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+
|
||||||
"ENDTRNS\t\t\t\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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -844,49 +846,49 @@ func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing All Sample Lines
|
// 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"+
|
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||||
"SPL\t09/01/2024\tTest Account2\t-123.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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Missing Transaction Sample Line
|
// Missing Transaction Sample Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Missing Split Sample Line
|
// Missing Split Sample Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Missing Transaction End Sample Line
|
// Missing Transaction End Sample Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Missing Transaction End Sample Line (following is data line)
|
// 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"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
"!SPL\tDATE\tACCNT\tAMOUNT\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"+
|
"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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
|
|
||||||
// Invalid Sample Line
|
// Invalid Sample Line
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||||
"!TEST\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)
|
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testing.T) {
|
func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||||
converter := IifTransactionDataFileImporter
|
importer := IifTransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -895,32 +897,32 @@ func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Date Column
|
// Missing Date Column
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tACCNT\tAMOUNT\t\n"+
|
"!TRNS\tACCNT\tAMOUNT\t\n"+
|
||||||
"!SPL\tACCNT\tAMOUNT\t\n"+
|
"!SPL\tACCNT\tAMOUNT\t\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\tTest Account\t123.45\n"+
|
"TRNS\tTest Account\t123.45\n"+
|
||||||
"SPL\tTest Account2\t-123.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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Account Column
|
// Missing Account Column
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tAMOUNT\t\n"+
|
"!TRNS\tDATE\tAMOUNT\t\n"+
|
||||||
"!SPL\tDATE\tAMOUNT\t\n"+
|
"!SPL\tDATE\tAMOUNT\t\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t09/01/2024\t123.45\n"+
|
"TRNS\t09/01/2024\t123.45\n"+
|
||||||
"SPL\t09/01/2024\t-123.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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Amount Column
|
// Missing Amount Column
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"!TRNS\tDATE\tACCNT\t\n"+
|
"!TRNS\tDATE\tACCNT\t\n"+
|
||||||
"!SPL\tDATE\tACCNT\t\n"+
|
"!SPL\tDATE\tACCNT\t\n"+
|
||||||
"!ENDTRNS\t\t\t\n"+
|
"!ENDTRNS\t\t\t\n"+
|
||||||
"TRNS\t09/01/2024\tTest Account\n"+
|
"TRNS\t09/01/2024\tTest Account\n"+
|
||||||
"SPL\t09/01/2024\tTest Account2\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package jdcom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/text/encoding/unicode"
|
"golang.org/x/text/encoding/unicode"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
@@ -27,7 +28,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ParseImportedData returns the imported data by parsing the jd.com finance transaction csv data
|
// 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()
|
fallback := unicode.UTF8.NewDecoder()
|
||||||
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
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)
|
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, jdComFinanceTransactionSupportedColumns, transactionRowParser)
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(jdComFinanceTransactionTypeNameMapping)
|
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/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
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 12:34:56,xxx,xxx,123.45,银行卡,交易成功,支出,其他网购\n" +
|
||||||
"2025-09-01 23:59:59,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n" +
|
"2025-09-01 23:59:59,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n" +
|
||||||
"2025-09-02 23:59:59,xxx,京东余额提现,0.03,银行卡,交易成功,不计收支,余额\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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 4, len(allNewTransactions))
|
assert.Equal(t, 4, len(allNewTransactions))
|
||||||
@@ -93,7 +94,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MinimumValidData(t *testin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -110,7 +111,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *
|
|||||||
"2025-09-01 02:34:56,xxx,xxx,0.12(已全额退款),银行卡,交易成功,不计收支\n" +
|
"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 01:23:45,xxx,xxx,3.45,银行卡,退款成功,不计收支\n" +
|
||||||
"2025-09-02 02:34:56,xxx,xxx,123.45(已退款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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||||
@@ -139,7 +140,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -153,7 +154,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testin
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01T01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
data2 := "导出信息:\n" +
|
data2 := "导出信息:\n" +
|
||||||
@@ -162,12 +163,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testin
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"09/01/2025 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -181,12 +182,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidType(t *testin
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,转账\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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -200,12 +201,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidAmount(t *test
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,¥0.12,银行卡,交易成功,支出\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -220,7 +221,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||||
"2025-09-01 01:23:45,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -235,7 +236,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||||
"2025-09-01 01:23:45,xxx,京东余额提现,0.05,银行卡,交易成功,不计收支,余额\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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
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"
|
"2025-09-01 01:23:45,xxx,京东小金库-转入,0.05,余额,交易成功,不计收支,小金库\n"
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
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"
|
"2025-09-01 01:23:45,xxx,京东小金库-转出,0.05,余额,交易成功,不计收支,小金库\n"
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
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"
|
"2025-09-01 01:23:45,xxx,价保退款,0.05,银行卡,交易成功,不计收支,其他\n"
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
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"
|
"2025-09-01 01:23:45,xxx,白条主动还款,0.05,银行卡,交易成功,不计收支,白条\n"
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -312,7 +313,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -326,7 +327,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,,0.12,银行卡,交易成功,支出\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.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -338,7 +339,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
|
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\"foo\"\"bar,\ntest\"\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, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment)
|
assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment)
|
||||||
|
|
||||||
@@ -348,13 +349,13 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
|
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\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, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -368,12 +369,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownStatusTransacti
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,xxxx,支出\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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownMemoTransferTransaction(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownMemoTransferTransaction(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -387,12 +388,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownMemoTransferTra
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,不计收支\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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -402,15 +403,15 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingFileHeader(t *testi
|
|||||||
|
|
||||||
data := "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
data := "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\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)
|
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)
|
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -425,7 +426,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
|||||||
"\n" +
|
"\n" +
|
||||||
"商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"xxx,xxx,0.12,银行卡,交易成功,支出\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)
|
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||||
|
|
||||||
// Missing Merchant Name Column
|
// Missing Merchant Name Column
|
||||||
@@ -435,7 +436,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Transaction Memo Column
|
// Missing Transaction Memo Column
|
||||||
@@ -445,7 +446,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,金额,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,金额,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Amount Column
|
// Missing Amount Column
|
||||||
@@ -455,7 +456,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,收/付款方式,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,收/付款方式,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,银行卡,交易成功,支出\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Related Account Column
|
// Missing Related Account Column
|
||||||
@@ -465,7 +466,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,交易状态,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,交易状态,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,0.12,交易成功,支出\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Status Column
|
// Missing Status Column
|
||||||
@@ -475,7 +476,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,收/支\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,收/支\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,支出\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Type Column
|
// Missing Type Column
|
||||||
@@ -485,12 +486,12 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
|
|||||||
"\n" +
|
"\n" +
|
||||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态\n" +
|
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态\n" +
|
||||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功\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)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJDComFinanceCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
func TestJDComFinanceCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
||||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
importer := JDComFinanceTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -503,6 +504,6 @@ func TestJDComFinanceCsvFileImporterParseImportedData_NoTransactionData(t *testi
|
|||||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||||
"\n" +
|
"\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)
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package mt
|
package mt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -22,7 +24,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ParseImportedData returns the imported data by parsing the mt940 file statement data
|
// 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)
|
mt940DataReader := createNewMT940FileReader(data)
|
||||||
mt940Data, err := mt940DataReader.read(ctx)
|
mt940Data, err := mt940DataReader.read(ctx)
|
||||||
|
|
||||||
@@ -38,5 +40,5 @@ func (c *mt940TransactionDataFileImporter) ParseImportedData(ctx core.Context, u
|
|||||||
|
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(mt940TransactionTypeNameMapping)
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -12,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := MT940TransactionDataFileImporter
|
importer := MT940TransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -20,7 +22,7 @@ func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T
|
|||||||
DefaultCurrency: "CNY",
|
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:
|
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||||
:20:123456789
|
:20:123456789
|
||||||
:25:12345678
|
:25:12345678
|
||||||
@@ -31,7 +33,7 @@ func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T
|
|||||||
:61:2506020603D234,56NTRFFOOBAR
|
:61:2506020603D234,56NTRFFOOBAR
|
||||||
:86:Transaction 2
|
:86:Transaction 2
|
||||||
:62F:C250601CNY123,45
|
:62F:C250601CNY123,45
|
||||||
-}`), 0, nil, nil, nil, nil, nil)
|
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAndCurrency(t *testing.T) {
|
func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAndCurrency(t *testing.T) {
|
||||||
converter := MT940TransactionDataFileImporter
|
importer := MT940TransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -78,7 +80,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAn
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||||
:20:123456789
|
:20:123456789
|
||||||
:25:12345678
|
:25:12345678
|
||||||
@@ -91,7 +93,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAn
|
|||||||
:61:250603C1,NTRFTEST
|
:61:250603C1,NTRFTEST
|
||||||
:86:Transaction 3
|
:86:Transaction 3
|
||||||
:62F:C250601CNY123,45
|
:62F:C250601CNY123,45
|
||||||
-}`), 0, nil, nil, nil, nil, nil)
|
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 3, len(allNewTransactions))
|
assert.Equal(t, 3, len(allNewTransactions))
|
||||||
@@ -101,7 +103,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmountAndCurrency(t *testing.T) {
|
func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmountAndCurrency(t *testing.T) {
|
||||||
converter := MT940TransactionDataFileImporter
|
importer := MT940TransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -109,7 +111,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||||
:20:123456789
|
:20:123456789
|
||||||
:25:12345678
|
:25:12345678
|
||||||
@@ -117,10 +119,10 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
|
|||||||
:60F:C250601CNY123,45
|
:60F:C250601CNY123,45
|
||||||
:61:2506010602C123 45NTRFTEST
|
:61:2506010602C123 45NTRFTEST
|
||||||
:62F:C250601CNY123,45
|
: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)
|
assert.EqualError(t, err, errs.ErrInvalidMT940File.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||||
:20:123456789
|
:20:123456789
|
||||||
:25:12345678
|
:25:12345678
|
||||||
@@ -128,12 +130,12 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
|
|||||||
:60F:C250601CNY123,45
|
:60F:C250601CNY123,45
|
||||||
:61:2506010602C12.45NTRFTEST
|
:61:2506010602C12.45NTRFTEST
|
||||||
:62F:C250601CNY123,45
|
: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)
|
assert.EqualError(t, err, errs.ErrInvalidMT940File.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testing.T) {
|
func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testing.T) {
|
||||||
converter := MT940TransactionDataFileImporter
|
importer := MT940TransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -141,7 +143,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testi
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||||
:20:123456789
|
:20:123456789
|
||||||
:25:12345678
|
:25:12345678
|
||||||
@@ -156,7 +158,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testi
|
|||||||
:61:250604RD123,45NTRFTEST
|
:61:250604RD123,45NTRFTEST
|
||||||
:86:Transaction 4
|
:86:Transaction 4
|
||||||
:62F:C250601CNY123,45
|
:62F:C250601CNY123,45
|
||||||
-}`), 0, nil, nil, nil, nil, nil)
|
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 4, len(allNewTransactions))
|
assert.Equal(t, 4, len(allNewTransactions))
|
||||||
@@ -167,7 +169,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := MT940TransactionDataFileImporter
|
importer := MT940TransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -175,7 +177,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||||
:20:123456789
|
:20:123456789
|
||||||
:25:12345678
|
:25:12345678
|
||||||
@@ -186,7 +188,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T
|
|||||||
Part 2
|
Part 2
|
||||||
Part 3
|
Part 3
|
||||||
:62F:C250601CNY123,45
|
:62F:C250601CNY123,45
|
||||||
-}`), 0, nil, nil, nil, nil, nil)
|
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -194,7 +196,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMT940TransactionDataFileParseImportedData_MissingRequiredField(t *testing.T) {
|
func TestMT940TransactionDataFileParseImportedData_MissingRequiredField(t *testing.T) {
|
||||||
converter := MT940TransactionDataFileImporter
|
importer := MT940TransactionDataFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -203,12 +205,12 @@ func TestMT940TransactionDataFileParseImportedData_MissingRequiredField(t *testi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing opening balance and closing balance
|
// Missing opening balance and closing balance
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
`{1:F01TESTBANK123456789}{2:I940TESTBANK}{4:
|
||||||
:20:123456789
|
:20:123456789
|
||||||
:28C:123/1
|
:28C:123/1
|
||||||
:61:250601C123,45NTRFTEST
|
:61:250601C123,45NTRFTEST
|
||||||
:86:Transaction 1
|
: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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
const ofx1USAsciiEncoding = "usascii"
|
const ofx1USAsciiEncoding = "usascii"
|
||||||
const ofx1UnicodeEncoding = "unicode"
|
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"
|
const ofx1SGMLDataFormat = "OFXSGML"
|
||||||
|
|
||||||
var ofx2HeaderPattern = regexp.MustCompile("<\\?OFX( +[A-Z]+=\"[^=]*\")* *\\?>")
|
var ofx2HeaderPattern = regexp.MustCompile("<\\?OFX( +[A-Z]+=\"[^=]*\")* *\\?>")
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package ofx
|
package ofx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"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
|
// 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)
|
ofxDataReader, err := createNewOFXFileReader(ctx, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -44,5 +46,5 @@ func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *m
|
|||||||
|
|
||||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(ofxTransactionTypeNameMapping)
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
@@ -12,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -20,7 +22,7 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
|||||||
DefaultCurrency: "CNY",
|
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"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -76,7 +78,7 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
|||||||
" </CCSTMTRS>\n"+
|
" </CCSTMTRS>\n"+
|
||||||
" </CCSTMTTRNRS>\n"+
|
" </CCSTMTTRNRS>\n"+
|
||||||
" </CREDITCARDMSGSRSV1>\n"+
|
" </CREDITCARDMSGSRSV1>\n"+
|
||||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -160,7 +162,7 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -168,7 +170,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -210,7 +212,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
|||||||
" </CCSTMTRS>\n"+
|
" </CCSTMTRS>\n"+
|
||||||
" </CCSTMTTRNRS>\n"+
|
" </CCSTMTTRNRS>\n"+
|
||||||
" </CREDITCARDMSGSRSV1>\n"+
|
" </CREDITCARDMSGSRSV1>\n"+
|
||||||
"</OFX>"), 0, nil, nil, nil, nil, nil)
|
"</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -243,7 +245,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -251,7 +253,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -295,7 +297,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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.Nil(t, err)
|
||||||
|
|
||||||
@@ -310,7 +312,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -318,7 +320,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -337,10 +339,10 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -359,10 +361,10 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -381,10 +383,10 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -403,12 +405,12 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -416,7 +418,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -435,7 +437,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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.Nil(t, err)
|
||||||
|
|
||||||
@@ -444,7 +446,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -452,7 +454,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -471,12 +473,12 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -484,7 +486,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -504,7 +506,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -512,7 +514,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -520,7 +522,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
|||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -541,13 +543,13 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
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"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -567,13 +569,13 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
||||||
|
|
||||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -595,7 +597,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -603,7 +605,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -612,7 +614,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Posted Date Node
|
// Missing Posted Date Node
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -628,12 +630,12 @@ func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testi
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrMissingAccountData.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -642,7 +644,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Default Currency Node
|
// Missing Default Currency Node
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -660,12 +662,12 @@ func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(t *testing.T) {
|
||||||
converter := OFXTransactionDataImporter
|
importer := OFXTransactionDataImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
@@ -674,7 +676,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing Posted Date Node
|
// Missing Posted Date Node
|
||||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -692,11 +694,11 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
|
||||||
|
|
||||||
// Missing Transaction Type Node
|
// Missing Transaction Type Node
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -714,11 +716,11 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||||
|
|
||||||
// Missing Amount Node
|
// Missing Amount Node
|
||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
|
||||||
"<OFX>\n"+
|
"<OFX>\n"+
|
||||||
" <BANKMSGSRSV1>\n"+
|
" <BANKMSGSRSV1>\n"+
|
||||||
" <STMTTRNRS>\n"+
|
" <STMTTRNRS>\n"+
|
||||||
@@ -736,6 +738,6 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
|
|||||||
" </STMTRS>\n"+
|
" </STMTRS>\n"+
|
||||||
" </STMTTRNRS>\n"+
|
" </STMTTRNRS>\n"+
|
||||||
" </BANKMSGSRSV1>\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)
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user