Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b90ced103 | |||
| 3a4b44762f | |||
| 20e2444307 | |||
| 8154bd712b | |||
| 4d0e376568 | |||
| 32cf41a7a0 | |||
| e85a4701ed | |||
| b79ffafaee | |||
| 8f6adaa417 | |||
| 0e634d83f4 | |||
| af8cbe0b15 | |||
| 411130db4e | |||
| c099443783 | |||
| 23ffdbb163 | |||
| 0b48502a10 | |||
| 25681f622d | |||
| f196ce969b | |||
| 0408c470fc | |||
| 01aeb945ff | |||
| 601a1f83c6 | |||
| 2a470742e0 | |||
| 8ba1e1997f | |||
| 27ae401a7f | |||
| 81727d3b1e | |||
| 06a0501633 | |||
| 781c2d9044 | |||
| 15e4ad00ee | |||
| 8064a00252 | |||
| f2d0fe407b | |||
| 9589657fd5 | |||
| 790837076f | |||
| 6d923027a0 | |||
| 13d5759e84 | |||
| efe39c7390 | |||
| c00770201b | |||
| 4eff3a337f | |||
| 451385011e | |||
| cd4d230d29 | |||
| ab6d4ee6fc | |||
| 274aa6a17c | |||
| 2f8d4ad5e4 | |||
| fe59d3b280 | |||
| e2c99c4f04 | |||
| 127393b64a | |||
| f3d240442b | |||
| 55bf8b9e30 | |||
| eadcf7768f | |||
| 876bf8cc31 | |||
| 6b5aac0111 | |||
| dc4a4e1463 | |||
| 0677ed07db | |||
| ecf6fbd187 | |||
| 351cebe169 | |||
| 0f94a90882 | |||
| 04996d784f | |||
| aafcfeda84 | |||
| 7283b724b1 | |||
| 0d55912f6c | |||
| 60108e26c7 | |||
| be129cd3c6 | |||
| f210bfa9f4 | |||
| 263113a67f | |||
| 3b29303237 | |||
| 6e5f857e97 | |||
| 791c0ea26e | |||
| 84523d8b8a | |||
| d35e127b9e | |||
| ebe00d3271 | |||
| 14b4e40039 | |||
| 15d1d269ae | |||
| e90b76c80e | |||
| e28e27080a | |||
| 2268496dcb | |||
| 3781327c58 | |||
| 51c33d7e83 | |||
| 975a56e7d9 | |||
| 29a87dcfaf | |||
| cad53d0bfc | |||
| 56a3905df1 | |||
| 428a1f2156 | |||
| b5233399e6 | |||
| f8878c5405 | |||
| 8dcaa457f9 | |||
| b24ebdb83e | |||
| d41a2141a7 | |||
| 09a1dd0358 | |||
| 531c4a44d5 | |||
| ceecff8c24 | |||
| f32cc4ab04 | |||
| 8fa46281e0 | |||
| f7bc4b3ab6 | |||
| ad4f5bd88d | |||
| e4cb66718d | |||
| 175b272fa0 | |||
| ca0fb9446b | |||
| 6eb749dca2 | |||
| 880b614636 | |||
| d146a99c65 | |||
| fd99c784b3 | |||
| 22f9c5243a | |||
| 67f5aaa5ee | |||
| 713b621169 | |||
| 80df5f95aa | |||
| 1e492d8724 | |||
| 602f15fe2e | |||
| 3335533a18 | |||
| d385358aa3 | |||
| d6ee8a416f | |||
| c5aa37037f | |||
| 6050f5deab | |||
| 5d07d1a70d | |||
| bae330c6f3 | |||
| ea17994c6c | |||
| c3d29ee2f8 | |||
| 515b9af61a | |||
| 4ba3893b83 | |||
| bcb6c4f419 | |||
| 53f101fb60 | |||
| 8da4f65048 | |||
| 428bcba56e | |||
| 68e896d8eb | |||
| eef62722a4 | |||
| e3dcb2ce0c | |||
| 0cf89562cd | |||
| 8b06731cdb | |||
| 36abd1acec |
@@ -1,5 +1,5 @@
|
||||
# Build backend binary file
|
||||
FROM golang:1.24.4-alpine3.22 AS be-builder
|
||||
FROM golang:1.24.5-alpine3.22 AS be-builder
|
||||
ARG RELEASE_BUILD
|
||||
ARG BUILD_PIPELINE
|
||||
ARG CHECK_3RD_API
|
||||
@@ -15,7 +15,7 @@ RUN apk add git gcc g++ libc-dev
|
||||
RUN ./build.sh backend
|
||||
|
||||
# Build frontend files
|
||||
FROM --platform=$BUILDPLATFORM node:22.16.0-alpine3.22 AS fe-builder
|
||||
FROM --platform=$BUILDPLATFORM node:22.18.0-alpine3.22 AS fe-builder
|
||||
ARG RELEASE_BUILD
|
||||
ARG BUILD_PIPELINE
|
||||
ENV RELEASE_BUILD=$RELEASE_BUILD
|
||||
@@ -27,7 +27,7 @@ RUN apk add git
|
||||
RUN ./build.sh frontend
|
||||
|
||||
# Package docker image
|
||||
FROM alpine:3.22.0
|
||||
FROM alpine:3.22.1
|
||||
LABEL maintainer="MaysWind <i@mayswind.net>"
|
||||
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
|
||||
RUN apk --no-cache add tzdata
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
[](https://github.com/mayswind/ezbookkeeping/blob/master/LICENSE)
|
||||
[](https://github.com/mayswind/ezbookkeeping/actions)
|
||||
[](https://goreportcard.com/report/github.com/mayswind/ezbookkeeping)
|
||||
[](https://deepwiki.com/mayswind/ezbookkeeping)
|
||||
[](https://hub.docker.com/r/mayswind/ezbookkeeping)
|
||||
[](https://github.com/mayswind/ezbookkeeping/releases)
|
||||
|
||||
## Introduction
|
||||
ezBookkeeping is a lightweight, self-hosted personal finance app with a sleek, user-friendly interface and powerful bookkeeping features. Built with simplicity and portability in mind, it's easy to deploy, easy to use, and requires minimal system resources — perfect for microservers, NAS devices, and even Raspberry Pi.
|
||||
[](https://hellogithub.com/en/repository/mayswind/ezbookkeeping)
|
||||
|
||||
The app is fully cross-platform and device-friendly — you can use it seamlessly on **mobile, tablet, 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.
|
||||
## 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 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.
|
||||
|
||||
Live Demo: [https://ezbookkeeping-demo.mayswind.net](https://ezbookkeeping-demo.mayswind.net)
|
||||
|
||||
@@ -100,7 +103,7 @@ You can also build a Docker image. Make sure you have [Docker](https://www.docke
|
||||
$ ./build.sh docker
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -111,7 +114,7 @@ Contributions of all kinds — bug reports, feature suggestions, documentation i
|
||||
Check out our [Contributor Graph](https://github.com/mayswind/ezbookkeeping/graphs/contributors) to see the amazing people who’ve already helped.
|
||||
|
||||
## 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. If you want to contribute a translation, please refer to our [translation guide](https://ezbookkeeping.mayswind.net/translating).
|
||||
|
||||
Currently available translations:
|
||||
|
||||
@@ -122,6 +125,7 @@ Currently available translations:
|
||||
| es | Español | [@Miguelonlonlon](https://github.com/Miguelonlonlon) |
|
||||
| it | Italiano | [@waron97](https://github.com/waron97) |
|
||||
| ja | 日本語 | [@tkymmm](https://github.com/tkymmm) |
|
||||
| nl | Nederlands | [@automagic](https://github.com/automagics) |
|
||||
| pt-BR | Português (Brasil) | [@thecodergus](https://github.com/thecodergus) |
|
||||
| ru | Русский | [@artegoser](https://github.com/artegoser) |
|
||||
| uk | Українська | [@nktlitvinenko](https://github.com/nktlitvinenko) |
|
||||
@@ -129,7 +133,7 @@ Currently available translations:
|
||||
| zh-Hans | 中文 (简体) | / |
|
||||
| zh-Hant | 中文 (繁體) | / |
|
||||
|
||||
Don't see your language? Help us add it!
|
||||
Don't see your language? Help us add it.
|
||||
|
||||
## Documentation
|
||||
1. [English](http://ezbookkeeping.mayswind.net)
|
||||
|
||||
@@ -112,7 +112,7 @@ goto :pre_parse_args
|
||||
set VERSION=%VERSION: =%
|
||||
set VERSION=%VERSION:,=%
|
||||
set VERSION=%VERSION:"=%
|
||||
for /f %%x in ('git rev-parse --short HEAD') do set "COMMIT_HASH=%%x"
|
||||
for /f %%x in ('git rev-parse --short^=7 HEAD') do set "COMMIT_HASH=%%x"
|
||||
call :set_unixtime BUILD_UNIXTIME
|
||||
call :set_date BUILD_DATE
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ check_type_dependencies() {
|
||||
|
||||
set_build_parameters() {
|
||||
VERSION="$(grep '"version": ' package.json | awk -F ':' '{print $2}' | tr -d ' ' | tr -d ',' | tr -d '"')"
|
||||
COMMIT_HASH="$(git rev-parse --short HEAD)"
|
||||
COMMIT_HASH="$(git rev-parse --short=7 HEAD)"
|
||||
BUILD_UNIXTIME="$(date '+%s')"
|
||||
}
|
||||
|
||||
|
||||
@@ -158,5 +158,9 @@ func getConfigWithoutSensitiveData(config *settings.Config) *settings.Config {
|
||||
clonedConfig.SecretKey = "****"
|
||||
clonedConfig.AmapApplicationSecret = "****"
|
||||
|
||||
if clonedConfig.WebDAVConfig != nil {
|
||||
clonedConfig.WebDAVConfig.Password = "****"
|
||||
}
|
||||
|
||||
return clonedConfig
|
||||
}
|
||||
|
||||
@@ -268,6 +268,19 @@ var UserData = &cli.Command{
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-session-revoke",
|
||||
Usage: "Revoke the specified user session",
|
||||
Action: bindAction(revokeUserToken),
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "token",
|
||||
Aliases: []string{"t"},
|
||||
Required: false,
|
||||
Usage: "Specific token content",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-session-clear",
|
||||
Usage: "Clear user all sessions",
|
||||
@@ -732,6 +745,26 @@ func createNewUserToken(c *core.CliContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func revokeUserToken(c *core.CliContext) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token := c.String("token")
|
||||
err = clis.UserData.RevokeUserToken(c, token)
|
||||
|
||||
if err != nil {
|
||||
log.CliErrorf(c, "[user_data.revokeUserToken] error occurs when revoking user token")
|
||||
return err
|
||||
}
|
||||
|
||||
log.CliInfof(c, "[user_data.revokeUserToken] the specified user token has been revoked successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearUserTokens(c *core.CliContext) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
@@ -913,15 +946,18 @@ func printUserInfo(user *models.User) {
|
||||
fmt.Printf("[DefaultCurrency] %s\n", user.DefaultCurrency)
|
||||
fmt.Printf("[FirstDayOfWeek] %s (%d)\n", user.FirstDayOfWeek, user.FirstDayOfWeek)
|
||||
fmt.Printf("[FiscalYearStart] %s (%d)\n", user.FiscalYearStart, user.FiscalYearStart)
|
||||
fmt.Printf("[CalendarDisplayType] %s (%d)\n", user.CalendarDisplayType, user.CalendarDisplayType)
|
||||
fmt.Printf("[DateDisplayType] %s (%d)\n", user.DateDisplayType, user.DateDisplayType)
|
||||
fmt.Printf("[LongDateFormat] %s (%d)\n", user.LongDateFormat, user.LongDateFormat)
|
||||
fmt.Printf("[ShortDateFormat] %s (%d)\n", user.ShortDateFormat, user.ShortDateFormat)
|
||||
fmt.Printf("[LongTimeFormat] %s (%d)\n", user.LongTimeFormat, user.LongTimeFormat)
|
||||
fmt.Printf("[ShortTimeFormat] %s (%d)\n", user.ShortTimeFormat, user.ShortTimeFormat)
|
||||
fmt.Printf("[FiscalYearFormat] %s (%d)\n", user.FiscalYearFormat, user.FiscalYearFormat)
|
||||
fmt.Printf("[CurrencyDisplayType] %s (%d)\n", user.CurrencyDisplayType, user.CurrencyDisplayType)
|
||||
fmt.Printf("[NumeralSystem] %s (%d)\n", user.NumeralSystem, user.NumeralSystem)
|
||||
fmt.Printf("[DecimalSeparator] %s (%d)\n", user.DecimalSeparator, user.DecimalSeparator)
|
||||
fmt.Printf("[DigitGroupingSymbol] %s (%d)\n", user.DigitGroupingSymbol, user.DigitGroupingSymbol)
|
||||
fmt.Printf("[DigitGrouping] %s (%d)\n", user.DigitGrouping, user.DigitGrouping)
|
||||
fmt.Printf("[CurrencyDisplayType] %s (%d)\n", user.CurrencyDisplayType, user.CurrencyDisplayType)
|
||||
fmt.Printf("[CoordinateDisplayType] %s (%d)\n", user.CoordinateDisplayType, user.CoordinateDisplayType)
|
||||
fmt.Printf("[ExpenseAmountColor] %s (%d)\n", user.ExpenseAmountColor, user.ExpenseAmountColor)
|
||||
fmt.Printf("[IncomeAmountColor] %s (%d)\n", user.IncomeAmountColor, user.IncomeAmountColor)
|
||||
|
||||
@@ -81,13 +81,13 @@ func sendTestMail(c *core.CliContext) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !config.EnableSMTP || mail.Container.Current == nil {
|
||||
if !config.EnableSMTP {
|
||||
return errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
toAddress := c.String("to")
|
||||
|
||||
err = mail.Container.Current.SendMail(&mail.MailMessage{
|
||||
err = mail.Container.SendMail(&mail.MailMessage{
|
||||
To: toAddress,
|
||||
Subject: "ezBookkeeping test e-mail",
|
||||
Body: "This is a test e-mail",
|
||||
|
||||
@@ -79,7 +79,7 @@ func startWebServer(c *core.CliContext) error {
|
||||
return err
|
||||
}
|
||||
|
||||
serverInfo := fmt.Sprintf("current server id is %d, current instance id is %d", requestid.Container.Current.GetCurrentServerUniqId(), requestid.Container.Current.GetCurrentInstanceUniqId())
|
||||
serverInfo := fmt.Sprintf("current server id is %d, current instance id is %d", requestid.Container.GetCurrentServerUniqId(), requestid.Container.GetCurrentInstanceUniqId())
|
||||
uuidServerInfo := ""
|
||||
if config.UuidGeneratorType == settings.InternalUuidGeneratorType {
|
||||
uuidServerInfo = fmt.Sprintf(", current uuid server id is %d", config.UuidServerId)
|
||||
@@ -323,7 +323,8 @@ func startWebServer(c *core.CliContext) error {
|
||||
|
||||
// Data
|
||||
apiV1Route.GET("/data/statistics.json", bindApi(api.DataManagements.DataStatisticsHandler))
|
||||
apiV1Route.POST("/data/clear.json", bindApi(api.DataManagements.ClearDataHandler))
|
||||
apiV1Route.POST("/data/clear/all.json", bindApi(api.DataManagements.ClearAllDataHandler))
|
||||
apiV1Route.POST("/data/clear/transactions.json", bindApi(api.DataManagements.ClearAllTransactionsHandler))
|
||||
|
||||
if config.EnableDataExport {
|
||||
apiV1Route.GET("/data/export.csv", bindCsv(api.DataManagements.ExportDataToEzbookkeepingCSVHandler))
|
||||
@@ -344,6 +345,7 @@ func startWebServer(c *core.CliContext) error {
|
||||
apiV1Route.GET("/transactions/count.json", bindApi(api.Transactions.TransactionCountHandler))
|
||||
apiV1Route.GET("/transactions/list.json", bindApi(api.Transactions.TransactionListHandler))
|
||||
apiV1Route.GET("/transactions/list/by_month.json", bindApi(api.Transactions.TransactionMonthListHandler))
|
||||
apiV1Route.GET("/transactions/reconciliation_statements.json", bindApi(api.Transactions.TransactionReconciliationStatementHandler))
|
||||
apiV1Route.GET("/transactions/statistics.json", bindApi(api.Transactions.TransactionStatisticsHandler))
|
||||
apiV1Route.GET("/transactions/statistics/trends.json", bindApi(api.Transactions.TransactionStatisticsTrendsHandler))
|
||||
apiV1Route.GET("/transactions/amounts.json", bindApi(api.Transactions.TransactionAmountsHandler))
|
||||
|
||||
@@ -115,7 +115,7 @@ log_file_max_size = 104857600
|
||||
log_file_max_days = 7
|
||||
|
||||
[storage]
|
||||
# Object storage type, supports "local_filesystem" and "minio" currently
|
||||
# Object storage type, supports "local_filesystem", "minio" and "webdav" currently
|
||||
type = local_filesystem
|
||||
|
||||
# For "local_filesystem" storage only, the storage root path (relative or absolute path)
|
||||
@@ -139,6 +139,28 @@ minio_bucket = ezbookkeeping
|
||||
# For "minio" storage only, the root path to store files in minio
|
||||
minio_root_path = /
|
||||
|
||||
# For "webdav" storage only, the webdav url
|
||||
webdav_url =
|
||||
|
||||
# For "webdav" storage only, the webdav username
|
||||
webdav_username =
|
||||
|
||||
# For "webdav" storage only, the webdav password
|
||||
webdav_password =
|
||||
|
||||
# For "webdav" storage only, the webdav root path to store files
|
||||
webdav_root_path = /
|
||||
|
||||
# For "webdav" storage only, requesting webdav url timeout (0 - 4294967295 milliseconds)
|
||||
# Set to 0 to disable timeout for requesting webdav url, default is 10000 (10 seconds)
|
||||
webdav_request_timeout = 10000
|
||||
|
||||
# For "webdav" storage only, proxy for requesting webdav url, supports "system" (use system proxy), "none" (do not use proxy), or proxy URL which starts with "http://", "https://" or "socks5://", default is "system"
|
||||
webdav_proxy = system
|
||||
|
||||
# For "webdav" storage only, set to true to skip tls verification when connect webdav
|
||||
webdav_skip_tls_verify = false
|
||||
|
||||
[uuid]
|
||||
# Uuid generator type, supports "internal" currently
|
||||
generator_type = internal
|
||||
|
||||
@@ -32,7 +32,7 @@ func main() {
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: "ezBookkeeping",
|
||||
Usage: "A lightweight, self-hosted personal finance app with a sleek, user-friendly interface and powerful bookkeeping features.",
|
||||
Usage: "A lightweight, self-hosted personal finance app with a user-friendly interface and powerful bookkeeping features.",
|
||||
Version: GetFullVersion(),
|
||||
Commands: []*cli.Command{
|
||||
cmd.WebServer,
|
||||
|
||||
@@ -3,32 +3,33 @@ module github.com/mayswind/ezbookkeeping
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.0.2
|
||||
github.com/boombuler/barcode v1.1.0
|
||||
github.com/extrame/xls v0.0.2-0.20200426124601-4a6cf263071b
|
||||
github.com/gin-contrib/cache v1.4.0
|
||||
github.com/gin-contrib/cache v1.4.1
|
||||
github.com/gin-contrib/gzip v1.2.3
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/go-co-op/gocron/v2 v2.16.2
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/go-sql-driver/mysql v1.9.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/go-co-op/gocron/v2 v2.16.3
|
||||
github.com/go-playground/validator/v10 v10.27.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/invopop/jsonschema v0.13.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/minio/minio-go/v7 v7.0.92
|
||||
github.com/mattn/go-sqlite3 v1.14.30
|
||||
github.com/minio/minio-go/v7 v7.0.95
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/urfave/cli/v3 v3.3.3
|
||||
github.com/urfave/cli/v3 v3.3.8
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/xuri/excelize/v2 v2.9.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/net v0.40.0
|
||||
golang.org/x/text v0.25.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/net v0.42.0
|
||||
golang.org/x/text v0.27.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.9
|
||||
xorm.io/xorm v1.3.10
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -36,7 +37,7 @@ require (
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
@@ -57,22 +58,21 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomodule/redigo v1.9.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/memcachier/mc/v3 v3.0.3 // indirect
|
||||
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||
github.com/minio/crc64nvme v1.0.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/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
@@ -85,13 +85,13 @@ require (
|
||||
github.com/tiendc/go-deepcopy v1.6.0 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ugorji/go/codec v1.2.14 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/nfp v0.0.1 // indirect
|
||||
golang.org/x/arch v0.17.0 // indirect
|
||||
golang.org/x/arch v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -4,16 +4,16 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0p
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
|
||||
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
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.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
@@ -42,30 +42,30 @@ github.com/extrame/xls v0.0.2-0.20200426124601-4a6cf263071b/go.mod h1:iACcgahst7
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/cache v1.4.0 h1:d1FUqCE2+gJQKT0vJjr7jMn1htW9+cypk5oF7aoQcmE=
|
||||
github.com/gin-contrib/cache v1.4.0/go.mod h1:6d0UAPedInkublPl/uJUB4bqwsEgJI1y5QGszhqnyxg=
|
||||
github.com/gin-contrib/cache v1.4.1 h1:HcLwLfw7p+FasNp5VAnFbbBj9SzB4bDtswvon7wYSg4=
|
||||
github.com/gin-contrib/cache v1.4.1/go.mod h1:tykDV+FgItJHYEO0eCasuRYsZKPPyb4BYhAjuTlG6RM=
|
||||
github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U=
|
||||
github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2 h1:r08P663ikXiulLT9XaabkLypL/W9MoCIbqgQoAutyX4=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2/go.mod h1:4YTLGCCAH75A5RlQ6q+h+VacO7CgjkgP0EJ+BEOXRSI=
|
||||
github.com/go-co-op/gocron/v2 v2.16.3 h1:kYqukZqBa8RC2+AFAHnunmKcs9GRTjwBo8WRF3I6cbI=
|
||||
github.com/go-co-op/gocron/v2 v2.16.3/go.mod h1:aTf7/+5Jo2E+cyAqq625UQ6DzpkV96b22VHIUAt6l3c=
|
||||
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-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/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
|
||||
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
@@ -87,8 +87,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
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.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -101,16 +101,16 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY=
|
||||
github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4=
|
||||
github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug=
|
||||
github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY=
|
||||
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
|
||||
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.92 h1:jpBFWyRS3p8P/9tsRc+NuvqoFi7qAmTCFPoRFmobbVw=
|
||||
github.com/minio/minio-go/v7 v7.0.92/go.mod h1:vTIc8DNcnAZIhyFsk8EB90AbPjj3j68aWIEQCiPj7d0=
|
||||
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
|
||||
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -125,8 +125,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
@@ -164,10 +164,10 @@ github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I=
|
||||
github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/ugorji/go/codec v1.2.14 h1:yOQvXCBc3Ij46LRkRoh4Yd5qK6LVOgi0bYOXfb7ifjw=
|
||||
github.com/ugorji/go/codec v1.2.14/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli/v3 v3.3.8 h1:BzolUExliMdet9NlJ/u4m5vHSotJ3PzEqSAZ1oPMa/E=
|
||||
github.com/urfave/cli/v3 v3.3.8/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
@@ -178,23 +178,23 @@ github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmji
|
||||
github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q=
|
||||
github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU=
|
||||
golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
@@ -215,5 +215,5 @@ nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYm
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
|
||||
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
|
||||
xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
|
||||
xorm.io/xorm v1.3.10 h1:yR83hTT4mKIPyA/lvWFTzS35xjLwkiYnwdw0Qupeh0o=
|
||||
xorm.io/xorm v1.3.10/go.mod h1:Lo7hmsFF0F0GbDE7ubX5ZKa+eCf0eCuiJAUG3oI5cxQ=
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ezbookkeeping",
|
||||
"version": "0.10.0",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -21,12 +21,12 @@
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"axios": "^1.9.0",
|
||||
"axios": "^1.11.0",
|
||||
"cbor-js": "^0.1.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dom7": "^4.0.6",
|
||||
"echarts": "^5.6.0",
|
||||
"echarts": "^5.5.1",
|
||||
"framework7": "^8.3.4",
|
||||
"framework7-icons": "^5.0.5",
|
||||
"framework7-vue": "^8.3.4",
|
||||
@@ -34,18 +34,18 @@
|
||||
"line-awesome": "^1.3.0",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.6.0",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia": "^3.0.3",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"skeleton-elements": "^4.0.1",
|
||||
"swiper": "^10.2.0",
|
||||
"ua-parser-js": "^1.0.39",
|
||||
"vue": "^3.5.16",
|
||||
"vue": "^3.5.18",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-i18n": "^11.1.5",
|
||||
"vue-i18n": "^11.1.11",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue3-perfect-scrollbar": "^2.0.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuetify": "^3.8.7"
|
||||
"vuetify": "^3.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^29.7.0",
|
||||
@@ -76,8 +76,13 @@
|
||||
"vue-tsc": "^2.2.10"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"last 5 Chrome versions",
|
||||
"last 5 Firefox versions",
|
||||
"last 5 Safari versions",
|
||||
"last 5 Edge versions",
|
||||
"last 5 ChromeAndroid versions",
|
||||
"last 5 iOS versions",
|
||||
"not IE <= 11",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type ApiUsingConfig struct {
|
||||
|
||||
// CurrentConfig returns the current config
|
||||
func (a *ApiUsingConfig) CurrentConfig() *settings.Config {
|
||||
return a.container.Current
|
||||
return a.container.GetCurrentConfig()
|
||||
}
|
||||
|
||||
// GetTransactionPictureInfoResponse returns the view-object of transaction picture basic info according to the transaction picture model
|
||||
@@ -53,15 +53,15 @@ func (a *ApiUsingConfig) GetAfterRegisterNotificationContent(userLanguage string
|
||||
language = clientLanguage
|
||||
}
|
||||
|
||||
if !a.container.Current.AfterRegisterNotification.Enabled {
|
||||
if !a.CurrentConfig().AfterRegisterNotification.Enabled {
|
||||
return ""
|
||||
}
|
||||
|
||||
if multiLanguageContent, exists := a.container.Current.AfterRegisterNotification.MultiLanguageContent[language]; exists {
|
||||
if multiLanguageContent, exists := a.CurrentConfig().AfterRegisterNotification.MultiLanguageContent[language]; exists {
|
||||
return multiLanguageContent
|
||||
}
|
||||
|
||||
return a.container.Current.AfterRegisterNotification.DefaultContent
|
||||
return a.CurrentConfig().AfterRegisterNotification.DefaultContent
|
||||
}
|
||||
|
||||
// GetAfterLoginNotificationContent returns the notification content displayed each time users log in
|
||||
@@ -72,15 +72,15 @@ func (a *ApiUsingConfig) GetAfterLoginNotificationContent(userLanguage string, c
|
||||
language = clientLanguage
|
||||
}
|
||||
|
||||
if !a.container.Current.AfterLoginNotification.Enabled {
|
||||
if !a.CurrentConfig().AfterLoginNotification.Enabled {
|
||||
return ""
|
||||
}
|
||||
|
||||
if multiLanguageContent, exists := a.container.Current.AfterLoginNotification.MultiLanguageContent[language]; exists {
|
||||
if multiLanguageContent, exists := a.CurrentConfig().AfterLoginNotification.MultiLanguageContent[language]; exists {
|
||||
return multiLanguageContent
|
||||
}
|
||||
|
||||
return a.container.Current.AfterLoginNotification.DefaultContent
|
||||
return a.CurrentConfig().AfterLoginNotification.DefaultContent
|
||||
}
|
||||
|
||||
// GetAfterOpenNotificationContent returns the notification content displayed each time users open the app
|
||||
@@ -91,15 +91,15 @@ func (a *ApiUsingConfig) GetAfterOpenNotificationContent(userLanguage string, cl
|
||||
language = clientLanguage
|
||||
}
|
||||
|
||||
if !a.container.Current.AfterOpenNotification.Enabled {
|
||||
if !a.CurrentConfig().AfterOpenNotification.Enabled {
|
||||
return ""
|
||||
}
|
||||
|
||||
if multiLanguageContent, exists := a.container.Current.AfterOpenNotification.MultiLanguageContent[language]; exists {
|
||||
if multiLanguageContent, exists := a.CurrentConfig().AfterOpenNotification.MultiLanguageContent[language]; exists {
|
||||
return multiLanguageContent
|
||||
}
|
||||
|
||||
return a.container.Current.AfterOpenNotification.DefaultContent
|
||||
return a.CurrentConfig().AfterOpenNotification.DefaultContent
|
||||
}
|
||||
|
||||
// ApiUsingDuplicateChecker represents an api that need to use duplicate checker
|
||||
|
||||
@@ -124,13 +124,13 @@ func (a *DataManagementsApi) DataStatisticsHandler(c *core.WebContext) (any, *er
|
||||
return dataStatisticsResp, nil
|
||||
}
|
||||
|
||||
// ClearDataHandler deletes all user data
|
||||
func (a *DataManagementsApi) ClearDataHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
// ClearAllDataHandler deletes all user data
|
||||
func (a *DataManagementsApi) ClearAllDataHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var clearDataReq models.ClearDataRequest
|
||||
err := c.ShouldBindJSON(&clearDataReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[data_managements.ClearDataHandler] parse request failed, because %s", err.Error())
|
||||
log.Warnf(c, "[data_managements.ClearAllDataHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.WebContext) (any, *errs.Er
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.Warnf(c, "[data_managements.ClearDataHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.Warnf(c, "[data_managements.ClearAllDataHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
@@ -156,39 +156,79 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.WebContext) (any, *errs.Er
|
||||
err = a.templates.DeleteAllTemplates(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearDataHandler] failed to delete all transaction templates, because %s", err.Error())
|
||||
log.Errorf(c, "[data_managements.ClearAllDataHandler] failed to delete all transaction templates, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.transactions.DeleteAllTransactions(c, uid)
|
||||
err = a.transactions.DeleteAllTransactions(c, uid, true)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearDataHandler] failed to delete all transactions, because %s", err.Error())
|
||||
log.Errorf(c, "[data_managements.ClearAllDataHandler] failed to delete all transactions, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.categories.DeleteAllCategories(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearDataHandler] failed to delete all transaction categories, because %s", err.Error())
|
||||
log.Errorf(c, "[data_managements.ClearAllDataHandler] failed to delete all transaction categories, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.tags.DeleteAllTags(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearDataHandler] failed to delete all transaction tags, because %s", err.Error())
|
||||
log.Errorf(c, "[data_managements.ClearAllDataHandler] failed to delete all transaction tags, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.userCustomExchangeRates.DeleteAllCustomExchangeRates(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearDataHandler] failed to delete all user custom exchange rates, because %s", err.Error())
|
||||
log.Errorf(c, "[data_managements.ClearAllDataHandler] failed to delete all user custom exchange rates, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[data_managements.ClearDataHandler] 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
|
||||
}
|
||||
|
||||
// ClearAllTransactionsHandler deletes all transactions
|
||||
func (a *DataManagementsApi) ClearAllTransactionsHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var clearDataReq models.ClearDataRequest
|
||||
err := c.ShouldBindJSON(&clearDataReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[data_managements.ClearAllTransactionsHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.Warnf(c, "[data_managements.ClearAllTransactionsHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
if !a.users.IsPasswordEqualsUserPassword(clearDataReq.Password, user) {
|
||||
return nil, errs.ErrUserPasswordWrong
|
||||
}
|
||||
|
||||
if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA) {
|
||||
return nil, errs.ErrNotPermittedToPerformThisAction
|
||||
}
|
||||
|
||||
err = a.transactions.DeleteAllTransactions(c, uid, false)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearAllTransactionsHandler] failed to delete all transactions, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[data_managements.ClearAllTransactionsHandler] user \"uid:%d\" has cleared all transactions", uid)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,7 @@ var (
|
||||
|
||||
// LatestExchangeRateHandler returns latest exchange rate data
|
||||
func (a *ExchangeRatesApi) LatestExchangeRateHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
dataSource := exchangerates.Container.Current
|
||||
|
||||
if dataSource == nil {
|
||||
return nil, errs.ErrInvalidExchangeRatesDataSource
|
||||
}
|
||||
|
||||
exchangeRateResponse, err := dataSource.GetLatestExchangeRates(c, c.GetCurrentUid(), a.container.Current)
|
||||
exchangeRateResponse, err := exchangerates.Container.GetLatestExchangeRates(c, c.GetCurrentUid(), a.CurrentConfig())
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
|
||||
@@ -130,7 +130,13 @@ func (a *TokensApi) TokenGenerateMCPHandler(c *core.WebContext) (any, *errs.Erro
|
||||
|
||||
// TokenRevokeCurrentHandler revokes current token of current user
|
||||
func (a *TokensApi) TokenRevokeCurrentHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
_, claims, err := a.tokens.ParseTokenByHeader(c)
|
||||
tokenString := c.GetTokenStringFromHeader()
|
||||
|
||||
if tokenString == "" {
|
||||
return false, errs.ErrTokenIsEmpty
|
||||
}
|
||||
|
||||
_, claims, err := a.tokens.ParseToken(c, tokenString)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.Or(err, errs.NewIncompleteOrIncorrectSubmissionError(err))
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
const pageCountForAccountStatement = 1000
|
||||
|
||||
// TransactionsApi represents transaction api
|
||||
type TransactionsApi struct {
|
||||
ApiUsingConfig
|
||||
@@ -286,6 +288,114 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any,
|
||||
return transactionResps, nil
|
||||
}
|
||||
|
||||
// TransactionReconciliationStatementHandler returns transaction reconciliation statement list of current user
|
||||
func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var reconciliationStatementRequest models.TransactionReconciliationStatementRequest
|
||||
err := c.ShouldBindQuery(&reconciliationStatementRequest)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionReconciliationStatementHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionReconciliationStatementHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to get user, because %s", err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
account, err := a.accounts.GetAccountByAccountId(c, uid, reconciliationStatementRequest.AccountId)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to get account \"id:%d\" for user \"uid:%d\", because %s", reconciliationStatementRequest.AccountId, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
if account.Type != models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
|
||||
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] account \"id:%d\" for user \"uid:%d\" is not a single account", reconciliationStatementRequest.AccountId, uid)
|
||||
return nil, errs.ErrAccountTypeInvalid
|
||||
}
|
||||
|
||||
maxTransactionTime := int64(0)
|
||||
|
||||
if reconciliationStatementRequest.EndTime > 0 {
|
||||
maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(reconciliationStatementRequest.EndTime)
|
||||
}
|
||||
|
||||
minTransactionTime := int64(0)
|
||||
|
||||
if reconciliationStatementRequest.StartTime > 0 {
|
||||
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(reconciliationStatementRequest.StartTime)
|
||||
}
|
||||
|
||||
transactionsWithAccountBalance, totalInflows, totalOutflows, openingBalance, closingBalance, err := a.transactions.GetAllTransactionsWithAccountBalanceByMaxTime(c, uid, pageCountForAccountStatement, maxTransactionTime, minTransactionTime, reconciliationStatementRequest.AccountId, account.Category)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to get transactions from \"%d\" to \"%d\" for user \"uid:%d\", because %s", reconciliationStatementRequest.StartTime, reconciliationStatementRequest.EndTime, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactions := make([]*models.Transaction, len(transactionsWithAccountBalance))
|
||||
transactionAccountBalanceMap := make(map[int64]*models.TransactionWithAccountBalance, len(transactionsWithAccountBalance))
|
||||
|
||||
for i := 0; i < len(transactionsWithAccountBalance); i++ {
|
||||
transactionWithBalance := transactionsWithAccountBalance[i]
|
||||
transactions[i] = transactionWithBalance.Transaction
|
||||
transactionAccountBalanceMap[transactionWithBalance.TransactionId] = transactionWithBalance
|
||||
transactionAccountBalanceMap[transactionWithBalance.RelatedId] = transactionWithBalance
|
||||
}
|
||||
|
||||
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, false, true, true, true)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
responseItems := make([]*models.TransactionReconciliationStatementResponseItem, len(transactionResult))
|
||||
|
||||
for i := 0; i < len(transactionResult); i++ {
|
||||
transactionResult := transactionResult[i]
|
||||
accountOpeningBalance := int64(0)
|
||||
accountClosingBalance := int64(0)
|
||||
|
||||
if transactionWithBalance, exists := transactionAccountBalanceMap[transactionResult.Id]; exists {
|
||||
accountOpeningBalance = transactionWithBalance.AccountOpeningBalance
|
||||
accountClosingBalance = transactionWithBalance.AccountClosingBalance
|
||||
} else {
|
||||
log.Warnf(c, "[transactions.TransactionReconciliationStatementHandler] missing account balance for transaction \"id:%d\" of user \"uid:%d\"", transactionResult.Id, uid)
|
||||
}
|
||||
|
||||
responseItems[i] = &models.TransactionReconciliationStatementResponseItem{
|
||||
TransactionInfoResponse: transactionResult,
|
||||
AccountOpeningBalance: accountOpeningBalance,
|
||||
AccountClosingBalance: accountClosingBalance,
|
||||
}
|
||||
}
|
||||
|
||||
reconciliationStatementResp := &models.TransactionReconciliationStatementResponse{
|
||||
Transactions: responseItems,
|
||||
TotalInflows: totalInflows,
|
||||
TotalOutflows: totalOutflows,
|
||||
OpeningBalance: openingBalance,
|
||||
ClosingBalance: closingBalance,
|
||||
}
|
||||
|
||||
return reconciliationStatementResp, nil
|
||||
}
|
||||
|
||||
// TransactionStatisticsHandler returns transaction statistics of current user
|
||||
func (a *TransactionsApi) TransactionStatisticsHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var statisticReq models.TransactionStatisticRequest
|
||||
@@ -699,7 +809,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
if transactionCreateReq.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE && transactionCreateReq.CategoryId > 0 {
|
||||
if transactionCreateReq.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE && transactionCreateReq.CategoryId != 0 {
|
||||
log.Warnf(c, "[transactions.TransactionCreateHandler] balance modification transaction cannot set category id")
|
||||
return nil, errs.ErrBalanceModificationTransactionCannotSetCategory
|
||||
}
|
||||
@@ -847,6 +957,14 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE && transactionModifyReq.CategoryId != 0 {
|
||||
log.Warnf(c, "[transactions.TransactionModifyHandler] balance modification transaction cannot set category id")
|
||||
return nil, errs.ErrBalanceModificationTransactionCannotSetCategory
|
||||
} else if transaction.Type != models.TRANSACTION_DB_TYPE_MODIFY_BALANCE && transactionModifyReq.CategoryId == 0 {
|
||||
log.Warnf(c, "[transactions.TransactionModifyHandler] non-balance modification transaction must set category id")
|
||||
return nil, errs.ErrIncompleteOrIncorrectSubmission
|
||||
}
|
||||
|
||||
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(c, uid, []int64{transaction.TransactionId})
|
||||
|
||||
if err != nil {
|
||||
@@ -1388,7 +1506,7 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
|
||||
return nil, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
if transactionCreateReq.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE && transactionCreateReq.CategoryId > 0 {
|
||||
if transactionCreateReq.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE && transactionCreateReq.CategoryId != 0 {
|
||||
log.Warnf(c, "[transactions.TransactionImportHandler] balance modification transaction \"index:%d\" cannot set category id", i)
|
||||
return nil, errs.ErrBalanceModificationTransactionCannotSetCategory
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
@@ -74,115 +75,129 @@ func (a *UserApplicationCloudSettingsApi) ApplicationSettingsUpdateHandler(c *co
|
||||
return false, errs.ErrNotPermittedToPerformThisAction
|
||||
}
|
||||
|
||||
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, uid)
|
||||
var userApplicationCloudSettings *models.UserApplicationCloudSetting
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return false, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
// Retry up to 3 times
|
||||
for i := 0; i < 3; i++ {
|
||||
userApplicationCloudSettings, err = a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, uid)
|
||||
|
||||
oldApplicationCloudSettingsMap := make(map[string]models.ApplicationCloudSetting)
|
||||
|
||||
if userApplicationCloudSettings != nil {
|
||||
for _, setting := range userApplicationCloudSettings.Settings {
|
||||
oldApplicationCloudSettingsMap[setting.SettingKey] = setting
|
||||
if err != nil {
|
||||
log.Errorf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] failed to get latest user application cloud settings for user \"uid:%d\" (try count %d), because %s", uid, i+1, err.Error())
|
||||
return false, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the full update settings are the same as the existing settings
|
||||
if userAppCloudSettingUpdateReq.FullUpdate {
|
||||
if len(userAppCloudSettingUpdateReq.Settings) == len(oldApplicationCloudSettingsMap) {
|
||||
needUpdate := false
|
||||
oldApplicationCloudSettingsMap := make(map[string]models.ApplicationCloudSetting)
|
||||
lastUpdateTime := int64(0)
|
||||
|
||||
if userApplicationCloudSettings != nil {
|
||||
for _, setting := range userApplicationCloudSettings.Settings {
|
||||
oldApplicationCloudSettingsMap[setting.SettingKey] = setting
|
||||
}
|
||||
|
||||
lastUpdateTime = userApplicationCloudSettings.UpdatedUnixTime
|
||||
}
|
||||
|
||||
// Check if the full update settings are the same as the existing settings
|
||||
if userAppCloudSettingUpdateReq.FullUpdate {
|
||||
if len(userAppCloudSettingUpdateReq.Settings) == len(oldApplicationCloudSettingsMap) {
|
||||
needUpdate := false
|
||||
|
||||
for _, setting := range userAppCloudSettingUpdateReq.Settings {
|
||||
oldSetting, exists := oldApplicationCloudSettingsMap[setting.SettingKey]
|
||||
|
||||
if !exists || oldSetting.SettingValue != setting.SettingValue {
|
||||
needUpdate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !needUpdate {
|
||||
return false, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
}
|
||||
} else { // Check if the partial update settings are the same as the existing settings or the settings to update are not set to sync
|
||||
needUpdate := true
|
||||
|
||||
for _, setting := range userAppCloudSettingUpdateReq.Settings {
|
||||
oldSetting, exists := oldApplicationCloudSettingsMap[setting.SettingKey]
|
||||
cloudSetting, exists := oldApplicationCloudSettingsMap[setting.SettingKey]
|
||||
|
||||
if !exists || oldSetting.SettingValue != setting.SettingValue {
|
||||
needUpdate = true
|
||||
break
|
||||
if !exists {
|
||||
needUpdate = false
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" is not set to sync (try count %d)", setting.SettingKey, i+1)
|
||||
} else if cloudSetting.SettingValue == setting.SettingValue {
|
||||
needUpdate = false
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" value \"%s\" is not changed, no need to update (try count %d)", setting.SettingKey, setting.SettingValue, i+1)
|
||||
}
|
||||
}
|
||||
|
||||
if !needUpdate {
|
||||
return false, errs.ErrNothingWillBeUpdated
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] no user application cloud settings need to update for user \"uid:%d\" (try count %d)", uid, i+1)
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
newApplicationCloudSettingsMap := make(map[string]models.ApplicationCloudSetting)
|
||||
var newApplicationCloudSettingSlice models.ApplicationCloudSettingSlice
|
||||
|
||||
if userAppCloudSettingUpdateReq.FullUpdate {
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user \"uid:%d\" application cloud settings force update, will overwrite all existing settings (try count %d)", uid, i+1)
|
||||
} else {
|
||||
if len(oldApplicationCloudSettingsMap) > 0 {
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user \"uid:%d\" application cloud settings exists, try to merge it with request settings (try count %d)", uid, i+1)
|
||||
newApplicationCloudSettingsMap = oldApplicationCloudSettingsMap
|
||||
}
|
||||
}
|
||||
} else { // Check if the partial update settings are the same as the existing settings or the settings to update are not set to sync
|
||||
needUpdate := true
|
||||
|
||||
for _, setting := range userAppCloudSettingUpdateReq.Settings {
|
||||
cloudSetting, exists := oldApplicationCloudSettingsMap[setting.SettingKey]
|
||||
newApplicationCloudSettingsMap[setting.SettingKey] = setting
|
||||
}
|
||||
|
||||
for settingKey, setting := range newApplicationCloudSettingsMap {
|
||||
settingType, exists := models.ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES[settingKey]
|
||||
|
||||
if !exists {
|
||||
needUpdate = false
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" is not set to sync", setting.SettingKey)
|
||||
} else if cloudSetting.SettingValue == setting.SettingValue {
|
||||
needUpdate = false
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" value \"%s\" is not changed, no need to update", setting.SettingKey, setting.SettingValue)
|
||||
}
|
||||
}
|
||||
|
||||
if !needUpdate {
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] no user application cloud settings need to update for user \"uid:%d\"", uid)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
newApplicationCloudSettingsMap := make(map[string]models.ApplicationCloudSetting)
|
||||
var newApplicationCloudSettingSlice models.ApplicationCloudSettingSlice
|
||||
|
||||
if userAppCloudSettingUpdateReq.FullUpdate {
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user \"uid:%d\" application cloud settings force update, will overwrite all existing settings", uid)
|
||||
} else {
|
||||
if len(oldApplicationCloudSettingsMap) > 0 {
|
||||
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user \"uid:%d\" application cloud settings exists, try to merge it with request settings", uid)
|
||||
newApplicationCloudSettingsMap = oldApplicationCloudSettingsMap
|
||||
}
|
||||
}
|
||||
|
||||
for _, setting := range userAppCloudSettingUpdateReq.Settings {
|
||||
newApplicationCloudSettingsMap[setting.SettingKey] = setting
|
||||
}
|
||||
|
||||
for settingKey, setting := range newApplicationCloudSettingsMap {
|
||||
settingType, exists := models.ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES[settingKey]
|
||||
|
||||
if !exists {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" is not supported to sync", settingKey)
|
||||
continue
|
||||
}
|
||||
|
||||
if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_STRING {
|
||||
// Do Nothing
|
||||
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER {
|
||||
_, err := utils.StringToFloat64(setting.SettingValue)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid number value \"%s\"", settingKey, setting.SettingValue)
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" is not supported to sync (try count %d)", settingKey, i+1)
|
||||
continue
|
||||
}
|
||||
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN {
|
||||
if setting.SettingValue != "true" && setting.SettingValue != "false" {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid boolean value \"%s\"", settingKey, setting.SettingValue)
|
||||
continue
|
||||
}
|
||||
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_STRING_BOOLEAN_MAP {
|
||||
var settingValueMap map[string]bool
|
||||
err := json.Unmarshal([]byte(setting.SettingValue), &settingValueMap)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid map value \"%s\", because %s", settingKey, setting.SettingValue, err.Error())
|
||||
if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_STRING {
|
||||
// Do Nothing
|
||||
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER {
|
||||
_, err := utils.StringToFloat64(setting.SettingValue)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid number value \"%s\" (try count %d)", settingKey, setting.SettingValue, i+1)
|
||||
continue
|
||||
}
|
||||
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN {
|
||||
if setting.SettingValue != "true" && setting.SettingValue != "false" {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid boolean value \"%s\" (try count %d)", settingKey, setting.SettingValue, i+1)
|
||||
continue
|
||||
}
|
||||
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_STRING_BOOLEAN_MAP {
|
||||
var settingValueMap map[string]bool
|
||||
err := json.Unmarshal([]byte(setting.SettingValue), &settingValueMap)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid map value \"%s\" (try count %d), because %s", settingKey, setting.SettingValue, i+1, err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has unknown type \"%s\" (try count %d)", settingKey, settingType, i+1)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has unknown type \"%s\"", settingKey, settingType)
|
||||
continue
|
||||
|
||||
newApplicationCloudSettingSlice = append(newApplicationCloudSettingSlice, setting)
|
||||
}
|
||||
|
||||
newApplicationCloudSettingSlice = append(newApplicationCloudSettingSlice, setting)
|
||||
}
|
||||
err = a.userAppCloudSettings.UpdateUserApplicationCloudSettings(c, uid, newApplicationCloudSettingSlice, userAppCloudSettingUpdateReq.FullUpdate, lastUpdateTime)
|
||||
|
||||
err = a.userAppCloudSettings.UpdateUserApplicationCloudSettings(c, uid, newApplicationCloudSettingSlice)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Wait for 100 milliseconds before retrying
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] failed to update user application cloud settings for user \"uid:%d\", because %s", uid, err.Error())
|
||||
|
||||
@@ -359,6 +359,24 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
|
||||
userNew.FiscalYearStart = core.FISCAL_YEAR_START_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.CalendarDisplayType != nil && *userUpdateReq.CalendarDisplayType != user.CalendarDisplayType {
|
||||
user.CalendarDisplayType = *userUpdateReq.CalendarDisplayType
|
||||
userNew.CalendarDisplayType = *userUpdateReq.CalendarDisplayType
|
||||
modifyProfileBasicInfo = true
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.CalendarDisplayType = core.CALENDAR_DISPLAY_TYPE_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.DateDisplayType != nil && *userUpdateReq.DateDisplayType != user.DateDisplayType {
|
||||
user.DateDisplayType = *userUpdateReq.DateDisplayType
|
||||
userNew.DateDisplayType = *userUpdateReq.DateDisplayType
|
||||
modifyProfileBasicInfo = true
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.DateDisplayType = core.DATE_DISPLAY_TYPE_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.LongDateFormat != nil && *userUpdateReq.LongDateFormat != user.LongDateFormat {
|
||||
user.LongDateFormat = *userUpdateReq.LongDateFormat
|
||||
userNew.LongDateFormat = *userUpdateReq.LongDateFormat
|
||||
@@ -404,6 +422,24 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
|
||||
userNew.FiscalYearFormat = core.FISCAL_YEAR_FORMAT_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.CurrencyDisplayType != nil && *userUpdateReq.CurrencyDisplayType != user.CurrencyDisplayType {
|
||||
user.CurrencyDisplayType = *userUpdateReq.CurrencyDisplayType
|
||||
userNew.CurrencyDisplayType = *userUpdateReq.CurrencyDisplayType
|
||||
modifyProfileBasicInfo = true
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.CurrencyDisplayType = core.CURRENCY_DISPLAY_TYPE_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.NumeralSystem != nil && *userUpdateReq.NumeralSystem != user.NumeralSystem {
|
||||
user.NumeralSystem = *userUpdateReq.NumeralSystem
|
||||
userNew.NumeralSystem = *userUpdateReq.NumeralSystem
|
||||
modifyProfileBasicInfo = true
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.NumeralSystem = core.NUMERAL_SYSTEM_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.DecimalSeparator != nil && *userUpdateReq.DecimalSeparator != user.DecimalSeparator {
|
||||
user.DecimalSeparator = *userUpdateReq.DecimalSeparator
|
||||
userNew.DecimalSeparator = *userUpdateReq.DecimalSeparator
|
||||
@@ -431,15 +467,6 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
|
||||
userNew.DigitGrouping = core.DIGIT_GROUPING_TYPE_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.CurrencyDisplayType != nil && *userUpdateReq.CurrencyDisplayType != user.CurrencyDisplayType {
|
||||
user.CurrencyDisplayType = *userUpdateReq.CurrencyDisplayType
|
||||
userNew.CurrencyDisplayType = *userUpdateReq.CurrencyDisplayType
|
||||
modifyProfileBasicInfo = true
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.CurrencyDisplayType = core.CURRENCY_DISPLAY_TYPE_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.CoordinateDisplayType != nil && *userUpdateReq.CoordinateDisplayType != user.CoordinateDisplayType {
|
||||
user.CoordinateDisplayType = *userUpdateReq.CoordinateDisplayType
|
||||
userNew.CoordinateDisplayType = *userUpdateReq.CoordinateDisplayType
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// AvatarProviderContainer contains the current user avatar provider
|
||||
type AvatarProviderContainer struct {
|
||||
Current AvatarProvider
|
||||
current AvatarProvider
|
||||
}
|
||||
|
||||
// Initialize a user avatar provider container singleton instance
|
||||
@@ -20,13 +20,13 @@ var (
|
||||
// InitializeAvatarProvider initializes the current user avatar provider according to the config
|
||||
func InitializeAvatarProvider(config *settings.Config) error {
|
||||
if config.AvatarProvider == core.USER_AVATAR_PROVIDER_INTERNAL {
|
||||
Container.Current = NewInternalStorageAvatarProvider(config)
|
||||
Container.current = NewInternalStorageAvatarProvider(config)
|
||||
return nil
|
||||
} else if config.AvatarProvider == core.USER_AVATAR_PROVIDER_GRAVATAR {
|
||||
Container.Current = NewGravatarAvatarProvider()
|
||||
Container.current = NewGravatarAvatarProvider()
|
||||
return nil
|
||||
} else if config.AvatarProvider == "" {
|
||||
Container.Current = NewNullAvatarProvider()
|
||||
Container.current = NewNullAvatarProvider()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,5 +35,9 @@ func InitializeAvatarProvider(config *settings.Config) error {
|
||||
|
||||
// GetAvatarUrl returns the avatar url by the current user avatar provider
|
||||
func (p *AvatarProviderContainer) GetAvatarUrl(user *models.User) string {
|
||||
return p.Current.GetAvatarUrl(user)
|
||||
if p.current == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return p.current.GetAvatarUrl(user)
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ type CliUsingConfig struct {
|
||||
|
||||
// CurrentConfig returns the current config
|
||||
func (l *CliUsingConfig) CurrentConfig() *settings.Config {
|
||||
return l.container.Current
|
||||
return l.container.GetCurrentConfig()
|
||||
}
|
||||
|
||||
@@ -445,6 +445,39 @@ func (l *UserDataCli) CreateNewUserToken(c *core.CliContext, username string, to
|
||||
return tokenRecord, token, nil
|
||||
}
|
||||
|
||||
// RevokeUserToken revokes the specified token of the user
|
||||
func (l *UserDataCli) RevokeUserToken(c *core.CliContext, token string) error {
|
||||
_, claims, err := l.tokens.ParseToken(c, token)
|
||||
|
||||
if err != nil {
|
||||
log.CliErrorf(c, "[user_data.RevokeUserToken] failed to parse token, because %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
|
||||
|
||||
if err != nil {
|
||||
log.CliErrorf(c, "[user_data.RevokeUserToken] failed to get user token id, because %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
tokenRecord := &models.TokenRecord{
|
||||
Uid: claims.Uid,
|
||||
UserTokenId: userTokenId,
|
||||
CreatedUnixTime: claims.IssuedAt,
|
||||
}
|
||||
|
||||
tokenId := l.tokens.GenerateTokenId(tokenRecord)
|
||||
err = l.tokens.DeleteToken(c, tokenRecord)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[user_data.RevokeUserToken] failed to revoke token \"id:%s\" for user \"uid:%d\", because %s", tokenId, claims.Uid, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearUserTokens clears all tokens of the specified user
|
||||
func (l *UserDataCli) ClearUserTokens(c *core.CliContext, username string) error {
|
||||
if username == "" {
|
||||
|
||||
@@ -2,21 +2,17 @@ package alipay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
csvdatatable "github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"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/utils"
|
||||
)
|
||||
|
||||
var alipayTransactionSupportedColumns = map[datatable.TransactionDataTableColumn]bool{
|
||||
@@ -61,7 +57,13 @@ func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Contex
|
||||
enc := simplifiedchinese.GB18030
|
||||
reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder())
|
||||
|
||||
dataTable, err := c.createNewAlipayBasicDataTable(ctx, reader, c.fileHeaderLine, c.dataHeaderStartContent, c.dataBottomEndLineRune)
|
||||
csvDataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataTable, err := createNewAlipayTransactionBasicDataTable(ctx, csvDataTable, c.fileHeaderLine, c.dataHeaderStartContent, c.dataBottomEndLineRune)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
@@ -83,80 +85,3 @@ func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Contex
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
func (c *alipayTransactionDataCsvFileImporter) createNewAlipayBasicDataTable(ctx core.Context, reader io.Reader, fileHeaderLine string, dataHeaderStartContent string, dataBottomEndLineRune rune) (datatable.BasicDataTable, error) {
|
||||
csvReader := csv.NewReader(reader)
|
||||
csvReader.FieldsPerRecord = -1
|
||||
|
||||
allOriginalLines := make([][]string, 0)
|
||||
hasFileHeader := false
|
||||
foundContentBeforeDataHeaderLine := false
|
||||
|
||||
for {
|
||||
items, err := csvReader.Read()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[alipay_transaction_data_csv_file_importer.createNewAlipayBasicDataTable] cannot parse alipay csv data, because %s", err.Error())
|
||||
return nil, errs.ErrInvalidCSVFile
|
||||
}
|
||||
|
||||
if !hasFileHeader {
|
||||
if len(items) <= 0 {
|
||||
continue
|
||||
} else if strings.Index(items[0], fileHeaderLine) == 0 {
|
||||
hasFileHeader = true
|
||||
continue
|
||||
} else {
|
||||
log.Warnf(ctx, "[alipay_transaction_data_csv_file_importer.createNewAlipayBasicDataTable] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !foundContentBeforeDataHeaderLine {
|
||||
if len(items) <= 0 {
|
||||
continue
|
||||
} else if strings.Index(items[0], dataHeaderStartContent) >= 0 {
|
||||
foundContentBeforeDataHeaderLine = true
|
||||
continue
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if foundContentBeforeDataHeaderLine {
|
||||
if len(items) <= 0 {
|
||||
continue
|
||||
} else if len(items) == 1 && dataBottomEndLineRune > 0 && utils.ContainsOnlyOneRune(items[0], dataBottomEndLineRune) {
|
||||
break
|
||||
}
|
||||
|
||||
for i := 0; i < len(items); i++ {
|
||||
items[i] = strings.Trim(items[i], " ")
|
||||
}
|
||||
|
||||
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
||||
log.Errorf(ctx, "[alipay_transaction_data_csv_file_importer.createNewAlipayBasicDataTable] cannot parse row \"index:%d\", because may missing some columns (column count %d in data row is less than header column count %d)", len(allOriginalLines), len(items), len(allOriginalLines[0]))
|
||||
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||
}
|
||||
|
||||
allOriginalLines = append(allOriginalLines, items)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasFileHeader || !foundContentBeforeDataHeaderLine {
|
||||
return nil, errs.ErrInvalidFileHeader
|
||||
}
|
||||
|
||||
if len(allOriginalLines) < 2 {
|
||||
log.Errorf(ctx, "[alipay_transaction_data_csv_file_importer.createNewAlipayBasicDataTable] cannot parse import data, because data table row count is less 1")
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
dataTable := csvdatatable.CreateNewCustomCsvBasicDataTable(allOriginalLines)
|
||||
|
||||
return dataTable, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package alipay
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
func createNewAlipayTransactionBasicDataTable(ctx core.Context, originalDataTable datatable.BasicDataTable, fileHeaderLine string, dataHeaderStartContent string, dataBottomEndLineRune rune) (datatable.BasicDataTable, error) {
|
||||
iterator := originalDataTable.DataRowIterator()
|
||||
allOriginalLines := make([][]string, 0)
|
||||
hasFileHeader := false
|
||||
foundContentBeforeDataHeaderLine := false
|
||||
|
||||
for iterator.HasNext() {
|
||||
row := iterator.Next()
|
||||
|
||||
if !hasFileHeader {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
} else if strings.Index(row.GetData(0), fileHeaderLine) == 0 {
|
||||
hasFileHeader = true
|
||||
continue
|
||||
} else {
|
||||
log.Warnf(ctx, "[alipay_transaction_data_extrator.createNewAlipayTransactionBasicDataTable] read unexpected line in row \"%s\" before read file header", iterator.CurrentRowId())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !foundContentBeforeDataHeaderLine {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
} else if strings.Index(row.GetData(0), dataHeaderStartContent) >= 0 {
|
||||
foundContentBeforeDataHeaderLine = true
|
||||
continue
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if foundContentBeforeDataHeaderLine {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
} else if row.ColumnCount() == 1 && dataBottomEndLineRune > 0 && utils.ContainsOnlyOneRune(row.GetData(0), dataBottomEndLineRune) {
|
||||
break
|
||||
}
|
||||
|
||||
items := make([]string, row.ColumnCount())
|
||||
|
||||
for i := 0; i < row.ColumnCount(); i++ {
|
||||
items[i] = strings.Trim(row.GetData(i), " ")
|
||||
}
|
||||
|
||||
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
||||
log.Errorf(ctx, "[alipay_transaction_data_extrator.createNewAlipayTransactionBasicDataTable] cannot parse row \"%s\", because may missing some columns (column count %d in data row is less than header column count %d)", iterator.CurrentRowId(), len(items), len(allOriginalLines[0]))
|
||||
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||
}
|
||||
|
||||
allOriginalLines = append(allOriginalLines, items)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasFileHeader || !foundContentBeforeDataHeaderLine {
|
||||
return nil, errs.ErrInvalidFileHeader
|
||||
}
|
||||
|
||||
if len(allOriginalLines) < 2 {
|
||||
log.Errorf(ctx, "[alipay_transaction_data_extrator.createNewAlipayTransactionBasicDataTable] cannot parse import data, because data table row count is less 1")
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
return csv.CreateNewCustomCsvBasicDataTable(allOriginalLines, true), nil
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import (
|
||||
|
||||
// CsvFileBasicDataTable defines the structure of csv data table
|
||||
type CsvFileBasicDataTable struct {
|
||||
allLines [][]string
|
||||
allLines [][]string
|
||||
hasTitleLine bool
|
||||
}
|
||||
|
||||
// CsvFileBasicDataTableRow defines the structure of csv data table row
|
||||
@@ -34,7 +35,11 @@ func (t *CsvFileBasicDataTable) DataRowCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(t.allLines) - 1
|
||||
if t.hasTitleLine {
|
||||
return len(t.allLines) - 1
|
||||
} else {
|
||||
return len(t.allLines)
|
||||
}
|
||||
}
|
||||
|
||||
// HeaderColumnNames returns the header column name list
|
||||
@@ -43,14 +48,24 @@ func (t *CsvFileBasicDataTable) HeaderColumnNames() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.allLines[0]
|
||||
if t.hasTitleLine {
|
||||
return t.allLines[0]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *CsvFileBasicDataTable) DataRowIterator() datatable.BasicDataTableRowIterator {
|
||||
startIndex := -1
|
||||
|
||||
if t.hasTitleLine {
|
||||
startIndex = 0
|
||||
}
|
||||
|
||||
return &CsvFileBasicDataTableRowIterator{
|
||||
dataTable: t,
|
||||
currentIndex: 0,
|
||||
currentIndex: startIndex,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,18 +110,19 @@ func (t *CsvFileBasicDataTableRowIterator) Next() datatable.BasicDataTableRow {
|
||||
}
|
||||
|
||||
// CreateNewCsvBasicDataTable returns comma separated values data table by io readers
|
||||
func CreateNewCsvBasicDataTable(ctx core.Context, reader io.Reader) (datatable.BasicDataTable, error) {
|
||||
return createNewCsvFileBasicDataTable(ctx, reader, ',')
|
||||
func CreateNewCsvBasicDataTable(ctx core.Context, reader io.Reader, hasTitleLine bool) (datatable.BasicDataTable, error) {
|
||||
return createNewCsvFileBasicDataTable(ctx, reader, ',', hasTitleLine)
|
||||
}
|
||||
|
||||
// CreateNewCustomCsvBasicDataTable returns character separated values data table by io readers
|
||||
func CreateNewCustomCsvBasicDataTable(allLines [][]string) datatable.BasicDataTable {
|
||||
func CreateNewCustomCsvBasicDataTable(allLines [][]string, hasTitleLine bool) datatable.BasicDataTable {
|
||||
return &CsvFileBasicDataTable{
|
||||
allLines: allLines,
|
||||
allLines: allLines,
|
||||
hasTitleLine: hasTitleLine,
|
||||
}
|
||||
}
|
||||
|
||||
func createNewCsvFileBasicDataTable(ctx core.Context, reader io.Reader, separator rune) (*CsvFileBasicDataTable, error) {
|
||||
func createNewCsvFileBasicDataTable(ctx core.Context, reader io.Reader, separator rune, hasTitleLine bool) (*CsvFileBasicDataTable, error) {
|
||||
csvReader := csv.NewReader(reader)
|
||||
csvReader.Comma = separator
|
||||
csvReader.FieldsPerRecord = -1
|
||||
@@ -133,6 +149,7 @@ func createNewCsvFileBasicDataTable(ctx core.Context, reader io.Reader, separato
|
||||
}
|
||||
|
||||
return &CsvFileBasicDataTable{
|
||||
allLines: allLines,
|
||||
allLines: allLines,
|
||||
hasTitleLine: hasTitleLine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -14,7 +14,17 @@ func TestCsvFileBasicDataTableDataRowCount(t *testing.T) {
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
})
|
||||
}, false)
|
||||
|
||||
assert.Equal(t, 3, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestCsvFileBasicDataTableDataRowCount_HasTitleLine(t *testing.T) {
|
||||
datatable := CreateNewCustomCsvBasicDataTable([][]string{
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
}, true)
|
||||
|
||||
assert.Equal(t, 2, datatable.DataRowCount())
|
||||
}
|
||||
@@ -22,14 +32,16 @@ func TestCsvFileBasicDataTableDataRowCount(t *testing.T) {
|
||||
func TestCsvFileBasicDataTableDataRowCount_OnlyHeaderLine(t *testing.T) {
|
||||
datatable := CreateNewCustomCsvBasicDataTable([][]string{
|
||||
{"A1", "B1", "C1"},
|
||||
})
|
||||
}, true)
|
||||
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestCsvFileBasicDataTableDataRowCount_EmptyContent(t *testing.T) {
|
||||
datatable := CreateNewCustomCsvBasicDataTable([][]string{})
|
||||
datatable := CreateNewCustomCsvBasicDataTable([][]string{}, false)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
|
||||
datatable = CreateNewCustomCsvBasicDataTable([][]string{}, true)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
@@ -38,14 +50,16 @@ func TestCsvFileBasicDataTableHeaderColumnNames(t *testing.T) {
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
})
|
||||
}, true)
|
||||
|
||||
assert.EqualValues(t, []string{"A1", "B1", "C1"}, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
func TestCsvFileBasicDataTableHeaderColumnNames_EmptyContent(t *testing.T) {
|
||||
datatable := CreateNewCustomCsvBasicDataTable([][]string{})
|
||||
datatable := CreateNewCustomCsvBasicDataTable([][]string{}, false)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
|
||||
datatable = CreateNewCustomCsvBasicDataTable([][]string{}, true)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
@@ -54,7 +68,34 @@ func TestCsvFileBasicDataTableRowIterator(t *testing.T) {
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
})
|
||||
}, false)
|
||||
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 3
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 4
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestCsvFileBasicDataTableRowIterator_HasTitleLine(t *testing.T) {
|
||||
datatable := CreateNewCustomCsvBasicDataTable([][]string{
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
}, true)
|
||||
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
@@ -81,7 +122,7 @@ func TestCsvFileBasicDataTableRowColumnCount(t *testing.T) {
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
})
|
||||
}, true)
|
||||
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
@@ -97,7 +138,32 @@ func TestCsvFileBasicDataTableRowGetData(t *testing.T) {
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
})
|
||||
}, false)
|
||||
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", row1.GetData(0))
|
||||
assert.Equal(t, "B1", row1.GetData(1))
|
||||
assert.Equal(t, "C1", row1.GetData(2))
|
||||
|
||||
row2 := iterator.Next()
|
||||
assert.Equal(t, "A2", row2.GetData(0))
|
||||
assert.Equal(t, "B2", row2.GetData(1))
|
||||
assert.Equal(t, "C2", row2.GetData(2))
|
||||
|
||||
row3 := iterator.Next()
|
||||
assert.Equal(t, "A3", row3.GetData(0))
|
||||
assert.Equal(t, "B3", row3.GetData(1))
|
||||
assert.Equal(t, "C3", row3.GetData(2))
|
||||
}
|
||||
|
||||
func TestCsvFileBasicDataTableRowGetData_HasTitleLine(t *testing.T) {
|
||||
datatable := CreateNewCustomCsvBasicDataTable([][]string{
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
}, true)
|
||||
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
@@ -117,7 +183,7 @@ func TestCsvFileBasicDataTableRowGetData_GetNotExistedColumnData(t *testing.T) {
|
||||
{"A1", "B1", "C1"},
|
||||
{"A2", "B2", "C2"},
|
||||
{"A3", "B3", "C3"},
|
||||
})
|
||||
}, true)
|
||||
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
@@ -130,7 +196,7 @@ func TestCreateNewCsvBasicDataTable(t *testing.T) {
|
||||
reader := bytes.NewReader([]byte("A1,B1,C1\n" +
|
||||
"A2,B2,C2\n" +
|
||||
"A3,B3,C3\n"))
|
||||
datatable, err := CreateNewCsvBasicDataTable(context, reader)
|
||||
datatable, err := CreateNewCsvBasicDataTable(context, reader, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 2, datatable.DataRowCount())
|
||||
@@ -160,7 +226,7 @@ func TestCreateNewCsvBasicDataTable_SkipBlankLine(t *testing.T) {
|
||||
"A2,B2,C2\n" +
|
||||
"\n" +
|
||||
"A3,B3,C3\n"))
|
||||
datatable, err := CreateNewCsvBasicDataTable(context, reader)
|
||||
datatable, err := CreateNewCsvBasicDataTable(context, reader, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 2, datatable.DataRowCount())
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package datatable
|
||||
|
||||
type testBasicDataTable struct {
|
||||
headerColumns []string
|
||||
rows []*testBasicDataTableRow
|
||||
}
|
||||
|
||||
type testBasicDataTableRow struct {
|
||||
rowId string
|
||||
rowColumns []string
|
||||
}
|
||||
|
||||
type testBasicDataTableRowIterator struct {
|
||||
rows []*testBasicDataTableRow
|
||||
currentIndex int
|
||||
}
|
||||
|
||||
func (t *testBasicDataTable) HeaderColumnNames() []string {
|
||||
return t.headerColumns
|
||||
}
|
||||
|
||||
func (t *testBasicDataTable) DataRowCount() int {
|
||||
return len(t.rows)
|
||||
}
|
||||
|
||||
func (t *testBasicDataTable) DataRowIterator() BasicDataTableRowIterator {
|
||||
return &testBasicDataTableRowIterator{
|
||||
rows: t.rows,
|
||||
currentIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *testBasicDataTableRow) ColumnCount() int {
|
||||
return len(r.rowColumns)
|
||||
}
|
||||
|
||||
func (r *testBasicDataTableRow) GetData(columnIndex int) string {
|
||||
if columnIndex < 0 || columnIndex >= len(r.rowColumns) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return r.rowColumns[columnIndex]
|
||||
}
|
||||
|
||||
func (t *testBasicDataTableRowIterator) HasNext() bool {
|
||||
return t.currentIndex+1 < len(t.rows)
|
||||
}
|
||||
|
||||
func (t *testBasicDataTableRowIterator) CurrentRowId() string {
|
||||
if t.currentIndex >= len(t.rows) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return t.rows[t.currentIndex].rowId
|
||||
}
|
||||
|
||||
func (t *testBasicDataTableRowIterator) Next() BasicDataTableRow {
|
||||
if t.currentIndex+1 >= len(t.rows) {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.currentIndex++
|
||||
row := t.rows[t.currentIndex]
|
||||
return row
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBasicDataTableToCommonDataTableWrapper_HeaderColumnCount(t *testing.T) {
|
||||
columns := []string{"Col1", "Col2", "Col3"}
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: []*testBasicDataTableRow{},
|
||||
}
|
||||
|
||||
commonDataTable := CreateNewCommonDataTableFromBasicDataTable(basicDataTable)
|
||||
assert.Equal(t, len(columns), commonDataTable.HeaderColumnCount())
|
||||
}
|
||||
|
||||
func TestBasicDataTableToCommonDataTableWrapper_HasColumn(t *testing.T) {
|
||||
columns := []string{"Col1", "Col2", "Col3"}
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: []*testBasicDataTableRow{},
|
||||
}
|
||||
|
||||
commonDataTable := CreateNewCommonDataTableFromBasicDataTable(basicDataTable)
|
||||
|
||||
assert.True(t, commonDataTable.HasColumn("Col1"))
|
||||
assert.True(t, commonDataTable.HasColumn("Col2"))
|
||||
assert.True(t, commonDataTable.HasColumn("Col3"))
|
||||
|
||||
assert.False(t, commonDataTable.HasColumn("Col4"))
|
||||
assert.False(t, commonDataTable.HasColumn(""))
|
||||
}
|
||||
|
||||
func TestBasicDataTableToCommonDataTableWrapper_DataRowCount(t *testing.T) {
|
||||
columns := []string{"Col1", "Col2", "Col3"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"A1", "B1", "C1"},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowColumns: []string{"A2", "B2", "C2"},
|
||||
},
|
||||
{
|
||||
rowId: "3",
|
||||
rowColumns: []string{"A3", "B3", "C3"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
commonDataTable := CreateNewCommonDataTableFromBasicDataTable(basicDataTable)
|
||||
assert.Equal(t, len(rows), commonDataTable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestBasicDataTableToCommonDataTableWrapper_DataRowIterator(t *testing.T) {
|
||||
columns := []string{"Col1", "Col2", "Col3"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"A1", "B1", "C1"},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowColumns: []string{"A2", "B2", "C2"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
commonDataTable := CreateNewCommonDataTableFromBasicDataTable(basicDataTable)
|
||||
iterator := commonDataTable.DataRowIterator()
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
firstRow := iterator.Next()
|
||||
assert.NotNil(t, firstRow)
|
||||
assert.Equal(t, len(columns), firstRow.ColumnCount())
|
||||
assert.True(t, firstRow.HasData("Col1"))
|
||||
assert.True(t, firstRow.HasData("Col2"))
|
||||
assert.True(t, firstRow.HasData("Col3"))
|
||||
assert.Equal(t, "A1", firstRow.GetData("Col1"))
|
||||
assert.Equal(t, "B1", firstRow.GetData("Col2"))
|
||||
assert.Equal(t, "C1", firstRow.GetData("Col3"))
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
secondRow := iterator.Next()
|
||||
assert.NotNil(t, secondRow)
|
||||
assert.Equal(t, len(columns), secondRow.ColumnCount())
|
||||
assert.True(t, secondRow.HasData("Col1"))
|
||||
assert.True(t, secondRow.HasData("Col2"))
|
||||
assert.True(t, secondRow.HasData("Col3"))
|
||||
assert.Equal(t, "A2", secondRow.GetData("Col1"))
|
||||
assert.Equal(t, "B2", secondRow.GetData("Col2"))
|
||||
assert.Equal(t, "C2", secondRow.GetData("Col3"))
|
||||
|
||||
assert.False(t, iterator.HasNext())
|
||||
assert.Nil(t, iterator.Next())
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
type testTransactionDataRowParser struct {
|
||||
}
|
||||
|
||||
func (p *testTransactionDataRowParser) Parse(rowData map[TransactionDataTableColumn]string) (map[TransactionDataTableColumn]string, bool, error) {
|
||||
rowData[TRANSACTION_DATA_TABLE_DESCRIPTION] = "Test Description"
|
||||
return rowData, true, nil
|
||||
}
|
||||
|
||||
func (p *testTransactionDataRowParser) GetAddedColumns() []TransactionDataTableColumn {
|
||||
return []TransactionDataTableColumn{TRANSACTION_DATA_TABLE_DESCRIPTION}
|
||||
}
|
||||
|
||||
func TestBasicDataTableToTransactionDataTableWrapper_HasColumn(t *testing.T) {
|
||||
columns := []string{"TransactionTime", "TransactionType", "Amount"}
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: []*testBasicDataTableRow{},
|
||||
}
|
||||
|
||||
columnMapping := map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "TransactionTime",
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "TransactionType",
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: "Amount",
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromBasicDataTable(basicDataTable, columnMapping)
|
||||
|
||||
assert.True(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TIME))
|
||||
assert.True(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE))
|
||||
assert.True(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_AMOUNT))
|
||||
|
||||
assert.False(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_DESCRIPTION))
|
||||
assert.False(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_CATEGORY))
|
||||
}
|
||||
|
||||
func TestBasicDataTableToTransactionDataTableWrapper_TransactionRowCount(t *testing.T) {
|
||||
columns := []string{"TransactionTime", "TransactionType", "Amount"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"2024-01-01", "1", "100"},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowColumns: []string{"2024-01-02", "2", "200"},
|
||||
},
|
||||
{
|
||||
rowId: "3",
|
||||
rowColumns: []string{"2024-01-03", "1", "300"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
columnMapping := map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "TransactionTime",
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "TransactionType",
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: "Amount",
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromBasicDataTable(basicDataTable, columnMapping)
|
||||
assert.Equal(t, len(rows), transactionDataTable.TransactionRowCount())
|
||||
}
|
||||
|
||||
func TestBasicDataTableToTransactionDataTableWrapper_TransactionRowIterator(t *testing.T) {
|
||||
columns := []string{"TransactionTime", "TransactionType", "Amount"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"2024-01-01", "1", "100"},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowColumns: []string{"2024-01-02", "2", "200"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
columnMapping := map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "TransactionTime",
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "TransactionType",
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: "Amount",
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromBasicDataTable(basicDataTable, columnMapping)
|
||||
iterator := transactionDataTable.TransactionRowIterator()
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
firstRow, err := iterator.Next(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, firstRow)
|
||||
assert.True(t, firstRow.IsValid())
|
||||
assert.Equal(t, "2024-01-01", firstRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME))
|
||||
assert.Equal(t, "1", firstRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE))
|
||||
assert.Equal(t, "100", firstRow.GetData(TRANSACTION_DATA_TABLE_AMOUNT))
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
secondRow, err := iterator.Next(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, secondRow)
|
||||
assert.True(t, secondRow.IsValid())
|
||||
assert.Equal(t, "2024-01-02", secondRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME))
|
||||
assert.Equal(t, "2", secondRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE))
|
||||
assert.Equal(t, "200", secondRow.GetData(TRANSACTION_DATA_TABLE_AMOUNT))
|
||||
|
||||
assert.False(t, iterator.HasNext())
|
||||
emptyRow, err := iterator.Next(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, emptyRow)
|
||||
}
|
||||
|
||||
func TestBasicDataTableToTransactionDataTableWrapper_TransactionRowIterator_EmptyRow(t *testing.T) {
|
||||
columns := []string{"TransactionTime", "TransactionType", "Amount"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{""},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
columnMapping := map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "TransactionTime",
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "TransactionType",
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: "Amount",
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromBasicDataTable(basicDataTable, columnMapping)
|
||||
iterator := transactionDataTable.TransactionRowIterator()
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
row, err := iterator.Next(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, row)
|
||||
assert.False(t, row.IsValid())
|
||||
}
|
||||
|
||||
func TestBasicDataTableToTransactionDataTableWrapper_TransactionRowIterator_InvalidRow(t *testing.T) {
|
||||
columns := []string{"TransactionTime", "TransactionType", "Amount"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"2024-01-01", "1"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
columnMapping := map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "TransactionTime",
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "TransactionType",
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: "Amount",
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromBasicDataTable(basicDataTable, columnMapping)
|
||||
iterator := transactionDataTable.TransactionRowIterator()
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
row, err := iterator.Next(nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, errs.ErrFewerFieldsInDataRowThanInHeaderRow, err)
|
||||
assert.Nil(t, row)
|
||||
}
|
||||
|
||||
func TestBasicDataTableToTransactionDataTableWrapper_TransactionRowIterator_WithRowParserAddedColumn(t *testing.T) {
|
||||
columns := []string{"TransactionTime", "TransactionType", "Amount"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"2024-01-01", "1", "100"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
columnMapping := map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "TransactionTime",
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "TransactionType",
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: "Amount",
|
||||
TRANSACTION_DATA_TABLE_DESCRIPTION: "Description",
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromBasicDataTableWithRowParser(basicDataTable, columnMapping, &testTransactionDataRowParser{})
|
||||
assert.True(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_DESCRIPTION))
|
||||
|
||||
iterator := transactionDataTable.TransactionRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
row, err := iterator.Next(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, row)
|
||||
assert.True(t, row.IsValid())
|
||||
assert.Equal(t, "Test Description", row.GetData(TRANSACTION_DATA_TABLE_DESCRIPTION))
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
type testCommonDataTable struct {
|
||||
headerColumns []string
|
||||
dataRows []*testCommonDataTableRow
|
||||
}
|
||||
|
||||
type testCommonDataTableRow struct {
|
||||
rowId string
|
||||
rowData map[string]string
|
||||
}
|
||||
|
||||
type testCommonDataTableRowIterator struct {
|
||||
dataTable *testCommonDataTable
|
||||
currentIndex int
|
||||
}
|
||||
|
||||
func (t *testCommonDataTable) DataRowCount() int {
|
||||
return len(t.dataRows)
|
||||
}
|
||||
|
||||
func (t *testCommonDataTable) HeaderColumnCount() int {
|
||||
return len(t.headerColumns)
|
||||
}
|
||||
|
||||
func (t *testCommonDataTable) HasColumn(columnName string) bool {
|
||||
for _, header := range t.headerColumns {
|
||||
if header == columnName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *testCommonDataTable) DataRowIterator() CommonDataTableRowIterator {
|
||||
return &testCommonDataTableRowIterator{
|
||||
dataTable: t,
|
||||
currentIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testCommonDataTableRow) GetData(dataKey string) string {
|
||||
return t.rowData[dataKey]
|
||||
}
|
||||
|
||||
func (t *testCommonDataTableRow) HasData(dataKey string) bool {
|
||||
_, exists := t.rowData[dataKey]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (t *testCommonDataTableRow) ColumnCount() int {
|
||||
return len(t.rowData)
|
||||
}
|
||||
|
||||
func (t *testCommonDataTableRowIterator) HasNext() bool {
|
||||
return t.currentIndex+1 < len(t.dataTable.dataRows)
|
||||
}
|
||||
|
||||
func (t *testCommonDataTableRowIterator) Next() CommonDataTableRow {
|
||||
if !t.HasNext() {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.currentIndex++
|
||||
return t.dataTable.dataRows[t.currentIndex]
|
||||
}
|
||||
|
||||
func (t *testCommonDataTableRowIterator) CurrentRowId() string {
|
||||
if t.currentIndex < 0 || t.currentIndex >= len(t.dataTable.dataRows) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return t.dataTable.dataRows[t.currentIndex].rowId
|
||||
}
|
||||
|
||||
type testCommonTransactionDataRowParser struct {
|
||||
returnError bool
|
||||
}
|
||||
|
||||
func (p *testCommonTransactionDataRowParser) Parse(ctx core.Context, user *models.User, dataRow CommonDataTableRow, rowId string) (map[TransactionDataTableColumn]string, bool, error) {
|
||||
if p.returnError {
|
||||
return nil, false, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
rowData := make(map[TransactionDataTableColumn]string)
|
||||
rowData[TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = dataRow.GetData("TransactionTime")
|
||||
rowData[TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataRow.GetData("TransactionType")
|
||||
rowData[TRANSACTION_DATA_TABLE_AMOUNT] = dataRow.GetData("Amount")
|
||||
rowData[TRANSACTION_DATA_TABLE_DESCRIPTION] = "Test Description"
|
||||
return rowData, true, nil
|
||||
}
|
||||
|
||||
func TestCommonDataTableToTransactionDataTableWrapper_HasColumn(t *testing.T) {
|
||||
basicDataTable := &testCommonDataTable{
|
||||
headerColumns: []string{"TransactionTime", "TransactionType", "Amount"},
|
||||
dataRows: []*testCommonDataTableRow{},
|
||||
}
|
||||
|
||||
supportedColumns := map[TransactionDataTableColumn]bool{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: true,
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: true,
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromCommonDataTable(basicDataTable, supportedColumns, &testCommonTransactionDataRowParser{})
|
||||
|
||||
assert.True(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TIME))
|
||||
assert.True(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE))
|
||||
assert.True(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_AMOUNT))
|
||||
|
||||
assert.False(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_CATEGORY))
|
||||
assert.False(t, transactionDataTable.HasColumn(TRANSACTION_DATA_TABLE_DESCRIPTION))
|
||||
}
|
||||
|
||||
func TestCommonDataTableToTransactionDataTableWrapper_TransactionRowCount(t *testing.T) {
|
||||
rows := []*testCommonDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowData: map[string]string{
|
||||
"TransactionTime": "2024-01-01",
|
||||
"TransactionType": "1",
|
||||
"Amount": "100",
|
||||
},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowData: map[string]string{
|
||||
"TransactionTime": "2024-01-02",
|
||||
"TransactionType": "2",
|
||||
"Amount": "200",
|
||||
},
|
||||
},
|
||||
{
|
||||
rowId: "3",
|
||||
rowData: map[string]string{
|
||||
"TransactionTime": "2024-01-03",
|
||||
"TransactionType": "1",
|
||||
"Amount": "300",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testCommonDataTable{
|
||||
headerColumns: []string{"TransactionTime", "TransactionType", "Amount"},
|
||||
dataRows: rows,
|
||||
}
|
||||
|
||||
supportedColumns := map[TransactionDataTableColumn]bool{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: true,
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: true,
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromCommonDataTable(basicDataTable, supportedColumns, &testCommonTransactionDataRowParser{})
|
||||
assert.Equal(t, len(rows), transactionDataTable.TransactionRowCount())
|
||||
}
|
||||
|
||||
func TestCommonDataTableToTransactionDataTableWrapper_TransactionRowIterator(t *testing.T) {
|
||||
rows := []*testCommonDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowData: map[string]string{
|
||||
"TransactionTime": "2024-01-01",
|
||||
"TransactionType": "1",
|
||||
"Amount": "100",
|
||||
},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowData: map[string]string{
|
||||
"TransactionTime": "2024-01-02",
|
||||
"TransactionType": "2",
|
||||
"Amount": "200",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testCommonDataTable{
|
||||
headerColumns: []string{"TransactionTime", "TransactionType", "Amount"},
|
||||
dataRows: rows,
|
||||
}
|
||||
|
||||
supportedColumns := map[TransactionDataTableColumn]bool{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: true,
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: true,
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromCommonDataTable(basicDataTable, supportedColumns, &testCommonTransactionDataRowParser{})
|
||||
iterator := transactionDataTable.TransactionRowIterator()
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
firstRow, err := iterator.Next(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, firstRow)
|
||||
assert.True(t, firstRow.IsValid())
|
||||
assert.Equal(t, "2024-01-01", firstRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME))
|
||||
assert.Equal(t, "1", firstRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE))
|
||||
assert.Equal(t, "100", firstRow.GetData(TRANSACTION_DATA_TABLE_AMOUNT))
|
||||
assert.Equal(t, "", firstRow.GetData(TRANSACTION_DATA_TABLE_DESCRIPTION))
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
secondRow, err := iterator.Next(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, secondRow)
|
||||
assert.True(t, secondRow.IsValid())
|
||||
assert.Equal(t, "2024-01-02", secondRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME))
|
||||
assert.Equal(t, "2", secondRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE))
|
||||
assert.Equal(t, "200", secondRow.GetData(TRANSACTION_DATA_TABLE_AMOUNT))
|
||||
assert.Equal(t, "", secondRow.GetData(TRANSACTION_DATA_TABLE_DESCRIPTION))
|
||||
|
||||
assert.False(t, iterator.HasNext())
|
||||
emptyRow, err := iterator.Next(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, emptyRow)
|
||||
}
|
||||
|
||||
func TestCommonDataTableToTransactionDataTableWrapper_TransactionRowIterator_EOF(t *testing.T) {
|
||||
rows := []*testCommonDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowData: map[string]string{
|
||||
"TransactionTime": "2024-01-01",
|
||||
"TransactionType": "1",
|
||||
"Amount": "100",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testCommonDataTable{
|
||||
headerColumns: []string{"TransactionTime", "TransactionType", "Amount"},
|
||||
dataRows: rows,
|
||||
}
|
||||
|
||||
supportedColumns := map[TransactionDataTableColumn]bool{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: true,
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: true,
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
}
|
||||
|
||||
transactionDataTable := CreateNewTransactionDataTableFromCommonDataTable(basicDataTable, supportedColumns, &testCommonTransactionDataRowParser{returnError: true})
|
||||
iterator := transactionDataTable.TransactionRowIterator()
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
row, err := iterator.Next(nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrOperationFailed.Message)
|
||||
assert.Nil(t, row)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package datatable
|
||||
|
||||
// SubBasicDataTable defines the structure of sub basic data table
|
||||
type SubBasicDataTable struct {
|
||||
baseTable BasicDataTable
|
||||
fromIndex int
|
||||
toIndex int
|
||||
}
|
||||
|
||||
// SubBasicDataTableRowIterator defines the structure of sub basic data table row iterator
|
||||
type SubBasicDataTableRowIterator struct {
|
||||
dataTable *SubBasicDataTable
|
||||
innerIterator BasicDataTableRowIterator
|
||||
currentIndex int
|
||||
}
|
||||
|
||||
// DataRowCount returns the total count of data row
|
||||
func (t *SubBasicDataTable) DataRowCount() int {
|
||||
return t.toIndex - t.fromIndex
|
||||
}
|
||||
|
||||
// HeaderColumnNames returns the header column name list
|
||||
func (t *SubBasicDataTable) HeaderColumnNames() []string {
|
||||
return t.baseTable.HeaderColumnNames()
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *SubBasicDataTable) DataRowIterator() BasicDataTableRowIterator {
|
||||
innerIterator := t.baseTable.DataRowIterator()
|
||||
currentIndex := -1
|
||||
|
||||
// skip rows until reaching the fromIndex
|
||||
for currentIndex = -1; currentIndex < t.fromIndex-1 && innerIterator.HasNext(); currentIndex++ {
|
||||
innerIterator.Next()
|
||||
}
|
||||
|
||||
return &SubBasicDataTableRowIterator{
|
||||
dataTable: t,
|
||||
innerIterator: innerIterator,
|
||||
currentIndex: currentIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
func (t *SubBasicDataTableRowIterator) HasNext() bool {
|
||||
return t.currentIndex+1 < t.dataTable.toIndex && t.innerIterator.HasNext()
|
||||
}
|
||||
|
||||
// CurrentRowId returns current row id
|
||||
func (t *SubBasicDataTableRowIterator) CurrentRowId() string {
|
||||
return t.innerIterator.CurrentRowId()
|
||||
}
|
||||
|
||||
// Next returns the next basic data row
|
||||
func (t *SubBasicDataTableRowIterator) Next() BasicDataTableRow {
|
||||
if t.currentIndex+1 >= t.dataTable.toIndex {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.currentIndex++
|
||||
return t.innerIterator.Next()
|
||||
}
|
||||
|
||||
// CreateSubBasicTable returns a sub basic data table that references a portion of the original table
|
||||
func CreateSubBasicTable(dataTable BasicDataTable, fromIndex, toIndex int) *SubBasicDataTable {
|
||||
if fromIndex < 0 {
|
||||
fromIndex = 0
|
||||
}
|
||||
|
||||
if fromIndex > dataTable.DataRowCount() {
|
||||
fromIndex = dataTable.DataRowCount()
|
||||
}
|
||||
|
||||
if toIndex > dataTable.DataRowCount() {
|
||||
toIndex = dataTable.DataRowCount()
|
||||
}
|
||||
|
||||
if toIndex < fromIndex {
|
||||
toIndex = fromIndex
|
||||
}
|
||||
|
||||
return &SubBasicDataTable{
|
||||
baseTable: dataTable,
|
||||
fromIndex: fromIndex,
|
||||
toIndex: toIndex,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateSubBasicTable_WithValidInput(t *testing.T) {
|
||||
columns := []string{"Col1", "Col2", "Col3"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"A1", "B1", "C1"},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowColumns: []string{"A2", "B2", "C2"},
|
||||
},
|
||||
{
|
||||
rowId: "3",
|
||||
rowColumns: []string{"A3", "B3", "C3"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
subTable := CreateSubBasicTable(basicDataTable, 1, 2)
|
||||
assert.Equal(t, 1, subTable.DataRowCount())
|
||||
assert.Equal(t, columns, subTable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
func TestCreateSubBasicTable_WithInvalidInput(t *testing.T) {
|
||||
columns := []string{"Col1", "Col2", "Col3"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"A1", "B1", "C1"},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowColumns: []string{"A2", "B2", "C2"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
subTable := CreateSubBasicTable(basicDataTable, -1, 2)
|
||||
assert.Equal(t, 0, subTable.fromIndex)
|
||||
assert.Equal(t, 2, subTable.toIndex)
|
||||
|
||||
subTable = CreateSubBasicTable(basicDataTable, 5, 2)
|
||||
assert.Equal(t, 2, subTable.fromIndex)
|
||||
assert.Equal(t, 2, subTable.toIndex)
|
||||
|
||||
subTable = CreateSubBasicTable(basicDataTable, 0, 5)
|
||||
assert.Equal(t, 0, subTable.fromIndex)
|
||||
assert.Equal(t, 2, subTable.toIndex)
|
||||
|
||||
subTable = CreateSubBasicTable(basicDataTable, 2, 1)
|
||||
assert.Equal(t, 2, subTable.fromIndex)
|
||||
assert.Equal(t, 2, subTable.toIndex)
|
||||
}
|
||||
|
||||
func TestSubBasicDataTable_DataRowIterator(t *testing.T) {
|
||||
columns := []string{"Col1", "Col2", "Col3"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"A1", "B1", "C1"},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowColumns: []string{"A2", "B2", "C2"},
|
||||
},
|
||||
{
|
||||
rowId: "3",
|
||||
rowColumns: []string{"A3", "B3", "C3"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
subTable := CreateSubBasicTable(basicDataTable, 1, 3)
|
||||
iterator := subTable.DataRowIterator()
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
firstRow := iterator.Next()
|
||||
assert.NotNil(t, firstRow)
|
||||
assert.Equal(t, "2", iterator.CurrentRowId())
|
||||
assert.Equal(t, "A2", firstRow.GetData(0))
|
||||
assert.Equal(t, "B2", firstRow.GetData(1))
|
||||
assert.Equal(t, "C2", firstRow.GetData(2))
|
||||
|
||||
assert.True(t, iterator.HasNext())
|
||||
secondRow := iterator.Next()
|
||||
assert.NotNil(t, secondRow)
|
||||
assert.Equal(t, "3", iterator.CurrentRowId())
|
||||
assert.Equal(t, "A3", secondRow.GetData(0))
|
||||
assert.Equal(t, "B3", secondRow.GetData(1))
|
||||
assert.Equal(t, "C3", secondRow.GetData(2))
|
||||
|
||||
assert.False(t, iterator.HasNext())
|
||||
assert.Nil(t, iterator.Next())
|
||||
}
|
||||
|
||||
func TestSubBasicDataTable_EmptyDataRange(t *testing.T) {
|
||||
columns := []string{"Col1", "Col2", "Col3"}
|
||||
rows := []*testBasicDataTableRow{
|
||||
{
|
||||
rowId: "1",
|
||||
rowColumns: []string{"A1", "B1", "C1"},
|
||||
},
|
||||
{
|
||||
rowId: "2",
|
||||
rowColumns: []string{"A2", "B2", "C2"},
|
||||
},
|
||||
}
|
||||
|
||||
basicDataTable := &testBasicDataTable{
|
||||
headerColumns: columns,
|
||||
rows: rows,
|
||||
}
|
||||
|
||||
subTable := CreateSubBasicTable(basicDataTable, 1, 1)
|
||||
assert.Equal(t, 0, subTable.DataRowCount())
|
||||
|
||||
iterator := subTable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
assert.Nil(t, iterator.Next())
|
||||
}
|
||||
@@ -153,11 +153,7 @@ func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Contex
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
if !c.hasHeaderLine {
|
||||
allLines = append([][]string{{}}, allLines...)
|
||||
}
|
||||
|
||||
dataTable := csvconverter.CreateNewCustomCsvBasicDataTable(allLines)
|
||||
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)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
type ExcelMSCFBFileBasicDataTable struct {
|
||||
workbook *xls.WorkBook
|
||||
headerLineColumnNames []string
|
||||
hasTitleLine bool
|
||||
}
|
||||
|
||||
// ExcelMSCFBFileBasicDataTableRow defines the structure of excel (microsoft compound file binary) file data table row
|
||||
@@ -26,7 +27,7 @@ type ExcelMSCFBFileBasicDataTableRow struct {
|
||||
type ExcelMSCFBFileBasicDataTableRowIterator struct {
|
||||
dataTable *ExcelMSCFBFileBasicDataTable
|
||||
currentSheetIndex int
|
||||
currentRowIndexInSheet uint16
|
||||
currentRowIndexInSheet int
|
||||
}
|
||||
|
||||
// DataRowCount returns the total count of data row
|
||||
@@ -36,11 +37,23 @@ func (t *ExcelMSCFBFileBasicDataTable) DataRowCount() int {
|
||||
for i := 0; i < t.workbook.NumSheets(); i++ {
|
||||
sheet := t.workbook.GetSheet(i)
|
||||
|
||||
if sheet.MaxRow < 1 {
|
||||
if sheet == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
totalDataRowCount += int(sheet.MaxRow)
|
||||
if t.hasTitleLine {
|
||||
if sheet.MaxRow < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
totalDataRowCount += int(sheet.MaxRow)
|
||||
} else {
|
||||
if sheet.MaxRow <= 0 && sheet.Row(0) == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
totalDataRowCount += int(sheet.MaxRow) + 1
|
||||
}
|
||||
}
|
||||
|
||||
return totalDataRowCount
|
||||
@@ -48,15 +61,25 @@ func (t *ExcelMSCFBFileBasicDataTable) DataRowCount() int {
|
||||
|
||||
// HeaderColumnNames returns the header column name list
|
||||
func (t *ExcelMSCFBFileBasicDataTable) HeaderColumnNames() []string {
|
||||
if !t.hasTitleLine {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.headerLineColumnNames
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *ExcelMSCFBFileBasicDataTable) DataRowIterator() datatable.BasicDataTableRowIterator {
|
||||
startIndex := -1
|
||||
|
||||
if t.hasTitleLine {
|
||||
startIndex = 0
|
||||
}
|
||||
|
||||
return &ExcelMSCFBFileBasicDataTableRowIterator{
|
||||
dataTable: t,
|
||||
currentSheetIndex: 0,
|
||||
currentRowIndexInSheet: 0,
|
||||
currentRowIndexInSheet: startIndex,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,15 +105,21 @@ func (t *ExcelMSCFBFileBasicDataTableRowIterator) HasNext() bool {
|
||||
|
||||
currentSheet := workbook.GetSheet(t.currentSheetIndex)
|
||||
|
||||
if t.currentRowIndexInSheet+1 <= currentSheet.MaxRow {
|
||||
if t.currentRowIndexInSheet+1 <= int(currentSheet.MaxRow) && currentSheet.Row(t.currentRowIndexInSheet+1) != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := t.currentSheetIndex + 1; i < workbook.NumSheets(); i++ {
|
||||
sheet := workbook.GetSheet(i)
|
||||
|
||||
if sheet.MaxRow < 1 {
|
||||
continue
|
||||
if t.dataTable.hasTitleLine {
|
||||
if sheet.MaxRow < 1 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if sheet.MaxRow <= 0 && sheet.Row(0) == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -107,20 +136,22 @@ func (t *ExcelMSCFBFileBasicDataTableRowIterator) CurrentRowId() string {
|
||||
// Next returns the next basic data row
|
||||
func (t *ExcelMSCFBFileBasicDataTableRowIterator) Next() datatable.BasicDataTableRow {
|
||||
workbook := t.dataTable.workbook
|
||||
currentRowIndexInTable := t.currentRowIndexInSheet
|
||||
|
||||
for i := t.currentSheetIndex; i < workbook.NumSheets(); i++ {
|
||||
sheet := workbook.GetSheet(i)
|
||||
|
||||
if currentRowIndexInTable+1 <= sheet.MaxRow {
|
||||
if t.currentRowIndexInSheet+1 <= int(sheet.MaxRow) && sheet.Row(t.currentRowIndexInSheet+1) != nil {
|
||||
t.currentRowIndexInSheet++
|
||||
currentRowIndexInTable = t.currentRowIndexInSheet
|
||||
break
|
||||
}
|
||||
|
||||
t.currentSheetIndex++
|
||||
t.currentRowIndexInSheet = 0
|
||||
currentRowIndexInTable = 0
|
||||
|
||||
if t.dataTable.hasTitleLine {
|
||||
t.currentRowIndexInSheet = 0
|
||||
} else {
|
||||
t.currentRowIndexInSheet = -1
|
||||
}
|
||||
}
|
||||
|
||||
if t.currentSheetIndex >= workbook.NumSheets() {
|
||||
@@ -129,7 +160,7 @@ func (t *ExcelMSCFBFileBasicDataTableRowIterator) Next() datatable.BasicDataTabl
|
||||
|
||||
currentSheet := workbook.GetSheet(t.currentSheetIndex)
|
||||
|
||||
if t.currentRowIndexInSheet > currentSheet.MaxRow {
|
||||
if t.currentRowIndexInSheet > int(currentSheet.MaxRow) || currentSheet.Row(t.currentRowIndexInSheet) == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -140,7 +171,7 @@ func (t *ExcelMSCFBFileBasicDataTableRowIterator) Next() datatable.BasicDataTabl
|
||||
}
|
||||
|
||||
// CreateNewExcelMSCFBFileBasicDataTable returns excel (microsoft compound file binary) data table by file binary data
|
||||
func CreateNewExcelMSCFBFileBasicDataTable(data []byte) (datatable.BasicDataTable, error) {
|
||||
func CreateNewExcelMSCFBFileBasicDataTable(data []byte, hasTitleLine bool) (datatable.BasicDataTable, error) {
|
||||
reader := bytes.NewReader(data)
|
||||
workbook, err := xls.OpenReader(reader, "")
|
||||
|
||||
@@ -148,12 +179,12 @@ func CreateNewExcelMSCFBFileBasicDataTable(data []byte) (datatable.BasicDataTabl
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var headerRowItems []string
|
||||
var firstRowItems []string
|
||||
|
||||
for i := 0; i < workbook.NumSheets(); i++ {
|
||||
sheet := workbook.GetSheet(i)
|
||||
|
||||
if sheet.MaxRow < 0 {
|
||||
if sheet.MaxRow <= 0 && sheet.Row(0) == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -171,21 +202,28 @@ func CreateNewExcelMSCFBFileBasicDataTable(data []byte) (datatable.BasicDataTabl
|
||||
break
|
||||
}
|
||||
|
||||
headerRowItems = append(headerRowItems, headerItem)
|
||||
firstRowItems = append(firstRowItems, headerItem)
|
||||
}
|
||||
} else {
|
||||
for j := 0; j <= min(row.LastCol(), len(headerRowItems)-1); j++ {
|
||||
for j := 0; j <= min(row.LastCol(), len(firstRowItems)-1); j++ {
|
||||
headerItem := row.Col(j)
|
||||
|
||||
if headerItem != headerRowItems[j] {
|
||||
if headerItem != firstRowItems[j] {
|
||||
return nil, errs.ErrFieldsInMultiTableAreDifferent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var headerLineColumnNames []string = nil
|
||||
|
||||
if hasTitleLine {
|
||||
headerLineColumnNames = firstRowItems
|
||||
}
|
||||
|
||||
return &ExcelMSCFBFileBasicDataTable{
|
||||
workbook: workbook,
|
||||
headerLineColumnNames: headerRowItems,
|
||||
headerLineColumnNames: headerLineColumnNames,
|
||||
hasTitleLine: hasTitleLine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,16 @@ func TestExcelMSCFBFileBasicDataTableDataRowCount(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableDataRowCount_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, datatable.DataRowCount())
|
||||
}
|
||||
@@ -22,7 +31,16 @@ func TestExcelMSCFBFileBasicDataTableDataRowCount_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 9, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableDataRowCount_MultipleSheets_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 5, datatable.DataRowCount())
|
||||
}
|
||||
@@ -31,7 +49,7 @@ func TestExcelMSCFBFileBasicDataTableDataRowCount_OnlyHeaderLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/only_one_row_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
}
|
||||
@@ -40,7 +58,11 @@ func TestExcelMSCFBFileBasicDataTableDataRowCount_EmptyContent(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
|
||||
datatable, err = CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
}
|
||||
@@ -49,7 +71,17 @@ func TestExcelMSCFBFileBasicDataTableHeaderColumnNames(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableHeaderColumnNames_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"A1", "B1", "C1"}, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
@@ -57,15 +89,47 @@ func TestExcelMSCFBFileBasicDataTableHeaderColumnNames_EmptyContent(t *testing.T
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
|
||||
datatable, err = CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableRowIterator(t *testing.T) {
|
||||
func TestExcelMSCFBFileBasicDataRowIterator(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 3
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 4
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataRowIterator_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
@@ -86,11 +150,66 @@ func TestExcelMSCFBFileBasicDataTableRowIterator(t *testing.T) {
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableRowIterator_MultipleSheets(t *testing.T) {
|
||||
func TestExcelMSCFBFileBasicDataRowIterator_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 1 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 1 data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 1 data row 3
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 3 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 3 data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 4 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 5 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 5 data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 5 data row 3
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataRowIterator_MultipleSheets_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
@@ -123,11 +242,12 @@ func TestExcelMSCFBFileBasicDataTableRowIterator_MultipleSheets(t *testing.T) {
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableRowIterator_OnlyHeaderLine(t *testing.T) {
|
||||
func TestExcelMSCFBFileBasicDataRowIterator_OnlyHeaderLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/only_one_row_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
@@ -140,11 +260,12 @@ func TestExcelMSCFBFileBasicDataTableRowIterator_OnlyHeaderLine(t *testing.T) {
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableRowIterator_EmptyContent(t *testing.T) {
|
||||
func TestExcelMSCFBFileBasicDataRowIterator_EmptyContent(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
@@ -155,13 +276,27 @@ func TestExcelMSCFBFileBasicDataTableRowIterator_EmptyContent(t *testing.T) {
|
||||
// not existed data row 2
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
datatable, err = CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator = datatable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 1
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 2
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableRowColumnCount(t *testing.T) {
|
||||
func TestExcelMSCFBFileBasicDataRowColumnCount(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
@@ -171,11 +306,36 @@ func TestExcelMSCFBFileBasicDataTableRowColumnCount(t *testing.T) {
|
||||
assert.EqualValues(t, 4, row2.ColumnCount())
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableRowGetData(t *testing.T) {
|
||||
func TestExcelMSCFBFileBasicDataRowGetData(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", row1.GetData(0))
|
||||
assert.Equal(t, "B1", row1.GetData(1))
|
||||
assert.Equal(t, "C1", row1.GetData(2))
|
||||
|
||||
row2 := iterator.Next()
|
||||
assert.Equal(t, "A2", row2.GetData(0))
|
||||
assert.Equal(t, "B2", row2.GetData(1))
|
||||
assert.Equal(t, "C2", row2.GetData(2))
|
||||
|
||||
row3 := iterator.Next()
|
||||
assert.Equal(t, "A3", row3.GetData(0))
|
||||
assert.Equal(t, "B3", row3.GetData(1))
|
||||
assert.Equal(t, "C3", row3.GetData(2))
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataRowGetData_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
@@ -189,22 +349,80 @@ func TestExcelMSCFBFileBasicDataTableRowGetData(t *testing.T) {
|
||||
assert.Equal(t, "C3", row2.GetData(2))
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableRowGetData_GetNotExistedColumnData(t *testing.T) {
|
||||
func TestExcelMSCFBFileBasicDataRowGetData_GetNotExistedColumnData(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
assert.Equal(t, "", row1.GetData(3))
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataTableRowGetData_MultipleSheets(t *testing.T) {
|
||||
func TestExcelMSCFBFileBasicDataRowGetData_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
sheet1Row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", sheet1Row1.GetData(0))
|
||||
assert.Equal(t, "B1", sheet1Row1.GetData(1))
|
||||
assert.Equal(t, "C1", sheet1Row1.GetData(2))
|
||||
|
||||
sheet1Row2 := iterator.Next()
|
||||
assert.Equal(t, "1-A2", sheet1Row2.GetData(0))
|
||||
assert.Equal(t, "1-B2", sheet1Row2.GetData(1))
|
||||
assert.Equal(t, "1-C2", sheet1Row2.GetData(2))
|
||||
|
||||
sheet1Row3 := iterator.Next()
|
||||
assert.Equal(t, "1-A3", sheet1Row3.GetData(0))
|
||||
assert.Equal(t, "1-B3", sheet1Row3.GetData(1))
|
||||
assert.Equal(t, "1-C3", sheet1Row3.GetData(2))
|
||||
|
||||
// skip empty sheet2
|
||||
|
||||
sheet3Row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", sheet3Row1.GetData(0))
|
||||
assert.Equal(t, "B1", sheet3Row1.GetData(1))
|
||||
assert.Equal(t, "C1", sheet3Row1.GetData(2))
|
||||
|
||||
sheet3Row2 := iterator.Next()
|
||||
assert.Equal(t, "3-A2", sheet3Row2.GetData(0))
|
||||
assert.Equal(t, "3-B2", sheet3Row2.GetData(1))
|
||||
assert.Equal(t, "", sheet3Row2.GetData(2))
|
||||
|
||||
sheet4Row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", sheet4Row1.GetData(0))
|
||||
assert.Equal(t, "B1", sheet4Row1.GetData(1))
|
||||
assert.Equal(t, "C1", sheet4Row1.GetData(2))
|
||||
|
||||
sheet5Row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", sheet5Row1.GetData(0))
|
||||
assert.Equal(t, "B1", sheet5Row1.GetData(1))
|
||||
assert.Equal(t, "C1", sheet5Row1.GetData(2))
|
||||
|
||||
sheet5Row2 := iterator.Next()
|
||||
assert.Equal(t, "5-A2", sheet5Row2.GetData(0))
|
||||
assert.Equal(t, "5-B2", sheet5Row2.GetData(1))
|
||||
assert.Equal(t, "5-C2", sheet5Row2.GetData(2))
|
||||
|
||||
sheet5Row3 := iterator.Next()
|
||||
assert.Equal(t, "5-A3", sheet5Row3.GetData(0))
|
||||
assert.Equal(t, "5-B3", sheet5Row3.GetData(1))
|
||||
assert.Equal(t, "5-C3", sheet5Row3.GetData(2))
|
||||
}
|
||||
|
||||
func TestExcelMSCFBFileBasicDataRowGetData_MultipleSheets_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
sheet1Row1 := iterator.Next()
|
||||
@@ -241,6 +459,6 @@ func TestCreateNewExcelMSCFBFileBasicDataTable_MultipleSheetsWithDifferentHeader
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_with_different_header_row_excel_file.xls")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = CreateNewExcelMSCFBFileBasicDataTable(testdata)
|
||||
_, err = CreateNewExcelMSCFBFileBasicDataTable(testdata, true)
|
||||
assert.EqualError(t, err, errs.ErrFieldsInMultiTableAreDifferent.Message)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type excelOOXMLSheet struct {
|
||||
type ExcelOOXMLFileBasicDataTable struct {
|
||||
sheets []*excelOOXMLSheet
|
||||
headerLineColumnNames []string
|
||||
hasTitleLine bool
|
||||
}
|
||||
|
||||
// ExcelOOXMLFileBasicDataTableRow defines the structure of excel (Office Open XML) file data table row
|
||||
@@ -47,7 +48,11 @@ func (t *ExcelOOXMLFileBasicDataTable) DataRowCount() int {
|
||||
continue
|
||||
}
|
||||
|
||||
totalDataRowCount += len(sheet.allData) - 1
|
||||
if t.hasTitleLine {
|
||||
totalDataRowCount += len(sheet.allData) - 1
|
||||
} else {
|
||||
totalDataRowCount += len(sheet.allData)
|
||||
}
|
||||
}
|
||||
|
||||
return totalDataRowCount
|
||||
@@ -55,15 +60,25 @@ func (t *ExcelOOXMLFileBasicDataTable) DataRowCount() int {
|
||||
|
||||
// HeaderColumnNames returns the header column name list
|
||||
func (t *ExcelOOXMLFileBasicDataTable) HeaderColumnNames() []string {
|
||||
if !t.hasTitleLine {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.headerLineColumnNames
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *ExcelOOXMLFileBasicDataTable) DataRowIterator() datatable.BasicDataTableRowIterator {
|
||||
startIndex := -1
|
||||
|
||||
if t.hasTitleLine {
|
||||
startIndex = 0
|
||||
}
|
||||
|
||||
return &ExcelOOXMLFileBasicDataTableRowIterator{
|
||||
dataTable: t,
|
||||
currentSheetIndex: 0,
|
||||
currentRowIndexInSheet: 0,
|
||||
currentRowIndexInSheet: startIndex,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,8 +113,14 @@ func (t *ExcelOOXMLFileBasicDataTableRowIterator) HasNext() bool {
|
||||
for i := t.currentSheetIndex + 1; i < len(sheets); i++ {
|
||||
sheet := sheets[i]
|
||||
|
||||
if len(sheet.allData) <= 1 {
|
||||
continue
|
||||
if t.dataTable.hasTitleLine {
|
||||
if len(sheet.allData) <= 1 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if len(sheet.allData) <= 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -116,20 +137,22 @@ func (t *ExcelOOXMLFileBasicDataTableRowIterator) CurrentRowId() string {
|
||||
// Next returns the next basic data row
|
||||
func (t *ExcelOOXMLFileBasicDataTableRowIterator) Next() datatable.BasicDataTableRow {
|
||||
sheets := t.dataTable.sheets
|
||||
currentRowIndexInTable := t.currentRowIndexInSheet
|
||||
|
||||
for i := t.currentSheetIndex; i < len(sheets); i++ {
|
||||
sheet := sheets[i]
|
||||
|
||||
if currentRowIndexInTable+1 < len(sheet.allData) {
|
||||
if t.currentRowIndexInSheet+1 < len(sheet.allData) {
|
||||
t.currentRowIndexInSheet++
|
||||
currentRowIndexInTable = t.currentRowIndexInSheet
|
||||
break
|
||||
}
|
||||
|
||||
t.currentSheetIndex++
|
||||
t.currentRowIndexInSheet = 0
|
||||
currentRowIndexInTable = 0
|
||||
|
||||
if t.dataTable.hasTitleLine {
|
||||
t.currentRowIndexInSheet = 0
|
||||
} else {
|
||||
t.currentRowIndexInSheet = -1
|
||||
}
|
||||
}
|
||||
|
||||
if t.currentSheetIndex >= len(sheets) {
|
||||
@@ -150,7 +173,7 @@ func (t *ExcelOOXMLFileBasicDataTableRowIterator) Next() datatable.BasicDataTabl
|
||||
}
|
||||
|
||||
// CreateNewExcelOOXMLFileBasicDataTable returns excel (Office Open XML) data table by file binary data
|
||||
func CreateNewExcelOOXMLFileBasicDataTable(data []byte) (datatable.BasicDataTable, error) {
|
||||
func CreateNewExcelOOXMLFileBasicDataTable(data []byte, hasTitleLine bool) (datatable.BasicDataTable, error) {
|
||||
reader := bytes.NewReader(data)
|
||||
file, err := excelize.OpenReader(reader)
|
||||
|
||||
@@ -161,7 +184,7 @@ func CreateNewExcelOOXMLFileBasicDataTable(data []byte) (datatable.BasicDataTabl
|
||||
}
|
||||
|
||||
sheetNames := file.GetSheetList()
|
||||
var headerRowItems []string
|
||||
var firstRowItems []string
|
||||
var sheets []*excelOOXMLSheet
|
||||
|
||||
for i := 0; i < len(sheetNames); i++ {
|
||||
@@ -186,13 +209,13 @@ func CreateNewExcelOOXMLFileBasicDataTable(data []byte) (datatable.BasicDataTabl
|
||||
break
|
||||
}
|
||||
|
||||
headerRowItems = append(headerRowItems, headerItem)
|
||||
firstRowItems = append(firstRowItems, headerItem)
|
||||
}
|
||||
} else {
|
||||
for j := 0; j < min(len(row), len(headerRowItems)); j++ {
|
||||
for j := 0; j < min(len(row), len(firstRowItems)); j++ {
|
||||
headerItem := row[j]
|
||||
|
||||
if headerItem != headerRowItems[j] {
|
||||
if headerItem != firstRowItems[j] {
|
||||
return nil, errs.ErrFieldsInMultiTableAreDifferent
|
||||
}
|
||||
}
|
||||
@@ -204,8 +227,15 @@ func CreateNewExcelOOXMLFileBasicDataTable(data []byte) (datatable.BasicDataTabl
|
||||
})
|
||||
}
|
||||
|
||||
var headerLineColumnNames []string = nil
|
||||
|
||||
if hasTitleLine {
|
||||
headerLineColumnNames = firstRowItems
|
||||
}
|
||||
|
||||
return &ExcelOOXMLFileBasicDataTable{
|
||||
sheets: sheets,
|
||||
headerLineColumnNames: headerRowItems,
|
||||
headerLineColumnNames: headerLineColumnNames,
|
||||
hasTitleLine: hasTitleLine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,16 @@ func TestExcelOOXMLFileBasicDataTableDataRowCount(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileBasicDataTableDataRowCount_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, datatable.DataRowCount())
|
||||
}
|
||||
@@ -22,7 +31,16 @@ func TestExcelOOXMLFileBasicDataTableDataRowCount_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 9, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileBasicDataTableDataRowCount_MultipleSheets_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 5, datatable.DataRowCount())
|
||||
}
|
||||
@@ -31,7 +49,7 @@ func TestExcelOOXMLFileBasicDataTableDataRowCount_OnlyHeaderLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/only_one_row_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
}
|
||||
@@ -40,7 +58,11 @@ func TestExcelOOXMLFileBasicDataTableDataRowCount_EmptyContent(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
|
||||
datatable, err = CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
}
|
||||
@@ -49,7 +71,17 @@ func TestExcelOOXMLFileBasicDataTableHeaderColumnNames(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileBasicDataTableHeaderColumnNames_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"A1", "B1", "C1"}, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
@@ -57,7 +89,12 @@ func TestExcelOOXMLFileBasicDataTableHeaderColumnNames_EmptyContent(t *testing.T
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
|
||||
datatable, err = CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
@@ -65,7 +102,34 @@ func TestExcelOOXMLFileBasicDataRowIterator(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 3
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 4
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileBasicDataRowIterator_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
@@ -90,7 +154,62 @@ func TestExcelOOXMLFileBasicDataRowIterator_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 1 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 1 data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 1 data row 3
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 3 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 3 data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 4 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 5 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 5 data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 5 data row 3
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileBasicDataRowIterator_MultipleSheets_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
@@ -127,7 +246,8 @@ func TestExcelOOXMLFileBasicDataRowIterator_OnlyHeaderLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/only_one_row_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
@@ -144,7 +264,8 @@ func TestExcelOOXMLFileBasicDataRowIterator_EmptyContent(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
@@ -155,13 +276,27 @@ func TestExcelOOXMLFileBasicDataRowIterator_EmptyContent(t *testing.T) {
|
||||
// not existed data row 2
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
datatable, err = CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator = datatable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 1
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 2
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileBasicDataRowColumnCount(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
@@ -175,7 +310,32 @@ func TestExcelOOXMLFileBasicDataRowGetData(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", row1.GetData(0))
|
||||
assert.Equal(t, "B1", row1.GetData(1))
|
||||
assert.Equal(t, "C1", row1.GetData(2))
|
||||
|
||||
row2 := iterator.Next()
|
||||
assert.Equal(t, "A2", row2.GetData(0))
|
||||
assert.Equal(t, "B2", row2.GetData(1))
|
||||
assert.Equal(t, "C2", row2.GetData(2))
|
||||
|
||||
row3 := iterator.Next()
|
||||
assert.Equal(t, "A3", row3.GetData(0))
|
||||
assert.Equal(t, "B3", row3.GetData(1))
|
||||
assert.Equal(t, "C3", row3.GetData(2))
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileBasicDataRowGetData_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
@@ -193,7 +353,8 @@ func TestExcelOOXMLFileBasicDataRowGetData_GetNotExistedColumnData(t *testing.T)
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
@@ -204,7 +365,64 @@ func TestExcelOOXMLFileBasicDataRowGetData_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, false)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
sheet1Row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", sheet1Row1.GetData(0))
|
||||
assert.Equal(t, "B1", sheet1Row1.GetData(1))
|
||||
assert.Equal(t, "C1", sheet1Row1.GetData(2))
|
||||
|
||||
sheet1Row2 := iterator.Next()
|
||||
assert.Equal(t, "1-A2", sheet1Row2.GetData(0))
|
||||
assert.Equal(t, "1-B2", sheet1Row2.GetData(1))
|
||||
assert.Equal(t, "1-C2", sheet1Row2.GetData(2))
|
||||
|
||||
sheet1Row3 := iterator.Next()
|
||||
assert.Equal(t, "1-A3", sheet1Row3.GetData(0))
|
||||
assert.Equal(t, "1-B3", sheet1Row3.GetData(1))
|
||||
assert.Equal(t, "1-C3", sheet1Row3.GetData(2))
|
||||
|
||||
// skip empty sheet2
|
||||
|
||||
sheet3Row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", sheet3Row1.GetData(0))
|
||||
assert.Equal(t, "B1", sheet3Row1.GetData(1))
|
||||
assert.Equal(t, "C1", sheet3Row1.GetData(2))
|
||||
|
||||
sheet3Row2 := iterator.Next()
|
||||
assert.Equal(t, "3-A2", sheet3Row2.GetData(0))
|
||||
assert.Equal(t, "3-B2", sheet3Row2.GetData(1))
|
||||
assert.Equal(t, "", sheet3Row2.GetData(2))
|
||||
|
||||
sheet4Row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", sheet4Row1.GetData(0))
|
||||
assert.Equal(t, "B1", sheet4Row1.GetData(1))
|
||||
assert.Equal(t, "C1", sheet4Row1.GetData(2))
|
||||
|
||||
sheet5Row1 := iterator.Next()
|
||||
assert.Equal(t, "A1", sheet5Row1.GetData(0))
|
||||
assert.Equal(t, "B1", sheet5Row1.GetData(1))
|
||||
assert.Equal(t, "C1", sheet5Row1.GetData(2))
|
||||
|
||||
sheet5Row2 := iterator.Next()
|
||||
assert.Equal(t, "5-A2", sheet5Row2.GetData(0))
|
||||
assert.Equal(t, "5-B2", sheet5Row2.GetData(1))
|
||||
assert.Equal(t, "5-C2", sheet5Row2.GetData(2))
|
||||
|
||||
sheet5Row3 := iterator.Next()
|
||||
assert.Equal(t, "5-A3", sheet5Row3.GetData(0))
|
||||
assert.Equal(t, "5-B3", sheet5Row3.GetData(1))
|
||||
assert.Equal(t, "5-C3", sheet5Row3.GetData(2))
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileBasicDataRowGetData_MultipleSheets_HasTitleLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.Nil(t, err)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
sheet1Row1 := iterator.Next()
|
||||
@@ -241,6 +459,6 @@ func TestCreateNewExcelOOXMLFileBasicDataTable_MultipleSheetsWithDifferentHeader
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_with_different_header_row_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = CreateNewExcelOOXMLFileBasicDataTable(testdata)
|
||||
_, err = CreateNewExcelOOXMLFileBasicDataTable(testdata, true)
|
||||
assert.EqualError(t, err, errs.ErrFieldsInMultiTableAreDifferent.Message)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@ package feidee
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
csvdatatable "github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
@@ -60,7 +59,13 @@ func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx c
|
||||
fallback := unicode.UTF8.NewDecoder()
|
||||
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
||||
|
||||
dataTable, err := c.createNewFeideeMymoneyAppBasicDataTable(ctx, reader)
|
||||
csvDataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataTable, err := createNewFeideeMymoneyAppTransactionBasicDataTable(ctx, csvDataTable)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
@@ -89,54 +94,6 @@ func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx c
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) createNewFeideeMymoneyAppBasicDataTable(ctx core.Context, reader io.Reader) (datatable.BasicDataTable, error) {
|
||||
csvReader := csv.NewReader(reader)
|
||||
csvReader.FieldsPerRecord = -1
|
||||
|
||||
allOriginalLines := make([][]string, 0)
|
||||
hasFileHeader := false
|
||||
|
||||
for {
|
||||
items, err := csvReader.Read()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] cannot parse feidee mymoney csv data, because %s", err.Error())
|
||||
return nil, errs.ErrInvalidCSVFile
|
||||
}
|
||||
|
||||
if !hasFileHeader {
|
||||
if len(items) <= 0 {
|
||||
continue
|
||||
} else if strings.Index(items[0], feideeMymoneyAppTransactionDataCsvFileHeader) == 0 {
|
||||
hasFileHeader = true
|
||||
continue
|
||||
} else {
|
||||
log.Warnf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
allOriginalLines = append(allOriginalLines, items)
|
||||
}
|
||||
|
||||
if !hasFileHeader {
|
||||
return nil, errs.ErrInvalidFileHeader
|
||||
}
|
||||
|
||||
if len(allOriginalLines) < 2 {
|
||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] cannot parse import data, because data table row count is less 1")
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
dataTable := csvdatatable.CreateNewCustomCsvBasicDataTable(allOriginalLines)
|
||||
|
||||
return dataTable, nil
|
||||
}
|
||||
|
||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) createNewFeideeMymoneyAppTransactionDataTable(ctx core.Context, commonDataTable datatable.CommonDataTable) (datatable.TransactionDataTable, error) {
|
||||
newColumns := make([]datatable.TransactionDataTableColumn, 0, 11)
|
||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package feidee
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
)
|
||||
|
||||
func createNewFeideeMymoneyAppTransactionBasicDataTable(ctx core.Context, originalDataTable datatable.BasicDataTable) (datatable.BasicDataTable, error) {
|
||||
iterator := originalDataTable.DataRowIterator()
|
||||
allOriginalLines := make([][]string, 0)
|
||||
hasFileHeader := false
|
||||
|
||||
for iterator.HasNext() {
|
||||
row := iterator.Next()
|
||||
|
||||
if !hasFileHeader {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
} else if strings.Index(row.GetData(0), feideeMymoneyAppTransactionDataCsvFileHeader) == 0 {
|
||||
hasFileHeader = true
|
||||
continue
|
||||
} else {
|
||||
log.Warnf(ctx, "[feidee_mymoney_app_transaction_data_extrator.createNewFeideeMymoneyAppTransactionBasicDataTable] read unexpected line in row \"%s\" before read file header", iterator.CurrentRowId())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]string, row.ColumnCount())
|
||||
|
||||
for i := 0; i < row.ColumnCount(); i++ {
|
||||
items[i] = strings.Trim(row.GetData(i), " ")
|
||||
}
|
||||
|
||||
allOriginalLines = append(allOriginalLines, items)
|
||||
}
|
||||
|
||||
if !hasFileHeader {
|
||||
return nil, errs.ErrInvalidFileHeader
|
||||
}
|
||||
|
||||
if len(allOriginalLines) < 2 {
|
||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_extrator.createNewFeideeMymoneyAppTransactionBasicDataTable] cannot parse import data, because data table row count is less 1")
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
return csv.CreateNewCustomCsvBasicDataTable(allOriginalLines, true), nil
|
||||
}
|
||||
@@ -32,7 +32,7 @@ var (
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the feidee mymoney (elecloud) transaction xlsx data
|
||||
func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
dataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data)
|
||||
dataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, true)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
|
||||
@@ -31,7 +31,7 @@ var (
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the feidee mymoney (web) transaction xls data
|
||||
func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
dataTable, err := excel.CreateNewExcelMSCFBFileBasicDataTable(data)
|
||||
dataTable, err := excel.CreateNewExcelMSCFBFileBasicDataTable(data, true)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
|
||||
@@ -7,24 +7,21 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
var fireflyIIITransactionSupportedColumns = map[datatable.TransactionDataTableColumn]bool{
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_TAGS: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: true,
|
||||
var fireflyIIITransactionDataColumnNameMapping = map[datatable.TransactionDataTableColumn]string{
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "date",
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "type",
|
||||
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: "category",
|
||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: "source_name",
|
||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY: "currency_code",
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: "amount",
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "destination_name",
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY: "foreign_currency_code",
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT: "foreign_amount",
|
||||
datatable.TRANSACTION_DATA_TABLE_TAGS: "tags",
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: "description",
|
||||
}
|
||||
|
||||
var fireflyIIITransactionTypeNameMapping = map[models.TransactionType]string{
|
||||
@@ -45,27 +42,14 @@ var (
|
||||
// ParseImportedData returns the imported data by parsing the firefly III transaction csv data
|
||||
func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
reader := bytes.NewReader(data)
|
||||
dataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader)
|
||||
dataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, true)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
commonDataTable := datatable.CreateNewCommonDataTableFromBasicDataTable(dataTable)
|
||||
|
||||
if !commonDataTable.HasColumn(fireflyIIITransactionTimeColumnName) ||
|
||||
!commonDataTable.HasColumn(fireflyIIITransactionTypeColumnName) ||
|
||||
!commonDataTable.HasColumn(fireflyIIITransactionSourceAccountNameColumnName) ||
|
||||
!commonDataTable.HasColumn(fireflyIIITransactionSourceAccountTypeColumnName) ||
|
||||
!commonDataTable.HasColumn(fireflyIIITransactionDestinationAccountNameColumnName) ||
|
||||
!commonDataTable.HasColumn(fireflyIIITransactionDestinationAccountTypeColumnName) ||
|
||||
!commonDataTable.HasColumn(fireflyIIITransactionAmountColumnName) {
|
||||
log.Errorf(ctx, "[fireflyiii_transaction_data_csv_file_importer.ParseImportedData] cannot parse Firefly III csv data, because missing essential columns in header row")
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||
}
|
||||
|
||||
transactionRowParser := createFireflyIIITransactionDataRowParser()
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, fireflyIIITransactionSupportedColumns, transactionRowParser)
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(fireflyIIITransactionTypeNameMapping, "", "", ",")
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
|
||||
@@ -20,11 +20,11 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MinimumValidData(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Initial balance account\",\"Test Account\",\"Asset account\",\n"+
|
||||
"Deposit,0.12,2024-09-01T01:23:45+08:00,\"A revenue account\",\"Revenue account\",\"Test Account\",\"Asset account\",\"Test Category\"\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category2\"\n"+
|
||||
"Transfer,0.05,2024-09-01T23:59:59+08:00,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category3\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
|
||||
"Deposit,0.12,2024-09-01T01:23:45+08:00,\"A revenue account\",\"Test Account\",\"Test Category\"\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category2\"\n"+
|
||||
"Transfer,0.05,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category3\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -91,16 +91,16 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidTime(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01 12:34:56+08:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01 12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidTransactionType(t *testing.T) {
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
@@ -109,107 +109,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidTransactionType(t
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
// income transactions
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Deposit,10.00,2024-09-01T12:34:56+08:00,\"A revenue account\",\"Revenue account\",\"Test Account\",\"Asset account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Deposit,10.00,2024-09-01T12:34:56+08:00,\"A revenue account\",\"Revenue account\",\"Test Account\",\"Debt\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
|
||||
// expense transactions
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-10.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-10.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Debt\",\"A expense account\",\"Expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
|
||||
// opening balance transactions
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",10.00,2024-09-01T12:34:56+08:00,\"Initial balance\",\"Initial balance account\",\"Test Account\",\"Asset account\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_MODIFY_BALANCE, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
|
||||
// transfer transactions
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account2", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-10.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Asset account\",\"Test Account2\",\"Debt\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account2", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Deposit,10.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Debt\",\"Test Account2\",\"Asset account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account2", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Debt\",\"Test Account2\",\"Debt\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1000), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account2", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidTransactionType(t *testing.T) {
|
||||
converter := FireflyIIITransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Revenue account\",\"Test Account2\",\"Expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Type,123.45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -222,15 +123,15 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseAccountNameAsCategoryN
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "A expense account", allNewTransactions[0].OriginalCategoryName)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Deposit,10.00,2024-09-01T12:34:56+08:00,\"A revenue account\",\"Revenue account\",\"Test Account\",\"Asset account\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Deposit,10.00,2024-09-01T12:34:56+08:00,\"A revenue account\",\"Test Account\",\"\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -246,20 +147,20 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidTimezone(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56-10:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56-10:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+00:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+00:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+12:45,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-1.00,2024-09-01T12:34:56+12:45,\"Test Account\",\"A expense account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
@@ -274,9 +175,9 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidAccountCurrency(t
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Initial balance account\",\"Test Account\",\"Asset account\",\n"+
|
||||
"Transfer,1.23,-1.10,2024-09-01T23:59:59+08:00,USD,EUR,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category2\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,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)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -301,8 +202,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Transfer,10.00,15.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,15.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -312,8 +213,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -322,8 +223,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseValidForeignAmountAndC
|
||||
assert.Equal(t, "USD", allNewTransactions[0].OriginalSourceAccountCurrency)
|
||||
assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,currency_code,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -340,14 +241,14 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAccountCurrency
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Initial balance account\",\"Test Account\",\"Asset account\",\n"+
|
||||
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category3\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,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)
|
||||
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,source_type,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Initial balance account\",\"Test Account\",\n"+
|
||||
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account2\",\"Asset account\",\"Test Account\",\"Asset account\",\"Test Category3\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,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)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -360,12 +261,12 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseNotSupportedCurrency(t
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,XXX,,\"Initial balance for \"\"Test Account\"\"\",\"Initial balance account\",\"Test Account\",\"Asset account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,XXX,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
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,source_type,destination_name,destination_type,category\n"+
|
||||
"Transfer,123.45,123.45,2024-09-01T23:59:59+08:00,USD,XXX,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category2\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
|
||||
"Transfer,123.45,123.45,2024-09-01T23:59:59+08:00,USD,XXX,\"Test Account\",\"Test Account2\",\"Test Category2\""), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -378,12 +279,12 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseInvalidAmount(t *testi
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-123 45,2024-09-01T12:34:56+08:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123 45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Transfer,123.45,123 45,2024-09-01T23:59:59+08:00,\"Test Account\",\"Asset account\",\"Test Account2\",\"Asset account\",\"Test Category2\""), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,source_name,destination_name,category\n"+
|
||||
"Transfer,123.45,123 45,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category2\""), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
@@ -396,8 +297,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseDescription(t *testing
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,description,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-123.45,\"foo bar\t#test\",2024-09-01T12:34:56+08:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,description,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123.45,\"foo bar\t#test\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -413,8 +314,8 @@ func TestFireFlyIIICsvFileConverterParseImportedData_ParseTags(t *testing.T) {
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte("type,amount,tags,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"Withdrawal,-123.45,\"tag1,tag2,tag3\",2024-09-01T12:34:56+08:00,\"Test Account\",\"Asset account\",\"A expense account\",\"Expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
allNewTransactions, _, _, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte("type,amount,tags,date,source_name,destination_name,category\n"+
|
||||
"Withdrawal,-123.45,\"tag1,tag2,tag3\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
@@ -437,7 +338,7 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MissingFileHeader(t *testin
|
||||
}
|
||||
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestFireFlyIIICsvFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
@@ -450,13 +351,18 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MissingRequiredColumn(t *te
|
||||
}
|
||||
|
||||
// Missing Time Column
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",123.45,\"Initial balance for \"\"Test Account\"\"\",\"Initial balance account\",\"Test Account\",\"Asset account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte("type,amount,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Type Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("amount,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Initial balance account\",\"Test Account\",\"Asset account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("amount,date,source_name,destination_name,category\n"+
|
||||
"123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Sub Category Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\"\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Account Name Column
|
||||
@@ -465,22 +371,12 @@ func TestFireFlyIIICsvFileConverterParseImportedData_MissingRequiredColumn(t *te
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,date,source_name,source_type,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Initial balance account\",\"Test Account\",\"Asset account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,date,source_name,destination_name,category\n"+
|
||||
"\"Opening balance\",2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Account2 Name Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Source Account Type Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,destination_type,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\"Asset account\",\"Asset account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Destination Account Type Column
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte("type,amount,date,source_name,source_type,destination_name,category\n"+
|
||||
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Asset account\",\"Test Account\",\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
@@ -2,132 +2,103 @@ package fireflyIII
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"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/utils"
|
||||
)
|
||||
|
||||
const fireflyIIITransactionTimeColumnName = "date"
|
||||
const fireflyIIITransactionTypeColumnName = "type"
|
||||
const fireflyIIITransactionCategoryColumnName = "category"
|
||||
const fireflyIIITransactionSourceAccountNameColumnName = "source_name"
|
||||
const fireflyIIITransactionSourceAccountTypeColumnName = "source_type"
|
||||
const fireflyIIITransactionCurrencyCodeColumnName = "currency_code"
|
||||
const fireflyIIITransactionAmountColumnName = "amount"
|
||||
const fireflyIIITransactionDestinationAccountNameColumnName = "destination_name"
|
||||
const fireflyIIITransactionDestinationAccountTypeColumnName = "destination_type"
|
||||
const fireflyIIITransactionForeignCurrencyCodeColumnName = "foreign_currency_code"
|
||||
const fireflyIIITransactionForeignAmountColumnName = "foreign_amount"
|
||||
const fireflyIIITransactionTagsColumnName = "tags"
|
||||
const fireflyIIITransactionDescriptionColumnName = "description"
|
||||
|
||||
const fireflyIIIAssetAccountName = "Asset account"
|
||||
const fireflyIIIExpenseAccountName = "Expense account"
|
||||
const fireflyIIIRevenueAccountName = "Revenue account"
|
||||
const fireflyIIIDebtAccountName = "Debt"
|
||||
|
||||
// fireflyIIITransactionDataRowParser defines the structure of firefly III transaction data row parser
|
||||
type fireflyIIITransactionDataRowParser struct {
|
||||
}
|
||||
|
||||
// GetAddedColumns returns the added columns after converting the data row
|
||||
func (p *fireflyIIITransactionDataRowParser) GetAddedColumns() []datatable.TransactionDataTableColumn {
|
||||
return []datatable.TransactionDataTableColumn{
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse returns the converted transaction data row
|
||||
func (p *fireflyIIITransactionDataRowParser) Parse(ctx core.Context, user *models.User, dataRow datatable.CommonDataTableRow, rowId string) (rowData map[datatable.TransactionDataTableColumn]string, rowDataValid bool, err error) {
|
||||
rowData = make(map[datatable.TransactionDataTableColumn]string, len(fireflyIIITransactionSupportedColumns))
|
||||
func (p *fireflyIIITransactionDataRowParser) Parse(data map[datatable.TransactionDataTableColumn]string) (rowData map[datatable.TransactionDataTableColumn]string, rowDataValid bool, err error) {
|
||||
rowData = make(map[datatable.TransactionDataTableColumn]string, len(data))
|
||||
|
||||
for column, value := range data {
|
||||
rowData[column] = value
|
||||
}
|
||||
|
||||
// use the expense and revenue account name as category names if the category name is empty
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] == "" {
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME]
|
||||
} else if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME]
|
||||
}
|
||||
}
|
||||
|
||||
// parse long date time and timezone
|
||||
dateTime, err := utils.ParseFromLongDateTimeWithTimezoneRFC3339Format(dataRow.GetData(fireflyIIITransactionTimeColumnName))
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] != "" {
|
||||
dateTime, err := utils.ParseFromLongDateTimeWithTimezoneRFC3339Format(rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME])
|
||||
|
||||
if err != nil {
|
||||
return nil, false, errs.ErrTransactionTimeInvalid
|
||||
if err != nil {
|
||||
return nil, false, errs.ErrTransactionTimeInvalid
|
||||
}
|
||||
|
||||
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_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location())
|
||||
|
||||
// parse transaction type, transaction category and amount
|
||||
transactionType := dataRow.GetData(fireflyIIITransactionTypeColumnName)
|
||||
sourceAccountType := dataRow.GetData(fireflyIIITransactionSourceAccountTypeColumnName)
|
||||
destinationAccountType := dataRow.GetData(fireflyIIITransactionDestinationAccountTypeColumnName)
|
||||
|
||||
amount, err := utils.ParseAmount(utils.TrimTrailingZerosInDecimal(dataRow.GetData(fireflyIIITransactionAmountColumnName)))
|
||||
|
||||
if err != nil {
|
||||
return nil, false, errs.ErrAmountInvalid
|
||||
}
|
||||
|
||||
foreignAmount := amount
|
||||
|
||||
if dataRow.HasData(fireflyIIITransactionForeignAmountColumnName) && dataRow.GetData(fireflyIIITransactionForeignAmountColumnName) != "" {
|
||||
foreignAmount, err = utils.ParseAmount(utils.TrimTrailingZerosInDecimal(dataRow.GetData(fireflyIIITransactionForeignAmountColumnName)))
|
||||
// trim trailing zero in decimal
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] != "" {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.TrimTrailingZerosInDecimal(rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
|
||||
amount, err := utils.ParseAmount(rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
|
||||
|
||||
if err != nil {
|
||||
return nil, false, errs.ErrAmountInvalid
|
||||
}
|
||||
}
|
||||
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(fireflyIIITransactionCategoryColumnName)
|
||||
|
||||
if sourceAccountType == fireflyIIIRevenueAccountName && (destinationAccountType == fireflyIIIAssetAccountName || destinationAccountType == fireflyIIIDebtAccountName) { // income
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME]
|
||||
|
||||
// if the category is empty, use the source account (revenue account) name as the category name
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] == "" {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(fireflyIIITransactionSourceAccountNameColumnName)
|
||||
}
|
||||
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountNameColumnName)
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
|
||||
} else if (sourceAccountType == fireflyIIIAssetAccountName || sourceAccountType == fireflyIIIDebtAccountName) && destinationAccountType == fireflyIIIExpenseAccountName { // expense
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE]
|
||||
|
||||
// if the category is empty, use the destination account (expense account) name as the category name
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] == "" {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(fireflyIIITransactionDestinationAccountNameColumnName)
|
||||
}
|
||||
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionSourceAccountNameColumnName)
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount)
|
||||
} else if transactionType == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE] { // opening balance
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE]
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = ""
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountNameColumnName)
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
|
||||
} else if (sourceAccountType == fireflyIIIAssetAccountName || sourceAccountType == fireflyIIIDebtAccountName) && (destinationAccountType == fireflyIIIAssetAccountName || destinationAccountType == fireflyIIIDebtAccountName) { // transfer
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER]
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionSourceAccountNameColumnName)
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountNameColumnName)
|
||||
|
||||
if transactionType == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] {
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount)
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(-foreignAmount)
|
||||
} else {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(foreignAmount)
|
||||
}
|
||||
} else {
|
||||
log.Errorf(ctx, "[fireflyiii_transaction_data_row_parser.Parse] cannot detect transaction type, source account type is \"%s\", destination account type is \"%s\", Firefly III transaction type is \"%s\"", sourceAccountType, destinationAccountType, transactionType)
|
||||
return nil, false, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
// parse account currency
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = dataRow.GetData(fireflyIIITransactionCurrencyCodeColumnName)
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = dataRow.GetData(fireflyIIITransactionForeignCurrencyCodeColumnName)
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] != "" {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.TrimTrailingZerosInDecimal(rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT])
|
||||
amount, err := utils.ParseAmount(rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT])
|
||||
|
||||
if err != nil {
|
||||
return nil, false, errs.ErrAmountInvalid
|
||||
}
|
||||
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(-amount)
|
||||
} else {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(amount)
|
||||
}
|
||||
} else {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT]
|
||||
}
|
||||
|
||||
// the related account currency field is foreign currency in firefly III actually
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] == "" && rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] != "" {
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] == "" {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY]
|
||||
}
|
||||
|
||||
// parse tags / description
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_TAGS] = dataRow.GetData(fireflyIIITransactionTagsColumnName)
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = dataRow.GetData(fireflyIIITransactionDescriptionColumnName)
|
||||
// the destination account of modify balance transaction in firefly III is the asset account
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE] {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME]
|
||||
}
|
||||
|
||||
// the destination account of income transaction in firefly III is the asset account
|
||||
if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] {
|
||||
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME]
|
||||
}
|
||||
|
||||
return rowData, true, nil
|
||||
}
|
||||
|
||||
// createFireflyIIITransactionDataRowParser returns firefly III transaction data row parser
|
||||
func createFireflyIIITransactionDataRowParser() datatable.CommonTransactionDataRowParser {
|
||||
func createFireflyIIITransactionDataRowParser() datatable.TransactionDataRowParser {
|
||||
return &fireflyIIITransactionDataRowParser{}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ func GetTransactionDataImporter(fileType string) (converter.TransactionDataImpor
|
||||
return alipay.AlipayAppTransactionDataCsvFileImporter, nil
|
||||
} else if fileType == "alipay_web_csv" {
|
||||
return alipay.AlipayWebTransactionDataCsvFileImporter, nil
|
||||
} else if fileType == "wechat_pay_app_xlsx" {
|
||||
return wechat.WeChatPayTransactionDataXlsxFileImporter, nil
|
||||
} else if fileType == "wechat_pay_app_csv" {
|
||||
return wechat.WeChatPayTransactionDataCsvFileImporter, nil
|
||||
} else {
|
||||
|
||||
@@ -2,14 +2,12 @@ package wechat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
csvdatatable "github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
@@ -17,22 +15,6 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
var wechatPayTransactionSupportedColumns = map[datatable.TransactionDataTableColumn]bool{
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: true,
|
||||
}
|
||||
|
||||
var wechatPayTransactionTypeNameMapping = map[models.TransactionType]string{
|
||||
models.TRANSACTION_TYPE_INCOME: "收入",
|
||||
models.TRANSACTION_TYPE_EXPENSE: "支出",
|
||||
models.TRANSACTION_TYPE_TRANSFER: "/",
|
||||
}
|
||||
|
||||
// wechatPayTransactionDataCsvFileImporter defines the structure of wechatPay csv importer for transaction data
|
||||
type wechatPayTransactionDataCsvFileImporter struct {
|
||||
fileHeaderLineBeginning string
|
||||
@@ -49,7 +31,13 @@ func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Con
|
||||
fallback := unicode.UTF8.NewDecoder()
|
||||
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
||||
|
||||
dataTable, err := c.createNewWeChatPayBasicDataTable(ctx, reader)
|
||||
csvDataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataTable, err := createNewWeChatPayTransactionBasicDataTable(ctx, csvDataTable)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
@@ -72,78 +60,3 @@ func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Con
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
|
||||
func (c *wechatPayTransactionDataCsvFileImporter) createNewWeChatPayBasicDataTable(ctx core.Context, reader io.Reader) (datatable.BasicDataTable, error) {
|
||||
csvReader := csv.NewReader(reader)
|
||||
csvReader.FieldsPerRecord = -1
|
||||
|
||||
allOriginalLines := make([][]string, 0)
|
||||
hasFileHeader := false
|
||||
foundContentBeforeDataHeaderLine := false
|
||||
|
||||
for {
|
||||
items, err := csvReader.Read()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[wechat_pay_transaction_data_csv_file_importer.createNewWeChatPayBasicDataTable] cannot parse wechat pay csv data, because %s", err.Error())
|
||||
return nil, errs.ErrInvalidCSVFile
|
||||
}
|
||||
|
||||
if !hasFileHeader {
|
||||
if len(items) <= 0 {
|
||||
continue
|
||||
} else if strings.Index(items[0], wechatPayTransactionDataCsvFileHeader) == 0 {
|
||||
hasFileHeader = true
|
||||
continue
|
||||
} else {
|
||||
log.Warnf(ctx, "[wechat_pay_transaction_data_csv_file_importer.createNewWeChatPayBasicDataTable] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !foundContentBeforeDataHeaderLine {
|
||||
if len(items) <= 0 {
|
||||
continue
|
||||
} else if strings.Index(items[0], wechatPayTransactionDataHeaderStartContentBeginning) == 0 {
|
||||
foundContentBeforeDataHeaderLine = true
|
||||
continue
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if foundContentBeforeDataHeaderLine {
|
||||
if len(items) <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := 0; i < len(items); i++ {
|
||||
items[i] = strings.Trim(items[i], " ")
|
||||
}
|
||||
|
||||
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
||||
log.Errorf(ctx, "[wechat_pay_transaction_data_csv_file_importer.createNewWeChatPayBasicDataTable] cannot parse row \"index:%d\", because may missing some columns (column count %d in data row is less than header column count %d)", len(allOriginalLines), len(items), len(allOriginalLines[0]))
|
||||
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||
}
|
||||
|
||||
allOriginalLines = append(allOriginalLines, items)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasFileHeader || !foundContentBeforeDataHeaderLine {
|
||||
return nil, errs.ErrInvalidFileHeader
|
||||
}
|
||||
|
||||
if len(allOriginalLines) < 2 {
|
||||
log.Errorf(ctx, "[wechat_pay_transaction_data_csv_file_importer.createNewWeChatPayBasicDataTable] cannot parse import data, because data table row count is less 1")
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
dataTable := csvdatatable.CreateNewCustomCsvBasicDataTable(allOriginalLines)
|
||||
|
||||
return dataTable, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
)
|
||||
|
||||
func createNewWeChatPayTransactionBasicDataTable(ctx core.Context, originalDataTable datatable.BasicDataTable) (datatable.BasicDataTable, error) {
|
||||
iterator := originalDataTable.DataRowIterator()
|
||||
allOriginalLines := make([][]string, 0)
|
||||
hasFileHeader := false
|
||||
foundContentBeforeDataHeaderLine := false
|
||||
|
||||
for iterator.HasNext() {
|
||||
row := iterator.Next()
|
||||
|
||||
if !hasFileHeader {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
} else if strings.Index(row.GetData(0), wechatPayTransactionDataCsvFileHeader) == 0 {
|
||||
hasFileHeader = true
|
||||
continue
|
||||
} else {
|
||||
log.Warnf(ctx, "[wechat_pay_transaction_data_extrator.createNewWeChatPayTransactionBasicDataTable] read unexpected line in row \"%s\" before read file header", iterator.CurrentRowId())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !foundContentBeforeDataHeaderLine {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
} else if strings.Index(row.GetData(0), wechatPayTransactionDataHeaderStartContentBeginning) == 0 {
|
||||
foundContentBeforeDataHeaderLine = true
|
||||
continue
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if foundContentBeforeDataHeaderLine {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
items := make([]string, row.ColumnCount())
|
||||
|
||||
for i := 0; i < row.ColumnCount(); i++ {
|
||||
items[i] = strings.Trim(row.GetData(i), " ")
|
||||
}
|
||||
|
||||
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
||||
log.Errorf(ctx, "[wechat_pay_transaction_data_extrator.createNewWeChatPayTransactionBasicDataTable] cannot parse row \"%s\", because may missing some columns (column count %d in data row is less than header column count %d)", iterator.CurrentRowId(), len(items), len(allOriginalLines[0]))
|
||||
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||
}
|
||||
|
||||
allOriginalLines = append(allOriginalLines, items)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasFileHeader || !foundContentBeforeDataHeaderLine {
|
||||
return nil, errs.ErrInvalidFileHeader
|
||||
}
|
||||
|
||||
if len(allOriginalLines) < 2 {
|
||||
log.Errorf(ctx, "[wechat_pay_transaction_data_extrator.createNewWeChatPayTransactionBasicDataTable] cannot parse import data, because data table row count is less 1")
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
return csv.CreateNewCustomCsvBasicDataTable(allOriginalLines, true), nil
|
||||
}
|
||||
@@ -29,6 +29,22 @@ const wechatPayTransactionDataCategoryTransferFromWeChatWallet = "零钱提现"
|
||||
|
||||
const wechatPayTransactionDataStatusRefundName = "退款"
|
||||
|
||||
var wechatPayTransactionSupportedColumns = map[datatable.TransactionDataTableColumn]bool{
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: true,
|
||||
}
|
||||
|
||||
var wechatPayTransactionTypeNameMapping = map[models.TransactionType]string{
|
||||
models.TRANSACTION_TYPE_INCOME: "收入",
|
||||
models.TRANSACTION_TYPE_EXPENSE: "支出",
|
||||
models.TRANSACTION_TYPE_TRANSFER: "/",
|
||||
}
|
||||
|
||||
// weChatPayTransactionDataRowParser defines the structure of wechat pay transaction data row parser
|
||||
type weChatPayTransactionDataRowParser struct {
|
||||
existedOriginalDataColumns map[string]bool
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"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/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
// wechatPayTransactionDataXlsxFileImporter defines the structure of wechatPay xlsx importer for transaction data
|
||||
type wechatPayTransactionDataXlsxFileImporter struct {
|
||||
dataHeaderStartContentBeginning string
|
||||
}
|
||||
|
||||
// Initialize a webchat pay transaction data xlsx file importer singleton instance
|
||||
var (
|
||||
WeChatPayTransactionDataXlsxFileImporter = &wechatPayTransactionDataXlsxFileImporter{}
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the wechat pay transaction csv data
|
||||
func (c *wechatPayTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
xlsxDataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataTable, err := createNewWeChatPayTransactionBasicDataTable(ctx, xlsxDataTable)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
commonDataTable := datatable.CreateNewCommonDataTableFromBasicDataTable(dataTable)
|
||||
|
||||
if !commonDataTable.HasColumn(wechatPayTransactionTimeColumnName) ||
|
||||
!commonDataTable.HasColumn(wechatPayTransactionCategoryColumnName) ||
|
||||
!commonDataTable.HasColumn(wechatPayTransactionTypeColumnName) ||
|
||||
!commonDataTable.HasColumn(wechatPayTransactionAmountColumnName) ||
|
||||
!commonDataTable.HasColumn(wechatPayTransactionStatusColumnName) {
|
||||
log.Errorf(ctx, "[wechat_pay_transaction_data_xlsx_file_importer.ParseImportedData] cannot parse wechat pay xlsx data, because missing essential columns in header row")
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||
}
|
||||
|
||||
transactionRowParser := createWeChatPayTransactionDataRowParser(dataTable.HeaderColumnNames())
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, wechatPayTransactionSupportedColumns, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(wechatPayTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package core
|
||||
|
||||
import "fmt"
|
||||
|
||||
// CalendarDisplayType represents calendar display type
|
||||
type CalendarDisplayType byte
|
||||
|
||||
// Calendar Display Type
|
||||
const (
|
||||
CALENDAR_DISPLAY_TYPE_DEFAULT CalendarDisplayType = 0
|
||||
CALENDAR_DISPLAY_TYPE_GREGORAIN CalendarDisplayType = 1
|
||||
CALENDAR_DISPLAY_TYPE_BUDDHIST CalendarDisplayType = 2
|
||||
CALENDAR_DISPLAY_TYPE_INVALID CalendarDisplayType = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the calendar display type enum
|
||||
func (f CalendarDisplayType) String() string {
|
||||
switch f {
|
||||
case CALENDAR_DISPLAY_TYPE_DEFAULT:
|
||||
return "Default"
|
||||
case CALENDAR_DISPLAY_TYPE_GREGORAIN:
|
||||
return "Gregorian"
|
||||
case CALENDAR_DISPLAY_TYPE_BUDDHIST:
|
||||
return "Buddhist"
|
||||
case CALENDAR_DISPLAY_TYPE_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||
}
|
||||
}
|
||||
|
||||
// DateDisplayType represents date display type
|
||||
type DateDisplayType byte
|
||||
|
||||
// Date Display Type
|
||||
const (
|
||||
DATE_DISPLAY_TYPE_DEFAULT DateDisplayType = 0
|
||||
DATE_DISPLAY_TYPE_GREGORAIN DateDisplayType = 1
|
||||
DATE_DISPLAY_TYPE_BUDDHIST DateDisplayType = 2
|
||||
DATE_DISPLAY_TYPE_INVALID DateDisplayType = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the date display type enum
|
||||
func (f DateDisplayType) String() string {
|
||||
switch f {
|
||||
case DATE_DISPLAY_TYPE_DEFAULT:
|
||||
return "Default"
|
||||
case DATE_DISPLAY_TYPE_GREGORAIN:
|
||||
return "Gregorian"
|
||||
case DATE_DISPLAY_TYPE_BUDDHIST:
|
||||
return "Buddhist"
|
||||
case DATE_DISPLAY_TYPE_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package core
|
||||
|
||||
import "context"
|
||||
|
||||
// Context is the base context of ezBookkeeping
|
||||
type Context interface {
|
||||
context.Context
|
||||
GetContextId() string
|
||||
GetClientLocale() string
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package core
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
@@ -23,6 +24,11 @@ const RemoteClientPortHeader = "X-Real-Port"
|
||||
// ClientTimezoneOffsetHeaderName represents the header name of client timezone offset
|
||||
const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset"
|
||||
|
||||
const tokenHeaderName = "Authorization"
|
||||
const tokenHeaderValuePrefix = "bearer "
|
||||
const tokenQueryStringParam = "token"
|
||||
const tokenCookieParam = "ebk_auth_token"
|
||||
|
||||
// WebContext represents the request and response context
|
||||
type WebContext struct {
|
||||
*gin.Context
|
||||
@@ -118,6 +124,41 @@ func (c *WebContext) GetCurrentUid() int64 {
|
||||
return claims.Uid
|
||||
}
|
||||
|
||||
// GetTokenStringFromHeader returns the token string from the request header
|
||||
func (c *WebContext) GetTokenStringFromHeader() string {
|
||||
tokenHeader := c.GetHeader(tokenHeaderName)
|
||||
|
||||
if len(tokenHeader) < 7 || !strings.EqualFold(tokenHeader[:7], tokenHeaderValuePrefix) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return tokenHeader[7:]
|
||||
}
|
||||
|
||||
// GetTokenStringFromQueryString returns the token string from the request query string
|
||||
func (c *WebContext) GetTokenStringFromQueryString() string {
|
||||
return c.Query(tokenQueryStringParam)
|
||||
}
|
||||
|
||||
// GetTokenStringFromCookie returns the token string from the request cookie
|
||||
func (c *WebContext) GetTokenStringFromCookie() string {
|
||||
tokenCookie, err := c.Cookie(tokenCookieParam)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return tokenCookie
|
||||
}
|
||||
|
||||
func (c *WebContext) SetTokenStringToCookie(token string, tokenExpiredTime int, path string) {
|
||||
if token != "" {
|
||||
c.SetCookie(tokenCookieParam, token, tokenExpiredTime, path, "", false, true)
|
||||
} else {
|
||||
c.SetCookie(tokenCookieParam, "", -1, path, "", false, true)
|
||||
}
|
||||
}
|
||||
|
||||
// GetClientLocale returns the client locale name
|
||||
func (c *WebContext) GetClientLocale() string {
|
||||
value := c.GetHeader(AcceptLanguageHeaderName)
|
||||
|
||||
@@ -4,6 +4,40 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NumeralSystem represents the type of numeral system
|
||||
type NumeralSystem byte
|
||||
|
||||
// Numeral System
|
||||
const (
|
||||
NUMERAL_SYSTEM_DEFAULT NumeralSystem = 0
|
||||
NUMERAL_SYSTEM_WESTERN_ARABIC_NUMERALS NumeralSystem = 1
|
||||
NUMERAL_SYSTEM_EASTERN_ARABIC_NUMERALS NumeralSystem = 2
|
||||
NUMERAL_SYSTEM_PERSIAN_DIGITS NumeralSystem = 3
|
||||
NUMERAL_SYSTEM_BURMESE_NUMERALS NumeralSystem = 4
|
||||
NUMERAL_SYSTEM_DEVANAGARI_NUMERALS NumeralSystem = 5
|
||||
NUMERAL_SYSTEM_INVALID NumeralSystem = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the decimal separator enum
|
||||
func (f NumeralSystem) String() string {
|
||||
switch f {
|
||||
case NUMERAL_SYSTEM_DEFAULT:
|
||||
return "Default"
|
||||
case NUMERAL_SYSTEM_WESTERN_ARABIC_NUMERALS:
|
||||
return "Western Arabic Numerals"
|
||||
case NUMERAL_SYSTEM_EASTERN_ARABIC_NUMERALS:
|
||||
return "Eastern Arabic Numerals"
|
||||
case NUMERAL_SYSTEM_PERSIAN_DIGITS:
|
||||
return "Persian Digits"
|
||||
case NUMERAL_SYSTEM_BURMESE_NUMERALS:
|
||||
return "Burmese Numerals"
|
||||
case NUMERAL_SYSTEM_DEVANAGARI_NUMERALS:
|
||||
return "Devanagari Numerals"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||
}
|
||||
}
|
||||
|
||||
// DecimalSeparator represents the type of decimal separator
|
||||
type DecimalSeparator byte
|
||||
|
||||
@@ -69,10 +103,11 @@ type DigitGroupingType byte
|
||||
|
||||
// Digit Grouping Type
|
||||
const (
|
||||
DIGIT_GROUPING_TYPE_DEFAULT DigitGroupingType = 0
|
||||
DIGIT_GROUPING_TYPE_NONE DigitGroupingType = 1
|
||||
DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR DigitGroupingType = 2
|
||||
DIGIT_GROUPING_TYPE_INVALID DigitGroupingType = 255
|
||||
DIGIT_GROUPING_TYPE_DEFAULT DigitGroupingType = 0
|
||||
DIGIT_GROUPING_TYPE_NONE DigitGroupingType = 1
|
||||
DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR DigitGroupingType = 2
|
||||
DIGIT_GROUPING_TYPE_INDIAN_NUMBER_GROUPING DigitGroupingType = 3
|
||||
DIGIT_GROUPING_TYPE_INVALID DigitGroupingType = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the digit grouping type enum
|
||||
@@ -84,6 +119,8 @@ func (d DigitGroupingType) String() string {
|
||||
return "None"
|
||||
case DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR:
|
||||
return "Thousands Separator"
|
||||
case DIGIT_GROUPING_TYPE_INDIAN_NUMBER_GROUPING:
|
||||
return "Indian Number Grouping"
|
||||
case DIGIT_GROUPING_TYPE_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestCronJobSchedulerContainerRepeatRun(t *testing.T) {
|
||||
InMemoryDuplicateCheckerCleanupIntervalDuration: 60 * time.Second,
|
||||
})
|
||||
|
||||
duplicatechecker.Container.Current = checker
|
||||
duplicatechecker.SetDuplicateChecker(checker)
|
||||
|
||||
container := &CronJobSchedulerContainer{
|
||||
allJobsMap: make(map[string]*CronJob),
|
||||
|
||||
@@ -22,7 +22,7 @@ func (j *CronJob) doRun() {
|
||||
start := time.Now()
|
||||
c := core.NewCronJobContext(j.Name, j.Period.GetInterval())
|
||||
|
||||
if duplicatechecker.Container.Current != nil {
|
||||
if duplicatechecker.Container.IsEnabled() {
|
||||
localAddr, err := utils.GetLocalIPAddressesString()
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// DuplicateCheckerContainer contains the current duplicate checker
|
||||
type DuplicateCheckerContainer struct {
|
||||
Current DuplicateChecker
|
||||
current DuplicateChecker
|
||||
}
|
||||
|
||||
// Initialize a duplicate checker container singleton instance
|
||||
@@ -21,7 +21,7 @@ var (
|
||||
func InitializeDuplicateChecker(config *settings.Config) error {
|
||||
if config.DuplicateCheckerType == settings.InMemoryDuplicateCheckerType {
|
||||
checker, err := NewInMemoryDuplicateChecker(config)
|
||||
Container.Current = checker
|
||||
Container.current = checker
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -29,37 +29,75 @@ func InitializeDuplicateChecker(config *settings.Config) error {
|
||||
return errs.ErrInvalidDuplicateCheckerType
|
||||
}
|
||||
|
||||
// SetDuplicateChecker sets the current duplicate checker
|
||||
func SetDuplicateChecker(checker DuplicateChecker) {
|
||||
Container.current = checker
|
||||
}
|
||||
|
||||
// IsEnabled returns whether the duplicate checker is enabled
|
||||
func (c *DuplicateCheckerContainer) IsEnabled() bool {
|
||||
return c.current != nil
|
||||
}
|
||||
|
||||
// GetSubmissionRemark returns whether the same submission has been processed and related remark by the current duplicate checker
|
||||
func (c *DuplicateCheckerContainer) GetSubmissionRemark(checkerType DuplicateCheckerType, uid int64, identification string) (bool, string) {
|
||||
return c.Current.GetSubmissionRemark(checkerType, uid, identification)
|
||||
if c.current == nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return c.current.GetSubmissionRemark(checkerType, uid, identification)
|
||||
}
|
||||
|
||||
// SetSubmissionRemark saves the identification and remark by the current duplicate checker
|
||||
func (c *DuplicateCheckerContainer) SetSubmissionRemark(checkerType DuplicateCheckerType, uid int64, identification string, remark string) {
|
||||
c.Current.SetSubmissionRemark(checkerType, uid, identification, remark)
|
||||
if c.current == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.current.SetSubmissionRemark(checkerType, uid, identification, remark)
|
||||
}
|
||||
|
||||
// RemoveSubmissionRemark removes the identification and remark by the current duplicate checker
|
||||
func (c *DuplicateCheckerContainer) RemoveSubmissionRemark(checkerType DuplicateCheckerType, uid int64, identification string) {
|
||||
c.Current.RemoveSubmissionRemark(checkerType, uid, identification)
|
||||
if c.current == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.current.RemoveSubmissionRemark(checkerType, uid, identification)
|
||||
}
|
||||
|
||||
// GetOrSetCronJobRunningInfo returns the running info when the cron job is running or saves the running info by the current duplicate checker
|
||||
func (c *DuplicateCheckerContainer) GetOrSetCronJobRunningInfo(jobName string, runningInfo string, runningInterval time.Duration) (bool, string) {
|
||||
return c.Current.GetOrSetCronJobRunningInfo(jobName, runningInfo, runningInterval)
|
||||
if c.current == nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return c.current.GetOrSetCronJobRunningInfo(jobName, runningInfo, runningInterval)
|
||||
}
|
||||
|
||||
// RemoveCronJobRunningInfo removes the running info of the cron job by the current duplicate checker
|
||||
func (c *DuplicateCheckerContainer) RemoveCronJobRunningInfo(jobName string) {
|
||||
c.Current.RemoveCronJobRunningInfo(jobName)
|
||||
if c.current == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.current.RemoveCronJobRunningInfo(jobName)
|
||||
}
|
||||
|
||||
// GetFailureCount returns the failure count of the specified failure key
|
||||
func (c *DuplicateCheckerContainer) GetFailureCount(failureKey string) uint32 {
|
||||
return c.Current.GetFailureCount(failureKey)
|
||||
if c.current == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return c.current.GetFailureCount(failureKey)
|
||||
}
|
||||
|
||||
// IncreaseFailureCount increases the failure count of the specified failure key
|
||||
func (c *DuplicateCheckerContainer) IncreaseFailureCount(failureKey string) uint32 {
|
||||
return c.Current.IncreaseFailureCount(failureKey)
|
||||
if c.current == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return c.current.IncreaseFailureCount(failureKey)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package exchangerates
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
// ExchangeRatesDataSourceContainer contains the current exchange rates data source
|
||||
type ExchangeRatesDataSourceContainer struct {
|
||||
Current ExchangeRatesDataSource
|
||||
current ExchangeRatesDataSource
|
||||
}
|
||||
|
||||
// Initialize a exchange rates data source container singleton instance
|
||||
@@ -18,60 +20,69 @@ var (
|
||||
// InitializeExchangeRatesDataSource initializes the current exchange rates data source according to the config
|
||||
func InitializeExchangeRatesDataSource(config *settings.Config) error {
|
||||
if config.ExchangeRatesDataSource == settings.ReserveBankOfAustraliaDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&ReserveBankOfAustraliaDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&ReserveBankOfAustraliaDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.BankOfCanadaDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&BankOfCanadaDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&BankOfCanadaDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.CzechNationalBankDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&CzechNationalBankDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&CzechNationalBankDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.DanmarksNationalbankDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&DanmarksNationalbankDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&DanmarksNationalbankDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.EuroCentralBankDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&EuroCentralBankDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&EuroCentralBankDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.NationalBankOfGeorgiaDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&NationalBankOfGeorgiaDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&NationalBankOfGeorgiaDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.CentralBankOfHungaryDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&CentralBankOfHungaryDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&CentralBankOfHungaryDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.BankOfIsraelDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&BankOfIsraelDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&BankOfIsraelDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.CentralBankOfMyanmarDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&CentralBankOfMyanmarDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&CentralBankOfMyanmarDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.NorgesBankDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&NorgesBankDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&NorgesBankDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.NationalBankOfPolandDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&NationalBankOfPolandDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&NationalBankOfPolandDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.NationalBankOfRomaniaDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&NationalBankOfRomaniaDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&NationalBankOfRomaniaDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.BankOfRussiaDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&BankOfRussiaDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&BankOfRussiaDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.SwissNationalBankDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&SwissNationalBankDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&SwissNationalBankDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.NationalBankOfUkraineDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&NationalBankOfUkraineDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&NationalBankOfUkraineDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.CentralBankOfUzbekistanDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&CentralBankOfUzbekistanDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&CentralBankOfUzbekistanDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.InternationalMonetaryFundDataSource {
|
||||
Container.Current = newCommonHttpExchangeRatesDataSource(&InternationalMonetaryFundDataSource{})
|
||||
Container.current = newCommonHttpExchangeRatesDataSource(&InternationalMonetaryFundDataSource{})
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.UserCustomExchangeRatesDataSource {
|
||||
Container.Current = newUserCustomExchangeRatesDataSource()
|
||||
Container.current = newUserCustomExchangeRatesDataSource()
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs.ErrInvalidExchangeRatesDataSource
|
||||
}
|
||||
|
||||
// GetLatestExchangeRates returns the latest exchange rates data from the current exchange rates data source
|
||||
func (e *ExchangeRatesDataSourceContainer) GetLatestExchangeRates(c core.Context, uid int64, currentConfig *settings.Config) (*models.LatestExchangeRateResponse, error) {
|
||||
if Container.current == nil {
|
||||
return nil, errs.ErrInvalidExchangeRatesDataSource
|
||||
}
|
||||
|
||||
return e.current.GetLatestExchangeRates(c, uid, currentConfig)
|
||||
}
|
||||
|
||||
@@ -326,15 +326,12 @@ func executeLatestExchangeRateHandler(t *testing.T, dataSourceType string) *mode
|
||||
err := InitializeExchangeRatesDataSource(config)
|
||||
assert.Nil(t, err)
|
||||
|
||||
dataSource := Container.Current
|
||||
assert.NotNil(t, dataSource)
|
||||
|
||||
ginContext, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
context := &core.WebContext{
|
||||
Context: ginContext,
|
||||
}
|
||||
|
||||
exchangeRateResponse, err := dataSource.GetLatestExchangeRates(context, context.GetCurrentUid(), config)
|
||||
exchangeRateResponse, err := Container.GetLatestExchangeRates(context, context.GetCurrentUid(), config)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, exchangeRateResponse)
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ var AllLanguages = map[string]*LocaleInfo{
|
||||
"ja": {
|
||||
Content: ja,
|
||||
},
|
||||
"nl": {
|
||||
Content: nl,
|
||||
},
|
||||
"pt-BR": {
|
||||
Content: ptBR,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package locales
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
)
|
||||
|
||||
var nl = &LocaleTextItems{
|
||||
DefaultTypes: &DefaultTypes{
|
||||
DecimalSeparator: core.DECIMAL_SEPARATOR_COMMA,
|
||||
DigitGroupingSymbol: core.DIGIT_GROUPING_SYMBOL_DOT,
|
||||
},
|
||||
DataConverterTextItems: &DataConverterTextItems{
|
||||
Alipay: "Alipay",
|
||||
WeChatWallet: "Wallet",
|
||||
},
|
||||
VerifyEmailTextItems: &VerifyEmailTextItems{
|
||||
Title: "Verifieer e-mail",
|
||||
SalutationFormat: "Hallo %s,",
|
||||
DescriptionAboveBtn: "Klik op de onderstaande link om je e-mailadres te bevestigen.",
|
||||
VerifyEmail: "Verifieer e-mail",
|
||||
DescriptionBelowBtnFormat: "Als je geen %s account hebt aangemaakt, kun je deze e-mail negeren. Als je niet op de bovenstaande link kunt klikken, kopieer dan de URL hierboven en plak deze in je browser. De verificatielink verloopt na %v minuten.",
|
||||
},
|
||||
ForgetPasswordMailTextItems: &ForgetPasswordMailTextItems{
|
||||
Title: "Wachtwoord opnieuw instellen",
|
||||
SalutationFormat: "Hallo %s,",
|
||||
DescriptionAboveBtn: "We hebben onlangs een verzoek ontvangen om je wachtwoord opnieuw in te stellen. Klik op de onderstaande link om je wachtwoord te resetten.",
|
||||
ResetPassword: "Wachtwoord opnieuw instellen",
|
||||
DescriptionBelowBtnFormat: "Als je geen verzoek hebt gedaan om je wachtwoord te resetten, kun je deze e-mail negeren. Als je niet op de bovenstaande link kunt klikken, kopieer dan de URL hierboven en plak deze in je browser. De link voor het opnieuw instellen van het wachtwoord verloopt na %v minuten.",
|
||||
},
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
// MailerContainer contains the current mailer
|
||||
type MailerContainer struct {
|
||||
Current Mailer
|
||||
current Mailer
|
||||
}
|
||||
|
||||
// Initialize a mailer container singleton instance
|
||||
@@ -17,7 +18,7 @@ var (
|
||||
// InitializeMailer initializes the current mailer according to the config
|
||||
func InitializeMailer(config *settings.Config) error {
|
||||
if !config.EnableSMTP {
|
||||
Container.Current = nil
|
||||
Container.current = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,11 +28,15 @@ func InitializeMailer(config *settings.Config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
Container.Current = mailer
|
||||
Container.current = mailer
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMail sends an email according to argument
|
||||
func (u *MailerContainer) SendMail(message *MailMessage) error {
|
||||
return u.Current.SendMail(message)
|
||||
func (m *MailerContainer) SendMail(message *MailMessage) error {
|
||||
if m.current == nil {
|
||||
return errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
return m.current.SendMail(message)
|
||||
}
|
||||
|
||||
@@ -68,13 +68,7 @@ func (h *mcpQueryLatestExchangeRatesToolHandler) Handle(c *core.WebContext, call
|
||||
return nil, nil, errs.ErrIncompleteOrIncorrectSubmission
|
||||
}
|
||||
|
||||
dataSource := exchangerates.Container.Current
|
||||
|
||||
if dataSource == nil {
|
||||
return nil, nil, errs.ErrInvalidExchangeRatesDataSource
|
||||
}
|
||||
|
||||
exchangeRateResponse, err := dataSource.GetLatestExchangeRates(c, user.Uid, currentConfig)
|
||||
exchangeRateResponse, err := exchangerates.Container.GetLatestExchangeRates(c, user.Uid, currentConfig)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -5,15 +5,8 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
const tokenCookieParam = "ebk_auth_token"
|
||||
|
||||
// AmapApiProxyAuthCookie adds amap api proxy auth cookie to cookies in response
|
||||
func AmapApiProxyAuthCookie(c *core.WebContext, config *settings.Config) {
|
||||
token := c.GetTextualToken()
|
||||
|
||||
if token != "" {
|
||||
c.SetCookie(tokenCookieParam, token, int(config.TokenExpiredTime), "/_AMapService", "", false, true)
|
||||
} else {
|
||||
c.SetCookie(tokenCookieParam, "", -1, "/_AMapService", "", false, true)
|
||||
}
|
||||
c.SetTokenStringToCookie(token, int(config.TokenExpiredTime), "/_AMapService")
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ const (
|
||||
TOKEN_SOURCE_TYPE_COOKIE TokenSourceType = 3
|
||||
)
|
||||
|
||||
const tokenQueryStringParam = "token"
|
||||
|
||||
// JWTAuthorization verifies whether current request is valid by jwt token in header
|
||||
func JWTAuthorization(c *core.WebContext) {
|
||||
jwtAuthorization(c, TOKEN_SOURCE_TYPE_HEADER)
|
||||
@@ -159,11 +157,19 @@ func getTokenClaims(c *core.WebContext, source TokenSourceType) (*core.UserToken
|
||||
}
|
||||
|
||||
func parseToken(c *core.WebContext, source TokenSourceType) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
tokenString := ""
|
||||
|
||||
if source == TOKEN_SOURCE_TYPE_ARGUMENT {
|
||||
return services.Tokens.ParseTokenByArgument(c, tokenQueryStringParam)
|
||||
tokenString = c.GetTokenStringFromQueryString()
|
||||
} else if source == TOKEN_SOURCE_TYPE_COOKIE {
|
||||
return services.Tokens.ParseTokenByCookie(c, tokenCookieParam)
|
||||
tokenString = c.GetTokenStringFromCookie()
|
||||
} else { // if source == TOKEN_SOURCE_TYPE_HEADER
|
||||
tokenString = c.GetTokenStringFromHeader()
|
||||
}
|
||||
|
||||
return services.Tokens.ParseTokenByHeader(c)
|
||||
if tokenString == "" {
|
||||
return nil, nil, errs.ErrTokenIsEmpty
|
||||
}
|
||||
|
||||
return services.Tokens.ParseToken(c, tokenString)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,7 @@ const requestIdHeader = "X-Request-ID"
|
||||
// RequestId generates a new request id and add it to context and response header
|
||||
func RequestId(config *settings.Config) core.MiddlewareHandlerFunc {
|
||||
return func(c *core.WebContext) {
|
||||
if requestid.Container.Current == nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
requestId := requestid.Container.Current.GenerateRequestId(c.ClientIP(), c.ClientPort())
|
||||
requestId := requestid.Container.GenerateRequestId(c.ClientIP(), c.ClientPort())
|
||||
c.SetContextId(requestId)
|
||||
|
||||
if config.EnableRequestIdHeader {
|
||||
|
||||
@@ -45,6 +45,16 @@ var liabilityAccountCategory = map[AccountCategory]bool{
|
||||
ACCOUNT_CATEGORY_CERTIFICATE_OF_DEPOSIT: false,
|
||||
}
|
||||
|
||||
// IsAsset returns whether the account category is an asset category
|
||||
func (c AccountCategory) IsAsset() bool {
|
||||
return assetAccountCategory[c]
|
||||
}
|
||||
|
||||
// IsLiability returns whether the account category is a liability category
|
||||
func (c AccountCategory) IsLiability() bool {
|
||||
return liabilityAccountCategory[c]
|
||||
}
|
||||
|
||||
// AccountType represents account type
|
||||
type AccountType byte
|
||||
|
||||
|
||||
@@ -120,6 +120,13 @@ type Transaction struct {
|
||||
DeletedUnixTime int64
|
||||
}
|
||||
|
||||
// TransactionWithAccountBalance represents a transaction item with account balance
|
||||
type TransactionWithAccountBalance struct {
|
||||
*Transaction
|
||||
AccountOpeningBalance int64
|
||||
AccountClosingBalance int64
|
||||
}
|
||||
|
||||
// TransactionGeoLocationRequest represents all parameters of transaction geographic location info update request
|
||||
type TransactionGeoLocationRequest struct {
|
||||
Latitude float64 `json:"latitude" binding:"required"`
|
||||
@@ -222,6 +229,13 @@ type TransactionListInMonthByPageRequest struct {
|
||||
TrimTag bool `form:"trim_tag"`
|
||||
}
|
||||
|
||||
// TransactionReconciliationStatementRequest represents all parameters of transaction reconciliation statement request
|
||||
type TransactionReconciliationStatementRequest struct {
|
||||
AccountId int64 `form:"account_id,string" binding:"required,min=1"`
|
||||
StartTime int64 `form:"start_time"`
|
||||
EndTime int64 `form:"end_time"`
|
||||
}
|
||||
|
||||
// TransactionStatisticRequest represents all parameters of transaction statistic request
|
||||
type TransactionStatisticRequest struct {
|
||||
StartTime int64 `form:"start_time" binding:"min=0"`
|
||||
@@ -322,6 +336,22 @@ type TransactionInfoPageWrapperResponse2 struct {
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
}
|
||||
|
||||
// TransactionReconciliationStatementResponseItem represents a transaction reconciliation statement response
|
||||
type TransactionReconciliationStatementResponseItem struct {
|
||||
*TransactionInfoResponse
|
||||
AccountOpeningBalance int64 `json:"accountOpeningBalance"`
|
||||
AccountClosingBalance int64 `json:"accountClosingBalance"`
|
||||
}
|
||||
|
||||
// TransactionReconciliationStatementResponse represents the response of all transaction reconciliation statement response
|
||||
type TransactionReconciliationStatementResponse struct {
|
||||
Transactions []*TransactionReconciliationStatementResponseItem `json:"transactions"`
|
||||
TotalInflows int64 `json:"totalInflows"`
|
||||
TotalOutflows int64 `json:"totalOutflows"`
|
||||
OpeningBalance int64 `json:"openingBalance"`
|
||||
ClosingBalance int64 `json:"closingBalance"`
|
||||
}
|
||||
|
||||
// TransactionStatisticResponse represents transaction statistic response
|
||||
type TransactionStatisticResponse struct {
|
||||
StartTime int64 `json:"startTime"`
|
||||
|
||||
@@ -95,15 +95,18 @@ type User struct {
|
||||
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
|
||||
FirstDayOfWeek core.WeekDay `xorm:"TINYINT NOT NULL"`
|
||||
FiscalYearStart core.FiscalYearStart `xorm:"SMALLINT"`
|
||||
CalendarDisplayType core.CalendarDisplayType `xorm:"TINYINT"`
|
||||
DateDisplayType core.DateDisplayType `xorm:"TINYINT"`
|
||||
LongDateFormat core.LongDateFormat `xorm:"TINYINT"`
|
||||
ShortDateFormat core.ShortDateFormat `xorm:"TINYINT"`
|
||||
LongTimeFormat core.LongTimeFormat `xorm:"TINYINT"`
|
||||
ShortTimeFormat core.ShortTimeFormat `xorm:"TINYINT"`
|
||||
FiscalYearFormat core.FiscalYearFormat `xorm:"TINYINT"`
|
||||
CurrencyDisplayType core.CurrencyDisplayType `xorm:"TINYINT"`
|
||||
NumeralSystem core.NumeralSystem `xorm:"TINYINT"`
|
||||
DecimalSeparator core.DecimalSeparator `xorm:"TINYINT"`
|
||||
DigitGroupingSymbol core.DigitGroupingSymbol `xorm:"TINYINT"`
|
||||
DigitGrouping core.DigitGroupingType `xorm:"TINYINT"`
|
||||
CurrencyDisplayType core.CurrencyDisplayType `xorm:"TINYINT"`
|
||||
CoordinateDisplayType core.CoordinateDisplayType `xorm:"TINYINT"`
|
||||
ExpenseAmountColor AmountColorType `xorm:"TINYINT"`
|
||||
IncomeAmountColor AmountColorType `xorm:"TINYINT"`
|
||||
@@ -130,15 +133,18 @@ type UserBasicInfo struct {
|
||||
DefaultCurrency string `json:"defaultCurrency"`
|
||||
FirstDayOfWeek core.WeekDay `json:"firstDayOfWeek"`
|
||||
FiscalYearStart core.FiscalYearStart `json:"fiscalYearStart"`
|
||||
CalendarDisplayType core.CalendarDisplayType `json:"calendarDisplayType"`
|
||||
DateDisplayType core.DateDisplayType `json:"dateDisplayType"`
|
||||
LongDateFormat core.LongDateFormat `json:"longDateFormat"`
|
||||
ShortDateFormat core.ShortDateFormat `json:"shortDateFormat"`
|
||||
LongTimeFormat core.LongTimeFormat `json:"longTimeFormat"`
|
||||
ShortTimeFormat core.ShortTimeFormat `json:"shortTimeFormat"`
|
||||
FiscalYearFormat core.FiscalYearFormat `json:"fiscalYearFormat"`
|
||||
CurrencyDisplayType core.CurrencyDisplayType `json:"currencyDisplayType"`
|
||||
NumeralSystem core.NumeralSystem `json:"numeralSystem"`
|
||||
DecimalSeparator core.DecimalSeparator `json:"decimalSeparator"`
|
||||
DigitGroupingSymbol core.DigitGroupingSymbol `json:"digitGroupingSymbol"`
|
||||
DigitGrouping core.DigitGroupingType `json:"digitGrouping"`
|
||||
CurrencyDisplayType core.CurrencyDisplayType `json:"currencyDisplayType"`
|
||||
CoordinateDisplayType core.CoordinateDisplayType `json:"coordinateDisplayType"`
|
||||
ExpenseAmountColor AmountColorType `json:"expenseAmountColor"`
|
||||
IncomeAmountColor AmountColorType `json:"incomeAmountColor"`
|
||||
@@ -193,15 +199,18 @@ type UserProfileUpdateRequest struct {
|
||||
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
|
||||
FirstDayOfWeek *core.WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
|
||||
FiscalYearStart *core.FiscalYearStart `json:"fiscalYearStart" binding:"omitempty,validFiscalYearStart"`
|
||||
CalendarDisplayType *core.CalendarDisplayType `json:"calendarDisplayType" binding:"omitempty,min=0,max=2"`
|
||||
DateDisplayType *core.DateDisplayType `json:"dateDisplayType" binding:"omitempty,min=0,max=2"`
|
||||
LongDateFormat *core.LongDateFormat `json:"longDateFormat" binding:"omitempty,min=0,max=3"`
|
||||
ShortDateFormat *core.ShortDateFormat `json:"shortDateFormat" binding:"omitempty,min=0,max=3"`
|
||||
LongTimeFormat *core.LongTimeFormat `json:"longTimeFormat" binding:"omitempty,min=0,max=3"`
|
||||
ShortTimeFormat *core.ShortTimeFormat `json:"shortTimeFormat" binding:"omitempty,min=0,max=3"`
|
||||
FiscalYearFormat *core.FiscalYearFormat `json:"fiscalYearFormat" binding:"omitempty,min=0,max=5"`
|
||||
CurrencyDisplayType *core.CurrencyDisplayType `json:"currencyDisplayType" binding:"omitempty,min=0,max=11"`
|
||||
NumeralSystem *core.NumeralSystem `json:"numeralSystem" binding:"omitempty,min=0,max=5"`
|
||||
DecimalSeparator *core.DecimalSeparator `json:"decimalSeparator" binding:"omitempty,min=0,max=3"`
|
||||
DigitGroupingSymbol *core.DigitGroupingSymbol `json:"digitGroupingSymbol" binding:"omitempty,min=0,max=4"`
|
||||
DigitGrouping *core.DigitGroupingType `json:"digitGrouping" binding:"omitempty,min=0,max=2"`
|
||||
CurrencyDisplayType *core.CurrencyDisplayType `json:"currencyDisplayType" binding:"omitempty,min=0,max=11"`
|
||||
DigitGrouping *core.DigitGroupingType `json:"digitGrouping" binding:"omitempty,min=0,max=3"`
|
||||
CoordinateDisplayType *core.CoordinateDisplayType `json:"coordinateDisplayType" binding:"omitempty,min=0,max=6"`
|
||||
ExpenseAmountColor *AmountColorType `json:"expenseAmountColor" binding:"omitempty,min=0,max=4"`
|
||||
IncomeAmountColor *AmountColorType `json:"incomeAmountColor" binding:"omitempty,min=0,max=4"`
|
||||
@@ -281,15 +290,18 @@ func (u *User) ToUserBasicInfo(avatarProvider core.UserAvatarProviderType, avata
|
||||
DefaultCurrency: u.DefaultCurrency,
|
||||
FirstDayOfWeek: u.FirstDayOfWeek,
|
||||
FiscalYearStart: fiscalYearStart,
|
||||
CalendarDisplayType: u.CalendarDisplayType,
|
||||
DateDisplayType: u.DateDisplayType,
|
||||
LongDateFormat: u.LongDateFormat,
|
||||
ShortDateFormat: u.ShortDateFormat,
|
||||
LongTimeFormat: u.LongTimeFormat,
|
||||
ShortTimeFormat: u.ShortTimeFormat,
|
||||
DecimalSeparator: u.DecimalSeparator,
|
||||
FiscalYearFormat: u.FiscalYearFormat,
|
||||
CurrencyDisplayType: u.CurrencyDisplayType,
|
||||
NumeralSystem: u.NumeralSystem,
|
||||
DigitGroupingSymbol: u.DigitGroupingSymbol,
|
||||
DigitGrouping: u.DigitGrouping,
|
||||
CurrencyDisplayType: u.CurrencyDisplayType,
|
||||
CoordinateDisplayType: u.CoordinateDisplayType,
|
||||
ExpenseAmountColor: u.ExpenseAmountColor,
|
||||
IncomeAmountColor: u.IncomeAmountColor,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
// RequestIdContainer contains the current request id generator
|
||||
type RequestIdContainer struct {
|
||||
Current RequestIdGenerator
|
||||
current RequestIdGenerator
|
||||
}
|
||||
|
||||
// Initialize a request id container singleton instance
|
||||
@@ -23,11 +23,33 @@ func InitializeRequestIdGenerator(c core.Context, config *settings.Config) error
|
||||
return err
|
||||
}
|
||||
|
||||
Container.Current = generator
|
||||
Container.current = generator
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateRequestId returns a new request id by the current request id generator
|
||||
func (u *RequestIdContainer) GenerateRequestId(clientIpAddr string, clientPort uint16) string {
|
||||
return u.Current.GenerateRequestId(clientIpAddr, clientPort)
|
||||
func (r *RequestIdContainer) GenerateRequestId(clientIpAddr string, clientPort uint16) string {
|
||||
if r.current == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return r.current.GenerateRequestId(clientIpAddr, clientPort)
|
||||
}
|
||||
|
||||
// GetCurrentServerUniqId returns current server unique id
|
||||
func (r *RequestIdContainer) GetCurrentServerUniqId() uint16 {
|
||||
if r.current == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return r.current.GetCurrentServerUniqId()
|
||||
}
|
||||
|
||||
// GetCurrentInstanceUniqId returns current application instance unique id
|
||||
func (r *RequestIdContainer) GetCurrentInstanceUniqId() uint16 {
|
||||
if r.current == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return r.current.GetCurrentInstanceUniqId()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -56,6 +57,28 @@ func (s *AccountService) GetAllAccountsByUid(c core.Context, uid int64) ([]*mode
|
||||
return accounts, err
|
||||
}
|
||||
|
||||
// GetAccountByAccountId returns account model according to account id
|
||||
func (s *AccountService) GetAccountByAccountId(c core.Context, uid int64, accountId int64) (*models.Account, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
if accountId <= 0 {
|
||||
return nil, errs.ErrAccountIdInvalid
|
||||
}
|
||||
|
||||
account := &models.Account{}
|
||||
has, err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=? AND account_id=?", uid, false, accountId).Get(account)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, errs.ErrAccountNotFound
|
||||
}
|
||||
|
||||
return account, err
|
||||
}
|
||||
|
||||
// GetAccountAndSubAccountsByAccountId returns account model and sub-account models according to account id
|
||||
func (s *AccountService) GetAccountAndSubAccountsByAccountId(c core.Context, uid int64, accountId int64) ([]*models.Account, error) {
|
||||
if uid <= 0 {
|
||||
@@ -640,9 +663,15 @@ func (s *AccountService) DeleteAccount(c core.Context, uid int64, accountId int6
|
||||
return errs.ErrAccountNotFound
|
||||
}
|
||||
|
||||
var accountAndSubAccountIdsConditions strings.Builder
|
||||
accountAndSubAccountIds := make([]int64, len(accountAndSubAccounts))
|
||||
|
||||
for i := 0; i < len(accountAndSubAccounts); i++ {
|
||||
if accountAndSubAccountIdsConditions.Len() > 0 {
|
||||
accountAndSubAccountIdsConditions.WriteString(",")
|
||||
}
|
||||
|
||||
accountAndSubAccountIdsConditions.WriteString("?")
|
||||
accountAndSubAccountIds[i] = accountAndSubAccounts[i].AccountId
|
||||
}
|
||||
|
||||
@@ -669,6 +698,31 @@ func (s *AccountService) DeleteAccount(c core.Context, uid int64, accountId int6
|
||||
}
|
||||
}
|
||||
|
||||
transactionTemplateQueryCondition := fmt.Sprintf("uid=? AND deleted=? AND (template_type=? OR (template_type=? AND scheduled_frequency_type<>? AND (scheduled_end_time IS NULL OR scheduled_end_time>=?))) AND (account_id IN (%s) OR related_account_id IN (%s))", accountAndSubAccountIdsConditions.String(), accountAndSubAccountIdsConditions.String())
|
||||
transactionTemplateQueryConditionParams := make([]any, 0, len(accountAndSubAccountIds)*2+6)
|
||||
transactionTemplateQueryConditionParams = append(transactionTemplateQueryConditionParams, uid)
|
||||
transactionTemplateQueryConditionParams = append(transactionTemplateQueryConditionParams, false)
|
||||
transactionTemplateQueryConditionParams = append(transactionTemplateQueryConditionParams, models.TRANSACTION_TEMPLATE_TYPE_NORMAL)
|
||||
transactionTemplateQueryConditionParams = append(transactionTemplateQueryConditionParams, models.TRANSACTION_TEMPLATE_TYPE_SCHEDULE)
|
||||
transactionTemplateQueryConditionParams = append(transactionTemplateQueryConditionParams, models.TRANSACTION_SCHEDULE_FREQUENCY_TYPE_DISABLED)
|
||||
transactionTemplateQueryConditionParams = append(transactionTemplateQueryConditionParams, now)
|
||||
|
||||
for i := 0; i < len(accountAndSubAccountIds); i++ {
|
||||
transactionTemplateQueryConditionParams = append(transactionTemplateQueryConditionParams, accountAndSubAccountIds[i])
|
||||
}
|
||||
|
||||
for i := 0; i < len(accountAndSubAccountIds); i++ {
|
||||
transactionTemplateQueryConditionParams = append(transactionTemplateQueryConditionParams, accountAndSubAccountIds[i])
|
||||
}
|
||||
|
||||
exists, err := sess.Cols("uid", "deleted", "account_id", "related_account_id", "template_type", "scheduled_frequency_type", "scheduled_end_time").Where(transactionTemplateQueryCondition, transactionTemplateQueryConditionParams...).Limit(1).Exist(&models.TransactionTemplate{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if exists {
|
||||
return errs.ErrAccountInUseCannotBeDeleted
|
||||
}
|
||||
|
||||
deletedRows, err := sess.Cols("balance", "deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).In("account_id", accountAndSubAccountIds).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
@@ -750,6 +804,14 @@ func (s *AccountService) DeleteSubAccount(c core.Context, uid int64, accountId i
|
||||
}
|
||||
}
|
||||
|
||||
exists, err := sess.Cols("uid", "deleted", "account_id", "related_account_id", "template_type", "scheduled_frequency_type", "scheduled_end_time").Where("uid=? AND deleted=? AND (template_type=? OR (template_type=? AND scheduled_frequency_type<>? AND (scheduled_end_time IS NULL OR scheduled_end_time>=?))) AND (account_id=? OR related_account_id=?)", uid, false, models.TRANSACTION_TEMPLATE_TYPE_NORMAL, models.TRANSACTION_TEMPLATE_TYPE_SCHEDULE, models.TRANSACTION_SCHEDULE_FREQUENCY_TYPE_DISABLED, now, accountId, accountId).Limit(1).Exist(&models.TransactionTemplate{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if exists {
|
||||
return errs.ErrSubAccountInUseCannotBeDeleted
|
||||
}
|
||||
|
||||
deletedRows, err := sess.Cols("balance", "deleted", "deleted_unix_time").Where("uid=? AND deleted=? AND account_id=?", uid, false, accountId).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
@@ -827,7 +889,7 @@ func (s *AccountService) GetAccountNames(accounts []*models.Account) []string {
|
||||
}
|
||||
|
||||
// GetAccountOrSubAccountIds returns a list of account ids or sub-account ids according to given account ids
|
||||
func (s *AccountService) GetAccountOrSubAccountIds(c *core.WebContext, accountIds string, uid int64) ([]int64, error) {
|
||||
func (s *AccountService) GetAccountOrSubAccountIds(c core.Context, accountIds string, uid int64) ([]int64, error) {
|
||||
if accountIds == "" || accountIds == "0" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/datastore"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/mail"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/storage"
|
||||
@@ -60,7 +60,7 @@ type ServiceUsingConfig struct {
|
||||
|
||||
// CurrentConfig returns the current config
|
||||
func (s *ServiceUsingConfig) CurrentConfig() *settings.Config {
|
||||
return s.container.Current
|
||||
return s.container.GetCurrentConfig()
|
||||
}
|
||||
|
||||
// ServiceUsingMailer represents a service that need to use mailer
|
||||
@@ -70,11 +70,7 @@ type ServiceUsingMailer struct {
|
||||
|
||||
// SendMail sends an email according to argument
|
||||
func (s *ServiceUsingMailer) SendMail(message *mail.MailMessage) error {
|
||||
if s.container.Current == nil {
|
||||
return errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
return s.container.Current.SendMail(message)
|
||||
return s.container.SendMail(message)
|
||||
}
|
||||
|
||||
// ServiceUsingUuid represents a service that need to use uuid
|
||||
@@ -98,43 +94,43 @@ type ServiceUsingStorage struct {
|
||||
}
|
||||
|
||||
// ExistsAvatar returns whether the user avatar exists from the current avatar object storage
|
||||
func (s *ServiceUsingStorage) ExistsAvatar(uid int64, fileExtension string) (bool, error) {
|
||||
return s.container.ExistsAvatar(s.getUserAvatarPath(uid, fileExtension))
|
||||
func (s *ServiceUsingStorage) ExistsAvatar(ctx core.Context, uid int64, fileExtension string) (bool, error) {
|
||||
return s.container.ExistsAvatar(ctx, s.getUserAvatarPath(uid, fileExtension))
|
||||
}
|
||||
|
||||
// ReadAvatar returns the user avatar from the current avatar object storage
|
||||
func (s *ServiceUsingStorage) ReadAvatar(uid int64, fileExtension string) (storage.ObjectInStorage, error) {
|
||||
return s.container.ReadAvatar(s.getUserAvatarPath(uid, fileExtension))
|
||||
func (s *ServiceUsingStorage) ReadAvatar(ctx core.Context, uid int64, fileExtension string) (storage.ObjectInStorage, error) {
|
||||
return s.container.ReadAvatar(ctx, s.getUserAvatarPath(uid, fileExtension))
|
||||
}
|
||||
|
||||
// SaveAvatar returns whether save the user avatar into the current avatar object storage successfully
|
||||
func (s *ServiceUsingStorage) SaveAvatar(uid int64, object storage.ObjectInStorage, fileExtension string) error {
|
||||
return s.container.SaveAvatar(s.getUserAvatarPath(uid, fileExtension), object)
|
||||
func (s *ServiceUsingStorage) SaveAvatar(ctx core.Context, uid int64, object storage.ObjectInStorage, fileExtension string) error {
|
||||
return s.container.SaveAvatar(ctx, s.getUserAvatarPath(uid, fileExtension), object)
|
||||
}
|
||||
|
||||
// DeleteAvatar returns whether delete the user avatar from the current avatar object storage successfully
|
||||
func (s *ServiceUsingStorage) DeleteAvatar(uid int64, fileExtension string) error {
|
||||
return s.container.DeleteAvatar(s.getUserAvatarPath(uid, fileExtension))
|
||||
func (s *ServiceUsingStorage) DeleteAvatar(ctx core.Context, uid int64, fileExtension string) error {
|
||||
return s.container.DeleteAvatar(ctx, s.getUserAvatarPath(uid, fileExtension))
|
||||
}
|
||||
|
||||
// ExistsTransactionPicture returns whether the transaction picture exists from the current transaction picture object storage
|
||||
func (s *ServiceUsingStorage) ExistsTransactionPicture(uid int64, pictureId int64, fileExtension string) (bool, error) {
|
||||
return s.container.ExistsTransactionPicture(s.getTransactionPicturePath(uid, pictureId, fileExtension))
|
||||
func (s *ServiceUsingStorage) ExistsTransactionPicture(ctx core.Context, uid int64, pictureId int64, fileExtension string) (bool, error) {
|
||||
return s.container.ExistsTransactionPicture(ctx, s.getTransactionPicturePath(uid, pictureId, fileExtension))
|
||||
}
|
||||
|
||||
// ReadTransactionPicture returns the transaction picture from the current transaction picture object storage
|
||||
func (s *ServiceUsingStorage) ReadTransactionPicture(uid int64, pictureId int64, fileExtension string) (storage.ObjectInStorage, error) {
|
||||
return s.container.ReadTransactionPicture(s.getTransactionPicturePath(uid, pictureId, fileExtension))
|
||||
func (s *ServiceUsingStorage) ReadTransactionPicture(ctx core.Context, uid int64, pictureId int64, fileExtension string) (storage.ObjectInStorage, error) {
|
||||
return s.container.ReadTransactionPicture(ctx, s.getTransactionPicturePath(uid, pictureId, fileExtension))
|
||||
}
|
||||
|
||||
// SaveTransactionPicture returns whether save the transaction picture into the current transaction picture object storage successfully
|
||||
func (s *ServiceUsingStorage) SaveTransactionPicture(uid int64, pictureId int64, object storage.ObjectInStorage, fileExtension string) error {
|
||||
return s.container.SaveTransactionPicture(s.getTransactionPicturePath(uid, pictureId, fileExtension), object)
|
||||
func (s *ServiceUsingStorage) SaveTransactionPicture(ctx core.Context, uid int64, pictureId int64, object storage.ObjectInStorage, fileExtension string) error {
|
||||
return s.container.SaveTransactionPicture(ctx, s.getTransactionPicturePath(uid, pictureId, fileExtension), object)
|
||||
}
|
||||
|
||||
// DeleteTransactionPicture returns whether delete the transaction picture from the current transaction picture object storage successfully
|
||||
func (s *ServiceUsingStorage) DeleteTransactionPicture(uid int64, pictureId int64, fileExtension string) error {
|
||||
return s.container.DeleteTransactionPicture(s.getTransactionPicturePath(uid, pictureId, fileExtension))
|
||||
func (s *ServiceUsingStorage) DeleteTransactionPicture(ctx core.Context, uid int64, pictureId int64, fileExtension string) error {
|
||||
return s.container.DeleteTransactionPicture(ctx, s.getTransactionPicturePath(uid, pictureId, fileExtension))
|
||||
}
|
||||
|
||||
func (s *ServiceUsingStorage) getUserAvatarPath(uid int64, fileExtension string) string {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
@@ -71,19 +72,9 @@ func (s *TokenService) GetAllUnexpiredNormalAndMCPTokensByUid(c core.Context, ui
|
||||
return tokenRecords, err
|
||||
}
|
||||
|
||||
// ParseTokenByHeader returns the token model according to request data
|
||||
func (s *TokenService) ParseTokenByHeader(c *core.WebContext) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
return s.parseToken(c, request.BearerExtractor{})
|
||||
}
|
||||
|
||||
// ParseTokenByArgument returns the token model according to request data
|
||||
func (s *TokenService) ParseTokenByArgument(c *core.WebContext, tokenParameterName string) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
return s.parseToken(c, request.ArgumentExtractor{tokenParameterName})
|
||||
}
|
||||
|
||||
// ParseTokenByCookie returns the token model according to request data
|
||||
func (s *TokenService) ParseTokenByCookie(c *core.WebContext, tokenCookieName string) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
return s.parseToken(c, utils.CookieExtractor{tokenCookieName})
|
||||
// ParseToken returns the token model according to token content
|
||||
func (s *TokenService) ParseToken(c core.Context, token string) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
return s.parseToken(c, token)
|
||||
}
|
||||
|
||||
// CreateTokenViaCli generates a new normal token and saves to database
|
||||
@@ -328,53 +319,52 @@ func (s *TokenService) GenerateTokenId(tokenRecord *models.TokenRecord) string {
|
||||
return fmt.Sprintf("%d:%d:%d", tokenRecord.Uid, tokenRecord.CreatedUnixTime, tokenRecord.UserTokenId)
|
||||
}
|
||||
|
||||
func (s *TokenService) parseToken(c *core.WebContext, extractor request.Extractor) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
func (s *TokenService) parseToken(c core.Context, tokenString string) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
claims := &core.UserTokenClaims{}
|
||||
|
||||
token, err := request.ParseFromRequest(c.Request, extractor,
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims,
|
||||
func(token *jwt.Token) (any, error) {
|
||||
now := time.Now().Unix()
|
||||
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[tokens.ParseToken] token \"utid:%s\" in token of user \"uid:%d\" is invalid, because %s", claims.UserTokenId, claims.Uid, err.Error())
|
||||
log.Warnf(c, "[tokens.parseToken] token \"utid:%s\" in token of user \"uid:%d\" is invalid, because %s", claims.UserTokenId, claims.Uid, err.Error())
|
||||
return nil, errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
tokenRecord, err := s.getTokenRecord(c, claims.Uid, userTokenId, claims.IssuedAt)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%d\" record not found, because %s", claims.UserTokenId, claims.Uid, err.Error())
|
||||
log.Warnf(c, "[tokens.parseToken] token \"utid:%s\" of user \"uid:%d\" record not found, because %s", claims.UserTokenId, claims.Uid, err.Error())
|
||||
return nil, errs.ErrTokenRecordNotFound
|
||||
}
|
||||
|
||||
if tokenRecord.ExpiredUnixTime < now {
|
||||
log.Warnf(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%d\" record is expired", claims.UserTokenId, claims.Uid)
|
||||
log.Warnf(c, "[tokens.parseToken] token \"utid:%s\" of user \"uid:%d\" record is expired", claims.UserTokenId, claims.Uid)
|
||||
return nil, errs.ErrTokenExpired
|
||||
}
|
||||
|
||||
return []byte(tokenRecord.Secret), nil
|
||||
},
|
||||
request.WithClaims(claims),
|
||||
request.WithParser(jwt.NewParser(jwt.WithIssuedAt())),
|
||||
jwt.WithIssuedAt(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if err == request.ErrNoTokenInRequest {
|
||||
if errors.Is(err, request.ErrNoTokenInRequest) {
|
||||
return nil, nil, errs.ErrTokenIsEmpty
|
||||
}
|
||||
|
||||
if err == jwt.ErrTokenMalformed || err == jwt.ErrTokenUnverifiable || err == jwt.ErrTokenSignatureInvalid {
|
||||
log.Warnf(c, "[tokens.ParseToken] token is invalid, because %s", err.Error())
|
||||
if errors.Is(err, jwt.ErrTokenMalformed) || errors.Is(err, jwt.ErrTokenUnverifiable) || errors.Is(err, jwt.ErrTokenSignatureInvalid) {
|
||||
log.Warnf(c, "[tokens.parseToken] token is invalid, because %s", err.Error())
|
||||
return nil, nil, errs.ErrCurrentInvalidToken
|
||||
}
|
||||
|
||||
if err == jwt.ErrTokenExpired {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return nil, nil, errs.ErrCurrentTokenExpired
|
||||
}
|
||||
|
||||
if err == jwt.ErrTokenUsedBeforeIssued {
|
||||
log.Warnf(c, "[tokens.ParseToken] token is invalid, because issue time is later than now")
|
||||
if errors.Is(err, jwt.ErrTokenUsedBeforeIssued) {
|
||||
log.Warnf(c, "[tokens.parseToken] token is invalid, because issue time is later than now")
|
||||
return nil, nil, errs.ErrCurrentInvalidToken
|
||||
}
|
||||
|
||||
|
||||
@@ -397,6 +397,14 @@ func (s *TransactionCategoryService) DeleteCategory(c core.Context, uid int64, c
|
||||
return errs.ErrTransactionCategoryInUseCannotBeDeleted
|
||||
}
|
||||
|
||||
exists, err = sess.Cols("uid", "deleted", "category_id", "template_type", "scheduled_frequency_type", "scheduled_end_time").Where("uid=? AND deleted=? AND (template_type=? OR (template_type=? AND scheduled_frequency_type<>? AND (scheduled_end_time IS NULL OR scheduled_end_time>=?)))", uid, false, models.TRANSACTION_TEMPLATE_TYPE_NORMAL, models.TRANSACTION_TEMPLATE_TYPE_SCHEDULE, models.TRANSACTION_SCHEDULE_FREQUENCY_TYPE_DISABLED, now).In("category_id", categoryAndSubCategoryIds).Limit(1).Exist(&models.TransactionTemplate{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if exists {
|
||||
return errs.ErrTransactionCategoryInUseCannotBeDeleted
|
||||
}
|
||||
|
||||
deletedRows, err := sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).In("category_id", categoryAndSubCategoryIds).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
@@ -543,7 +551,7 @@ func (s *TransactionCategoryService) GetCategoryNames(categories []*models.Trans
|
||||
}
|
||||
|
||||
// GetCategoryOrSubCategoryIds returns all category ids and sub-category ids according to given category ids
|
||||
func (s *TransactionCategoryService) GetCategoryOrSubCategoryIds(c *core.WebContext, categoryIds string, uid int64) ([]int64, error) {
|
||||
func (s *TransactionCategoryService) GetCategoryOrSubCategoryIds(c core.Context, categoryIds string, uid int64) ([]int64, error) {
|
||||
if categoryIds == "" || categoryIds == "0" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ func (s *TransactionPictureService) GetPictureByPictureId(c core.Context, uid in
|
||||
return nil, errs.ErrTransactionPictureExtensionInvalid
|
||||
}
|
||||
|
||||
pictureFile, err := s.ReadTransactionPicture(pictureInfo.Uid, pictureInfo.PictureId, pictureInfo.PictureExtension)
|
||||
pictureFile, err := s.ReadTransactionPicture(c, pictureInfo.Uid, pictureInfo.PictureId, pictureInfo.PictureExtension)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errs.ErrTransactionPictureNoExists
|
||||
@@ -199,7 +199,7 @@ func (s *TransactionPictureService) UploadPicture(c core.Context, pictureInfo *m
|
||||
pictureInfo.CreatedUnixTime = time.Now().Unix()
|
||||
pictureInfo.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
err := s.SaveTransactionPicture(pictureInfo.Uid, pictureInfo.PictureId, pictureFile, pictureInfo.PictureExtension)
|
||||
err := s.SaveTransactionPicture(c, pictureInfo.Uid, pictureInfo.PictureId, pictureFile, pictureInfo.PictureExtension)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -396,6 +396,28 @@ func (s *TransactionTagService) DeleteTag(c core.Context, uid int64, tagId int64
|
||||
return errs.ErrTransactionTagInUseCannotBeDeleted
|
||||
}
|
||||
|
||||
var relatedTransactionTemplatesByTag []*models.TransactionTemplate
|
||||
err = sess.Cols("uid", "deleted", "tag_ids", "template_type", "scheduled_frequency_type", "scheduled_end_time").Where("uid=? AND deleted=? AND (template_type=? OR (template_type=? AND scheduled_frequency_type<>? AND (scheduled_end_time IS NULL OR scheduled_end_time>=?))) AND tag_ids LIKE ?", uid, false, models.TRANSACTION_TEMPLATE_TYPE_NORMAL, models.TRANSACTION_TEMPLATE_TYPE_SCHEDULE, models.TRANSACTION_SCHEDULE_FREQUENCY_TYPE_DISABLED, now, "%%"+utils.Int64ToString(tagId)+"%%").Find(&relatedTransactionTemplatesByTag)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < len(relatedTransactionTemplatesByTag); i++ {
|
||||
template := relatedTransactionTemplatesByTag[i]
|
||||
tagIds, err := s.GetTagIds(template.TagIds)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for j := 0; j < len(tagIds); j++ {
|
||||
if tagIds[j] == tagId {
|
||||
return errs.ErrTransactionTagInUseCannotBeDeleted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletedRows, err := sess.ID(tagId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
@@ -122,7 +123,13 @@ func (s *TransactionTemplateService) CreateTemplate(c core.Context, template *mo
|
||||
template.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDataDB(template.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Insert(template)
|
||||
err := s.isTemplateValid(sess, template)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Insert(template)
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -136,6 +143,12 @@ func (s *TransactionTemplateService) ModifyTemplate(c core.Context, template *mo
|
||||
template.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDataDB(template.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
err := s.isTemplateValid(sess, template)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedRows, err := sess.ID(template.TemplateId).Cols("name", "type", "category_id", "account_id", "scheduled_frequency_type", "scheduled_frequency", "scheduled_start_time", "scheduled_end_time", "scheduled_at", "scheduled_timezone_utc_offset", "tag_ids", "amount", "related_account_id", "related_account_amount", "hide_amount", "comment", "updated_unix_time").Where("uid=? AND deleted=?", template.Uid, false).Update(template)
|
||||
|
||||
if err != nil {
|
||||
@@ -249,3 +262,85 @@ func (s *TransactionTemplateService) DeleteAllTemplates(c core.Context, uid int6
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *TransactionTemplateService) isTemplateValid(sess *xorm.Session, template *models.TransactionTemplate) error {
|
||||
// check accounts are valid
|
||||
sourceAccount := &models.Account{}
|
||||
destinationAccount := &models.Account{}
|
||||
has, err := sess.ID(template.AccountId).Where("uid=? AND deleted=?", template.Uid, false).Get(sourceAccount)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return errs.ErrSourceAccountNotFound
|
||||
}
|
||||
|
||||
if sourceAccount.Hidden {
|
||||
return errs.ErrCannotUseHiddenAccount
|
||||
}
|
||||
|
||||
if template.Type == models.TRANSACTION_TYPE_TRANSFER {
|
||||
if template.RelatedAccountId <= 0 {
|
||||
return errs.ErrAccountIdInvalid
|
||||
} else {
|
||||
has, err = sess.ID(template.RelatedAccountId).Where("uid=? AND deleted=?", template.Uid, false).Get(destinationAccount)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return errs.ErrDestinationAccountNotFound
|
||||
}
|
||||
|
||||
if destinationAccount.Hidden {
|
||||
return errs.ErrCannotUseHiddenAccount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sourceAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS || (destinationAccount != nil && destinationAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS) {
|
||||
return errs.ErrCannotAddTransactionToParentAccount
|
||||
}
|
||||
|
||||
// check category is valid
|
||||
category := &models.TransactionCategory{}
|
||||
has, err = sess.ID(template.CategoryId).Where("uid=? AND deleted=?", template.Uid, false).Get(category)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return errs.ErrTransactionCategoryNotFound
|
||||
}
|
||||
|
||||
if category.Hidden {
|
||||
return errs.ErrCannotUseHiddenTransactionCategory
|
||||
}
|
||||
|
||||
if category.ParentCategoryId == models.LevelOneTransactionCategoryParentId {
|
||||
return errs.ErrCannotUsePrimaryCategoryForTransaction
|
||||
}
|
||||
|
||||
if (template.Type == models.TRANSACTION_TYPE_INCOME && category.Type != models.CATEGORY_TYPE_INCOME) ||
|
||||
(template.Type == models.TRANSACTION_TYPE_EXPENSE && category.Type != models.CATEGORY_TYPE_EXPENSE) ||
|
||||
(template.Type == models.TRANSACTION_TYPE_TRANSFER && category.Type != models.CATEGORY_TYPE_TRANSFER) {
|
||||
return errs.ErrTransactionCategoryTypeInvalid
|
||||
}
|
||||
|
||||
// check tags are valid
|
||||
tagIds := template.GetTagIds()
|
||||
var tags []*models.TransactionTag
|
||||
err = sess.Where("uid=? AND deleted=?", template.Uid, false).In("tag_id", tagIds).Find(&tags)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(tags) < len(tagIds) {
|
||||
return errs.ErrTransactionTagNotFound
|
||||
}
|
||||
|
||||
for i := 0; i < len(tags); i++ {
|
||||
if tags[i].Hidden {
|
||||
return errs.ErrCannotUseHiddenTransactionTag
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -107,6 +107,96 @@ func (s *TransactionService) GetAllSpecifiedTransactions(c core.Context, uid int
|
||||
return allTransactions, nil
|
||||
}
|
||||
|
||||
// GetAllTransactionsWithAccountBalanceByMaxTime returns account statement within time range
|
||||
func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c core.Context, uid int64, pageCount int32, maxTransactionTime int64, minTransactionTime int64, accountId int64, accountCategory models.AccountCategory) ([]*models.TransactionWithAccountBalance, int64, int64, int64, int64, error) {
|
||||
if maxTransactionTime <= 0 {
|
||||
maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix())
|
||||
}
|
||||
|
||||
var allTransactions []*models.Transaction
|
||||
|
||||
for maxTransactionTime > 0 {
|
||||
transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, []int64{accountId}, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", "", 1, pageCount, false, true)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, 0, 0, 0, err
|
||||
}
|
||||
|
||||
allTransactions = append(allTransactions, transactions...)
|
||||
|
||||
if len(transactions) < int(pageCount) {
|
||||
maxTransactionTime = 0
|
||||
break
|
||||
}
|
||||
|
||||
maxTransactionTime = transactions[len(transactions)-1].TransactionTime - 1
|
||||
}
|
||||
|
||||
allTransactionsAndAccountBalance := make([]*models.TransactionWithAccountBalance, 0, len(allTransactions))
|
||||
|
||||
if len(allTransactions) < 1 {
|
||||
return allTransactionsAndAccountBalance, 0, 0, 0, 0, nil
|
||||
}
|
||||
|
||||
totalInflows := int64(0)
|
||||
totalOutflows := int64(0)
|
||||
openingBalance := int64(0)
|
||||
accumulatedBalance := int64(0)
|
||||
lastAccumulatedBalance := int64(0)
|
||||
|
||||
for i := len(allTransactions) - 1; i >= 0; i-- {
|
||||
transaction := allTransactions[i]
|
||||
|
||||
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||
accumulatedBalance = accumulatedBalance + transaction.RelatedAccountAmount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME {
|
||||
accumulatedBalance = accumulatedBalance + transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||
accumulatedBalance = accumulatedBalance - transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||
accumulatedBalance = accumulatedBalance - transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||
accumulatedBalance = accumulatedBalance + transaction.Amount
|
||||
} else {
|
||||
log.Errorf(c, "[transactions.GetAllTransactionsWithAccountBalanceByMaxTime] trasaction type (%d) is invalid (id:%d)", transaction.TransactionId, transaction.Type)
|
||||
return nil, 0, 0, 0, 0, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
if transaction.TransactionTime < minTransactionTime {
|
||||
openingBalance = accumulatedBalance
|
||||
lastAccumulatedBalance = accumulatedBalance
|
||||
continue
|
||||
}
|
||||
|
||||
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||
if accountCategory.IsAsset() {
|
||||
totalInflows = totalInflows + transaction.RelatedAccountAmount
|
||||
} else if accountCategory.IsLiability() {
|
||||
totalOutflows = totalOutflows - transaction.RelatedAccountAmount
|
||||
}
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME {
|
||||
totalInflows = totalInflows + transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||
totalOutflows = totalOutflows + transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||
totalOutflows = totalOutflows + transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||
totalInflows = totalInflows + transaction.Amount
|
||||
}
|
||||
|
||||
transactionsAndAccountBalance := &models.TransactionWithAccountBalance{
|
||||
Transaction: transaction,
|
||||
AccountOpeningBalance: lastAccumulatedBalance,
|
||||
AccountClosingBalance: accumulatedBalance,
|
||||
}
|
||||
|
||||
lastAccumulatedBalance = accumulatedBalance
|
||||
allTransactionsAndAccountBalance = append(allTransactionsAndAccountBalance, transactionsAndAccountBalance)
|
||||
}
|
||||
|
||||
return allTransactionsAndAccountBalance, totalInflows, totalOutflows, openingBalance, accumulatedBalance, nil
|
||||
}
|
||||
|
||||
// GetTransactionsByMaxTime returns transactions before given time
|
||||
func (s *TransactionService) GetTransactionsByMaxTime(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType, amountFilter string, keyword string, page int32, count int32, needOneMoreItem bool, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
if uid <= 0 {
|
||||
@@ -580,7 +670,7 @@ func (s *TransactionService) CreateScheduledTransactions(c core.Context, current
|
||||
log.Infof(c, "[transactions.CreateScheduledTransactions] transaction template \"id:%d\" has created a new trasaction \"id:%d\"", template.TemplateId, transaction.TransactionId)
|
||||
} else {
|
||||
failedCount++
|
||||
log.Errorf(c, "[transactions.CreateScheduledTransactions] transaction template \"id:%d\" failed to create new trasaction", template.TemplateId)
|
||||
log.Errorf(c, "[transactions.CreateScheduledTransactions] transaction template \"id:%d\" failed to create new trasaction, because %s", template.TemplateId, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -912,7 +1002,7 @@ func (s *TransactionService) ModifyTransaction(c core.Context, transaction *mode
|
||||
return errs.ErrBalanceModificationTransactionCannotChangeAccountId
|
||||
}
|
||||
|
||||
if transaction.RelatedAccountAmount != oldTransaction.RelatedAccountAmount {
|
||||
if transaction.Amount != oldTransaction.Amount && transaction.RelatedAccountAmount != oldTransaction.RelatedAccountAmount {
|
||||
sourceAccount.UpdatedUnixTime = time.Now().Unix()
|
||||
updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)+(%d)", oldTransaction.RelatedAccountAmount, transaction.RelatedAccountAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
|
||||
|
||||
@@ -1230,7 +1320,7 @@ func (s *TransactionService) DeleteTransaction(c core.Context, uid int64, transa
|
||||
}
|
||||
|
||||
// DeleteAllTransactions deletes all existed transactions from database
|
||||
func (s *TransactionService) DeleteAllTransactions(c core.Context, uid int64) error {
|
||||
func (s *TransactionService) DeleteAllTransactions(c core.Context, uid int64, deleteAccount bool) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -1254,12 +1344,12 @@ func (s *TransactionService) DeleteAllTransactions(c core.Context, uid int64) er
|
||||
|
||||
accountUpdateModel := &models.Account{
|
||||
Balance: 0,
|
||||
Deleted: true,
|
||||
Deleted: deleteAccount,
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
// Update all transaction to deleted
|
||||
// Update all transactions to deleted
|
||||
_, err := sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
@@ -1273,14 +1363,14 @@ func (s *TransactionService) DeleteAllTransactions(c core.Context, uid int64) er
|
||||
return err
|
||||
}
|
||||
|
||||
// Update all transaction picture to deleted
|
||||
// Update all transaction pictures to deleted
|
||||
_, err = sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(pictureUpdateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update all account table to deleted
|
||||
// Update all accounts to deleted or set amount to zero
|
||||
_, err = sess.Cols("balance", "deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(accountUpdateModel)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/datastore"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
@@ -45,7 +46,7 @@ func (s *UserApplicationCloudSettingsService) GetUserApplicationCloudSettingsByU
|
||||
}
|
||||
|
||||
// UpdateUserApplicationCloudSettings updates user application cloud settings
|
||||
func (s *UserApplicationCloudSettingsService) UpdateUserApplicationCloudSettings(c core.Context, uid int64, settings models.ApplicationCloudSettingSlice) error {
|
||||
func (s *UserApplicationCloudSettingsService) UpdateUserApplicationCloudSettings(c core.Context, uid int64, settings models.ApplicationCloudSettingSlice, forceUpdate bool, lastUpdateTime int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -65,14 +66,21 @@ func (s *UserApplicationCloudSettingsService) UpdateUserApplicationCloudSettings
|
||||
return err
|
||||
}
|
||||
|
||||
updatedRows := int64(0)
|
||||
|
||||
if !exists {
|
||||
_, err = sess.Insert(userApplicationCloudSetting)
|
||||
updatedRows, err = sess.Insert(userApplicationCloudSetting)
|
||||
} else if forceUpdate || lastUpdateTime <= 0 {
|
||||
updatedRows, err = sess.ID(uid).Cols("settings", "updated_unix_time").Update(userApplicationCloudSetting)
|
||||
} else {
|
||||
_, err = sess.ID(uid).Cols("settings", "updated_unix_time").Update(userApplicationCloudSetting)
|
||||
updatedRows, err = sess.ID(uid).Cols("settings", "updated_unix_time").Where("updated_unix_time=?", lastUpdateTime).Update(userApplicationCloudSetting)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
log.Errorf(c, "[user_app_cloud_settings.UpdateUserApplicationCloudSettings] failed to update user application cloud settings")
|
||||
return errs.ErrDatabaseOperationFailed
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -162,7 +162,7 @@ func (s *UserService) GetUserAvatar(c core.Context, uid int64, fileExtension str
|
||||
return nil, errs.ErrUserAvatarExtensionInvalid
|
||||
}
|
||||
|
||||
avatarFile, err := s.ReadAvatar(user.Uid, user.CustomAvatarType)
|
||||
avatarFile, err := s.ReadAvatar(c, user.Uid, user.CustomAvatarType)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errs.ErrUserAvatarNoExists
|
||||
@@ -293,6 +293,14 @@ func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLa
|
||||
updateCols = append(updateCols, "fiscal_year_start")
|
||||
}
|
||||
|
||||
if core.CALENDAR_DISPLAY_TYPE_DEFAULT <= user.CalendarDisplayType && user.CalendarDisplayType <= core.CALENDAR_DISPLAY_TYPE_BUDDHIST {
|
||||
updateCols = append(updateCols, "calendar_display_type")
|
||||
}
|
||||
|
||||
if core.DATE_DISPLAY_TYPE_DEFAULT <= user.DateDisplayType && user.DateDisplayType <= core.DATE_DISPLAY_TYPE_BUDDHIST {
|
||||
updateCols = append(updateCols, "date_display_type")
|
||||
}
|
||||
|
||||
if core.LONG_DATE_FORMAT_DEFAULT <= user.LongDateFormat && user.LongDateFormat <= core.LONG_DATE_FORMAT_D_M_YYYY {
|
||||
updateCols = append(updateCols, "long_date_format")
|
||||
}
|
||||
@@ -313,6 +321,14 @@ func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLa
|
||||
updateCols = append(updateCols, "fiscal_year_format")
|
||||
}
|
||||
|
||||
if core.CURRENCY_DISPLAY_TYPE_DEFAULT <= user.CurrencyDisplayType && user.CurrencyDisplayType <= core.CURRENCY_DISPLAY_TYPE_NAME_AFTER_AMOUNT {
|
||||
updateCols = append(updateCols, "currency_display_type")
|
||||
}
|
||||
|
||||
if core.NUMERAL_SYSTEM_DEFAULT <= user.NumeralSystem && user.NumeralSystem <= core.NUMERAL_SYSTEM_DEVANAGARI_NUMERALS {
|
||||
updateCols = append(updateCols, "numeral_system")
|
||||
}
|
||||
|
||||
if core.DECIMAL_SEPARATOR_DEFAULT <= user.DecimalSeparator && user.DecimalSeparator <= core.DECIMAL_SEPARATOR_COMMA {
|
||||
updateCols = append(updateCols, "decimal_separator")
|
||||
}
|
||||
@@ -321,14 +337,10 @@ func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLa
|
||||
updateCols = append(updateCols, "digit_grouping_symbol")
|
||||
}
|
||||
|
||||
if core.DIGIT_GROUPING_TYPE_DEFAULT <= user.DigitGrouping && user.DigitGrouping <= core.DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR {
|
||||
if core.DIGIT_GROUPING_TYPE_DEFAULT <= user.DigitGrouping && user.DigitGrouping <= core.DIGIT_GROUPING_TYPE_INDIAN_NUMBER_GROUPING {
|
||||
updateCols = append(updateCols, "digit_grouping")
|
||||
}
|
||||
|
||||
if core.CURRENCY_DISPLAY_TYPE_DEFAULT <= user.CurrencyDisplayType && user.CurrencyDisplayType <= core.CURRENCY_DISPLAY_TYPE_NAME_AFTER_AMOUNT {
|
||||
updateCols = append(updateCols, "currency_display_type")
|
||||
}
|
||||
|
||||
if core.COORDINATE_DISPLAY_TYPE_DEFAULT <= user.CoordinateDisplayType && user.CoordinateDisplayType <= core.COORDINATE_DISPLAY_TYPE_LONGITUDE_LATITUDE_DEGREES_MINUTES_SECONDS {
|
||||
updateCols = append(updateCols, "coordinate_display_type")
|
||||
}
|
||||
@@ -371,7 +383,7 @@ func (s *UserService) UpdateUserAvatar(c core.Context, uid int64, avatarFile mul
|
||||
|
||||
defer avatarFile.Close()
|
||||
|
||||
err := s.SaveAvatar(uid, avatarFile, fileExtension)
|
||||
err := s.SaveAvatar(c, uid, avatarFile, fileExtension)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -394,7 +406,7 @@ func (s *UserService) UpdateUserAvatar(c core.Context, uid int64, avatarFile mul
|
||||
}
|
||||
|
||||
if fileExtension != oldFileExtension && oldFileExtension != "" {
|
||||
err = s.DeleteAvatar(uid, oldFileExtension)
|
||||
err = s.DeleteAvatar(c, uid, oldFileExtension)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[users.UpdateUserAvatar] failed to delete old avatar with extension \"%s\" for user \"uid:%d\", because %s", oldFileExtension, uid, err.Error())
|
||||
@@ -410,7 +422,7 @@ func (s *UserService) RemoveUserAvatar(c core.Context, uid int64, fileExtension
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
err := s.DeleteAvatar(uid, fileExtension)
|
||||
err := s.DeleteAvatar(c, uid, fileExtension)
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
|
||||
@@ -63,6 +63,7 @@ const (
|
||||
const (
|
||||
LocalFileSystemObjectStorageType string = "local_filesystem"
|
||||
MinIOStorageType string = "minio"
|
||||
WebDAVStorageType string = "webdav"
|
||||
)
|
||||
|
||||
// Uuid generator types
|
||||
@@ -137,6 +138,8 @@ const (
|
||||
defaultLogFileMaxSize uint32 = 104857600 // 100 MB
|
||||
defaultLogFileMaxDays uint32 = 7 // days
|
||||
|
||||
defaultWebDAVRequestTimeout uint32 = 10000 // 10 seconds
|
||||
|
||||
defaultInMemoryDuplicateCheckerCleanupInterval uint32 = 60 // 1 minutes
|
||||
defaultDuplicateSubmissionsInterval uint32 = 300 // 5 minutes
|
||||
|
||||
@@ -195,6 +198,17 @@ type MinIOConfig struct {
|
||||
RootPath string
|
||||
}
|
||||
|
||||
// WebDAVConfig represents the WebDAV setting config
|
||||
type WebDAVConfig struct {
|
||||
Url string
|
||||
Username string
|
||||
Password string
|
||||
RootPath string
|
||||
RequestTimeout uint32
|
||||
Proxy string
|
||||
SkipTLSVerify bool
|
||||
}
|
||||
|
||||
// TipConfig represents a tip setting config
|
||||
type TipConfig struct {
|
||||
Enabled bool
|
||||
@@ -264,6 +278,7 @@ type Config struct {
|
||||
StorageType string
|
||||
LocalFileSystemPath string
|
||||
MinIOConfig *MinIOConfig
|
||||
WebDAVConfig *WebDAVConfig
|
||||
|
||||
// Uuid
|
||||
UuidGeneratorType string
|
||||
@@ -697,6 +712,8 @@ func loadStorageConfiguration(config *Config, configFile *ini.File, sectionName
|
||||
config.StorageType = LocalFileSystemObjectStorageType
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "type") == MinIOStorageType {
|
||||
config.StorageType = MinIOStorageType
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "type") == WebDAVStorageType {
|
||||
config.StorageType = WebDAVStorageType
|
||||
} else {
|
||||
return errs.ErrInvalidStorageType
|
||||
}
|
||||
@@ -718,9 +735,18 @@ func loadStorageConfiguration(config *Config, configFile *ini.File, sectionName
|
||||
minIOConfig.SkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "minio_skip_tls_verify", false)
|
||||
minIOConfig.Bucket = getConfigItemStringValue(configFile, sectionName, "minio_bucket")
|
||||
minIOConfig.RootPath = getConfigItemStringValue(configFile, sectionName, "minio_root_path")
|
||||
|
||||
config.MinIOConfig = minIOConfig
|
||||
|
||||
webDAVConfig := &WebDAVConfig{}
|
||||
webDAVConfig.Url = getConfigItemStringValue(configFile, sectionName, "webdav_url")
|
||||
webDAVConfig.Username = getConfigItemStringValue(configFile, sectionName, "webdav_username")
|
||||
webDAVConfig.Password = getConfigItemStringValue(configFile, sectionName, "webdav_password")
|
||||
webDAVConfig.RootPath = getConfigItemStringValue(configFile, sectionName, "webdav_root_path")
|
||||
webDAVConfig.RequestTimeout = getConfigItemUint32Value(configFile, sectionName, "webdav_request_timeout", defaultWebDAVRequestTimeout)
|
||||
webDAVConfig.Proxy = getConfigItemStringValue(configFile, sectionName, "webdav_proxy", "system")
|
||||
webDAVConfig.SkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "webdav_skip_tls_verify", false)
|
||||
config.WebDAVConfig = webDAVConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package settings
|
||||
|
||||
// ConfigContainer contains the current setting config
|
||||
type ConfigContainer struct {
|
||||
Current *Config
|
||||
current *Config
|
||||
}
|
||||
|
||||
// Initialize a config container singleton instance
|
||||
@@ -15,5 +15,10 @@ var (
|
||||
|
||||
// SetCurrentConfig sets the current config by a given config
|
||||
func SetCurrentConfig(config *Config) {
|
||||
Container.Current = config
|
||||
Container.current = config
|
||||
}
|
||||
|
||||
// GetCurrentConfig returns the current config
|
||||
func (c *ConfigContainer) GetCurrentConfig() *Config {
|
||||
return c.current
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// bytesSliceObject represents a byte slice object in storage
|
||||
type bytesSliceObject struct {
|
||||
*bytes.Reader
|
||||
}
|
||||
|
||||
// Close does nothing because it does not hold any resources that need to be released
|
||||
func (b *bytesSliceObject) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newByteSliceObject creates a new byte slice object from the specified byte slice
|
||||
func newByteSliceObject(data []byte) ObjectInStorage {
|
||||
return &bytesSliceObject{
|
||||
Reader: bytes.NewReader(data),
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
@@ -28,17 +29,17 @@ func NewLocalFileSystemObjectStorage(config *settings.Config, pathPrefix string)
|
||||
}
|
||||
|
||||
// Exists returns whether the file exists
|
||||
func (s *LocalFileSystemObjectStorage) Exists(path string) (bool, error) {
|
||||
func (s *LocalFileSystemObjectStorage) Exists(ctx core.Context, path string) (bool, error) {
|
||||
return utils.IsExists(s.getFinalPath(path))
|
||||
}
|
||||
|
||||
// Read returns the object instance according to specified the file path
|
||||
func (s *LocalFileSystemObjectStorage) Read(path string) (ObjectInStorage, error) {
|
||||
func (s *LocalFileSystemObjectStorage) Read(ctx core.Context, path string) (ObjectInStorage, error) {
|
||||
return os.Open(s.getFinalPath(path))
|
||||
}
|
||||
|
||||
// Save returns whether save the object instance successfully
|
||||
func (s *LocalFileSystemObjectStorage) Save(path string, object ObjectInStorage) error {
|
||||
func (s *LocalFileSystemObjectStorage) Save(ctx core.Context, path string, object ObjectInStorage) error {
|
||||
finalPath := s.getFinalPath(path)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(finalPath), os.ModePerm); err != nil {
|
||||
@@ -59,7 +60,7 @@ func (s *LocalFileSystemObjectStorage) Save(path string, object ObjectInStorage)
|
||||
}
|
||||
|
||||
// Delete returns whether delete the object according to specified the file path successfully
|
||||
func (s *LocalFileSystemObjectStorage) Delete(path string) error {
|
||||
func (s *LocalFileSystemObjectStorage) Delete(ctx core.Context, path string) error {
|
||||
return os.Remove(s.getFinalPath(path))
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
@@ -64,8 +65,7 @@ func NewMinIOObjectStorage(config *settings.Config, pathPrefix string) (*MinIOOb
|
||||
}
|
||||
|
||||
// Exists returns whether the file exists
|
||||
func (s *MinIOObjectStorage) Exists(path string) (bool, error) {
|
||||
ctx := context.Background()
|
||||
func (s *MinIOObjectStorage) Exists(ctx core.Context, path string) (bool, error) {
|
||||
objectInfo, err := s.minIOClient.StatObject(ctx, s.minIOConfig.Bucket, s.getFinalPath(path), minio.StatObjectOptions{})
|
||||
|
||||
if err == nil && !objectInfo.IsDeleteMarker {
|
||||
@@ -76,22 +76,19 @@ func (s *MinIOObjectStorage) Exists(path string) (bool, error) {
|
||||
}
|
||||
|
||||
// Read returns the object instance according to specified the file path
|
||||
func (s *MinIOObjectStorage) Read(path string) (ObjectInStorage, error) {
|
||||
ctx := context.Background()
|
||||
func (s *MinIOObjectStorage) Read(ctx core.Context, path string) (ObjectInStorage, error) {
|
||||
return s.minIOClient.GetObject(ctx, s.minIOConfig.Bucket, s.getFinalPath(path), minio.GetObjectOptions{})
|
||||
}
|
||||
|
||||
// Save returns whether save the object instance successfully
|
||||
func (s *MinIOObjectStorage) Save(path string, object ObjectInStorage) error {
|
||||
ctx := context.Background()
|
||||
func (s *MinIOObjectStorage) Save(ctx core.Context, path string, object ObjectInStorage) error {
|
||||
_, err := s.minIOClient.PutObject(ctx, s.minIOConfig.Bucket, s.getFinalPath(path), object, -1, minio.PutObjectOptions{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete returns whether delete the object according to specified the file path successfully
|
||||
func (s *MinIOObjectStorage) Delete(path string) error {
|
||||
ctx := context.Background()
|
||||
func (s *MinIOObjectStorage) Delete(ctx core.Context, path string) error {
|
||||
return s.minIOClient.RemoveObject(ctx, s.minIOConfig.Bucket, s.getFinalPath(path), minio.RemoveObjectOptions{})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package storage
|
||||
|
||||
import "github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
|
||||
// ObjectStorage represents an object storage to store file object
|
||||
type ObjectStorage interface {
|
||||
Exists(path string) (bool, error)
|
||||
Read(path string) (ObjectInStorage, error)
|
||||
Save(path string, object ObjectInStorage) error
|
||||
Delete(path string) error
|
||||
Exists(ctx core.Context, path string) (bool, error)
|
||||
Read(ctx core.Context, path string) (ObjectInStorage, error)
|
||||
Save(ctx core.Context, path string, object ObjectInStorage) error
|
||||
Delete(ctx core.Context, path string) error
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
@@ -10,8 +11,8 @@ const transactionPicturePathPrefix = "transaction"
|
||||
|
||||
// StorageContainer contains the current object storage
|
||||
type StorageContainer struct {
|
||||
AvatarCurrentStorage ObjectStorage
|
||||
TransactionPictureCurrentStorage ObjectStorage
|
||||
avatarCurrentStorage ObjectStorage
|
||||
transactionPictureCurrentStorage ObjectStorage
|
||||
}
|
||||
|
||||
// Initialize a object storage container singleton instance
|
||||
@@ -21,63 +22,99 @@ var (
|
||||
|
||||
// InitializeStorageContainer initializes the current object storage according to the config
|
||||
func InitializeStorageContainer(config *settings.Config) error {
|
||||
avatarStorage, err := newObjectStorage(config, avatarPathPrefix)
|
||||
if config.AvatarProvider == core.USER_AVATAR_PROVIDER_INTERNAL {
|
||||
avatarStorage, err := newObjectStorage(config, avatarPathPrefix)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Container.avatarCurrentStorage = avatarStorage
|
||||
}
|
||||
|
||||
Container.AvatarCurrentStorage = avatarStorage
|
||||
if config.EnableTransactionPictures {
|
||||
transactionPictureStorage, err := newObjectStorage(config, transactionPicturePathPrefix)
|
||||
|
||||
transactionPictureStorage, err := newObjectStorage(config, transactionPicturePathPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
Container.transactionPictureCurrentStorage = transactionPictureStorage
|
||||
}
|
||||
|
||||
Container.TransactionPictureCurrentStorage = transactionPictureStorage
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExistsAvatar returns whether the avatar file exists from the current avatar object storage
|
||||
func (s *StorageContainer) ExistsAvatar(path string) (bool, error) {
|
||||
return s.AvatarCurrentStorage.Exists(path)
|
||||
func (s *StorageContainer) ExistsAvatar(ctx core.Context, path string) (bool, error) {
|
||||
if s.avatarCurrentStorage == nil {
|
||||
return false, errs.ErrSystemError
|
||||
}
|
||||
|
||||
return s.avatarCurrentStorage.Exists(ctx, path)
|
||||
}
|
||||
|
||||
// ReadAvatar returns the avatar file from the current avatar object storage
|
||||
func (s *StorageContainer) ReadAvatar(path string) (ObjectInStorage, error) {
|
||||
return s.AvatarCurrentStorage.Read(path)
|
||||
func (s *StorageContainer) ReadAvatar(ctx core.Context, path string) (ObjectInStorage, error) {
|
||||
if s.avatarCurrentStorage == nil {
|
||||
return nil, errs.ErrSystemError
|
||||
}
|
||||
|
||||
return s.avatarCurrentStorage.Read(ctx, path)
|
||||
}
|
||||
|
||||
// SaveAvatar returns whether save the avatar file into the current avatar object storage successfully
|
||||
func (s *StorageContainer) SaveAvatar(path string, object ObjectInStorage) error {
|
||||
return s.AvatarCurrentStorage.Save(path, object)
|
||||
func (s *StorageContainer) SaveAvatar(ctx core.Context, path string, object ObjectInStorage) error {
|
||||
if s.avatarCurrentStorage == nil {
|
||||
return errs.ErrSystemError
|
||||
}
|
||||
|
||||
return s.avatarCurrentStorage.Save(ctx, path, object)
|
||||
}
|
||||
|
||||
// DeleteAvatar returns whether delete the avatar file from the current avatar object storage successfully
|
||||
func (s *StorageContainer) DeleteAvatar(path string) error {
|
||||
return s.AvatarCurrentStorage.Delete(path)
|
||||
func (s *StorageContainer) DeleteAvatar(ctx core.Context, path string) error {
|
||||
if s.avatarCurrentStorage == nil {
|
||||
return errs.ErrSystemError
|
||||
}
|
||||
|
||||
return s.avatarCurrentStorage.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ExistsTransactionPicture returns whether the transaction picture file exists from the current transaction picture object storage
|
||||
func (s *StorageContainer) ExistsTransactionPicture(path string) (bool, error) {
|
||||
return s.TransactionPictureCurrentStorage.Exists(path)
|
||||
func (s *StorageContainer) ExistsTransactionPicture(ctx core.Context, path string) (bool, error) {
|
||||
if s.transactionPictureCurrentStorage == nil {
|
||||
return false, errs.ErrSystemError
|
||||
}
|
||||
|
||||
return s.transactionPictureCurrentStorage.Exists(ctx, path)
|
||||
}
|
||||
|
||||
// ReadTransactionPicture returns the transaction picture file from the current transaction picture object storage
|
||||
func (s *StorageContainer) ReadTransactionPicture(path string) (ObjectInStorage, error) {
|
||||
return s.TransactionPictureCurrentStorage.Read(path)
|
||||
func (s *StorageContainer) ReadTransactionPicture(ctx core.Context, path string) (ObjectInStorage, error) {
|
||||
if s.transactionPictureCurrentStorage == nil {
|
||||
return nil, errs.ErrSystemError
|
||||
}
|
||||
|
||||
return s.transactionPictureCurrentStorage.Read(ctx, path)
|
||||
}
|
||||
|
||||
// SaveTransactionPicture returns whether save the transaction picture file into the current transaction picture object storage successfully
|
||||
func (s *StorageContainer) SaveTransactionPicture(path string, object ObjectInStorage) error {
|
||||
return s.TransactionPictureCurrentStorage.Save(path, object)
|
||||
func (s *StorageContainer) SaveTransactionPicture(ctx core.Context, path string, object ObjectInStorage) error {
|
||||
if s.transactionPictureCurrentStorage == nil {
|
||||
return errs.ErrSystemError
|
||||
}
|
||||
|
||||
return s.transactionPictureCurrentStorage.Save(ctx, path, object)
|
||||
}
|
||||
|
||||
// DeleteTransactionPicture returns whether delete the transaction picture file from the current transaction picture object storage successfully
|
||||
func (s *StorageContainer) DeleteTransactionPicture(path string) error {
|
||||
return s.TransactionPictureCurrentStorage.Delete(path)
|
||||
func (s *StorageContainer) DeleteTransactionPicture(ctx core.Context, path string) error {
|
||||
if s.transactionPictureCurrentStorage == nil {
|
||||
return errs.ErrSystemError
|
||||
}
|
||||
|
||||
return s.transactionPictureCurrentStorage.Delete(ctx, path)
|
||||
}
|
||||
|
||||
func newObjectStorage(config *settings.Config, pathPrefix string) (ObjectStorage, error) {
|
||||
@@ -85,6 +122,8 @@ func newObjectStorage(config *settings.Config, pathPrefix string) (ObjectStorage
|
||||
return NewLocalFileSystemObjectStorage(config, pathPrefix)
|
||||
} else if config.StorageType == settings.MinIOStorageType {
|
||||
return NewMinIOObjectStorage(config, pathPrefix)
|
||||
} else if config.StorageType == settings.WebDAVStorageType {
|
||||
return NewWebDAVObjectStorage(config, pathPrefix)
|
||||
}
|
||||
|
||||
return nil, errs.ErrInvalidStorageType
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// WebDAVObjectStorage represents WebDAV object storage
|
||||
type WebDAVObjectStorage struct {
|
||||
httpClient *http.Client
|
||||
webDavConfig *settings.WebDAVConfig
|
||||
rootPath string
|
||||
}
|
||||
|
||||
// NewWebDAVObjectStorage returns a WebDAV object storage
|
||||
func NewWebDAVObjectStorage(config *settings.Config, pathPrefix string) (*WebDAVObjectStorage, error) {
|
||||
webDavConfig := config.WebDAVConfig
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
utils.SetProxyUrl(transport, webDavConfig.Proxy)
|
||||
|
||||
if webDavConfig.SkipTLSVerify {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: time.Duration(webDavConfig.RequestTimeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
storage := &WebDAVObjectStorage{
|
||||
httpClient: client,
|
||||
webDavConfig: webDavConfig,
|
||||
rootPath: webDavConfig.RootPath,
|
||||
}
|
||||
|
||||
storage.rootPath = storage.getFinalPath(pathPrefix)
|
||||
storage.rootPath = strings.ReplaceAll(storage.rootPath, "\\", "/")
|
||||
|
||||
ctx := core.NewNullContext()
|
||||
exists, err := storage.directoryExists(ctx, storage.rootPath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
err := storage.createAllDirectories(ctx, "", storage.rootPath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
// Exists returns whether the file exists
|
||||
func (s *WebDAVObjectStorage) Exists(ctx core.Context, path string) (bool, error) {
|
||||
req, err := http.NewRequest("HEAD", s.getFinalFileUrl(path), nil)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(s.webDavConfig.Username, s.webDavConfig.Password)
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.Exists] cannot check file exists, because %s", err.Error())
|
||||
return false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return true, nil
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Errorf(ctx, "[webdav_storage.Exists] cannot check file exists, http status code is %d", resp.StatusCode)
|
||||
return false, errs.ErrSystemError
|
||||
}
|
||||
|
||||
// Read returns the object instance according to specified the file path
|
||||
func (s *WebDAVObjectStorage) Read(ctx core.Context, path string) (ObjectInStorage, error) {
|
||||
req, err := http.NewRequest("GET", s.getFinalFileUrl(path), nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(s.webDavConfig.Username, s.webDavConfig.Password)
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.Read] cannot get file, because %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.Read] cannot read response (http status code %d) body, because %s", resp.StatusCode, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Errorf(ctx, "[webdav_storage.Read] cannot get file, http status code is %d, response is %s", resp.StatusCode, string(body))
|
||||
return nil, errs.ErrSystemError
|
||||
}
|
||||
|
||||
return newByteSliceObject(body), nil
|
||||
}
|
||||
|
||||
// Save returns whether save the object instance successfully
|
||||
func (s *WebDAVObjectStorage) Save(ctx core.Context, path string, object ObjectInStorage) error {
|
||||
finalPath := s.getFinalPath(path)
|
||||
dir := strings.ReplaceAll(filepath.Dir(finalPath), "\\", "/")
|
||||
|
||||
exists, err := s.directoryExists(ctx, dir)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
rootExists, err := s.directoryExists(ctx, s.rootPath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !rootExists {
|
||||
err := s.createAllDirectories(ctx, "", s.rootPath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.createAllDirectories(ctx, s.rootPath, strings.ReplaceAll(filepath.Dir(path), "\\", "/"))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(object)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", s.getFinalFileUrl(path), bytes.NewReader(data))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(s.webDavConfig.Username, s.webDavConfig.Password)
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.Save] cannot save file, because %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.Save] cannot read response (http status code %d) body, because %s", resp.StatusCode, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
log.Errorf(ctx, "[webdav_storage.Save] cannot save file, http status code is %d, response is %s", resp.StatusCode, string(body))
|
||||
return errs.ErrSystemError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete returns whether delete the object according to specified the file path successfully
|
||||
func (s *WebDAVObjectStorage) Delete(ctx core.Context, path string) error {
|
||||
req, err := http.NewRequest("DELETE", s.getFinalFileUrl(path), nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(s.webDavConfig.Username, s.webDavConfig.Password)
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.Delete] cannot delete file, because %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.Delete] cannot read response (http status code %d) body, because %s", resp.StatusCode, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusNotFound {
|
||||
log.Errorf(ctx, "[webdav_storage.Delete] cannot delete file, http status code is %d, response is %s", resp.StatusCode, string(body))
|
||||
return errs.ErrSystemError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WebDAVObjectStorage) directoryExists(ctx core.Context, path string) (bool, error) {
|
||||
req, err := http.NewRequest("PROPFIND", s.getFinalDirectoryUrl(path), nil)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(s.webDavConfig.Username, s.webDavConfig.Password)
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.directoryExists] cannot check directory exists, because %s", err.Error())
|
||||
return false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusMultiStatus || resp.StatusCode == http.StatusOK {
|
||||
return true, nil
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Errorf(ctx, "[webdav_storage.directoryExists] cannot check directory exists, http status code is %d", resp.StatusCode)
|
||||
return false, errs.ErrSystemError
|
||||
}
|
||||
|
||||
func (s *WebDAVObjectStorage) createDirectory(ctx core.Context, path string) error {
|
||||
req, err := http.NewRequest("MKCOL", s.getFinalDirectoryUrl(path), nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(s.webDavConfig.Username, s.webDavConfig.Password)
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.createDirectory] cannot create directory, because %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[webdav_storage.createDirectory] cannot read response (http status code %d) body, because %s", resp.StatusCode, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusMethodNotAllowed {
|
||||
log.Errorf(ctx, "[webdav_storage.createDirectory] cannot create directory, http status code is %d, response is %s", resp.StatusCode, string(body))
|
||||
return errs.ErrSystemError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WebDAVObjectStorage) createAllDirectories(ctx core.Context, currentPath string, path string) error {
|
||||
directories := strings.Split(path, "/")
|
||||
|
||||
for _, dir := range directories {
|
||||
if len(dir) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
currentPath = currentPath + "/" + dir
|
||||
exists, err := s.directoryExists(ctx, currentPath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
err = s.createDirectory(ctx, currentPath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WebDAVObjectStorage) getFinalFileUrl(filePath string) string {
|
||||
finalUrl := s.webDavConfig.Url
|
||||
|
||||
if len(finalUrl) < 1 || finalUrl[len(finalUrl)-1] != '/' {
|
||||
finalUrl = finalUrl + "/"
|
||||
}
|
||||
|
||||
finalPath := s.getFinalPath(filePath)
|
||||
|
||||
if len(finalPath) > 0 && finalPath[0] == '/' {
|
||||
finalPath = finalPath[1:]
|
||||
}
|
||||
|
||||
return finalUrl + finalPath
|
||||
}
|
||||
|
||||
func (s *WebDAVObjectStorage) getFinalDirectoryUrl(dirPath string) string {
|
||||
finalUrl := s.webDavConfig.Url
|
||||
|
||||
if len(finalUrl) < 1 || finalUrl[len(finalUrl)-1] != '/' {
|
||||
finalUrl = finalUrl + "/"
|
||||
}
|
||||
|
||||
if len(dirPath) > 0 && dirPath[0] == '/' {
|
||||
dirPath = dirPath[1:]
|
||||
}
|
||||
|
||||
if len(dirPath) > 0 && dirPath[len(dirPath)-1] != '/' {
|
||||
dirPath = dirPath + "/"
|
||||
}
|
||||
|
||||
return finalUrl + dirPath
|
||||
}
|
||||
|
||||
func (s *WebDAVObjectStorage) getFinalPath(path string) string {
|
||||
rootPath := s.rootPath
|
||||
|
||||
if len(rootPath) < 1 || rootPath[len(rootPath)-1] != '/' {
|
||||
rootPath = rootPath + "/"
|
||||
}
|
||||
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
|
||||
return rootPath + path
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5/request"
|
||||
)
|
||||
|
||||
// CookieExtractor extracts a token from request cookies
|
||||
type CookieExtractor []string
|
||||
|
||||
func (e CookieExtractor) ExtractToken(req *http.Request) (string, error) {
|
||||
for _, arg := range e {
|
||||
if cookie, _ := req.Cookie(arg); cookie != nil {
|
||||
return cookie.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", request.ErrNoTokenInRequest
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
// UuidContainer contains the current uuid generator
|
||||
type UuidContainer struct {
|
||||
Current UuidGenerator
|
||||
current UuidGenerator
|
||||
}
|
||||
|
||||
// Initialize a uuid container singleton instance
|
||||
@@ -19,7 +19,7 @@ var (
|
||||
func InitializeUuidGenerator(config *settings.Config) error {
|
||||
if config.UuidGeneratorType == settings.InternalUuidGeneratorType {
|
||||
generator, err := NewInternalUuidGenerator(config)
|
||||
Container.Current = generator
|
||||
Container.current = generator
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -29,10 +29,18 @@ func InitializeUuidGenerator(config *settings.Config) error {
|
||||
|
||||
// GenerateUuid returns a new uuid by the current uuid generator
|
||||
func (u *UuidContainer) GenerateUuid(uuidType UuidType) int64 {
|
||||
return u.Current.GenerateUuid(uuidType)
|
||||
if u.current == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return u.current.GenerateUuid(uuidType)
|
||||
}
|
||||
|
||||
// GenerateUuids returns new uuids by the current uuid generator
|
||||
func (u *UuidContainer) GenerateUuids(uuidType UuidType, count uint16) []int64 {
|
||||
return u.Current.GenerateUuids(uuidType, count)
|
||||
if u.current == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return u.current.GenerateUuids(uuidType, count)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-env': {},
|
||||
},
|
||||
plugins: {
|
||||
'autoprefixer': {
|
||||
logical: false
|
||||
},
|
||||
'postcss-preset-env': {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="750" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<path d="m2.38122,215.61524l748.61878,-113.11523l0,197.99999l-750,0l1.38122,-84.88476z" fill="#111111" id="svg_3" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0" stroke-width="null"/>
|
||||
<g id="BACKGROUND"/>
|
||||
</g>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="750" height="300" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<path d="m2.38122,215.61524l748.61878,-113.11523l0,197.99999l-750,0l1.38122,-84.88476z" fill="#272829" id="svg_3" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0" stroke-width="null"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 383 B |
@@ -1,73 +1,72 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="750" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="750" height="300" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<path d="m2.381218,216.615238l748.618787,-113.11523l0,197.999993l-750.000002,0l1.381215,-84.884763z" fill="#f9f4ea" id="svg_3" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0" stroke-width="null"/>
|
||||
<g id="BACKGROUND"/>
|
||||
<g id="svg_1">
|
||||
<path d="m623,300.642881c-63.833,0 -127.667,0 -191.5,0c0.109,-1.4 0.038,-2.844 0.365,-4.191c0.775,-3.196 1.715,-6.353 2.636,-9.511c0.602,-2.062 1.084,-4.195 1.979,-6.129c2.321,-5.011 3.543,-10.485 6.466,-15.235c0.126,-0.205 0.181,-0.471 0.207,-0.716c0.161,-1.56 1.197,-2.67 1.938,-3.947c1.587,-2.735 3.482,-5.229 5.48,-7.668c1.135,-1.386 2.09,-2.918 3.127,-4.385c0.878,0.766 1.792,1.494 2.627,2.303c3.598,3.486 7.44,6.677 11.787,9.17c6.411,3.678 13.171,5.353 20.6,3.493c4.237,-1.061 8.191,-2.676 12.041,-4.69c7.177,-3.755 13.324,-8.74 18.465,-14.989c0.938,-1.14 1.95,-2.218 2.932,-3.33c1.704,0.772 3.2,1.503 4.737,2.135c5.842,2.405 11.935,3.647 18.246,3.4c5.454,-0.214 10.695,-1.651 15.701,-3.762c10.855,-4.578 21.031,-10.344 30.219,-17.776c0.768,-0.622 1.628,-1.13 2.446,-1.692c2.325,-0.514 4.693,-0.884 6.966,-1.569c6.536,-1.969 12.432,-5.303 18.194,-8.877c2.296,-1.424 4.526,-2.953 6.786,-4.435c2.576,-0.065 5.17,-0.355 7.723,-0.145c4.5,0.369 8.904,1.307 12.737,3.908c2.238,1.519 3.939,3.496 4.736,6.04c-0.14,0.221 -0.167,0.323 -0.227,0.349c-1.063,0.465 -2.143,0.893 -3.194,1.383c-6.055,2.821 -10.479,7.337 -13.259,13.349c-2.424,5.241 -1.382,10.402 1.047,15.386c1.19,2.441 3.62,3.714 6.339,3.293c4.946,-0.766 8.461,-3.775 11.243,-7.64c3.751,-5.209 4.589,-11.135 3.601,-17.373c-0.335,-2.116 -0.785,-4.215 -1.232,-6.582c4.976,-0.537 9.83,-1.131 14.698,-1.569c6.83,-0.614 13.517,-1.964 19.931,-4.322c7.326,-2.694 14.531,-5.719 21.76,-8.672c7.81,-3.19 14.893,-7.747 22.164,-11.949c6.273,-3.625 11.789,-8.086 15.927,-14.114c0.129,-0.188 0.372,-0.296 0.562,-0.442c0,14.833 0,29.667 0,44.5c-2.472,0.148 -4.942,0.406 -7.415,0.423c-6.998,0.048 -13.85,1.277 -20.66,2.647c-6.182,1.243 -12.155,3.319 -18.031,5.599c-4.17,1.619 -8.171,3.69 -12.377,5.197c-6.222,2.23 -11.549,6.109 -17.404,9.01c-6.341,3.141 -11.549,7.948 -17.087,12.288c-6.142,4.813 -11.179,10.666 -16.643,16.128c-2.606,2.605 -4.496,5.696 -6.591,8.626c-3.688,5.156 -7.004,10.578 -10.453,15.904c-0.214,0.329 -0.232,0.783 -0.34,1.179z" fill="#FEDBD0" id="svg_4"/>
|
||||
<path d="m623,300.642881c0.108,-0.396 0.126,-0.85 0.338,-1.178c3.449,-5.325 6.766,-10.747 10.453,-15.904c2.095,-2.93 3.985,-6.021 6.591,-8.626c5.464,-5.462 10.501,-11.315 16.643,-16.128c5.538,-4.34 10.746,-9.147 17.087,-12.288c5.855,-2.901 11.182,-6.78 17.404,-9.01c4.206,-1.507 8.207,-3.578 12.377,-5.197c5.876,-2.281 11.849,-4.356 18.031,-5.599c6.81,-1.37 13.662,-2.598 20.66,-2.647c2.472,-0.017 4.943,-0.275 7.415,-0.423c0,25.667 0,51.333 0,77c-42.332,0 -84.666,0 -126.999,0zm53.375,-36.965c1.139,-0.251 2.619,-0.443 3.985,-0.943c0.563,-0.206 1.229,-1.084 1.215,-1.641c-0.013,-0.519 -0.836,-1.415 -1.355,-1.454c-2.359,-0.18 -4.741,-0.217 -7.104,-0.089c-0.962,0.052 -1.502,0.973 -1.518,1.987c-0.016,0.99 0.656,1.517 1.505,1.71c0.952,0.218 1.942,0.265 3.272,0.43zm31.44,-5.633c-1.287,0.123 -2.369,0.146 -3.416,0.349c-0.971,0.189 -1.705,0.856 -1.602,1.897c0.107,1.09 0.798,1.832 2.005,1.764c2.388,-0.135 4.776,-0.288 7.159,-0.498c0.801,-0.071 1.394,-0.547 1.424,-1.445c0.031,-0.915 -0.549,-1.353 -1.359,-1.482c-1.467,-0.232 -2.94,-0.411 -4.211,-0.585zm18.439,-17.441c1.112,-0.215 2.701,-0.371 4.167,-0.88c0.615,-0.213 1.278,-1.134 1.369,-1.807c0.134,-0.982 -0.882,-1.442 -1.701,-1.451c-2.232,-0.024 -4.478,0.013 -6.689,0.279c-0.592,0.071 -1.399,0.978 -1.532,1.621c-0.188,0.906 0.367,1.814 1.446,2.009c0.808,0.145 1.643,0.135 2.94,0.229zm-28.051,11.011c0.01,0.05 0.02,0.1 0.03,0.149c1.375,-0.248 2.772,-0.412 4.117,-0.773c0.882,-0.237 1.453,-1.041 1.088,-1.923c-0.234,-0.566 -0.959,-1.235 -1.526,-1.297c-1.551,-0.169 -3.138,-0.088 -4.705,-0.005c-0.552,0.029 -1.29,0.196 -1.585,0.577c-0.46,0.595 -0.888,1.461 -0.802,2.145c0.056,0.438 1.022,0.872 1.655,1.091c0.52,0.178 1.149,0.036 1.728,0.036zm16.873,29.366c0.595,-0.074 1.654,-0.193 2.711,-0.339c1.004,-0.139 1.888,-0.673 1.714,-1.707c-0.109,-0.646 -0.848,-1.376 -1.491,-1.706c-1.902,-0.976 -3.9,-0.309 -5.789,0.143c-0.519,0.124 -1.159,1.292 -1.087,1.904c0.073,0.618 0.87,1.313 1.518,1.651c0.518,0.27 1.289,0.054 2.424,0.054zm-62.107,-1.702c0.992,-0.141 2.333,-0.217 3.6,-0.565c0.519,-0.142 1.203,-0.862 1.214,-1.333c0.01,-0.437 -0.739,-1.228 -1.219,-1.282c-2.214,-0.249 -4.455,-0.413 -6.676,-0.329c-0.544,0.02 -1.336,1.018 -1.5,1.692c-0.23,0.941 0.638,1.49 1.508,1.636c0.896,0.15 1.823,0.117 3.073,0.181zm56.714,10.81c-0.95,0.101 -1.623,0.107 -2.263,0.255c-0.974,0.227 -1.67,0.848 -1.699,1.888c-0.03,1.088 0.736,1.636 1.698,1.861c1.885,0.441 3.648,-0.083 5.244,-1.002c0.951,-0.548 0.863,-1.725 -0.147,-2.201c-0.951,-0.447 -2.046,-0.59 -2.833,-0.801zm26.315,-37.502c0.766,-0.263 1.911,-0.507 2.907,-1.034c1.329,-0.703 1.286,-2.119 -0.137,-2.548c-1.368,-0.413 -2.888,-0.43 -4.34,-0.418c-1.058,0.008 -1.957,0.585 -1.937,1.817c0.019,1.175 0.899,1.694 1.904,1.985c0.389,0.112 0.813,0.105 1.603,0.198zm0.345,19.21c0.952,-0.19 1.982,-0.219 2.829,-0.627c0.533,-0.258 1.055,-1.068 1.089,-1.659c0.026,-0.456 -0.629,-1.207 -1.134,-1.38c-0.902,-0.31 -1.919,-0.375 -2.888,-0.376c-2.054,-0.003 -3.157,0.781 -3.079,2.046c0.086,1.385 0.91,1.871 3.183,1.996zm-51.014,9.677c1.121,-0.151 2.125,-0.188 3.072,-0.44c1.06,-0.282 1.275,-1.309 0.548,-2.125c-0.839,-0.942 -4.41,-1.793 -5.578,-1.336c-0.85,0.332 -1.374,0.897 -1.335,1.857c0.039,0.958 0.583,1.513 1.482,1.72c0.639,0.147 1.292,0.233 1.811,0.324zm54.676,11.76c-1.539,-1.969 -3.594,-2.067 -5.636,-1.96c-0.518,0.027 -1.346,0.675 -1.414,1.13c-0.09,0.604 0.254,1.474 0.712,1.921c1.518,1.481 4.853,0.946 6.338,-1.091zm-63.279,1.341c0.587,-0.147 1.677,-0.286 2.637,-0.716c0.486,-0.218 1.047,-0.983 1.011,-1.458c-0.039,-0.507 -0.658,-1.215 -1.173,-1.388c-0.918,-0.307 -1.947,-0.334 -2.934,-0.373c-1.421,-0.057 -2.583,0.753 -2.604,1.715c-0.021,1.006 1.352,2.18 3.063,2.22zm-31.358,-4.32c-0.518,0.079 -1.188,0.107 -1.81,0.293c-0.819,0.245 -1.502,0.673 -1.465,1.719c0.041,1.148 0.804,1.672 1.744,1.718c1.049,0.052 2.16,-0.065 3.152,-0.39c0.58,-0.19 1.252,-0.858 1.4,-1.436c0.245,-0.953 -0.599,-1.458 -1.413,-1.727c-0.457,-0.149 -0.975,-0.114 -1.608,-0.177z" fill="#548099" id="svg_7"/>
|
||||
<path d="m346.953,290.630881c0.806,1.415 1.73,2.778 2.39,4.258c0.832,1.868 1.448,3.832 2.157,5.754c-43.5,0 -87,0 -130.5,0c0.664,-4.841 1.467,-9.669 1.949,-14.528c0.409,-4.123 0.447,-8.283 0.649,-12.426c0.655,-0.292 1.496,-0.416 1.932,-0.903c1.935,-2.161 4.26,-3.808 6.56,-5.537c3.753,-2.82 7.508,-5.604 11.694,-7.767c0.521,-0.269 0.858,-0.895 1.28,-1.356c1.969,-0.804 3.904,-1.705 5.914,-2.389c2.802,-0.953 5.66,-1.745 8.494,-2.605c0.444,0.781 0.854,1.584 1.34,2.338c1.034,1.602 3.777,1.039 4.439,-0.312c0.597,-1.219 1.348,-2.508 0.685,-3.985c1.22,-0.01 2.44,-0.019 3.659,-0.029c0.702,1.529 1.924,2.014 3.551,1.953c1.667,-0.062 1.744,-1.442 2.288,-2.46c1.394,-0.16 2.786,-0.341 4.183,-0.474c0.906,-0.086 1.406,0.366 1.468,1.282c-0.581,0.457 -1.236,0.848 -1.725,1.388c-0.997,1.1 -1.005,2.435 -0.49,3.73c0.443,1.113 1.448,1.554 2.592,1.627c1.41,0.09 3.026,-0.987 3.526,-2.301c0.455,-1.196 0.04,-2.505 -1.157,-3.529c-0.436,-0.373 -0.922,-0.689 -1.385,-1.031c0.254,-0.166 0.511,-0.479 0.762,-0.474c1.561,0.031 3.12,0.133 4.678,0.211c1.094,0.858 2.293,1.005 3.583,0.518c0.516,0.348 0.986,0.863 1.554,1.015c2.989,0.797 5.976,1.653 9.016,2.189c2.214,0.391 4.217,1.307 6.306,1.99c2.187,0.715 4.316,1.674 6.363,2.734c2.392,1.238 4.669,2.697 6.993,4.066c3.399,2.002 6.29,4.673 9.358,7.11c-0.825,0.879 -1.67,1.742 -2.471,2.642c-2.811,3.157 -5.61,6.324 -8.407,9.493c-0.931,1.055 -1.882,2.098 -2.752,3.203c-1.282,1.629 -1.073,4.988 0.312,5.917c1.948,1.307 4.248,0.857 5.65,-1.118c0.623,-0.878 1.155,-1.829 1.845,-2.649c3.565,-4.239 7.427,-8.242 10.273,-13.047c2.732,1.785 4.457,4.533 6.55,6.923c0.331,0.378 0.315,1.06 0.46,1.601c-0.496,0.652 -1.051,1.268 -1.474,1.965c-0.803,1.321 -0.775,3.932 -0.021,4.694c1.143,1.154 3.035,1.425 4.766,0.701c0.375,-0.158 0.775,-0.257 1.163,-0.382zm-72.407,-25.96c0.019,-2.027 -1.097,-3.298 -2.884,-3.284c-1.897,0.015 -4.097,2.687 -4.097,4.975c0,1.415 1.258,2.597 2.755,2.588c2.478,-0.015 4.202,-1.761 4.226,-4.279zm25.954,-5.911c0.013,-1.961 -1.734,-3.932 -3.438,-3.88c-1.956,0.061 -3.375,1.636 -3.444,3.82c-0.054,1.74 1.356,2.824 3.628,2.788c1.744,-0.026 3.244,-1.285 3.254,-2.728zm-11.518,6.038c0.038,-2.048 -0.319,-2.983 -1.43,-3.368c-1.198,-0.415 -2.281,-0.054 -3.198,0.77c-1.084,0.975 -1.515,2.591 -1.055,3.791c0.59,1.539 1.254,2.056 2.702,2.102c1.99,0.063 2.939,-0.986 2.981,-3.295z" fill="#DAF1FF" id="svg_9"/>
|
||||
<path d="m703.429,159.265881c0.502,-1.426 1.928,-1.133 2.946,-1.451c1.315,-0.41 2.746,-0.447 4.128,-0.646c-0.073,1.037 -0.284,2.089 -0.18,3.108c0.102,0.994 0.945,1.465 1.891,1.687c1.031,0.242 1.717,-0.193 2.278,-1.028c0.839,-1.249 1.542,-2.52 1.195,-4.107c2.698,-0.336 5.388,-0.789 8.096,-0.966c1.798,-0.118 3.675,0.481 5.423,0.193c4.629,-0.762 9.263,-0.294 13.889,-0.384c2.297,-0.044 4.602,0.301 6.904,0.47c0,6 0,12 0,18c-0.168,0.168 -0.379,0.31 -0.499,0.507c-3.606,5.916 -8.197,10.898 -14.1,14.566c-9.541,5.927 -19.201,11.641 -29.712,15.749c-4.875,1.905 -9.688,3.974 -14.602,5.768c-6.175,2.255 -12.495,3.985 -19.078,4.65c-6.024,0.609 -12.036,1.349 -18.086,2.036c-0.09,-0.077 -0.245,-0.163 -0.332,-0.294c-0.23,-0.345 -0.451,-0.7 -0.642,-1.069c-2.945,-5.662 -7.74,-8.766 -13.872,-9.97c-2.517,-0.494 -5.059,-0.863 -7.59,-1.289c0.122,-1.386 1.372,-1.852 2.257,-2.453c3.107,-2.111 5.83,-4.582 8.397,-7.337c2.64,-2.832 5.637,-5.351 8.608,-7.852c3.084,-2.596 6.282,-5.065 9.508,-7.485c4.242,-3.181 8.981,-5.539 13.649,-8.011c1.312,-0.695 2.5,-1.622 3.798,-2.346c2.435,-1.357 4.861,-2.746 7.38,-3.931c3.057,-1.438 5.85,-3.482 9.246,-4.157c1.207,-0.24 2.427,-0.418 3.641,-0.625c0.038,0.747 0.084,1.493 0.112,2.241c0.048,1.279 0.794,1.688 1.971,1.714c3.458,0.077 4.892,-2.151 3.376,-5.288z" fill="#FEDBD0" id="svg_10"/>
|
||||
<path d="m57.51,258.254881c7.145,-2.064 14.546,-3.315 21.271,-6.71c0.294,-0.148 0.596,-0.312 0.913,-0.376c1.115,-0.226 1.846,0.461 1.457,1.539c-0.502,1.392 -1.12,2.772 -1.899,4.026c-1.746,2.811 -3.623,5.542 -5.446,8.305c-3.48,3.206 -6.708,6.758 -10.503,9.534c-5.403,3.951 -11.477,6.726 -18.266,7.474c-3.769,0.415 -7.528,1.343 -11.356,0.469c-0.543,-0.124 -1.098,-0.198 -1.702,-0.304c-2.741,5.966 -4.551,12.065 -5.48,18.43c-0.667,0 -1.333,0 -2,0c0.047,-0.996 -0.041,-2.022 0.163,-2.985c1.855,-8.751 4.84,-17.099 9.313,-24.86c2.197,-3.813 4.602,-7.509 7.008,-11.196c1.63,-2.497 3.424,-4.887 5.145,-7.325c0.707,-0.793 1.414,-1.587 1.826,-2.049c-1.337,-4.444 -2.733,-8.452 -3.722,-12.558c-0.906,-3.763 -0.302,-7.588 0.281,-11.383c0.427,-2.786 0.691,-5.602 0.898,-8.414c0.539,-7.303 1.03,-14.61 1.491,-21.919c0.141,-2.242 0.072,-4.498 0.198,-6.741c0.083,-1.468 -0.072,-3.421 1.695,-3.792c1.799,-0.378 2.727,1.386 3.382,2.777c1.878,3.985 3.742,7.986 4.607,12.361c0.5,2.527 1.048,5.048 1.682,7.544c1.66,6.54 1.353,13.035 -0.341,19.504c-1.01,3.857 -2.083,7.696 -3.115,11.547c-0.081,0.302 -0.059,0.632 -0.138,1.602c4.488,-4.461 7.787,-9.186 11.478,-13.553c3.756,-4.445 7.3,-9.081 10.746,-13.773c3.441,-4.686 6.087,-9.859 8.756,-15.053c-1.95,-0.825 -3.721,-1.493 -5.418,-2.314c-1.995,-0.966 -3.474,-2.515 -4.659,-4.396c-2.918,-4.634 -4.822,-9.683 -6.084,-14.97c-1.099,-4.607 -2.018,-9.257 -2.983,-13.895c-0.37,-1.781 0.934,-2.944 2.664,-2.336c0.675,0.237 1.307,0.735 1.828,1.246c5.293,5.196 10.216,10.715 14.295,16.93c0.951,1.448 1.61,3.09 2.377,4.656c0.715,1.462 1.396,2.941 2.091,4.412c0.184,-0.025 0.369,-0.049 0.553,-0.074c0.57,-2.137 1.031,-4.311 1.741,-6.4c0.686,-2.019 0.513,-3.751 -0.461,-5.692c-1.933,-3.85 -2.263,-8.14 -2.788,-12.352c-0.267,-2.142 -0.713,-4.277 -0.791,-6.425c-0.243,-6.719 2.212,-12.722 5.395,-18.432c1.78,-3.192 3.947,-6.169 5.951,-9.235c0.226,-0.346 0.484,-0.688 0.789,-0.962c1.019,-0.913 1.987,-0.691 2.548,0.593c0.197,0.453 0.33,0.948 0.404,1.438c0.771,5.099 1.548,10.196 1.283,15.386c-0.242,4.729 -0.409,9.461 -0.621,14.191c-0.263,5.86 -1.892,11.295 -5.25,16.133c-0.69,0.994 -1.642,1.891 -2.657,2.548c-0.904,0.586 -1.382,1.11 -1.026,2.166c0.173,0.034 0.378,0.141 0.461,0.081c4.238,-3.071 9.005,-4.968 13.999,-6.39c3.119,-0.888 6.217,-1.852 9.316,-2.809c0.709,-0.219 1.533,-0.754 1.974,0.12c0.211,0.418 -0.073,1.317 -0.429,1.756c-3.838,4.724 -7.825,9.269 -13.147,12.472c-4.119,2.479 -8.495,3.238 -13.181,2.755c-0.401,-0.041 -0.801,-0.094 -1.403,-0.165c-0.513,1.27 -1.061,2.461 -1.48,3.695c-1.126,3.313 -2.51,6.49 -4.453,9.422c-0.589,0.889 -0.936,1.94 -1.393,2.917c-0.092,0.177 -0.184,0.354 -0.276,0.531c0.237,-0.025 0.475,-0.049 0.712,-0.074c8.518,-4.686 17.852,-5.489 27.302,-5.726c4.238,-0.106 8.496,0.056 12.66,-0.997c0.32,-0.081 0.688,-0.208 0.977,-0.122c0.431,0.129 0.934,0.345 1.165,0.687c0.135,0.199 -0.127,0.769 -0.346,1.076c-0.233,0.327 -0.62,0.546 -0.949,0.8c-4.471,3.456 -8.851,7.037 -13.446,10.319c-3.093,2.209 -6.334,4.341 -9.788,5.883c-4.479,1.999 -9.254,2.422 -14.099,0.841c-3.231,-1.054 -6.577,-1.775 -9.11,-4.412c-5.55,7.005 -10.925,13.789 -16.3,20.573c0.1,0.131 0.201,0.262 0.301,0.394c1.158,-0.542 2.28,-1.184 3.48,-1.603c2.105,-0.736 4.22,-1.533 6.399,-1.952c4.669,-0.897 9.384,-1.167 14.146,-0.602c2.304,0.273 4.653,0.157 6.981,0.233c0.495,0.016 1.018,0.013 1.476,0.172c0.931,0.323 1.159,1.218 0.56,1.997c-0.15,0.194 -0.348,0.364 -0.553,0.501c-5.39,3.602 -10.583,7.572 -16.826,9.66c-4.521,1.513 -9.105,2.594 -13.959,1.56c-2.984,-0.636 -6.028,-0.992 -9.407,-1.532c-1.229,1.622 -2.828,3.432 -4.06,5.465c-0.787,1.297 -1.522,2.517 -2.777,3.402c-0.313,0.22 -0.452,0.686 -0.672,1.038c-2.79,3.854 -5.6,7.693 -8.352,11.574c-0.505,0.712 -0.774,1.592 -1.151,2.396c-0.148,0.187 -0.298,0.372 -0.446,0.558c-0.049,0.137 -0.098,0.273 -0.148,0.409c0.085,-0.101 0.169,-0.202 0.254,-0.303c0.175,-0.159 0.349,-0.318 0.524,-0.476c0.359,-0.195 0.757,-0.343 1.072,-0.594c4.498,-3.578 9.142,-6.901 14.687,-8.726c0.769,-0.253 1.457,-0.754 2.182,-1.141zm-3.085,-15.668c-0.545,0.543 -0.931,1.13 -0.546,2.46c0.526,-0.893 0.821,-1.396 1.117,-1.898c-0.19,-0.188 -0.381,-0.375 -0.571,-0.562z" fill="#56819B" id="svg_11"/>
|
||||
<path d="m203.5,300.642881c-28.167,0 -56.333,0 -84.5,0c1.579,-2.025 3.197,-4.021 4.731,-6.079c6.246,-8.378 11.41,-17.326 14.302,-27.453c0.29,-1.015 0.785,-1.708 1.872,-2.113c1.625,-0.605 3.261,-1.3 4.707,-2.241c2.703,-1.757 5.273,-3.719 7.899,-5.595c0.724,0.841 1.492,0.323 2.277,0.105c1.509,-0.419 3.015,-0.961 4.557,-1.123c1.518,-0.159 2.939,-0.342 4.212,-1.261c0.46,-0.332 1.027,-0.601 1.581,-0.702c3.082,-0.565 6.171,-1.099 9.267,-1.587c3.507,-0.553 7.013,-1.126 10.537,-1.545c4.593,-0.547 9.198,-1.015 13.808,-1.394c3.384,-0.278 6.784,-0.345 10.175,-0.546c0.372,-0.022 0.727,-0.309 1.091,-0.473c0.773,0.86 1.621,1.664 2.303,2.59c1.619,2.199 3.404,4.325 4.683,6.715c3.164,5.915 4.317,12.338 4.11,19.02c-1.822,1.284 -3.545,2.656 -4.742,4.6c-0.692,1.125 -1.486,2.192 -2.282,3.248c-1.578,2.094 -3.217,4.141 -4.778,6.247c-1.37,1.847 -2.75,3.695 -3.969,5.642c-0.765,1.221 -1.238,2.624 -1.841,3.945z" fill="#B2CDDD" id="svg_12"/>
|
||||
<path d="m0,167.642881c1.953,0.858 4.023,1.524 5.837,2.613c4.339,2.606 8.422,5.588 12.183,9.001c7.607,6.902 12.849,15.19 15.549,25.179c1.135,4.2 1.898,8.407 2.177,12.703c0.491,7.553 -0.901,14.816 -3.643,21.862c-1.81,4.652 -4.023,9.07 -6.872,13.17c-0.178,0.257 -0.213,0.613 -0.314,0.923c-0.706,0.697 -1.407,1.399 -2.12,2.089c-1.599,1.548 -3.107,3.208 -4.825,4.612c-4.805,3.927 -9.685,7.762 -14.564,11.598c-1.008,0.792 -1.893,1.912 -3.409,1.75c0.001,-35.167 0.001,-70.333 0.001,-105.5z" fill="#54809A" id="svg_14"/>
|
||||
<path d="m26.5,300.642881c0.929,-6.365 2.74,-12.464 5.48,-18.43c0.603,0.106 1.158,0.18 1.702,0.304c3.828,0.874 7.586,-0.054 11.356,-0.469c6.789,-0.748 12.863,-3.524 18.266,-7.474c3.796,-2.775 7.023,-6.328 10.503,-9.534c3.102,2.025 6.632,2.88 10.186,3.581c4.458,0.88 8.946,1.606 13.421,2.397c0.163,0.029 0.331,0.078 0.493,0.066c5.706,-0.412 11.501,0.316 17.092,-1.612c2.083,-0.719 4.354,-0.881 6.519,-1.383c0.977,-0.227 2.248,-0.094 2.553,-1.51c3.712,1.091 7.464,0.778 11.605,0.016c-0.403,1.373 -0.662,2.386 -0.997,3.374c-3.081,9.089 -8.229,17.02 -13.851,24.685c-1.456,1.986 -2.886,3.991 -4.328,5.988c-30,0.001 -60,0.001 -90,0.001z" fill="#B2CDDD" id="svg_15"/>
|
||||
<path d="m453.697,248.860881c-1.036,1.466 -1.992,2.999 -3.127,4.385c-1.997,2.439 -3.893,4.933 -5.48,7.668c-0.741,1.277 -1.777,2.387 -1.938,3.947c-0.025,0.244 -0.081,0.511 -0.207,0.716c-2.923,4.751 -4.145,10.224 -6.466,15.235c-0.896,1.934 -1.377,4.067 -1.979,6.129c-0.922,3.158 -1.861,6.316 -2.636,9.511c-0.327,1.347 -0.255,2.791 -0.365,4.191c-8.333,0 -16.667,0 -25,0c-0.064,-0.492 -0.127,-0.984 -0.191,-1.476c-0.786,-6.001 -1.833,-11.982 -2.289,-18.008c-0.5,-6.618 -0.296,-13.268 0.94,-19.837c1.29,-6.857 3.538,-13.302 7.994,-18.814c1.804,-2.231 3.905,-4.056 6.617,-5.126c7.006,-2.763 13.85,-2.131 20.494,1.109c5.215,2.544 9.46,6.425 13.633,10.37z" fill="#FEF6F3" id="svg_18"/>
|
||||
<path d="m453.697,248.860881c-4.173,-3.945 -8.418,-7.826 -13.632,-10.369c-6.644,-3.241 -13.488,-3.872 -20.494,-1.109c-2.712,1.069 -4.814,2.894 -6.617,5.126c-4.456,5.512 -6.704,11.957 -7.994,18.814c-1.236,6.569 -1.44,13.219 -0.94,19.837c0.456,6.026 1.503,12.007 2.289,18.008c0.064,0.492 0.128,0.984 0.191,1.476c-1,0 -2,0 -3,0c-0.048,-0.899 -0.047,-1.804 -0.152,-2.696c-0.636,-5.436 -1.441,-10.857 -1.901,-16.307c-0.335,-3.965 -0.414,-7.975 -0.272,-11.952c0.276,-7.714 1.882,-15.169 5.327,-22.127c2.157,-4.356 4.886,-8.252 9.027,-11.079c4.653,-3.177 9.791,-4.007 15.208,-3.599c7.091,0.533 12.977,3.859 18.324,8.305c2.236,1.859 4.459,3.733 6.689,5.599c3.79,3.227 7.337,6.807 11.421,9.605c7.432,5.091 15.641,6.85 24.394,3.67c4.21,-1.529 8.286,-3.37 12.08,-5.845c5.415,-3.533 10.19,-7.729 14.251,-12.749c0.603,-0.745 1.155,-1.531 1.799,-2.388c-4.865,-3.29 -8.586,-7.267 -11.563,-11.948c-1.977,-4.036 -3.754,-8.144 -4.246,-12.674c-0.618,-5.684 0.519,-10.88 4.306,-15.343c7.45,-8.777 17.131,-4.118 20.673,1.989c3.019,5.207 4.006,10.856 2.952,16.788c-0.522,2.935 -1.291,5.827 -1.948,8.738c-1.193,2.637 -2.33,5.303 -3.604,7.9c-0.639,1.302 -1.527,2.482 -2.352,3.795c1.172,0.621 2.086,1.221 3.08,1.615c10.465,4.143 20.984,4.634 31.595,0.488c5.979,-2.336 11.681,-5.231 17.151,-8.551c3.608,-2.19 6.999,-4.739 10.487,-7.127c2.397,-1.768 4.815,-3.507 7.186,-5.31c7.254,-5.519 15.14,-9.814 23.882,-12.49c3.984,-1.219 8.052,-1.862 12.21,-1.993c0.661,-0.021 1.321,-0.104 1.981,-0.157c2.531,0.427 5.073,0.795 7.59,1.29c6.133,1.204 10.928,4.308 13.872,9.97c0.191,0.368 0.411,0.723 0.642,1.069c0.087,0.131 0.242,0.217 0.332,0.294c6.05,-0.686 12.062,-1.426 18.086,-2.036c6.583,-0.666 12.902,-2.396 19.078,-4.65c4.914,-1.794 9.728,-3.863 14.602,-5.768c10.511,-4.108 20.171,-9.822 29.712,-15.749c5.903,-3.667 10.494,-8.65 14.1,-14.566c0.12,-0.197 0.331,-0.339 0.499,-0.507c0,1.667 0,3.333 0,5c-0.19,0.146 -0.434,0.254 -0.562,0.442c-4.137,6.029 -9.654,10.489 -15.927,14.114c-7.271,4.202 -14.354,8.758 -22.164,11.949c-7.229,2.953 -14.434,5.979 -21.76,8.672c-6.414,2.358 -13.101,3.708 -19.931,4.322c-4.868,0.438 -9.722,1.032 -14.698,1.569c0.447,2.367 0.896,4.466 1.232,6.582c0.989,6.238 0.15,12.164 -3.601,17.373c-2.782,3.864 -6.297,6.874 -11.243,7.64c-2.72,0.421 -5.149,-0.852 -6.339,-3.293c-2.429,-4.984 -3.471,-10.145 -1.047,-15.386c2.78,-6.012 7.204,-10.527 13.259,-13.349c1.051,-0.49 2.131,-0.918 3.194,-1.383c0.06,-0.026 0.087,-0.129 0.227,-0.349c-0.797,-2.545 -2.498,-4.521 -4.736,-6.04c-3.833,-2.602 -8.237,-3.539 -12.737,-3.908c-2.553,-0.209 -5.147,0.08 -7.722,0.145c-2.839,0.556 -5.759,0.858 -8.5,1.723c-5.473,1.727 -10.671,4.173 -15.496,7.29c-2.762,1.783 -5.307,3.901 -7.95,5.869c-0.817,0.561 -1.677,1.07 -2.446,1.692c-9.188,7.432 -19.365,13.198 -30.219,17.776c-5.006,2.111 -10.247,3.548 -15.701,3.762c-6.311,0.247 -12.404,-0.995 -18.246,-3.4c-1.536,-0.632 -3.032,-1.364 -4.737,-2.135c-0.982,1.112 -1.995,2.19 -2.932,3.33c-5.141,6.249 -11.288,11.234 -18.465,14.989c-3.85,2.014 -7.804,3.629 -12.041,4.69c-7.43,1.86 -14.189,0.184 -20.6,-3.493c-4.347,-2.493 -8.189,-5.684 -11.787,-9.17c-0.834,-0.816 -1.748,-1.545 -2.626,-2.31zm73.068,-22.082c1.719,-5.217 2.952,-10.54 2.417,-16.06c-0.418,-4.322 -1.932,-8.262 -5.474,-11.091c-3.886,-3.104 -8.374,-3.952 -12.441,0.361c-3.003,3.185 -4.691,7.101 -4.647,11.502c0.057,5.679 1.696,10.943 5.001,15.629c2.474,4.007 5.76,7.16 9.901,9.719c2.297,-3.261 4.164,-6.449 5.243,-10.06zm125.196,-5.674c-5.95,1.56 -10.071,5.141 -13.571,9.623c-2.34,2.996 -2.873,6.522 -2.83,10.114c0.026,2.222 0.639,4.441 1.956,6.347c1.279,1.852 2.618,2.387 4.71,1.598c1.375,-0.518 2.773,-1.183 3.906,-2.097c6.705,-5.413 8.435,-12.613 7.132,-20.786c-0.256,-1.601 -0.846,-3.15 -1.303,-4.799z" fill="#5D859D" id="svg_20"/>
|
||||
<path d="m0,273.142881c1.516,0.161 2.401,-0.958 3.409,-1.75c4.879,-3.836 9.759,-7.671 14.564,-11.598c1.717,-1.404 3.226,-3.064 4.825,-4.612c0.713,-0.69 1.414,-1.392 2.12,-2.089c0.999,0.956 2.174,0.507 3.297,0.397c2.798,-0.273 5.591,-0.608 8.392,-0.847c3.306,-0.282 6.367,0.969 9.522,1.631c-1.72,2.438 -3.515,4.828 -5.144,7.325c-2.406,3.687 -4.811,7.384 -7.008,11.196c-4.473,7.761 -7.457,16.109 -9.313,24.86c-0.204,0.963 -0.116,1.988 -0.163,2.985c-8.167,0 -16.333,0 -24.5,0c-0.001,-9.165 -0.001,-18.331 -0.001,-27.498z" fill="#B2CDDD" id="svg_24"/>
|
||||
<path d="m116.5,300.642881c1.442,-1.996 2.872,-4.002 4.328,-5.988c5.622,-7.665 10.77,-15.596 13.851,-24.685c0.335,-0.988 0.594,-2.002 0.997,-3.374c-4.141,0.762 -7.892,1.075 -11.605,-0.016c-2.444,-2.027 -4.795,-4.112 -5.824,-7.288c-2.189,-6.757 -0.875,-12.83 3.686,-18.161c2.091,-2.444 5.436,-2.988 8.48,-1.804c4.269,1.661 6.988,4.729 8.105,9.135c0.943,3.719 1.461,7.501 0.999,11.362c-0.087,0.726 -0.013,1.471 -0.013,2.42c0.622,-0.123 1.191,-0.128 1.66,-0.349c1.127,-0.53 2.259,-1.079 3.295,-1.764c5.63,-3.723 11.032,-7.753 15.879,-12.467c8.534,-8.302 18.932,-11.766 30.552,-10.834c8.313,0.666 15.79,3.901 21.214,10.745c0.508,0.641 1.185,1.147 1.784,1.715c1.658,2.364 3.474,4.635 4.939,7.113c3.137,5.308 4.259,11.225 4.77,17.286c-0.202,4.143 -0.24,8.303 -0.649,12.426c-0.482,4.859 -1.285,9.687 -1.949,14.528c-0.667,0 -1.333,0 -2,0c0.064,-0.902 0.077,-1.811 0.199,-2.704c0.954,-6.964 1.934,-13.924 1.911,-20.977c0.208,-6.682 -0.946,-13.105 -4.11,-19.02c-1.279,-2.39 -3.064,-4.516 -4.683,-6.715c-0.682,-0.926 -1.53,-1.731 -2.303,-2.59c-1.733,-2.164 -3.651,-4.181 -6.227,-5.273c-3.279,-1.39 -6.602,-2.749 -10.019,-3.724c-4.067,-1.161 -8.247,-0.643 -12.392,-0.074c-6.704,0.92 -12.55,3.703 -17.688,8.031c-3.75,3.16 -7.454,6.375 -11.178,9.566c-2.626,1.876 -5.196,3.838 -7.899,5.595c-1.447,0.941 -3.082,1.635 -4.707,2.241c-1.087,0.405 -1.582,1.099 -1.872,2.113c-2.892,10.127 -8.056,19.075 -14.302,27.453c-1.535,2.058 -3.152,4.055 -4.731,6.079c-0.831,-0.001 -1.665,-0.001 -2.498,-0.001zm21.085,-44.076c-0.226,-1.841 -0.346,-3.67 -0.69,-5.455c-0.702,-3.64 -2.383,-6.701 -5.587,-8.784c-3.248,-2.112 -6.703,-1.67 -8.715,1.513c-2.711,4.287 -3.974,9.005 -2.481,14.042c1.149,3.876 3.627,6.344 7.888,6.96c2.418,0.349 4.596,-0.35 6.873,-0.68c1.263,-0.183 1.866,-0.953 2.018,-2.163c0.228,-1.812 0.463,-3.622 0.694,-5.433z" fill="#5E879F" id="svg_33"/>
|
||||
<path d="m221.11,276.961881c0.023,7.053 -0.957,14.013 -1.911,20.977c-0.123,0.894 -0.136,1.803 -0.199,2.704c-5.167,0 -10.333,0 -15.5,0c0.603,-1.321 1.076,-2.724 1.839,-3.944c1.218,-1.946 2.599,-3.795 3.969,-5.642c1.561,-2.105 3.2,-4.153 4.778,-6.247c0.796,-1.057 1.59,-2.123 2.282,-3.248c1.197,-1.945 2.92,-3.316 4.742,-4.6z" fill="#DAF1FF" id="svg_34"/>
|
||||
<path d="m508.133,227.129881c2.977,4.682 6.698,8.658 11.563,11.948c-0.643,0.857 -1.196,1.643 -1.799,2.388c-4.06,5.019 -8.836,9.216 -14.251,12.749c-3.794,2.475 -7.869,4.315 -12.08,5.845c-8.754,3.18 -16.962,1.421 -24.394,-3.67c-4.084,-2.797 -7.631,-6.378 -11.421,-9.605c0.51,-0.232 1.208,-0.327 1.499,-0.721c1.406,-1.904 3.562,-2.659 5.458,-3.826c0.553,-0.341 0.885,-1.041 1.319,-1.576c2.188,-1.133 4.354,-2.31 6.567,-3.391c5.425,-2.651 10.837,-5.319 16.666,-7.044c1.654,-0.49 3.333,-0.605 4.974,-0.974c5.244,-1.179 10.582,-1.552 15.899,-2.123z" fill="#FEDBD0" id="svg_39"/>
|
||||
<path d="m586.228,224.746881c-3.488,2.388 -6.878,4.937 -10.487,7.127c-5.47,3.32 -11.172,6.215 -17.151,8.551c-10.612,4.146 -21.13,3.656 -31.595,-0.488c-0.995,-0.394 -1.908,-0.993 -3.08,-1.615c0.824,-1.313 1.713,-2.492 2.352,-3.795c1.274,-2.598 2.411,-5.263 3.604,-7.9c5.575,0.006 11.151,0.061 16.726,-0.003c2.524,-0.029 5.044,-0.372 7.568,-0.427c1.561,-0.034 3.128,0.21 4.693,0.321c0.579,0.041 1.161,0.112 1.738,0.083c2.645,-0.133 5.286,-0.381 7.931,-0.43c5.194,-0.097 10.349,-0.52 15.467,-1.418c0.723,-0.127 1.488,-0.01 2.234,-0.006z" fill="#FEDAD0" id="svg_40"/>
|
||||
<path d="m595.508,185.574881c1.815,-0.428 3.265,-0.768 4.714,-1.114c1.426,-0.341 2.629,-0.045 3.518,1.197c0.932,1.301 0.614,2.648 -0.249,3.691c-1.258,1.521 -2.76,2.843 -4.187,4.221c-0.475,0.459 -1.03,0.837 -1.562,1.234c-0.678,0.505 -1.005,1.152 -0.635,1.948c0.77,1.659 0.227,2.937 -1.15,3.892c-1.168,0.81 -2.502,0.376 -4.104,-1.156c-0.049,-0.047 -0.145,-0.046 -0.414,-0.123c-1.114,0.772 -2.349,1.596 -3.549,2.469c-1.042,0.758 -2.035,0.944 -3.203,0.204c-1.139,-0.721 -1.505,-1.711 -1.397,-2.964c0.128,-1.487 0.271,-2.972 0.428,-4.683c-0.666,-0.273 -1.329,-0.554 -1.999,-0.816c-0.692,-0.271 -1.439,-0.442 -2.076,-0.806c-0.871,-0.497 -1.611,-2.212 -1.29,-3.033c0.402,-1.03 1.054,-1.877 2.306,-2.007c1.484,-0.154 2.961,-0.413 4.449,-0.496c1.31,-0.073 2.229,-0.56 2.663,-1.83c0.614,-1.798 1.3,-3.576 1.818,-5.402c0.548,-1.934 1.235,-2.706 2.722,-2.679c1.553,0.028 2.468,0.811 2.643,2.367c0.21,1.888 0.358,3.784 0.554,5.886z" fill="#57829B" id="svg_41"/>
|
||||
<path d="m715.687,156.829881c0.347,1.587 -0.356,2.857 -1.195,4.107c-0.56,0.834 -1.247,1.27 -2.278,1.028c-0.946,-0.222 -1.79,-0.693 -1.891,-1.687c-0.104,-1.019 0.107,-2.071 0.18,-3.108c0.053,-0.063 0.104,-0.129 0.16,-0.19c2.162,-2.296 2.731,-2.313 5.024,-0.15z" fill="#648CA3" id="svg_57"/>
|
||||
<path d="m706.431,153.888881c-1.644,0.036 -3.161,-1.439 -3.192,-3.104c-0.028,-1.499 1.198,-2.548 3.001,-2.567c1.618,-0.017 3.153,1.284 3.199,2.789c0.058,1.866 -1.115,2.593 -3.008,2.882z" fill="#6F92A7" id="svg_58"/>
|
||||
<path d="m703.429,159.265881c1.516,3.137 0.082,5.365 -3.374,5.289c-1.177,-0.026 -1.923,-0.435 -1.971,-1.714c-0.028,-0.747 -0.074,-1.493 -0.112,-2.241c1.107,-1.124 2.482,-1.565 4.025,-1.576c0.175,0.01 0.35,0.019 0.525,0.029c0.302,0.072 0.604,0.142 0.907,0.213z" fill="#6F92A7" id="svg_61"/>
|
||||
<path d="m702.521,159.053881c-0.175,-0.009 -0.35,-0.019 -0.525,-0.029c0.175,0.009 0.35,0.019 0.525,0.029z" fill="#FEDBD0" id="svg_63"/>
|
||||
<path d="m223.597,273.687881c-0.512,-6.061 -1.633,-11.978 -4.77,-17.286c-1.465,-2.479 -3.282,-4.749 -4.939,-7.113c4.654,-0.328 9.281,-0.385 13.788,1.146c0.932,0.317 1.925,0.51 2.906,0.615c4.351,0.469 7.965,2.621 11.503,4.953c1.017,0.67 1.987,1.413 2.979,2.123c-0.421,0.461 -0.758,1.086 -1.279,1.356c-4.186,2.163 -7.941,4.946 -11.694,7.767c-2.3,1.728 -4.625,3.375 -6.56,5.537c-0.438,0.486 -1.279,0.611 -1.934,0.902z" fill="#B2CDDD" id="svg_64"/>
|
||||
<path d="m335.508,275.129881c-2.846,4.805 -6.708,8.808 -10.273,13.047c-0.69,0.82 -1.222,1.771 -1.845,2.649c-1.402,1.974 -3.702,2.424 -5.65,1.118c-1.385,-0.929 -1.593,-4.288 -0.312,-5.917c0.87,-1.105 1.82,-2.147 2.752,-3.203c2.797,-3.169 5.596,-6.336 8.407,-9.493c0.802,-0.9 1.646,-1.763 2.471,-2.642c0.73,-0.128 1.455,-0.312 2.191,-0.373c1.829,-0.152 2.838,0.825 2.697,2.636c-0.056,0.733 -0.286,1.452 -0.438,2.178z" fill="#B4CFDE" id="svg_66"/>
|
||||
<path d="m365.213,292.639881c-2.923,0.011 -4.061,-1.133 -3.662,-3.836c0.188,-1.269 0.596,-2.692 1.385,-3.648c2.256,-2.732 4.501,-5.508 7.436,-7.601c2.437,-1.738 4.98,-1.037 5.309,1.618c0.102,0.82 -0.158,1.866 -0.631,2.545c-2.263,3.244 -4.637,6.413 -7.017,9.575c-0.726,0.965 -1.689,1.614 -2.82,1.347z" fill="#B7D0DE" id="svg_68"/>
|
||||
<path d="m345.5,268.101881c-2.654,0.057 -4.406,-1.231 -4.777,-3.208c-0.12,-0.641 0.009,-1.577 0.414,-2.029c2.596,-2.899 5.285,-5.716 7.986,-8.519c0.445,-0.463 1.105,-0.714 1.651,-1.085c1.131,-0.771 2.18,-0.518 3.214,0.206c1.045,0.731 1.135,1.807 0.959,2.898c-0.359,2.215 -1.79,3.89 -3.09,5.585c-1.255,1.636 -2.622,3.204 -4.082,4.659c-0.761,0.758 -1.828,1.209 -2.275,1.493z" fill="#B7D0DE" id="svg_69"/>
|
||||
<path d="m362.953,268.123881c-2.726,-0.059 -4.533,-2.69 -3.744,-5.101c0.43,-1.314 1.096,-2.661 2.005,-3.683c1.368,-1.538 2.89,-3.059 4.636,-4.101c1.254,-0.749 3.001,-1.022 4.494,-0.932c1.821,0.11 2.8,2.638 1.747,4.331c-0.991,1.594 -1.889,3.258 -3.578,4.394c-1.045,0.703 -1.697,2 -2.499,3.05c-0.892,1.168 -1.963,2 -3.061,2.042z" fill="#B7D0DE" id="svg_70"/>
|
||||
<path d="m346.953,290.630881c-0.389,0.126 -0.788,0.224 -1.164,0.382c-1.732,0.724 -3.623,0.453 -4.766,-0.701c-0.755,-0.762 -0.782,-3.373 0.021,-4.694c0.423,-0.696 0.978,-1.312 1.474,-1.965c0.473,-0.289 1.11,-0.468 1.391,-0.887c1.734,-2.582 4.36,-3.985 6.856,-5.494c2.881,1.421 3.464,2.328 2.74,4.45c-0.288,0.844 -0.712,1.688 -1.258,2.389c-1.717,2.21 -3.522,4.352 -5.294,6.52z" fill="#B7D0DE" id="svg_71"/>
|
||||
<path d="m57.51,258.254881c-0.725,0.387 -1.413,0.888 -2.182,1.141c-5.545,1.826 -10.189,5.148 -14.687,8.726c-0.315,0.251 -0.713,0.398 -1.073,0.593c-0.061,-0.064 -0.122,-0.126 -0.184,-0.188c0.378,-0.802 0.647,-1.682 1.152,-2.394c2.752,-3.881 5.562,-7.72 8.352,-11.574c0.196,1.12 1.068,0.99 1.89,1.103c2.438,0.336 4.816,0.879 6.732,2.593z" fill="#B2CDDD" id="svg_72"/>
|
||||
<path d="m270.464,237.841881c-0.074,1.702 -1.733,3.845 -3.338,4.195c-1.004,0.219 -1.98,0.172 -2.694,-0.786c-0.887,-1.19 -1.42,-2.497 -0.845,-3.92c0.328,-0.812 0.93,-1.582 1.586,-2.177c0.994,-0.901 2.15,-1.678 3.602,-0.953c1.113,0.558 1.759,2.025 1.689,3.641z" fill="#5E879F" id="svg_77"/>
|
||||
<path d="m286.407,233.645881c-0.038,2.102 -1.875,3.799 -4.097,3.782c-1.9,-0.014 -3.265,-1.56 -3.191,-3.616c0.07,-1.971 2.163,-4.021 4.041,-3.957c1.579,0.054 3.278,2.038 3.247,3.791z" fill="#5D859D" id="svg_79"/>
|
||||
<path d="m265.936,251.172881c0.663,1.477 -0.088,2.766 -0.685,3.985c-0.662,1.351 -3.405,1.914 -4.439,0.312c-0.486,-0.754 -0.896,-1.557 -1.34,-2.338c0.027,-0.161 0.077,-0.321 0.078,-0.482c0.005,-1.561 0.793,-2.748 2.171,-3.216c1.382,-0.47 2.73,0.014 3.716,1.205c0.155,0.188 0.332,0.357 0.499,0.534z" fill="#5E879F" id="svg_81"/>
|
||||
<path d="m298.765,241.316881c1.714,-0.007 3.078,1.376 3.141,3.186c0.065,1.862 -1.575,3.436 -3.519,3.378c-1.716,-0.052 -3.134,-1.535 -3.102,-3.246c0.029,-1.599 1.824,-3.311 3.48,-3.318z" fill="#5E879F" id="svg_82"/>
|
||||
<path d="m282.445,251.329881c0.463,0.342 0.949,0.658 1.385,1.031c1.197,1.024 1.613,2.333 1.157,3.529c-0.5,1.314 -2.117,2.391 -3.526,2.301c-1.144,-0.073 -2.149,-0.514 -2.592,-1.627c-0.515,-1.295 -0.507,-2.629 0.49,-3.73c0.49,-0.54 1.145,-0.931 1.725,-1.388c0.454,-0.038 0.908,-0.077 1.361,-0.116z" fill="#5E879F" id="svg_83"/>
|
||||
<path d="m275.434,250.637881c-0.544,1.018 -0.621,2.398 -2.288,2.46c-1.627,0.06 -2.849,-0.425 -3.551,-1.953c-0.009,-0.574 -0.08,-1.155 -0.016,-1.721c0.219,-1.933 1.62,-3.182 3.424,-3.108c1.562,0.064 2.446,1.248 2.477,3.336c0.005,0.328 -0.03,0.657 -0.046,0.986z" fill="#648CA3" id="svg_85"/>
|
||||
<path d="m291.468,251.583881c-1.29,0.487 -2.489,0.34 -3.583,-0.518c-0.97,-2.772 -0.614,-3.836 1.427,-5.153c1.211,-0.782 2.179,-0.419 3.161,0.28c1.001,0.713 1.325,1.786 0.845,2.838c-0.429,0.938 -1.219,1.709 -1.85,2.553z" fill="#648CA3" id="svg_86"/>
|
||||
<path d="m281.534,241.295881c1.544,-0.012 2.727,1.275 2.695,2.933c-0.03,1.558 -1.463,2.836 -3.168,2.825c-1.485,-0.01 -2.853,-1.174 -2.926,-2.491c-0.089,-1.609 1.621,-3.253 3.399,-3.267z" fill="#648CA3" id="svg_87"/>
|
||||
<path d="m293.736,231.754881c1.644,0.208 2.646,1.505 2.404,3.111c-0.277,1.838 -1.463,3.036 -2.797,2.827c-1.772,-0.278 -3.009,-1.846 -2.752,-3.487c0.254,-1.615 1.58,-2.649 3.145,-2.451z" fill="#648CA3" id="svg_88"/>
|
||||
<path d="m593.498,223.123881c2.643,-1.967 5.188,-4.085 7.95,-5.869c4.826,-3.116 10.023,-5.563 15.496,-7.29c2.742,-0.865 5.661,-1.167 8.5,-1.723c-2.26,1.482 -4.49,3.011 -6.786,4.435c-5.762,3.575 -11.658,6.908 -18.194,8.877c-2.273,0.686 -4.641,1.056 -6.966,1.57z" fill="#FDFDFE" id="svg_122"/>
|
||||
<path d="m676.375,263.677881c-1.33,-0.165 -2.321,-0.213 -3.273,-0.429c-0.848,-0.193 -1.521,-0.721 -1.505,-1.71c0.016,-1.015 0.557,-1.935 1.518,-1.987c2.362,-0.128 4.745,-0.09 7.104,0.089c0.519,0.039 1.342,0.936 1.355,1.454c0.014,0.556 -0.651,1.434 -1.215,1.641c-1.365,0.5 -2.844,0.691 -3.984,0.942z" fill="#EDD1CA" id="svg_131"/>
|
||||
<path d="m707.815,258.044881c1.271,0.174 2.745,0.353 4.21,0.585c0.81,0.129 1.39,0.567 1.359,1.482c-0.03,0.897 -0.622,1.374 -1.424,1.445c-2.382,0.211 -4.77,0.363 -7.159,0.498c-1.207,0.068 -1.898,-0.673 -2.005,-1.764c-0.103,-1.041 0.632,-1.708 1.602,-1.897c1.047,-0.203 2.13,-0.226 3.417,-0.349z" fill="#EDD1CA" id="svg_132"/>
|
||||
<path d="m726.254,240.603881c-1.297,-0.094 -2.132,-0.084 -2.94,-0.23c-1.079,-0.195 -1.634,-1.103 -1.446,-2.009c0.133,-0.642 0.94,-1.549 1.532,-1.621c2.211,-0.265 4.457,-0.303 6.689,-0.279c0.819,0.009 1.835,0.469 1.701,1.451c-0.092,0.674 -0.755,1.594 -1.369,1.807c-1.466,0.51 -3.055,0.666 -4.167,0.881z" fill="#EDD1CA" id="svg_133"/>
|
||||
<path d="m698.203,251.614881c-0.58,0 -1.209,0.142 -1.727,-0.036c-0.633,-0.219 -1.599,-0.652 -1.655,-1.091c-0.087,-0.684 0.341,-1.55 0.802,-2.145c0.295,-0.381 1.033,-0.548 1.585,-0.577c1.567,-0.083 3.154,-0.164 4.705,0.005c0.566,0.062 1.292,0.731 1.526,1.297c0.365,0.882 -0.206,1.686 -1.088,1.923c-1.345,0.361 -2.742,0.525 -4.117,0.773c-0.01,-0.05 -0.02,-0.1 -0.031,-0.149z" fill="#EDD1CA" id="svg_134"/>
|
||||
<path d="m715.076,280.980881c-1.135,0 -1.906,0.215 -2.424,-0.055c-0.648,-0.338 -1.446,-1.033 -1.518,-1.651c-0.072,-0.612 0.568,-1.78 1.087,-1.904c1.89,-0.452 3.887,-1.119 5.789,-0.143c0.643,0.33 1.382,1.06 1.491,1.706c0.174,1.034 -0.71,1.569 -1.714,1.707c-1.056,0.147 -2.116,0.266 -2.711,0.34z" fill="#EDD1CA" id="svg_135"/>
|
||||
<path d="m652.969,279.278881c-1.25,-0.064 -2.178,-0.031 -3.074,-0.181c-0.87,-0.146 -1.738,-0.696 -1.508,-1.636c0.165,-0.674 0.957,-1.671 1.5,-1.692c2.221,-0.083 4.462,0.08 6.676,0.329c0.48,0.054 1.229,0.845 1.219,1.282c-0.011,0.471 -0.695,1.191 -1.214,1.333c-1.265,0.348 -2.607,0.425 -3.599,0.565z" fill="#EDD1CA" id="svg_136"/>
|
||||
<path d="m709.683,290.088881c0.787,0.211 1.882,0.354 2.833,0.801c1.011,0.476 1.098,1.653 0.147,2.201c-1.596,0.919 -3.36,1.443 -5.244,1.002c-0.962,-0.225 -1.728,-0.773 -1.698,-1.861c0.029,-1.04 0.724,-1.662 1.699,-1.888c0.64,-0.148 1.313,-0.154 2.263,-0.255z" fill="#EDD1CA" id="svg_137"/>
|
||||
<path d="m735.998,252.586881c-0.79,-0.092 -1.214,-0.085 -1.604,-0.198c-1.005,-0.291 -1.885,-0.81 -1.904,-1.985c-0.02,-1.232 0.879,-1.808 1.937,-1.817c1.452,-0.012 2.972,0.005 4.34,0.418c1.423,0.429 1.466,1.845 0.137,2.548c-0.995,0.527 -2.14,0.771 -2.906,1.034z" fill="#EDD1CA" id="svg_138"/>
|
||||
<path d="m736.343,271.796881c-2.273,-0.125 -3.097,-0.611 -3.183,-1.997c-0.078,-1.265 1.025,-2.049 3.079,-2.046c0.969,0.001 1.987,0.067 2.888,0.376c0.505,0.173 1.16,0.925 1.134,1.38c-0.034,0.591 -0.556,1.402 -1.089,1.659c-0.846,0.409 -1.876,0.438 -2.829,0.628z" fill="#EDD1CA" id="svg_139"/>
|
||||
<path d="m685.329,281.473881c-0.519,-0.091 -1.172,-0.177 -1.811,-0.324c-0.899,-0.207 -1.443,-0.763 -1.482,-1.72c-0.039,-0.96 0.485,-1.524 1.335,-1.857c1.168,-0.457 4.739,0.394 5.578,1.336c0.727,0.816 0.512,1.843 -0.548,2.125c-0.947,0.252 -1.951,0.289 -3.072,0.44z" fill="#EDD1CA" id="svg_140"/>
|
||||
<path d="m740.005,293.233881c-1.484,2.038 -4.82,2.573 -6.338,1.091c-0.458,-0.447 -0.802,-1.318 -0.712,-1.921c0.068,-0.455 0.896,-1.103 1.414,-1.13c2.041,-0.107 4.097,-0.01 5.636,1.96z" fill="#EDD1CA" id="svg_141"/>
|
||||
<path d="m676.726,294.574881c-1.711,-0.04 -3.084,-1.214 -3.063,-2.22c0.021,-0.962 1.182,-1.772 2.604,-1.715c0.987,0.039 2.016,0.066 2.934,0.373c0.515,0.172 1.134,0.88 1.173,1.388c0.036,0.475 -0.525,1.241 -1.011,1.458c-0.96,0.43 -2.05,0.569 -2.637,0.716z" fill="#EDD1CA" id="svg_142"/>
|
||||
<path d="m645.368,290.254881c0.633,0.063 1.151,0.028 1.608,0.179c0.814,0.269 1.658,0.774 1.413,1.727c-0.148,0.577 -0.82,1.246 -1.4,1.436c-0.992,0.324 -2.103,0.442 -3.152,0.39c-0.94,-0.046 -1.703,-0.571 -1.744,-1.718c-0.038,-1.047 0.646,-1.474 1.465,-1.719c0.622,-0.188 1.291,-0.216 1.81,-0.295z" fill="#EDD1CA" id="svg_143"/>
|
||||
<path d="m274.546,264.670881c-0.023,2.518 -1.747,4.264 -4.225,4.279c-1.497,0.009 -2.755,-1.173 -2.755,-2.588c0,-2.289 2.2,-4.961 4.097,-4.975c1.785,-0.014 2.901,1.257 2.883,3.284z" fill="#5C869F" id="svg_145"/>
|
||||
<path d="m300.5,258.759881c-0.01,1.443 -1.51,2.701 -3.254,2.729c-2.272,0.036 -3.682,-1.048 -3.628,-2.788c0.068,-2.185 1.488,-3.76 3.444,-3.82c1.704,-0.053 3.451,1.919 3.438,3.879z" fill="#5C869F" id="svg_146"/>
|
||||
<path d="m288.982,264.797881c-0.042,2.309 -0.991,3.358 -2.981,3.296c-1.448,-0.045 -2.113,-0.562 -2.702,-2.102c-0.46,-1.2 -0.029,-2.816 1.055,-3.791c0.917,-0.825 2,-1.185 3.198,-0.77c1.111,0.384 1.468,1.319 1.43,3.367z" fill="#5E879F" id="svg_147"/>
|
||||
<path d="m54.996,243.147881c-0.296,0.503 -0.591,1.005 -1.117,1.898c-0.385,-1.33 0.001,-1.917 0.546,-2.46c0.191,0.188 0.381,0.375 0.571,0.562z" fill="#B7D0DE" id="svg_148"/>
|
||||
<path d="m54.996,243.147881c-0.19,-0.187 -0.381,-0.375 -0.571,-0.562c0.19,0.188 0.381,0.375 0.571,0.562z" fill="#FFF6F3" id="svg_149"/>
|
||||
<path d="m85.732,206.903881c-0.237,0.025 -0.475,0.049 -0.712,0.074c0.092,-0.177 0.185,-0.353 0.276,-0.531c0.145,0.152 0.291,0.304 0.436,0.457z" fill="#FFF6F3" id="svg_150"/>
|
||||
<path d="m38.938,269.085881c0.15,-0.185 0.299,-0.37 0.447,-0.557c0.061,0.06 0.122,0.123 0.183,0.187c-0.174,0.159 -0.349,0.318 -0.523,0.477l-0.107,-0.107z" fill="#B2CDDD" id="svg_151"/>
|
||||
<path d="m39.045,269.192881c-0.085,0.101 -0.169,0.202 -0.254,0.303c0.049,-0.136 0.099,-0.272 0.148,-0.409c-0.001,-0.001 0.106,0.106 0.106,0.106z" fill="#B2CDDD" id="svg_152"/>
|
||||
<path d="m152.512,257.161881c3.724,-3.191 7.427,-6.406 11.178,-9.566c5.138,-4.329 10.983,-7.111 17.688,-8.031c4.144,-0.569 8.325,-1.086 12.392,0.074c3.417,0.975 6.74,2.334 10.019,3.724c2.576,1.092 4.493,3.109 6.227,5.273c-0.363,0.164 -0.719,0.451 -1.09,0.473c-3.391,0.201 -6.791,0.268 -10.175,0.546c-4.61,0.379 -9.215,0.848 -13.808,1.394c-3.524,0.419 -7.03,0.992 -10.537,1.545c-3.095,0.488 -6.184,1.022 -9.267,1.587c-0.554,0.102 -1.122,0.37 -1.581,0.702c-1.272,0.919 -2.694,1.102 -4.212,1.261c-1.542,0.161 -3.047,0.703 -4.557,1.123c-0.786,0.218 -1.553,0.736 -2.277,-0.105z" fill="#FCF4F2" id="svg_153"/>
|
||||
<path d="m511.621,227.118881c-3.305,-4.685 -4.944,-9.95 -5.001,-15.629c-0.044,-4.401 1.644,-8.317 4.647,-11.502c4.067,-4.313 8.555,-3.465 12.441,-0.361c3.542,2.83 5.056,6.77 5.474,11.091c0.534,5.52 -0.699,10.843 -2.418,16.059c-0.983,-0.041 -1.967,-0.142 -2.947,-0.111c-4.066,0.131 -8.131,0.299 -12.196,0.453z" fill="#FDFDFE" id="svg_154"/>
|
||||
<path d="m651.961,221.104881c0.457,1.649 1.046,3.198 1.302,4.8c1.302,8.173 -0.427,15.373 -7.132,20.786c-1.133,0.914 -2.531,1.579 -3.906,2.097c-2.092,0.788 -3.43,0.253 -4.71,-1.598c-1.317,-1.906 -1.929,-4.125 -1.956,-6.347c-0.043,-3.592 0.49,-7.119 2.83,-10.114c3.502,-4.483 7.622,-8.064 13.572,-9.624z" fill="#FCDACF" id="svg_155"/>
|
||||
<path d="m511.621,227.118881c4.065,-0.154 8.13,-0.323 12.197,-0.451c0.98,-0.031 1.964,0.07 2.947,0.111c-1.078,3.612 -2.945,6.8 -5.242,10.06c-4.142,-2.56 -7.428,-5.713 -9.902,-9.72z" fill="#FCDACF" id="svg_156"/>
|
||||
<path d="m137.585,256.566881c-0.232,1.81 -0.466,3.621 -0.694,5.431c-0.152,1.21 -0.755,1.98 -2.018,2.163c-2.277,0.33 -4.455,1.03 -6.873,0.68c-4.262,-0.616 -6.739,-3.083 -7.888,-6.96c-1.494,-5.037 -0.23,-9.754 2.481,-14.042c2.013,-3.184 5.467,-3.625 8.715,-1.513c3.204,2.084 4.885,5.144 5.587,8.784c0.345,1.788 0.465,3.616 0.69,5.457z" fill="#FCF4F2" id="svg_163"/>
|
||||
<g id="BACKGROUND"/>
|
||||
<g id="svg_1">
|
||||
<path d="m623,300.642881c-63.833,0 -127.667,0 -191.5,0c0.109,-1.4 0.038,-2.844 0.365,-4.191c0.775,-3.196 1.715,-6.353 2.636,-9.511c0.602,-2.062 1.084,-4.195 1.979,-6.129c2.321,-5.011 3.543,-10.485 6.466,-15.235c0.126,-0.205 0.181,-0.471 0.207,-0.716c0.161,-1.56 1.197,-2.67 1.938,-3.947c1.587,-2.735 3.482,-5.229 5.48,-7.668c1.135,-1.386 2.09,-2.918 3.127,-4.385c0.878,0.766 1.792,1.494 2.627,2.303c3.598,3.486 7.44,6.677 11.787,9.17c6.411,3.678 13.171,5.353 20.6,3.493c4.237,-1.061 8.191,-2.676 12.041,-4.69c7.177,-3.755 13.324,-8.74 18.465,-14.989c0.938,-1.14 1.95,-2.218 2.932,-3.33c1.704,0.772 3.2,1.503 4.737,2.135c5.842,2.405 11.935,3.647 18.246,3.4c5.454,-0.214 10.695,-1.651 15.701,-3.762c10.855,-4.578 21.031,-10.344 30.219,-17.776c0.768,-0.622 1.628,-1.13 2.446,-1.692c2.325,-0.514 4.693,-0.884 6.966,-1.569c6.536,-1.969 12.432,-5.303 18.194,-8.877c2.296,-1.424 4.526,-2.953 6.786,-4.435c2.576,-0.065 5.17,-0.355 7.723,-0.145c4.5,0.369 8.904,1.307 12.737,3.908c2.238,1.519 3.939,3.496 4.736,6.04c-0.14,0.221 -0.167,0.323 -0.227,0.349c-1.063,0.465 -2.143,0.893 -3.194,1.383c-6.055,2.821 -10.479,7.337 -13.259,13.349c-2.424,5.241 -1.382,10.402 1.047,15.386c1.19,2.441 3.62,3.714 6.339,3.293c4.946,-0.766 8.461,-3.775 11.243,-7.64c3.751,-5.209 4.589,-11.135 3.601,-17.373c-0.335,-2.116 -0.785,-4.215 -1.232,-6.582c4.976,-0.537 9.83,-1.131 14.698,-1.569c6.83,-0.614 13.517,-1.964 19.931,-4.322c7.326,-2.694 14.531,-5.719 21.76,-8.672c7.81,-3.19 14.893,-7.747 22.164,-11.949c6.273,-3.625 11.789,-8.086 15.927,-14.114c0.129,-0.188 0.372,-0.296 0.562,-0.442c0,14.833 0,29.667 0,44.5c-2.472,0.148 -4.942,0.406 -7.415,0.423c-6.998,0.048 -13.85,1.277 -20.66,2.647c-6.182,1.243 -12.155,3.319 -18.031,5.599c-4.17,1.619 -8.171,3.69 -12.377,5.197c-6.222,2.23 -11.549,6.109 -17.404,9.01c-6.341,3.141 -11.549,7.948 -17.087,12.288c-6.142,4.813 -11.179,10.666 -16.643,16.128c-2.606,2.605 -4.496,5.696 -6.591,8.626c-3.688,5.156 -7.004,10.578 -10.453,15.904c-0.214,0.329 -0.232,0.783 -0.34,1.179z" fill="#FEDBD0" id="svg_4"/>
|
||||
<path d="m623,300.642881c0.108,-0.396 0.126,-0.85 0.338,-1.178c3.449,-5.325 6.766,-10.747 10.453,-15.904c2.095,-2.93 3.985,-6.021 6.591,-8.626c5.464,-5.462 10.501,-11.315 16.643,-16.128c5.538,-4.34 10.746,-9.147 17.087,-12.288c5.855,-2.901 11.182,-6.78 17.404,-9.01c4.206,-1.507 8.207,-3.578 12.377,-5.197c5.876,-2.281 11.849,-4.356 18.031,-5.599c6.81,-1.37 13.662,-2.598 20.66,-2.647c2.472,-0.017 4.943,-0.275 7.415,-0.423c0,25.667 0,51.333 0,77c-42.332,0 -84.666,0 -126.999,0zm53.375,-36.965c1.139,-0.251 2.619,-0.443 3.985,-0.943c0.563,-0.206 1.229,-1.084 1.215,-1.641c-0.013,-0.519 -0.836,-1.415 -1.355,-1.454c-2.359,-0.18 -4.741,-0.217 -7.104,-0.089c-0.962,0.052 -1.502,0.973 -1.518,1.987c-0.016,0.99 0.656,1.517 1.505,1.71c0.952,0.218 1.942,0.265 3.272,0.43zm31.44,-5.633c-1.287,0.123 -2.369,0.146 -3.416,0.349c-0.971,0.189 -1.705,0.856 -1.602,1.897c0.107,1.09 0.798,1.832 2.005,1.764c2.388,-0.135 4.776,-0.288 7.159,-0.498c0.801,-0.071 1.394,-0.547 1.424,-1.445c0.031,-0.915 -0.549,-1.353 -1.359,-1.482c-1.467,-0.232 -2.94,-0.411 -4.211,-0.585zm18.439,-17.441c1.112,-0.215 2.701,-0.371 4.167,-0.88c0.615,-0.213 1.278,-1.134 1.369,-1.807c0.134,-0.982 -0.882,-1.442 -1.701,-1.451c-2.232,-0.024 -4.478,0.013 -6.689,0.279c-0.592,0.071 -1.399,0.978 -1.532,1.621c-0.188,0.906 0.367,1.814 1.446,2.009c0.808,0.145 1.643,0.135 2.94,0.229zm-28.051,11.011c0.01,0.05 0.02,0.1 0.03,0.149c1.375,-0.248 2.772,-0.412 4.117,-0.773c0.882,-0.237 1.453,-1.041 1.088,-1.923c-0.234,-0.566 -0.959,-1.235 -1.526,-1.297c-1.551,-0.169 -3.138,-0.088 -4.705,-0.005c-0.552,0.029 -1.29,0.196 -1.585,0.577c-0.46,0.595 -0.888,1.461 -0.802,2.145c0.056,0.438 1.022,0.872 1.655,1.091c0.52,0.178 1.149,0.036 1.728,0.036zm16.873,29.366c0.595,-0.074 1.654,-0.193 2.711,-0.339c1.004,-0.139 1.888,-0.673 1.714,-1.707c-0.109,-0.646 -0.848,-1.376 -1.491,-1.706c-1.902,-0.976 -3.9,-0.309 -5.789,0.143c-0.519,0.124 -1.159,1.292 -1.087,1.904c0.073,0.618 0.87,1.313 1.518,1.651c0.518,0.27 1.289,0.054 2.424,0.054zm-62.107,-1.702c0.992,-0.141 2.333,-0.217 3.6,-0.565c0.519,-0.142 1.203,-0.862 1.214,-1.333c0.01,-0.437 -0.739,-1.228 -1.219,-1.282c-2.214,-0.249 -4.455,-0.413 -6.676,-0.329c-0.544,0.02 -1.336,1.018 -1.5,1.692c-0.23,0.941 0.638,1.49 1.508,1.636c0.896,0.15 1.823,0.117 3.073,0.181zm56.714,10.81c-0.95,0.101 -1.623,0.107 -2.263,0.255c-0.974,0.227 -1.67,0.848 -1.699,1.888c-0.03,1.088 0.736,1.636 1.698,1.861c1.885,0.441 3.648,-0.083 5.244,-1.002c0.951,-0.548 0.863,-1.725 -0.147,-2.201c-0.951,-0.447 -2.046,-0.59 -2.833,-0.801zm26.315,-37.502c0.766,-0.263 1.911,-0.507 2.907,-1.034c1.329,-0.703 1.286,-2.119 -0.137,-2.548c-1.368,-0.413 -2.888,-0.43 -4.34,-0.418c-1.058,0.008 -1.957,0.585 -1.937,1.817c0.019,1.175 0.899,1.694 1.904,1.985c0.389,0.112 0.813,0.105 1.603,0.198zm0.345,19.21c0.952,-0.19 1.982,-0.219 2.829,-0.627c0.533,-0.258 1.055,-1.068 1.089,-1.659c0.026,-0.456 -0.629,-1.207 -1.134,-1.38c-0.902,-0.31 -1.919,-0.375 -2.888,-0.376c-2.054,-0.003 -3.157,0.781 -3.079,2.046c0.086,1.385 0.91,1.871 3.183,1.996zm-51.014,9.677c1.121,-0.151 2.125,-0.188 3.072,-0.44c1.06,-0.282 1.275,-1.309 0.548,-2.125c-0.839,-0.942 -4.41,-1.793 -5.578,-1.336c-0.85,0.332 -1.374,0.897 -1.335,1.857c0.039,0.958 0.583,1.513 1.482,1.72c0.639,0.147 1.292,0.233 1.811,0.324zm54.676,11.76c-1.539,-1.969 -3.594,-2.067 -5.636,-1.96c-0.518,0.027 -1.346,0.675 -1.414,1.13c-0.09,0.604 0.254,1.474 0.712,1.921c1.518,1.481 4.853,0.946 6.338,-1.091zm-63.279,1.341c0.587,-0.147 1.677,-0.286 2.637,-0.716c0.486,-0.218 1.047,-0.983 1.011,-1.458c-0.039,-0.507 -0.658,-1.215 -1.173,-1.388c-0.918,-0.307 -1.947,-0.334 -2.934,-0.373c-1.421,-0.057 -2.583,0.753 -2.604,1.715c-0.021,1.006 1.352,2.18 3.063,2.22zm-31.358,-4.32c-0.518,0.079 -1.188,0.107 -1.81,0.293c-0.819,0.245 -1.502,0.673 -1.465,1.719c0.041,1.148 0.804,1.672 1.744,1.718c1.049,0.052 2.16,-0.065 3.152,-0.39c0.58,-0.19 1.252,-0.858 1.4,-1.436c0.245,-0.953 -0.599,-1.458 -1.413,-1.727c-0.457,-0.149 -0.975,-0.114 -1.608,-0.177z" fill="#548099" id="svg_7"/>
|
||||
<path d="m346.953,290.630881c0.806,1.415 1.73,2.778 2.39,4.258c0.832,1.868 1.448,3.832 2.157,5.754c-43.5,0 -87,0 -130.5,0c0.664,-4.841 1.467,-9.669 1.949,-14.528c0.409,-4.123 0.447,-8.283 0.649,-12.426c0.655,-0.292 1.496,-0.416 1.932,-0.903c1.935,-2.161 4.26,-3.808 6.56,-5.537c3.753,-2.82 7.508,-5.604 11.694,-7.767c0.521,-0.269 0.858,-0.895 1.28,-1.356c1.969,-0.804 3.904,-1.705 5.914,-2.389c2.802,-0.953 5.66,-1.745 8.494,-2.605c0.444,0.781 0.854,1.584 1.34,2.338c1.034,1.602 3.777,1.039 4.439,-0.312c0.597,-1.219 1.348,-2.508 0.685,-3.985c1.22,-0.01 2.44,-0.019 3.659,-0.029c0.702,1.529 1.924,2.014 3.551,1.953c1.667,-0.062 1.744,-1.442 2.288,-2.46c1.394,-0.16 2.786,-0.341 4.183,-0.474c0.906,-0.086 1.406,0.366 1.468,1.282c-0.581,0.457 -1.236,0.848 -1.725,1.388c-0.997,1.1 -1.005,2.435 -0.49,3.73c0.443,1.113 1.448,1.554 2.592,1.627c1.41,0.09 3.026,-0.987 3.526,-2.301c0.455,-1.196 0.04,-2.505 -1.157,-3.529c-0.436,-0.373 -0.922,-0.689 -1.385,-1.031c0.254,-0.166 0.511,-0.479 0.762,-0.474c1.561,0.031 3.12,0.133 4.678,0.211c1.094,0.858 2.293,1.005 3.583,0.518c0.516,0.348 0.986,0.863 1.554,1.015c2.989,0.797 5.976,1.653 9.016,2.189c2.214,0.391 4.217,1.307 6.306,1.99c2.187,0.715 4.316,1.674 6.363,2.734c2.392,1.238 4.669,2.697 6.993,4.066c3.399,2.002 6.29,4.673 9.358,7.11c-0.825,0.879 -1.67,1.742 -2.471,2.642c-2.811,3.157 -5.61,6.324 -8.407,9.493c-0.931,1.055 -1.882,2.098 -2.752,3.203c-1.282,1.629 -1.073,4.988 0.312,5.917c1.948,1.307 4.248,0.857 5.65,-1.118c0.623,-0.878 1.155,-1.829 1.845,-2.649c3.565,-4.239 7.427,-8.242 10.273,-13.047c2.732,1.785 4.457,4.533 6.55,6.923c0.331,0.378 0.315,1.06 0.46,1.601c-0.496,0.652 -1.051,1.268 -1.474,1.965c-0.803,1.321 -0.775,3.932 -0.021,4.694c1.143,1.154 3.035,1.425 4.766,0.701c0.375,-0.158 0.775,-0.257 1.163,-0.382zm-72.407,-25.96c0.019,-2.027 -1.097,-3.298 -2.884,-3.284c-1.897,0.015 -4.097,2.687 -4.097,4.975c0,1.415 1.258,2.597 2.755,2.588c2.478,-0.015 4.202,-1.761 4.226,-4.279zm25.954,-5.911c0.013,-1.961 -1.734,-3.932 -3.438,-3.88c-1.956,0.061 -3.375,1.636 -3.444,3.82c-0.054,1.74 1.356,2.824 3.628,2.788c1.744,-0.026 3.244,-1.285 3.254,-2.728zm-11.518,6.038c0.038,-2.048 -0.319,-2.983 -1.43,-3.368c-1.198,-0.415 -2.281,-0.054 -3.198,0.77c-1.084,0.975 -1.515,2.591 -1.055,3.791c0.59,1.539 1.254,2.056 2.702,2.102c1.99,0.063 2.939,-0.986 2.981,-3.295z" fill="#DAF1FF" id="svg_9"/>
|
||||
<path d="m703.429,159.265881c0.502,-1.426 1.928,-1.133 2.946,-1.451c1.315,-0.41 2.746,-0.447 4.128,-0.646c-0.073,1.037 -0.284,2.089 -0.18,3.108c0.102,0.994 0.945,1.465 1.891,1.687c1.031,0.242 1.717,-0.193 2.278,-1.028c0.839,-1.249 1.542,-2.52 1.195,-4.107c2.698,-0.336 5.388,-0.789 8.096,-0.966c1.798,-0.118 3.675,0.481 5.423,0.193c4.629,-0.762 9.263,-0.294 13.889,-0.384c2.297,-0.044 4.602,0.301 6.904,0.47c0,6 0,12 0,18c-0.168,0.168 -0.379,0.31 -0.499,0.507c-3.606,5.916 -8.197,10.898 -14.1,14.566c-9.541,5.927 -19.201,11.641 -29.712,15.749c-4.875,1.905 -9.688,3.974 -14.602,5.768c-6.175,2.255 -12.495,3.985 -19.078,4.65c-6.024,0.609 -12.036,1.349 -18.086,2.036c-0.09,-0.077 -0.245,-0.163 -0.332,-0.294c-0.23,-0.345 -0.451,-0.7 -0.642,-1.069c-2.945,-5.662 -7.74,-8.766 -13.872,-9.97c-2.517,-0.494 -5.059,-0.863 -7.59,-1.289c0.122,-1.386 1.372,-1.852 2.257,-2.453c3.107,-2.111 5.83,-4.582 8.397,-7.337c2.64,-2.832 5.637,-5.351 8.608,-7.852c3.084,-2.596 6.282,-5.065 9.508,-7.485c4.242,-3.181 8.981,-5.539 13.649,-8.011c1.312,-0.695 2.5,-1.622 3.798,-2.346c2.435,-1.357 4.861,-2.746 7.38,-3.931c3.057,-1.438 5.85,-3.482 9.246,-4.157c1.207,-0.24 2.427,-0.418 3.641,-0.625c0.038,0.747 0.084,1.493 0.112,2.241c0.048,1.279 0.794,1.688 1.971,1.714c3.458,0.077 4.892,-2.151 3.376,-5.288z" fill="#FEDBD0" id="svg_10"/>
|
||||
<path d="m57.51,258.254881c7.145,-2.064 14.546,-3.315 21.271,-6.71c0.294,-0.148 0.596,-0.312 0.913,-0.376c1.115,-0.226 1.846,0.461 1.457,1.539c-0.502,1.392 -1.12,2.772 -1.899,4.026c-1.746,2.811 -3.623,5.542 -5.446,8.305c-3.48,3.206 -6.708,6.758 -10.503,9.534c-5.403,3.951 -11.477,6.726 -18.266,7.474c-3.769,0.415 -7.528,1.343 -11.356,0.469c-0.543,-0.124 -1.098,-0.198 -1.702,-0.304c-2.741,5.966 -4.551,12.065 -5.48,18.43c-0.667,0 -1.333,0 -2,0c0.047,-0.996 -0.041,-2.022 0.163,-2.985c1.855,-8.751 4.84,-17.099 9.313,-24.86c2.197,-3.813 4.602,-7.509 7.008,-11.196c1.63,-2.497 3.424,-4.887 5.145,-7.325c0.707,-0.793 1.414,-1.587 1.826,-2.049c-1.337,-4.444 -2.733,-8.452 -3.722,-12.558c-0.906,-3.763 -0.302,-7.588 0.281,-11.383c0.427,-2.786 0.691,-5.602 0.898,-8.414c0.539,-7.303 1.03,-14.61 1.491,-21.919c0.141,-2.242 0.072,-4.498 0.198,-6.741c0.083,-1.468 -0.072,-3.421 1.695,-3.792c1.799,-0.378 2.727,1.386 3.382,2.777c1.878,3.985 3.742,7.986 4.607,12.361c0.5,2.527 1.048,5.048 1.682,7.544c1.66,6.54 1.353,13.035 -0.341,19.504c-1.01,3.857 -2.083,7.696 -3.115,11.547c-0.081,0.302 -0.059,0.632 -0.138,1.602c4.488,-4.461 7.787,-9.186 11.478,-13.553c3.756,-4.445 7.3,-9.081 10.746,-13.773c3.441,-4.686 6.087,-9.859 8.756,-15.053c-1.95,-0.825 -3.721,-1.493 -5.418,-2.314c-1.995,-0.966 -3.474,-2.515 -4.659,-4.396c-2.918,-4.634 -4.822,-9.683 -6.084,-14.97c-1.099,-4.607 -2.018,-9.257 -2.983,-13.895c-0.37,-1.781 0.934,-2.944 2.664,-2.336c0.675,0.237 1.307,0.735 1.828,1.246c5.293,5.196 10.216,10.715 14.295,16.93c0.951,1.448 1.61,3.09 2.377,4.656c0.715,1.462 1.396,2.941 2.091,4.412c0.184,-0.025 0.369,-0.049 0.553,-0.074c0.57,-2.137 1.031,-4.311 1.741,-6.4c0.686,-2.019 0.513,-3.751 -0.461,-5.692c-1.933,-3.85 -2.263,-8.14 -2.788,-12.352c-0.267,-2.142 -0.713,-4.277 -0.791,-6.425c-0.243,-6.719 2.212,-12.722 5.395,-18.432c1.78,-3.192 3.947,-6.169 5.951,-9.235c0.226,-0.346 0.484,-0.688 0.789,-0.962c1.019,-0.913 1.987,-0.691 2.548,0.593c0.197,0.453 0.33,0.948 0.404,1.438c0.771,5.099 1.548,10.196 1.283,15.386c-0.242,4.729 -0.409,9.461 -0.621,14.191c-0.263,5.86 -1.892,11.295 -5.25,16.133c-0.69,0.994 -1.642,1.891 -2.657,2.548c-0.904,0.586 -1.382,1.11 -1.026,2.166c0.173,0.034 0.378,0.141 0.461,0.081c4.238,-3.071 9.005,-4.968 13.999,-6.39c3.119,-0.888 6.217,-1.852 9.316,-2.809c0.709,-0.219 1.533,-0.754 1.974,0.12c0.211,0.418 -0.073,1.317 -0.429,1.756c-3.838,4.724 -7.825,9.269 -13.147,12.472c-4.119,2.479 -8.495,3.238 -13.181,2.755c-0.401,-0.041 -0.801,-0.094 -1.403,-0.165c-0.513,1.27 -1.061,2.461 -1.48,3.695c-1.126,3.313 -2.51,6.49 -4.453,9.422c-0.589,0.889 -0.936,1.94 -1.393,2.917c-0.092,0.177 -0.184,0.354 -0.276,0.531c0.237,-0.025 0.475,-0.049 0.712,-0.074c8.518,-4.686 17.852,-5.489 27.302,-5.726c4.238,-0.106 8.496,0.056 12.66,-0.997c0.32,-0.081 0.688,-0.208 0.977,-0.122c0.431,0.129 0.934,0.345 1.165,0.687c0.135,0.199 -0.127,0.769 -0.346,1.076c-0.233,0.327 -0.62,0.546 -0.949,0.8c-4.471,3.456 -8.851,7.037 -13.446,10.319c-3.093,2.209 -6.334,4.341 -9.788,5.883c-4.479,1.999 -9.254,2.422 -14.099,0.841c-3.231,-1.054 -6.577,-1.775 -9.11,-4.412c-5.55,7.005 -10.925,13.789 -16.3,20.573c0.1,0.131 0.201,0.262 0.301,0.394c1.158,-0.542 2.28,-1.184 3.48,-1.603c2.105,-0.736 4.22,-1.533 6.399,-1.952c4.669,-0.897 9.384,-1.167 14.146,-0.602c2.304,0.273 4.653,0.157 6.981,0.233c0.495,0.016 1.018,0.013 1.476,0.172c0.931,0.323 1.159,1.218 0.56,1.997c-0.15,0.194 -0.348,0.364 -0.553,0.501c-5.39,3.602 -10.583,7.572 -16.826,9.66c-4.521,1.513 -9.105,2.594 -13.959,1.56c-2.984,-0.636 -6.028,-0.992 -9.407,-1.532c-1.229,1.622 -2.828,3.432 -4.06,5.465c-0.787,1.297 -1.522,2.517 -2.777,3.402c-0.313,0.22 -0.452,0.686 -0.672,1.038c-2.79,3.854 -5.6,7.693 -8.352,11.574c-0.505,0.712 -0.774,1.592 -1.151,2.396c-0.148,0.187 -0.298,0.372 -0.446,0.558c-0.049,0.137 -0.098,0.273 -0.148,0.409c0.085,-0.101 0.169,-0.202 0.254,-0.303c0.175,-0.159 0.349,-0.318 0.524,-0.476c0.359,-0.195 0.757,-0.343 1.072,-0.594c4.498,-3.578 9.142,-6.901 14.687,-8.726c0.769,-0.253 1.457,-0.754 2.182,-1.141zm-3.085,-15.668c-0.545,0.543 -0.931,1.13 -0.546,2.46c0.526,-0.893 0.821,-1.396 1.117,-1.898c-0.19,-0.188 -0.381,-0.375 -0.571,-0.562z" fill="#56819B" id="svg_11"/>
|
||||
<path d="m203.5,300.642881c-28.167,0 -56.333,0 -84.5,0c1.579,-2.025 3.197,-4.021 4.731,-6.079c6.246,-8.378 11.41,-17.326 14.302,-27.453c0.29,-1.015 0.785,-1.708 1.872,-2.113c1.625,-0.605 3.261,-1.3 4.707,-2.241c2.703,-1.757 5.273,-3.719 7.899,-5.595c0.724,0.841 1.492,0.323 2.277,0.105c1.509,-0.419 3.015,-0.961 4.557,-1.123c1.518,-0.159 2.939,-0.342 4.212,-1.261c0.46,-0.332 1.027,-0.601 1.581,-0.702c3.082,-0.565 6.171,-1.099 9.267,-1.587c3.507,-0.553 7.013,-1.126 10.537,-1.545c4.593,-0.547 9.198,-1.015 13.808,-1.394c3.384,-0.278 6.784,-0.345 10.175,-0.546c0.372,-0.022 0.727,-0.309 1.091,-0.473c0.773,0.86 1.621,1.664 2.303,2.59c1.619,2.199 3.404,4.325 4.683,6.715c3.164,5.915 4.317,12.338 4.11,19.02c-1.822,1.284 -3.545,2.656 -4.742,4.6c-0.692,1.125 -1.486,2.192 -2.282,3.248c-1.578,2.094 -3.217,4.141 -4.778,6.247c-1.37,1.847 -2.75,3.695 -3.969,5.642c-0.765,1.221 -1.238,2.624 -1.841,3.945z" fill="#B2CDDD" id="svg_12"/>
|
||||
<path d="m0,167.642881c1.953,0.858 4.023,1.524 5.837,2.613c4.339,2.606 8.422,5.588 12.183,9.001c7.607,6.902 12.849,15.19 15.549,25.179c1.135,4.2 1.898,8.407 2.177,12.703c0.491,7.553 -0.901,14.816 -3.643,21.862c-1.81,4.652 -4.023,9.07 -6.872,13.17c-0.178,0.257 -0.213,0.613 -0.314,0.923c-0.706,0.697 -1.407,1.399 -2.12,2.089c-1.599,1.548 -3.107,3.208 -4.825,4.612c-4.805,3.927 -9.685,7.762 -14.564,11.598c-1.008,0.792 -1.893,1.912 -3.409,1.75c0.001,-35.167 0.001,-70.333 0.001,-105.5z" fill="#54809A" id="svg_14"/>
|
||||
<path d="m26.5,300.642881c0.929,-6.365 2.74,-12.464 5.48,-18.43c0.603,0.106 1.158,0.18 1.702,0.304c3.828,0.874 7.586,-0.054 11.356,-0.469c6.789,-0.748 12.863,-3.524 18.266,-7.474c3.796,-2.775 7.023,-6.328 10.503,-9.534c3.102,2.025 6.632,2.88 10.186,3.581c4.458,0.88 8.946,1.606 13.421,2.397c0.163,0.029 0.331,0.078 0.493,0.066c5.706,-0.412 11.501,0.316 17.092,-1.612c2.083,-0.719 4.354,-0.881 6.519,-1.383c0.977,-0.227 2.248,-0.094 2.553,-1.51c3.712,1.091 7.464,0.778 11.605,0.016c-0.403,1.373 -0.662,2.386 -0.997,3.374c-3.081,9.089 -8.229,17.02 -13.851,24.685c-1.456,1.986 -2.886,3.991 -4.328,5.988c-30,0.001 -60,0.001 -90,0.001z" fill="#B2CDDD" id="svg_15"/>
|
||||
<path d="m453.697,248.860881c-1.036,1.466 -1.992,2.999 -3.127,4.385c-1.997,2.439 -3.893,4.933 -5.48,7.668c-0.741,1.277 -1.777,2.387 -1.938,3.947c-0.025,0.244 -0.081,0.511 -0.207,0.716c-2.923,4.751 -4.145,10.224 -6.466,15.235c-0.896,1.934 -1.377,4.067 -1.979,6.129c-0.922,3.158 -1.861,6.316 -2.636,9.511c-0.327,1.347 -0.255,2.791 -0.365,4.191c-8.333,0 -16.667,0 -25,0c-0.064,-0.492 -0.127,-0.984 -0.191,-1.476c-0.786,-6.001 -1.833,-11.982 -2.289,-18.008c-0.5,-6.618 -0.296,-13.268 0.94,-19.837c1.29,-6.857 3.538,-13.302 7.994,-18.814c1.804,-2.231 3.905,-4.056 6.617,-5.126c7.006,-2.763 13.85,-2.131 20.494,1.109c5.215,2.544 9.46,6.425 13.633,10.37z" fill="#FEF6F3" id="svg_18"/>
|
||||
<path d="m453.697,248.860881c-4.173,-3.945 -8.418,-7.826 -13.632,-10.369c-6.644,-3.241 -13.488,-3.872 -20.494,-1.109c-2.712,1.069 -4.814,2.894 -6.617,5.126c-4.456,5.512 -6.704,11.957 -7.994,18.814c-1.236,6.569 -1.44,13.219 -0.94,19.837c0.456,6.026 1.503,12.007 2.289,18.008c0.064,0.492 0.128,0.984 0.191,1.476c-1,0 -2,0 -3,0c-0.048,-0.899 -0.047,-1.804 -0.152,-2.696c-0.636,-5.436 -1.441,-10.857 -1.901,-16.307c-0.335,-3.965 -0.414,-7.975 -0.272,-11.952c0.276,-7.714 1.882,-15.169 5.327,-22.127c2.157,-4.356 4.886,-8.252 9.027,-11.079c4.653,-3.177 9.791,-4.007 15.208,-3.599c7.091,0.533 12.977,3.859 18.324,8.305c2.236,1.859 4.459,3.733 6.689,5.599c3.79,3.227 7.337,6.807 11.421,9.605c7.432,5.091 15.641,6.85 24.394,3.67c4.21,-1.529 8.286,-3.37 12.08,-5.845c5.415,-3.533 10.19,-7.729 14.251,-12.749c0.603,-0.745 1.155,-1.531 1.799,-2.388c-4.865,-3.29 -8.586,-7.267 -11.563,-11.948c-1.977,-4.036 -3.754,-8.144 -4.246,-12.674c-0.618,-5.684 0.519,-10.88 4.306,-15.343c7.45,-8.777 17.131,-4.118 20.673,1.989c3.019,5.207 4.006,10.856 2.952,16.788c-0.522,2.935 -1.291,5.827 -1.948,8.738c-1.193,2.637 -2.33,5.303 -3.604,7.9c-0.639,1.302 -1.527,2.482 -2.352,3.795c1.172,0.621 2.086,1.221 3.08,1.615c10.465,4.143 20.984,4.634 31.595,0.488c5.979,-2.336 11.681,-5.231 17.151,-8.551c3.608,-2.19 6.999,-4.739 10.487,-7.127c2.397,-1.768 4.815,-3.507 7.186,-5.31c7.254,-5.519 15.14,-9.814 23.882,-12.49c3.984,-1.219 8.052,-1.862 12.21,-1.993c0.661,-0.021 1.321,-0.104 1.981,-0.157c2.531,0.427 5.073,0.795 7.59,1.29c6.133,1.204 10.928,4.308 13.872,9.97c0.191,0.368 0.411,0.723 0.642,1.069c0.087,0.131 0.242,0.217 0.332,0.294c6.05,-0.686 12.062,-1.426 18.086,-2.036c6.583,-0.666 12.902,-2.396 19.078,-4.65c4.914,-1.794 9.728,-3.863 14.602,-5.768c10.511,-4.108 20.171,-9.822 29.712,-15.749c5.903,-3.667 10.494,-8.65 14.1,-14.566c0.12,-0.197 0.331,-0.339 0.499,-0.507c0,1.667 0,3.333 0,5c-0.19,0.146 -0.434,0.254 -0.562,0.442c-4.137,6.029 -9.654,10.489 -15.927,14.114c-7.271,4.202 -14.354,8.758 -22.164,11.949c-7.229,2.953 -14.434,5.979 -21.76,8.672c-6.414,2.358 -13.101,3.708 -19.931,4.322c-4.868,0.438 -9.722,1.032 -14.698,1.569c0.447,2.367 0.896,4.466 1.232,6.582c0.989,6.238 0.15,12.164 -3.601,17.373c-2.782,3.864 -6.297,6.874 -11.243,7.64c-2.72,0.421 -5.149,-0.852 -6.339,-3.293c-2.429,-4.984 -3.471,-10.145 -1.047,-15.386c2.78,-6.012 7.204,-10.527 13.259,-13.349c1.051,-0.49 2.131,-0.918 3.194,-1.383c0.06,-0.026 0.087,-0.129 0.227,-0.349c-0.797,-2.545 -2.498,-4.521 -4.736,-6.04c-3.833,-2.602 -8.237,-3.539 -12.737,-3.908c-2.553,-0.209 -5.147,0.08 -7.722,0.145c-2.839,0.556 -5.759,0.858 -8.5,1.723c-5.473,1.727 -10.671,4.173 -15.496,7.29c-2.762,1.783 -5.307,3.901 -7.95,5.869c-0.817,0.561 -1.677,1.07 -2.446,1.692c-9.188,7.432 -19.365,13.198 -30.219,17.776c-5.006,2.111 -10.247,3.548 -15.701,3.762c-6.311,0.247 -12.404,-0.995 -18.246,-3.4c-1.536,-0.632 -3.032,-1.364 -4.737,-2.135c-0.982,1.112 -1.995,2.19 -2.932,3.33c-5.141,6.249 -11.288,11.234 -18.465,14.989c-3.85,2.014 -7.804,3.629 -12.041,4.69c-7.43,1.86 -14.189,0.184 -20.6,-3.493c-4.347,-2.493 -8.189,-5.684 -11.787,-9.17c-0.834,-0.816 -1.748,-1.545 -2.626,-2.31zm73.068,-22.082c1.719,-5.217 2.952,-10.54 2.417,-16.06c-0.418,-4.322 -1.932,-8.262 -5.474,-11.091c-3.886,-3.104 -8.374,-3.952 -12.441,0.361c-3.003,3.185 -4.691,7.101 -4.647,11.502c0.057,5.679 1.696,10.943 5.001,15.629c2.474,4.007 5.76,7.16 9.901,9.719c2.297,-3.261 4.164,-6.449 5.243,-10.06zm125.196,-5.674c-5.95,1.56 -10.071,5.141 -13.571,9.623c-2.34,2.996 -2.873,6.522 -2.83,10.114c0.026,2.222 0.639,4.441 1.956,6.347c1.279,1.852 2.618,2.387 4.71,1.598c1.375,-0.518 2.773,-1.183 3.906,-2.097c6.705,-5.413 8.435,-12.613 7.132,-20.786c-0.256,-1.601 -0.846,-3.15 -1.303,-4.799z" fill="#5D859D" id="svg_20"/>
|
||||
<path d="m0,273.142881c1.516,0.161 2.401,-0.958 3.409,-1.75c4.879,-3.836 9.759,-7.671 14.564,-11.598c1.717,-1.404 3.226,-3.064 4.825,-4.612c0.713,-0.69 1.414,-1.392 2.12,-2.089c0.999,0.956 2.174,0.507 3.297,0.397c2.798,-0.273 5.591,-0.608 8.392,-0.847c3.306,-0.282 6.367,0.969 9.522,1.631c-1.72,2.438 -3.515,4.828 -5.144,7.325c-2.406,3.687 -4.811,7.384 -7.008,11.196c-4.473,7.761 -7.457,16.109 -9.313,24.86c-0.204,0.963 -0.116,1.988 -0.163,2.985c-8.167,0 -16.333,0 -24.5,0c-0.001,-9.165 -0.001,-18.331 -0.001,-27.498z" fill="#B2CDDD" id="svg_24"/>
|
||||
<path d="m116.5,300.642881c1.442,-1.996 2.872,-4.002 4.328,-5.988c5.622,-7.665 10.77,-15.596 13.851,-24.685c0.335,-0.988 0.594,-2.002 0.997,-3.374c-4.141,0.762 -7.892,1.075 -11.605,-0.016c-2.444,-2.027 -4.795,-4.112 -5.824,-7.288c-2.189,-6.757 -0.875,-12.83 3.686,-18.161c2.091,-2.444 5.436,-2.988 8.48,-1.804c4.269,1.661 6.988,4.729 8.105,9.135c0.943,3.719 1.461,7.501 0.999,11.362c-0.087,0.726 -0.013,1.471 -0.013,2.42c0.622,-0.123 1.191,-0.128 1.66,-0.349c1.127,-0.53 2.259,-1.079 3.295,-1.764c5.63,-3.723 11.032,-7.753 15.879,-12.467c8.534,-8.302 18.932,-11.766 30.552,-10.834c8.313,0.666 15.79,3.901 21.214,10.745c0.508,0.641 1.185,1.147 1.784,1.715c1.658,2.364 3.474,4.635 4.939,7.113c3.137,5.308 4.259,11.225 4.77,17.286c-0.202,4.143 -0.24,8.303 -0.649,12.426c-0.482,4.859 -1.285,9.687 -1.949,14.528c-0.667,0 -1.333,0 -2,0c0.064,-0.902 0.077,-1.811 0.199,-2.704c0.954,-6.964 1.934,-13.924 1.911,-20.977c0.208,-6.682 -0.946,-13.105 -4.11,-19.02c-1.279,-2.39 -3.064,-4.516 -4.683,-6.715c-0.682,-0.926 -1.53,-1.731 -2.303,-2.59c-1.733,-2.164 -3.651,-4.181 -6.227,-5.273c-3.279,-1.39 -6.602,-2.749 -10.019,-3.724c-4.067,-1.161 -8.247,-0.643 -12.392,-0.074c-6.704,0.92 -12.55,3.703 -17.688,8.031c-3.75,3.16 -7.454,6.375 -11.178,9.566c-2.626,1.876 -5.196,3.838 -7.899,5.595c-1.447,0.941 -3.082,1.635 -4.707,2.241c-1.087,0.405 -1.582,1.099 -1.872,2.113c-2.892,10.127 -8.056,19.075 -14.302,27.453c-1.535,2.058 -3.152,4.055 -4.731,6.079c-0.831,-0.001 -1.665,-0.001 -2.498,-0.001zm21.085,-44.076c-0.226,-1.841 -0.346,-3.67 -0.69,-5.455c-0.702,-3.64 -2.383,-6.701 -5.587,-8.784c-3.248,-2.112 -6.703,-1.67 -8.715,1.513c-2.711,4.287 -3.974,9.005 -2.481,14.042c1.149,3.876 3.627,6.344 7.888,6.96c2.418,0.349 4.596,-0.35 6.873,-0.68c1.263,-0.183 1.866,-0.953 2.018,-2.163c0.228,-1.812 0.463,-3.622 0.694,-5.433z" fill="#5E879F" id="svg_33"/>
|
||||
<path d="m221.11,276.961881c0.023,7.053 -0.957,14.013 -1.911,20.977c-0.123,0.894 -0.136,1.803 -0.199,2.704c-5.167,0 -10.333,0 -15.5,0c0.603,-1.321 1.076,-2.724 1.839,-3.944c1.218,-1.946 2.599,-3.795 3.969,-5.642c1.561,-2.105 3.2,-4.153 4.778,-6.247c0.796,-1.057 1.59,-2.123 2.282,-3.248c1.197,-1.945 2.92,-3.316 4.742,-4.6z" fill="#DAF1FF" id="svg_34"/>
|
||||
<path d="m508.133,227.129881c2.977,4.682 6.698,8.658 11.563,11.948c-0.643,0.857 -1.196,1.643 -1.799,2.388c-4.06,5.019 -8.836,9.216 -14.251,12.749c-3.794,2.475 -7.869,4.315 -12.08,5.845c-8.754,3.18 -16.962,1.421 -24.394,-3.67c-4.084,-2.797 -7.631,-6.378 -11.421,-9.605c0.51,-0.232 1.208,-0.327 1.499,-0.721c1.406,-1.904 3.562,-2.659 5.458,-3.826c0.553,-0.341 0.885,-1.041 1.319,-1.576c2.188,-1.133 4.354,-2.31 6.567,-3.391c5.425,-2.651 10.837,-5.319 16.666,-7.044c1.654,-0.49 3.333,-0.605 4.974,-0.974c5.244,-1.179 10.582,-1.552 15.899,-2.123z" fill="#FEDBD0" id="svg_39"/>
|
||||
<path d="m586.228,224.746881c-3.488,2.388 -6.878,4.937 -10.487,7.127c-5.47,3.32 -11.172,6.215 -17.151,8.551c-10.612,4.146 -21.13,3.656 -31.595,-0.488c-0.995,-0.394 -1.908,-0.993 -3.08,-1.615c0.824,-1.313 1.713,-2.492 2.352,-3.795c1.274,-2.598 2.411,-5.263 3.604,-7.9c5.575,0.006 11.151,0.061 16.726,-0.003c2.524,-0.029 5.044,-0.372 7.568,-0.427c1.561,-0.034 3.128,0.21 4.693,0.321c0.579,0.041 1.161,0.112 1.738,0.083c2.645,-0.133 5.286,-0.381 7.931,-0.43c5.194,-0.097 10.349,-0.52 15.467,-1.418c0.723,-0.127 1.488,-0.01 2.234,-0.006z" fill="#FEDAD0" id="svg_40"/>
|
||||
<path d="m595.508,185.574881c1.815,-0.428 3.265,-0.768 4.714,-1.114c1.426,-0.341 2.629,-0.045 3.518,1.197c0.932,1.301 0.614,2.648 -0.249,3.691c-1.258,1.521 -2.76,2.843 -4.187,4.221c-0.475,0.459 -1.03,0.837 -1.562,1.234c-0.678,0.505 -1.005,1.152 -0.635,1.948c0.77,1.659 0.227,2.937 -1.15,3.892c-1.168,0.81 -2.502,0.376 -4.104,-1.156c-0.049,-0.047 -0.145,-0.046 -0.414,-0.123c-1.114,0.772 -2.349,1.596 -3.549,2.469c-1.042,0.758 -2.035,0.944 -3.203,0.204c-1.139,-0.721 -1.505,-1.711 -1.397,-2.964c0.128,-1.487 0.271,-2.972 0.428,-4.683c-0.666,-0.273 -1.329,-0.554 -1.999,-0.816c-0.692,-0.271 -1.439,-0.442 -2.076,-0.806c-0.871,-0.497 -1.611,-2.212 -1.29,-3.033c0.402,-1.03 1.054,-1.877 2.306,-2.007c1.484,-0.154 2.961,-0.413 4.449,-0.496c1.31,-0.073 2.229,-0.56 2.663,-1.83c0.614,-1.798 1.3,-3.576 1.818,-5.402c0.548,-1.934 1.235,-2.706 2.722,-2.679c1.553,0.028 2.468,0.811 2.643,2.367c0.21,1.888 0.358,3.784 0.554,5.886z" fill="#57829B" id="svg_41"/>
|
||||
<path d="m715.687,156.829881c0.347,1.587 -0.356,2.857 -1.195,4.107c-0.56,0.834 -1.247,1.27 -2.278,1.028c-0.946,-0.222 -1.79,-0.693 -1.891,-1.687c-0.104,-1.019 0.107,-2.071 0.18,-3.108c0.053,-0.063 0.104,-0.129 0.16,-0.19c2.162,-2.296 2.731,-2.313 5.024,-0.15z" fill="#648CA3" id="svg_57"/>
|
||||
<path d="m706.431,153.888881c-1.644,0.036 -3.161,-1.439 -3.192,-3.104c-0.028,-1.499 1.198,-2.548 3.001,-2.567c1.618,-0.017 3.153,1.284 3.199,2.789c0.058,1.866 -1.115,2.593 -3.008,2.882z" fill="#6F92A7" id="svg_58"/>
|
||||
<path d="m703.429,159.265881c1.516,3.137 0.082,5.365 -3.374,5.289c-1.177,-0.026 -1.923,-0.435 -1.971,-1.714c-0.028,-0.747 -0.074,-1.493 -0.112,-2.241c1.107,-1.124 2.482,-1.565 4.025,-1.576c0.175,0.01 0.35,0.019 0.525,0.029c0.302,0.072 0.604,0.142 0.907,0.213z" fill="#6F92A7" id="svg_61"/>
|
||||
<path d="m702.521,159.053881c-0.175,-0.009 -0.35,-0.019 -0.525,-0.029c0.175,0.009 0.35,0.019 0.525,0.029z" fill="#FEDBD0" id="svg_63"/>
|
||||
<path d="m223.597,273.687881c-0.512,-6.061 -1.633,-11.978 -4.77,-17.286c-1.465,-2.479 -3.282,-4.749 -4.939,-7.113c4.654,-0.328 9.281,-0.385 13.788,1.146c0.932,0.317 1.925,0.51 2.906,0.615c4.351,0.469 7.965,2.621 11.503,4.953c1.017,0.67 1.987,1.413 2.979,2.123c-0.421,0.461 -0.758,1.086 -1.279,1.356c-4.186,2.163 -7.941,4.946 -11.694,7.767c-2.3,1.728 -4.625,3.375 -6.56,5.537c-0.438,0.486 -1.279,0.611 -1.934,0.902z" fill="#B2CDDD" id="svg_64"/>
|
||||
<path d="m335.508,275.129881c-2.846,4.805 -6.708,8.808 -10.273,13.047c-0.69,0.82 -1.222,1.771 -1.845,2.649c-1.402,1.974 -3.702,2.424 -5.65,1.118c-1.385,-0.929 -1.593,-4.288 -0.312,-5.917c0.87,-1.105 1.82,-2.147 2.752,-3.203c2.797,-3.169 5.596,-6.336 8.407,-9.493c0.802,-0.9 1.646,-1.763 2.471,-2.642c0.73,-0.128 1.455,-0.312 2.191,-0.373c1.829,-0.152 2.838,0.825 2.697,2.636c-0.056,0.733 -0.286,1.452 -0.438,2.178z" fill="#B4CFDE" id="svg_66"/>
|
||||
<path d="m365.213,292.639881c-2.923,0.011 -4.061,-1.133 -3.662,-3.836c0.188,-1.269 0.596,-2.692 1.385,-3.648c2.256,-2.732 4.501,-5.508 7.436,-7.601c2.437,-1.738 4.98,-1.037 5.309,1.618c0.102,0.82 -0.158,1.866 -0.631,2.545c-2.263,3.244 -4.637,6.413 -7.017,9.575c-0.726,0.965 -1.689,1.614 -2.82,1.347z" fill="#B7D0DE" id="svg_68"/>
|
||||
<path d="m345.5,268.101881c-2.654,0.057 -4.406,-1.231 -4.777,-3.208c-0.12,-0.641 0.009,-1.577 0.414,-2.029c2.596,-2.899 5.285,-5.716 7.986,-8.519c0.445,-0.463 1.105,-0.714 1.651,-1.085c1.131,-0.771 2.18,-0.518 3.214,0.206c1.045,0.731 1.135,1.807 0.959,2.898c-0.359,2.215 -1.79,3.89 -3.09,5.585c-1.255,1.636 -2.622,3.204 -4.082,4.659c-0.761,0.758 -1.828,1.209 -2.275,1.493z" fill="#B7D0DE" id="svg_69"/>
|
||||
<path d="m362.953,268.123881c-2.726,-0.059 -4.533,-2.69 -3.744,-5.101c0.43,-1.314 1.096,-2.661 2.005,-3.683c1.368,-1.538 2.89,-3.059 4.636,-4.101c1.254,-0.749 3.001,-1.022 4.494,-0.932c1.821,0.11 2.8,2.638 1.747,4.331c-0.991,1.594 -1.889,3.258 -3.578,4.394c-1.045,0.703 -1.697,2 -2.499,3.05c-0.892,1.168 -1.963,2 -3.061,2.042z" fill="#B7D0DE" id="svg_70"/>
|
||||
<path d="m346.953,290.630881c-0.389,0.126 -0.788,0.224 -1.164,0.382c-1.732,0.724 -3.623,0.453 -4.766,-0.701c-0.755,-0.762 -0.782,-3.373 0.021,-4.694c0.423,-0.696 0.978,-1.312 1.474,-1.965c0.473,-0.289 1.11,-0.468 1.391,-0.887c1.734,-2.582 4.36,-3.985 6.856,-5.494c2.881,1.421 3.464,2.328 2.74,4.45c-0.288,0.844 -0.712,1.688 -1.258,2.389c-1.717,2.21 -3.522,4.352 -5.294,6.52z" fill="#B7D0DE" id="svg_71"/>
|
||||
<path d="m57.51,258.254881c-0.725,0.387 -1.413,0.888 -2.182,1.141c-5.545,1.826 -10.189,5.148 -14.687,8.726c-0.315,0.251 -0.713,0.398 -1.073,0.593c-0.061,-0.064 -0.122,-0.126 -0.184,-0.188c0.378,-0.802 0.647,-1.682 1.152,-2.394c2.752,-3.881 5.562,-7.72 8.352,-11.574c0.196,1.12 1.068,0.99 1.89,1.103c2.438,0.336 4.816,0.879 6.732,2.593z" fill="#B2CDDD" id="svg_72"/>
|
||||
<path d="m270.464,237.841881c-0.074,1.702 -1.733,3.845 -3.338,4.195c-1.004,0.219 -1.98,0.172 -2.694,-0.786c-0.887,-1.19 -1.42,-2.497 -0.845,-3.92c0.328,-0.812 0.93,-1.582 1.586,-2.177c0.994,-0.901 2.15,-1.678 3.602,-0.953c1.113,0.558 1.759,2.025 1.689,3.641z" fill="#5E879F" id="svg_77"/>
|
||||
<path d="m286.407,233.645881c-0.038,2.102 -1.875,3.799 -4.097,3.782c-1.9,-0.014 -3.265,-1.56 -3.191,-3.616c0.07,-1.971 2.163,-4.021 4.041,-3.957c1.579,0.054 3.278,2.038 3.247,3.791z" fill="#5D859D" id="svg_79"/>
|
||||
<path d="m265.936,251.172881c0.663,1.477 -0.088,2.766 -0.685,3.985c-0.662,1.351 -3.405,1.914 -4.439,0.312c-0.486,-0.754 -0.896,-1.557 -1.34,-2.338c0.027,-0.161 0.077,-0.321 0.078,-0.482c0.005,-1.561 0.793,-2.748 2.171,-3.216c1.382,-0.47 2.73,0.014 3.716,1.205c0.155,0.188 0.332,0.357 0.499,0.534z" fill="#5E879F" id="svg_81"/>
|
||||
<path d="m298.765,241.316881c1.714,-0.007 3.078,1.376 3.141,3.186c0.065,1.862 -1.575,3.436 -3.519,3.378c-1.716,-0.052 -3.134,-1.535 -3.102,-3.246c0.029,-1.599 1.824,-3.311 3.48,-3.318z" fill="#5E879F" id="svg_82"/>
|
||||
<path d="m282.445,251.329881c0.463,0.342 0.949,0.658 1.385,1.031c1.197,1.024 1.613,2.333 1.157,3.529c-0.5,1.314 -2.117,2.391 -3.526,2.301c-1.144,-0.073 -2.149,-0.514 -2.592,-1.627c-0.515,-1.295 -0.507,-2.629 0.49,-3.73c0.49,-0.54 1.145,-0.931 1.725,-1.388c0.454,-0.038 0.908,-0.077 1.361,-0.116z" fill="#5E879F" id="svg_83"/>
|
||||
<path d="m275.434,250.637881c-0.544,1.018 -0.621,2.398 -2.288,2.46c-1.627,0.06 -2.849,-0.425 -3.551,-1.953c-0.009,-0.574 -0.08,-1.155 -0.016,-1.721c0.219,-1.933 1.62,-3.182 3.424,-3.108c1.562,0.064 2.446,1.248 2.477,3.336c0.005,0.328 -0.03,0.657 -0.046,0.986z" fill="#648CA3" id="svg_85"/>
|
||||
<path d="m291.468,251.583881c-1.29,0.487 -2.489,0.34 -3.583,-0.518c-0.97,-2.772 -0.614,-3.836 1.427,-5.153c1.211,-0.782 2.179,-0.419 3.161,0.28c1.001,0.713 1.325,1.786 0.845,2.838c-0.429,0.938 -1.219,1.709 -1.85,2.553z" fill="#648CA3" id="svg_86"/>
|
||||
<path d="m281.534,241.295881c1.544,-0.012 2.727,1.275 2.695,2.933c-0.03,1.558 -1.463,2.836 -3.168,2.825c-1.485,-0.01 -2.853,-1.174 -2.926,-2.491c-0.089,-1.609 1.621,-3.253 3.399,-3.267z" fill="#648CA3" id="svg_87"/>
|
||||
<path d="m293.736,231.754881c1.644,0.208 2.646,1.505 2.404,3.111c-0.277,1.838 -1.463,3.036 -2.797,2.827c-1.772,-0.278 -3.009,-1.846 -2.752,-3.487c0.254,-1.615 1.58,-2.649 3.145,-2.451z" fill="#648CA3" id="svg_88"/>
|
||||
<path d="m593.498,223.123881c2.643,-1.967 5.188,-4.085 7.95,-5.869c4.826,-3.116 10.023,-5.563 15.496,-7.29c2.742,-0.865 5.661,-1.167 8.5,-1.723c-2.26,1.482 -4.49,3.011 -6.786,4.435c-5.762,3.575 -11.658,6.908 -18.194,8.877c-2.273,0.686 -4.641,1.056 -6.966,1.57z" fill="#FDFDFE" id="svg_122"/>
|
||||
<path d="m676.375,263.677881c-1.33,-0.165 -2.321,-0.213 -3.273,-0.429c-0.848,-0.193 -1.521,-0.721 -1.505,-1.71c0.016,-1.015 0.557,-1.935 1.518,-1.987c2.362,-0.128 4.745,-0.09 7.104,0.089c0.519,0.039 1.342,0.936 1.355,1.454c0.014,0.556 -0.651,1.434 -1.215,1.641c-1.365,0.5 -2.844,0.691 -3.984,0.942z" fill="#EDD1CA" id="svg_131"/>
|
||||
<path d="m707.815,258.044881c1.271,0.174 2.745,0.353 4.21,0.585c0.81,0.129 1.39,0.567 1.359,1.482c-0.03,0.897 -0.622,1.374 -1.424,1.445c-2.382,0.211 -4.77,0.363 -7.159,0.498c-1.207,0.068 -1.898,-0.673 -2.005,-1.764c-0.103,-1.041 0.632,-1.708 1.602,-1.897c1.047,-0.203 2.13,-0.226 3.417,-0.349z" fill="#EDD1CA" id="svg_132"/>
|
||||
<path d="m726.254,240.603881c-1.297,-0.094 -2.132,-0.084 -2.94,-0.23c-1.079,-0.195 -1.634,-1.103 -1.446,-2.009c0.133,-0.642 0.94,-1.549 1.532,-1.621c2.211,-0.265 4.457,-0.303 6.689,-0.279c0.819,0.009 1.835,0.469 1.701,1.451c-0.092,0.674 -0.755,1.594 -1.369,1.807c-1.466,0.51 -3.055,0.666 -4.167,0.881z" fill="#EDD1CA" id="svg_133"/>
|
||||
<path d="m698.203,251.614881c-0.58,0 -1.209,0.142 -1.727,-0.036c-0.633,-0.219 -1.599,-0.652 -1.655,-1.091c-0.087,-0.684 0.341,-1.55 0.802,-2.145c0.295,-0.381 1.033,-0.548 1.585,-0.577c1.567,-0.083 3.154,-0.164 4.705,0.005c0.566,0.062 1.292,0.731 1.526,1.297c0.365,0.882 -0.206,1.686 -1.088,1.923c-1.345,0.361 -2.742,0.525 -4.117,0.773c-0.01,-0.05 -0.02,-0.1 -0.031,-0.149z" fill="#EDD1CA" id="svg_134"/>
|
||||
<path d="m715.076,280.980881c-1.135,0 -1.906,0.215 -2.424,-0.055c-0.648,-0.338 -1.446,-1.033 -1.518,-1.651c-0.072,-0.612 0.568,-1.78 1.087,-1.904c1.89,-0.452 3.887,-1.119 5.789,-0.143c0.643,0.33 1.382,1.06 1.491,1.706c0.174,1.034 -0.71,1.569 -1.714,1.707c-1.056,0.147 -2.116,0.266 -2.711,0.34z" fill="#EDD1CA" id="svg_135"/>
|
||||
<path d="m652.969,279.278881c-1.25,-0.064 -2.178,-0.031 -3.074,-0.181c-0.87,-0.146 -1.738,-0.696 -1.508,-1.636c0.165,-0.674 0.957,-1.671 1.5,-1.692c2.221,-0.083 4.462,0.08 6.676,0.329c0.48,0.054 1.229,0.845 1.219,1.282c-0.011,0.471 -0.695,1.191 -1.214,1.333c-1.265,0.348 -2.607,0.425 -3.599,0.565z" fill="#EDD1CA" id="svg_136"/>
|
||||
<path d="m709.683,290.088881c0.787,0.211 1.882,0.354 2.833,0.801c1.011,0.476 1.098,1.653 0.147,2.201c-1.596,0.919 -3.36,1.443 -5.244,1.002c-0.962,-0.225 -1.728,-0.773 -1.698,-1.861c0.029,-1.04 0.724,-1.662 1.699,-1.888c0.64,-0.148 1.313,-0.154 2.263,-0.255z" fill="#EDD1CA" id="svg_137"/>
|
||||
<path d="m735.998,252.586881c-0.79,-0.092 -1.214,-0.085 -1.604,-0.198c-1.005,-0.291 -1.885,-0.81 -1.904,-1.985c-0.02,-1.232 0.879,-1.808 1.937,-1.817c1.452,-0.012 2.972,0.005 4.34,0.418c1.423,0.429 1.466,1.845 0.137,2.548c-0.995,0.527 -2.14,0.771 -2.906,1.034z" fill="#EDD1CA" id="svg_138"/>
|
||||
<path d="m736.343,271.796881c-2.273,-0.125 -3.097,-0.611 -3.183,-1.997c-0.078,-1.265 1.025,-2.049 3.079,-2.046c0.969,0.001 1.987,0.067 2.888,0.376c0.505,0.173 1.16,0.925 1.134,1.38c-0.034,0.591 -0.556,1.402 -1.089,1.659c-0.846,0.409 -1.876,0.438 -2.829,0.628z" fill="#EDD1CA" id="svg_139"/>
|
||||
<path d="m685.329,281.473881c-0.519,-0.091 -1.172,-0.177 -1.811,-0.324c-0.899,-0.207 -1.443,-0.763 -1.482,-1.72c-0.039,-0.96 0.485,-1.524 1.335,-1.857c1.168,-0.457 4.739,0.394 5.578,1.336c0.727,0.816 0.512,1.843 -0.548,2.125c-0.947,0.252 -1.951,0.289 -3.072,0.44z" fill="#EDD1CA" id="svg_140"/>
|
||||
<path d="m740.005,293.233881c-1.484,2.038 -4.82,2.573 -6.338,1.091c-0.458,-0.447 -0.802,-1.318 -0.712,-1.921c0.068,-0.455 0.896,-1.103 1.414,-1.13c2.041,-0.107 4.097,-0.01 5.636,1.96z" fill="#EDD1CA" id="svg_141"/>
|
||||
<path d="m676.726,294.574881c-1.711,-0.04 -3.084,-1.214 -3.063,-2.22c0.021,-0.962 1.182,-1.772 2.604,-1.715c0.987,0.039 2.016,0.066 2.934,0.373c0.515,0.172 1.134,0.88 1.173,1.388c0.036,0.475 -0.525,1.241 -1.011,1.458c-0.96,0.43 -2.05,0.569 -2.637,0.716z" fill="#EDD1CA" id="svg_142"/>
|
||||
<path d="m645.368,290.254881c0.633,0.063 1.151,0.028 1.608,0.179c0.814,0.269 1.658,0.774 1.413,1.727c-0.148,0.577 -0.82,1.246 -1.4,1.436c-0.992,0.324 -2.103,0.442 -3.152,0.39c-0.94,-0.046 -1.703,-0.571 -1.744,-1.718c-0.038,-1.047 0.646,-1.474 1.465,-1.719c0.622,-0.188 1.291,-0.216 1.81,-0.295z" fill="#EDD1CA" id="svg_143"/>
|
||||
<path d="m274.546,264.670881c-0.023,2.518 -1.747,4.264 -4.225,4.279c-1.497,0.009 -2.755,-1.173 -2.755,-2.588c0,-2.289 2.2,-4.961 4.097,-4.975c1.785,-0.014 2.901,1.257 2.883,3.284z" fill="#5C869F" id="svg_145"/>
|
||||
<path d="m300.5,258.759881c-0.01,1.443 -1.51,2.701 -3.254,2.729c-2.272,0.036 -3.682,-1.048 -3.628,-2.788c0.068,-2.185 1.488,-3.76 3.444,-3.82c1.704,-0.053 3.451,1.919 3.438,3.879z" fill="#5C869F" id="svg_146"/>
|
||||
<path d="m288.982,264.797881c-0.042,2.309 -0.991,3.358 -2.981,3.296c-1.448,-0.045 -2.113,-0.562 -2.702,-2.102c-0.46,-1.2 -0.029,-2.816 1.055,-3.791c0.917,-0.825 2,-1.185 3.198,-0.77c1.111,0.384 1.468,1.319 1.43,3.367z" fill="#5E879F" id="svg_147"/>
|
||||
<path d="m54.996,243.147881c-0.296,0.503 -0.591,1.005 -1.117,1.898c-0.385,-1.33 0.001,-1.917 0.546,-2.46c0.191,0.188 0.381,0.375 0.571,0.562z" fill="#B7D0DE" id="svg_148"/>
|
||||
<path d="m54.996,243.147881c-0.19,-0.187 -0.381,-0.375 -0.571,-0.562c0.19,0.188 0.381,0.375 0.571,0.562z" fill="#FFF6F3" id="svg_149"/>
|
||||
<path d="m85.732,206.903881c-0.237,0.025 -0.475,0.049 -0.712,0.074c0.092,-0.177 0.185,-0.353 0.276,-0.531c0.145,0.152 0.291,0.304 0.436,0.457z" fill="#FFF6F3" id="svg_150"/>
|
||||
<path d="m38.938,269.085881c0.15,-0.185 0.299,-0.37 0.447,-0.557c0.061,0.06 0.122,0.123 0.183,0.187c-0.174,0.159 -0.349,0.318 -0.523,0.477l-0.107,-0.107z" fill="#B2CDDD" id="svg_151"/>
|
||||
<path d="m39.045,269.192881c-0.085,0.101 -0.169,0.202 -0.254,0.303c0.049,-0.136 0.099,-0.272 0.148,-0.409c-0.001,-0.001 0.106,0.106 0.106,0.106z" fill="#B2CDDD" id="svg_152"/>
|
||||
<path d="m152.512,257.161881c3.724,-3.191 7.427,-6.406 11.178,-9.566c5.138,-4.329 10.983,-7.111 17.688,-8.031c4.144,-0.569 8.325,-1.086 12.392,0.074c3.417,0.975 6.74,2.334 10.019,3.724c2.576,1.092 4.493,3.109 6.227,5.273c-0.363,0.164 -0.719,0.451 -1.09,0.473c-3.391,0.201 -6.791,0.268 -10.175,0.546c-4.61,0.379 -9.215,0.848 -13.808,1.394c-3.524,0.419 -7.03,0.992 -10.537,1.545c-3.095,0.488 -6.184,1.022 -9.267,1.587c-0.554,0.102 -1.122,0.37 -1.581,0.702c-1.272,0.919 -2.694,1.102 -4.212,1.261c-1.542,0.161 -3.047,0.703 -4.557,1.123c-0.786,0.218 -1.553,0.736 -2.277,-0.105z" fill="#FCF4F2" id="svg_153"/>
|
||||
<path d="m511.621,227.118881c-3.305,-4.685 -4.944,-9.95 -5.001,-15.629c-0.044,-4.401 1.644,-8.317 4.647,-11.502c4.067,-4.313 8.555,-3.465 12.441,-0.361c3.542,2.83 5.056,6.77 5.474,11.091c0.534,5.52 -0.699,10.843 -2.418,16.059c-0.983,-0.041 -1.967,-0.142 -2.947,-0.111c-4.066,0.131 -8.131,0.299 -12.196,0.453z" fill="#FDFDFE" id="svg_154"/>
|
||||
<path d="m651.961,221.104881c0.457,1.649 1.046,3.198 1.302,4.8c1.302,8.173 -0.427,15.373 -7.132,20.786c-1.133,0.914 -2.531,1.579 -3.906,2.097c-2.092,0.788 -3.43,0.253 -4.71,-1.598c-1.317,-1.906 -1.929,-4.125 -1.956,-6.347c-0.043,-3.592 0.49,-7.119 2.83,-10.114c3.502,-4.483 7.622,-8.064 13.572,-9.624z" fill="#FCDACF" id="svg_155"/>
|
||||
<path d="m511.621,227.118881c4.065,-0.154 8.13,-0.323 12.197,-0.451c0.98,-0.031 1.964,0.07 2.947,0.111c-1.078,3.612 -2.945,6.8 -5.242,10.06c-4.142,-2.56 -7.428,-5.713 -9.902,-9.72z" fill="#FCDACF" id="svg_156"/>
|
||||
<path d="m137.585,256.566881c-0.232,1.81 -0.466,3.621 -0.694,5.431c-0.152,1.21 -0.755,1.98 -2.018,2.163c-2.277,0.33 -4.455,1.03 -6.873,0.68c-4.262,-0.616 -6.739,-3.083 -7.888,-6.96c-1.494,-5.037 -0.23,-9.754 2.481,-14.042c2.013,-3.184 5.467,-3.625 8.715,-1.513c3.204,2.084 4.885,5.144 5.587,8.784c0.345,1.788 0.465,3.616 0.69,5.457z" fill="#FCF4F2" id="svg_163"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="180" height="140" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<g id="svg_232">
|
||||
<g id="svg_233">
|
||||
<g id="svg_234">
|
||||
<path d="m76.933,96.939l-6.102,30.954l-0.712,0l4.014,-32.367c0,0 2.8,2.119 2.8,1.413z" fill="#3F3945" id="svg_235"/>
|
||||
<path d="m81.83,96.939l-6.102,30.954l-0.712,0l4.014,-32.367c0.001,0 2.8,2.119 2.8,1.413z" fill="#3F3945" id="svg_236"/>
|
||||
</g>
|
||||
<g id="svg_237">
|
||||
<path d="m98.866,96.939l6.102,30.954l0.712,0l-4.014,-32.367c-0.001,0 -2.8,2.119 -2.8,1.413z" fill="#3F3945" id="svg_238"/>
|
||||
<path d="m93.968,96.939l6.102,30.954l0.712,0l-4.014,-32.367c0,0 -2.8,2.119 -2.8,1.413z" fill="#3F3945" id="svg_239"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m103.771,77.118l-3.711,-11.503c-1.121,-3.475 -4.355,-5.83 -8.007,-5.83l-1.946,0l-1.864,0l-4.498,0c-3.651,0 -6.886,2.355 -8.007,5.83l-3.711,11.503l-0.748,0c-1.852,0 -3.353,1.501 -3.353,3.353l0,5.309c0,6.848 5.551,12.399 12.399,12.399l7.917,0l1.864,0l5.365,0c6.848,0 12.399,-5.551 12.399,-12.399l0,-5.309c0,-1.852 -1.501,-3.353 -3.353,-3.353l-0.746,0z" fill="#9292AA" id="svg_240"/>
|
||||
<g id="svg_241">
|
||||
<g id="svg_242">
|
||||
<rect fill="#3F3945" height="30.178" id="svg_243" transform="matrix(0.374 -0.9274 0.9274 0.374 31.4581 419.478)" width="0.486" x="310.463764" y="-78.932512"/>
|
||||
</g>
|
||||
<g id="svg_244">
|
||||
<rect fill="#3F3945" height="0.486" id="svg_245" transform="matrix(0.9271 -0.3748 0.3748 0.9271 -46.1495 135.573)" width="30.178" x="119.351398" y="23.748997"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m103.942,77.118l-0.171,0l-3.711,-11.503c-1.121,-3.475 -4.355,-5.83 -8.007,-5.83l-1.946,0l-1.864,0l-4.498,0c-3.651,0 -6.886,2.355 -8.007,5.83l-3.711,11.503l-0.01,0l0,12.579c0,4.58 3.633,8.447 8.213,8.482c0.032,0 0.064,0 0.096,0l7.917,0l1.864,0l5.365,0c0.088,0 0.176,-0.001 0.264,-0.003c4.577,-0.096 8.206,-3.907 8.206,-8.485l0,-12.573z" fill="#CFCCDF" id="svg_246"/>
|
||||
<path d="m70.573,93.419c2.27,2.894 5.79,4.76 9.753,4.76l7.917,0l1.864,0l5.365,0c3.963,0 7.483,-1.866 9.753,-4.76l-34.652,0z" fill="#9292AA" id="svg_247"/>
|
||||
</g>
|
||||
<g id="svg_345">
|
||||
<polygon fill="#D7914E" id="svg_346" points="105.989990234375,118.74800109863281 106.02999877929688,125.25300598144531 106.30999755859375,127.28900146484375 109.94100952148438,127.48800659179688 110.94900512695312,126.00199890136719 110.30099487304688,117.8699951171875 "/>
|
||||
<rect fill="#3F3945" height="15.55" id="svg_347" width="21.717" x="76.059" y="78.354"/>
|
||||
<path d="m68.326,77.424c-0.902,-0.263 -1.782,-0.6 -2.629,-1.005c-1.383,-0.661 -2.848,-1.829 -2.702,-3.355c0.096,-1.006 0.995,-3.146 1.705,-3.865c1.915,-1.94 7.913,-13.124 9.945,-14.721c0.888,-0.698 9.498,-4.708 9.904,-2.435c0.077,0.43 -0.507,3.353 -0.629,3.772c-0.972,3.357 -3.12,4.862 -5.219,7.656c-1.819,2.422 -4.013,4.552 -5.728,7.048c0.632,1.036 1.411,2.12 2.385,2.843c3.033,2.25 6.071,4.491 9.116,6.724c-0.729,0.863 -1.156,1.903 -1.474,2.987c-5.004,-1.626 -9.621,-4.173 -14.674,-5.649z" fill="#D7914E" id="svg_348"/>
|
||||
<path d="m103.921,77.424c0.902,-0.263 1.782,-0.6 2.629,-1.005c1.383,-0.661 2.848,-1.829 2.702,-3.355c-0.096,-1.006 -0.995,-3.146 -1.705,-3.865c-1.915,-1.94 -7.913,-13.124 -9.945,-14.721c-0.888,-0.698 -9.498,-4.708 -9.904,-2.435c-0.077,0.43 0.507,3.353 0.629,3.772c0.972,3.357 3.12,4.862 5.219,7.656c1.819,2.422 4.013,4.552 5.728,7.048c-0.632,1.036 -1.411,2.12 -2.385,2.843c-3.033,2.25 -6.071,4.491 -9.116,6.724c0.729,0.863 1.156,1.903 1.474,2.987c5.004,-1.626 9.621,-4.173 14.674,-5.649z" fill="#D7914E" id="svg_349"/>
|
||||
<path d="m92.764,74.378c0,1.592 -1.348,2.882 -3.011,2.882c-1.663,0 -3.011,-1.29 -3.011,-2.882c0,-1.592 1.348,-2.882 3.011,-2.882c1.663,-0.001 3.011,1.29 3.011,2.882z" fill="#A0616B" id="svg_350"/>
|
||||
<g id="svg_351">
|
||||
<path d="m104.514,121.741l-0.079,-1.984c-0.668,-10.878 -2.243,-21.139 -3.588,-24.237c-1.649,-1.06 -7.638,-2.66 -13.028,-3.43l-4.808,-0.687l1.373,-9.615l4.808,0.687c4.352,0.622 14.889,2.484 18.744,6.338c1.096,1.095 4.427,1.511 6.193,30.349l-0.116,2.139l-9.499,0.44zm-3.804,-26.502l0,0l0,0z" fill="#3F3945" id="svg_352"/>
|
||||
</g>
|
||||
<g id="svg_353">
|
||||
<path d="m75.533,124.821l-0.868,-4.778c-0.162,-0.892 -2.938,-16.203 -3.432,-23.825c-0.527,-8.127 4.433,-11.396 7.13,-11.99l4.743,-1.044l2.088,9.485l-4.083,0.899c-0.103,0.258 -0.261,0.86 -0.186,2.021c0.458,7.067 3.258,22.503 3.296,22.716l0.869,4.778l-9.557,1.738z" fill="#3F3945" id="svg_354"/>
|
||||
</g>
|
||||
<path d="m99.534,58.12c-0.233,-3.776 -5.585,-7.346 -12.204,-7.346c-6.619,0 -12.365,1.538 -13.534,5.16c-0.585,1.811 2.424,21.23 2.401,22.425c-0.075,3.845 -0.257,5.671 -0.257,5.671l21.993,0.187c0,0 0.083,-2.617 -0.09,-5.498c-0.11,-1.83 1.812,-18.636 1.691,-20.599z" fill="#D7914E" id="svg_355"/>
|
||||
<path d="m86.871,53.229l-0.575,0c-1.537,0 -2.783,-1.246 -2.783,-2.783l0,-8.716l6.141,0l0,8.716c0,1.537 -1.246,2.783 -2.783,2.783z" fill="#E8A8B8" id="svg_356"/>
|
||||
<path d="m83.514,44.073c0.813,2.108 2.586,5.356 6.14,6.155l0,-8.499l-5.55,0l-0.59,0.259l0,2.085z" fill="#D98E9D" id="svg_357" opacity="0.5"/>
|
||||
<path d="m88.507,31.913l-1.4,0.188c-2.784,0.374 -4.738,2.933 -4.364,5.717l0.64,4.77c0.378,2.815 2.966,4.791 5.781,4.413l2.485,-0.334c2.154,-0.289 3.665,-2.269 3.376,-4.423l-0.801,-5.967c-0.373,-2.783 -2.933,-4.737 -5.717,-4.364z" fill="#E8A8B8" id="svg_358"/>
|
||||
<path d="m85.319,41.204c0.201,1.498 -0.85,2.874 -2.348,3.075c-1.497,0.201 -2.874,-0.85 -3.075,-2.348c-0.201,-1.498 0.85,-2.874 2.348,-3.075c1.497,-0.201 2.874,0.85 3.075,2.348z" fill="#E8A8B8" id="svg_359"/>
|
||||
<path d="m82.98,39.098c0.342,0.948 0.611,1.922 0.805,2.91c0.098,-0.121 0.301,-0.197 0.452,-0.161" fill="none" id="svg_360"/>
|
||||
<path d="m79.405,36.266c0.064,0.203 0.171,0.389 0.277,0.573c0.448,0.779 0.895,1.558 1.343,2.337c0.533,-0.343 1.228,-0.251 1.862,-0.25c0.168,0.696 0.253,1.418 0.279,2.134c0.23,-0.047 0.468,-0.064 0.703,-0.052c-0.081,-0.824 -0.109,-1.66 -0.135,-2.487c0.207,-0.25 0.524,-0.439 0.753,-0.669c-0.368,-0.4 -0.571,-0.938 -0.615,-1.479c-0.056,-0.687 0.26,-1.236 0.6,-1.836c0.34,-0.6 0.843,-1.132 1.487,-1.379c1.383,-0.531 2.731,-1.006 4.207,-0.891c0.582,0.045 1.221,0.088 1.716,0.399c0.765,-0.017 1.343,-0.848 1.326,-1.613c-0.017,-0.765 -0.473,-1.453 -1.003,-2.004c-0.374,-0.389 -0.797,-0.736 -1.275,-0.987c-0.595,-0.313 -1.261,-0.47 -1.928,-0.554c-1.757,-0.221 -3.575,0.058 -5.185,0.796c0.294,0.06 0.505,0.162 0.799,0.222c-0.498,0.05 -0.985,0.172 -1.483,0.222c-0.53,0.053 -1.065,0.108 -1.57,0.277c-0.922,0.309 -1.687,0.985 -2.259,1.772c-0.572,0.787 -0.969,1.684 -1.345,2.581c-0.163,0.389 -0.326,0.803 -0.264,1.22c0.051,0.347 0.254,0.652 0.469,0.93c0.272,0.349 0.797,0.732 1.241,0.738z" fill="#3F3945" id="svg_361"/>
|
||||
<path d="m90.769,42.967l-2.971,-0.714c0,0 0.493,1.603 1.441,1.755c0.947,0.15 1.53,-1.041 1.53,-1.041z" fill="#FFFFFF" id="svg_362"/>
|
||||
<path d="m89.656,38.335c0.04,0.302 -0.171,0.58 -0.473,0.62c-0.302,0.04 -0.58,-0.171 -0.62,-0.473c-0.04,-0.302 0.171,-0.58 0.473,-0.62c0.302,-0.041 0.579,0.171 0.62,0.473z" fill="#3F3945" id="svg_363"/>
|
||||
<path d="m93.483,37.821c0.04,0.302 -0.171,0.58 -0.473,0.62c-0.302,0.041 -0.579,-0.171 -0.62,-0.473c-0.04,-0.302 0.171,-0.58 0.473,-0.62c0.301,-0.041 0.579,0.171 0.62,0.473z" fill="#3F3945" id="svg_364"/>
|
||||
<polygon fill="#FFFFFF" id="svg_365" points="86.84201049804688,52.28199768066406 82.7130126953125,51.47599792480469 83.10000610351562,54.055999755859375 87.48599243164062,60.11799621582031 91.87100219726562,54.82899475097656 89.93600463867188,51.08900451660156 "/>
|
||||
<path d="m83.382,49.978c0,0 3.161,1.747 6.287,0l0.359,1.527l-0.773,3.712l-2.315,-2.559l-1.996,2.689l-2.102,-3.483l0.54,-1.886z" fill="#EFF2F7" id="svg_366"/>
|
||||
<g id="svg_367">
|
||||
<path d="m77.129,132.034c0,0 -0.359,1.321 -0.166,2.048c0.193,0.728 0.945,2.038 2.77,2.094c1.825,0.057 4.203,0.173 4.061,-4.926l-6.665,0.784z" fill="#525560" id="svg_368"/>
|
||||
<path d="m79.816,123.896c0.162,-0.288 -0.354,-2.445 1.152,-2.371c1.506,0.074 1.678,0.793 1.567,1.853c-0.111,1.06 -0.807,5.388 -0.807,5.388l-2.112,0.193l0.2,-5.063z" fill="#3F3945" id="svg_369"/>
|
||||
<path d="m79.5,122.394c0,0 -0.642,3.727 -0.709,3.871c-0.067,0.145 -1.992,5.885 -1.714,6.789c0.2,0.651 1.325,1.583 3.732,1.492c1.852,-0.07 3.219,-1.763 2.945,-3.596c-0.243,-1.626 -0.549,-4.7 -0.547,-4.746c0.005,-0.089 -0.298,-3.866 -0.298,-3.866s-1.764,2.646 -1.904,5.904l-0.65,-0.035c-0.001,0.001 0.162,-5.996 -0.855,-5.813z" fill="#3F3945" id="svg_370"/>
|
||||
<g id="svg_371">
|
||||
<path d="m82.319,124.27c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.056 -2.13,-0.157 -2.176,-0.118c-0.101,0.085 -0.252,0.073 -0.339,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.506,-0.432 1.748,-0.955 2.831,0.148c0.093,0.095 0.092,0.247 -0.003,0.34c-0.046,0.045 -0.107,0.068 -0.168,0.068z" fill="#525560" id="svg_372"/>
|
||||
</g>
|
||||
<g id="svg_373">
|
||||
<path d="m82.161,125.502c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.086 -0.252,0.073 -0.339,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.506,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.092,0.247 -0.003,0.34c-0.046,0.046 -0.107,0.069 -0.168,0.069z" fill="#525560" id="svg_374"/>
|
||||
</g>
|
||||
<g id="svg_375">
|
||||
<path d="m82.003,126.735c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.037,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.085 -0.252,0.073 -0.339,-0.028c-0.086,-0.102 -0.074,-0.252 0.027,-0.338c0.505,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.091,0.247 -0.003,0.34c-0.046,0.046 -0.107,0.069 -0.168,0.069z" fill="#525560" id="svg_376"/>
|
||||
</g>
|
||||
<g id="svg_377">
|
||||
<path d="m81.845,127.968c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.086 -0.252,0.073 -0.338,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.505,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.092,0.247 -0.003,0.34c-0.048,0.046 -0.109,0.069 -0.169,0.069z" fill="#525560" id="svg_378"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_379">
|
||||
<path d="m109.648,124.917c0,0 -0.906,-1.496 -0.021,-1.749c0.885,-0.253 1.434,0.241 1.434,0.241l2.491,2.198l1.365,1.652l-1.831,-0.581l-3.438,-1.761z" fill="#3F3945" id="svg_380"/>
|
||||
<path d="m106.055,124.579c0,0 0.944,1.669 1.662,1.261c0.406,-0.231 0.985,-1.755 1.573,-1.617c0.588,0.138 2.961,1.476 3.837,1.794c0.876,0.318 -2.173,-2.574 -2.173,-2.574s2.521,1.913 3.304,2.2c0.784,0.286 7.197,1.07 7.591,2.986l0.064,1.261c0,0 -1.789,2.128 -6.783,1.15c-4.995,-0.978 -7.744,-0.907 -8.65,-1.133c-0.907,-0.227 -1.107,-5.191 -0.425,-5.328z" fill="#3F3945" id="svg_381"/>
|
||||
<path d="m116.03,130.259c-2.331,-0.074 -6.343,-1.231 -7.828,-1.202c-0.855,0.017 -1.737,-0.166 -2.331,-0.325c0.14,0.637 0.344,1.108 0.609,1.174c0.907,0.226 3.656,0.155 8.65,1.133c4.995,0.978 6.783,-1.15 6.783,-1.15l-0.064,-1.261c-0.423,1.066 -3.488,1.705 -5.819,1.631z" fill="#525560" id="svg_382"/>
|
||||
<g id="svg_383">
|
||||
<path d="m111.068,126.016c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.102 0.611,-0.059 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.176,-0.107 -0.34,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.877,1.073c-0.032,0.086 -0.108,0.142 -0.192,0.153z" fill="#525560" id="svg_384"/>
|
||||
</g>
|
||||
<g id="svg_385">
|
||||
<path d="m111.893,126.603c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.296,-0.102 0.611,-0.059 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.176,-0.107 -0.34,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.878,1.073c-0.032,0.086 -0.107,0.142 -0.191,0.153z" fill="#525560" id="svg_386"/>
|
||||
</g>
|
||||
<g id="svg_387">
|
||||
<path d="m112.861,126.952c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.1 0.612,-0.058 0.908,0.124c0.113,0.069 0.149,0.217 0.079,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.175,-0.108 -0.339,-0.133 -0.502,-0.078c-0.435,0.147 -0.781,0.824 -0.878,1.073c-0.032,0.085 -0.107,0.141 -0.191,0.152z" fill="#525560" id="svg_388"/>
|
||||
</g>
|
||||
<g id="svg_389">
|
||||
<path d="m113.865,127.252c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.103 0.611,-0.058 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.175,-0.107 -0.339,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.877,1.073c-0.033,0.086 -0.108,0.141 -0.192,0.153z" fill="#525560" id="svg_390"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m85.292,83.099c-0.866,-0.069 -1.783,-0.265 -2.389,-0.888c-0.606,-0.622 0.539,-1.594 1.239,-2.108c0.968,0.44 2.076,-0.063 3.133,-0.182c0.641,-0.072 1.289,0.003 1.926,0.105c0.817,0.131 1.636,0.311 2.374,0.685c0.738,0.374 1.394,0.96 1.713,1.723c0.075,0.179 -0.024,0.35 -0.217,0.353c-0.194,0.003 -0.358,-0.133 -0.51,-0.253c-0.477,-0.376 -1.026,-0.66 -1.608,-0.834c-0.105,-0.031 -0.185,0.144 -0.086,0.191c1.456,0.689 1.801,1.611 1.775,1.694c-0.028,0.09 -0.139,0.124 -0.234,0.122c-0.171,-0.003 -0.452,-0.259 -0.594,-0.354c-0.603,-0.399 -1.146,-0.472 -1.146,-0.472s-0.619,0.036 0.899,0.801c0.187,0.094 0.381,0.221 0.443,0.421c0.011,0.035 0.017,0.073 0.01,0.109c-0.008,0.043 -0.035,0.081 -0.067,0.112c-0.098,0.093 -0.242,0.124 -0.376,0.112c-0.134,-0.011 -0.262,-0.06 -0.388,-0.109c-0.618,-0.237 -1.235,-0.475 -1.853,-0.712c0.163,0.103 1.282,0.662 1.403,0.813c0.047,0.059 0.092,0.131 0.077,0.204c-0.023,0.109 -0.157,0.146 -0.268,0.157c-0.743,0.074 -2.45,-0.542 -3.146,-0.813c-0.695,-0.269 -1.365,-0.818 -2.11,-0.877z" fill="#E8A8B8" id="svg_391"/>
|
||||
<g id="svg_392">
|
||||
<path d="m87.677,40.908c-1.249,0 -2.339,-0.926 -2.51,-2.197c-0.09,-0.671 0.086,-1.337 0.497,-1.875c0.411,-0.538 1.006,-0.884 1.677,-0.974c1.386,-0.186 2.663,0.79 2.849,2.174c0.186,1.385 -0.79,2.662 -2.175,2.849c-0.113,0.016 -0.226,0.023 -0.338,0.023zm0.003,-4.826c-0.102,0 -0.203,0.007 -0.306,0.021c-0.606,0.081 -1.145,0.394 -1.516,0.88c-0.372,0.487 -0.531,1.089 -0.45,1.695c0.169,1.252 1.324,2.137 2.575,1.966c1.252,-0.168 2.134,-1.324 1.966,-2.576c-0.154,-1.148 -1.139,-1.986 -2.269,-1.986z" fill="#3F3945" id="svg_393"/>
|
||||
</g>
|
||||
<g id="svg_394">
|
||||
<path d="m93.067,40.184c-0.552,0 -1.086,-0.179 -1.532,-0.52c-0.538,-0.411 -0.884,-1.006 -0.974,-1.677c-0.186,-1.385 0.789,-2.663 2.174,-2.849c1.384,-0.184 2.663,0.79 2.849,2.175c0.09,0.671 -0.086,1.336 -0.497,1.874c-0.411,0.538 -1.006,0.884 -1.678,0.974c-0.114,0.015 -0.228,0.023 -0.342,0.023zm0.007,-4.826c-0.102,0 -0.203,0.007 -0.306,0.021c-1.252,0.168 -2.133,1.324 -1.965,2.576c0.082,0.607 0.394,1.145 0.88,1.517c0.488,0.372 1.094,0.532 1.695,0.449c0.607,-0.081 1.146,-0.394 1.517,-0.88c0.372,-0.487 0.531,-1.088 0.45,-1.695c-0.156,-1.15 -1.141,-1.988 -2.271,-1.988z" fill="#3F3945" id="svg_395"/>
|
||||
</g>
|
||||
<g id="svg_396">
|
||||
<rect fill="#3F3945" height="0.242" id="svg_397" transform="matrix(0.9187 -0.3949 0.3949 0.9187 -20.6624 136.864)" width="2.505" x="133.483205" y="-48.057873"/>
|
||||
</g>
|
||||
<g id="svg_398">
|
||||
<path d="m90.262,87.938l20.542,0l0,-1.506l-20.542,0c-0.416,0 -0.753,0.337 -0.753,0.753l0,0c0,0.415 0.337,0.753 0.753,0.753z" fill="#A8A5C4" id="svg_399"/>
|
||||
<path d="m92.011,87.938l19.809,0c0.876,0 1.643,-0.59 1.867,-1.436l3.496,-13.172c0.325,-1.226 -0.599,-2.428 -1.867,-2.428l-17.297,0c-0.876,0 -1.643,0.589 -1.867,1.436l-4.141,15.6z" fill="#CFCCDF" id="svg_400"/>
|
||||
<path d="m115.315,70.902c1.268,0 2.193,1.202 1.867,2.428l-3.496,13.172c-0.108,0.406 -0.342,0.751 -0.652,1.002l-12.041,-16.601l14.322,0l0,-0.001z" fill="#CFCCDF" id="svg_401"/>
|
||||
<path d="m103.676,80.586c-0.217,0.859 0.324,1.556 1.208,1.556c0.884,0 1.776,-0.696 1.993,-1.556c0.217,-0.859 -0.324,-1.556 -1.208,-1.556c-0.884,0 -1.776,0.697 -1.993,1.556z" fill="#FFFFFF" id="svg_402"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_403">
|
||||
<path d="m126.823,42.216l-2.049,-3.268c0,0 0.64,-1.378 -0.06,-2.56c-0.699,-1.182 -1.769,-3.318 -2.053,-3.046s0.287,2.342 0.287,2.342s-1.883,-3.209 -3.159,-3.401c-1.276,-0.193 1.325,3.309 1.325,3.309s-2.943,-3.063 -3.122,-2.455s1.769,3.318 1.933,3.488c0.164,0.17 -2.237,-2.215 -2.411,-1.829c-0.175,0.386 2.694,4.394 3.295,4.906c0.602,0.513 5.102,3.607 5.102,3.607l0.912,-1.093z" fill="#E8A8B8" id="svg_404"/>
|
||||
<path d="m152.166,57.969l-0.035,-0.084l-9.44,-2.281l-1.141,4.72c-0.16,0.665 -4.486,17.513 -4.967,25.197c-0.286,4.586 11.326,34.167 12.018,35.98l0.099,1.43l-1.833,2.344c0,0 1.511,0.448 3.066,0.122c1.555,-0.326 2.9,-1.025 2.9,-1.025l-1.123,-4.079c0,0 -3.448,-21.082 -4.531,-27.574c-0.438,-2.627 -1.976,-4.794 -1.954,-5.541c0.256,-4.092 3.397,-11.984 5.887,-17.698c-0.193,5.235 -0.476,12.31 -0.735,16.441c-0.282,4.514 4.782,36.055 4.784,37.293l0.006,-0.012l-0.008,0.041l-2.354,2.58c0,0 1.511,0.448 3.066,0.122c1.555,-0.326 2.573,-1.911 2.573,-1.911s1.925,-36.981 2.142,-40.473c0.463,-7.413 0.469,-20.196 0.469,-20.737l0,-4.856l-8.889,0l0,0.001z" fill="#E8A8B8" id="svg_405"/>
|
||||
<path d="m141.222,34.996c-0.081,1.39 0.046,2.794 -0.178,4.168c-0.224,1.374 -0.881,2.774 -2.104,3.438c-1.778,0.965 -3.985,0.055 -5.662,-1.077c-1.677,-1.132 -3.352,-2.546 -5.374,-2.628c-2.174,-0.088 -4.267,1.672 -4.554,3.829c-0.278,2.09 1.001,4.141 2.706,5.38c1.706,1.239 3.786,1.824 5.818,2.387c2.631,0.728 5.511,1.443 8.015,0.354c1.623,-0.706 2.845,-2.08 3.982,-3.437c2.616,-3.121 5.148,-6.53 5.932,-10.527c0.536,-2.731 0.007,-8.52 -3.222,-9.671c-3.815,-1.359 -5.207,5.17 -5.359,7.784z" fill="#5387C4" id="svg_406"/>
|
||||
<path d="m163.17,56.037l0.452,10.385l-2.032,17.61l-25.06,-1.355l0.249,-2.57c0.734,-7.587 2.437,-15.048 5.067,-22.202l0.846,-2.301l20.478,0.433z" fill="#DC614B" id="svg_407"/>
|
||||
<path d="m166.608,27.766c-1.695,-1.277 -2.558,-3.404 -2.871,-5.503c-0.313,-2.1 -0.152,-4.237 -0.217,-6.359c-0.046,-1.511 -0.218,-3.057 -0.898,-4.407c-0.68,-1.35 -1.955,-2.479 -3.46,-2.629c-0.924,-0.092 -1.869,0.183 -2.779,-0.006c-0.198,0.405 -0.265,0.944 0.064,1.251c0.33,0.307 0.747,0.518 1.037,0.863c0.396,0.469 0.501,1.116 0.519,1.729c0.082,2.845 -1.37,5.539 -1.477,8.384c-0.052,1.381 0.227,2.787 0.901,3.993c0.674,1.206 1.758,2.199 3.058,2.666c1.841,0.659 4.217,0.449 6.123,0.018z" fill="#3F3945" id="svg_408"/>
|
||||
<path d="m148.76,25.747l1.141,-0.145c0,0 1.821,1.121 2.287,1.037c1.09,-0.195 1.289,-0.589 1.289,-0.589s1.077,-0.719 1.563,-0.685c6.145,0.436 5.828,4.114 6.358,6.234c0.928,3.716 -2.395,9.115 -2.395,9.115s4.126,13.236 3.942,15.548l-11.556,1.9c-3.17,0.521 -6.421,-0.151 -9.124,-1.887l0,0l1.264,-11.978l-0.833,-2.272c-1.126,-3.07 -1.414,-6.387 -0.821,-9.603c0.253,-1.368 0.523,-2.637 0.751,-3.252c0.644,-1.736 4.295,-3.189 6.134,-3.423z" fill="#5387C4" id="svg_409"/>
|
||||
<g id="svg_410">
|
||||
<g id="svg_411">
|
||||
<path d="m152.026,28.777l0,0c1.696,0 3.07,-1.375 3.07,-3.07l0,-8.061c0,-0.203 -0.165,-0.367 -0.367,-0.367l-5.406,0c-0.203,0 -0.367,0.164 -0.367,0.367l0,8.061c-0.001,1.695 1.374,3.07 3.07,3.07z" fill="#E8A8B8" id="svg_412"/>
|
||||
<path d="m155.096,19.621c-0.813,2.108 -2.586,5.356 -6.141,6.155l0,-8.499l5.55,0l0.59,0.259l0,2.085l0.001,0z" fill="#D98E9D" id="svg_413" opacity="0.5"/>
|
||||
</g>
|
||||
<path d="m147.55,8.2l1.413,0c2.809,0 5.086,2.277 5.086,5.086l0,4.812c0,2.84 -2.302,5.143 -5.143,5.143l-2.507,0c-2.173,0 -3.934,-1.761 -3.934,-3.934l0,-6.021c0,-2.809 2.277,-5.086 5.085,-5.086z" fill="#E8A8B8" id="svg_414"/>
|
||||
<circle cx="154.682" cy="16.984" fill="#E8A8B8" id="svg_415" r="2.736"/>
|
||||
<path d="m153.984,14.586c-0.213,0.985 -0.35,1.986 -0.41,2.991c-0.114,-0.107 -0.325,-0.155 -0.47,-0.099" fill="none" id="svg_416"/>
|
||||
<path d="m145.848,19.027l2.962,-0.753c0,0 -0.472,1.61 -1.418,1.773c-0.947,0.163 -1.544,-1.02 -1.544,-1.02z" fill="#FFFFFF" id="svg_417"/>
|
||||
<path d="m146.767,14.676c0,0.305 0.247,0.552 0.552,0.552c0.305,0 0.552,-0.247 0.552,-0.552c0,-0.305 -0.247,-0.552 -0.552,-0.552c-0.305,0 -0.552,0.247 -0.552,0.552z" fill="#3F3945" id="svg_418"/>
|
||||
<path d="m142.905,14.676c0,0.305 0.247,0.552 0.552,0.552c0.305,0 0.552,-0.247 0.552,-0.552c0,-0.305 -0.247,-0.552 -0.552,-0.552c-0.305,0 -0.552,0.247 -0.552,0.552z" fill="#3F3945" id="svg_419"/>
|
||||
<path d="m151.485,8.973c0,0 -1.112,2.88 0.312,4.687c0.434,0.551 0.784,2.299 0.931,2.984c0.153,0 0.321,-1.654 1.2,-2.207c0.304,-0.195 1.535,-0.114 1.535,-0.114s0.673,-1.691 0.818,-2.126c0.185,-0.554 0.492,-1.063 0.666,-1.62c0.581,-1.859 -0.652,-4.078 -2.537,-4.567c-0.551,-0.143 -1.122,-0.216 -1.691,-0.236c-12.074,-0.429 -10.65,5.234 -10.605,6.527c5.007,0.668 9.371,-3.328 9.371,-3.328z" fill="#3F3945" id="svg_420"/>
|
||||
</g>
|
||||
<path d="m163.38,57.685c0.802,-0.333 1.613,-0.804 1.996,-1.584c0.383,-0.78 -1.006,-1.349 -1.831,-1.621c-0.785,0.718 -1.993,0.582 -3.035,0.797c-0.632,0.13 -1.225,0.402 -1.799,0.696c-0.736,0.377 -1.46,0.802 -2.046,1.386c-0.586,0.584 -1.028,1.344 -1.095,2.169c-0.016,0.193 0.131,0.325 0.316,0.269c0.185,-0.057 0.299,-0.237 0.407,-0.398c0.337,-0.505 0.771,-0.945 1.27,-1.29c0.091,-0.063 0.221,0.08 0.141,0.155c-1.171,1.105 -1.214,2.089 -1.164,2.16c0.055,0.077 0.171,0.074 0.26,0.044c0.161,-0.056 0.349,-0.387 0.455,-0.52c0.45,-0.566 0.944,-0.804 0.944,-0.804s0.6,-0.157 -0.607,1.04c-0.148,0.147 -0.293,0.329 -0.291,0.538c0,0.037 0.006,0.074 0.024,0.106c0.021,0.038 0.059,0.066 0.098,0.085c0.122,0.058 0.268,0.043 0.392,-0.009c0.124,-0.052 0.231,-0.139 0.335,-0.223c0.514,-0.417 1.027,-0.834 1.541,-1.251c-0.123,0.149 -1.014,1.026 -1.082,1.207c-0.026,0.07 -0.047,0.153 -0.009,0.218c0.056,0.096 0.195,0.09 0.303,0.066c0.73,-0.16 2.161,-1.274 2.74,-1.746c0.58,-0.473 1.047,-1.203 1.737,-1.49z" fill="#E8A8B8" id="svg_421"/>
|
||||
<path d="m163.052,29.686c2.636,2.75 5.273,5.5 7.909,8.25c2.443,2.548 5.045,5.473 5.038,9.003c-0.005,2.685 -1.557,5.122 -3.318,7.148c-1.249,1.437 -2.708,2.804 -4.535,3.338c-1.827,0.535 -4.086,-0.005 -5.022,-1.663c-1.015,-1.796 -0.176,-4.044 0.782,-5.871c0.958,-1.827 2.065,-3.848 1.519,-5.837c-0.388,-1.416 -1.538,-2.469 -2.585,-3.498c-2.957,-2.908 -5.618,-6.529 -6.459,-10.668c-0.357,-1.759 -0.916,-3.975 1.325,-3.962c1.979,0.012 4.113,2.473 5.346,3.76z" fill="#5387C4" id="svg_422"/>
|
||||
<path d="m148.515,121.53l-1.375,3.425l-3.653,1.661c-1.819,0.738 -1.155,1.634 -1.155,1.634l11.019,-0.762l-1.007,-6.275l-3.829,0.317z" fill="#3F3945" id="svg_423"/>
|
||||
<path d="m152.805,125.823l-3.653,1.661c-1.819,0.738 -1.155,1.634 -1.155,1.634l10.017,-0.755l0.776,-6.717l-3.943,-0.403l0.317,1.9l-2.359,2.68z" fill="#3F3945" id="svg_424"/>
|
||||
</g>
|
||||
<g id="svg_425">
|
||||
<path d="m24.854,55.079c4.549,0.076 9.099,0.153 13.648,0.229c1.546,0.026 3.103,0.051 4.624,-0.227c3.221,-0.588 11.197,-8.695 13.435,-10.265c0.838,0.743 1.646,1.432 2.484,2.174c-2.347,4.501 -3.586,7.027 -6.704,11.032c-0.932,1.197 -1.938,2.402 -3.293,3.082c-1.054,0.528 -2.246,0.7 -3.417,0.839c-4.995,0.59 -10.047,0.696 -15.063,0.317c-0.661,-0.05 -1.359,-0.122 -1.888,-0.522c-0.461,-0.348 -0.723,-0.894 -0.967,-1.418c-1.162,-2.485 -1.699,-2.754 -2.859,-5.241z" fill="#EDA956" id="svg_426"/>
|
||||
<path d="m27.581,35.701c0,0 -7.203,0.647 -9.146,7.365c-1.943,6.718 1.781,9.308 1.781,9.308l1.376,0.081l0.648,-1.295l0.486,1.295l8.579,0.971l0.567,-0.648l0.405,0.648c0,0 4.209,-1.295 3.966,-4.694c-0.243,-3.399 -0.081,-5.018 -1.133,-7.041c-1.052,-2.023 -2.914,-4.452 -2.914,-4.452l-4.615,-1.538z" fill="#563238" id="svg_427"/>
|
||||
<g id="svg_428">
|
||||
<path d="m35.733,93.744l4.374,34.965l0.712,0l-2.287,-36.243c0.001,0.001 -2.799,1.917 -2.799,1.278z" fill="#3F3945" id="svg_429"/>
|
||||
<path d="m45.446,92.449l16.677,36.26l0.712,0l-14.589,-37.538c0,0.001 -2.8,1.917 -2.8,1.278z" fill="#3F3945" id="svg_430"/>
|
||||
<path d="m23.661,92.449l-16.677,36.26l-0.712,0l14.589,-37.538c0.001,0.001 2.8,1.917 2.8,1.278z" fill="#3F3945" id="svg_431"/>
|
||||
<path d="m31.755,93.744l-4.374,34.965l-0.712,0l2.287,-36.243c-0.001,0.001 2.799,1.917 2.799,1.278z" fill="#3F3945" id="svg_432"/>
|
||||
<g id="svg_433">
|
||||
<rect fill="#3F3945" height="0.485" id="svg_434" transform="matrix(0.4675 -0.884 0.884 0.4675 -9.1243 344.263)" width="19.406" x="230.048508" y="-67.858469"/>
|
||||
</g>
|
||||
<g id="svg_435">
|
||||
<rect fill="#3F3945" height="22.897" id="svg_436" transform="matrix(0.6928 -0.7211 0.7211 0.6928 -43.4672 259.313)" width="0.485" x="174.469854" y="-57.134714"/>
|
||||
</g>
|
||||
<g id="svg_437">
|
||||
<rect fill="#3F3945" height="0.486" id="svg_438" transform="matrix(0.6874 -0.7263 0.7263 0.6874 -50.5056 245.956)" width="24.505" x="141.987717" y="-45.752078"/>
|
||||
</g>
|
||||
<g id="svg_439">
|
||||
<polygon fill="#3F3945" id="svg_440" points="30.022003173828125,107.79200744628906 20.957000732421875,92.89999389648438 21.37298583984375,92.64700317382812 30.43798828125,107.53900146484375 "/>
|
||||
</g>
|
||||
<path d="m18.899,55.868c0,0 10.684,-2.59 14.245,19.101l0.56,2.427c0.816,3.538 3.897,6.093 7.525,6.241l4.676,0.191c3.044,0.124 5.701,2.099 6.697,4.978l1.262,3.645l2.586,0c0,0 -0.971,5.18 -6.151,5.18c-5.18,0 -17.155,0 -17.155,0l-11.007,0c0,0 -9.065,0.648 -10.684,-11.331c-1.619,-11.979 -7.447,-31.08 7.446,-30.432z" fill="#5A8ECC" id="svg_441"/>
|
||||
<path d="m59.2,124.768l2.354,2.58l3.653,1.66c1.819,0.738 1.155,1.635 1.155,1.635l-10.017,-0.755l-0.757,-3.442l3.612,-1.678z" fill="#3F3945" id="svg_442"/>
|
||||
<path d="m55.342,116.368l3.858,8.4l2.354,2.58c0,0 -1.511,0.448 -3.066,0.122c-1.555,-0.326 -2.9,-1.025 -2.9,-1.025l-4.08,-6.569l-0.47,-3.327l4.304,-0.181z" fill="#E8A8B8" id="svg_443"/>
|
||||
<polygon fill="#E8A8B8" id="svg_444" points="46.364990234375,119.7550048828125 43.110992431640625,122.447998046875 43.89599609375,126.93699645996094 46.701995849609375,128.0590057373047 41.989013671875,127.94700622558594 39.519989013671875,121.55099487304688 42.217010498046875,118.19599914550781 "/>
|
||||
<path d="m46.702,128.059c0,0 0.991,0.013 1.331,0.391c0.34,0.377 0,0.944 0,0.944l-7.615,-0.213l-2.581,-7.406l1.683,-0.224l2.469,6.396l4.713,0.112z" fill="#3F3945" id="svg_445"/>
|
||||
<g id="svg_446">
|
||||
<path d="m47.267,120.77l-7.793,-3.96l1.98,-3.896c3.337,-6.565 6.933,-14.445 8.429,-18.673c-4.337,-0.627 -12.462,-1.279 -19.433,-1.635l-4.365,-0.222l0.446,-8.73l4.365,0.222c23.576,1.202 25.569,2.862 26.639,3.754c3.192,2.661 2.048,6.952 -2.774,17.706c-2.604,5.804 -5.396,11.306 -5.513,11.538l-1.981,3.896zm5.396,-25.969l0,0l0,0z" fill="#9D3542" id="svg_447"/>
|
||||
</g>
|
||||
<path d="m38.418,93.753l-8.349,0.463c-4.148,0 -9.259,-3.453 -9.259,-7.601l-0.124,-2.204l16.226,-1.472l1.506,10.814z" fill="#9D3542" id="svg_448"/>
|
||||
<g id="svg_449">
|
||||
<path d="m49.849,123.164l-0.339,-4.357c-0.68,-8.739 -2.282,-19.42 -3.618,-21.755c-1.457,-1.115 -7.727,-2.828 -13.42,-3.641l-4.326,-0.618l1.236,-8.653l4.327,0.618c4.303,0.615 14.717,2.449 18.469,6.2c3.528,3.527 5.236,16.723 6.047,27.171l0.339,4.357l-8.715,0.678zm-4.032,-26.233l0,0l0,0z" fill="#9D3542" id="svg_450"/>
|
||||
</g>
|
||||
<path d="m30.622,55.47c0,0 -6.79,-0.266 -7.257,-0.224c-1.32,0.117 -3.098,0.479 -3.965,0.973c-1.958,1.114 -2.138,2.935 -2.305,4.379c-0.226,1.95 4.137,11.453 4.137,11.453l-1.692,8.038c0,0 -0.26,3.501 0,5.711l17.557,-2.327l-1.423,-6.738l-1.457,-8.822c0,0 2.514,-4.672 0.89,-6.394c-3.922,-4.16 -4.485,-6.049 -4.485,-6.049z" fill="#EDA956" id="svg_451"/>
|
||||
<g id="svg_452">
|
||||
<path d="m26.036,58.4l0,0c-1.314,0 -2.378,-1.065 -2.378,-2.379l0,-10.132c0,-0.157 0.127,-0.285 0.285,-0.285l4.188,0c0.157,0 0.285,0.127 0.285,0.285l0,10.132c-0.001,1.314 -1.066,2.379 -2.38,2.379z" fill="#E8A8B8" id="svg_453"/>
|
||||
<path d="m23.658,51.307c0.63,1.633 2.003,4.149 4.757,4.768l0,-10.471l-4.3,0l-0.457,0.201l0,5.502z" fill="#D98E9D" id="svg_454" opacity="0.5"/>
|
||||
</g>
|
||||
<path d="m30.232,37.823l-1.413,0c-2.809,0 -5.086,2.277 -5.086,5.086l0,4.812c0,2.84 2.302,5.143 5.143,5.143l0.659,0c3.193,0 5.782,-2.589 5.782,-5.782l0,-4.173c0,-2.809 -2.277,-5.086 -5.085,-5.086z" fill="#E8A8B8" id="svg_455"/>
|
||||
<path d="m25.836,46.607c0,1.511 -1.225,2.736 -2.736,2.736s-2.736,-1.225 -2.736,-2.736c0,-1.511 1.225,-2.736 2.736,-2.736s2.736,1.225 2.736,2.736z" fill="#E8A8B8" id="svg_456"/>
|
||||
<path d="m23.798,42.265c0.213,0.985 0.35,1.986 0.41,2.991c0.114,-0.106 0.325,-0.155 0.47,-0.099" fill="none" id="svg_457"/>
|
||||
<path d="m31.935,48.65l-2.962,-0.753c0,0 0.472,1.61 1.418,1.773c0.946,0.163 1.544,-1.02 1.544,-1.02z" fill="#FFFFFF" id="svg_458"/>
|
||||
<circle cx="30.464" cy="44.299" fill="#3F3945" id="svg_459" r="0.552"/>
|
||||
<path d="m34.877,44.299c0,0.305 -0.247,0.552 -0.552,0.552c-0.305,0 -0.552,-0.247 -0.552,-0.552c0,-0.305 0.247,-0.552 0.552,-0.552c0.305,0 0.552,0.247 0.552,0.552z" fill="#3F3945" id="svg_460"/>
|
||||
<path d="m11.452,86.3c-0.717,-5.305 -2.256,-12.005 -2.459,-17.77l0,0l5.055,14.542c1.072,3.084 3.979,5.151 7.244,5.151l3.238,0c2.861,0 5.44,1.725 6.532,4.37l2.081,5.039l-11.007,0c0,-0.001 -9.065,0.647 -10.684,-11.332z" fill="#6F9EDE" id="svg_461"/>
|
||||
<path d="m23.69,89.615c-0.835,-0.239 -1.695,-0.614 -2.165,-1.344c-0.47,-0.73 0.845,-1.456 1.633,-1.82c0.862,0.623 2.047,0.35 3.107,0.443c0.643,0.057 1.263,0.258 1.867,0.485c0.774,0.29 1.542,0.63 2.191,1.142c0.649,0.513 1.176,1.218 1.337,2.029c0.038,0.19 -0.093,0.338 -0.283,0.303c-0.19,-0.035 -0.324,-0.201 -0.449,-0.349c-0.393,-0.463 -0.874,-0.851 -1.41,-1.136c-0.097,-0.052 -0.21,0.105 -0.122,0.17c1.29,0.964 1.446,1.936 1.404,2.013c-0.046,0.083 -0.161,0.093 -0.254,0.073c-0.167,-0.037 -0.391,-0.344 -0.512,-0.464c-0.512,-0.51 -1.03,-0.69 -1.03,-0.69s-0.614,-0.087 0.722,0.964c0.164,0.129 0.329,0.293 0.351,0.5c0.004,0.037 0.003,0.075 -0.012,0.109c-0.017,0.041 -0.051,0.072 -0.088,0.096c-0.114,0.072 -0.262,0.074 -0.391,0.036c-0.129,-0.038 -0.245,-0.111 -0.359,-0.184c-0.558,-0.355 -1.116,-0.71 -1.675,-1.066c0.14,0.133 1.125,0.903 1.214,1.075c0.034,0.067 0.065,0.147 0.034,0.216c-0.044,0.102 -0.183,0.112 -0.294,0.101c-0.743,-0.075 -2.293,-1.017 -2.922,-1.421c-0.628,-0.404 -1.176,-1.076 -1.894,-1.281z" fill="#E8A8B8" id="svg_462"/>
|
||||
<path d="m19.326,56.368c0,0 -7.823,5.529 -9.727,14.837c-1.904,9.307 4.138,16.479 11.694,17.018l2.888,-1.432c0,0 -9.08,-9.595 -8.445,-12.768c0.635,-3.173 5.921,-10.222 5.921,-10.222s-0.521,-8.173 -2.331,-7.433z" fill="#EDA956" id="svg_463"/>
|
||||
</g>
|
||||
<path d="m24.036,44.593c0,0 7.272,-1.157 8.422,-4.988c0,0 0.668,3.521 2.86,3.303c0,0 1.247,-7.694 -6.037,-7.37c-6.637,0.295 -7.568,3.739 -7.313,8.592c0,0.001 1.62,-0.817 2.068,0.463z" fill="#563238" id="svg_464"/>
|
||||
<path d="m23.442,47.902c0,0.278 -0.226,0.504 -0.504,0.504c-0.278,0 -0.504,-0.226 -0.504,-0.504c0,-0.278 0.226,-0.504 0.504,-0.504c0.278,0 0.504,0.225 0.504,0.504z" fill="#FFFFFF" id="svg_465"/>
|
||||
<path d="m56.561,44.817l2.687,-2.855c0,0 -0.068,-2.583 0.408,-4.01c0.476,-1.427 0.476,-1.427 0.476,-1.427s0.748,-0.136 0.68,0.952c-0.068,1.088 0.068,2.039 0.068,2.039s3.195,-5.709 4.01,-5.981c0.816,-0.272 -0.748,3.127 -0.952,3.602c-0.204,0.476 2.651,-3.058 2.991,-2.923c0.34,0.136 -0.884,2.515 -2.039,3.806c0,0 2.787,-2.447 3.127,-2.243c0.34,0.204 -1.555,2.214 -1.555,2.214s2.708,-2.37 0.671,0.776c-0.866,1.337 -4.962,4.554 -5.437,4.758c-0.476,0.204 -2.651,3.466 -2.651,3.466l-2.484,-2.174z" fill="#E8A8B8" id="svg_466"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 31 KiB |
@@ -1,406 +1,179 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="180" height="140" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="BACKGROUND"/>
|
||||
<g id="OBJECTS">
|
||||
<g id="svg_2">
|
||||
<g id="svg_3">
|
||||
<g id="svg_5"/>
|
||||
<g id="svg_20"/>
|
||||
<g id="svg_35"/>
|
||||
</g>
|
||||
<g id="svg_48">
|
||||
<g id="svg_51"/>
|
||||
<g id="svg_56"/>
|
||||
<g id="svg_70">
|
||||
<g id="svg_74"/>
|
||||
<g id="svg_76"/>
|
||||
<g id="svg_78"/>
|
||||
<g id="svg_80"/>
|
||||
</g>
|
||||
<g id="svg_82">
|
||||
<g id="svg_86"/>
|
||||
<g id="svg_88"/>
|
||||
<g id="svg_90"/>
|
||||
<g id="svg_92"/>
|
||||
</g>
|
||||
<g id="svg_95"/>
|
||||
<g id="svg_100"/>
|
||||
</g>
|
||||
<g id="svg_104">
|
||||
<g id="svg_116"/>
|
||||
<g id="svg_118"/>
|
||||
<g id="svg_126"/>
|
||||
<g id="svg_135"/>
|
||||
</g>
|
||||
<g id="svg_146">
|
||||
<g id="svg_155"/>
|
||||
<g id="svg_157"/>
|
||||
<g id="svg_165"/>
|
||||
<g id="svg_175"/>
|
||||
<g id="svg_180">
|
||||
<g id="svg_184"/>
|
||||
<g id="svg_186"/>
|
||||
<g id="svg_188"/>
|
||||
<g id="svg_190"/>
|
||||
</g>
|
||||
<g id="svg_192">
|
||||
<g id="svg_196"/>
|
||||
<g id="svg_198"/>
|
||||
<g id="svg_200"/>
|
||||
<g id="svg_202"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_205">
|
||||
<g id="svg_206"/>
|
||||
<g id="svg_209"/>
|
||||
</g>
|
||||
<g id="svg_213">
|
||||
<g id="svg_214">
|
||||
<g id="svg_216"/>
|
||||
</g>
|
||||
<g id="svg_219">
|
||||
<g id="svg_221"/>
|
||||
</g>
|
||||
<g id="svg_224">
|
||||
<g id="svg_226"/>
|
||||
</g>
|
||||
<g id="svg_228"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_231">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="180" height="140" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<g id="svg_232">
|
||||
<g id="svg_233">
|
||||
<g id="svg_234">
|
||||
<path d="m76.933,96.939l-6.102,30.954l-0.712,0l4.014,-32.367c0,0 2.8,2.119 2.8,1.413z" fill="#2D294C" id="svg_235"/>
|
||||
<path d="m81.83,96.939l-6.102,30.954l-0.712,0l4.014,-32.367c0.001,0 2.8,2.119 2.8,1.413z" fill="#2D294C" id="svg_236"/>
|
||||
<g id="svg_233">
|
||||
<g id="svg_234">
|
||||
<path d="m76.933,96.939l-6.102,30.954l-0.712,0l4.014,-32.367c0,0 2.8,2.119 2.8,1.413z" fill="#2D294C" id="svg_235"/>
|
||||
<path d="m81.83,96.939l-6.102,30.954l-0.712,0l4.014,-32.367c0.001,0 2.8,2.119 2.8,1.413z" fill="#2D294C" id="svg_236"/>
|
||||
</g>
|
||||
<g id="svg_237">
|
||||
<path d="m98.866,96.939l6.102,30.954l0.712,0l-4.014,-32.367c-0.001,0 -2.8,2.119 -2.8,1.413z" fill="#2D294C" id="svg_238"/>
|
||||
<path d="m93.968,96.939l6.102,30.954l0.712,0l-4.014,-32.367c0,0 -2.8,2.119 -2.8,1.413z" fill="#2D294C" id="svg_239"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_237">
|
||||
<path d="m98.866,96.939l6.102,30.954l0.712,0l-4.014,-32.367c-0.001,0 -2.8,2.119 -2.8,1.413z" fill="#2D294C" id="svg_238"/>
|
||||
<path d="m93.968,96.939l6.102,30.954l0.712,0l-4.014,-32.367c0,0 -2.8,2.119 -2.8,1.413z" fill="#2D294C" id="svg_239"/>
|
||||
<path d="m103.771,77.118l-3.711,-11.503c-1.121,-3.475 -4.355,-5.83 -8.007,-5.83l-1.946,0l-1.864,0l-4.498,0c-3.651,0 -6.886,2.355 -8.007,5.83l-3.711,11.503l-0.748,0c-1.852,0 -3.353,1.501 -3.353,3.353l0,5.309c0,6.848 5.551,12.399 12.399,12.399l7.917,0l1.864,0l5.365,0c6.848,0 12.399,-5.551 12.399,-12.399l0,-5.309c0,-1.852 -1.501,-3.353 -3.353,-3.353l-0.746,0z" fill="#9292AA" id="svg_240"/>
|
||||
<g id="svg_241">
|
||||
<g id="svg_242">
|
||||
<rect fill="#2D294C" height="30.178" id="svg_243" transform="matrix(0.374 -0.9274 0.9274 0.374 31.4581 419.478)" width="0.486" x="310.463764" y="-78.932512"/>
|
||||
</g>
|
||||
<g id="svg_244">
|
||||
<rect fill="#2D294C" height="0.486" id="svg_245" transform="matrix(0.9271 -0.3748 0.3748 0.9271 -46.1495 135.573)" width="30.178" x="119.351398" y="23.748997"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m103.771,77.118l-3.711,-11.503c-1.121,-3.475 -4.355,-5.83 -8.007,-5.83l-1.946,0l-1.864,0l-4.498,0c-3.651,0 -6.886,2.355 -8.007,5.83l-3.711,11.503l-0.748,0c-1.852,0 -3.353,1.501 -3.353,3.353l0,5.309c0,6.848 5.551,12.399 12.399,12.399l7.917,0l1.864,0l5.365,0c6.848,0 12.399,-5.551 12.399,-12.399l0,-5.309c0,-1.852 -1.501,-3.353 -3.353,-3.353l-0.746,0z" fill="#9292AA" id="svg_240"/>
|
||||
<g id="svg_241">
|
||||
<g id="svg_242">
|
||||
<rect fill="#2D294C" height="30.178" id="svg_243" transform="matrix(0.374 -0.9274 0.9274 0.374 31.4581 419.478)" width="0.486" x="310.463764" y="-78.932512"/>
|
||||
</g>
|
||||
<g id="svg_244">
|
||||
<rect fill="#2D294C" height="0.486" id="svg_245" transform="matrix(0.9271 -0.3748 0.3748 0.9271 -46.1495 135.573)" width="30.178" x="119.351398" y="23.748997"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m103.942,77.118l-0.171,0l-3.711,-11.503c-1.121,-3.475 -4.355,-5.83 -8.007,-5.83l-1.946,0l-1.864,0l-4.498,0c-3.651,0 -6.886,2.355 -8.007,5.83l-3.711,11.503l-0.01,0l0,12.579c0,4.58 3.633,8.447 8.213,8.482c0.032,0 0.064,0 0.096,0l7.917,0l1.864,0l5.365,0c0.088,0 0.176,-0.001 0.264,-0.003c4.577,-0.096 8.206,-3.907 8.206,-8.485l0,-12.573z" fill="#CFCCDF" id="svg_246"/>
|
||||
<path d="m70.573,93.419c2.27,2.894 5.79,4.76 9.753,4.76l7.917,0l1.864,0l5.365,0c3.963,0 7.483,-1.866 9.753,-4.76l-34.652,0z" fill="#9292AA" id="svg_247"/>
|
||||
</g>
|
||||
<g id="svg_248">
|
||||
<g id="svg_249"/>
|
||||
<g id="svg_251"/>
|
||||
<g id="svg_253"/>
|
||||
<g id="svg_255"/>
|
||||
<g id="svg_260"/>
|
||||
<g id="svg_264"/>
|
||||
<g id="svg_268">
|
||||
<g id="svg_269"/>
|
||||
<g id="svg_271"/>
|
||||
<g id="svg_273"/>
|
||||
<g id="svg_275"/>
|
||||
<g id="svg_277"/>
|
||||
<g id="svg_279"/>
|
||||
<g id="svg_281"/>
|
||||
<g id="svg_283"/>
|
||||
<g id="svg_285"/>
|
||||
<g id="svg_287"/>
|
||||
<g id="svg_289"/>
|
||||
<g id="svg_291"/>
|
||||
<g id="svg_293"/>
|
||||
<g id="svg_295"/>
|
||||
<g id="svg_297"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_299">
|
||||
<g id="svg_300"/>
|
||||
<g id="svg_308"/>
|
||||
<g id="svg_313"/>
|
||||
<g id="svg_315">
|
||||
<g id="svg_319"/>
|
||||
<g id="svg_321"/>
|
||||
<g id="svg_323"/>
|
||||
<g id="svg_325"/>
|
||||
</g>
|
||||
<g id="svg_329"/>
|
||||
<path d="m103.942,77.118l-0.171,0l-3.711,-11.503c-1.121,-3.475 -4.355,-5.83 -8.007,-5.83l-1.946,0l-1.864,0l-4.498,0c-3.651,0 -6.886,2.355 -8.007,5.83l-3.711,11.503l-0.01,0l0,12.579c0,4.58 3.633,8.447 8.213,8.482c0.032,0 0.064,0 0.096,0l7.917,0l1.864,0l5.365,0c0.088,0 0.176,-0.001 0.264,-0.003c4.577,-0.096 8.206,-3.907 8.206,-8.485l0,-12.573z" fill="#CFCCDF" id="svg_246"/>
|
||||
<path d="m70.573,93.419c2.27,2.894 5.79,4.76 9.753,4.76l7.917,0l1.864,0l5.365,0c3.963,0 7.483,-1.866 9.753,-4.76l-34.652,0z" fill="#9292AA" id="svg_247"/>
|
||||
</g>
|
||||
<g id="svg_345">
|
||||
<polygon fill="#D7914E" id="svg_346" points="105.989990234375,118.74800109863281 106.02999877929688,125.25300598144531 106.30999755859375,127.28900146484375 109.94100952148438,127.48800659179688 110.94900512695312,126.00199890136719 110.30099487304688,117.8699951171875 "/>
|
||||
<rect fill="#2D294C" height="15.55" id="svg_347" width="21.717" x="76.059" y="78.354"/>
|
||||
<path d="m68.326,77.424c-0.902,-0.263 -1.782,-0.6 -2.629,-1.005c-1.383,-0.661 -2.848,-1.829 -2.702,-3.355c0.096,-1.006 0.995,-3.146 1.705,-3.865c1.915,-1.94 7.913,-13.124 9.945,-14.721c0.888,-0.698 9.498,-4.708 9.904,-2.435c0.077,0.43 -0.507,3.353 -0.629,3.772c-0.972,3.357 -3.12,4.862 -5.219,7.656c-1.819,2.422 -4.013,4.552 -5.728,7.048c0.632,1.036 1.411,2.12 2.385,2.843c3.033,2.25 6.071,4.491 9.116,6.724c-0.729,0.863 -1.156,1.903 -1.474,2.987c-5.004,-1.626 -9.621,-4.173 -14.674,-5.649z" fill="#D7914E" id="svg_348"/>
|
||||
<path d="m103.921,77.424c0.902,-0.263 1.782,-0.6 2.629,-1.005c1.383,-0.661 2.848,-1.829 2.702,-3.355c-0.096,-1.006 -0.995,-3.146 -1.705,-3.865c-1.915,-1.94 -7.913,-13.124 -9.945,-14.721c-0.888,-0.698 -9.498,-4.708 -9.904,-2.435c-0.077,0.43 0.507,3.353 0.629,3.772c0.972,3.357 3.12,4.862 5.219,7.656c1.819,2.422 4.013,4.552 5.728,7.048c-0.632,1.036 -1.411,2.12 -2.385,2.843c-3.033,2.25 -6.071,4.491 -9.116,6.724c0.729,0.863 1.156,1.903 1.474,2.987c5.004,-1.626 9.621,-4.173 14.674,-5.649z" fill="#D7914E" id="svg_349"/>
|
||||
<path d="m92.764,74.378c0,1.592 -1.348,2.882 -3.011,2.882c-1.663,0 -3.011,-1.29 -3.011,-2.882c0,-1.592 1.348,-2.882 3.011,-2.882c1.663,-0.001 3.011,1.29 3.011,2.882z" fill="#A0616B" id="svg_350"/>
|
||||
<g id="svg_351">
|
||||
<path d="m104.514,121.741l-0.079,-1.984c-0.668,-10.878 -2.243,-21.139 -3.588,-24.237c-1.649,-1.06 -7.638,-2.66 -13.028,-3.43l-4.808,-0.687l1.373,-9.615l4.808,0.687c4.352,0.622 14.889,2.484 18.744,6.338c1.096,1.095 4.427,1.511 6.193,30.349l-0.116,2.139l-9.499,0.44zm-3.804,-26.502l0,0l0,0z" fill="#2D294C" id="svg_352"/>
|
||||
</g>
|
||||
<g id="svg_353">
|
||||
<path d="m75.533,124.821l-0.868,-4.778c-0.162,-0.892 -2.938,-16.203 -3.432,-23.825c-0.527,-8.127 4.433,-11.396 7.13,-11.99l4.743,-1.044l2.088,9.485l-4.083,0.899c-0.103,0.258 -0.261,0.86 -0.186,2.021c0.458,7.067 3.258,22.503 3.296,22.716l0.869,4.778l-9.557,1.738z" fill="#2D294C" id="svg_354"/>
|
||||
</g>
|
||||
<path d="m99.534,58.12c-0.233,-3.776 -5.585,-7.346 -12.204,-7.346c-6.619,0 -12.365,1.538 -13.534,5.16c-0.585,1.811 2.424,21.23 2.401,22.425c-0.075,3.845 -0.257,5.671 -0.257,5.671l21.993,0.187c0,0 0.083,-2.617 -0.09,-5.498c-0.11,-1.83 1.812,-18.636 1.691,-20.599z" fill="#D7914E" id="svg_355"/>
|
||||
<path d="m86.871,53.229l-0.575,0c-1.537,0 -2.783,-1.246 -2.783,-2.783l0,-8.716l6.141,0l0,8.716c0,1.537 -1.246,2.783 -2.783,2.783z" fill="#EFAFC0" id="svg_356"/>
|
||||
<path d="m83.514,44.073c0.813,2.108 2.586,5.356 6.14,6.155l0,-8.499l-5.55,0l-0.59,0.259l0,2.085z" fill="#D98E9D" id="svg_357" opacity="0.5"/>
|
||||
<path d="m88.507,31.913l-1.4,0.188c-2.784,0.374 -4.738,2.933 -4.364,5.717l0.64,4.77c0.378,2.815 2.966,4.791 5.781,4.413l2.485,-0.334c2.154,-0.289 3.665,-2.269 3.376,-4.423l-0.801,-5.967c-0.373,-2.783 -2.933,-4.737 -5.717,-4.364z" fill="#EFAFC0" id="svg_358"/>
|
||||
<path d="m85.319,41.204c0.201,1.498 -0.85,2.874 -2.348,3.075c-1.497,0.201 -2.874,-0.85 -3.075,-2.348c-0.201,-1.498 0.85,-2.874 2.348,-3.075c1.497,-0.201 2.874,0.85 3.075,2.348z" fill="#EFAFC0" id="svg_359"/>
|
||||
<path d="m82.98,39.098c0.342,0.948 0.611,1.922 0.805,2.91c0.098,-0.121 0.301,-0.197 0.452,-0.161" fill="none" id="svg_360"/>
|
||||
<path d="m79.405,36.266c0.064,0.203 0.171,0.389 0.277,0.573c0.448,0.779 0.895,1.558 1.343,2.337c0.533,-0.343 1.228,-0.251 1.862,-0.25c0.168,0.696 0.253,1.418 0.279,2.134c0.23,-0.047 0.468,-0.064 0.703,-0.052c-0.081,-0.824 -0.109,-1.66 -0.135,-2.487c0.207,-0.25 0.524,-0.439 0.753,-0.669c-0.368,-0.4 -0.571,-0.938 -0.615,-1.479c-0.056,-0.687 0.26,-1.236 0.6,-1.836c0.34,-0.6 0.843,-1.132 1.487,-1.379c1.383,-0.531 2.731,-1.006 4.207,-0.891c0.582,0.045 1.221,0.088 1.716,0.399c0.765,-0.017 1.343,-0.848 1.326,-1.613c-0.017,-0.765 -0.473,-1.453 -1.003,-2.004c-0.374,-0.389 -0.797,-0.736 -1.275,-0.987c-0.595,-0.313 -1.261,-0.47 -1.928,-0.554c-1.757,-0.221 -3.575,0.058 -5.185,0.796c0.294,0.06 0.505,0.162 0.799,0.222c-0.498,0.05 -0.985,0.172 -1.483,0.222c-0.53,0.053 -1.065,0.108 -1.57,0.277c-0.922,0.309 -1.687,0.985 -2.259,1.772c-0.572,0.787 -0.969,1.684 -1.345,2.581c-0.163,0.389 -0.326,0.803 -0.264,1.22c0.051,0.347 0.254,0.652 0.469,0.93c0.272,0.349 0.797,0.732 1.241,0.738z" fill="#2D294C" id="svg_361"/>
|
||||
<path d="m90.769,42.967l-2.971,-0.714c0,0 0.493,1.603 1.441,1.755c0.947,0.15 1.53,-1.041 1.53,-1.041z" fill="#FFFFFF" id="svg_362"/>
|
||||
<path d="m89.656,38.335c0.04,0.302 -0.171,0.58 -0.473,0.62c-0.302,0.04 -0.58,-0.171 -0.62,-0.473c-0.04,-0.302 0.171,-0.58 0.473,-0.62c0.302,-0.041 0.579,0.171 0.62,0.473z" fill="#2D294C" id="svg_363"/>
|
||||
<path d="m93.483,37.821c0.04,0.302 -0.171,0.58 -0.473,0.62c-0.302,0.041 -0.579,-0.171 -0.62,-0.473c-0.04,-0.302 0.171,-0.58 0.473,-0.62c0.301,-0.041 0.579,0.171 0.62,0.473z" fill="#2D294C" id="svg_364"/>
|
||||
<polygon fill="#FFFFFF" id="svg_365" points="86.84201049804688,52.28199768066406 82.7130126953125,51.47599792480469 83.10000610351562,54.055999755859375 87.48599243164062,60.11799621582031 91.87100219726562,54.82899475097656 89.93600463867188,51.08900451660156 "/>
|
||||
<path d="m83.382,49.978c0,0 3.161,1.747 6.287,0l0.359,1.527l-0.773,3.712l-2.315,-2.559l-1.996,2.689l-2.102,-3.483l0.54,-1.886z" fill="#EFF2F7" id="svg_366"/>
|
||||
<g id="svg_367">
|
||||
<path d="m77.129,132.034c0,0 -0.359,1.321 -0.166,2.048c0.193,0.728 0.945,2.038 2.77,2.094c1.825,0.057 4.203,0.173 4.061,-4.926l-6.665,0.784z" fill="#3F3E66" id="svg_368"/>
|
||||
<path d="m79.816,123.896c0.162,-0.288 -0.354,-2.445 1.152,-2.371c1.506,0.074 1.678,0.793 1.567,1.853c-0.111,1.06 -0.807,5.388 -0.807,5.388l-2.112,0.193l0.2,-5.063z" fill="#2D294C" id="svg_369"/>
|
||||
<path d="m79.5,122.394c0,0 -0.642,3.727 -0.709,3.871c-0.067,0.145 -1.992,5.885 -1.714,6.789c0.2,0.651 1.325,1.583 3.732,1.492c1.852,-0.07 3.219,-1.763 2.945,-3.596c-0.243,-1.626 -0.549,-4.7 -0.547,-4.746c0.005,-0.089 -0.298,-3.866 -0.298,-3.866s-1.764,2.646 -1.904,5.904l-0.65,-0.035c-0.001,0.001 0.162,-5.996 -0.855,-5.813z" fill="#2D294C" id="svg_370"/>
|
||||
<g id="svg_371">
|
||||
<path d="m82.319,124.27c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.056 -2.13,-0.157 -2.176,-0.118c-0.101,0.085 -0.252,0.073 -0.339,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.506,-0.432 1.748,-0.955 2.831,0.148c0.093,0.095 0.092,0.247 -0.003,0.34c-0.046,0.045 -0.107,0.068 -0.168,0.068z" fill="#3F3E66" id="svg_372"/>
|
||||
<polygon fill="#D7914E" id="svg_346" points="105.989990234375,118.74800109863281 106.02999877929688,125.25300598144531 106.30999755859375,127.28900146484375 109.94100952148438,127.48800659179688 110.94900512695312,126.00199890136719 110.30099487304688,117.8699951171875 "/>
|
||||
<rect fill="#2D294C" height="15.55" id="svg_347" width="21.717" x="76.059" y="78.354"/>
|
||||
<path d="m68.326,77.424c-0.902,-0.263 -1.782,-0.6 -2.629,-1.005c-1.383,-0.661 -2.848,-1.829 -2.702,-3.355c0.096,-1.006 0.995,-3.146 1.705,-3.865c1.915,-1.94 7.913,-13.124 9.945,-14.721c0.888,-0.698 9.498,-4.708 9.904,-2.435c0.077,0.43 -0.507,3.353 -0.629,3.772c-0.972,3.357 -3.12,4.862 -5.219,7.656c-1.819,2.422 -4.013,4.552 -5.728,7.048c0.632,1.036 1.411,2.12 2.385,2.843c3.033,2.25 6.071,4.491 9.116,6.724c-0.729,0.863 -1.156,1.903 -1.474,2.987c-5.004,-1.626 -9.621,-4.173 -14.674,-5.649z" fill="#D7914E" id="svg_348"/>
|
||||
<path d="m103.921,77.424c0.902,-0.263 1.782,-0.6 2.629,-1.005c1.383,-0.661 2.848,-1.829 2.702,-3.355c-0.096,-1.006 -0.995,-3.146 -1.705,-3.865c-1.915,-1.94 -7.913,-13.124 -9.945,-14.721c-0.888,-0.698 -9.498,-4.708 -9.904,-2.435c-0.077,0.43 0.507,3.353 0.629,3.772c0.972,3.357 3.12,4.862 5.219,7.656c1.819,2.422 4.013,4.552 5.728,7.048c-0.632,1.036 -1.411,2.12 -2.385,2.843c-3.033,2.25 -6.071,4.491 -9.116,6.724c0.729,0.863 1.156,1.903 1.474,2.987c5.004,-1.626 9.621,-4.173 14.674,-5.649z" fill="#D7914E" id="svg_349"/>
|
||||
<path d="m92.764,74.378c0,1.592 -1.348,2.882 -3.011,2.882c-1.663,0 -3.011,-1.29 -3.011,-2.882c0,-1.592 1.348,-2.882 3.011,-2.882c1.663,-0.001 3.011,1.29 3.011,2.882z" fill="#A0616B" id="svg_350"/>
|
||||
<g id="svg_351">
|
||||
<path d="m104.514,121.741l-0.079,-1.984c-0.668,-10.878 -2.243,-21.139 -3.588,-24.237c-1.649,-1.06 -7.638,-2.66 -13.028,-3.43l-4.808,-0.687l1.373,-9.615l4.808,0.687c4.352,0.622 14.889,2.484 18.744,6.338c1.096,1.095 4.427,1.511 6.193,30.349l-0.116,2.139l-9.499,0.44zm-3.804,-26.502l0,0l0,0z" fill="#2D294C" id="svg_352"/>
|
||||
</g>
|
||||
<g id="svg_373">
|
||||
<path d="m82.161,125.502c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.086 -0.252,0.073 -0.339,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.506,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.092,0.247 -0.003,0.34c-0.046,0.046 -0.107,0.069 -0.168,0.069z" fill="#3F3E66" id="svg_374"/>
|
||||
<g id="svg_353">
|
||||
<path d="m75.533,124.821l-0.868,-4.778c-0.162,-0.892 -2.938,-16.203 -3.432,-23.825c-0.527,-8.127 4.433,-11.396 7.13,-11.99l4.743,-1.044l2.088,9.485l-4.083,0.899c-0.103,0.258 -0.261,0.86 -0.186,2.021c0.458,7.067 3.258,22.503 3.296,22.716l0.869,4.778l-9.557,1.738z" fill="#2D294C" id="svg_354"/>
|
||||
</g>
|
||||
<g id="svg_375">
|
||||
<path d="m82.003,126.735c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.037,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.085 -0.252,0.073 -0.339,-0.028c-0.086,-0.102 -0.074,-0.252 0.027,-0.338c0.505,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.091,0.247 -0.003,0.34c-0.046,0.046 -0.107,0.069 -0.168,0.069z" fill="#3F3E66" id="svg_376"/>
|
||||
<path d="m99.534,58.12c-0.233,-3.776 -5.585,-7.346 -12.204,-7.346c-6.619,0 -12.365,1.538 -13.534,5.16c-0.585,1.811 2.424,21.23 2.401,22.425c-0.075,3.845 -0.257,5.671 -0.257,5.671l21.993,0.187c0,0 0.083,-2.617 -0.09,-5.498c-0.11,-1.83 1.812,-18.636 1.691,-20.599z" fill="#D7914E" id="svg_355"/>
|
||||
<path d="m86.871,53.229l-0.575,0c-1.537,0 -2.783,-1.246 -2.783,-2.783l0,-8.716l6.141,0l0,8.716c0,1.537 -1.246,2.783 -2.783,2.783z" fill="#EFAFC0" id="svg_356"/>
|
||||
<path d="m83.514,44.073c0.813,2.108 2.586,5.356 6.14,6.155l0,-8.499l-5.55,0l-0.59,0.259l0,2.085z" fill="#D98E9D" id="svg_357" opacity="0.5"/>
|
||||
<path d="m88.507,31.913l-1.4,0.188c-2.784,0.374 -4.738,2.933 -4.364,5.717l0.64,4.77c0.378,2.815 2.966,4.791 5.781,4.413l2.485,-0.334c2.154,-0.289 3.665,-2.269 3.376,-4.423l-0.801,-5.967c-0.373,-2.783 -2.933,-4.737 -5.717,-4.364z" fill="#EFAFC0" id="svg_358"/>
|
||||
<path d="m85.319,41.204c0.201,1.498 -0.85,2.874 -2.348,3.075c-1.497,0.201 -2.874,-0.85 -3.075,-2.348c-0.201,-1.498 0.85,-2.874 2.348,-3.075c1.497,-0.201 2.874,0.85 3.075,2.348z" fill="#EFAFC0" id="svg_359"/>
|
||||
<path d="m82.98,39.098c0.342,0.948 0.611,1.922 0.805,2.91c0.098,-0.121 0.301,-0.197 0.452,-0.161" fill="none" id="svg_360"/>
|
||||
<path d="m79.405,36.266c0.064,0.203 0.171,0.389 0.277,0.573c0.448,0.779 0.895,1.558 1.343,2.337c0.533,-0.343 1.228,-0.251 1.862,-0.25c0.168,0.696 0.253,1.418 0.279,2.134c0.23,-0.047 0.468,-0.064 0.703,-0.052c-0.081,-0.824 -0.109,-1.66 -0.135,-2.487c0.207,-0.25 0.524,-0.439 0.753,-0.669c-0.368,-0.4 -0.571,-0.938 -0.615,-1.479c-0.056,-0.687 0.26,-1.236 0.6,-1.836c0.34,-0.6 0.843,-1.132 1.487,-1.379c1.383,-0.531 2.731,-1.006 4.207,-0.891c0.582,0.045 1.221,0.088 1.716,0.399c0.765,-0.017 1.343,-0.848 1.326,-1.613c-0.017,-0.765 -0.473,-1.453 -1.003,-2.004c-0.374,-0.389 -0.797,-0.736 -1.275,-0.987c-0.595,-0.313 -1.261,-0.47 -1.928,-0.554c-1.757,-0.221 -3.575,0.058 -5.185,0.796c0.294,0.06 0.505,0.162 0.799,0.222c-0.498,0.05 -0.985,0.172 -1.483,0.222c-0.53,0.053 -1.065,0.108 -1.57,0.277c-0.922,0.309 -1.687,0.985 -2.259,1.772c-0.572,0.787 -0.969,1.684 -1.345,2.581c-0.163,0.389 -0.326,0.803 -0.264,1.22c0.051,0.347 0.254,0.652 0.469,0.93c0.272,0.349 0.797,0.732 1.241,0.738z" fill="#2D294C" id="svg_361"/>
|
||||
<path d="m90.769,42.967l-2.971,-0.714c0,0 0.493,1.603 1.441,1.755c0.947,0.15 1.53,-1.041 1.53,-1.041z" fill="#FFFFFF" id="svg_362"/>
|
||||
<path d="m89.656,38.335c0.04,0.302 -0.171,0.58 -0.473,0.62c-0.302,0.04 -0.58,-0.171 -0.62,-0.473c-0.04,-0.302 0.171,-0.58 0.473,-0.62c0.302,-0.041 0.579,0.171 0.62,0.473z" fill="#2D294C" id="svg_363"/>
|
||||
<path d="m93.483,37.821c0.04,0.302 -0.171,0.58 -0.473,0.62c-0.302,0.041 -0.579,-0.171 -0.62,-0.473c-0.04,-0.302 0.171,-0.58 0.473,-0.62c0.301,-0.041 0.579,0.171 0.62,0.473z" fill="#2D294C" id="svg_364"/>
|
||||
<polygon fill="#FFFFFF" id="svg_365" points="86.84201049804688,52.28199768066406 82.7130126953125,51.47599792480469 83.10000610351562,54.055999755859375 87.48599243164062,60.11799621582031 91.87100219726562,54.82899475097656 89.93600463867188,51.08900451660156 "/>
|
||||
<path d="m83.382,49.978c0,0 3.161,1.747 6.287,0l0.359,1.527l-0.773,3.712l-2.315,-2.559l-1.996,2.689l-2.102,-3.483l0.54,-1.886z" fill="#EFF2F7" id="svg_366"/>
|
||||
<g id="svg_367">
|
||||
<path d="m77.129,132.034c0,0 -0.359,1.321 -0.166,2.048c0.193,0.728 0.945,2.038 2.77,2.094c1.825,0.057 4.203,0.173 4.061,-4.926l-6.665,0.784z" fill="#3F3E66" id="svg_368"/>
|
||||
<path d="m79.816,123.896c0.162,-0.288 -0.354,-2.445 1.152,-2.371c1.506,0.074 1.678,0.793 1.567,1.853c-0.111,1.06 -0.807,5.388 -0.807,5.388l-2.112,0.193l0.2,-5.063z" fill="#2D294C" id="svg_369"/>
|
||||
<path d="m79.5,122.394c0,0 -0.642,3.727 -0.709,3.871c-0.067,0.145 -1.992,5.885 -1.714,6.789c0.2,0.651 1.325,1.583 3.732,1.492c1.852,-0.07 3.219,-1.763 2.945,-3.596c-0.243,-1.626 -0.549,-4.7 -0.547,-4.746c0.005,-0.089 -0.298,-3.866 -0.298,-3.866s-1.764,2.646 -1.904,5.904l-0.65,-0.035c-0.001,0.001 0.162,-5.996 -0.855,-5.813z" fill="#2D294C" id="svg_370"/>
|
||||
<g id="svg_371">
|
||||
<path d="m82.319,124.27c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.056 -2.13,-0.157 -2.176,-0.118c-0.101,0.085 -0.252,0.073 -0.339,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.506,-0.432 1.748,-0.955 2.831,0.148c0.093,0.095 0.092,0.247 -0.003,0.34c-0.046,0.045 -0.107,0.068 -0.168,0.068z" fill="#3F3E66" id="svg_372"/>
|
||||
</g>
|
||||
<g id="svg_373">
|
||||
<path d="m82.161,125.502c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.086 -0.252,0.073 -0.339,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.506,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.092,0.247 -0.003,0.34c-0.046,0.046 -0.107,0.069 -0.168,0.069z" fill="#3F3E66" id="svg_374"/>
|
||||
</g>
|
||||
<g id="svg_375">
|
||||
<path d="m82.003,126.735c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.037,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.085 -0.252,0.073 -0.339,-0.028c-0.086,-0.102 -0.074,-0.252 0.027,-0.338c0.505,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.091,0.247 -0.003,0.34c-0.046,0.046 -0.107,0.069 -0.168,0.069z" fill="#3F3E66" id="svg_376"/>
|
||||
</g>
|
||||
<g id="svg_377">
|
||||
<path d="m81.845,127.968c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.086 -0.252,0.073 -0.338,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.505,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.092,0.247 -0.003,0.34c-0.048,0.046 -0.109,0.069 -0.169,0.069z" fill="#3F3E66" id="svg_378"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_377">
|
||||
<path d="m81.845,127.968c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.086 -0.252,0.073 -0.338,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.505,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.092,0.247 -0.003,0.34c-0.048,0.046 -0.109,0.069 -0.169,0.069z" fill="#3F3E66" id="svg_378"/>
|
||||
<g id="svg_379">
|
||||
<path d="m109.648,124.917c0,0 -0.906,-1.496 -0.021,-1.749c0.885,-0.253 1.434,0.241 1.434,0.241l2.491,2.198l1.365,1.652l-1.831,-0.581l-3.438,-1.761z" fill="#2D294C" id="svg_380"/>
|
||||
<path d="m106.055,124.579c0,0 0.944,1.669 1.662,1.261c0.406,-0.231 0.985,-1.755 1.573,-1.617c0.588,0.138 2.961,1.476 3.837,1.794c0.876,0.318 -2.173,-2.574 -2.173,-2.574s2.521,1.913 3.304,2.2c0.784,0.286 7.197,1.07 7.591,2.986l0.064,1.261c0,0 -1.789,2.128 -6.783,1.15c-4.995,-0.978 -7.744,-0.907 -8.65,-1.133c-0.907,-0.227 -1.107,-5.191 -0.425,-5.328z" fill="#2D294C" id="svg_381"/>
|
||||
<path d="m116.03,130.259c-2.331,-0.074 -6.343,-1.231 -7.828,-1.202c-0.855,0.017 -1.737,-0.166 -2.331,-0.325c0.14,0.637 0.344,1.108 0.609,1.174c0.907,0.226 3.656,0.155 8.65,1.133c4.995,0.978 6.783,-1.15 6.783,-1.15l-0.064,-1.261c-0.423,1.066 -3.488,1.705 -5.819,1.631z" fill="#3F3E66" id="svg_382"/>
|
||||
<g id="svg_383">
|
||||
<path d="m111.068,126.016c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.102 0.611,-0.059 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.176,-0.107 -0.34,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.877,1.073c-0.032,0.086 -0.108,0.142 -0.192,0.153z" fill="#3F3E66" id="svg_384"/>
|
||||
</g>
|
||||
<g id="svg_385">
|
||||
<path d="m111.893,126.603c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.296,-0.102 0.611,-0.059 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.176,-0.107 -0.34,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.878,1.073c-0.032,0.086 -0.107,0.142 -0.191,0.153z" fill="#3F3E66" id="svg_386"/>
|
||||
</g>
|
||||
<g id="svg_387">
|
||||
<path d="m112.861,126.952c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.1 0.612,-0.058 0.908,0.124c0.113,0.069 0.149,0.217 0.079,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.175,-0.108 -0.339,-0.133 -0.502,-0.078c-0.435,0.147 -0.781,0.824 -0.878,1.073c-0.032,0.085 -0.107,0.141 -0.191,0.152z" fill="#3F3E66" id="svg_388"/>
|
||||
</g>
|
||||
<g id="svg_389">
|
||||
<path d="m113.865,127.252c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.103 0.611,-0.058 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.175,-0.107 -0.339,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.877,1.073c-0.033,0.086 -0.108,0.141 -0.192,0.153z" fill="#3F3E66" id="svg_390"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_379">
|
||||
<path d="m109.648,124.917c0,0 -0.906,-1.496 -0.021,-1.749c0.885,-0.253 1.434,0.241 1.434,0.241l2.491,2.198l1.365,1.652l-1.831,-0.581l-3.438,-1.761z" fill="#2D294C" id="svg_380"/>
|
||||
<path d="m106.055,124.579c0,0 0.944,1.669 1.662,1.261c0.406,-0.231 0.985,-1.755 1.573,-1.617c0.588,0.138 2.961,1.476 3.837,1.794c0.876,0.318 -2.173,-2.574 -2.173,-2.574s2.521,1.913 3.304,2.2c0.784,0.286 7.197,1.07 7.591,2.986l0.064,1.261c0,0 -1.789,2.128 -6.783,1.15c-4.995,-0.978 -7.744,-0.907 -8.65,-1.133c-0.907,-0.227 -1.107,-5.191 -0.425,-5.328z" fill="#2D294C" id="svg_381"/>
|
||||
<path d="m116.03,130.259c-2.331,-0.074 -6.343,-1.231 -7.828,-1.202c-0.855,0.017 -1.737,-0.166 -2.331,-0.325c0.14,0.637 0.344,1.108 0.609,1.174c0.907,0.226 3.656,0.155 8.65,1.133c4.995,0.978 6.783,-1.15 6.783,-1.15l-0.064,-1.261c-0.423,1.066 -3.488,1.705 -5.819,1.631z" fill="#3F3E66" id="svg_382"/>
|
||||
<g id="svg_383">
|
||||
<path d="m111.068,126.016c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.102 0.611,-0.059 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.176,-0.107 -0.34,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.877,1.073c-0.032,0.086 -0.108,0.142 -0.192,0.153z" fill="#3F3E66" id="svg_384"/>
|
||||
<path d="m85.292,83.099c-0.866,-0.069 -1.783,-0.265 -2.389,-0.888c-0.606,-0.622 0.539,-1.594 1.239,-2.108c0.968,0.44 2.076,-0.063 3.133,-0.182c0.641,-0.072 1.289,0.003 1.926,0.105c0.817,0.131 1.636,0.311 2.374,0.685c0.738,0.374 1.394,0.96 1.713,1.723c0.075,0.179 -0.024,0.35 -0.217,0.353c-0.194,0.003 -0.358,-0.133 -0.51,-0.253c-0.477,-0.376 -1.026,-0.66 -1.608,-0.834c-0.105,-0.031 -0.185,0.144 -0.086,0.191c1.456,0.689 1.801,1.611 1.775,1.694c-0.028,0.09 -0.139,0.124 -0.234,0.122c-0.171,-0.003 -0.452,-0.259 -0.594,-0.354c-0.603,-0.399 -1.146,-0.472 -1.146,-0.472s-0.619,0.036 0.899,0.801c0.187,0.094 0.381,0.221 0.443,0.421c0.011,0.035 0.017,0.073 0.01,0.109c-0.008,0.043 -0.035,0.081 -0.067,0.112c-0.098,0.093 -0.242,0.124 -0.376,0.112c-0.134,-0.011 -0.262,-0.06 -0.388,-0.109c-0.618,-0.237 -1.235,-0.475 -1.853,-0.712c0.163,0.103 1.282,0.662 1.403,0.813c0.047,0.059 0.092,0.131 0.077,0.204c-0.023,0.109 -0.157,0.146 -0.268,0.157c-0.743,0.074 -2.45,-0.542 -3.146,-0.813c-0.695,-0.269 -1.365,-0.818 -2.11,-0.877z" fill="#EFAFC0" id="svg_391"/>
|
||||
<g id="svg_392">
|
||||
<path d="m87.677,40.908c-1.249,0 -2.339,-0.926 -2.51,-2.197c-0.09,-0.671 0.086,-1.337 0.497,-1.875c0.411,-0.538 1.006,-0.884 1.677,-0.974c1.386,-0.186 2.663,0.79 2.849,2.174c0.186,1.385 -0.79,2.662 -2.175,2.849c-0.113,0.016 -0.226,0.023 -0.338,0.023zm0.003,-4.826c-0.102,0 -0.203,0.007 -0.306,0.021c-0.606,0.081 -1.145,0.394 -1.516,0.88c-0.372,0.487 -0.531,1.089 -0.45,1.695c0.169,1.252 1.324,2.137 2.575,1.966c1.252,-0.168 2.134,-1.324 1.966,-2.576c-0.154,-1.148 -1.139,-1.986 -2.269,-1.986z" fill="#2D294C" id="svg_393"/>
|
||||
</g>
|
||||
<g id="svg_385">
|
||||
<path d="m111.893,126.603c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.296,-0.102 0.611,-0.059 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.176,-0.107 -0.34,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.878,1.073c-0.032,0.086 -0.107,0.142 -0.191,0.153z" fill="#3F3E66" id="svg_386"/>
|
||||
<g id="svg_394">
|
||||
<path d="m93.067,40.184c-0.552,0 -1.086,-0.179 -1.532,-0.52c-0.538,-0.411 -0.884,-1.006 -0.974,-1.677c-0.186,-1.385 0.789,-2.663 2.174,-2.849c1.384,-0.184 2.663,0.79 2.849,2.175c0.09,0.671 -0.086,1.336 -0.497,1.874c-0.411,0.538 -1.006,0.884 -1.678,0.974c-0.114,0.015 -0.228,0.023 -0.342,0.023zm0.007,-4.826c-0.102,0 -0.203,0.007 -0.306,0.021c-1.252,0.168 -2.133,1.324 -1.965,2.576c0.082,0.607 0.394,1.145 0.88,1.517c0.488,0.372 1.094,0.532 1.695,0.449c0.607,-0.081 1.146,-0.394 1.517,-0.88c0.372,-0.487 0.531,-1.088 0.45,-1.695c-0.156,-1.15 -1.141,-1.988 -2.271,-1.988z" fill="#2D294C" id="svg_395"/>
|
||||
</g>
|
||||
<g id="svg_387">
|
||||
<path d="m112.861,126.952c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.1 0.612,-0.058 0.908,0.124c0.113,0.069 0.149,0.217 0.079,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.175,-0.108 -0.339,-0.133 -0.502,-0.078c-0.435,0.147 -0.781,0.824 -0.878,1.073c-0.032,0.085 -0.107,0.141 -0.191,0.152z" fill="#3F3E66" id="svg_388"/>
|
||||
<g id="svg_396">
|
||||
<rect fill="#2D294C" height="0.242" id="svg_397" transform="matrix(0.9187 -0.3949 0.3949 0.9187 -20.6624 136.864)" width="2.505" x="133.483205" y="-48.057873"/>
|
||||
</g>
|
||||
<g id="svg_389">
|
||||
<path d="m113.865,127.252c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.103 0.611,-0.058 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.175,-0.107 -0.339,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.877,1.073c-0.033,0.086 -0.108,0.141 -0.192,0.153z" fill="#3F3E66" id="svg_390"/>
|
||||
<g id="svg_398">
|
||||
<path d="m90.262,87.938l20.542,0l0,-1.506l-20.542,0c-0.416,0 -0.753,0.337 -0.753,0.753l0,0c0,0.415 0.337,0.753 0.753,0.753z" fill="#A8A5C4" id="svg_399"/>
|
||||
<path d="m92.011,87.938l19.809,0c0.876,0 1.643,-0.59 1.867,-1.436l3.496,-13.172c0.325,-1.226 -0.599,-2.428 -1.867,-2.428l-17.297,0c-0.876,0 -1.643,0.589 -1.867,1.436l-4.141,15.6z" fill="#CFCCDF" id="svg_400"/>
|
||||
<path d="m115.315,70.902c1.268,0 2.193,1.202 1.867,2.428l-3.496,13.172c-0.108,0.406 -0.342,0.751 -0.652,1.002l-12.041,-16.601l14.322,0l0,-0.001z" fill="#CFCCDF" id="svg_401"/>
|
||||
<path d="m103.676,80.586c-0.217,0.859 0.324,1.556 1.208,1.556c0.884,0 1.776,-0.696 1.993,-1.556c0.217,-0.859 -0.324,-1.556 -1.208,-1.556c-0.884,0 -1.776,0.697 -1.993,1.556z" fill="#FFFFFF" id="svg_402"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m85.292,83.099c-0.866,-0.069 -1.783,-0.265 -2.389,-0.888c-0.606,-0.622 0.539,-1.594 1.239,-2.108c0.968,0.44 2.076,-0.063 3.133,-0.182c0.641,-0.072 1.289,0.003 1.926,0.105c0.817,0.131 1.636,0.311 2.374,0.685c0.738,0.374 1.394,0.96 1.713,1.723c0.075,0.179 -0.024,0.35 -0.217,0.353c-0.194,0.003 -0.358,-0.133 -0.51,-0.253c-0.477,-0.376 -1.026,-0.66 -1.608,-0.834c-0.105,-0.031 -0.185,0.144 -0.086,0.191c1.456,0.689 1.801,1.611 1.775,1.694c-0.028,0.09 -0.139,0.124 -0.234,0.122c-0.171,-0.003 -0.452,-0.259 -0.594,-0.354c-0.603,-0.399 -1.146,-0.472 -1.146,-0.472s-0.619,0.036 0.899,0.801c0.187,0.094 0.381,0.221 0.443,0.421c0.011,0.035 0.017,0.073 0.01,0.109c-0.008,0.043 -0.035,0.081 -0.067,0.112c-0.098,0.093 -0.242,0.124 -0.376,0.112c-0.134,-0.011 -0.262,-0.06 -0.388,-0.109c-0.618,-0.237 -1.235,-0.475 -1.853,-0.712c0.163,0.103 1.282,0.662 1.403,0.813c0.047,0.059 0.092,0.131 0.077,0.204c-0.023,0.109 -0.157,0.146 -0.268,0.157c-0.743,0.074 -2.45,-0.542 -3.146,-0.813c-0.695,-0.269 -1.365,-0.818 -2.11,-0.877z" fill="#EFAFC0" id="svg_391"/>
|
||||
<g id="svg_392">
|
||||
<path d="m87.677,40.908c-1.249,0 -2.339,-0.926 -2.51,-2.197c-0.09,-0.671 0.086,-1.337 0.497,-1.875c0.411,-0.538 1.006,-0.884 1.677,-0.974c1.386,-0.186 2.663,0.79 2.849,2.174c0.186,1.385 -0.79,2.662 -2.175,2.849c-0.113,0.016 -0.226,0.023 -0.338,0.023zm0.003,-4.826c-0.102,0 -0.203,0.007 -0.306,0.021c-0.606,0.081 -1.145,0.394 -1.516,0.88c-0.372,0.487 -0.531,1.089 -0.45,1.695c0.169,1.252 1.324,2.137 2.575,1.966c1.252,-0.168 2.134,-1.324 1.966,-2.576c-0.154,-1.148 -1.139,-1.986 -2.269,-1.986z" fill="#2D294C" id="svg_393"/>
|
||||
</g>
|
||||
<g id="svg_394">
|
||||
<path d="m93.067,40.184c-0.552,0 -1.086,-0.179 -1.532,-0.52c-0.538,-0.411 -0.884,-1.006 -0.974,-1.677c-0.186,-1.385 0.789,-2.663 2.174,-2.849c1.384,-0.184 2.663,0.79 2.849,2.175c0.09,0.671 -0.086,1.336 -0.497,1.874c-0.411,0.538 -1.006,0.884 -1.678,0.974c-0.114,0.015 -0.228,0.023 -0.342,0.023zm0.007,-4.826c-0.102,0 -0.203,0.007 -0.306,0.021c-1.252,0.168 -2.133,1.324 -1.965,2.576c0.082,0.607 0.394,1.145 0.88,1.517c0.488,0.372 1.094,0.532 1.695,0.449c0.607,-0.081 1.146,-0.394 1.517,-0.88c0.372,-0.487 0.531,-1.088 0.45,-1.695c-0.156,-1.15 -1.141,-1.988 -2.271,-1.988z" fill="#2D294C" id="svg_395"/>
|
||||
</g>
|
||||
<g id="svg_396">
|
||||
<rect fill="#2D294C" height="0.242" id="svg_397" transform="matrix(0.9187 -0.3949 0.3949 0.9187 -20.6624 136.864)" width="2.505" x="133.483205" y="-48.057873"/>
|
||||
</g>
|
||||
<g id="svg_398">
|
||||
<path d="m90.262,87.938l20.542,0l0,-1.506l-20.542,0c-0.416,0 -0.753,0.337 -0.753,0.753l0,0c0,0.415 0.337,0.753 0.753,0.753z" fill="#A8A5C4" id="svg_399"/>
|
||||
<path d="m92.011,87.938l19.809,0c0.876,0 1.643,-0.59 1.867,-1.436l3.496,-13.172c0.325,-1.226 -0.599,-2.428 -1.867,-2.428l-17.297,0c-0.876,0 -1.643,0.589 -1.867,1.436l-4.141,15.6z" fill="#CFCCDF" id="svg_400"/>
|
||||
<path d="m115.315,70.902c1.268,0 2.193,1.202 1.867,2.428l-3.496,13.172c-0.108,0.406 -0.342,0.751 -0.652,1.002l-12.041,-16.601l14.322,0l0,-0.001z" fill="#CFCCDF" id="svg_401"/>
|
||||
<path d="m103.676,80.586c-0.217,0.859 0.324,1.556 1.208,1.556c0.884,0 1.776,-0.696 1.993,-1.556c0.217,-0.859 -0.324,-1.556 -1.208,-1.556c-0.884,0 -1.776,0.697 -1.993,1.556z" fill="#FFFFFF" id="svg_402"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_403">
|
||||
<path d="m126.823,42.216l-2.049,-3.268c0,0 0.64,-1.378 -0.06,-2.56c-0.699,-1.182 -1.769,-3.318 -2.053,-3.046s0.287,2.342 0.287,2.342s-1.883,-3.209 -3.159,-3.401c-1.276,-0.193 1.325,3.309 1.325,3.309s-2.943,-3.063 -3.122,-2.455s1.769,3.318 1.933,3.488c0.164,0.17 -2.237,-2.215 -2.411,-1.829c-0.175,0.386 2.694,4.394 3.295,4.906c0.602,0.513 5.102,3.607 5.102,3.607l0.912,-1.093z" fill="#EFAFC0" id="svg_404"/>
|
||||
<path d="m152.166,57.969l-0.035,-0.084l-9.44,-2.281l-1.141,4.72c-0.16,0.665 -4.486,17.513 -4.967,25.197c-0.286,4.586 11.326,34.167 12.018,35.98l0.099,1.43l-1.833,2.344c0,0 1.511,0.448 3.066,0.122c1.555,-0.326 2.9,-1.025 2.9,-1.025l-1.123,-4.079c0,0 -3.448,-21.082 -4.531,-27.574c-0.438,-2.627 -1.976,-4.794 -1.954,-5.541c0.256,-4.092 3.397,-11.984 5.887,-17.698c-0.193,5.235 -0.476,12.31 -0.735,16.441c-0.282,4.514 4.782,36.055 4.784,37.293l0.006,-0.012l-0.008,0.041l-2.354,2.58c0,0 1.511,0.448 3.066,0.122c1.555,-0.326 2.573,-1.911 2.573,-1.911s1.925,-36.981 2.142,-40.473c0.463,-7.413 0.469,-20.196 0.469,-20.737l0,-4.856l-8.889,0l0,0.001z" fill="#EFAFC0" id="svg_405"/>
|
||||
<path d="m141.222,34.996c-0.081,1.39 0.046,2.794 -0.178,4.168c-0.224,1.374 -0.881,2.774 -2.104,3.438c-1.778,0.965 -3.985,0.055 -5.662,-1.077c-1.677,-1.132 -3.352,-2.546 -5.374,-2.628c-2.174,-0.088 -4.267,1.672 -4.554,3.829c-0.278,2.09 1.001,4.141 2.706,5.38c1.706,1.239 3.786,1.824 5.818,2.387c2.631,0.728 5.511,1.443 8.015,0.354c1.623,-0.706 2.845,-2.08 3.982,-3.437c2.616,-3.121 5.148,-6.53 5.932,-10.527c0.536,-2.731 0.007,-8.52 -3.222,-9.671c-3.815,-1.359 -5.207,5.17 -5.359,7.784z" fill="#5387C4" id="svg_406"/>
|
||||
<path d="m163.17,56.037l0.452,10.385l-2.032,17.61l-25.06,-1.355l0.249,-2.57c0.734,-7.587 2.437,-15.048 5.067,-22.202l0.846,-2.301l20.478,0.433z" fill="#DC614B" id="svg_407"/>
|
||||
<path d="m166.608,27.766c-1.695,-1.277 -2.558,-3.404 -2.871,-5.503c-0.313,-2.1 -0.152,-4.237 -0.217,-6.359c-0.046,-1.511 -0.218,-3.057 -0.898,-4.407c-0.68,-1.35 -1.955,-2.479 -3.46,-2.629c-0.924,-0.092 -1.869,0.183 -2.779,-0.006c-0.198,0.405 -0.265,0.944 0.064,1.251c0.33,0.307 0.747,0.518 1.037,0.863c0.396,0.469 0.501,1.116 0.519,1.729c0.082,2.845 -1.37,5.539 -1.477,8.384c-0.052,1.381 0.227,2.787 0.901,3.993c0.674,1.206 1.758,2.199 3.058,2.666c1.841,0.659 4.217,0.449 6.123,0.018z" fill="#2D294C" id="svg_408"/>
|
||||
<path d="m148.76,25.747l1.141,-0.145c0,0 1.821,1.121 2.287,1.037c1.09,-0.195 1.289,-0.589 1.289,-0.589s1.077,-0.719 1.563,-0.685c6.145,0.436 5.828,4.114 6.358,6.234c0.928,3.716 -2.395,9.115 -2.395,9.115s4.126,13.236 3.942,15.548l-11.556,1.9c-3.17,0.521 -6.421,-0.151 -9.124,-1.887l0,0l1.264,-11.978l-0.833,-2.272c-1.126,-3.07 -1.414,-6.387 -0.821,-9.603c0.253,-1.368 0.523,-2.637 0.751,-3.252c0.644,-1.736 4.295,-3.189 6.134,-3.423z" fill="#6F9EDE" id="svg_409"/>
|
||||
<g id="svg_410">
|
||||
<g id="svg_411">
|
||||
<path d="m152.026,28.777l0,0c1.696,0 3.07,-1.375 3.07,-3.07l0,-8.061c0,-0.203 -0.165,-0.367 -0.367,-0.367l-5.406,0c-0.203,0 -0.367,0.164 -0.367,0.367l0,8.061c-0.001,1.695 1.374,3.07 3.07,3.07z" fill="#EFAFC0" id="svg_412"/>
|
||||
<path d="m155.096,19.621c-0.813,2.108 -2.586,5.356 -6.141,6.155l0,-8.499l5.55,0l0.59,0.259l0,2.085l0.001,0z" fill="#D98E9D" id="svg_413" opacity="0.5"/>
|
||||
<path d="m126.823,42.216l-2.049,-3.268c0,0 0.64,-1.378 -0.06,-2.56c-0.699,-1.182 -1.769,-3.318 -2.053,-3.046s0.287,2.342 0.287,2.342s-1.883,-3.209 -3.159,-3.401c-1.276,-0.193 1.325,3.309 1.325,3.309s-2.943,-3.063 -3.122,-2.455s1.769,3.318 1.933,3.488c0.164,0.17 -2.237,-2.215 -2.411,-1.829c-0.175,0.386 2.694,4.394 3.295,4.906c0.602,0.513 5.102,3.607 5.102,3.607l0.912,-1.093z" fill="#EFAFC0" id="svg_404"/>
|
||||
<path d="m152.166,57.969l-0.035,-0.084l-9.44,-2.281l-1.141,4.72c-0.16,0.665 -4.486,17.513 -4.967,25.197c-0.286,4.586 11.326,34.167 12.018,35.98l0.099,1.43l-1.833,2.344c0,0 1.511,0.448 3.066,0.122c1.555,-0.326 2.9,-1.025 2.9,-1.025l-1.123,-4.079c0,0 -3.448,-21.082 -4.531,-27.574c-0.438,-2.627 -1.976,-4.794 -1.954,-5.541c0.256,-4.092 3.397,-11.984 5.887,-17.698c-0.193,5.235 -0.476,12.31 -0.735,16.441c-0.282,4.514 4.782,36.055 4.784,37.293l0.006,-0.012l-0.008,0.041l-2.354,2.58c0,0 1.511,0.448 3.066,0.122c1.555,-0.326 2.573,-1.911 2.573,-1.911s1.925,-36.981 2.142,-40.473c0.463,-7.413 0.469,-20.196 0.469,-20.737l0,-4.856l-8.889,0l0,0.001z" fill="#EFAFC0" id="svg_405"/>
|
||||
<path d="m141.222,34.996c-0.081,1.39 0.046,2.794 -0.178,4.168c-0.224,1.374 -0.881,2.774 -2.104,3.438c-1.778,0.965 -3.985,0.055 -5.662,-1.077c-1.677,-1.132 -3.352,-2.546 -5.374,-2.628c-2.174,-0.088 -4.267,1.672 -4.554,3.829c-0.278,2.09 1.001,4.141 2.706,5.38c1.706,1.239 3.786,1.824 5.818,2.387c2.631,0.728 5.511,1.443 8.015,0.354c1.623,-0.706 2.845,-2.08 3.982,-3.437c2.616,-3.121 5.148,-6.53 5.932,-10.527c0.536,-2.731 0.007,-8.52 -3.222,-9.671c-3.815,-1.359 -5.207,5.17 -5.359,7.784z" fill="#6F9EDE" id="svg_406"/>
|
||||
<path d="m163.17,56.037l0.452,10.385l-2.032,17.61l-25.06,-1.355l0.249,-2.57c0.734,-7.587 2.437,-15.048 5.067,-22.202l0.846,-2.301l20.478,0.433z" fill="#DC614B" id="svg_407"/>
|
||||
<path d="m166.608,27.766c-1.695,-1.277 -2.558,-3.404 -2.871,-5.503c-0.313,-2.1 -0.152,-4.237 -0.217,-6.359c-0.046,-1.511 -0.218,-3.057 -0.898,-4.407c-0.68,-1.35 -1.955,-2.479 -3.46,-2.629c-0.924,-0.092 -1.869,0.183 -2.779,-0.006c-0.198,0.405 -0.265,0.944 0.064,1.251c0.33,0.307 0.747,0.518 1.037,0.863c0.396,0.469 0.501,1.116 0.519,1.729c0.082,2.845 -1.37,5.539 -1.477,8.384c-0.052,1.381 0.227,2.787 0.901,3.993c0.674,1.206 1.758,2.199 3.058,2.666c1.841,0.659 4.217,0.449 6.123,0.018z" fill="#2D294C" id="svg_408"/>
|
||||
<path d="m148.76,25.747l1.141,-0.145c0,0 1.821,1.121 2.287,1.037c1.09,-0.195 1.289,-0.589 1.289,-0.589s1.077,-0.719 1.563,-0.685c6.145,0.436 5.828,4.114 6.358,6.234c0.928,3.716 -2.395,9.115 -2.395,9.115s4.126,13.236 3.942,15.548l-11.556,1.9c-3.17,0.521 -6.421,-0.151 -9.124,-1.887l0,0l1.264,-11.978l-0.833,-2.272c-1.126,-3.07 -1.414,-6.387 -0.821,-9.603c0.253,-1.368 0.523,-2.637 0.751,-3.252c0.644,-1.736 4.295,-3.189 6.134,-3.423z" fill="#6F9EDE" id="svg_409"/>
|
||||
<g id="svg_410">
|
||||
<g id="svg_411">
|
||||
<path d="m152.026,28.777l0,0c1.696,0 3.07,-1.375 3.07,-3.07l0,-8.061c0,-0.203 -0.165,-0.367 -0.367,-0.367l-5.406,0c-0.203,0 -0.367,0.164 -0.367,0.367l0,8.061c-0.001,1.695 1.374,3.07 3.07,3.07z" fill="#EFAFC0" id="svg_412"/>
|
||||
<path d="m155.096,19.621c-0.813,2.108 -2.586,5.356 -6.141,6.155l0,-8.499l5.55,0l0.59,0.259l0,2.085l0.001,0z" fill="#D98E9D" id="svg_413" opacity="0.5"/>
|
||||
</g>
|
||||
<path d="m147.55,8.2l1.413,0c2.809,0 5.086,2.277 5.086,5.086l0,4.812c0,2.84 -2.302,5.143 -5.143,5.143l-2.507,0c-2.173,0 -3.934,-1.761 -3.934,-3.934l0,-6.021c0,-2.809 2.277,-5.086 5.085,-5.086z" fill="#EFAFC0" id="svg_414"/>
|
||||
<circle cx="154.682" cy="16.984" fill="#EFAFC0" id="svg_415" r="2.736"/>
|
||||
<path d="m153.984,14.586c-0.213,0.985 -0.35,1.986 -0.41,2.991c-0.114,-0.107 -0.325,-0.155 -0.47,-0.099" fill="none" id="svg_416"/>
|
||||
<path d="m145.848,19.027l2.962,-0.753c0,0 -0.472,1.61 -1.418,1.773c-0.947,0.163 -1.544,-1.02 -1.544,-1.02z" fill="#FFFFFF" id="svg_417"/>
|
||||
<path d="m146.767,14.676c0,0.305 0.247,0.552 0.552,0.552c0.305,0 0.552,-0.247 0.552,-0.552c0,-0.305 -0.247,-0.552 -0.552,-0.552c-0.305,0 -0.552,0.247 -0.552,0.552z" fill="#2D294C" id="svg_418"/>
|
||||
<path d="m142.905,14.676c0,0.305 0.247,0.552 0.552,0.552c0.305,0 0.552,-0.247 0.552,-0.552c0,-0.305 -0.247,-0.552 -0.552,-0.552c-0.305,0 -0.552,0.247 -0.552,0.552z" fill="#2D294C" id="svg_419"/>
|
||||
<path d="m151.485,8.973c0,0 -1.112,2.88 0.312,4.687c0.434,0.551 0.784,2.299 0.931,2.984c0.153,0 0.321,-1.654 1.2,-2.207c0.304,-0.195 1.535,-0.114 1.535,-0.114s0.673,-1.691 0.818,-2.126c0.185,-0.554 0.492,-1.063 0.666,-1.62c0.581,-1.859 -0.652,-4.078 -2.537,-4.567c-0.551,-0.143 -1.122,-0.216 -1.691,-0.236c-12.074,-0.429 -10.65,5.234 -10.605,6.527c5.007,0.668 9.371,-3.328 9.371,-3.328z" fill="#2D294C" id="svg_420"/>
|
||||
</g>
|
||||
<path d="m147.55,8.2l1.413,0c2.809,0 5.086,2.277 5.086,5.086l0,4.812c0,2.84 -2.302,5.143 -5.143,5.143l-2.507,0c-2.173,0 -3.934,-1.761 -3.934,-3.934l0,-6.021c0,-2.809 2.277,-5.086 5.085,-5.086z" fill="#EFAFC0" id="svg_414"/>
|
||||
<circle cx="154.682" cy="16.984" fill="#EFAFC0" id="svg_415" r="2.736"/>
|
||||
<path d="m153.984,14.586c-0.213,0.985 -0.35,1.986 -0.41,2.991c-0.114,-0.107 -0.325,-0.155 -0.47,-0.099" fill="none" id="svg_416"/>
|
||||
<path d="m145.848,19.027l2.962,-0.753c0,0 -0.472,1.61 -1.418,1.773c-0.947,0.163 -1.544,-1.02 -1.544,-1.02z" fill="#FFFFFF" id="svg_417"/>
|
||||
<path d="m146.767,14.676c0,0.305 0.247,0.552 0.552,0.552c0.305,0 0.552,-0.247 0.552,-0.552c0,-0.305 -0.247,-0.552 -0.552,-0.552c-0.305,0 -0.552,0.247 -0.552,0.552z" fill="#2D294C" id="svg_418"/>
|
||||
<path d="m142.905,14.676c0,0.305 0.247,0.552 0.552,0.552c0.305,0 0.552,-0.247 0.552,-0.552c0,-0.305 -0.247,-0.552 -0.552,-0.552c-0.305,0 -0.552,0.247 -0.552,0.552z" fill="#2D294C" id="svg_419"/>
|
||||
<path d="m151.485,8.973c0,0 -1.112,2.88 0.312,4.687c0.434,0.551 0.784,2.299 0.931,2.984c0.153,0 0.321,-1.654 1.2,-2.207c0.304,-0.195 1.535,-0.114 1.535,-0.114s0.673,-1.691 0.818,-2.126c0.185,-0.554 0.492,-1.063 0.666,-1.62c0.581,-1.859 -0.652,-4.078 -2.537,-4.567c-0.551,-0.143 -1.122,-0.216 -1.691,-0.236c-12.074,-0.429 -10.65,5.234 -10.605,6.527c5.007,0.668 9.371,-3.328 9.371,-3.328z" fill="#2D294C" id="svg_420"/>
|
||||
</g>
|
||||
<path d="m163.38,57.685c0.802,-0.333 1.613,-0.804 1.996,-1.584c0.383,-0.78 -1.006,-1.349 -1.831,-1.621c-0.785,0.718 -1.993,0.582 -3.035,0.797c-0.632,0.13 -1.225,0.402 -1.799,0.696c-0.736,0.377 -1.46,0.802 -2.046,1.386c-0.586,0.584 -1.028,1.344 -1.095,2.169c-0.016,0.193 0.131,0.325 0.316,0.269c0.185,-0.057 0.299,-0.237 0.407,-0.398c0.337,-0.505 0.771,-0.945 1.27,-1.29c0.091,-0.063 0.221,0.08 0.141,0.155c-1.171,1.105 -1.214,2.089 -1.164,2.16c0.055,0.077 0.171,0.074 0.26,0.044c0.161,-0.056 0.349,-0.387 0.455,-0.52c0.45,-0.566 0.944,-0.804 0.944,-0.804s0.6,-0.157 -0.607,1.04c-0.148,0.147 -0.293,0.329 -0.291,0.538c0,0.037 0.006,0.074 0.024,0.106c0.021,0.038 0.059,0.066 0.098,0.085c0.122,0.058 0.268,0.043 0.392,-0.009c0.124,-0.052 0.231,-0.139 0.335,-0.223c0.514,-0.417 1.027,-0.834 1.541,-1.251c-0.123,0.149 -1.014,1.026 -1.082,1.207c-0.026,0.07 -0.047,0.153 -0.009,0.218c0.056,0.096 0.195,0.09 0.303,0.066c0.73,-0.16 2.161,-1.274 2.74,-1.746c0.58,-0.473 1.047,-1.203 1.737,-1.49z" fill="#EFAFC0" id="svg_421"/>
|
||||
<path d="m163.052,29.686c2.636,2.75 5.273,5.5 7.909,8.25c2.443,2.548 5.045,5.473 5.038,9.003c-0.005,2.685 -1.557,5.122 -3.318,7.148c-1.249,1.437 -2.708,2.804 -4.535,3.338c-1.827,0.535 -4.086,-0.005 -5.022,-1.663c-1.015,-1.796 -0.176,-4.044 0.782,-5.871c0.958,-1.827 2.065,-3.848 1.519,-5.837c-0.388,-1.416 -1.538,-2.469 -2.585,-3.498c-2.957,-2.908 -5.618,-6.529 -6.459,-10.668c-0.357,-1.759 -0.916,-3.975 1.325,-3.962c1.979,0.012 4.113,2.473 5.346,3.76z" fill="#6F9EDE" id="svg_422"/>
|
||||
<path d="m148.515,121.53l-1.375,3.425l-3.653,1.661c-1.819,0.738 -1.155,1.634 -1.155,1.634l11.019,-0.762l-1.007,-6.275l-3.829,0.317z" fill="#2D294C" id="svg_423"/>
|
||||
<path d="m152.805,125.823l-3.653,1.661c-1.819,0.738 -1.155,1.634 -1.155,1.634l10.017,-0.755l0.776,-6.717l-3.943,-0.403l0.317,1.9l-2.359,2.68z" fill="#2D294C" id="svg_424"/>
|
||||
<path d="m163.38,57.685c0.802,-0.333 1.613,-0.804 1.996,-1.584c0.383,-0.78 -1.006,-1.349 -1.831,-1.621c-0.785,0.718 -1.993,0.582 -3.035,0.797c-0.632,0.13 -1.225,0.402 -1.799,0.696c-0.736,0.377 -1.46,0.802 -2.046,1.386c-0.586,0.584 -1.028,1.344 -1.095,2.169c-0.016,0.193 0.131,0.325 0.316,0.269c0.185,-0.057 0.299,-0.237 0.407,-0.398c0.337,-0.505 0.771,-0.945 1.27,-1.29c0.091,-0.063 0.221,0.08 0.141,0.155c-1.171,1.105 -1.214,2.089 -1.164,2.16c0.055,0.077 0.171,0.074 0.26,0.044c0.161,-0.056 0.349,-0.387 0.455,-0.52c0.45,-0.566 0.944,-0.804 0.944,-0.804s0.6,-0.157 -0.607,1.04c-0.148,0.147 -0.293,0.329 -0.291,0.538c0,0.037 0.006,0.074 0.024,0.106c0.021,0.038 0.059,0.066 0.098,0.085c0.122,0.058 0.268,0.043 0.392,-0.009c0.124,-0.052 0.231,-0.139 0.335,-0.223c0.514,-0.417 1.027,-0.834 1.541,-1.251c-0.123,0.149 -1.014,1.026 -1.082,1.207c-0.026,0.07 -0.047,0.153 -0.009,0.218c0.056,0.096 0.195,0.09 0.303,0.066c0.73,-0.16 2.161,-1.274 2.74,-1.746c0.58,-0.473 1.047,-1.203 1.737,-1.49z" fill="#EFAFC0" id="svg_421"/>
|
||||
<path d="m163.052,29.686c2.636,2.75 5.273,5.5 7.909,8.25c2.443,2.548 5.045,5.473 5.038,9.003c-0.005,2.685 -1.557,5.122 -3.318,7.148c-1.249,1.437 -2.708,2.804 -4.535,3.338c-1.827,0.535 -4.086,-0.005 -5.022,-1.663c-1.015,-1.796 -0.176,-4.044 0.782,-5.871c0.958,-1.827 2.065,-3.848 1.519,-5.837c-0.388,-1.416 -1.538,-2.469 -2.585,-3.498c-2.957,-2.908 -5.618,-6.529 -6.459,-10.668c-0.357,-1.759 -0.916,-3.975 1.325,-3.962c1.979,0.012 4.113,2.473 5.346,3.76z" fill="#6F9EDE" id="svg_422"/>
|
||||
<path d="m148.515,121.53l-1.375,3.425l-3.653,1.661c-1.819,0.738 -1.155,1.634 -1.155,1.634l11.019,-0.762l-1.007,-6.275l-3.829,0.317z" fill="#2D294C" id="svg_423"/>
|
||||
<path d="m152.805,125.823l-3.653,1.661c-1.819,0.738 -1.155,1.634 -1.155,1.634l10.017,-0.755l0.776,-6.717l-3.943,-0.403l0.317,1.9l-2.359,2.68z" fill="#2D294C" id="svg_424"/>
|
||||
</g>
|
||||
<g id="svg_425">
|
||||
<path d="m24.854,55.079c4.549,0.076 9.099,0.153 13.648,0.229c1.546,0.026 3.103,0.051 4.624,-0.227c3.221,-0.588 11.197,-8.695 13.435,-10.265c0.838,0.743 1.646,1.432 2.484,2.174c-2.347,4.501 -3.586,7.027 -6.704,11.032c-0.932,1.197 -1.938,2.402 -3.293,3.082c-1.054,0.528 -2.246,0.7 -3.417,0.839c-4.995,0.59 -10.047,0.696 -15.063,0.317c-0.661,-0.05 -1.359,-0.122 -1.888,-0.522c-0.461,-0.348 -0.723,-0.894 -0.967,-1.418c-1.162,-2.485 -1.699,-2.754 -2.859,-5.241z" fill="#EDA956" id="svg_426"/>
|
||||
<path d="m27.581,35.701c0,0 -7.203,0.647 -9.146,7.365c-1.943,6.718 1.781,9.308 1.781,9.308l1.376,0.081l0.648,-1.295l0.486,1.295l8.579,0.971l0.567,-0.648l0.405,0.648c0,0 4.209,-1.295 3.966,-4.694c-0.243,-3.399 -0.081,-5.018 -1.133,-7.041c-1.052,-2.023 -2.914,-4.452 -2.914,-4.452l-4.615,-1.538z" fill="#563238" id="svg_427"/>
|
||||
<g id="svg_428">
|
||||
<path d="m35.733,93.744l4.374,34.965l0.712,0l-2.287,-36.243c0.001,0.001 -2.799,1.917 -2.799,1.278z" fill="#2D294C" id="svg_429"/>
|
||||
<path d="m45.446,92.449l16.677,36.26l0.712,0l-14.589,-37.538c0,0.001 -2.8,1.917 -2.8,1.278z" fill="#2D294C" id="svg_430"/>
|
||||
<path d="m23.661,92.449l-16.677,36.26l-0.712,0l14.589,-37.538c0.001,0.001 2.8,1.917 2.8,1.278z" fill="#2D294C" id="svg_431"/>
|
||||
<path d="m31.755,93.744l-4.374,34.965l-0.712,0l2.287,-36.243c-0.001,0.001 2.799,1.917 2.799,1.278z" fill="#2D294C" id="svg_432"/>
|
||||
<g id="svg_433">
|
||||
<rect fill="#2D294C" height="0.485" id="svg_434" transform="matrix(0.4675 -0.884 0.884 0.4675 -9.1243 344.263)" width="19.406" x="230.048508" y="-67.858469"/>
|
||||
<path d="m24.854,55.079c4.549,0.076 9.099,0.153 13.648,0.229c1.546,0.026 3.103,0.051 4.624,-0.227c3.221,-0.588 11.197,-8.695 13.435,-10.265c0.838,0.743 1.646,1.432 2.484,2.174c-2.347,4.501 -3.586,7.027 -6.704,11.032c-0.932,1.197 -1.938,2.402 -3.293,3.082c-1.054,0.528 -2.246,0.7 -3.417,0.839c-4.995,0.59 -10.047,0.696 -15.063,0.317c-0.661,-0.05 -1.359,-0.122 -1.888,-0.522c-0.461,-0.348 -0.723,-0.894 -0.967,-1.418c-1.162,-2.485 -1.699,-2.754 -2.859,-5.241z" fill="#FFCD8B" id="svg_426"/>
|
||||
<path d="m27.581,35.701c0,0 -7.203,0.647 -9.146,7.365c-1.943,6.718 1.781,9.308 1.781,9.308l1.376,0.081l0.648,-1.295l0.486,1.295l8.579,0.971l0.567,-0.648l0.405,0.648c0,0 4.209,-1.295 3.966,-4.694c-0.243,-3.399 -0.081,-5.018 -1.133,-7.041c-1.052,-2.023 -2.914,-4.452 -2.914,-4.452l-4.615,-1.538z" fill="#563238" id="svg_427"/>
|
||||
<g id="svg_428">
|
||||
<path d="m35.733,93.744l4.374,34.965l0.712,0l-2.287,-36.243c0.001,0.001 -2.799,1.917 -2.799,1.278z" fill="#2D294C" id="svg_429"/>
|
||||
<path d="m45.446,92.449l16.677,36.26l0.712,0l-14.589,-37.538c0,0.001 -2.8,1.917 -2.8,1.278z" fill="#2D294C" id="svg_430"/>
|
||||
<path d="m23.661,92.449l-16.677,36.26l-0.712,0l14.589,-37.538c0.001,0.001 2.8,1.917 2.8,1.278z" fill="#2D294C" id="svg_431"/>
|
||||
<path d="m31.755,93.744l-4.374,34.965l-0.712,0l2.287,-36.243c-0.001,0.001 2.799,1.917 2.799,1.278z" fill="#2D294C" id="svg_432"/>
|
||||
<g id="svg_433">
|
||||
<rect fill="#2D294C" height="0.485" id="svg_434" transform="matrix(0.4675 -0.884 0.884 0.4675 -9.1243 344.263)" width="19.406" x="230.048508" y="-67.858469"/>
|
||||
</g>
|
||||
<g id="svg_435">
|
||||
<rect fill="#2D294C" height="22.897" id="svg_436" transform="matrix(0.6928 -0.7211 0.7211 0.6928 -43.4672 259.313)" width="0.485" x="174.469854" y="-57.134714"/>
|
||||
</g>
|
||||
<g id="svg_437">
|
||||
<rect fill="#2D294C" height="0.486" id="svg_438" transform="matrix(0.6874 -0.7263 0.7263 0.6874 -50.5056 245.956)" width="24.505" x="141.987717" y="-45.752078"/>
|
||||
</g>
|
||||
<g id="svg_439">
|
||||
<polygon fill="#2D294C" id="svg_440" points="30.022003173828125,107.79200744628906 20.957000732421875,92.89999389648438 21.37298583984375,92.64700317382812 30.43798828125,107.53900146484375 "/>
|
||||
</g>
|
||||
<path d="m18.899,55.868c0,0 10.684,-2.59 14.245,19.101l0.56,2.427c0.816,3.538 3.897,6.093 7.525,6.241l4.676,0.191c3.044,0.124 5.701,2.099 6.697,4.978l1.262,3.645l2.586,0c0,0 -0.971,5.18 -6.151,5.18c-5.18,0 -17.155,0 -17.155,0l-11.007,0c0,0 -9.065,0.648 -10.684,-11.331c-1.619,-11.979 -7.447,-31.08 7.446,-30.432z" fill="#5A8ECC" id="svg_441"/>
|
||||
<path d="m59.2,124.768l2.354,2.58l3.653,1.66c1.819,0.738 1.155,1.635 1.155,1.635l-10.017,-0.755l-0.757,-3.442l3.612,-1.678z" fill="#2D294C" id="svg_442"/>
|
||||
<path d="m55.342,116.368l3.858,8.4l2.354,2.58c0,0 -1.511,0.448 -3.066,0.122c-1.555,-0.326 -2.9,-1.025 -2.9,-1.025l-4.08,-6.569l-0.47,-3.327l4.304,-0.181z" fill="#EFAFC0" id="svg_443"/>
|
||||
<polygon fill="#EFAFC0" id="svg_444" points="46.364990234375,119.7550048828125 43.110992431640625,122.447998046875 43.89599609375,126.93699645996094 46.701995849609375,128.0590057373047 41.989013671875,127.94700622558594 39.519989013671875,121.55099487304688 42.217010498046875,118.19599914550781 "/>
|
||||
<path d="m46.702,128.059c0,0 0.991,0.013 1.331,0.391c0.34,0.377 0,0.944 0,0.944l-7.615,-0.213l-2.581,-7.406l1.683,-0.224l2.469,6.396l4.713,0.112z" fill="#2D294C" id="svg_445"/>
|
||||
<g id="svg_446">
|
||||
<path d="m47.267,120.77l-7.793,-3.96l1.98,-3.896c3.337,-6.565 6.933,-14.445 8.429,-18.673c-4.337,-0.627 -12.462,-1.279 -19.433,-1.635l-4.365,-0.222l0.446,-8.73l4.365,0.222c23.576,1.202 25.569,2.862 26.639,3.754c3.192,2.661 2.048,6.952 -2.774,17.706c-2.604,5.804 -5.396,11.306 -5.513,11.538l-1.981,3.896zm5.396,-25.969l0,0l0,0z" fill="#DC614B" id="svg_447"/>
|
||||
</g>
|
||||
<path d="m38.418,93.753l-8.349,0.463c-4.148,0 -9.259,-3.453 -9.259,-7.601l-0.124,-2.204l16.226,-1.472l1.506,10.814z" fill="#DC614B" id="svg_448"/>
|
||||
<g id="svg_449">
|
||||
<path d="m49.849,123.164l-0.339,-4.357c-0.68,-8.739 -2.282,-19.42 -3.618,-21.755c-1.457,-1.115 -7.727,-2.828 -13.42,-3.641l-4.326,-0.618l1.236,-8.653l4.327,0.618c4.303,0.615 14.717,2.449 18.469,6.2c3.528,3.527 5.236,16.723 6.047,27.171l0.339,4.357l-8.715,0.678zm-4.032,-26.233l0,0l0,0z" fill="#DC614B" id="svg_450"/>
|
||||
</g>
|
||||
<path d="m30.622,55.47c0,0 -6.79,-0.266 -7.257,-0.224c-1.32,0.117 -3.098,0.479 -3.965,0.973c-1.958,1.114 -2.138,2.935 -2.305,4.379c-0.226,1.95 4.137,11.453 4.137,11.453l-1.692,8.038c0,0 -0.26,3.501 0,5.711l17.557,-2.327l-1.423,-6.738l-1.457,-8.822c0,0 2.514,-4.672 0.89,-6.394c-3.922,-4.16 -4.485,-6.049 -4.485,-6.049z" fill="#FFCD8B" id="svg_451"/>
|
||||
<g id="svg_452">
|
||||
<path d="m26.036,58.4l0,0c-1.314,0 -2.378,-1.065 -2.378,-2.379l0,-10.132c0,-0.157 0.127,-0.285 0.285,-0.285l4.188,0c0.157,0 0.285,0.127 0.285,0.285l0,10.132c-0.001,1.314 -1.066,2.379 -2.38,2.379z" fill="#EFAFC0" id="svg_453"/>
|
||||
<path d="m23.658,51.307c0.63,1.633 2.003,4.149 4.757,4.768l0,-10.471l-4.3,0l-0.457,0.201l0,5.502z" fill="#D98E9D" id="svg_454" opacity="0.5"/>
|
||||
</g>
|
||||
<path d="m30.232,37.823l-1.413,0c-2.809,0 -5.086,2.277 -5.086,5.086l0,4.812c0,2.84 2.302,5.143 5.143,5.143l0.659,0c3.193,0 5.782,-2.589 5.782,-5.782l0,-4.173c0,-2.809 -2.277,-5.086 -5.085,-5.086z" fill="#EFAFC0" id="svg_455"/>
|
||||
<path d="m25.836,46.607c0,1.511 -1.225,2.736 -2.736,2.736s-2.736,-1.225 -2.736,-2.736c0,-1.511 1.225,-2.736 2.736,-2.736s2.736,1.225 2.736,2.736z" fill="#EFAFC0" id="svg_456"/>
|
||||
<path d="m23.798,42.265c0.213,0.985 0.35,1.986 0.41,2.991c0.114,-0.106 0.325,-0.155 0.47,-0.099" fill="none" id="svg_457"/>
|
||||
<path d="m31.935,48.65l-2.962,-0.753c0,0 0.472,1.61 1.418,1.773c0.946,0.163 1.544,-1.02 1.544,-1.02z" fill="#FFFFFF" id="svg_458"/>
|
||||
<circle cx="30.464" cy="44.299" fill="#2D294C" id="svg_459" r="0.552"/>
|
||||
<path d="m34.877,44.299c0,0.305 -0.247,0.552 -0.552,0.552c-0.305,0 -0.552,-0.247 -0.552,-0.552c0,-0.305 0.247,-0.552 0.552,-0.552c0.305,0 0.552,0.247 0.552,0.552z" fill="#2D294C" id="svg_460"/>
|
||||
<path d="m11.452,86.3c-0.717,-5.305 -2.256,-12.005 -2.459,-17.77l0,0l5.055,14.542c1.072,3.084 3.979,5.151 7.244,5.151l3.238,0c2.861,0 5.44,1.725 6.532,4.37l2.081,5.039l-11.007,0c0,-0.001 -9.065,0.647 -10.684,-11.332z" fill="#6F9EDE" id="svg_461"/>
|
||||
<path d="m23.69,89.615c-0.835,-0.239 -1.695,-0.614 -2.165,-1.344c-0.47,-0.73 0.845,-1.456 1.633,-1.82c0.862,0.623 2.047,0.35 3.107,0.443c0.643,0.057 1.263,0.258 1.867,0.485c0.774,0.29 1.542,0.63 2.191,1.142c0.649,0.513 1.176,1.218 1.337,2.029c0.038,0.19 -0.093,0.338 -0.283,0.303c-0.19,-0.035 -0.324,-0.201 -0.449,-0.349c-0.393,-0.463 -0.874,-0.851 -1.41,-1.136c-0.097,-0.052 -0.21,0.105 -0.122,0.17c1.29,0.964 1.446,1.936 1.404,2.013c-0.046,0.083 -0.161,0.093 -0.254,0.073c-0.167,-0.037 -0.391,-0.344 -0.512,-0.464c-0.512,-0.51 -1.03,-0.69 -1.03,-0.69s-0.614,-0.087 0.722,0.964c0.164,0.129 0.329,0.293 0.351,0.5c0.004,0.037 0.003,0.075 -0.012,0.109c-0.017,0.041 -0.051,0.072 -0.088,0.096c-0.114,0.072 -0.262,0.074 -0.391,0.036c-0.129,-0.038 -0.245,-0.111 -0.359,-0.184c-0.558,-0.355 -1.116,-0.71 -1.675,-1.066c0.14,0.133 1.125,0.903 1.214,1.075c0.034,0.067 0.065,0.147 0.034,0.216c-0.044,0.102 -0.183,0.112 -0.294,0.101c-0.743,-0.075 -2.293,-1.017 -2.922,-1.421c-0.628,-0.404 -1.176,-1.076 -1.894,-1.281z" fill="#EFAFC0" id="svg_462"/>
|
||||
<path d="m19.326,56.368c0,0 -7.823,5.529 -9.727,14.837c-1.904,9.307 4.138,16.479 11.694,17.018l2.888,-1.432c0,0 -9.08,-9.595 -8.445,-12.768c0.635,-3.173 5.921,-10.222 5.921,-10.222s-0.521,-8.173 -2.331,-7.433z" fill="#FFCD8B" id="svg_463"/>
|
||||
</g>
|
||||
<g id="svg_435">
|
||||
<rect fill="#2D294C" height="22.897" id="svg_436" transform="matrix(0.6928 -0.7211 0.7211 0.6928 -43.4672 259.313)" width="0.485" x="174.469854" y="-57.134714"/>
|
||||
</g>
|
||||
<g id="svg_437">
|
||||
<rect fill="#2D294C" height="0.486" id="svg_438" transform="matrix(0.6874 -0.7263 0.7263 0.6874 -50.5056 245.956)" width="24.505" x="141.987717" y="-45.752078"/>
|
||||
</g>
|
||||
<g id="svg_439">
|
||||
<polygon fill="#2D294C" id="svg_440" points="30.022003173828125,107.79200744628906 20.957000732421875,92.89999389648438 21.37298583984375,92.64700317382812 30.43798828125,107.53900146484375 "/>
|
||||
</g>
|
||||
<path d="m18.899,55.868c0,0 10.684,-2.59 14.245,19.101l0.56,2.427c0.816,3.538 3.897,6.093 7.525,6.241l4.676,0.191c3.044,0.124 5.701,2.099 6.697,4.978l1.262,3.645l2.586,0c0,0 -0.971,5.18 -6.151,5.18c-5.18,0 -17.155,0 -17.155,0l-11.007,0c0,0 -9.065,0.648 -10.684,-11.331c-1.619,-11.979 -7.447,-31.08 7.446,-30.432z" fill="#5A8ECC" id="svg_441"/>
|
||||
<path d="m59.2,124.768l2.354,2.58l3.653,1.66c1.819,0.738 1.155,1.635 1.155,1.635l-10.017,-0.755l-0.757,-3.442l3.612,-1.678z" fill="#2D294C" id="svg_442"/>
|
||||
<path d="m55.342,116.368l3.858,8.4l2.354,2.58c0,0 -1.511,0.448 -3.066,0.122c-1.555,-0.326 -2.9,-1.025 -2.9,-1.025l-4.08,-6.569l-0.47,-3.327l4.304,-0.181z" fill="#EFAFC0" id="svg_443"/>
|
||||
<polygon fill="#EFAFC0" id="svg_444" points="46.364990234375,119.7550048828125 43.110992431640625,122.447998046875 43.89599609375,126.93699645996094 46.701995849609375,128.0590057373047 41.989013671875,127.94700622558594 39.519989013671875,121.55099487304688 42.217010498046875,118.19599914550781 "/>
|
||||
<path d="m46.702,128.059c0,0 0.991,0.013 1.331,0.391c0.34,0.377 0,0.944 0,0.944l-7.615,-0.213l-2.581,-7.406l1.683,-0.224l2.469,6.396l4.713,0.112z" fill="#2D294C" id="svg_445"/>
|
||||
<g id="svg_446">
|
||||
<path d="m47.267,120.77l-7.793,-3.96l1.98,-3.896c3.337,-6.565 6.933,-14.445 8.429,-18.673c-4.337,-0.627 -12.462,-1.279 -19.433,-1.635l-4.365,-0.222l0.446,-8.73l4.365,0.222c23.576,1.202 25.569,2.862 26.639,3.754c3.192,2.661 2.048,6.952 -2.774,17.706c-2.604,5.804 -5.396,11.306 -5.513,11.538l-1.981,3.896zm5.396,-25.969l0,0l0,0z" fill="#9D3542" id="svg_447"/>
|
||||
</g>
|
||||
<path d="m38.418,93.753l-8.349,0.463c-4.148,0 -9.259,-3.453 -9.259,-7.601l-0.124,-2.204l16.226,-1.472l1.506,10.814z" fill="#DC614B" id="svg_448"/>
|
||||
<g id="svg_449">
|
||||
<path d="m49.849,123.164l-0.339,-4.357c-0.68,-8.739 -2.282,-19.42 -3.618,-21.755c-1.457,-1.115 -7.727,-2.828 -13.42,-3.641l-4.326,-0.618l1.236,-8.653l4.327,0.618c4.303,0.615 14.717,2.449 18.469,6.2c3.528,3.527 5.236,16.723 6.047,27.171l0.339,4.357l-8.715,0.678zm-4.032,-26.233l0,0l0,0z" fill="#DC614B" id="svg_450"/>
|
||||
</g>
|
||||
<path d="m30.622,55.47c0,0 -6.79,-0.266 -7.257,-0.224c-1.32,0.117 -3.098,0.479 -3.965,0.973c-1.958,1.114 -2.138,2.935 -2.305,4.379c-0.226,1.95 4.137,11.453 4.137,11.453l-1.692,8.038c0,0 -0.26,3.501 0,5.711l17.557,-2.327l-1.423,-6.738l-1.457,-8.822c0,0 2.514,-4.672 0.89,-6.394c-3.922,-4.16 -4.485,-6.049 -4.485,-6.049z" fill="#FFCD8B" id="svg_451"/>
|
||||
<g id="svg_452">
|
||||
<path d="m26.036,58.4l0,0c-1.314,0 -2.378,-1.065 -2.378,-2.379l0,-10.132c0,-0.157 0.127,-0.285 0.285,-0.285l4.188,0c0.157,0 0.285,0.127 0.285,0.285l0,10.132c-0.001,1.314 -1.066,2.379 -2.38,2.379z" fill="#EFAFC0" id="svg_453"/>
|
||||
<path d="m23.658,51.307c0.63,1.633 2.003,4.149 4.757,4.768l0,-10.471l-4.3,0l-0.457,0.201l0,5.502z" fill="#D98E9D" id="svg_454" opacity="0.5"/>
|
||||
</g>
|
||||
<path d="m30.232,37.823l-1.413,0c-2.809,0 -5.086,2.277 -5.086,5.086l0,4.812c0,2.84 2.302,5.143 5.143,5.143l0.659,0c3.193,0 5.782,-2.589 5.782,-5.782l0,-4.173c0,-2.809 -2.277,-5.086 -5.085,-5.086z" fill="#EFAFC0" id="svg_455"/>
|
||||
<path d="m25.836,46.607c0,1.511 -1.225,2.736 -2.736,2.736s-2.736,-1.225 -2.736,-2.736c0,-1.511 1.225,-2.736 2.736,-2.736s2.736,1.225 2.736,2.736z" fill="#EFAFC0" id="svg_456"/>
|
||||
<path d="m23.798,42.265c0.213,0.985 0.35,1.986 0.41,2.991c0.114,-0.106 0.325,-0.155 0.47,-0.099" fill="none" id="svg_457"/>
|
||||
<path d="m31.935,48.65l-2.962,-0.753c0,0 0.472,1.61 1.418,1.773c0.946,0.163 1.544,-1.02 1.544,-1.02z" fill="#FFFFFF" id="svg_458"/>
|
||||
<circle cx="30.464" cy="44.299" fill="#2D294C" id="svg_459" r="0.552"/>
|
||||
<path d="m34.877,44.299c0,0.305 -0.247,0.552 -0.552,0.552c-0.305,0 -0.552,-0.247 -0.552,-0.552c0,-0.305 0.247,-0.552 0.552,-0.552c0.305,0 0.552,0.247 0.552,0.552z" fill="#2D294C" id="svg_460"/>
|
||||
<path d="m11.452,86.3c-0.717,-5.305 -2.256,-12.005 -2.459,-17.77l0,0l5.055,14.542c1.072,3.084 3.979,5.151 7.244,5.151l3.238,0c2.861,0 5.44,1.725 6.532,4.37l2.081,5.039l-11.007,0c0,-0.001 -9.065,0.647 -10.684,-11.332z" fill="#6F9EDE" id="svg_461"/>
|
||||
<path d="m23.69,89.615c-0.835,-0.239 -1.695,-0.614 -2.165,-1.344c-0.47,-0.73 0.845,-1.456 1.633,-1.82c0.862,0.623 2.047,0.35 3.107,0.443c0.643,0.057 1.263,0.258 1.867,0.485c0.774,0.29 1.542,0.63 2.191,1.142c0.649,0.513 1.176,1.218 1.337,2.029c0.038,0.19 -0.093,0.338 -0.283,0.303c-0.19,-0.035 -0.324,-0.201 -0.449,-0.349c-0.393,-0.463 -0.874,-0.851 -1.41,-1.136c-0.097,-0.052 -0.21,0.105 -0.122,0.17c1.29,0.964 1.446,1.936 1.404,2.013c-0.046,0.083 -0.161,0.093 -0.254,0.073c-0.167,-0.037 -0.391,-0.344 -0.512,-0.464c-0.512,-0.51 -1.03,-0.69 -1.03,-0.69s-0.614,-0.087 0.722,0.964c0.164,0.129 0.329,0.293 0.351,0.5c0.004,0.037 0.003,0.075 -0.012,0.109c-0.017,0.041 -0.051,0.072 -0.088,0.096c-0.114,0.072 -0.262,0.074 -0.391,0.036c-0.129,-0.038 -0.245,-0.111 -0.359,-0.184c-0.558,-0.355 -1.116,-0.71 -1.675,-1.066c0.14,0.133 1.125,0.903 1.214,1.075c0.034,0.067 0.065,0.147 0.034,0.216c-0.044,0.102 -0.183,0.112 -0.294,0.101c-0.743,-0.075 -2.293,-1.017 -2.922,-1.421c-0.628,-0.404 -1.176,-1.076 -1.894,-1.281z" fill="#EFAFC0" id="svg_462"/>
|
||||
<path d="m19.326,56.368c0,0 -7.823,5.529 -9.727,14.837c-1.904,9.307 4.138,16.479 11.694,17.018l2.888,-1.432c0,0 -9.08,-9.595 -8.445,-12.768c0.635,-3.173 5.921,-10.222 5.921,-10.222s-0.521,-8.173 -2.331,-7.433z" fill="#FFCD8B" id="svg_463"/>
|
||||
</g>
|
||||
<path d="m24.036,44.593c0,0 7.272,-1.157 8.422,-4.988c0,0 0.668,3.521 2.86,3.303c0,0 1.247,-7.694 -6.037,-7.37c-6.637,0.295 -7.568,3.739 -7.313,8.592c0,0.001 1.62,-0.817 2.068,0.463z" fill="#563238" id="svg_464"/>
|
||||
<path d="m23.442,47.902c0,0.278 -0.226,0.504 -0.504,0.504c-0.278,0 -0.504,-0.226 -0.504,-0.504c0,-0.278 0.226,-0.504 0.504,-0.504c0.278,0 0.504,0.225 0.504,0.504z" fill="#FFFFFF" id="svg_465"/>
|
||||
<path d="m56.561,44.817l2.687,-2.855c0,0 -0.068,-2.583 0.408,-4.01c0.476,-1.427 0.476,-1.427 0.476,-1.427s0.748,-0.136 0.68,0.952c-0.068,1.088 0.068,2.039 0.068,2.039s3.195,-5.709 4.01,-5.981c0.816,-0.272 -0.748,3.127 -0.952,3.602c-0.204,0.476 2.651,-3.058 2.991,-2.923c0.34,0.136 -0.884,2.515 -2.039,3.806c0,0 2.787,-2.447 3.127,-2.243c0.34,0.204 -1.555,2.214 -1.555,2.214s2.708,-2.37 0.671,0.776c-0.866,1.337 -4.962,4.554 -5.437,4.758c-0.476,0.204 -2.651,3.466 -2.651,3.466l-2.484,-2.174z" fill="#EFAFC0" id="svg_466"/>
|
||||
<path d="m24.036,44.593c0,0 7.272,-1.157 8.422,-4.988c0,0 0.668,3.521 2.86,3.303c0,0 1.247,-7.694 -6.037,-7.37c-6.637,0.295 -7.568,3.739 -7.313,8.592c0,0.001 1.62,-0.817 2.068,0.463z" fill="#563238" id="svg_464"/>
|
||||
<path d="m23.442,47.902c0,0.278 -0.226,0.504 -0.504,0.504c-0.278,0 -0.504,-0.226 -0.504,-0.504c0,-0.278 0.226,-0.504 0.504,-0.504c0.278,0 0.504,0.225 0.504,0.504z" fill="#FFFFFF" id="svg_465"/>
|
||||
<path d="m56.561,44.817l2.687,-2.855c0,0 -0.068,-2.583 0.408,-4.01c0.476,-1.427 0.476,-1.427 0.476,-1.427s0.748,-0.136 0.68,0.952c-0.068,1.088 0.068,2.039 0.068,2.039s3.195,-5.709 4.01,-5.981c0.816,-0.272 -0.748,3.127 -0.952,3.602c-0.204,0.476 2.651,-3.058 2.991,-2.923c0.34,0.136 -0.884,2.515 -2.039,3.806c0,0 2.787,-2.447 3.127,-2.243c0.34,0.204 -1.555,2.214 -1.555,2.214s2.708,-2.37 0.671,0.776c-0.866,1.337 -4.962,4.554 -5.437,4.758c-0.476,0.204 -2.651,3.466 -2.651,3.466l-2.484,-2.174z" fill="#EFAFC0" id="svg_466"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_467"/>
|
||||
<g id="svg_480">
|
||||
<g id="svg_481"/>
|
||||
<g id="svg_500">
|
||||
<g id="svg_502"/>
|
||||
<g id="svg_517"/>
|
||||
<g id="svg_532"/>
|
||||
</g>
|
||||
<g id="svg_545">
|
||||
<g id="svg_553"/>
|
||||
<g id="svg_555"/>
|
||||
</g>
|
||||
<g id="svg_570">
|
||||
<g id="svg_572"/>
|
||||
</g>
|
||||
<g id="svg_591">
|
||||
<g id="svg_592"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_596">
|
||||
<g id="svg_597">
|
||||
<g id="svg_600"/>
|
||||
<g id="svg_612"/>
|
||||
<g id="svg_621">
|
||||
<g id="svg_625"/>
|
||||
<g id="svg_627"/>
|
||||
<g id="svg_629"/>
|
||||
<g id="svg_631"/>
|
||||
</g>
|
||||
<g id="svg_633">
|
||||
<g id="svg_637"/>
|
||||
<g id="svg_639"/>
|
||||
<g id="svg_641"/>
|
||||
<g id="svg_643"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_645">
|
||||
<g id="svg_646">
|
||||
<g id="svg_647"/>
|
||||
<g id="svg_650"/>
|
||||
</g>
|
||||
<g id="svg_656">
|
||||
<g id="svg_657"/>
|
||||
<g id="svg_660"/>
|
||||
</g>
|
||||
<g id="svg_666">
|
||||
<g id="svg_671"/>
|
||||
<g id="svg_677"/>
|
||||
<g id="svg_680"/>
|
||||
<g id="svg_691"/>
|
||||
</g>
|
||||
<g id="svg_700">
|
||||
<g id="svg_704"/>
|
||||
<g id="svg_706"/>
|
||||
<g id="svg_709"/>
|
||||
</g>
|
||||
<g id="svg_730"/>
|
||||
</g>
|
||||
<g id="svg_735">
|
||||
<g id="svg_736"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_740">
|
||||
<g id="svg_742">
|
||||
<g id="svg_743"/>
|
||||
<g id="svg_746"/>
|
||||
<g id="svg_749"/>
|
||||
<g id="svg_752"/>
|
||||
<g id="svg_755"/>
|
||||
</g>
|
||||
<g id="svg_758"/>
|
||||
</g>
|
||||
<g id="svg_761">
|
||||
<g id="svg_762"/>
|
||||
<g id="svg_765"/>
|
||||
</g>
|
||||
<g id="svg_768">
|
||||
<g id="svg_771">
|
||||
<g id="svg_772"/>
|
||||
<g id="svg_774"/>
|
||||
</g>
|
||||
<g id="svg_776">
|
||||
<g id="svg_777"/>
|
||||
<g id="svg_779"/>
|
||||
</g>
|
||||
<g id="svg_781">
|
||||
<g id="svg_782"/>
|
||||
<g id="svg_784"/>
|
||||
</g>
|
||||
<g id="svg_786">
|
||||
<g id="svg_787"/>
|
||||
<g id="svg_789"/>
|
||||
</g>
|
||||
<g id="svg_791">
|
||||
<g id="svg_792"/>
|
||||
<g id="svg_794"/>
|
||||
</g>
|
||||
<g id="svg_796">
|
||||
<g id="svg_797"/>
|
||||
<g id="svg_799"/>
|
||||
</g>
|
||||
<g id="svg_801">
|
||||
<g id="svg_802"/>
|
||||
<g id="svg_804"/>
|
||||
</g>
|
||||
<g id="svg_806">
|
||||
<g id="svg_807"/>
|
||||
<g id="svg_809"/>
|
||||
</g>
|
||||
<g id="svg_811"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="DESIGNED_BY_FREEPIK">
|
||||
<g id="svg_814">
|
||||
<g id="XMLID_319_">
|
||||
<g id="XMLID_338_"/>
|
||||
</g>
|
||||
<g id="svg_815"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="120" height="240" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<g id="svg_107">
|
||||
<g id="svg_108">
|
||||
<path d="m61.3,141.244l-2.689,64.307l-4.824,-0.362c0,0 -5.422,-36.369 -4.115,-54.575l11.628,-9.37z" fill="#E7B598" id="svg_109"/>
|
||||
<g id="svg_110">
|
||||
<path d="m63.882,214.201c0,0 6.033,2.309 6.64,4.92l-13.121,0l-2.064,-4.069l-1.397,4.069l-1.412,0c0,0 -1.324,-10.555 1.259,-13.931l10.095,9.011z" fill="#3F3945" id="svg_111"/>
|
||||
<path d="m58.7,203.276l-0.089,2.275l5.837,9.767c0,0 -6.44,1.208 -8.852,-3.375c-2.412,-4.583 -1.809,-6.754 -1.809,-6.754l-0.33,-2.194l5.243,0.281z" fill="#E7B598" id="svg_112"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_113">
|
||||
<path d="m47.796,141.244l-2.689,64.307l-4.824,-0.362c0,0 -5.422,-36.369 -4.114,-54.575l11.627,-9.37z" fill="#E7B598" id="svg_114"/>
|
||||
<g id="svg_115">
|
||||
<path d="m50.378,214.201c0,0 6.033,2.309 6.64,4.92l-13.121,0l-2.065,-4.069l-1.397,4.069l-1.412,0c0,0 -1.324,-10.555 1.259,-13.931l10.096,9.011z" fill="#3F3945" id="svg_116"/>
|
||||
<path d="m45.196,203.276l-0.089,2.275l5.837,9.767c0,0 -6.44,1.208 -8.852,-3.375c-2.412,-4.583 -1.809,-6.754 -1.809,-6.754l-0.33,-2.194l5.243,0.281z" fill="#E7B598" id="svg_117"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m37.759,96.171c0,0 -6.725,1.185 -10.812,43.664l-1.451,16.827c0,0 5.437,-1.917 14.885,2.343c9.449,4.26 30.029,-4.686 30.029,-4.686s-1.349,-39.087 -10.452,-59.906l-22.199,1.758z" fill="#C4B6A7" id="svg_118"/>
|
||||
</g>
|
||||
<g id="svg_119">
|
||||
<path d="m36.29,33.629c0,0 0.423,6.627 -1.553,10.145c-1.976,3.518 -3.479,4.967 -4.006,8.788c0,0 16.576,4.945 33.177,-0.558l-6.522,-18.376l-21.096,0l0,0.001z" fill="#4F3639" id="svg_120"/>
|
||||
<path d="m57.354,26.041c0,0 -15.531,-4.207 -19.923,3.58c-3.187,5.65 0.578,15.881 7.856,18.764c6.932,2.746 4.965,-19.842 4.965,-19.842l7.102,-2.502z" fill="#4F3639" id="svg_121"/>
|
||||
<path d="m55.874,40.087c-0.157,1.865 1.227,3.504 3.092,3.661c1.865,0.157 3.504,-1.227 3.661,-3.092c0.157,-1.865 -1.227,-3.504 -3.092,-3.661c-1.865,-0.158 -3.504,1.227 -3.661,3.092z" fill="#DEA888" id="svg_122"/>
|
||||
<path d="m54.352,49.556l-0.576,7.363c-0.197,2.586 -1.896,3.847 -4.44,3.633l0,0c-2.172,-0.183 -4.117,-1.476 -4.205,-3.653l1.464,-11.544l7.757,4.201z" fill="#E7B598" id="svg_123"/>
|
||||
<polygon fill="#DEA888" id="svg_124" points="53.94700622558594,54.875 45.44700622558594,47.135986328125 54.425994873046875,48.803009033203125 "/>
|
||||
<path d="m52.092,50.715l0,0c4.354,0.077 8,-3.3 8.182,-6.853l0.379,-8.387c0.228,-4.454 -3.118,-8.286 -7.562,-8.66l0,0c-4.549,-0.383 -9.346,2.927 -9.729,7.476l-0.231,6.749c-0.18,5.218 3.756,9.236 8.961,9.675z" fill="#E7B598" id="svg_125"/>
|
||||
<path d="m48.166,30.423c1.575,3.798 -3.21,9.62 -3.21,9.62l-5.761,-6.925c-0.001,0 7.856,-5.383 8.971,-2.695z" fill="#4F3639" id="svg_126"/>
|
||||
<g id="svg_127">
|
||||
<path d="m39.079,40.019c-0.157,1.865 1.227,3.504 3.092,3.662c1.865,0.157 3.504,-1.227 3.661,-3.092c0.157,-1.865 -1.227,-3.504 -3.092,-3.661c-1.864,-0.159 -3.503,1.226 -3.661,3.091z" fill="#E7B598" id="svg_128"/>
|
||||
<path d="m43.552,41.407c0,0 0.206,-2.448 -2.167,-2.26" fill="none" id="svg_129" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
</g>
|
||||
<g id="svg_130">
|
||||
<polyline fill="none" id="svg_131" points="55.81500244140625,41.614990234375 55.968994140625,43.125 54.83299255371094,43.170989990234375 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
<path d="m50.628,35.968c0,0 1.881,-0.164 1.902,0.627c0,0 0,0.535 -1.641,0.646c-1.641,0.11 -2.068,-0.013 -2.219,-0.516c-0.152,-0.503 1.076,-0.75 1.958,-0.757z" fill="#1F2326" id="svg_132"/>
|
||||
<path d="m58.494,36.176c0,0 -1.553,-0.162 -1.678,0.581c0,0 -0.073,0.503 1.287,0.613c1.36,0.11 1.802,-0.069 1.929,-0.475c0.144,-0.46 -0.8,-0.709 -1.538,-0.719z" fill="#1F2326" id="svg_133"/>
|
||||
<path d="m57.385,39.954c-0.013,0.45 0.33,0.826 0.766,0.84c0.436,0.014 0.8,-0.34 0.813,-0.79c0.013,-0.45 -0.329,-0.826 -0.766,-0.84c-0.436,-0.014 -0.8,0.34 -0.813,0.79z" fill="#1F2326" id="svg_134"/>
|
||||
<path d="m50.465,39.732c-0.013,0.45 0.329,0.826 0.766,0.84c0.436,0.014 0.8,-0.34 0.813,-0.79c0.013,-0.45 -0.329,-0.826 -0.766,-0.84c-0.435,-0.014 -0.799,0.34 -0.813,0.79z" fill="#1F2326" id="svg_135"/>
|
||||
<path d="m56.815,44.735l-5.726,0c0,0 0.905,1.788 3.263,1.779c2.148,-0.007 2.463,-1.779 2.463,-1.779z" fill="#FFFFFF" id="svg_136"/>
|
||||
</g>
|
||||
<path d="m47.205,30.423c0,0 3.424,5.851 14.408,3.798c0,0 2.67,-5.993 -4.259,-8.18c-6.93,-2.188 -14.898,1.508 -14.898,1.508l0,4.927l4.749,-2.053z" fill="#4F3639" id="svg_137"/>
|
||||
</g>
|
||||
<g id="svg_138">
|
||||
<g id="svg_139">
|
||||
<path d="m56.454,58.018c0,0 9.821,2.329 11.553,15.317l-6.3,5.391l-5.253,-20.708z" fill="#707DEF" id="svg_140"/>
|
||||
<g id="svg_141">
|
||||
<path d="m72.006,94.803c-0.177,-0.291 -0.064,2.104 -0.172,1.78c0,0 -2.925,-4.972 -7.25,-15.385c-1.106,-2.664 0.385,-10.814 3.26,-11.041c2.111,-0.167 4.618,3.77 5.121,5.827l5.243,13.186l-6.202,5.633z" fill="#E7B598" id="svg_142"/>
|
||||
<path d="m73.62,90.323l20.437,-11.019l1.882,2.079l-16.512,16.377c-2.398,2.406 -6.473,1.593 -7.765,-1.548l0,0c-0.895,-2.177 -0.063,-4.681 1.958,-5.889z" fill="#E7B598" id="svg_143"/>
|
||||
<path d="m101.709,78.454c1.374,1.815 0.663,4.668 -1.588,6.371c-2.251,1.703 -5.189,1.612 -6.563,-0.203c-1.374,-1.815 -0.663,-4.668 1.588,-6.371c2.251,-1.703 5.189,-1.612 6.563,0.203z" fill="#E7B598" id="svg_144"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_145">
|
||||
<g id="svg_146">
|
||||
<g id="svg_147">
|
||||
<path d="m36.26,97.764c0.829,0.016 25.04,-0.28 25.04,-0.28l-1.802,-35.76l-17.901,3.048l-5.337,32.992z" fill="#FFFFFF" id="svg_148"/>
|
||||
<path d="m41.597,57.649l7.183,-1.259l5.305,0.957l4.102,8.332c0,0 1.227,3.814 -5.311,4.419c-6.538,0.605 -11.078,-3.995 -12.167,-6.84c-1.09,-2.846 0.888,-5.609 0.888,-5.609z" fill="#E7B598" id="svg_149"/>
|
||||
</g>
|
||||
<g id="svg_150">
|
||||
<path d="m54.085,57.347c0,0 5.376,11.324 4.043,23.017c-1.332,11.694 5.31,24.05 5.31,24.05l0,-32.265c0,0 -0.07,-13.466 -9.353,-14.802z" fill="#707DEF" id="svg_151"/>
|
||||
<path d="m62.766,67.107c0,0 2.192,5.383 0.673,14.431" fill="none" id="svg_152" stroke="#4F63D8" stroke-miterlimit="10" stroke-width="0.764"/>
|
||||
</g>
|
||||
<path d="m41.597,57.649c0,0 16.376,20.229 -9.038,48.331l2.684,-46.65l6.354,-1.681z" fill="#707DEF" id="svg_153"/>
|
||||
</g>
|
||||
<g id="svg_154">
|
||||
<g id="svg_155">
|
||||
<g id="svg_156">
|
||||
<path d="m35.286,59.315c0,0 -12.081,2.553 -12.675,26.91l14.457,-1.365l1.98,-19.807l-3.762,-5.738z" fill="#707DEF" id="svg_157"/>
|
||||
<path d="m36.679,90.803c0,0 3.15,-3.086 0.389,-5.944c0,0 -10.297,-1.601 -16.298,2.167c0,0 -2.512,4.286 0.698,6.608c0,0.001 4.326,-2.831 15.211,-2.831z" fill="#8C9BEF" id="svg_158"/>
|
||||
<line fill="none" id="svg_159" stroke="#4F63D8" stroke-miterlimit="10" stroke-width="0.764" x1="37.068" x2="39.46" y1="84.86" y2="70.101"/>
|
||||
</g>
|
||||
<g id="svg_160">
|
||||
<path d="m36.679,90.803l22.472,2.831l-1.249,4.419l-30.375,0.75c-3.043,0.075 -5.654,-2.153 -6.059,-5.169l0,0c0,0.001 6.728,-4.835 15.211,-2.831z" fill="#E7B598" id="svg_161"/>
|
||||
<path d="m68.003,95.927c-0.117,2.805 -3.031,4.961 -6.508,4.816c-3.478,-0.145 -6.202,-2.536 -6.085,-5.341c0.117,-2.805 3.031,-4.961 6.508,-4.816c3.478,0.145 6.202,2.536 6.085,5.341z" fill="#E7B598" id="svg_162"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_163">
|
||||
<path d="m37.285,93.313l23.99,0l0,-2.707l-23.99,0c-0.747,0 -1.353,0.606 -1.353,1.354l0,0c0,0.747 0.605,1.353 1.353,1.353z" fill="#1C2123" id="svg_164"/>
|
||||
<g id="svg_165">
|
||||
<path d="m58.117,93.313l34.442,0c1.831,0 3.438,-1.221 3.928,-2.984l6.199,-22.27c0.548,-1.969 -0.933,-3.918 -2.976,-3.918l-31.85,0c-1.222,0 -2.291,0.824 -2.601,2.006l-7.142,27.166z" fill="#2E353A" id="svg_166"/>
|
||||
<path d="m78.798,79.973c0,1.661 1.347,3.007 3.008,3.007s3.007,-1.346 3.007,-3.007c0,-1.661 -1.346,-3.007 -3.007,-3.007s-3.008,1.346 -3.008,3.007z" fill="#FFFFFF" id="svg_167"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -1,94 +1,24 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="120" height="240" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="OBJECTS">
|
||||
<g id="svg_2">
|
||||
<g id="svg_3">
|
||||
<g id="svg_4">
|
||||
<g id="svg_5">
|
||||
<g id="svg_6"/>
|
||||
</g>
|
||||
<g id="svg_13">
|
||||
<g id="svg_14">
|
||||
<g id="svg_15">
|
||||
<g id="svg_17">
|
||||
<g id="svg_18"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_22">
|
||||
<g id="svg_23">
|
||||
<g id="svg_25">
|
||||
<g id="svg_26"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_29">
|
||||
<g id="svg_31"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_33">
|
||||
<g id="svg_34">
|
||||
<g id="svg_35">
|
||||
<g id="svg_37">
|
||||
<g id="svg_38"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_42"/>
|
||||
<g id="svg_45">
|
||||
<g id="svg_46"/>
|
||||
<g id="svg_49">
|
||||
<g id="svg_50"/>
|
||||
<g id="svg_53"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_57">
|
||||
<g id="svg_59"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_63">
|
||||
<g id="svg_69"/>
|
||||
<g id="svg_72">
|
||||
<g id="svg_78"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_87">
|
||||
<g id="svg_89">
|
||||
<g id="svg_90">
|
||||
<g id="svg_91">
|
||||
<g id="svg_94"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_98">
|
||||
<g id="svg_99"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_105">
|
||||
<g id="svg_106">
|
||||
<g id="svg_107">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="120" height="240" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<g id="svg_107">
|
||||
<g id="svg_108">
|
||||
<path d="m61.3,141.244l-2.689,64.307l-4.824,-0.362c0,0 -5.422,-36.369 -4.115,-54.575l11.628,-9.37z" fill="#EDBC9C" id="svg_109"/>
|
||||
<g id="svg_110">
|
||||
<path d="m63.882,214.201c0,0 6.033,2.309 6.64,4.92l-13.121,0l-2.064,-4.069l-1.397,4.069l-1.412,0c0,0 -1.324,-10.555 1.259,-13.931l10.095,9.011z" fill="#20202A" id="svg_111"/>
|
||||
<path d="m58.7,203.276l-0.089,2.275l5.837,9.767c0,0 -6.44,1.208 -8.852,-3.375c-2.412,-4.583 -1.809,-6.754 -1.809,-6.754l-0.33,-2.194l5.243,0.281z" fill="#EDBC9C" id="svg_112"/>
|
||||
</g>
|
||||
<path d="m61.3,141.244l-2.689,64.307l-4.824,-0.362c0,0 -5.422,-36.369 -4.115,-54.575l11.628,-9.37z" fill="#EDBC9C" id="svg_109"/>
|
||||
<g id="svg_110">
|
||||
<path d="m63.882,214.201c0,0 6.033,2.309 6.64,4.92l-13.121,0l-2.064,-4.069l-1.397,4.069l-1.412,0c0,0 -1.324,-10.555 1.259,-13.931l10.095,9.011z" fill="#20202A" id="svg_111"/>
|
||||
<path d="m58.7,203.276l-0.089,2.275l5.837,9.767c0,0 -6.44,1.208 -8.852,-3.375c-2.412,-4.583 -1.809,-6.754 -1.809,-6.754l-0.33,-2.194l5.243,0.281z" fill="#EDBC9C" id="svg_112"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_113">
|
||||
<path d="m47.796,141.244l-2.689,64.307l-4.824,-0.362c0,0 -5.422,-36.369 -4.114,-54.575l11.627,-9.37z" fill="#EDBC9C" id="svg_114"/>
|
||||
<g id="svg_115">
|
||||
<path d="m50.378,214.201c0,0 6.033,2.309 6.64,4.92l-13.121,0l-2.065,-4.069l-1.397,4.069l-1.412,0c0,0 -1.324,-10.555 1.259,-13.931l10.096,9.011z" fill="#20202A" id="svg_116"/>
|
||||
<path d="m45.196,203.276l-0.089,2.275l5.837,9.767c0,0 -6.44,1.208 -8.852,-3.375c-2.412,-4.583 -1.809,-6.754 -1.809,-6.754l-0.33,-2.194l5.243,0.281z" fill="#EDBC9C" id="svg_117"/>
|
||||
</g>
|
||||
<path d="m47.796,141.244l-2.689,64.307l-4.824,-0.362c0,0 -5.422,-36.369 -4.114,-54.575l11.627,-9.37z" fill="#EDBC9C" id="svg_114"/>
|
||||
<g id="svg_115">
|
||||
<path d="m50.378,214.201c0,0 6.033,2.309 6.64,4.92l-13.121,0l-2.065,-4.069l-1.397,4.069l-1.412,0c0,0 -1.324,-10.555 1.259,-13.931l10.096,9.011z" fill="#20202A" id="svg_116"/>
|
||||
<path d="m45.196,203.276l-0.089,2.275l5.837,9.767c0,0 -6.44,1.208 -8.852,-3.375c-2.412,-4.583 -1.809,-6.754 -1.809,-6.754l-0.33,-2.194l5.243,0.281z" fill="#EDBC9C" id="svg_117"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m37.759,96.171c0,0 -6.725,1.185 -10.812,43.664l-1.451,16.827c0,0 5.437,-1.917 14.885,2.343c9.449,4.26 30.029,-4.686 30.029,-4.686s-1.349,-39.087 -10.452,-59.906l-22.199,1.758z" fill="#C4B6A7" id="svg_118"/>
|
||||
</g>
|
||||
<g id="svg_119">
|
||||
</g>
|
||||
<g id="svg_119">
|
||||
<path d="m36.29,33.629c0,0 0.423,6.627 -1.553,10.145c-1.976,3.518 -3.479,4.967 -4.006,8.788c0,0 16.576,4.945 33.177,-0.558l-6.522,-18.376l-21.096,0l0,0.001z" fill="#412424" id="svg_120"/>
|
||||
<path d="m57.354,26.041c0,0 -15.531,-4.207 -19.923,3.58c-3.187,5.65 0.578,15.881 7.856,18.764c6.932,2.746 4.965,-19.842 4.965,-19.842l7.102,-2.502z" fill="#412424" id="svg_121"/>
|
||||
<path d="m55.874,40.087c-0.157,1.865 1.227,3.504 3.092,3.661c1.865,0.157 3.504,-1.227 3.661,-3.092c0.157,-1.865 -1.227,-3.504 -3.092,-3.661c-1.865,-0.158 -3.504,1.227 -3.661,3.092z" fill="#DEA888" id="svg_122"/>
|
||||
@@ -97,191 +27,61 @@
|
||||
<path d="m52.092,50.715l0,0c4.354,0.077 8,-3.3 8.182,-6.853l0.379,-8.387c0.228,-4.454 -3.118,-8.286 -7.562,-8.66l0,0c-4.549,-0.383 -9.346,2.927 -9.729,7.476l-0.231,6.749c-0.18,5.218 3.756,9.236 8.961,9.675z" fill="#EDBC9C" id="svg_125"/>
|
||||
<path d="m48.166,30.423c1.575,3.798 -3.21,9.62 -3.21,9.62l-5.761,-6.925c-0.001,0 7.856,-5.383 8.971,-2.695z" fill="#412424" id="svg_126"/>
|
||||
<g id="svg_127">
|
||||
<path d="m39.079,40.019c-0.157,1.865 1.227,3.504 3.092,3.662c1.865,0.157 3.504,-1.227 3.661,-3.092c0.157,-1.865 -1.227,-3.504 -3.092,-3.661c-1.864,-0.159 -3.503,1.226 -3.661,3.091z" fill="#EDBC9C" id="svg_128"/>
|
||||
<path d="m43.552,41.407c0,0 0.206,-2.448 -2.167,-2.26" fill="none" id="svg_129" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
<path d="m39.079,40.019c-0.157,1.865 1.227,3.504 3.092,3.662c1.865,0.157 3.504,-1.227 3.661,-3.092c0.157,-1.865 -1.227,-3.504 -3.092,-3.661c-1.864,-0.159 -3.503,1.226 -3.661,3.091z" fill="#EDBC9C" id="svg_128"/>
|
||||
<path d="m43.552,41.407c0,0 0.206,-2.448 -2.167,-2.26" fill="none" id="svg_129" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
</g>
|
||||
<g id="svg_130">
|
||||
<polyline fill="none" id="svg_131" points="55.81500244140625,41.614990234375 55.968994140625,43.125 54.83299255371094,43.170989990234375 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
<path d="m50.628,35.968c0,0 1.881,-0.164 1.902,0.627c0,0 0,0.535 -1.641,0.646c-1.641,0.11 -2.068,-0.013 -2.219,-0.516c-0.152,-0.503 1.076,-0.75 1.958,-0.757z" fill="#1F2326" id="svg_132"/>
|
||||
<path d="m58.494,36.176c0,0 -1.553,-0.162 -1.678,0.581c0,0 -0.073,0.503 1.287,0.613c1.36,0.11 1.802,-0.069 1.929,-0.475c0.144,-0.46 -0.8,-0.709 -1.538,-0.719z" fill="#1F2326" id="svg_133"/>
|
||||
<path d="m57.385,39.954c-0.013,0.45 0.33,0.826 0.766,0.84c0.436,0.014 0.8,-0.34 0.813,-0.79c0.013,-0.45 -0.329,-0.826 -0.766,-0.84c-0.436,-0.014 -0.8,0.34 -0.813,0.79z" fill="#1F2326" id="svg_134"/>
|
||||
<path d="m50.465,39.732c-0.013,0.45 0.329,0.826 0.766,0.84c0.436,0.014 0.8,-0.34 0.813,-0.79c0.013,-0.45 -0.329,-0.826 -0.766,-0.84c-0.435,-0.014 -0.799,0.34 -0.813,0.79z" fill="#1F2326" id="svg_135"/>
|
||||
<path d="m56.815,44.735l-5.726,0c0,0 0.905,1.788 3.263,1.779c2.148,-0.007 2.463,-1.779 2.463,-1.779z" fill="#FFFFFF" id="svg_136"/>
|
||||
<polyline fill="none" id="svg_131" points="55.81500244140625,41.614990234375 55.968994140625,43.125 54.83299255371094,43.170989990234375 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
<path d="m50.628,35.968c0,0 1.881,-0.164 1.902,0.627c0,0 0,0.535 -1.641,0.646c-1.641,0.11 -2.068,-0.013 -2.219,-0.516c-0.152,-0.503 1.076,-0.75 1.958,-0.757z" fill="#1F2326" id="svg_132"/>
|
||||
<path d="m58.494,36.176c0,0 -1.553,-0.162 -1.678,0.581c0,0 -0.073,0.503 1.287,0.613c1.36,0.11 1.802,-0.069 1.929,-0.475c0.144,-0.46 -0.8,-0.709 -1.538,-0.719z" fill="#1F2326" id="svg_133"/>
|
||||
<path d="m57.385,39.954c-0.013,0.45 0.33,0.826 0.766,0.84c0.436,0.014 0.8,-0.34 0.813,-0.79c0.013,-0.45 -0.329,-0.826 -0.766,-0.84c-0.436,-0.014 -0.8,0.34 -0.813,0.79z" fill="#1F2326" id="svg_134"/>
|
||||
<path d="m50.465,39.732c-0.013,0.45 0.329,0.826 0.766,0.84c0.436,0.014 0.8,-0.34 0.813,-0.79c0.013,-0.45 -0.329,-0.826 -0.766,-0.84c-0.435,-0.014 -0.799,0.34 -0.813,0.79z" fill="#1F2326" id="svg_135"/>
|
||||
<path d="m56.815,44.735l-5.726,0c0,0 0.905,1.788 3.263,1.779c2.148,-0.007 2.463,-1.779 2.463,-1.779z" fill="#FFFFFF" id="svg_136"/>
|
||||
</g>
|
||||
<path d="m47.205,30.423c0,0 3.424,5.851 14.408,3.798c0,0 2.67,-5.993 -4.259,-8.18c-6.93,-2.188 -14.898,1.508 -14.898,1.508l0,4.927l4.749,-2.053z" fill="#412424" id="svg_137"/>
|
||||
</g>
|
||||
<g id="svg_138">
|
||||
</g>
|
||||
<g id="svg_138">
|
||||
<g id="svg_139">
|
||||
<path d="m56.454,58.018c0,0 9.821,2.329 11.553,15.317l-6.3,5.391l-5.253,-20.708z" fill="#808DFF" id="svg_140"/>
|
||||
<g id="svg_141">
|
||||
<path d="m72.006,94.803c-0.177,-0.291 -0.064,2.104 -0.172,1.78c0,0 -2.925,-4.972 -7.25,-15.385c-1.106,-2.664 0.385,-10.814 3.26,-11.041c2.111,-0.167 4.618,3.77 5.121,5.827l5.243,13.186l-6.202,5.633z" fill="#EDBC9C" id="svg_142"/>
|
||||
<path d="m73.62,90.323l20.437,-11.019l1.882,2.079l-16.512,16.377c-2.398,2.406 -6.473,1.593 -7.765,-1.548l0,0c-0.895,-2.177 -0.063,-4.681 1.958,-5.889z" fill="#EDBC9C" id="svg_143"/>
|
||||
<path d="m101.709,78.454c1.374,1.815 0.663,4.668 -1.588,6.371c-2.251,1.703 -5.189,1.612 -6.563,-0.203c-1.374,-1.815 -0.663,-4.668 1.588,-6.371c2.251,-1.703 5.189,-1.612 6.563,0.203z" fill="#EDBC9C" id="svg_144"/>
|
||||
</g>
|
||||
<path d="m56.454,58.018c0,0 9.821,2.329 11.553,15.317l-6.3,5.391l-5.253,-20.708z" fill="#808DFF" id="svg_140"/>
|
||||
<g id="svg_141">
|
||||
<path d="m72.006,94.803c-0.177,-0.291 -0.064,2.104 -0.172,1.78c0,0 -2.925,-4.972 -7.25,-15.385c-1.106,-2.664 0.385,-10.814 3.26,-11.041c2.111,-0.167 4.618,3.77 5.121,5.827l5.243,13.186l-6.202,5.633z" fill="#EDBC9C" id="svg_142"/>
|
||||
<path d="m73.62,90.323l20.437,-11.019l1.882,2.079l-16.512,16.377c-2.398,2.406 -6.473,1.593 -7.765,-1.548l0,0c-0.895,-2.177 -0.063,-4.681 1.958,-5.889z" fill="#EDBC9C" id="svg_143"/>
|
||||
<path d="m101.709,78.454c1.374,1.815 0.663,4.668 -1.588,6.371c-2.251,1.703 -5.189,1.612 -6.563,-0.203c-1.374,-1.815 -0.663,-4.668 1.588,-6.371c2.251,-1.703 5.189,-1.612 6.563,0.203z" fill="#EDBC9C" id="svg_144"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_145">
|
||||
<g id="svg_146">
|
||||
<g id="svg_147">
|
||||
<path d="m36.26,97.764c0.829,0.016 25.04,-0.28 25.04,-0.28l-1.802,-35.76l-17.901,3.048l-5.337,32.992z" fill="#FFFFFF" id="svg_148"/>
|
||||
<path d="m41.597,57.649l7.183,-1.259l5.305,0.957l4.102,8.332c0,0 1.227,3.814 -5.311,4.419c-6.538,0.605 -11.078,-3.995 -12.167,-6.84c-1.09,-2.846 0.888,-5.609 0.888,-5.609z" fill="#EDBC9C" id="svg_149"/>
|
||||
<g id="svg_146">
|
||||
<g id="svg_147">
|
||||
<path d="m36.26,97.764c0.829,0.016 25.04,-0.28 25.04,-0.28l-1.802,-35.76l-17.901,3.048l-5.337,32.992z" fill="#FFFFFF" id="svg_148"/>
|
||||
<path d="m41.597,57.649l7.183,-1.259l5.305,0.957l4.102,8.332c0,0 1.227,3.814 -5.311,4.419c-6.538,0.605 -11.078,-3.995 -12.167,-6.84c-1.09,-2.846 0.888,-5.609 0.888,-5.609z" fill="#EDBC9C" id="svg_149"/>
|
||||
</g>
|
||||
<g id="svg_150">
|
||||
<path d="m54.085,57.347c0,0 5.376,11.324 4.043,23.017c-1.332,11.694 5.31,24.05 5.31,24.05l0,-32.265c0,0 -0.07,-13.466 -9.353,-14.802z" fill="#808DFF" id="svg_151"/>
|
||||
<path d="m62.766,67.107c0,0 2.192,5.383 0.673,14.431" fill="none" id="svg_152" stroke="#5F73E8" stroke-miterlimit="10" stroke-width="0.764"/>
|
||||
</g>
|
||||
<path d="m41.597,57.649c0,0 16.376,20.229 -9.038,48.331l2.684,-46.65l6.354,-1.681z" fill="#808DFF" id="svg_153"/>
|
||||
</g>
|
||||
<g id="svg_150">
|
||||
<path d="m54.085,57.347c0,0 5.376,11.324 4.043,23.017c-1.332,11.694 5.31,24.05 5.31,24.05l0,-32.265c0,0 -0.07,-13.466 -9.353,-14.802z" fill="#808DFF" id="svg_151"/>
|
||||
<path d="m62.766,67.107c0,0 2.192,5.383 0.673,14.431" fill="none" id="svg_152" stroke="#5F73E8" stroke-miterlimit="10" stroke-width="0.764"/>
|
||||
<g id="svg_154">
|
||||
<g id="svg_155">
|
||||
<g id="svg_156">
|
||||
<path d="m35.286,59.315c0,0 -12.081,2.553 -12.675,26.91l14.457,-1.365l1.98,-19.807l-3.762,-5.738z" fill="#808DFF" id="svg_157"/>
|
||||
<path d="m36.679,90.803c0,0 3.15,-3.086 0.389,-5.944c0,0 -10.297,-1.601 -16.298,2.167c0,0 -2.512,4.286 0.698,6.608c0,0.001 4.326,-2.831 15.211,-2.831z" fill="#9CABFF" id="svg_158"/>
|
||||
<line fill="none" id="svg_159" stroke="#5F73E8" stroke-miterlimit="10" stroke-width="0.764" x1="37.068" x2="39.46" y1="84.86" y2="70.101"/>
|
||||
</g>
|
||||
<g id="svg_160">
|
||||
<path d="m36.679,90.803l22.472,2.831l-1.249,4.419l-30.375,0.75c-3.043,0.075 -5.654,-2.153 -6.059,-5.169l0,0c0,0.001 6.728,-4.835 15.211,-2.831z" fill="#EDBC9C" id="svg_161"/>
|
||||
<path d="m68.003,95.927c-0.117,2.805 -3.031,4.961 -6.508,4.816c-3.478,-0.145 -6.202,-2.536 -6.085,-5.341c0.117,-2.805 3.031,-4.961 6.508,-4.816c3.478,0.145 6.202,2.536 6.085,5.341z" fill="#EDBC9C" id="svg_162"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_163">
|
||||
<path d="m37.285,93.313l23.99,0l0,-2.707l-23.99,0c-0.747,0 -1.353,0.606 -1.353,1.354l0,0c0,0.747 0.605,1.353 1.353,1.353z" fill="#1C2123" id="svg_164"/>
|
||||
<g id="svg_165">
|
||||
<path d="m58.117,93.313l34.442,0c1.831,0 3.438,-1.221 3.928,-2.984l6.199,-22.27c0.548,-1.969 -0.933,-3.918 -2.976,-3.918l-31.85,0c-1.222,0 -2.291,0.824 -2.601,2.006l-7.142,27.166z" fill="#2E353A" id="svg_166"/>
|
||||
<path d="m78.798,79.973c0,1.661 1.347,3.007 3.008,3.007s3.007,-1.346 3.007,-3.007c0,-1.661 -1.346,-3.007 -3.007,-3.007s-3.008,1.346 -3.008,3.007z" fill="#FFFFFF" id="svg_167"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m41.597,57.649c0,0 16.376,20.229 -9.038,48.331l2.684,-46.65l6.354,-1.681z" fill="#808DFF" id="svg_153"/>
|
||||
</g>
|
||||
<g id="svg_154">
|
||||
<g id="svg_155">
|
||||
<g id="svg_156">
|
||||
<path d="m35.286,59.315c0,0 -12.081,2.553 -12.675,26.91l14.457,-1.365l1.98,-19.807l-3.762,-5.738z" fill="#808DFF" id="svg_157"/>
|
||||
<path d="m36.679,90.803c0,0 3.15,-3.086 0.389,-5.944c0,0 -10.297,-1.601 -16.298,2.167c0,0 -2.512,4.286 0.698,6.608c0,0.001 4.326,-2.831 15.211,-2.831z" fill="#9CABFF" id="svg_158"/>
|
||||
<line fill="none" id="svg_159" stroke="#5F73E8" stroke-miterlimit="10" stroke-width="0.764" x1="37.068" x2="39.46" y1="84.86" y2="70.101"/>
|
||||
</g>
|
||||
<g id="svg_160">
|
||||
<path d="m36.679,90.803l22.472,2.831l-1.249,4.419l-30.375,0.75c-3.043,0.075 -5.654,-2.153 -6.059,-5.169l0,0c0,0.001 6.728,-4.835 15.211,-2.831z" fill="#EDBC9C" id="svg_161"/>
|
||||
<path d="m68.003,95.927c-0.117,2.805 -3.031,4.961 -6.508,4.816c-3.478,-0.145 -6.202,-2.536 -6.085,-5.341c0.117,-2.805 3.031,-4.961 6.508,-4.816c3.478,0.145 6.202,2.536 6.085,5.341z" fill="#EDBC9C" id="svg_162"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_163">
|
||||
<path d="m37.285,93.313l23.99,0l0,-2.707l-23.99,0c-0.747,0 -1.353,0.606 -1.353,1.354l0,0c0,0.747 0.605,1.353 1.353,1.353z" fill="#1C2123" id="svg_164"/>
|
||||
<g id="svg_165">
|
||||
<path d="m58.117,93.313l34.442,0c1.831,0 3.438,-1.221 3.928,-2.984l6.199,-22.27c0.548,-1.969 -0.933,-3.918 -2.976,-3.918l-31.85,0c-1.222,0 -2.291,0.824 -2.601,2.006l-7.142,27.166z" fill="#2E353A" id="svg_166"/>
|
||||
<path d="m78.798,79.973c0,1.661 1.347,3.007 3.008,3.007s3.007,-1.346 3.007,-3.007c0,-1.661 -1.346,-3.007 -3.007,-3.007s-3.008,1.346 -3.008,3.007z" fill="#FFFFFF" id="svg_167"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_168">
|
||||
<g id="svg_170">
|
||||
<g id="svg_171">
|
||||
<g id="svg_172"/>
|
||||
<g id="svg_174"/>
|
||||
</g>
|
||||
<g id="svg_178">
|
||||
<g id="svg_179">
|
||||
<g id="svg_180"/>
|
||||
</g>
|
||||
<g id="svg_183"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_186">
|
||||
<g id="svg_187">
|
||||
<g id="svg_188">
|
||||
<g id="svg_189">
|
||||
<g id="svg_191">
|
||||
<g id="svg_192"/>
|
||||
<g id="svg_195"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_199">
|
||||
<g id="svg_201">
|
||||
<g id="svg_202"/>
|
||||
<g id="svg_205"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_209"/>
|
||||
</g>
|
||||
<g id="svg_212">
|
||||
<g id="svg_213"/>
|
||||
<g id="svg_216"/>
|
||||
<g id="svg_219">
|
||||
<g id="svg_220">
|
||||
<g id="svg_222"/>
|
||||
</g>
|
||||
<g id="svg_225">
|
||||
<g id="svg_226"/>
|
||||
<g id="svg_229"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_232">
|
||||
<g id="svg_233"/>
|
||||
</g>
|
||||
<g id="svg_239">
|
||||
<g id="svg_246"/>
|
||||
<g id="svg_254">
|
||||
<g id="svg_255"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_259">
|
||||
<g id="svg_261">
|
||||
<g id="svg_262">
|
||||
<g id="svg_264"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_268">
|
||||
<g id="svg_269">
|
||||
<g id="svg_270">
|
||||
<g id="svg_271">
|
||||
<g id="svg_275"/>
|
||||
</g>
|
||||
<g id="svg_281">
|
||||
<g id="svg_282">
|
||||
<g id="svg_283">
|
||||
<g id="svg_284">
|
||||
<g id="svg_286"/>
|
||||
</g>
|
||||
<g id="svg_292"/>
|
||||
</g>
|
||||
<g id="svg_295">
|
||||
<g id="svg_296">
|
||||
<g id="svg_298"/>
|
||||
</g>
|
||||
<g id="svg_304"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_307">
|
||||
<g id="svg_308">
|
||||
<g id="svg_309">
|
||||
<g id="svg_310"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_315"/>
|
||||
<g id="svg_318">
|
||||
<g id="svg_319"/>
|
||||
<g id="svg_322">
|
||||
<g id="svg_323"/>
|
||||
<g id="svg_326"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_329">
|
||||
<g id="svg_336"/>
|
||||
<g id="svg_343"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_350">
|
||||
<g id="svg_351">
|
||||
<g id="svg_352"/>
|
||||
<g id="svg_355"/>
|
||||
<g id="svg_358">
|
||||
<g id="svg_360"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_363"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_368">
|
||||
<g id="svg_370"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="DESIGNED_BY_FREEPIK">
|
||||
<g id="svg_373">
|
||||
<g id="XMLID_319_">
|
||||
<g id="XMLID_338_"/>
|
||||
</g>
|
||||
<g id="svg_374"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="240" height="240" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<g id="svg_188">
|
||||
<g id="svg_189">
|
||||
<polygon fill="#E7B598" id="svg_190" points="105.89300537109375,190.30499267578125 107.11898803710938,208.34300231933594 115.42999267578125,207.98500061035156 115.1409912109375,189.718994140625 "/>
|
||||
<g id="svg_191">
|
||||
<g id="svg_192">
|
||||
<path d="m106.501,206.34l-15.149,6.908c-0.992,0.498 -1.514,1.611 -1.261,2.692l0,0l26.72,0l-0.993,-9.711l-9.317,0.111z" id="svg_193"/>
|
||||
<rect fill="#CCCCCC" height="2.874" id="svg_194" width="27.532" x="89.751" y="215.94"/>
|
||||
</g>
|
||||
<g id="svg_195">
|
||||
<line fill="none" id="svg_196" stroke="#CCCCCC" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="101.835" x2="103.478" y1="208.069" y2="209.465"/>
|
||||
<line fill="none" id="svg_197" stroke="#CCCCCC" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="99.631" x2="101.286" y1="209.129" y2="210.537"/>
|
||||
<line fill="none" id="svg_198" stroke="#CCCCCC" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="97.419" x2="99.168" y1="210.239" y2="211.73"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_199">
|
||||
<polygon fill="#E7B598" id="svg_200" points="132.95599365234375,190.30499267578125 134.1820068359375,208.34300231933594 142.49301147460938,207.98500061035156 142.20401000976562,189.718994140625 "/>
|
||||
<g id="svg_201">
|
||||
<g id="svg_202">
|
||||
<path d="m133.563,206.34l-15.149,6.908c-0.992,0.498 -1.514,1.611 -1.261,2.692l0,0l26.72,0l-0.993,-9.711l-9.317,0.111z" id="svg_203"/>
|
||||
<rect fill="#CCCCCC" height="2.874" id="svg_204" width="27.532" x="116.814" y="215.94"/>
|
||||
</g>
|
||||
<g id="svg_205">
|
||||
<line fill="none" id="svg_206" stroke="#CCCCCC" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="128.898" x2="130.54" y1="208.069" y2="209.465"/>
|
||||
<line fill="none" id="svg_207" stroke="#CCCCCC" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="126.694" x2="128.349" y1="209.129" y2="210.537"/>
|
||||
<line fill="none" id="svg_208" stroke="#CCCCCC" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="124.482" x2="126.231" y1="210.239" y2="211.73"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_209">
|
||||
<polygon fill="#A69382" id="svg_210" points="110.69400024414062,105.5979995727539 142.58099365234375,108.02799987792969 145.22799682617188,197.60800170898438 129.34298706054688,197.60800170898438 123.68499755859375,130.04200744628906 116.10400390625,158.91400146484375 118.68499755859375,198.6999969482422 103.80899047851562,198.7429962158203 98.67599487304688,154.75100708007812 "/>
|
||||
<line fill="none" id="svg_211" stroke="#A69382" stroke-miterlimit="10" stroke-width="0.75" x1="116.554" x2="123.685" y1="126.149" y2="130.042"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_212">
|
||||
<g id="svg_213">
|
||||
<polygon fill="#EBAB9D" id="svg_214" points="111.10598754882812,78.26899719238281 111.31298828125,87.06099700927734 106.32199096679688,87.125 106.69000244140625,77.66100311279297 "/>
|
||||
<path d="m110.799,63.347c0,0 -6.674,4.388 -5.975,15.518l6.489,0.73l-0.514,-16.248z" fill="#707DEF" id="svg_215"/>
|
||||
</g>
|
||||
<g id="svg_216">
|
||||
<path d="m126.881,58.394c8.026,0 17.123,2.774 17.123,2.774l-0.346,51.05l-33.9,-2.604l0.359,-44.65c0.007,-0.79 0.442,-1.514 1.136,-1.891l9.375,-4.011c0,0.001 4.615,-0.668 6.253,-0.668z" fill="#707DEF" id="svg_217"/>
|
||||
<line fill="none" id="svg_218" stroke="#566AE1" stroke-miterlimit="10" stroke-width="0.764" x1="109.995" x2="109.995" y1="79.446" y2="68.69"/>
|
||||
</g>
|
||||
<g id="svg_219">
|
||||
<g id="svg_220">
|
||||
<path d="m139.987,78.372l2.181,25.251c0,0 2.408,5.078 6.667,-0.204c4.259,-5.282 0.614,-25.916 0.614,-25.916l-9.462,0.869z" fill="#E7B598" id="svg_221"/>
|
||||
<g id="svg_222">
|
||||
<path d="m137.918,80.866c0,0 -1.819,-18.64 4.081,-19.735c6.117,-1.136 9.67,10.396 10.236,18.659l-14.317,1.076z" fill="#707DEF" id="svg_223"/>
|
||||
<path d="m140.187,80.695l-2.269,0.171c0,0 -0.664,-6.379 -0.265,-10.813" fill="none" id="svg_224" stroke="#566AE1" stroke-miterlimit="10" stroke-width="0.764"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_225">
|
||||
<g id="svg_226">
|
||||
<path d="m125.509,79.979c0,0 -1.228,-4.452 -5.684,-4.886c-4.456,-0.434 -5.767,3.064 -4.928,5.368c0.839,2.304 3.738,6.054 8.13,4.136c4.392,-1.918 2.482,-4.618 2.482,-4.618z" fill="#E7B598" id="svg_227"/>
|
||||
<path d="m118.346,75.099c0,0 -4.376,0.502 -7.633,2.089c-0.331,0.161 -0.512,0.525 -0.464,0.89l0,0c0.065,0.493 0.531,0.831 1.02,0.74l6.039,-1.129l1.038,-2.59z" fill="#E7B598" id="svg_228"/>
|
||||
</g>
|
||||
<g id="svg_229">
|
||||
<polygon fill="#E7B598" id="svg_230" points="143.11099243164062,104.90899658203125 123.0260009765625,84.59700012207031 125.50900268554688,79.97899627685547 145.04400634765625,92.39099884033203 "/>
|
||||
<line fill="none" id="svg_231" stroke="#DEA888" stroke-miterlimit="10" stroke-width="0.764" x1="140.974" x2="145.044" y1="89.805" y2="92.391"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_232">
|
||||
<g id="svg_233">
|
||||
<path d="m98.081,85.09l8.447,25.074c0.29,0.862 1.151,1.399 2.053,1.28l20.53,-2.697c1.196,-0.157 1.947,-1.373 1.551,-2.513l-8.695,-25.042c-0.296,-0.853 -1.151,-1.381 -2.047,-1.264l-20.282,2.664c-1.19,0.157 -1.94,1.361 -1.557,2.498z" fill="#222324" id="svg_234"/>
|
||||
<polygon fill="#222324" id="svg_235" points="98.44100952148438,83.23100280761719 97.36801147460938,84.6240005493164 129.35501098632812,109.32099914550781 130.50201416015625,107.82599639892578 "/>
|
||||
<path d="m97.114,86.335l8.447,25.074c0.29,0.862 1.15,1.399 2.053,1.28l20.53,-2.697c1.196,-0.157 1.947,-1.373 1.551,-2.513l-8.695,-25.04c-0.296,-0.853 -1.151,-1.381 -2.047,-1.263l-20.282,2.664c-1.19,0.155 -1.94,1.359 -1.557,2.495z" fill="#556167" id="svg_236"/>
|
||||
<path d="m109.496,94.445c0.009,1.221 1.005,2.204 2.226,2.195c1.221,-0.009 2.204,-1.005 2.195,-2.226c-0.009,-1.221 -1.005,-2.204 -2.226,-2.195c-1.221,0.008 -2.204,1.005 -2.195,2.226z" fill="#86969C" id="svg_237"/>
|
||||
</g>
|
||||
<path d="m98.071,93.622l1.923,5.188c0.235,0.633 0.876,1.019 1.545,0.931l0,0c1.97,-0.259 3.232,-2.23 2.642,-4.128l-0.541,-1.74c-0.433,-1.394 -1.754,-2.319 -3.212,-2.25l-1.07,0.051c-0.977,0.047 -1.627,1.03 -1.287,1.948z" fill="#E7B598" id="svg_238"/>
|
||||
</g>
|
||||
<g id="svg_239">
|
||||
<path d="m119.195,26.137c0,0 19.114,-5.53 12.146,14.151l-3.095,-6.884l-9.051,-7.267z" fill="#4F3639" id="svg_240"/>
|
||||
<path d="m111.261,33.648c0,0 -4.703,2.124 0.548,8.693l1.677,-9.011l-2.225,0.318z" fill="#4F3639" id="svg_241"/>
|
||||
<path d="m116.028,43.543c0.332,1.727 -0.755,3.394 -2.427,3.723c-1.672,0.329 -3.296,-0.805 -3.628,-2.532c-0.332,-1.727 0.755,-3.394 2.426,-3.723c1.673,-0.33 3.298,0.804 3.629,2.532z" fill="#DEA888" id="svg_242"/>
|
||||
<path d="m119.478,50.804l1.02,8.228c0.352,2.89 2.265,4.218 5.017,3.848l0,0c2.349,-0.315 4.79,-1.776 4.371,-4.31l-1.213,-12.919l-9.195,5.153z" fill="#E7B598" id="svg_243"/>
|
||||
<path d="m120.34,57.913c0,0 9.269,-3.364 8.49,-11.668c-0.232,-2.474 -9.473,3.717 -9.473,3.717l0.983,7.951z" fill="#DEA888" id="svg_244"/>
|
||||
<path d="m122.243,53.961l0,0c-5.105,0.903 -8.527,-3.188 -8.757,-5.275l-2.19,-9.681c-0.788,-4.945 2.364,-9.612 7.123,-10.548l0,0c4.872,-0.958 10.458,2.177 11.425,7.21l0.911,6.449c0.828,5.806 -2.939,10.749 -8.512,11.845z" fill="#E7B598" id="svg_245"/>
|
||||
<g id="svg_246">
|
||||
<polyline fill="none" id="svg_247" points="117.26400756835938,45.770999908447266 117.29000854492188,47.65800094604492 118.43499755859375,47.56100082397461 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5281"/>
|
||||
<path d="m121.502,40.326c0,0 -1.907,0.072 -1.828,0.868c0,0 0.068,0.537 1.727,0.441c1.659,-0.096 2.072,-0.273 2.16,-0.796c0.089,-0.524 -1.173,-0.617 -2.059,-0.513z" fill="#1F2326" id="svg_248"/>
|
||||
<path d="m113.909,41.295c0,0 1.537,-0.358 1.756,0.372c0,0 0.136,0.495 -1.214,0.777c-1.35,0.282 -1.816,0.157 -1.995,-0.234c-0.201,-0.444 0.715,-0.812 1.453,-0.915z" fill="#1F2326" id="svg_249"/>
|
||||
<path d="m115.497,44.944c0.07,0.45 -0.227,0.87 -0.662,0.939c-0.436,0.069 -0.845,-0.24 -0.915,-0.69c-0.07,-0.45 0.226,-0.87 0.662,-0.939c0.435,-0.069 0.845,0.24 0.915,0.69z" fill="#1F2326" id="svg_250"/>
|
||||
<path d="m122.407,43.851c0.07,0.45 -0.226,0.87 -0.662,0.939c-0.435,0.068 -0.845,-0.241 -0.915,-0.69c-0.07,-0.45 0.226,-0.87 0.662,-0.939c0.436,-0.069 0.845,0.24 0.915,0.69z" fill="#1F2326" id="svg_251"/>
|
||||
<path d="m117.256,49.092l4.875,-0.54c0,0 -0.146,1.713 -2.692,1.714c-1.653,0.001 -2.183,-1.174 -2.183,-1.174z" fill="#FFFFFF" id="svg_252"/>
|
||||
</g>
|
||||
<path d="m131.754,33.022l-0.413,7.266l-1.408,1.572l0,0c-1.085,-0.37 -2.49,-0.348 -3.389,-1.572c-0.665,-0.904 -1.326,-2.593 -1.349,-3.715l-0.032,-1.564l-3.829,2.827c-0.6,-1.4 -1.279,-3.264 -1.279,-3.264s-0.913,0.065 -2.157,3.101c0,0 -9.875,2.985 -9.87,-3.036c0.005,-6.355 11.168,-8.498 11.168,-8.498l9.609,3.278l2.949,3.605z" fill="#4F3639" id="svg_253"/>
|
||||
<g id="svg_254">
|
||||
<g id="svg_255">
|
||||
<path d="m135.054,42.129c0.358,1.864 -0.814,3.662 -2.618,4.017c-1.804,0.355 -3.557,-0.868 -3.915,-2.732c-0.358,-1.864 0.814,-3.662 2.618,-4.017c1.805,-0.356 3.557,0.868 3.915,2.732z" fill="#E7B598" id="svg_256"/>
|
||||
<path d="m130.839,43.999c0,0 -0.47,-2.447 1.868,-2.507" fill="none" id="svg_257" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m118.435,60.001l4,-1.86l7.054,0.202l4.137,0.578c0,0 1.542,6.197 -8.92,6.542c-8.365,0.276 -6.271,-5.462 -6.271,-5.462z" fill="#E7B598" id="svg_258"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.9 KiB |
@@ -1,200 +1,79 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="240" height="240" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="BACKGROUND"/>
|
||||
<g id="OBJECTS">
|
||||
<g id="svg_2">
|
||||
<g id="svg_3">
|
||||
<g id="svg_4">
|
||||
<g id="svg_5">
|
||||
<g id="svg_6"/>
|
||||
</g>
|
||||
<g id="svg_13">
|
||||
<g id="svg_14">
|
||||
<g id="svg_15">
|
||||
<g id="svg_17">
|
||||
<g id="svg_18"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_22">
|
||||
<g id="svg_23">
|
||||
<g id="svg_25">
|
||||
<g id="svg_26"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_29">
|
||||
<g id="svg_31"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_33">
|
||||
<g id="svg_34">
|
||||
<g id="svg_35">
|
||||
<g id="svg_37">
|
||||
<g id="svg_38"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_42"/>
|
||||
<g id="svg_45">
|
||||
<g id="svg_46"/>
|
||||
<g id="svg_49">
|
||||
<g id="svg_50"/>
|
||||
<g id="svg_53"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_57">
|
||||
<g id="svg_59"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_63">
|
||||
<g id="svg_69"/>
|
||||
<g id="svg_72">
|
||||
<g id="svg_78"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_87">
|
||||
<g id="svg_89">
|
||||
<g id="svg_90">
|
||||
<g id="svg_91">
|
||||
<g id="svg_94"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_98">
|
||||
<g id="svg_99"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_105">
|
||||
<g id="svg_106">
|
||||
<g id="svg_107">
|
||||
<g id="svg_108">
|
||||
<g id="svg_110"/>
|
||||
</g>
|
||||
<g id="svg_113">
|
||||
<g id="svg_115"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_119">
|
||||
<g id="svg_127"/>
|
||||
<g id="svg_130"/>
|
||||
</g>
|
||||
<g id="svg_138">
|
||||
<g id="svg_139">
|
||||
<g id="svg_141"/>
|
||||
</g>
|
||||
<g id="svg_145">
|
||||
<g id="svg_146">
|
||||
<g id="svg_147"/>
|
||||
<g id="svg_150"/>
|
||||
</g>
|
||||
<g id="svg_154">
|
||||
<g id="svg_155">
|
||||
<g id="svg_156"/>
|
||||
<g id="svg_160"/>
|
||||
</g>
|
||||
<g id="svg_163">
|
||||
<g id="svg_165"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_168">
|
||||
<g id="svg_170">
|
||||
<g id="svg_171">
|
||||
<g id="svg_172"/>
|
||||
<g id="svg_174"/>
|
||||
</g>
|
||||
<g id="svg_178">
|
||||
<g id="svg_179">
|
||||
<g id="svg_180"/>
|
||||
</g>
|
||||
<g id="svg_183"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_186">
|
||||
<g id="svg_187">
|
||||
<g id="svg_188">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="240" height="240" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<g id="svg_188">
|
||||
<g id="svg_189">
|
||||
<polygon fill="#EDBC9C" id="svg_190" points="105.89300537109375,190.30499267578125 107.11898803710938,208.34300231933594 115.42999267578125,207.98500061035156 115.1409912109375,189.718994140625 "/>
|
||||
<g id="svg_191">
|
||||
<g id="svg_192">
|
||||
<path d="m106.501,206.34l-15.149,6.908c-0.992,0.498 -1.514,1.611 -1.261,2.692l0,0l26.72,0l-0.993,-9.711l-9.317,0.111z" id="svg_193"/>
|
||||
<rect fill="#FFFFFF" height="2.874" id="svg_194" width="27.532" x="89.751" y="215.94"/>
|
||||
<polygon fill="#EDBC9C" id="svg_190" points="105.89300537109375,190.30499267578125 107.11898803710938,208.34300231933594 115.42999267578125,207.98500061035156 115.1409912109375,189.718994140625 "/>
|
||||
<g id="svg_191">
|
||||
<g id="svg_192">
|
||||
<path d="m106.501,206.34l-15.149,6.908c-0.992,0.498 -1.514,1.611 -1.261,2.692l0,0l26.72,0l-0.993,-9.711l-9.317,0.111z" id="svg_193"/>
|
||||
<rect fill="#FFFFFF" height="2.874" id="svg_194" width="27.532" x="89.751" y="215.94"/>
|
||||
</g>
|
||||
<g id="svg_195">
|
||||
<line fill="none" id="svg_196" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="101.835" x2="103.478" y1="208.069" y2="209.465"/>
|
||||
<line fill="none" id="svg_197" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="99.631" x2="101.286" y1="209.129" y2="210.537"/>
|
||||
<line fill="none" id="svg_198" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="97.419" x2="99.168" y1="210.239" y2="211.73"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_195">
|
||||
<line fill="none" id="svg_196" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="101.835" x2="103.478" y1="208.069" y2="209.465"/>
|
||||
<line fill="none" id="svg_197" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="99.631" x2="101.286" y1="209.129" y2="210.537"/>
|
||||
<line fill="none" id="svg_198" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="97.419" x2="99.168" y1="210.239" y2="211.73"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_199">
|
||||
<polygon fill="#EDBC9C" id="svg_200" points="132.95599365234375,190.30499267578125 134.1820068359375,208.34300231933594 142.49301147460938,207.98500061035156 142.20401000976562,189.718994140625 "/>
|
||||
<g id="svg_201">
|
||||
<g id="svg_202">
|
||||
<path d="m133.563,206.34l-15.149,6.908c-0.992,0.498 -1.514,1.611 -1.261,2.692l0,0l26.72,0l-0.993,-9.711l-9.317,0.111z" id="svg_203"/>
|
||||
<rect fill="#FFFFFF" height="2.874" id="svg_204" width="27.532" x="116.814" y="215.94"/>
|
||||
<polygon fill="#EDBC9C" id="svg_200" points="132.95599365234375,190.30499267578125 134.1820068359375,208.34300231933594 142.49301147460938,207.98500061035156 142.20401000976562,189.718994140625 "/>
|
||||
<g id="svg_201">
|
||||
<g id="svg_202">
|
||||
<path d="m133.563,206.34l-15.149,6.908c-0.992,0.498 -1.514,1.611 -1.261,2.692l0,0l26.72,0l-0.993,-9.711l-9.317,0.111z" id="svg_203"/>
|
||||
<rect fill="#FFFFFF" height="2.874" id="svg_204" width="27.532" x="116.814" y="215.94"/>
|
||||
</g>
|
||||
<g id="svg_205">
|
||||
<line fill="none" id="svg_206" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="128.898" x2="130.54" y1="208.069" y2="209.465"/>
|
||||
<line fill="none" id="svg_207" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="126.694" x2="128.349" y1="209.129" y2="210.537"/>
|
||||
<line fill="none" id="svg_208" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="124.482" x2="126.231" y1="210.239" y2="211.73"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_205">
|
||||
<line fill="none" id="svg_206" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="128.898" x2="130.54" y1="208.069" y2="209.465"/>
|
||||
<line fill="none" id="svg_207" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="126.694" x2="128.349" y1="209.129" y2="210.537"/>
|
||||
<line fill="none" id="svg_208" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="124.482" x2="126.231" y1="210.239" y2="211.73"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_209">
|
||||
<polygon fill="#C4B6A7" id="svg_210" points="110.69400024414062,105.5979995727539 142.58099365234375,108.02799987792969 145.22799682617188,197.60800170898438 129.34298706054688,197.60800170898438 123.68499755859375,130.04200744628906 116.10400390625,158.91400146484375 118.68499755859375,198.6999969482422 103.80899047851562,198.7429962158203 98.67599487304688,154.75100708007812 "/>
|
||||
<line fill="none" id="svg_211" stroke="#A69382" stroke-miterlimit="10" stroke-width="0.75" x1="116.554" x2="123.685" y1="126.149" y2="130.042"/>
|
||||
<polygon fill="#C4B6A7" id="svg_210" points="110.69400024414062,105.5979995727539 142.58099365234375,108.02799987792969 145.22799682617188,197.60800170898438 129.34298706054688,197.60800170898438 123.68499755859375,130.04200744628906 116.10400390625,158.91400146484375 118.68499755859375,198.6999969482422 103.80899047851562,198.7429962158203 98.67599487304688,154.75100708007812 "/>
|
||||
<line fill="none" id="svg_211" stroke="#A69382" stroke-miterlimit="10" stroke-width="0.75" x1="116.554" x2="123.685" y1="126.149" y2="130.042"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_212">
|
||||
</g>
|
||||
<g id="svg_212">
|
||||
<g id="svg_213">
|
||||
<polygon fill="#EBAB9D" id="svg_214" points="111.10598754882812,78.26899719238281 111.31298828125,87.06099700927734 106.32199096679688,87.125 106.69000244140625,77.66100311279297 "/>
|
||||
<path d="m110.799,63.347c0,0 -6.674,4.388 -5.975,15.518l6.489,0.73l-0.514,-16.248z" fill="#808DFF" id="svg_215"/>
|
||||
<polygon fill="#EBAB9D" id="svg_214" points="111.10598754882812,78.26899719238281 111.31298828125,87.06099700927734 106.32199096679688,87.125 106.69000244140625,77.66100311279297 "/>
|
||||
<path d="m110.799,63.347c0,0 -6.674,4.388 -5.975,15.518l6.489,0.73l-0.514,-16.248z" fill="#808DFF" id="svg_215"/>
|
||||
</g>
|
||||
<g id="svg_216">
|
||||
<path d="m126.881,58.394c8.026,0 17.123,2.774 17.123,2.774l-0.346,51.05l-33.9,-2.604l0.359,-44.65c0.007,-0.79 0.442,-1.514 1.136,-1.891l9.375,-4.011c0,0.001 4.615,-0.668 6.253,-0.668z" fill="#808DFF" id="svg_217"/>
|
||||
<line fill="none" id="svg_218" stroke="#667AF1" stroke-miterlimit="10" stroke-width="0.764" x1="109.995" x2="109.995" y1="79.446" y2="68.69"/>
|
||||
<path d="m126.881,58.394c8.026,0 17.123,2.774 17.123,2.774l-0.346,51.05l-33.9,-2.604l0.359,-44.65c0.007,-0.79 0.442,-1.514 1.136,-1.891l9.375,-4.011c0,0.001 4.615,-0.668 6.253,-0.668z" fill="#808DFF" id="svg_217"/>
|
||||
<line fill="none" id="svg_218" stroke="#667AF1" stroke-miterlimit="10" stroke-width="0.764" x1="109.995" x2="109.995" y1="79.446" y2="68.69"/>
|
||||
</g>
|
||||
<g id="svg_219">
|
||||
<g id="svg_220">
|
||||
<path d="m139.987,78.372l2.181,25.251c0,0 2.408,5.078 6.667,-0.204c4.259,-5.282 0.614,-25.916 0.614,-25.916l-9.462,0.869z" fill="#EDBC9C" id="svg_221"/>
|
||||
<g id="svg_222">
|
||||
<path d="m137.918,80.866c0,0 -1.819,-18.64 4.081,-19.735c6.117,-1.136 9.67,10.396 10.236,18.659l-14.317,1.076z" fill="#808DFF" id="svg_223"/>
|
||||
<path d="m140.187,80.695l-2.269,0.171c0,0 -0.664,-6.379 -0.265,-10.813" fill="none" id="svg_224" stroke="#667AF1" stroke-miterlimit="10" stroke-width="0.764"/>
|
||||
<g id="svg_220">
|
||||
<path d="m139.987,78.372l2.181,25.251c0,0 2.408,5.078 6.667,-0.204c4.259,-5.282 0.614,-25.916 0.614,-25.916l-9.462,0.869z" fill="#EDBC9C" id="svg_221"/>
|
||||
<g id="svg_222">
|
||||
<path d="m137.918,80.866c0,0 -1.819,-18.64 4.081,-19.735c6.117,-1.136 9.67,10.396 10.236,18.659l-14.317,1.076z" fill="#808DFF" id="svg_223"/>
|
||||
<path d="m140.187,80.695l-2.269,0.171c0,0 -0.664,-6.379 -0.265,-10.813" fill="none" id="svg_224" stroke="#667AF1" stroke-miterlimit="10" stroke-width="0.764"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_225">
|
||||
<g id="svg_226">
|
||||
<path d="m125.509,79.979c0,0 -1.228,-4.452 -5.684,-4.886c-4.456,-0.434 -5.767,3.064 -4.928,5.368c0.839,2.304 3.738,6.054 8.13,4.136c4.392,-1.918 2.482,-4.618 2.482,-4.618z" fill="#EDBC9C" id="svg_227"/>
|
||||
<path d="m118.346,75.099c0,0 -4.376,0.502 -7.633,2.089c-0.331,0.161 -0.512,0.525 -0.464,0.89l0,0c0.065,0.493 0.531,0.831 1.02,0.74l6.039,-1.129l1.038,-2.59z" fill="#EDBC9C" id="svg_228"/>
|
||||
<g id="svg_225">
|
||||
<g id="svg_226">
|
||||
<path d="m125.509,79.979c0,0 -1.228,-4.452 -5.684,-4.886c-4.456,-0.434 -5.767,3.064 -4.928,5.368c0.839,2.304 3.738,6.054 8.13,4.136c4.392,-1.918 2.482,-4.618 2.482,-4.618z" fill="#EDBC9C" id="svg_227"/>
|
||||
<path d="m118.346,75.099c0,0 -4.376,0.502 -7.633,2.089c-0.331,0.161 -0.512,0.525 -0.464,0.89l0,0c0.065,0.493 0.531,0.831 1.02,0.74l6.039,-1.129l1.038,-2.59z" fill="#EDBC9C" id="svg_228"/>
|
||||
</g>
|
||||
<g id="svg_229">
|
||||
<polygon fill="#EDBC9C" id="svg_230" points="143.11099243164062,104.90899658203125 123.0260009765625,84.59700012207031 125.50900268554688,79.97899627685547 145.04400634765625,92.39099884033203 "/>
|
||||
<line fill="none" id="svg_231" stroke="#DEA888" stroke-miterlimit="10" stroke-width="0.764" x1="140.974" x2="145.044" y1="89.805" y2="92.391"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_229">
|
||||
<polygon fill="#EDBC9C" id="svg_230" points="143.11099243164062,104.90899658203125 123.0260009765625,84.59700012207031 125.50900268554688,79.97899627685547 145.04400634765625,92.39099884033203 "/>
|
||||
<line fill="none" id="svg_231" stroke="#DEA888" stroke-miterlimit="10" stroke-width="0.764" x1="140.974" x2="145.044" y1="89.805" y2="92.391"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_232">
|
||||
</g>
|
||||
<g id="svg_232">
|
||||
<g id="svg_233">
|
||||
<path d="m98.081,85.09l8.447,25.074c0.29,0.862 1.151,1.399 2.053,1.28l20.53,-2.697c1.196,-0.157 1.947,-1.373 1.551,-2.513l-8.695,-25.042c-0.296,-0.853 -1.151,-1.381 -2.047,-1.264l-20.282,2.664c-1.19,0.157 -1.94,1.361 -1.557,2.498z" fill="#121314" id="svg_234"/>
|
||||
<polygon fill="#121314" id="svg_235" points="98.44100952148438,83.23100280761719 97.36801147460938,84.6240005493164 129.35501098632812,109.32099914550781 130.50201416015625,107.82599639892578 "/>
|
||||
<path d="m97.114,86.335l8.447,25.074c0.29,0.862 1.15,1.399 2.053,1.28l20.53,-2.697c1.196,-0.157 1.947,-1.373 1.551,-2.513l-8.695,-25.04c-0.296,-0.853 -1.151,-1.381 -2.047,-1.263l-20.282,2.664c-1.19,0.155 -1.94,1.359 -1.557,2.495z" fill="#455157" id="svg_236"/>
|
||||
<path d="m109.496,94.445c0.009,1.221 1.005,2.204 2.226,2.195c1.221,-0.009 2.204,-1.005 2.195,-2.226c-0.009,-1.221 -1.005,-2.204 -2.226,-2.195c-1.221,0.008 -2.204,1.005 -2.195,2.226z" fill="#86969C" id="svg_237"/>
|
||||
<path d="m98.081,85.09l8.447,25.074c0.29,0.862 1.151,1.399 2.053,1.28l20.53,-2.697c1.196,-0.157 1.947,-1.373 1.551,-2.513l-8.695,-25.042c-0.296,-0.853 -1.151,-1.381 -2.047,-1.264l-20.282,2.664c-1.19,0.157 -1.94,1.361 -1.557,2.498z" fill="#121314" id="svg_234"/>
|
||||
<polygon fill="#121314" id="svg_235" points="98.44100952148438,83.23100280761719 97.36801147460938,84.6240005493164 129.35501098632812,109.32099914550781 130.50201416015625,107.82599639892578 "/>
|
||||
<path d="m97.114,86.335l8.447,25.074c0.29,0.862 1.15,1.399 2.053,1.28l20.53,-2.697c1.196,-0.157 1.947,-1.373 1.551,-2.513l-8.695,-25.04c-0.296,-0.853 -1.151,-1.381 -2.047,-1.263l-20.282,2.664c-1.19,0.155 -1.94,1.359 -1.557,2.495z" fill="#455157" id="svg_236"/>
|
||||
<path d="m109.496,94.445c0.009,1.221 1.005,2.204 2.226,2.195c1.221,-0.009 2.204,-1.005 2.195,-2.226c-0.009,-1.221 -1.005,-2.204 -2.226,-2.195c-1.221,0.008 -2.204,1.005 -2.195,2.226z" fill="#86969C" id="svg_237"/>
|
||||
</g>
|
||||
<path d="m98.071,93.622l1.923,5.188c0.235,0.633 0.876,1.019 1.545,0.931l0,0c1.97,-0.259 3.232,-2.23 2.642,-4.128l-0.541,-1.74c-0.433,-1.394 -1.754,-2.319 -3.212,-2.25l-1.07,0.051c-0.977,0.047 -1.627,1.03 -1.287,1.948z" fill="#EDBC9C" id="svg_238"/>
|
||||
</g>
|
||||
<g id="svg_239">
|
||||
</g>
|
||||
<g id="svg_239">
|
||||
<path d="m119.195,26.137c0,0 19.114,-5.53 12.146,14.151l-3.095,-6.884l-9.051,-7.267z" fill="#412424" id="svg_240"/>
|
||||
<path d="m111.261,33.648c0,0 -4.703,2.124 0.548,8.693l1.677,-9.011l-2.225,0.318z" fill="#412424" id="svg_241"/>
|
||||
<path d="m116.028,43.543c0.332,1.727 -0.755,3.394 -2.427,3.723c-1.672,0.329 -3.296,-0.805 -3.628,-2.532c-0.332,-1.727 0.755,-3.394 2.426,-3.723c1.673,-0.33 3.298,0.804 3.629,2.532z" fill="#DEA888" id="svg_242"/>
|
||||
@@ -202,96 +81,21 @@
|
||||
<path d="m120.34,57.913c0,0 9.269,-3.364 8.49,-11.668c-0.232,-2.474 -9.473,3.717 -9.473,3.717l0.983,7.951z" fill="#DEA888" id="svg_244"/>
|
||||
<path d="m122.243,53.961l0,0c-5.105,0.903 -8.527,-3.188 -8.757,-5.275l-2.19,-9.681c-0.788,-4.945 2.364,-9.612 7.123,-10.548l0,0c4.872,-0.958 10.458,2.177 11.425,7.21l0.911,6.449c0.828,5.806 -2.939,10.749 -8.512,11.845z" fill="#EDBC9C" id="svg_245"/>
|
||||
<g id="svg_246">
|
||||
<polyline fill="none" id="svg_247" points="117.26400756835938,45.770999908447266 117.29000854492188,47.65800094604492 118.43499755859375,47.56100082397461 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5281"/>
|
||||
<path d="m121.502,40.326c0,0 -1.907,0.072 -1.828,0.868c0,0 0.068,0.537 1.727,0.441c1.659,-0.096 2.072,-0.273 2.16,-0.796c0.089,-0.524 -1.173,-0.617 -2.059,-0.513z" fill="#1F2326" id="svg_248"/>
|
||||
<path d="m113.909,41.295c0,0 1.537,-0.358 1.756,0.372c0,0 0.136,0.495 -1.214,0.777c-1.35,0.282 -1.816,0.157 -1.995,-0.234c-0.201,-0.444 0.715,-0.812 1.453,-0.915z" fill="#1F2326" id="svg_249"/>
|
||||
<path d="m115.497,44.944c0.07,0.45 -0.227,0.87 -0.662,0.939c-0.436,0.069 -0.845,-0.24 -0.915,-0.69c-0.07,-0.45 0.226,-0.87 0.662,-0.939c0.435,-0.069 0.845,0.24 0.915,0.69z" fill="#1F2326" id="svg_250"/>
|
||||
<path d="m122.407,43.851c0.07,0.45 -0.226,0.87 -0.662,0.939c-0.435,0.068 -0.845,-0.241 -0.915,-0.69c-0.07,-0.45 0.226,-0.87 0.662,-0.939c0.436,-0.069 0.845,0.24 0.915,0.69z" fill="#1F2326" id="svg_251"/>
|
||||
<path d="m117.256,49.092l4.875,-0.54c0,0 -0.146,1.713 -2.692,1.714c-1.653,0.001 -2.183,-1.174 -2.183,-1.174z" fill="#FFFFFF" id="svg_252"/>
|
||||
<polyline fill="none" id="svg_247" points="117.26400756835938,45.770999908447266 117.29000854492188,47.65800094604492 118.43499755859375,47.56100082397461 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5281"/>
|
||||
<path d="m121.502,40.326c0,0 -1.907,0.072 -1.828,0.868c0,0 0.068,0.537 1.727,0.441c1.659,-0.096 2.072,-0.273 2.16,-0.796c0.089,-0.524 -1.173,-0.617 -2.059,-0.513z" fill="#1F2326" id="svg_248"/>
|
||||
<path d="m113.909,41.295c0,0 1.537,-0.358 1.756,0.372c0,0 0.136,0.495 -1.214,0.777c-1.35,0.282 -1.816,0.157 -1.995,-0.234c-0.201,-0.444 0.715,-0.812 1.453,-0.915z" fill="#1F2326" id="svg_249"/>
|
||||
<path d="m115.497,44.944c0.07,0.45 -0.227,0.87 -0.662,0.939c-0.436,0.069 -0.845,-0.24 -0.915,-0.69c-0.07,-0.45 0.226,-0.87 0.662,-0.939c0.435,-0.069 0.845,0.24 0.915,0.69z" fill="#1F2326" id="svg_250"/>
|
||||
<path d="m122.407,43.851c0.07,0.45 -0.226,0.87 -0.662,0.939c-0.435,0.068 -0.845,-0.241 -0.915,-0.69c-0.07,-0.45 0.226,-0.87 0.662,-0.939c0.436,-0.069 0.845,0.24 0.915,0.69z" fill="#1F2326" id="svg_251"/>
|
||||
<path d="m117.256,49.092l4.875,-0.54c0,0 -0.146,1.713 -2.692,1.714c-1.653,0.001 -2.183,-1.174 -2.183,-1.174z" fill="#FFFFFF" id="svg_252"/>
|
||||
</g>
|
||||
<path d="m131.754,33.022l-0.413,7.266l-1.408,1.572l0,0c-1.085,-0.37 -2.49,-0.348 -3.389,-1.572c-0.665,-0.904 -1.326,-2.593 -1.349,-3.715l-0.032,-1.564l-3.829,2.827c-0.6,-1.4 -1.279,-3.264 -1.279,-3.264s-0.913,0.065 -2.157,3.101c0,0 -9.875,2.985 -9.87,-3.036c0.005,-6.355 11.168,-8.498 11.168,-8.498l9.609,3.278l2.949,3.605z" fill="#412424" id="svg_253"/>
|
||||
<g id="svg_254">
|
||||
<g id="svg_255">
|
||||
<path d="m135.054,42.129c0.358,1.864 -0.814,3.662 -2.618,4.017c-1.804,0.355 -3.557,-0.868 -3.915,-2.732c-0.358,-1.864 0.814,-3.662 2.618,-4.017c1.805,-0.356 3.557,0.868 3.915,2.732z" fill="#EDBC9C" id="svg_256"/>
|
||||
<path d="m130.839,43.999c0,0 -0.47,-2.447 1.868,-2.507" fill="none" id="svg_257" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
</g>
|
||||
<g id="svg_255">
|
||||
<path d="m135.054,42.129c0.358,1.864 -0.814,3.662 -2.618,4.017c-1.804,0.355 -3.557,-0.868 -3.915,-2.732c-0.358,-1.864 0.814,-3.662 2.618,-4.017c1.805,-0.356 3.557,0.868 3.915,2.732z" fill="#EDBC9C" id="svg_256"/>
|
||||
<path d="m130.839,43.999c0,0 -0.47,-2.447 1.868,-2.507" fill="none" id="svg_257" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m118.435,60.001l4,-1.86l7.054,0.202l4.137,0.578c0,0 1.542,6.197 -8.92,6.542c-8.365,0.276 -6.271,-5.462 -6.271,-5.462z" fill="#EDBC9C" id="svg_258"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_259">
|
||||
<g id="svg_261">
|
||||
<g id="svg_262">
|
||||
<g id="svg_264"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_268">
|
||||
<g id="svg_269">
|
||||
<g id="svg_270">
|
||||
<g id="svg_271">
|
||||
<g id="svg_275"/>
|
||||
</g>
|
||||
<g id="svg_281">
|
||||
<g id="svg_282">
|
||||
<g id="svg_283">
|
||||
<g id="svg_284">
|
||||
<g id="svg_286"/>
|
||||
</g>
|
||||
<g id="svg_292"/>
|
||||
</g>
|
||||
<g id="svg_295">
|
||||
<g id="svg_296">
|
||||
<g id="svg_298"/>
|
||||
</g>
|
||||
<g id="svg_304"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_307">
|
||||
<g id="svg_308">
|
||||
<g id="svg_309">
|
||||
<g id="svg_310"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_315"/>
|
||||
<g id="svg_318">
|
||||
<g id="svg_319"/>
|
||||
<g id="svg_322">
|
||||
<g id="svg_323"/>
|
||||
<g id="svg_326"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_329">
|
||||
<g id="svg_336"/>
|
||||
<g id="svg_343"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_350">
|
||||
<g id="svg_351">
|
||||
<g id="svg_352"/>
|
||||
<g id="svg_355"/>
|
||||
<g id="svg_358">
|
||||
<g id="svg_360"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_363"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_368">
|
||||
<g id="svg_370"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="DESIGNED_BY_FREEPIK">
|
||||
<g id="svg_373">
|
||||
<g id="XMLID_319_">
|
||||
<g id="XMLID_338_"/>
|
||||
</g>
|
||||
<g id="svg_374"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.9 KiB |