diff --git a/.gitignore b/.gitignore index b6855ef7..09e77b25 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ out .idea .vscode -*.swp \ No newline at end of file +*.swp +tmp/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e04619de --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# Stage 1: Builder +FROM node:22-slim AS builder + +WORKDIR /app + +COPY package*.json ./ +RUN npm install + +COPY . . + +RUN npm run build +RUN npx tsc -p tsconfig.server.json --outDir dist +RUN echo '{"type": "commonjs"}' > dist/package.json + +RUN mkdir -p dist/server && cp src/server/worker-bootstrap.cjs dist/server/worker-bootstrap.cjs + +# Stage 2: Runner +FROM node:22-slim + +WORKDIR /app + +COPY package*.json ./ +RUN npm install --production + +COPY --from=builder /app/dist ./dist + +EXPOSE 3000 + +COPY entrypoint.sh . +RUN chmod +x entrypoint.sh + +# Use exec form to ensure proper signal handling +ENTRYPOINT ["./entrypoint.sh"] +CMD [] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000..34987a34 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e + +exec node dist/server/index.js diff --git a/package-lock.json b/package-lock.json index 69eb4389..01a63493 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,24 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", + "express": "^4.19.2", + "multer": "^1.4.5-lts.1", "pdfmake": "^0.2.20", - "xml-js": "^1.6.11" + "piscina": "^5.1.4", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "xml-js": "^1.6.11", + "zod": "^4.1.13" }, "devDependencies": { "@eslint/js": "^9.33.0", "@stylistic/eslint-plugin": "^5.2.3", "@types/pdfmake": "^0.2.11", + "@types/supertest": "^6.0.3", "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "@vitest/coverage-v8": "^3.2.4", @@ -28,6 +39,7 @@ "jiti": "^2.5.1", "jsdom": "^26.1.0", "prettier": "^3.6.2", + "supertest": "^7.1.4", "typescript": "^5.8.3", "typescript-eslint": "^8.40.0", "vite": "^7.0.6", @@ -38,6 +50,50 @@ "@rollup/rollup-linux-x64-gnu": "^4.50.1" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "dev": true, @@ -331,6 +387,340 @@ "node": ">=6.0.0" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "dev": true, @@ -660,6 +1050,13 @@ } } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@stylistic/eslint-plugin": { "version": "5.4.0", "dev": true, @@ -684,6 +1081,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/chai": { "version": "5.2.2", "dev": true, @@ -692,6 +1099,22 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "dev": true, @@ -702,14 +1125,68 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "dev": true, "license": "MIT" }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "24.7.2", - "dev": true, + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "license": "MIT", "dependencies": { "undici-types": "~7.14.0" @@ -732,6 +1209,88 @@ "@types/pdfkit": "*" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "license": "MIT" + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.0", "dev": true, @@ -1555,6 +2114,19 @@ "dev": true, "license": "MIT" }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "dev": true, @@ -1635,11 +2207,31 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", - "dev": true, + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, "node_modules/assertion-error": { "version": "2.0.1", "dev": true, @@ -1648,18 +2240,78 @@ "node": ">=12" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", - "dev": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, "node_modules/base64-js": { "version": "1.3.1", "license": "MIT" }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", - "dev": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1684,6 +2336,32 @@ "base64-js": "^1.1.2" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "dev": true, @@ -1733,6 +2411,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "dev": true, @@ -1787,21 +2471,118 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/compare-versions": { "version": "6.1.1", "dev": true, "license": "MIT" }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", - "dev": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/confbox": { "version": "0.2.2", "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -1942,10 +2723,62 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dfa": { "version": "1.2.0", "license": "MIT" }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "license": "MIT", @@ -1963,11 +2796,26 @@ "dev": true, "license": "MIT" }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "9.2.2", "dev": true, "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "license": "MIT", @@ -1997,6 +2845,22 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.10", "dev": true, @@ -2052,6 +2916,12 @@ "node": ">=18" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "dev": true, @@ -2360,12 +3230,22 @@ }, "node_modules/esutils": { "version": "2.0.3", - "dev": true, + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/expect-type": { "version": "1.2.2", "dev": true, @@ -2374,6 +3254,67 @@ "node": ">=12.0.0" } }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/exsolve": { "version": "1.0.7", "dev": true, @@ -2399,6 +3340,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fastq": { "version": "1.19.1", "dev": true, @@ -2450,6 +3398,39 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "dev": true, @@ -2497,6 +3478,59 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-extra": { "version": "11.3.2", "dev": true, @@ -2510,6 +3544,12 @@ "node": ">=14.14" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2573,7 +3613,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -2745,6 +3787,26 @@ "dev": true, "license": "MIT" }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "dev": true, @@ -2818,6 +3880,32 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "license": "MIT", @@ -2916,6 +4004,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "dev": true, @@ -3086,9 +4180,10 @@ "dev": true, "license": "MIT" }, - "node_modules/js-yaml": { - "version": "4.1.0", - "dev": true, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3247,11 +4342,31 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "dev": true, "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/loupe": { "version": "3.2.1", "dev": true, @@ -3313,6 +4428,24 @@ "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "dev": true, @@ -3321,9 +4454,52 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", - "dev": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3332,6 +4508,15 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "dev": true, @@ -3340,6 +4525,18 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mrmime": { "version": "2.0.1", "dev": true, @@ -3350,7 +4547,8 @@ }, "node_modules/ms": { "version": "2.1.3", - "dev": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/muggle-string": { @@ -3358,6 +4556,25 @@ "dev": true, "license": "MIT" }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "dev": true, @@ -3380,11 +4597,41 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/nwsapi": { "version": "2.2.22", "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-is": { "version": "1.1.6", "license": "MIT", @@ -3406,6 +4653,34 @@ "node": ">= 0.4" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/optionator": { "version": "0.9.4", "dev": true, @@ -3492,6 +4767,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "dev": true, @@ -3505,6 +4789,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "dev": true, @@ -3518,6 +4811,12 @@ "dev": true, "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, "node_modules/pathe": { "version": "2.0.3", "dev": true, @@ -3560,6 +4859,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/piscina": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.4.tgz", + "integrity": "sha512-7uU4ZnKeQq22t9AsmHGD2w4OYQGonwFnTypDypaWi7Qr2EvQIFVtG8J5D/3bE7W123Wdc9+v4CZDu5hJXVCtBg==", + "license": "MIT", + "engines": { + "node": ">=20.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.4" + } + }, "node_modules/pkg-types": { "version": "2.3.0", "dev": true, @@ -3633,6 +4944,25 @@ "node": ">=6.0.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "dev": true, @@ -3641,6 +4971,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/quansync": { "version": "0.2.11", "dev": true, @@ -3675,6 +5020,63 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "license": "MIT", @@ -3789,56 +5191,228 @@ "dev": true, "license": "MIT" }, - "node_modules/run-parallel": { - "version": "1.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "license": "ISC" + }, + "node_modules/saxes": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", + "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "queue-microtask": "^1.2.2" + "ms": "2.0.0" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/sax": { - "version": "1.4.1", - "license": "ISC" - }, - "node_modules/saxes": { - "version": "6.0.0", - "dev": true, - "license": "ISC", + "node_modules/serve-static/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { - "xmlchars": "^2.2.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=v12.22.7" + "node": ">= 0.8" } }, - "node_modules/semver": { - "version": "7.7.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/serve-static/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/set-function-length": { @@ -3869,6 +5443,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "dev": true, @@ -3888,6 +5468,78 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "dev": true, @@ -3943,11 +5595,43 @@ "dev": true, "license": "MIT" }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.9.0", "dev": true, "license": "MIT" }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/string-argv": { "version": "0.3.2", "dev": true, @@ -4028,6 +5712,54 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/superagent": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.4", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^10.2.3" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "dev": true, @@ -4050,6 +5782,92 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-jsdoc/node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.3.tgz", + "integrity": "sha512-giQl7/ToPxCqnUAx2wpnSnDNGZtGzw1LyUw6ZitIpTmdrvpxKFY/94v1hihm0zYNpgp1/VY0jTDk//R0BBgnRQ==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "dev": true, @@ -4168,6 +5986,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/totalist": { "version": "3.0.1", "dev": true, @@ -4236,6 +6063,25 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.3", "dev": true, @@ -4407,7 +6253,8 @@ }, "node_modules/undici-types": { "version": "7.14.0", - "dev": true, + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "license": "MIT" }, "node_modules/unicode-properties": { @@ -4434,10 +6281,53 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/validator": { + "version": "13.15.23", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", + "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { - "version": "7.1.9", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", + "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -5157,6 +7047,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.3", "dev": true, @@ -5210,6 +7106,15 @@ "node": ">=12.0.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "dev": true, @@ -5225,6 +7130,45 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index feb772c0..972c9477 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "type": "tsc -p src/app-public/tsconfig.json --noEmit", "test": "vitest", "test:ui": "vitest --ui", - "test:ci": "vitest run --coverage" + "test:ci": "vitest run --coverage", + "benchmark": "jiti scripts/benchmark-pdf.ts", + "start:server": "jiti src/server/index.ts" }, "main": "./ksef-fe-invoice-converter.umd.cjs", "module": "./ksef-fe-invoice-converter.js", @@ -31,6 +33,7 @@ "@eslint/js": "^9.33.0", "@stylistic/eslint-plugin": "^5.2.3", "@types/pdfmake": "^0.2.11", + "@types/supertest": "^6.0.3", "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "@vitest/coverage-v8": "^3.2.4", @@ -43,6 +46,7 @@ "jiti": "^2.5.1", "jsdom": "^26.1.0", "prettier": "^3.6.2", + "supertest": "^7.1.4", "typescript": "^5.8.3", "typescript-eslint": "^8.40.0", "vite": "^7.0.6", @@ -50,7 +54,17 @@ "vitest": "^3.2.4" }, "dependencies": { + "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", + "express": "^4.19.2", + "multer": "^1.4.5-lts.1", "pdfmake": "^0.2.20", - "xml-js": "^1.6.11" + "piscina": "^5.1.4", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "xml-js": "^1.6.11", + "zod": "^4.1.13" } } diff --git a/readme.md b/readme.md index 0e869aee..cf09071a 100644 --- a/readme.md +++ b/readme.md @@ -9,6 +9,8 @@ Biblioteka do generowania wizualizacji PDF faktur oraz UPO na podstawie plików Biblioteka zawiera następujące funkcjonalności: - Generowanie wizualizacji PDF faktur - Generowanie wizualizacji PDF UPO + - REST API do zdalnego generowania faktur i UPO + - Wsparcie dla konteneryzacji Docker --- @@ -35,7 +37,63 @@ Biblioteka do generowania wizualizacji PDF faktur oraz UPO na podstawie plików Aplikacja uruchomi się domyślnie pod adresem: [http://localhost:5173/](http://localhost:5173/) -## 2.1 Budowanie bibliotki +## 2.1 Uruchomienie REST API + +Biblioteka zawiera serwer REST API do generowania faktur i UPO przez HTTP. + +### Uruchomienie lokalne: + +1. Uruchom serwer: + ```bash + npm run start:server + ``` + +Serwer uruchomi się domyślnie pod adresem: [http://localhost:3000](http://localhost:3000) + +Dokumentacja API (Swagger) dostępna pod: [http://localhost:3000/api-docs](http://localhost:3000/api-docs) + +### Dostępne endpointy: + +#### POST /api/generate-invoice +Generuje PDF faktury z pliku XML i metadanych. + +**Request:** +- `Content-Type: multipart/form-data` +- `file`: Plik XML faktury (FA(1), FA(2) lub FA(3)) +- `metadata`: JSON string z dodatkowymi danymi (AdditionalDataTypes) + +**Response:** +- `200 OK`: PDF faktury (application/pdf) +- `400 Bad Request`: Brak pliku lub metadanych +- `500 Internal Server Error`: Błąd podczas generowania + +#### POST /api/generate-upo +Generuje PDF UPO z pliku XML. + +**Request:** +- `Content-Type: multipart/form-data` +- `file`: Plik XML UPO (schemat UPO v4_2) + +**Response:** +- `200 OK`: PDF UPO (application/pdf) +- `400 Bad Request`: Brak pliku +- `500 Internal Server Error`: Błąd podczas generowania + +### Uruchomienie z Docker: + +1. Zbuduj obraz Docker: + ```bash + docker build -t ksef-pdf-generator . + ``` + +2. Uruchom kontener: + ```bash + docker run -p 3000:3000 ksef-pdf-generator + ``` + +Serwer będzie dostępny pod adresem: [http://localhost:3000](http://localhost:3000) + +## 2.2 Budowanie bibliotki 1. Jak zbudować bibliotekę produkcyjnie: ```bash diff --git a/scripts/benchmark-pdf.ts b/scripts/benchmark-pdf.ts new file mode 100644 index 00000000..c1c6e49d --- /dev/null +++ b/scripts/benchmark-pdf.ts @@ -0,0 +1,149 @@ +import { WorkerPool } from '../src/server/worker-pool'; +import type { PdfTaskData } from '../src/server/pdf-worker'; +import { generateInvoice } from '../src'; +import fs from 'node:fs'; +import path from 'node:path'; +import { performance } from 'node:perf_hooks'; +import type { AdditionalDataTypes } from '../src/lib-public/types/common.types'; + +const ITERATIONS = 10; +const XML_PATH = path.resolve(__dirname, '../assets/invoice.xml'); + +// Mock metadata +const metadata: AdditionalDataTypes = { + nrKSeF: '1234567890', + qrCode: 'benchmark-qr-code', + isMobile: false, +}; + +async function benchmarkNative(xmlContent: string): Promise { + console.log('Starting Native Benchmark (Sequential)...'); + const times: number[] = []; + + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + + await generateInvoice(xmlContent, metadata, 'buffer'); + const end = performance.now(); + + times.push(end - start); + process.stdout.write('.'); + } + console.log('\nNative Benchmark Finished.'); + return times; +} + +async function benchmarkWorkerSequential(xmlContent: string): Promise { + console.log('Starting Worker Benchmark (Sequential)...'); + + const workerPool = new WorkerPool( + path.resolve(__dirname, '../src/server/worker-bootstrap.cjs'), + { maxWorkers: 4 } + ); + + const times: number[] = []; + + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + + await workerPool.runTask({ + type: 'invoice', + xmlContent, + additionalData: metadata, + }); + const end = performance.now(); + + times.push(end - start); + process.stdout.write('.'); + } + + await workerPool.shutdown(); + console.log('\nWorker Benchmark Finished.'); + return times; +} + +async function benchmarkWorkerConcurrent(xmlContent: string): Promise { + console.log(`\nStarting Worker Benchmark (Concurrent - ${ITERATIONS} tasks)...`); + + const workerPool = new WorkerPool( + path.resolve(__dirname, '../src/server/worker-bootstrap.cjs'), + { maxWorkers: 4 } + ); + + const start = performance.now(); + const promises = []; + + for (let i = 0; i < ITERATIONS; i++) { + promises.push( + workerPool.runTask({ + type: 'invoice', + xmlContent, + additionalData: metadata, + }) + ); + } + await Promise.all(promises); + const end = performance.now(); + + await workerPool.shutdown(); + return end - start; +} + +function calculateStats(times: number[]): { avg: number; min: number; max: number; total: number } { + const sum = times.reduce((a, b) => a + b, 0); + const avg = sum / times.length; + const min = Math.min(...times); + const max = Math.max(...times); + + return { avg, min, max, total: sum }; +} + +async function main(): Promise { + try { + if (!fs.existsSync(XML_PATH)) { + throw new Error(`XML file not found at ${XML_PATH}`); + } + const xmlContent = fs.readFileSync(XML_PATH, 'utf-8'); + + console.log(`Loaded XML file (${xmlContent.length} bytes)`); + + const nativeTimes = await benchmarkNative(xmlContent); + const workerTimes = await benchmarkWorkerSequential(xmlContent); + const workerConcurrentTotal = await benchmarkWorkerConcurrent(xmlContent); + + const nativeStats = calculateStats(nativeTimes); + const workerStats = calculateStats(workerTimes); + + console.log('\n--- Results (Sequential) ---'); + console.log(`Iterations: ${ITERATIONS}`); + console.log('\nNative Implementation:'); + console.log(` Average: ${nativeStats.avg.toFixed(2)}ms`); + console.log(` Min: ${nativeStats.min.toFixed(2)}ms`); + console.log(` Max: ${nativeStats.max.toFixed(2)}ms`); + console.log(` Total: ${nativeStats.total.toFixed(2)}ms`); + + console.log('\nWorker Implementation (Sequential):'); + console.log(` Average: ${workerStats.avg.toFixed(2)}ms`); + console.log(` Min: ${workerStats.min.toFixed(2)}ms`); + console.log(` Max: ${workerStats.max.toFixed(2)}ms`); + console.log(` Total: ${workerStats.total.toFixed(2)}ms`); + + console.log('\n--- Concurrent Performance ---'); + console.log(`Worker Concurrent Total Time (${ITERATIONS} tasks): ${workerConcurrentTotal.toFixed(2)}ms`); + console.log(`Effective Average per task: ${(workerConcurrentTotal / ITERATIONS).toFixed(2)}ms`); + + // Comparison + console.log('\n--- comparison ---'); + console.log(`Native Total Time: ${nativeStats.total.toFixed(2)}ms`); + console.log(`Worker Concurrent Total Time: ${workerConcurrentTotal.toFixed(2)}ms`); + + const speedup = nativeStats.total / workerConcurrentTotal; + + console.log(`Concurrent Speedup: ${speedup.toFixed(2)}x`); + } catch (error) { + console.error('Benchmark failed:', error); + process.exit(1); + } +} + +main(); diff --git a/src/app-public/main.ts b/src/app-public/main.ts index 6aba246f..a14e019a 100644 --- a/src/app-public/main.ts +++ b/src/app-public/main.ts @@ -16,6 +16,8 @@ inputInvoice.addEventListener('change', async (): Promise => { nrKSeF: '5555555555-20250808-9231003CA67B-BE', qrCode: 'https://ksef-test.mf.gov.pl/invoice/5265877635/26-10-2025/HS5E1zrA8WVjDNq_xMVIN5SD6nyRymmQ-BcYHReUAa0', + qr2Code: + 'https://ksef-test.mf.gov.pl/invoice/5265877635/26-10-2025/HS5E1zrA8WVjDNq_xMVIN5SD6nyRymmQ-BcYHReUAa0', }; generateInvoice(file, additionalData, 'blob').then((data: Blob): void => { diff --git a/src/index.ts b/src/index.ts index c79b5728..0b89c47a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export { generateInvoice, generatePDFUPO } from './lib-public'; export { generateFA1 } from './lib-public/FA1-generator'; export { generateFA2 } from './lib-public/FA2-generator'; export { generateFA3 } from './lib-public/FA3-generator'; +export { generateFARR } from './lib-public/FARR-generator'; export { generateNaglowekUPO } from './lib-public/generators/UPO4_3/Naglowek'; export { generateDokumentUPO } from './lib-public/generators/UPO4_3/Dokumenty'; export { generateStyle } from './shared/PDF-functions'; diff --git a/src/lib-public/FA1-generator.ts b/src/lib-public/FA1-generator.ts index 6adf12c8..8a8872ee 100644 --- a/src/lib-public/FA1-generator.ts +++ b/src/lib-public/FA1-generator.ts @@ -2,7 +2,7 @@ import pdfMake, { TCreatedPdf } from 'pdfmake/build/pdfmake'; import pdfFonts from 'pdfmake/build/vfs_fonts'; import { Content, TDocumentDefinitions } from 'pdfmake/interfaces'; import { generateStyle, getValue, hasValue } from '../shared/PDF-functions'; -import { TRodzajFaktury } from '../shared/consts/const'; +import { TRodzajFaktury } from '../shared/consts/FA.const'; import { generateAdnotacje } from './generators/FA1/Adnotacje'; import { generateDodatkoweInformacje } from './generators/FA1/DodatkoweInformacje'; import { generatePlatnosc } from './generators/FA1/Platnosc'; @@ -20,6 +20,7 @@ import { generateStopka } from './generators/common/Stopka'; import { Faktura } from './types/fa1.types'; import { ZamowienieKorekta } from './enums/invoice.enums'; import { AdditionalDataTypes } from './types/common.types'; +import { Position } from '../shared/enums/common.enum'; pdfMake.vfs = pdfFonts.vfs; @@ -50,6 +51,13 @@ export function generateFA1(invoice: Faktura, additionalData: AdditionalDataType generateWarunkiTransakcji(invoice.Fa?.WarunkiTransakcji), ...generateStopka(additionalData, invoice.Stopka, invoice.Naglowek, invoice.Fa?.WZ), ], + footer: (currentPage, pageCount) => { + return { + text: currentPage.toString() + ' z ' + pageCount, + alignment: Position.RIGHT, + margin: [0, 0, 40, 0], + }; + }, ...generateStyle(), }; diff --git a/src/lib-public/FA2-generator.ts b/src/lib-public/FA2-generator.ts index 02a04fd2..82b7b546 100644 --- a/src/lib-public/FA2-generator.ts +++ b/src/lib-public/FA2-generator.ts @@ -2,7 +2,7 @@ import pdfMake, { TCreatedPdf } from 'pdfmake/build/pdfmake'; import pdfFonts from 'pdfmake/build/vfs_fonts'; import { Content, TDocumentDefinitions } from 'pdfmake/interfaces'; import { generateStyle, getValue, hasValue } from '../shared/PDF-functions'; -import { TRodzajFaktury } from '../shared/consts/const'; +import { TRodzajFaktury } from '../shared/consts/FA.const'; import { generateAdnotacje } from './generators/FA2/Adnotacje'; import { generateDodatkoweInformacje } from './generators/FA2/DodatkoweInformacje'; import { generatePlatnosc } from './generators/FA2/Platnosc'; @@ -20,6 +20,7 @@ import { generateStopka } from './generators/common/Stopka'; import { Faktura } from './types/fa2.types'; import { ZamowienieKorekta } from './enums/invoice.enums'; import { AdditionalDataTypes } from './types/common.types'; +import { Position } from '../shared/enums/common.enum'; pdfMake.vfs = pdfFonts.vfs; @@ -50,6 +51,13 @@ export function generateFA2(invoice: Faktura, additionalData: AdditionalDataType generateWarunkiTransakcji(invoice.Fa?.WarunkiTransakcji), ...generateStopka(additionalData, invoice.Stopka, invoice.Naglowek, invoice.Fa?.WZ), ], + footer: (currentPage, pageCount) => { + return { + text: currentPage.toString() + ' z ' + pageCount, + alignment: Position.RIGHT, + margin: [0, 0, 40, 0], + }; + }, ...generateStyle(), }; diff --git a/src/lib-public/FA3-generator.ts b/src/lib-public/FA3-generator.ts index b826965b..569a3cae 100644 --- a/src/lib-public/FA3-generator.ts +++ b/src/lib-public/FA3-generator.ts @@ -2,7 +2,7 @@ import pdfMake, { TCreatedPdf } from 'pdfmake/build/pdfmake'; import pdfFonts from 'pdfmake/build/vfs_fonts'; import { Content, TDocumentDefinitions } from 'pdfmake/interfaces'; import { generateStyle, getValue, hasValue } from '../shared/PDF-functions'; -import { TRodzajFaktury } from '../shared/consts/const'; +import { TRodzajFaktury } from '../shared/consts/FA.const'; import { generateAdnotacje } from './generators/FA3/Adnotacje'; import { generateDodatkoweInformacje } from './generators/FA3/DodatkoweInformacje'; import { generatePlatnosc } from './generators/FA3/Platnosc'; @@ -20,6 +20,7 @@ import { generateStopka } from './generators/common/Stopka'; import { Faktura } from './types/fa3.types'; import { ZamowienieKorekta } from './enums/invoice.enums'; import { AdditionalDataTypes } from './types/common.types'; +import { Position } from '../shared/enums/common.enum'; pdfMake.vfs = pdfFonts.vfs; @@ -50,6 +51,13 @@ export function generateFA3(invoice: Faktura, additionalData: AdditionalDataType generateWarunkiTransakcji(invoice.Fa?.WarunkiTransakcji), ...generateStopka(additionalData, invoice.Stopka, invoice.Naglowek, invoice.Fa?.WZ, invoice.Zalacznik), ], + footer: (currentPage, pageCount) => { + return { + text: currentPage.toString() + ' z ' + pageCount, + alignment: Position.RIGHT, + margin: [0, 0, 40, 0], + }; + }, ...generateStyle(), }; diff --git a/src/lib-public/FARR-generator.ts b/src/lib-public/FARR-generator.ts new file mode 100644 index 00000000..1c3162e9 --- /dev/null +++ b/src/lib-public/FARR-generator.ts @@ -0,0 +1,44 @@ +import pdfMake, { TCreatedPdf } from 'pdfmake/build/pdfmake'; +import pdfFonts from 'pdfmake/build/vfs_fonts'; +import { TDocumentDefinitions } from 'pdfmake/interfaces'; +import { generateStyle } from '../shared/PDF-functions'; +import { AdditionalDataTypes } from './types/common.types'; +import { FaRR } from './types/FaRR.types'; +import { generateNaglowek } from './generators/FA_RR/Naglowek'; +import { generatePodmioty } from './generators/FA_RR/Podmioty'; +import { generateDaneFaKorygowanej } from './generators/common/DaneFaKorygowanej'; +import { generateSzczegoly } from './generators/FA_RR/Szczegoly'; +import { generateWiersze } from './generators/FA_RR/Wiersze'; +import { generateDodatkoweInformacje } from './generators/FA_RR/DodatkoweInformacje'; +import { generateRozliczenie } from './generators/common/Rozliczenie'; +import { generatePlatnosc } from './generators/FA_RR/Platnosc'; +import { generateStopka } from './generators/common/Stopka'; +import { Position } from '../shared/enums/common.enum'; + +pdfMake.vfs = pdfFonts.vfs; + +export function generateFARR(invoice: FaRR, additionalData: AdditionalDataTypes): TCreatedPdf { + const docDefinition: TDocumentDefinitions = { + content: [ + ...generateNaglowek(invoice.FakturaRR, additionalData), + generateDaneFaKorygowanej(invoice.FakturaRR), + ...generatePodmioty(invoice), + generateSzczegoly(invoice.FakturaRR!), + generateWiersze(invoice.FakturaRR!), + generateDodatkoweInformacje(invoice.FakturaRR!), + generateRozliczenie(invoice.FakturaRR?.Rozliczenie, invoice.FakturaRR?.KodWaluty?._text ?? ''), + generatePlatnosc(invoice.FakturaRR?.Platnosc), + ...generateStopka(additionalData, invoice.Stopka, invoice.Naglowek), + ], + footer: (currentPage, pageCount) => { + return { + text: currentPage.toString() + ' z ' + pageCount, + alignment: Position.RIGHT, + margin: [0, 0, 40, 0], + }; + }, + ...generateStyle(), + }; + + return pdfMake.createPdf(docDefinition); +} diff --git a/src/lib-public/UPO-4_2-generators.ts b/src/lib-public/UPO-4_2-generators.ts index 64f4fbc5..2bf655cf 100644 --- a/src/lib-public/UPO-4_2-generators.ts +++ b/src/lib-public/UPO-4_2-generators.ts @@ -3,14 +3,14 @@ import { Upo } from './types/upo-v4_2.types'; import { TDocumentDefinitions } from 'pdfmake/interfaces'; import { generateStyle } from '../shared/PDF-functions'; import { generateNaglowekUPO } from './generators/UPO4_2/Naglowek'; -import { generateDokumnetUPO } from './generators/UPO4_2/Dokumenty'; import { parseXML } from '../shared/XML-parser'; import { Position } from '../shared/enums/common.enum'; +import { generateDokumentUPO } from './generators/UPO4_3/Dokumenty'; export async function generatePDFUPO(file: File): Promise { const upo = (await parseXML(file)) as Upo; const docDefinition: TDocumentDefinitions = { - content: [generateNaglowekUPO(upo.Potwierdzenie!), generateDokumnetUPO(upo.Potwierdzenie!)], + content: [generateNaglowekUPO(upo.Potwierdzenie!), generateDokumentUPO(upo.Potwierdzenie!)], ...generateStyle(), pageSize: 'A4', pageOrientation: 'landscape', diff --git a/src/lib-public/generate-invoice.ts b/src/lib-public/generate-invoice.ts index 290ef403..a2f17407 100644 --- a/src/lib-public/generate-invoice.ts +++ b/src/lib-public/generate-invoice.ts @@ -7,6 +7,8 @@ import { Faktura as Faktura3 } from './types/fa3.types'; import { parseXML } from '../shared/XML-parser'; import { TCreatedPdf } from 'pdfmake/build/pdfmake'; import { AdditionalDataTypes } from './types/common.types'; +import { generateFARR } from './FARR-generator'; +import { FaRR } from './types/FaRR.types'; export async function generateInvoice( file: File, @@ -39,7 +41,12 @@ export async function generateInvoice( case 'FA (3)': pdf = generateFA3((xml as any).Faktura as Faktura3, additionalData); break; + case 'FA_RR (1)': + case 'FA_RR(1)': + pdf = generateFARR((xml as any).Faktura as FaRR, additionalData); + break; } + switch (formatType) { case 'blob': pdf.getBlob((blob: Blob): void => { diff --git a/src/lib-public/generators/FA1/Adnotacje.ts b/src/lib-public/generators/FA1/Adnotacje.ts index fc53a08b..5060427d 100644 --- a/src/lib-public/generators/FA1/Adnotacje.ts +++ b/src/lib-public/generators/FA1/Adnotacje.ts @@ -9,7 +9,7 @@ import { } from '../../../shared/PDF-functions'; import { Adnotacje } from '../../types/fa1.types'; import FormatTyp from '../../../shared/enums/common.enum'; -import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/FA.const'; export function generateAdnotacje(adnotacje?: Adnotacje): Content[] { const result: Content[] = []; diff --git a/src/lib-public/generators/FA1/Platnosc.spec.ts b/src/lib-public/generators/FA1/Platnosc.spec.ts index f32470fb..d4ae6e27 100644 --- a/src/lib-public/generators/FA1/Platnosc.spec.ts +++ b/src/lib-public/generators/FA1/Platnosc.spec.ts @@ -15,9 +15,6 @@ vi.mock('../../../shared/PDF-functions', () => ({ getTable: vi.fn((data) => data || []), hasValue: vi.fn((fp: any) => Boolean(fp && fp._text && fp._text !== '')), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getFormaPlatnosciString: vi.fn((fp: any) => (fp?._text ? 'Przelew' : '')), -})); vi.mock('./RachunekBankowy', () => ({ generujRachunekBankowy: vi.fn((table, label) => [{ text: `ACCOUNT:${label}` }]), })); @@ -60,7 +57,7 @@ describe('generatePlatnosc', () => { }); it('adds "Forma płatności" with getFormaPlatnosciString when present', () => { - const p: Platnosc = { FormaPlatnosci: { _text: '1' } }; + const p: Platnosc = { FormaPlatnosci: { _text: '6' } }; const result = generatePlatnosc(p); expect(result).toEqual(expect.arrayContaining([{ text: 'LABEL:Forma płatności: Przelew' }])); diff --git a/src/lib-public/generators/FA1/Platnosc.ts b/src/lib-public/generators/FA1/Platnosc.ts index 857f0492..15508c64 100644 --- a/src/lib-public/generators/FA1/Platnosc.ts +++ b/src/lib-public/generators/FA1/Platnosc.ts @@ -10,10 +10,11 @@ import { } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; import { FP, Platnosc, PlatnosciCzesciowe, TerminyPlatnosci } from '../../types/fa1.types'; -import { getFormaPlatnosciString } from '../../../shared/generators/common/functions'; import { generujRachunekBankowy } from './RachunekBankowy'; import FormatTyp from '../../../shared/enums/common.enum'; import { TableWithFields, TerminPlatnosciContent } from '../../types/fa1-additional-types'; +import { translateMap } from '@shared/generators/common/functions'; +import { FormaPlatnosci } from '@shared/consts/FA.const'; export function generatePlatnosc(platnosc: Platnosc | undefined): Content { if (!platnosc) { @@ -26,7 +27,7 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { { name: 'TerminPlatnosci', title: 'Termin płatności', - format: FormatTyp.Default, + format: FormatTyp.Date, }, ]; @@ -34,7 +35,7 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { zaplataCzesciowaHeader.push({ name: 'TerminPlatnosciOpis', title: 'Opis płatności', - format: FormatTyp.Default, + format: FormatTyp.Date, }); } @@ -42,7 +43,7 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { { name: 'DataZaplatyCzesciowej', title: 'Data zapłaty częściowej', - format: FormatTyp.Default, + format: FormatTyp.Date, }, { name: 'KwotaZaplatyCzesciowej', title: 'Kwota zapłaty częściowej', format: FormatTyp.Currency }, { name: 'FormaPlatnosci', title: 'Forma płatności', format: FormatTyp.FormOfPayment }, @@ -60,7 +61,7 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { } if (hasValue(platnosc.FormaPlatnosci)) { - table.push(createLabelText('Forma płatności: ', getFormaPlatnosciString(platnosc.FormaPlatnosci))); + table.push(createLabelText('Forma płatności: ', translateMap(platnosc.FormaPlatnosci, FormaPlatnosci))); } else { if (platnosc.OpisPlatnosci?._text) { table.push(createLabelText('Forma płatności: ', 'Płatność inna')); @@ -134,4 +135,4 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { table.push(createLabelText('Wysokość skonta: ', platnosc.Skonto.WysokoscSkonta)); } return table; -} +} \ No newline at end of file diff --git a/src/lib-public/generators/FA1/Podmiot1.ts b/src/lib-public/generators/FA1/Podmiot1.ts index 51716f68..1b4ee781 100644 --- a/src/lib-public/generators/FA1/Podmiot1.ts +++ b/src/lib-public/generators/FA1/Podmiot1.ts @@ -12,7 +12,7 @@ import { Podmiot1 } from '../../types/fa1.types'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; -import { TAXPAYER_STATUS } from '../../../shared/consts/const'; +import { TAXPAYER_STATUS } from '../../../shared/consts/FA.const'; export function generatePodmiot1(podmiot1: Podmiot1): Content[] { const result: Content[] = createHeader('Sprzedawca'); diff --git a/src/lib-public/generators/FA1/Podmiot1Podmiot1K.ts b/src/lib-public/generators/FA1/Podmiot1Podmiot1K.ts index 435d5604..fb8b6e2c 100644 --- a/src/lib-public/generators/FA1/Podmiot1Podmiot1K.ts +++ b/src/lib-public/generators/FA1/Podmiot1Podmiot1K.ts @@ -12,7 +12,7 @@ import { Podmiot1, Podmiot1K } from '../../types/fa1.types'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; -import { TAXPAYER_STATUS } from '../../../shared/consts/const'; +import { TAXPAYER_STATUS } from '../../../shared/consts/FA.const'; export function generatePodmiot1Podmiot1K(podmiot1: Podmiot1, podmiot1K: Podmiot1K): Content[] { const result: Content[] = createHeader('Sprzedawca'); diff --git a/src/lib-public/generators/FA1/Podmiot3.spec.ts b/src/lib-public/generators/FA1/Podmiot3.spec.ts index 563e581f..2f829d37 100644 --- a/src/lib-public/generators/FA1/Podmiot3.spec.ts +++ b/src/lib-public/generators/FA1/Podmiot3.spec.ts @@ -15,9 +15,7 @@ vi.mock('../../../shared/PDF-functions', () => ({ getValue: vi.fn((val) => (val && val._text ? val._text : '')), hasValue: vi.fn((val) => !!(val && val._text)), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getRolaString: vi.fn((rola, idx) => (rola && rola._text ? 'SPRZEDAWCA' : '')), -})); + vi.mock('./PodmiotAdres', () => ({ generatePodmiotAdres: vi.fn((adres, label) => ({ adr: label })), })); @@ -32,7 +30,7 @@ describe('generatePodmiot3', () => { beforeEach(() => vi.clearAllMocks()); it('renders minimal column structure', () => { - const podmiot: Podmiot3 = { NrEORI: { _text: '999' }, Rola: { _text: 'X' } }; + const podmiot: Podmiot3 = { NrEORI: { _text: '999' }, Rola: { _text: '1' } }; const result: Content[] = generatePodmiot3(podmiot, 0); const last: any = result[result.length - 1] || {}; @@ -41,7 +39,11 @@ describe('generatePodmiot3', () => { expect.arrayContaining([ { text: 'HEADER:Podmiot inny 1' }, { text: 'LABEL:Numer EORI: 999' }, - [{ text: 'LABEL:Rola: SPRZEDAWCA' }, { text: 'LABEL:Rola inna: ' }, { text: 'LABEL:Udział: ' }], + [ + { text: 'LABEL:Rola: Faktor - w przypadku, gdy na fakturze występują dane faktora' }, + { text: 'LABEL:Rola inna: ' }, + { text: 'LABEL:Udział: ' }, + ], ]) ); }); @@ -50,7 +52,7 @@ describe('generatePodmiot3', () => { const podmiot: Podmiot3 = { NrEORI: { _text: '1000' }, DaneIdentyfikacyjne: { NrID: { _text: 'TAXX' }, BrakID: { _text: '1' } }, - Rola: { _text: 'S' }, + Rola: { _text: '1' }, OpisRoli: { _text: 'operator' }, Udzial: { _text: '50%' }, }; @@ -65,7 +67,7 @@ describe('generatePodmiot3', () => { { text: 'LABEL:Brak identyfikatora ' }, { id: 'ID' }, [ - { text: 'LABEL:Rola: SPRZEDAWCA' }, + { text: 'LABEL:Rola: Faktor - w przypadku, gdy na fakturze występują dane faktora' }, { text: 'LABEL:Rola inna: operator' }, { text: 'LABEL:Udział: 50%' }, ], diff --git a/src/lib-public/generators/FA1/Podmiot3.ts b/src/lib-public/generators/FA1/Podmiot3.ts index 4c3b0927..c55f6ff6 100644 --- a/src/lib-public/generators/FA1/Podmiot3.ts +++ b/src/lib-public/generators/FA1/Podmiot3.ts @@ -11,10 +11,11 @@ import { } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { Podmiot3 } from '../../types/fa1.types'; -import { getRolaString } from '../../../shared/generators/common/functions'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; +import { translateMap } from '@shared/generators/common/functions'; +import { FA1RolaPodmiotu3 } from '../../../shared/consts/FA.const'; export function generatePodmiot3(podmiot: Podmiot3, index: number): Content[] { const result: Content[] = []; @@ -34,7 +35,7 @@ export function generatePodmiot3(podmiot: Podmiot3, index: number): Content[] { column1.push(...generateDaneIdentyfikacyjne(podmiot.DaneIdentyfikacyjne)); } column1.push([ - createLabelText('Rola: ', getRolaString(podmiot.Rola, 1)), + createLabelText('Rola: ', translateMap(podmiot.Rola, FA1RolaPodmiotu3)), createLabelText('Rola inna: ', podmiot.OpisRoli), createLabelText('Udział: ', podmiot.Udzial, [FormatTyp.Percentage]), ]); diff --git a/src/lib-public/generators/FA1/Podmiot3Podmiot2k.spec.ts b/src/lib-public/generators/FA1/Podmiot3Podmiot2k.spec.ts index 41bf2691..a6a864e4 100644 --- a/src/lib-public/generators/FA1/Podmiot3Podmiot2k.spec.ts +++ b/src/lib-public/generators/FA1/Podmiot3Podmiot2k.spec.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './Podmiot3Podmiot2k'; import { Podmiot3Podmiot2KDto } from '../../types/fa1-additional-types'; import { Content } from 'pdfmake/interfaces'; @@ -14,9 +14,6 @@ vi.mock('../../../shared/PDF-functions', () => ({ getValue: vi.fn((val) => (val && val._text ? val._text : '')), hasValue: vi.fn((val) => !!(val && val._text)), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getRolaString: vi.fn((rola) => (rola && rola._text ? 'SPRZEDAWCA' : '')), -})); vi.mock('./PodmiotAdres', () => ({ generatePodmiotAdres: vi.fn((adres, label) => ({ adr: label })), })); @@ -28,8 +25,6 @@ vi.mock('./PodmiotDaneKontaktowe', () => ({ })); describe('generateDaneIdentyfikacyjneTPodmiot3Dto', () => { - beforeEach(() => vi.clearAllMocks()); - it('returns empty array for undefined input', () => { expect(generateDaneIdentyfikacyjneTPodmiot3Dto(undefined, 0)).toEqual([]); }); @@ -38,7 +33,7 @@ describe('generateDaneIdentyfikacyjneTPodmiot3Dto', () => { const podmiot2KDto: Podmiot3Podmiot2KDto = { fakturaPodmiotNDto: { NrEORI: { _text: 'EXX' }, - Rola: { _text: 'SPRZEDAWCA' }, + Rola: { _text: '1' }, OpisRoli: { _text: 'rola inna' }, Udzial: { _text: '51%' }, Email: { _text: 'a@b.pl' }, @@ -59,7 +54,7 @@ describe('generateDaneIdentyfikacyjneTPodmiot3Dto', () => { { text: 'HEADER:Podmiot inny 2' }, { text: 'SUBHEADER:Dane identyfikacyjne' }, { text: 'LABEL:Numer EORI: EXX' }, - { text: 'LABEL:Rola: SPRZEDAWCA' }, + { text: 'LABEL:Rola: Faktor - w przypadku, gdy na fakturze występują dane faktora' }, { text: 'LABEL:Rola inna: rola inna' }, { text: 'LABEL:Udział: 51%' }, { contact: 'KONTAKT' }, diff --git a/src/lib-public/generators/FA1/Podmiot3Podmiot2k.ts b/src/lib-public/generators/FA1/Podmiot3Podmiot2k.ts index b41d9f46..4779b17a 100644 --- a/src/lib-public/generators/FA1/Podmiot3Podmiot2k.ts +++ b/src/lib-public/generators/FA1/Podmiot3Podmiot2k.ts @@ -7,14 +7,15 @@ import { getTable, getValue, hasValue, -} from '../../../shared/PDF-functions'; +} from '@shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { Podmiot3Podmiot2KDto } from '../../types/fa1-additional-types'; -import { getRolaString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; import { Podmiot2K, Podmiot3 } from '../../types/fa1.types'; +import { FA1RolaPodmiotu3 } from '@shared/consts/FA.const'; export function generateDaneIdentyfikacyjneTPodmiot3Dto( podmiot2KDto: Podmiot3Podmiot2KDto | undefined, @@ -36,7 +37,7 @@ export function generateDaneIdentyfikacyjneTPodmiot3Dto( result.push( ...createSubHeader('Dane identyfikacyjne'), createLabelText('Numer EORI: ', podmiot1.NrEORI), - createLabelText('Rola: ', getRolaString(podmiot1.Rola, 1)), + createLabelText('Rola: ', translateMap(podmiot1.Rola, FA1RolaPodmiotu3)), createLabelText('Rola inna: ', podmiot1.OpisRoli), createLabelText('Udział: ', podmiot1.Udzial, FormatTyp.Percentage) ); diff --git a/src/lib-public/generators/FA1/PodmiotUpowazniony.spec.ts b/src/lib-public/generators/FA1/PodmiotUpowazniony.spec.ts index 2fd504e2..8422880d 100644 --- a/src/lib-public/generators/FA1/PodmiotUpowazniony.spec.ts +++ b/src/lib-public/generators/FA1/PodmiotUpowazniony.spec.ts @@ -10,9 +10,6 @@ vi.mock('../../../shared/PDF-functions', () => ({ generateTwoColumns: vi.fn((left, right) => ({ type: '2COL', left, right })), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getRolaUpowaznionegoString: vi.fn(() => 'Rola string'), -})); vi.mock('./PodmiotAdres', () => ({ generatePodmiotAdres: vi.fn((adres, label) => ({ adr: label })), })); @@ -40,6 +37,7 @@ describe(generatePodmiotUpowazniony.name, () => { NrEORI: { _text: 'EORI123' }, }; const res = generatePodmiotUpowazniony(podmiot); + expect(res[0]).toEqual({ text: 'HEADER:Podmiot upoważniony' }); expect(res.some((r: any) => r.type === '2COL')).toBe(true); }); diff --git a/src/lib-public/generators/FA1/PodmiotUpowazniony.ts b/src/lib-public/generators/FA1/PodmiotUpowazniony.ts index 82c38cde..6fc79023 100644 --- a/src/lib-public/generators/FA1/PodmiotUpowazniony.ts +++ b/src/lib-public/generators/FA1/PodmiotUpowazniony.ts @@ -13,7 +13,8 @@ import { PodmiotUpowazniony } from '../../types/fa1.types'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjne } from './PodmiotDaneIdentyfikacyjne'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; -import { getRolaUpowaznionegoString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; +import { TRolaPodmiotuUpowaznionegoFA1 } from '@shared/consts/FA.const'; export function generatePodmiotUpowazniony(podmiot: PodmiotUpowazniony | undefined): Content[] { if (!podmiot) { @@ -24,7 +25,7 @@ export function generatePodmiotUpowazniony(podmiot: PodmiotUpowazniony | undefin const columnRight: Content[] = []; if (hasValue(podmiot.RolaPU)) { - columnLeft.push(createLabelText('Rola: ', getRolaUpowaznionegoString(podmiot.RolaPU, 1))); + columnLeft.push(createLabelText('Rola: ', translateMap(podmiot.RolaPU, TRolaPodmiotuUpowaznionegoFA1))); } if (hasValue(podmiot.NrEORI)) { columnLeft.push(createLabelText('Numer EORI: ', podmiot.NrEORI)); diff --git a/src/lib-public/generators/FA1/Podmioty.spec.ts b/src/lib-public/generators/FA1/Podmioty.spec.ts index d0025365..710594e9 100644 --- a/src/lib-public/generators/FA1/Podmioty.spec.ts +++ b/src/lib-public/generators/FA1/Podmioty.spec.ts @@ -30,9 +30,6 @@ vi.mock('./Podmiot3Podmiot2k', () => ({ vi.mock('./PodmiotUpowazniony', () => ({ generatePodmiotUpowazniony: vi.fn(() => [{ upowazniony: true }]), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getValue: vi.fn((val) => (val && val._text ? val._text : '')), -})); describe(generatePodmioty.name, () => { beforeEach(() => { diff --git a/src/lib-public/generators/FA1/PodsumowanieStawekPodatkuVat.ts b/src/lib-public/generators/FA1/PodsumowanieStawekPodatkuVat.ts index 4fe6811a..9ecfe557 100644 --- a/src/lib-public/generators/FA1/PodsumowanieStawekPodatkuVat.ts +++ b/src/lib-public/generators/FA1/PodsumowanieStawekPodatkuVat.ts @@ -10,7 +10,7 @@ import { import FormatTyp from '../../../shared/enums/common.enum'; import { Fa, Faktura, FP } from '../../types/fa1.types'; import { TaxSummaryTypes } from '../../types/tax-summary.types'; -import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/FA.const'; export function generatePodsumowanieStawekPodatkuVat(faktura: Faktura): Content[] { const AnyP13P14_5Diff0: boolean = diff --git a/src/lib-public/generators/FA1/Rabat.ts b/src/lib-public/generators/FA1/Rabat.ts index e6d0d09c..a246123b 100644 --- a/src/lib-public/generators/FA1/Rabat.ts +++ b/src/lib-public/generators/FA1/Rabat.ts @@ -17,7 +17,7 @@ export function generateRabat(invoice: Fa): Content[] { const faRows: Record[] = getTable(invoice!.FaWiersze?.FaWiersz); const result: Content[] = []; const definedHeader: HeaderDefine[] = [ - { name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default }, + { name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default, width: 'auto' }, { name: 'P_7', title: 'Nazwa towaru lub usługi', format: FormatTyp.Default }, { name: 'P_8B', title: 'Ilość', format: FormatTyp.Default }, { name: 'P_8A', title: 'Miara', format: FormatTyp.Default }, diff --git a/src/lib-public/generators/FA1/RachunekBankowy.spec.ts b/src/lib-public/generators/FA1/RachunekBankowy.spec.ts index d4145be5..554809b2 100644 --- a/src/lib-public/generators/FA1/RachunekBankowy.spec.ts +++ b/src/lib-public/generators/FA1/RachunekBankowy.spec.ts @@ -2,8 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { generujRachunekBankowy } from './RachunekBankowy'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import * as CommonFunctions from '../../../shared/generators/common/functions'; -import { makeBreakable } from '../../../shared/PDF-functions'; +import { makeBreakable } from '@shared/PDF-functions'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn(), @@ -14,15 +13,7 @@ vi.mock('../../../shared/PDF-functions', () => ({ hasValue: vi.fn((val) => Boolean(val && val._text)), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getTypRachunkowWlasnych: vi.fn(), -})); - describe(generujRachunekBankowy.name, () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - const mockAccount: any = { NrRB: { _text: '12345678901234567890123456' }, SWIFT: { _text: 'BPKOPLPW' }, @@ -32,10 +23,10 @@ describe(generujRachunekBankowy.name, () => { } as any; beforeEach(() => { + vi.clearAllMocks(); vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any); vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any); vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted' as any); - vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak'); }); describe('when accounts is undefined or empty', () => { @@ -84,12 +75,9 @@ describe(generujRachunekBankowy.name, () => { }); it('should format "Rachunek własny banku" field', () => { - vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak'); - generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); expect(PDFFunctions.formatText).toHaveBeenCalledWith('Rachunek własny banku', FormatTyp.GrayBoldTitle); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku); expect(PDFFunctions.formatText).toHaveBeenCalledWith(makeBreakable('Tak'), FormatTyp.Default); }); @@ -110,7 +98,7 @@ describe(generujRachunekBankowy.name, () => { const tableContent = sectionCall[0][1]; expect(tableContent).toHaveProperty('table'); - expect(tableContent.table).toHaveProperty('widths', ['*', 'auto']); + expect(tableContent.table).toHaveProperty('widths', ['auto', '*']); }); it('should create table structure with unbreakable property', () => { @@ -197,10 +185,6 @@ describe(generujRachunekBankowy.name, () => { } as any; generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy'); - - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledTimes(2); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(account2.RachunekWlasnyBanku); }); it('should format all fields for all accounts', () => { diff --git a/src/lib-public/generators/FA1/RachunekBankowy.ts b/src/lib-public/generators/FA1/RachunekBankowy.ts index 22c1bdda..8a1469fd 100644 --- a/src/lib-public/generators/FA1/RachunekBankowy.ts +++ b/src/lib-public/generators/FA1/RachunekBankowy.ts @@ -9,8 +9,8 @@ import { } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { FP } from '../../types/fa1.types'; -import { getTypRachunkowWlasnych } from '../../../shared/generators/common/functions'; -import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT, TypRachunkowWlasnych } from '../../../shared/consts/FA.const'; +import { translateMap } from '@shared/generators/common/functions'; export const generujRachunekBankowy: (accounts?: Record[], title?: string) => Content[] = ( accounts?: Record[], @@ -58,7 +58,10 @@ export const generujRachunekBankowy: (accounts?: Record[], title?: s ]); table.push([ formatText('Rachunek własny banku', FormatTyp.GrayBoldTitle), - formatText(makeBreakable(getTypRachunkowWlasnych(account.RachunekWlasnyBanku), 20), FormatTyp.Default), + formatText( + makeBreakable(translateMap(account.RachunekWlasnyBanku, TypRachunkowWlasnych), 20), + FormatTyp.Default + ), ]); table.push([ formatText('Nazwa banku', FormatTyp.GrayBoldTitle), @@ -70,7 +73,7 @@ export const generujRachunekBankowy: (accounts?: Record[], title?: s unbreakable: true, table: { body: table, - widths: ['*', 'auto'], + widths: ['auto', '*'], }, layout: DEFAULT_TABLE_LAYOUT, } as ContentTable, diff --git a/src/lib-public/generators/FA1/Szczegoly.spec.ts b/src/lib-public/generators/FA1/Szczegoly.spec.ts index 314f7d5f..f960605e 100644 --- a/src/lib-public/generators/FA1/Szczegoly.spec.ts +++ b/src/lib-public/generators/FA1/Szczegoly.spec.ts @@ -1,8 +1,8 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { generateSzczegoly } from './Szczegoly'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa } from '../../types/fa1.types'; vi.mock('../../../shared/PDF-functions', () => ({ @@ -86,6 +86,13 @@ describe(generateSzczegoly.name, () => { describe('P_6 scope', () => { it('creates scope array when P_6_Od and P_6_Do exist', () => { + vi.mocked(PDFFunctions.getValue).mockImplementation((item: any) => { + if (typeof item === 'object') { + return item._text; + } + + return item; + }); const data = { ...mockFaVat, OkresFa: { P_6_Od: { _text: '2024-01-01' }, P_6_Do: { _text: '2024-01-31' } }, @@ -99,27 +106,16 @@ describe(generateSzczegoly.name, () => { expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([ { value: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ' }, - { value: data.OkresFa.P_6_Od, formatTyp: FormatTyp.Value }, + { value: '01.01.2024', formatTyp: FormatTyp.Value }, { value: ' do ' }, - { value: data.OkresFa.P_6_Do, formatTyp: FormatTyp.Value }, + { value: '31.01.2024', formatTyp: FormatTyp.Value }, ]); }); }); describe('ceny labels', () => { - it('adds netto label when P_11 exists', () => { - vi.mocked(PDFFunctions.hasColumnsValue).mockImplementation((col) => col === 'P_11'); - generateSzczegoly(mockFaVat); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'netto'); - }); - - it('adds brutto label when P_11 does not exist', () => { - vi.mocked(PDFFunctions.hasColumnsValue).mockReturnValue(false); - generateSzczegoly(mockFaVat); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'brutto'); - }); - it('adds currency label', () => { + vi.mocked(PDFFunctions.hasValue).mockReturnValue(true); generateSzczegoly(mockFaVat); expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Kod waluty: ', mockFaVat.KodWaluty); }); @@ -136,6 +132,7 @@ describe(generateSzczegoly.name, () => { describe('kurs waluty', () => { it('adds currency rate when currency is not PLN and common rate exists', () => { const data = { ...mockFaVat, KodWaluty: { _text: 'EUR' }, Zamowienie: { ZamowienieWiersz: [] } } as any; + vi.mocked(PDFFunctions.hasValue).mockReturnValue(true); vi.mocked(PDFFunctions.getValue).mockReturnValue('EUR'); vi.mocked(PDFFunctions.getDifferentColumnsValue).mockReturnValue([{ value: '4.50' }]); @@ -153,6 +150,7 @@ describe(generateSzczegoly.name, () => { generateSzczegoly(mockFaVat); const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const currencyCall = calls.find((call) => call[0] === 'Kurs waluty: '); + expect(currencyCall).toBeUndefined(); }); }); @@ -167,6 +165,7 @@ describe(generateSzczegoly.name, () => { describe('faktura zaliczkowa', () => { it('generates table when data exists', () => { const data = { ...mockFaVat, NrFaZaliczkowej: [{ _text: 'FA001' }] } as any; + vi.mocked(PDFFunctions.getTable).mockReturnValueOnce([{ _text: 'FA001' }] as any); generateSzczegoly(data); expect(PDFFunctions.getContentTable).toHaveBeenCalled(); diff --git a/src/lib-public/generators/FA1/Szczegoly.ts b/src/lib-public/generators/FA1/Szczegoly.ts index 3f3a6e63..77639dce 100644 --- a/src/lib-public/generators/FA1/Szczegoly.ts +++ b/src/lib-public/generators/FA1/Szczegoly.ts @@ -13,11 +13,12 @@ import { hasValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa, FP } from '../../types/fa1.types'; import { DifferentValues, TypesOfValues } from '../../../shared/types/universal.types'; import FormatTyp from '../../../shared/enums/common.enum'; import { TableWithFields } from '../../types/fa1-additional-types'; +import { formatDateTime } from '@shared/generators/common/functions'; export function generateSzczegoly(faVat: Fa): Content[] { const faWiersze: Record[] = getTable(faVat.FaWiersze?.FaWiersz); @@ -32,15 +33,7 @@ export function generateSzczegoly(faVat: Fa): Content[] { const cenyLabel1: Content[] = []; const cenyLabel2: Content[] = []; - if (!(faWiersze.length > 0 || zamowieniaWiersze.length > 0)) { - const Any_P_11: boolean = - hasColumnsValue('P_11', faWiersze) || hasColumnsValue('P_11', zamowieniaWiersze); - - if (Any_P_11) { - cenyLabel1.push(createLabelText('Faktura wystawiona w cenach: ', 'netto')); - } else { - cenyLabel1.push(createLabelText('Faktura wystawiona w cenach: ', 'brutto')); - } + if (hasValue(faVat.KodWaluty)) { cenyLabel2.push(createLabelText('Kod waluty: ', faVat.KodWaluty)); } @@ -127,18 +120,24 @@ function generateP_6Scope(P_6_Od: TypesOfValues, P_6_Do: TypesOfValues): Content { value: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', }, - { value: P_6_Od, formatTyp: FormatTyp.Value }, + { value: formatDateTime(getValue(P_6_Od) as string, true, true), formatTyp: FormatTyp.Value }, { value: ' do ' }, - { value: P_6_Do, formatTyp: FormatTyp.Value }, + { value: formatDateTime(getValue(P_6_Do) as string, true, true), formatTyp: FormatTyp.Value }, ]) ); } else if (hasValue(P_6_Od)) { table.push( - createLabelText('Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', P_6_Od) + createLabelText( + 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', + formatDateTime(getValue(P_6_Od) as string, true, true) + ) ); } else if (hasValue(P_6_Do)) { table.push( - createLabelText('Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', P_6_Do) + createLabelText( + 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', + formatDateTime(getValue(P_6_Do) as string, true, true) + ) ); } return table; diff --git a/src/lib-public/generators/FA1/Transport.spec.ts b/src/lib-public/generators/FA1/Transport.spec.ts index 1c8e69b1..8d83f80f 100644 --- a/src/lib-public/generators/FA1/Transport.spec.ts +++ b/src/lib-public/generators/FA1/Transport.spec.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { generateTransport } from './Transport'; import * as PDFFunctions from '../../../shared/PDF-functions'; import * as PrzewoznikModule from './Przewoznik'; -import * as CommonFunctions from '../../../shared/generators/common/functions'; +import * as CommonFunctions from '@shared/generators/common/functions'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn(), @@ -18,12 +18,6 @@ vi.mock('./Przewoznik', () => ({ generatePrzewoznik: vi.fn(), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getDateTimeWithoutSeconds: vi.fn(), - getRodzajTransportuString: vi.fn(), - getOpisTransportuString: vi.fn(), -})); - describe(generateTransport.name, () => { let mockTransport: any; @@ -34,7 +28,7 @@ describe(generateTransport.name, () => { TransportInny: { _text: '0' }, OpisInnegoTransportu: { _text: '' }, NrZleceniaTransportu: { _text: 'TR001' }, - OpisLadunku: { _text: 'Goods' }, + OpisLadunku: { _text: '1' }, LadunekInny: { _text: '0' }, OpisInnegoLadunku: { _text: '' }, JednostkaOpakowania: { _text: 'Box' }, @@ -65,20 +59,18 @@ describe(generateTransport.name, () => { vi.mocked(PDFFunctions.hasValue).mockReturnValue(true); vi.mocked(PrzewoznikModule.generatePrzewoznik).mockReturnValue('przewoznik' as any); vi.mocked(CommonFunctions.getDateTimeWithoutSeconds).mockReturnValue('2024-01-01 10:00'); - vi.mocked(CommonFunctions.getRodzajTransportuString).mockReturnValue('Road'); - vi.mocked(CommonFunctions.getOpisTransportuString).mockReturnValue('Opis'); }); it('should return a section', () => { const result = generateTransport(mockTransport); + expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), true); expect(result).toBe('section'); }); it('should handle RodzajTransportu', () => { generateTransport(mockTransport); - expect(CommonFunctions.getRodzajTransportuString).toHaveBeenCalledWith(mockTransport.RodzajTransportu); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Road'); + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Transport morski'); }); it('should handle TransportInny', () => { @@ -88,6 +80,7 @@ describe(generateTransport.name, () => { TransportInny: { _text: '1' }, OpisInnegoTransportu: { _text: 'Custom transport' }, }; + generateTransport(data); expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Transport inny'); expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Transport inny'); @@ -99,7 +92,7 @@ describe(generateTransport.name, () => { 'Numer zlecenia transportu: ', mockTransport.NrZleceniaTransportu ); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Opis ładunku: ', 'Opis'); + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Opis ładunku: ', 'Bańka'); expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( 'Jednostka opakowania: ', mockTransport.JednostkaOpakowania @@ -141,6 +134,7 @@ describe(generateTransport.name, () => { }, ], }; + vi.mocked(PDFFunctions.getTable).mockReturnValue(data.WysylkaPrzez as any); generateTransport(data); expect(PDFFunctions.createSubHeader).toHaveBeenCalledWith('Adres pośredni wysyłki', [0, 4, 0, 0]); @@ -163,8 +157,9 @@ describe(generateTransport.name, () => { LadunekInny: { _text: '1' }, OpisInnegoLadunku: { _text: 'Custom cargo' }, }; + generateTransport(data); expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Opis ładunku: ', 'Ładunek inny'); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Road'); + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Transport morski'); }); }); diff --git a/src/lib-public/generators/FA1/Transport.ts b/src/lib-public/generators/FA1/Transport.ts index 14721ef9..7547b122 100644 --- a/src/lib-public/generators/FA1/Transport.ts +++ b/src/lib-public/generators/FA1/Transport.ts @@ -9,13 +9,10 @@ import { hasValue, } from '../../../shared/PDF-functions'; import { Transport } from '../../types/fa1.types'; -import { - getDateTimeWithoutSeconds, - getOpisTransportuString, - getRodzajTransportuString, -} from '../../../shared/generators/common/functions'; +import { getDateTimeWithoutSeconds, translateMap } from '@shared/generators/common/functions'; import { generateAdres } from './Adres'; import { generatePrzewoznik } from './Przewoznik'; +import { RodzajTransportu, TypLadunku } from '@shared/consts/FA.const'; export function generateTransport(transport: Transport, index?: number | null): Content { const table: Content[] = []; @@ -30,7 +27,7 @@ export function generateTransport(transport: Transport, index?: number | null): table.push(createHeader(index ? `Transport ${index}` : 'Transport')); if (transport.RodzajTransportu?._text) { columns.transport.push( - createLabelText('Rodzaj transportu: ', getRodzajTransportuString(transport.RodzajTransportu)) + createLabelText('Rodzaj transportu: ', translateMap(transport.RodzajTransportu, RodzajTransportu)) ); } else if (transport.TransportInny?._text == '1' && transport.OpisInnegoTransportu?._text) { columns.transport.push(createLabelText('Rodzaj transportu: ', 'Transport inny')); @@ -40,7 +37,7 @@ export function generateTransport(transport: Transport, index?: number | null): } columns.dane.push(createLabelText('Numer zlecenia transportu: ', transport.NrZleceniaTransportu)); if (hasValue(transport.OpisLadunku)) { - columns.dane.push(createLabelText('Opis ładunku: ', getOpisTransportuString(transport.OpisLadunku))); + columns.dane.push(createLabelText('Opis ładunku: ', translateMap(transport.OpisLadunku, TypLadunku))); if (transport.LadunekInny?._text === '1' && transport.OpisInnegoLadunku?._text) { columns.dane.push(createLabelText('Opis ładunku: ', 'Ładunek inny')); columns.dane.push(createLabelText('Opis innego ładunku: ', transport.OpisInnegoLadunku)); diff --git a/src/lib-public/generators/FA1/WarunkiTransakcji.ts b/src/lib-public/generators/FA1/WarunkiTransakcji.ts index b64f72ed..39c3f50d 100644 --- a/src/lib-public/generators/FA1/WarunkiTransakcji.ts +++ b/src/lib-public/generators/FA1/WarunkiTransakcji.ts @@ -25,11 +25,11 @@ export function generateWarunkiTransakcji(warunkiTransakcji: WarunkiTransakcji | const zamowienia: Zamowienia[] = getTable(warunkiTransakcji?.Zamowienia); const partiaTowaru: FP[] = getTable(warunkiTransakcji?.NrPartiiTowaru); const definedHeaderUmowy: HeaderDefine[] = [ - { name: 'DataUmowy', title: 'Data umowy', format: FormatTyp.Default }, + { name: 'DataUmowy', title: 'Data umowy', format: FormatTyp.Date }, { name: 'NrUmowy', title: 'Numer umowy', format: FormatTyp.Default }, ]; const definedHeaderZamowienia: HeaderDefine[] = [ - { name: 'DataZamowienia', title: 'Data zamówienia', format: FormatTyp.Default }, + { name: 'DataZamowienia', title: 'Data zamówienia', format: FormatTyp.Date }, { name: 'NrZamowienia', title: 'Numer zamówienia', format: FormatTyp.Default }, ]; const definedHeaderPartiaTowaru: HeaderDefine[] = [ @@ -102,4 +102,4 @@ export function generateWarunkiTransakcji(warunkiTransakcji: WarunkiTransakcji | } return createSection(table, true); -} +} \ No newline at end of file diff --git a/src/lib-public/generators/FA1/Wiersze.spec.ts b/src/lib-public/generators/FA1/Wiersze.spec.ts index 24210ef7..01ba352d 100644 --- a/src/lib-public/generators/FA1/Wiersze.spec.ts +++ b/src/lib-public/generators/FA1/Wiersze.spec.ts @@ -1,7 +1,7 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa } from '../../types/fa1.types'; import { generateWiersze } from './Wiersze'; @@ -37,7 +37,7 @@ describe(generateWiersze.name, () => { RodzajFaktury: { _text: TRodzajFaktury.VAT }, } as any; - const setupBasicMocks = (p15Value: string, rodzajFakturyValue: string, currencyValue: string = 'PLN') => { + const setupBasicMocks = (p15Value: string, rodzajFakturyValue: string, currencyValue = 'PLN') => { vi.mocked(PDFFunctions.getTable).mockReturnValue([ { NrWierszaFa: { _text: '1' }, @@ -52,9 +52,15 @@ describe(generateWiersze.name, () => { }); vi.mocked(PDFFunctions.getValue).mockImplementation((field: any) => { - if (field === mockFaVat.P_15 || field?._text === p15Value) return p15Value; - if (field === mockFaVat.RodzajFaktury || field?._text === rodzajFakturyValue) return rodzajFakturyValue; - if (field === mockFaVat.KodWaluty || field?._text === currencyValue) return currencyValue; + if (field === mockFaVat.P_15 || field?._text === p15Value) { + return p15Value; + } + if (field === mockFaVat.RodzajFaktury || field?._text === rodzajFakturyValue) { + return rodzajFakturyValue; + } + if (field === mockFaVat.KodWaluty || field?._text === currencyValue) { + return currencyValue; + } return undefined; }); @@ -99,17 +105,6 @@ describe(generateWiersze.name, () => { }); describe('price text formatting', () => { - it('should display "netto" when P_11 is in fieldsWithValue', () => { - setupBasicMocks('200', TRodzajFaktury.VAT); - - generateWiersze(mockFaVat); - - expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('netto'), [ - FormatTyp.Label, - FormatTyp.MarginBottom8, - ]); - }); - it('should display "brutto" when P_11 is not in fieldsWithValue', () => { vi.mocked(PDFFunctions.getTable).mockReturnValue([ { NrWierszaFa: { _text: '1' }, P_12: { _text: '23' } }, @@ -127,11 +122,6 @@ describe(generateWiersze.name, () => { vi.mocked(PDFFunctions.getTStawkaPodatku).mockReturnValue('23'); generateWiersze(mockFaVat); - - expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('brutto'), [ - FormatTyp.Label, - FormatTyp.MarginBottom8, - ]); }); it('should include currency code in price text', () => { @@ -211,6 +201,7 @@ describe(generateWiersze.name, () => { generateWiersze(mockFaVat); const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0]; + expect(sectionCall).not.toContain('\n'); expect(sectionCall.filter((item: any) => item?.table === 'table2')).toHaveLength(0); }); @@ -305,9 +296,15 @@ describe(generateWiersze.name, () => { }); vi.mocked(PDFFunctions.getValue).mockImplementation((field: any) => { - if (field === mockFaVat.P_15) return '200'; - if (field === mockFaVat.RodzajFaktury) return TRodzajFaktury.VAT; - if (field === mockFaVat.KodWaluty) return undefined; + if (field === mockFaVat.P_15) { + return '200'; + } + if (field === mockFaVat.RodzajFaktury) { + return TRodzajFaktury.VAT; + } + if (field === mockFaVat.KodWaluty) { + return undefined; + } return undefined; }); @@ -336,14 +333,12 @@ describe(generateWiersze.name, () => { generateWiersze(mockFaVat); - expect(PDFFunctions.createSection).toHaveBeenCalledWith( - expect.arrayContaining(['Header', 'formatted text']), - true - ); + expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.arrayContaining(['Header']), true); }); it('should return the result of createSection', () => { const mockSection = { section: 'test' }; + setupBasicMocks('200', TRodzajFaktury.VAT); vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); diff --git a/src/lib-public/generators/FA1/Wiersze.ts b/src/lib-public/generators/FA1/Wiersze.ts index 3cc44735..3b11a665 100644 --- a/src/lib-public/generators/FA1/Wiersze.ts +++ b/src/lib-public/generators/FA1/Wiersze.ts @@ -11,7 +11,7 @@ import { getValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { Procedura, TRodzajFaktury } from '../../../shared/consts/const'; +import { Procedura, TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa, FP } from '../../types/fa1.types'; import FormatTyp, { Position } from '../../../shared/enums/common.enum'; import { FormContentState } from '../../../shared/types/additional-data.types'; @@ -69,20 +69,20 @@ export function generateWiersze(faVat: Fa): Content { mappingData: Procedura, width: '*', }, - { name: 'KwotaAkcyzy', title: 'KwotaAkcyzy', format: FormatTyp.Default, width: 'auto' }, + { name: 'KwotaAkcyzy', title: 'Kwota podatku akcyzowego', format: FormatTyp.Default, width: 'auto' }, { name: 'GTU', title: 'GTU', format: FormatTyp.Default, width: 'auto' }, { name: 'Procedura', title: 'Oznaczenia dotyczące procedur', format: FormatTyp.Default, width: '*' }, - { name: 'P_6A', title: 'Data dostawy / wykonania', format: FormatTyp.Default, width: 'auto' }, + { name: 'P_6A', title: 'Data dostawy / wykonania', format: FormatTyp.Date, width: 'auto' }, ]; let content: FormContentState = getContentTable<(typeof faWiersze)[0]>( [...definedHeaderLp, ...definedHeader1, ...definedHeader2], faWiersze, '*' ); - const ceny: string | ContentText = formatText( - `Faktura wystawiona w cenach ${content.fieldsWithValue.includes('P_11') ? 'netto' : 'brutto'} w walucie ${faVat.KodWaluty?._text}`, - [FormatTyp.Label, FormatTyp.MarginBottom8] - ); + const ceny: string | ContentText = formatText(`Faktura wystawiona w walucie ${faVat.KodWaluty?._text}`, [ + FormatTyp.Label, + FormatTyp.MarginBottom8, + ]); const p_15: string | number | undefined = getValue(faVat.P_15); let opis: ContentStack[] = []; diff --git a/src/lib-public/generators/FA1/Zamowienia.spec.ts b/src/lib-public/generators/FA1/Zamowienia.spec.ts index 926c81ba..298dcbde 100644 --- a/src/lib-public/generators/FA1/Zamowienia.spec.ts +++ b/src/lib-public/generators/FA1/Zamowienia.spec.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Zamowienie } from '../../types/fa1.types'; import { generateZamowienie } from './Zamowienie'; import { ZamowienieKorekta } from '../../enums/invoice.enums'; @@ -13,7 +13,7 @@ vi.mock('../../../shared/PDF-functions', () => ({ getContentTable: vi.fn(), getTable: vi.fn(), getValue: vi.fn(), - getTStawkaPodatku: vi.fn() + getTStawkaPodatku: vi.fn(), })); describe(generateZamowienie.name, () => { @@ -100,6 +100,7 @@ describe(generateZamowienie.name, () => { ); const tableCall = vi.mocked(PDFFunctions.getTable).mock.results[0].value; + expect(tableCall[0].NrWierszaZam._text).toBe('1'); }); @@ -121,6 +122,7 @@ describe(generateZamowienie.name, () => { const calls = vi.mocked(PDFFunctions.getContentTable).mock.calls; const header = calls[0][0]; const cenaNetto = header.find((h: any) => h.name === 'P_9AZ'); + expect(cenaNetto?.format).toBe(FormatTyp.CurrencyAbs); }); @@ -141,6 +143,7 @@ describe(generateZamowienie.name, () => { const calls = vi.mocked(PDFFunctions.getContentTable).mock.calls; const header = calls[0][0]; const cenaNetto = header.find((h: any) => h.name === 'P_9AZ'); + expect(cenaNetto?.format).toBe(FormatTyp.Currency); }); }); @@ -236,25 +239,6 @@ describe(generateZamowienie.name, () => { }); describe('price text', () => { - it('should display "netto" when P_11 is in fieldsWithValue', () => { - vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ - content: { table: {} } as any, - fieldsWithValue: ['P_11', 'P_7Z'], - }); - - const result = generateZamowienie( - mockOrderData, - ZamowienieKorekta.BeforeCorrection, - '100', - TRodzajFaktury.ZAL, - 'EUR' - ); - - const stack = (result[0] as any).stack; - expect(stack[1]).toContain('netto'); - expect(stack[1]).toContain('EUR'); - }); - it('should display "brutto" when P_11 is not in fieldsWithValue', () => { vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ content: { table: {} } as any, @@ -270,7 +254,7 @@ describe(generateZamowienie.name, () => { ); const stack = (result[0] as any).stack; - expect(stack[1]).toContain('brutto'); + expect(stack[1]).toContain('USD'); }); }); diff --git a/src/lib-public/generators/FA1/Zamowienie.ts b/src/lib-public/generators/FA1/Zamowienie.ts index 52f5f863..f0dc8b0d 100644 --- a/src/lib-public/generators/FA1/Zamowienie.ts +++ b/src/lib-public/generators/FA1/Zamowienie.ts @@ -9,7 +9,7 @@ import { getValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { Procedura, TRodzajFaktury } from '../../../shared/consts/const'; +import { Procedura, TRodzajFaktury } from '../../../shared/consts/FA.const'; import { FP, Zamowienie } from '../../types/fa1.types'; import FormatTyp, { Position } from '../../../shared/enums/common.enum'; import { ZamowienieKorekta } from '../../enums/invoice.enums'; @@ -101,7 +101,7 @@ export function generateZamowienie( table.push(content.content); } } - const ceny = `Faktura wystawiona w cenach ${content.fieldsWithValue.includes('P_11') ? 'netto' : 'brutto'} w walucie ${KodWaluty}`; + const ceny = `Faktura wystawiona w walucie ${KodWaluty}`; let opis: Content = ''; if (Number(p_15) > 0 && rodzajFaktury == TRodzajFaktury.ZAL) { diff --git a/src/lib-public/generators/FA2/Adnotacje.ts b/src/lib-public/generators/FA2/Adnotacje.ts index 91aefb46..aeeb9b35 100644 --- a/src/lib-public/generators/FA2/Adnotacje.ts +++ b/src/lib-public/generators/FA2/Adnotacje.ts @@ -11,7 +11,7 @@ import { } from '../../../shared/PDF-functions'; import { Adnotacje, NoweSrodkiTransportu, Zwolnienie } from '../../types/fa2.types'; import FormatTyp from '../../../shared/enums/common.enum'; -import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/FA.const'; import { FP } from '../../types/fa1.types'; export function generateAdnotacje(adnotacje?: Adnotacje): Content[] { diff --git a/src/lib-public/generators/FA2/Platnosc.spec.ts b/src/lib-public/generators/FA2/Platnosc.spec.ts index 36781a1b..c10cadc6 100644 --- a/src/lib-public/generators/FA2/Platnosc.spec.ts +++ b/src/lib-public/generators/FA2/Platnosc.spec.ts @@ -10,8 +10,9 @@ import { getContentTable, hasValue, } from '../../../shared/PDF-functions'; -import { getFormaPlatnosciString } from '../../../shared/generators/common/functions'; import { generujRachunekBankowy } from './RachunekBankowy'; +import { translateMap } from '@shared/generators/common/functions'; +import { FormaPlatnosci } from '@shared/consts/FA.const'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]), @@ -26,10 +27,6 @@ vi.mock('../../../shared/PDF-functions', () => ({ hasValue: vi.fn((v: any) => !!v), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getFormaPlatnosciString: vi.fn((v: any) => `Forma: ${v}`), -})); - vi.mock('./RachunekBankowy', () => ({ generujRachunekBankowy: vi.fn((data: any, label: string): Content[] => [{ text: label }]), })); @@ -80,7 +77,7 @@ describe(generatePlatnosc.name, () => { const result = generatePlatnosc(platnosc as Platnosc); expect(hasValue).toHaveBeenCalledWith({ _text: 'Karta' }); - expect(getFormaPlatnosciString).toHaveBeenCalledWith({ _text: 'Karta' }); + expect(translateMap).toHaveBeenCalledWith({ _text: 'Karta' }, FormaPlatnosci); }); it('generuje tabelę zapłaty częściowej i terminów płatności', () => { diff --git a/src/lib-public/generators/FA2/Platnosc.ts b/src/lib-public/generators/FA2/Platnosc.ts index 22e38298..44deec00 100644 --- a/src/lib-public/generators/FA2/Platnosc.ts +++ b/src/lib-public/generators/FA2/Platnosc.ts @@ -10,11 +10,12 @@ import { } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; import { Platnosc, TerminPlatnosci, ZaplataCzesciowa } from '../../types/fa2.types'; -import { getFormaPlatnosciString } from '../../../shared/generators/common/functions'; import { generujRachunekBankowy } from './RachunekBankowy'; import FormatTyp from '../../../shared/enums/common.enum'; import { FP } from '../../types/fa1.types'; import { FormContentState } from '../../../shared/types/additional-data.types'; +import { translateMap } from '@shared/generators/common/functions'; +import { FormaPlatnosci } from '@shared/consts/FA.const'; export function generatePlatnosc(platnosc: Platnosc | undefined): Content { if (!platnosc) { @@ -27,19 +28,19 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { { name: 'Termin', title: 'Termin płatności', - format: FormatTyp.Default, + format: FormatTyp.Date, }, ]; if (terminPlatnosci.some((termin: TerminPlatnosci): FP | undefined => termin.TerminOpis)) { - zaplataCzesciowaHeader.push({ name: 'TerminOpis', title: 'Opis płatności', format: FormatTyp.Default }); + zaplataCzesciowaHeader.push({ name: 'TerminOpis', title: 'Opis płatności', format: FormatTyp.Date }); } const zaplataCzesciowaNaglowek: HeaderDefine[] = [ { name: 'DataZaplatyCzesciowej', title: 'Data zapłaty częściowej', - format: FormatTyp.Default, + format: FormatTyp.Date, }, { name: 'KwotaZaplatyCzesciowej', title: 'Kwota zapłaty częściowej', format: FormatTyp.Currency }, { name: 'FormaPlatnosci', title: 'Forma płatności', format: FormatTyp.FormOfPayment }, @@ -57,7 +58,7 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { } if (hasValue(platnosc.FormaPlatnosci)) { - table.push(createLabelText('Forma płatności: ', getFormaPlatnosciString(platnosc.FormaPlatnosci))); + table.push(createLabelText('Forma płatności: ', translateMap(platnosc.FormaPlatnosci, FormaPlatnosci))); } else { if (platnosc.OpisPlatnosci?._text) { table.push(createLabelText('Forma płatności: ', 'Płatność inna')); @@ -131,4 +132,4 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { table.push(createLabelText('Wysokość skonta: ', platnosc.Skonto.WysokoscSkonta)); } return table; -} +} \ No newline at end of file diff --git a/src/lib-public/generators/FA2/Podmiot1.ts b/src/lib-public/generators/FA2/Podmiot1.ts index 2790b52a..ca2e8e01 100644 --- a/src/lib-public/generators/FA2/Podmiot1.ts +++ b/src/lib-public/generators/FA2/Podmiot1.ts @@ -5,7 +5,7 @@ import { generateAdres } from './Adres'; import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TAXPAYER_STATUS } from '../../../shared/consts/const'; +import { TAXPAYER_STATUS } from '../../../shared/consts/FA.const'; export function generatePodmiot1(podmiot1: Podmiot1): Content[] { const result: Content[] = createHeader('Sprzedawca'); diff --git a/src/lib-public/generators/FA2/Podmiot1Podmiot1K.ts b/src/lib-public/generators/FA2/Podmiot1Podmiot1K.ts index 5dc81f27..8c801c32 100644 --- a/src/lib-public/generators/FA2/Podmiot1Podmiot1K.ts +++ b/src/lib-public/generators/FA2/Podmiot1Podmiot1K.ts @@ -13,7 +13,7 @@ import { Podmiot1, Podmiot1K } from '../../types/fa2.types'; import { generateAdres } from './Adres'; import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; -import { TAXPAYER_STATUS } from '../../../shared/consts/const'; +import { TAXPAYER_STATUS } from '../../../shared/consts/FA.const'; export function generatePodmiot1Podmiot1K(podmiot1: Podmiot1, podmiot1K: Podmiot1K): Content[] { const result: Content[] = createHeader('Sprzedawca'); diff --git a/src/lib-public/generators/FA2/Podmiot3.spec.ts b/src/lib-public/generators/FA2/Podmiot3.spec.ts index e9eaadea..b5d2b0a5 100644 --- a/src/lib-public/generators/FA2/Podmiot3.spec.ts +++ b/src/lib-public/generators/FA2/Podmiot3.spec.ts @@ -5,17 +5,14 @@ import * as PDFFunctions from '../../../shared/PDF-functions'; import * as AdresModule from './Adres'; import * as DaneIdModule from './PodmiotDaneIdentyfikacyjneTPodmiot3Dto'; import * as DaneKontaktoweModule from './PodmiotDaneKontaktowe'; -import * as CommonFunctions from '../../../shared/generators/common/functions'; vi.mock('../../../shared/PDF-functions'); vi.mock('./Adres'); vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot3Dto'); vi.mock('./PodmiotDaneKontaktowe'); -vi.mock('../../../shared/generators/common/functions'); describe(generatePodmiot3.name, () => { beforeEach(() => { - vi.clearAllMocks(); vi.mocked(PDFFunctions.createHeader).mockImplementation((text) => [{ text }]); vi.mocked(PDFFunctions.createLabelText).mockImplementation((label, value) => [ { text: `${label}${value ?? ''}` }, @@ -29,7 +26,6 @@ describe(generatePodmiot3.name, () => { { text: 'mockDaneIdentyfikacyjne' }, ]); vi.mocked(DaneKontaktoweModule.generateDaneKontaktowe).mockReturnValue([{ text: 'mockDaneKontaktowe' }]); - vi.mocked(CommonFunctions.getRolaString).mockReturnValue('mockRola'); }); it('generates minimal podmiot3 structure', () => { @@ -94,7 +90,7 @@ describe(generatePodmiot3.name, () => { IDNabywcy: 'ID1', NrEORI: 'EORI1', DaneIdentyfikacyjne: {}, - Rola: 2, + Rola: { _text: '2' }, OpisRoli: 'opisRoli', Udzial: '50%', Adres: {}, @@ -119,9 +115,15 @@ describe(generatePodmiot3.name, () => { expect( flatColumn1.some((c) => typeof c.text === 'string' && c.text.includes('Identyfikator nabywcy: ID1')) ).toBe(true); - expect(flatColumn1.some((c) => typeof c.text === 'string' && c.text.includes('Rola: mockRola'))).toBe( - true - ); + expect( + flatColumn1.some( + (c) => + typeof c.text === 'string' && + c.text.includes( + 'Rola: Odbiorca - w przypadku, gdy na fakturze występują dane jednostek wewnętrznych, oddziałów, wyodrębnionych w ramach nabywcy, które same nie stanowią nabywcy w rozumieniu ustawy' + ) + ) + ).toBe(true); expect( flatColumn1.some((c) => typeof c.text === 'string' && c.text.includes('Rola inna: opisRoli')) ).toBe(true); diff --git a/src/lib-public/generators/FA2/Podmiot3.ts b/src/lib-public/generators/FA2/Podmiot3.ts index 0f6c3ede..7e944c6d 100644 --- a/src/lib-public/generators/FA2/Podmiot3.ts +++ b/src/lib-public/generators/FA2/Podmiot3.ts @@ -10,8 +10,9 @@ import { Podmiot3 } from '../../types/fa2.types'; import { generateAdres } from './Adres'; import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot3Dto'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; -import { getRolaString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; import FormatTyp from '../../../shared/enums/common.enum'; +import { FA2RolaPodmiotu3 } from '../../../shared/consts/FA.const'; export function generatePodmiot3(podmiot: Podmiot3, index: number): Content[] { const result: Content[] = []; @@ -22,7 +23,7 @@ export function generatePodmiot3(podmiot: Podmiot3, index: number): Content[] { createLabelText('Identyfikator nabywcy: ', podmiot.IDNabywcy), createLabelText('Numer EORI: ', podmiot.NrEORI), ...generateDaneIdentyfikacyjneTPodmiot3Dto(podmiot.DaneIdentyfikacyjne), - createLabelText('Rola: ', getRolaString(podmiot.Rola, 2)), + createLabelText('Rola: ', translateMap(podmiot.Rola, FA2RolaPodmiotu3)), createLabelText('Rola inna: ', podmiot.OpisRoli), createLabelText('Udział: ', podmiot.Udzial, [FormatTyp.Percentage]), ]; diff --git a/src/lib-public/generators/FA2/Podmiot3Podmiot2k.spec.ts b/src/lib-public/generators/FA2/Podmiot3Podmiot2k.spec.ts index 9851748c..c07ca926 100644 --- a/src/lib-public/generators/FA2/Podmiot3Podmiot2k.spec.ts +++ b/src/lib-public/generators/FA2/Podmiot3Podmiot2k.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './Podmiot3Podmiot2k'; -import {Content} from "pdfmake/interfaces"; +import { Content } from 'pdfmake/interfaces'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn((text: string) => [`header:${text}`]), @@ -11,10 +11,6 @@ vi.mock('../../../shared/PDF-functions', () => ({ generateLine: vi.fn((): Content[] => [{ line: true } as any]), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getRolaString: vi.fn((rola: any) => `rola:${rola}`), -})); - vi.mock('./PodmiotAdres', () => ({ generatePodmiotAdres: vi.fn((adres: any, label?: string) => [`adres:${label ?? ''}`]), })); @@ -30,6 +26,7 @@ vi.mock('./PodmiotDaneKontaktowe', () => ({ describe(generateDaneIdentyfikacyjneTPodmiot3Dto.name, () => { it('should return empty array when input is undefined', () => { const result = generateDaneIdentyfikacyjneTPodmiot3Dto(undefined, 0); + expect(result).toEqual([]); }); @@ -56,9 +53,11 @@ describe(generateDaneIdentyfikacyjneTPodmiot3Dto.name, () => { const result = generateDaneIdentyfikacyjneTPodmiot3Dto(podmiotDto as any, 0); - expect(result[0]).toEqual([{ - "line": true, - }]); + expect(result[0]).toEqual([ + { + line: true, + }, + ]); expect(result.some((r: any) => r.columns)).toBe(true); }); @@ -71,8 +70,10 @@ describe(generateDaneIdentyfikacyjneTPodmiot3Dto.name, () => { const result = generateDaneIdentyfikacyjneTPodmiot3Dto(podmiotDto as any, 1); - expect(result[0]).toEqual([{ - "line": true, - }]); + expect(result[0]).toEqual([ + { + line: true, + }, + ]); }); }); diff --git a/src/lib-public/generators/FA2/Podmiot3Podmiot2k.ts b/src/lib-public/generators/FA2/Podmiot3Podmiot2k.ts index 8e6602f6..33e9db77 100644 --- a/src/lib-public/generators/FA2/Podmiot3Podmiot2k.ts +++ b/src/lib-public/generators/FA2/Podmiot3Podmiot2k.ts @@ -8,13 +8,14 @@ import { hasValue, } from '../../../shared/PDF-functions'; import { Podmiot3Podmiot2KDto } from '../../types/fa2-additional-types'; -import { getRolaString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjneTPodmiot2Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot2Dto'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; import FormatTyp from '../../../shared/enums/common.enum'; import { Podmiot1DaneKontaktowe, Podmiot2K, Podmiot3 } from '../../types/fa2.types'; import { Adres } from '../../types/fa1.types'; +import { FA2RolaPodmiotu3 } from '../../../shared/consts/FA.const'; export function generateDaneIdentyfikacyjneTPodmiot3Dto( podmiot2KDto: Podmiot3Podmiot2KDto | undefined, @@ -40,7 +41,7 @@ export function generateDaneIdentyfikacyjneTPodmiot3Dto( result.push( ...createHeader('Dane identyfikacyjne'), createLabelText('Numer EORI: ', podmiot1.NrEORI), - createLabelText('Rola: ', getRolaString(podmiot1.Rola, 2)), + createLabelText('Rola: ', translateMap(podmiot1.Rola, FA2RolaPodmiotu3)), createLabelText('Rola inna: ', podmiot1.OpisRoli), createLabelText('Udział: ', podmiot1.Udzial, FormatTyp.Percentage) ); diff --git a/src/lib-public/generators/FA2/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts b/src/lib-public/generators/FA2/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts index 6c9cc9b5..f32a20ad 100644 --- a/src/lib-public/generators/FA2/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts +++ b/src/lib-public/generators/FA2/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts @@ -44,7 +44,22 @@ describe(generateDaneIdentyfikacyjneTPodmiot2Dto.name, () => { expect(result.some((r: any) => r.text.includes('Numer VAT-UE'))).toBe(true); }); - it('adds KodKraju section when KodKraju is present', () => { + it('adds NrID section when KodKraju is not present', () => { + const data: DaneIdentyfikacyjneTPodmiot2Dto = { + ...baseData, + NrID: { _text: '999999' }, + }; + const result = generateDaneIdentyfikacyjneTPodmiot2Dto(data); + expect(createLabelTextArray).toHaveBeenCalledWith([ + { value: 'Identyfikator podatkowy inny: ', formatTyp: FormatTyp.Label }, + { value: '', formatTyp: FormatTyp.Value }, + { value: ' ' }, + { value: data.NrID, formatTyp: FormatTyp.Value }, + ]); + expect(result.some((r: any) => (r as any).text.includes('Identyfikator podatkowy inny'))).toBe(true); + }); + + it('adds NrID section when KodKraju is present', () => { const data: DaneIdentyfikacyjneTPodmiot2Dto = { ...baseData, KodKraju: { _text: 'DE' }, diff --git a/src/lib-public/generators/FA2/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts b/src/lib-public/generators/FA2/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts index a3dd45e4..18362dee 100644 --- a/src/lib-public/generators/FA2/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts +++ b/src/lib-public/generators/FA2/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts @@ -19,11 +19,11 @@ export function generateDaneIdentyfikacyjneTPodmiot2Dto( ]) ); } - if (daneIdentyfikacyjne.KodKraju?._text) { + if (daneIdentyfikacyjne.NrID?._text) { result.push( createLabelTextArray([ { value: 'Identyfikator podatkowy inny: ', formatTyp: FormatTyp.Label }, - { value: daneIdentyfikacyjne.KodKraju, formatTyp: FormatTyp.Value }, + { value: daneIdentyfikacyjne.KodKraju || '', formatTyp: FormatTyp.Value }, { value: ' ' }, { value: daneIdentyfikacyjne.NrID, formatTyp: FormatTyp.Value }, ]) diff --git a/src/lib-public/generators/FA2/PodmiotUpowazniony.spec.ts b/src/lib-public/generators/FA2/PodmiotUpowazniony.spec.ts index 26ca3548..54a8bb20 100644 --- a/src/lib-public/generators/FA2/PodmiotUpowazniony.spec.ts +++ b/src/lib-public/generators/FA2/PodmiotUpowazniony.spec.ts @@ -6,7 +6,8 @@ import { createHeader, createLabelText, hasValue } from '../../../shared/PDF-fun import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; import { generatePodmiotUpowaznionyDaneKontaktowe } from './PodmiotUpowaznionyDaneKontaktowe'; -import { getRolaUpowaznionegoString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; +import { TRolaPodmiotuUpowaznionegoFA2 } from '@shared/consts/FA.const'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn(() => [{ text: 'Header: Podmiot upoważniony' }]), @@ -26,10 +27,6 @@ vi.mock('./PodmiotUpowaznionyDaneKontaktowe', () => ({ generatePodmiotUpowaznionyDaneKontaktowe: vi.fn(() => [{ text: 'Kontakt content' }]), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getRolaUpowaznionegoString: vi.fn(() => 'Agent celny'), -})); - describe(generatePodmiotUpowazniony.name, () => { const baseData: PodmiotUpowazniony = { RolaPU: { _text: '1' }, @@ -46,26 +43,35 @@ describe(generatePodmiotUpowazniony.name, () => { it('returns empty array when podmiotUpowazniony is undefined', () => { const result = generatePodmiotUpowazniony(undefined); + expect(result).toEqual([]); expect(createHeader).not.toHaveBeenCalled(); }); it('always starts with header', () => { const result = generatePodmiotUpowazniony(baseData); + expect(createHeader).toHaveBeenCalledWith('Podmiot upoważniony'); expect(result[0]).toEqual({ text: 'Header: Podmiot upoważniony' }); }); it('adds Rola when RolaPU has value', () => { const result = generatePodmiotUpowazniony(baseData); + expect(hasValue).toHaveBeenCalledWith(baseData.RolaPU); - expect(getRolaUpowaznionegoString).toHaveBeenCalledWith(baseData.RolaPU, 2); - expect(createLabelText).toHaveBeenCalledWith('Rola: ', 'Agent celny'); - expect(result.some((r) => (r as any).text?.includes('Rola: Agent celny'))).toBe(true); + expect(translateMap).toHaveBeenCalledWith(baseData.RolaPU, TRolaPodmiotuUpowaznionegoFA2); + expect( + result.some((r) => + (r as any).text?.includes( + 'Rola: Organ egzekucyjny - w przypadku, o którym mowa w art. 106c pkt 1 ustawy' + ) + ) + ).toBe(true); }); it('adds EORI when NrEORI has value', () => { const result = generatePodmiotUpowazniony(baseData); + expect(hasValue).toHaveBeenCalledWith(baseData.NrEORI); expect(createLabelText).toHaveBeenCalledWith('Numer EORI: ', baseData.NrEORI); expect(result.some((r) => (r as any).text?.includes('Numer EORI: EORI123'))).toBe(true); @@ -73,24 +79,29 @@ describe(generatePodmiotUpowazniony.name, () => { it('adds DaneIdentyfikacyjne when present', () => { const result = generatePodmiotUpowazniony(baseData); + expect(generateDaneIdentyfikacyjneTPodmiot1Dto).toHaveBeenCalledWith(baseData.DaneIdentyfikacyjne); expect(result.flat().some((r) => (r as any).text === 'Dane identyfikacyjne content')).toBe(true); }); it('adds Adres, AdresKoresp, and DaneKontaktowe sections', () => { const result = generatePodmiotUpowazniony(baseData); + expect(generatePodmiotAdres).toHaveBeenCalledWith(baseData.Adres); expect(generatePodmiotAdres).toHaveBeenCalledWith(baseData.AdresKoresp, 'Adres korespondencyjny'); expect(generatePodmiotUpowaznionyDaneKontaktowe).toHaveBeenCalledWith(baseData.DaneKontaktowe); const flattened = result.flat(); + expect(flattened.some((r) => (r as any).text === 'Adres content')).toBe(true); expect(flattened.some((r) => (r as any).text === 'Kontakt content')).toBe(true); }); it('handles missing optional fields gracefully', () => { const data: PodmiotUpowazniony = { Adres: { miejscowosc: 'Gdańsk' } } as any; + (hasValue as any).mockReturnValue(false); const result = generatePodmiotUpowazniony(data); + expect(result[0]).toEqual({ text: 'Header: Podmiot upoważniony' }); expect(createLabelText).not.toHaveBeenCalled(); expect(generateDaneIdentyfikacyjneTPodmiot1Dto).not.toHaveBeenCalled(); diff --git a/src/lib-public/generators/FA2/PodmiotUpowazniony.ts b/src/lib-public/generators/FA2/PodmiotUpowazniony.ts index e2df5148..a13c118c 100644 --- a/src/lib-public/generators/FA2/PodmiotUpowazniony.ts +++ b/src/lib-public/generators/FA2/PodmiotUpowazniony.ts @@ -4,7 +4,8 @@ import { PodmiotUpowazniony } from '../../types/fa2.types'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; import { generatePodmiotUpowaznionyDaneKontaktowe } from './PodmiotUpowaznionyDaneKontaktowe'; -import { getRolaUpowaznionegoString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; +import { TRolaPodmiotuUpowaznionegoFA2 } from '@shared/consts/FA.const'; export function generatePodmiotUpowazniony(podmiotUpowazniony: PodmiotUpowazniony | undefined): Content[] { if (!podmiotUpowazniony) { @@ -13,7 +14,9 @@ export function generatePodmiotUpowazniony(podmiotUpowazniony: PodmiotUpowaznion const result: Content[] = createHeader('Podmiot upoważniony'); if (hasValue(podmiotUpowazniony.RolaPU)) { - result.push(createLabelText('Rola: ', getRolaUpowaznionegoString(podmiotUpowazniony.RolaPU, 2))); + result.push( + createLabelText('Rola: ', translateMap(podmiotUpowazniony.RolaPU, TRolaPodmiotuUpowaznionegoFA2)) + ); } if (hasValue(podmiotUpowazniony.NrEORI)) { result.push(createLabelText('Numer EORI: ', podmiotUpowazniony.NrEORI)); diff --git a/src/lib-public/generators/FA2/PodsumowanieStawekPodatkuVat.ts b/src/lib-public/generators/FA2/PodsumowanieStawekPodatkuVat.ts index cd7f286b..243e0007 100644 --- a/src/lib-public/generators/FA2/PodsumowanieStawekPodatkuVat.ts +++ b/src/lib-public/generators/FA2/PodsumowanieStawekPodatkuVat.ts @@ -10,7 +10,7 @@ import { import FormatTyp from '../../../shared/enums/common.enum'; import { Fa, Faktura, FP } from '../../types/fa2.types'; import { TaxSummaryTypes } from '../../types/tax-summary.types'; -import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/FA.const'; export function generatePodsumowanieStawekPodatkuVat(faktura: Faktura): Content[] { const AnyP13P14_5Diff0: boolean = diff --git a/src/lib-public/generators/FA2/Rabat.ts b/src/lib-public/generators/FA2/Rabat.ts index 38439954..bbce3f9e 100644 --- a/src/lib-public/generators/FA2/Rabat.ts +++ b/src/lib-public/generators/FA2/Rabat.ts @@ -18,7 +18,7 @@ export function generateRabat(invoice: Fa): Content[] { const faRows: Record[] = getTable(invoice!.FaWiersz); const result: Content[] = []; const definedHeader: HeaderDefine[] = [ - { name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default }, + { name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default, width: 'auto' }, { name: 'P_7', title: 'Nazwa towaru lub usługi', format: FormatTyp.Default }, { name: 'P_8B', title: 'Ilość', format: FormatTyp.Default }, { name: 'P_8A', title: 'Miara', format: FormatTyp.Default }, diff --git a/src/lib-public/generators/FA2/RachunekBankowy.spec.ts b/src/lib-public/generators/FA2/RachunekBankowy.spec.ts index fa2c6299..1374f924 100644 --- a/src/lib-public/generators/FA2/RachunekBankowy.spec.ts +++ b/src/lib-public/generators/FA2/RachunekBankowy.spec.ts @@ -1,10 +1,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { generujRachunekBankowy } from './RachunekBankowy'; import * as PDFFunctions from '../../../shared/PDF-functions'; +import { makeBreakable } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { FP } from '../../types/fa2.types'; -import * as CommonFunctions from '../../../shared/generators/common/functions'; -import { makeBreakable } from '../../../shared/PDF-functions'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn(), @@ -15,10 +14,6 @@ vi.mock('../../../shared/PDF-functions', () => ({ hasValue: vi.fn((val) => Boolean(val && val._text)), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getTypRachunkowWlasnych: vi.fn(), -})); - describe(generujRachunekBankowy.name, () => { beforeEach(() => { vi.clearAllMocks(); @@ -36,7 +31,6 @@ describe(generujRachunekBankowy.name, () => { vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any); vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any); vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted' as any); - vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak'); }); describe('when accounts is undefined or empty', () => { @@ -92,12 +86,9 @@ describe(generujRachunekBankowy.name, () => { }); it('should format "Rachunek własny banku" field', () => { - vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak'); - generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); expect(PDFFunctions.formatText).toHaveBeenCalledWith('Rachunek własny banku', FormatTyp.GrayBoldTitle); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku); expect(PDFFunctions.formatText).toHaveBeenCalledWith(makeBreakable('Tak', 20), FormatTyp.Default); }); @@ -127,7 +118,7 @@ describe(generujRachunekBankowy.name, () => { const tableContent = sectionCall[0][1]; expect(tableContent).toHaveProperty('table'); - expect(tableContent.table).toHaveProperty('widths', ['*', 'auto']); + expect(tableContent.table).toHaveProperty('widths', ['auto', '*']); }); it('should create table structure with unbreakable property', () => { @@ -214,10 +205,6 @@ describe(generujRachunekBankowy.name, () => { }; generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy'); - - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledTimes(2); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(account2.RachunekWlasnyBanku); }); it('should format all fields for all accounts', () => { diff --git a/src/lib-public/generators/FA2/RachunekBankowy.ts b/src/lib-public/generators/FA2/RachunekBankowy.ts index 840e0d93..7418a39a 100644 --- a/src/lib-public/generators/FA2/RachunekBankowy.ts +++ b/src/lib-public/generators/FA2/RachunekBankowy.ts @@ -9,9 +9,9 @@ import { } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { RachunekBankowy } from '../../types/fa2.types'; -import { getTypRachunkowWlasnych } from '../../../shared/generators/common/functions'; import { FP } from '../../types/fa1.types'; -import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT, TypRachunkowWlasnych } from '../../../shared/consts/FA.const'; +import { translateMap } from '@shared/generators/common/functions'; export const generujRachunekBankowy: (accounts?: Record[], title?: string) => Content[] = ( accounts?: RachunekBankowy[], @@ -40,7 +40,10 @@ export const generujRachunekBankowy: (accounts?: Record[], title?: s ]); table.push([ formatText('Rachunek własny banku', FormatTyp.GrayBoldTitle), - formatText(makeBreakable(getTypRachunkowWlasnych(account.RachunekWlasnyBanku), 20), FormatTyp.Default), + formatText( + makeBreakable(translateMap(account.RachunekWlasnyBanku, TypRachunkowWlasnych), 20), + FormatTyp.Default + ), ]); table.push([ formatText('Nazwa banku', FormatTyp.GrayBoldTitle), @@ -66,7 +69,7 @@ export const generujRachunekBankowy: (accounts?: Record[], title?: s unbreakable: true, table: { body: table, - widths: ['*', 'auto'], + widths: ['auto', '*'], }, layout: DEFAULT_TABLE_LAYOUT, } as ContentTable, diff --git a/src/lib-public/generators/FA2/Szczegoly.spec.ts b/src/lib-public/generators/FA2/Szczegoly.spec.ts index 2a4bfe29..6073c409 100644 --- a/src/lib-public/generators/FA2/Szczegoly.spec.ts +++ b/src/lib-public/generators/FA2/Szczegoly.spec.ts @@ -1,8 +1,8 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { generateSzczegoly } from './Szczegoly'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa } from '../../types/fa2.types'; vi.mock('../../../shared/PDF-functions', () => ({ @@ -23,7 +23,6 @@ describe(generateSzczegoly.name, () => { beforeEach(() => { vi.clearAllMocks(); }); - const mockFaVat: Fa = { FaWiersz: [], Zamowienie: { @@ -67,6 +66,7 @@ describe(generateSzczegoly.name, () => { it('should call createSection and return result', () => { const mockSection = 'section'; + vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); const result = generateSzczegoly(mockFaVat); @@ -136,6 +136,13 @@ describe(generateSzczegoly.name, () => { describe('P_6 scope', () => { it('should generate P_6 scope when both P_6_Od and P_6_Do exist', () => { + vi.mocked(PDFFunctions.getValue).mockImplementation((item: any) => { + if (typeof item === 'object') { + return item._text; + } + + return item; + }); const data = { ...mockFaVat, OkresFa: { @@ -154,13 +161,20 @@ describe(generateSzczegoly.name, () => { { value: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', }, - { value: data.OkresFa.P_6_Od, formatTyp: FormatTyp.Value }, + { value: '01.01.2024', formatTyp: FormatTyp.Value }, { value: ' do ' }, - { value: data.OkresFa.P_6_Do, formatTyp: FormatTyp.Value }, + { value: '31.01.2024', formatTyp: FormatTyp.Value }, ]); }); it('should generate P_6 scope when only P_6_Od exists', () => { + vi.mocked(PDFFunctions.getValue).mockImplementation((item: any) => { + if (typeof item === 'object') { + return item._text; + } + + return item; + }); const data = { ...mockFaVat, OkresFa: { @@ -175,11 +189,18 @@ describe(generateSzczegoly.name, () => { expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', - data.OkresFa.P_6_Od + '01.01.2024' ); }); it('should generate P_6 scope when only P_6_Do exists', () => { + vi.mocked(PDFFunctions.getValue).mockImplementation((item: any) => { + if (typeof item === 'object') { + return item._text; + } + + return item; + }); const data = { ...mockFaVat, OkresFa: { @@ -194,7 +215,7 @@ describe(generateSzczegoly.name, () => { expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', - data.OkresFa.P_6_Do + '31.01.2024' ); }); @@ -208,42 +229,15 @@ describe(generateSzczegoly.name, () => { const p6Call = calls.find((call) => call[0].some((item: any) => item.value?.includes('Data dokonania lub zakończenia')) ); + expect(p6Call).toBeUndefined(); }); }); describe('ceny labels', () => { - it('should add "netto" label when P_11 exists in FaWiersz', () => { - vi.mocked(PDFFunctions.getTable).mockReturnValue([]); - - vi.mocked(PDFFunctions.hasColumnsValue).mockImplementation((column: string) => column === 'P_11'); - - generateSzczegoly(mockFaVat); - - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'netto'); - }); - - it('should add "netto" label when P_11 exists in ZamowienieWiersz', () => { - vi.mocked(PDFFunctions.getTable).mockReturnValue([]); - - vi.mocked(PDFFunctions.hasColumnsValue).mockImplementation((column: string) => column === 'P_11'); - - generateSzczegoly(mockFaVat); - - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'netto'); - }); - - it('should add "brutto" label when P_11 does not exist', () => { - vi.mocked(PDFFunctions.getTable).mockReturnValue([]); - vi.mocked(PDFFunctions.hasColumnsValue).mockReturnValue(false); - - generateSzczegoly(mockFaVat); - - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'brutto'); - }); - it('should add currency code label', () => { vi.mocked(PDFFunctions.getTable).mockReturnValue([]); + vi.mocked(PDFFunctions.hasValue).mockReturnValue(true); generateSzczegoly(mockFaVat); @@ -252,16 +246,19 @@ describe(generateSzczegoly.name, () => { it('should not add ceny labels when FaWiersz or ZamowienieWiersz exist', () => { vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === mockFaVat.FaWiersz) return [{}]; + if (field === mockFaVat.FaWiersz) { + return [{}]; + } return []; }); vi.mocked(PDFFunctions.createLabelText).mockClear(); generateSzczegoly(mockFaVat); - const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; + const cenyCall = calls.find((call) => call[0] === 'Faktura wystawiona w cenach: '); + expect(cenyCall).toBeUndefined(); }); }); @@ -291,6 +288,7 @@ describe(generateSzczegoly.name, () => { const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const ossCall = calls.find((call) => call[0] === 'Procedura One Stop Shop'); + expect(ossCall).toBeUndefined(); }); }); @@ -358,6 +356,7 @@ describe(generateSzczegoly.name, () => { const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const currencyCall = calls.find((call) => call[0] === 'Kurs waluty: '); + expect(currencyCall).toBeUndefined(); }); @@ -385,6 +384,7 @@ describe(generateSzczegoly.name, () => { const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const currencyCall = calls.find((call) => call[0] === 'Kurs waluty: '); + expect(currencyCall).toBeUndefined(); }); }); @@ -427,9 +427,11 @@ describe(generateSzczegoly.name, () => { generateSzczegoly(mockFaVat); const calls = vi.mocked(PDFFunctions.generateTwoColumns).mock.calls; + expect(calls.length).toBeGreaterThanOrEqual(1); const firstCall = calls[0]; + expect(Array.isArray(firstCall[0])).toBe(true); expect(Array.isArray(firstCall[1])).toBe(true); }); @@ -449,7 +451,7 @@ describe(generateSzczegoly.name, () => { } as any; vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === data.ZaliczkaCzesciowa) + if (field === data.ZaliczkaCzesciowa) { return [ { P_6Z: { _text: '2024-01-01' }, @@ -457,6 +459,7 @@ describe(generateSzczegoly.name, () => { KursWalutyZW: { _text: '4.50' }, }, ] as any; + } return []; }); @@ -506,7 +509,9 @@ describe(generateSzczegoly.name, () => { } as any; vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === data.ZaliczkaCzesciowa) return [{ P_6Z: { _text: '' } }] as any; + if (field === data.ZaliczkaCzesciowa) { + return [{ P_6Z: { _text: '' } }] as any; + } return []; }); @@ -533,12 +538,13 @@ describe(generateSzczegoly.name, () => { } as any; vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === data.FakturaZaliczkowa) + if (field === data.FakturaZaliczkowa) { return [ { NrKSeFFaZaliczkowej: { _text: 'FA001' }, }, ] as any; + } return []; }); @@ -590,7 +596,9 @@ describe(generateSzczegoly.name, () => { } as any; vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === data.FakturaZaliczkowa) return [{ NrKSeFFaZaliczkowej: { _text: '' } }] as any; + if (field === data.FakturaZaliczkowa) { + return [{ NrKSeFFaZaliczkowej: { _text: '' } }] as any; + } return []; }); diff --git a/src/lib-public/generators/FA2/Szczegoly.ts b/src/lib-public/generators/FA2/Szczegoly.ts index fab0d57f..44371b3e 100644 --- a/src/lib-public/generators/FA2/Szczegoly.ts +++ b/src/lib-public/generators/FA2/Szczegoly.ts @@ -13,13 +13,14 @@ import { hasValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa, ZaliczkaCzesciowa } from '../../types/fa2.types'; import { DifferentValues, ObjectKeysOfFP, TypesOfValues } from '../../../shared/types/universal.types'; import { FP } from '../../types/fa1.types'; import FormatTyp from '../../../shared/enums/common.enum'; import { TableWithFields } from '../../types/fa1-additional-types'; import { FA2FakturaZaliczkowaData } from '../../types/common.types'; +import { formatDateTime } from '@shared/generators/common/functions'; export function generateSzczegoly(faVat: Fa): Content[] { const faWiersze: Record[] = getTable(faVat.FaWiersz); @@ -34,15 +35,7 @@ export function generateSzczegoly(faVat: Fa): Content[] { const cenyLabel1: Content[] = []; const cenyLabel2: Content[] = []; - if (!(faWiersze.length > 0 || zamowieniaWiersze.length > 0)) { - const Any_P_11: boolean = - hasColumnsValue('P_11', faWiersze) || hasColumnsValue('P_11', zamowieniaWiersze); - - if (Any_P_11) { - cenyLabel1.push(createLabelText('Faktura wystawiona w cenach: ', 'netto')); - } else { - cenyLabel1.push(createLabelText('Faktura wystawiona w cenach: ', 'brutto')); - } + if (hasValue(faVat.KodWaluty)) { cenyLabel2.push(createLabelText('Kod waluty: ', faVat.KodWaluty)); } @@ -120,18 +113,24 @@ function generateP_6Scope(P_6_Od: TypesOfValues, P_6_Do: TypesOfValues): Content { value: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', }, - { value: P_6_Od, formatTyp: FormatTyp.Value }, + { value: formatDateTime(getValue(P_6_Od) as string, true, true), formatTyp: FormatTyp.Value }, { value: ' do ' }, - { value: P_6_Do, formatTyp: FormatTyp.Value }, + { value: formatDateTime(getValue(P_6_Do) as string, true, true), formatTyp: FormatTyp.Value }, ]) ); } else if (hasValue(P_6_Od)) { table.push( - createLabelText('Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', P_6_Od) + createLabelText( + 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', + formatDateTime(getValue(P_6_Od) as string, true, true) + ) ); } else if (hasValue(P_6_Do)) { table.push( - createLabelText('Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', P_6_Do) + createLabelText( + 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', + formatDateTime(getValue(P_6_Do) as string, true, true) + ) ); } return table; @@ -145,7 +144,7 @@ function generateZaliczkaCzesciowa(zaliczkaCzesciowaData: ZaliczkaCzesciowa[] | const table: Content[] = []; const zaplataCzesciowaHeader: HeaderDefine[] = [ - { name: 'P_6Z', title: 'Data otrzymania płatności', format: FormatTyp.Default }, + { name: 'P_6Z', title: 'Data otrzymania płatności', format: FormatTyp.Date }, { name: 'P_15Z', title: 'Kwota płatności', format: FormatTyp.Default }, { name: 'KursWalutyZW', title: 'Kurs waluty', format: FormatTyp.Currency6 }, ]; @@ -167,17 +166,17 @@ function generateFakturaZaliczkowa(fakturaZaliczkowaData: ObjectKeysOfFP[] | und return []; } const fakturaZaliczkowa = getTable(fakturaZaliczkowaData) as unknown as FA2FakturaZaliczkowaData[]; - const fakturaZaliczkowaMapped = fakturaZaliczkowa.map(item => { - const fp = - ( - 'NrFaZaliczkowej' in item && item.NrFaZaliczkowej - ) ? item.NrFaZaliczkowej : ('NrKSeFFaZaliczkowej' in item ? item.NrKSeFFaZaliczkowej : undefined ); - - return{ - ...item, - NrFaZaliczkowej : fp ?? { _text: ''}, - }; - }) + const fakturaZaliczkowaMapped = fakturaZaliczkowa.map((item) => { + if ('NrFaZaliczkowej' in item && item.NrFaZaliczkowej) { + return { ...item, NrFaZaliczkowej: item.NrFaZaliczkowej }; + } + + if ('NrKSeFFaZaliczkowej' in item && item.NrKSeFFaZaliczkowej) { + return { ...item, NrFaZaliczkowej: item.NrKSeFFaZaliczkowej }; + } + + return { ...item, NrFaZaliczkowej: { _text: '' } }; + }); const table: Content[] = []; const fakturaZaliczkowaHeader: HeaderDefine[] = [ @@ -190,7 +189,7 @@ function generateFakturaZaliczkowa(fakturaZaliczkowaData: ObjectKeysOfFP[] | und const tableFakturaZaliczkowa: TableWithFields = getContentTable<(typeof fakturaZaliczkowa)[0]>( fakturaZaliczkowaHeader, - fakturaZaliczkowaMapped, + fakturaZaliczkowaMapped, 'auto', [0, 4, 0, 0] ); diff --git a/src/lib-public/generators/FA2/Transport.spec.ts b/src/lib-public/generators/FA2/Transport.spec.ts index 2fdb7102..c04dc768 100644 --- a/src/lib-public/generators/FA2/Transport.spec.ts +++ b/src/lib-public/generators/FA2/Transport.spec.ts @@ -3,9 +3,10 @@ import { generateTransport } from './Transport'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { Transport } from '../../types/fa2.types'; -import { Kraj } from '../../../shared/consts/const'; +import { Kraj, TypLadunku } from '../../../shared/consts/FA.const'; import * as PrzewoznikModule from './Przewoznik'; -import * as CommonFunctions from '../../../shared/generators/common/functions'; +import * as CommonFunctions from '@shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn(), @@ -22,17 +23,7 @@ vi.mock('./Przewoznik', () => ({ generatePrzewoznik: vi.fn(), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getDateTimeWithoutSeconds: vi.fn(), - getRodzajTransportuString: vi.fn(), - getOpisTransportuString: vi.fn(), -})); - describe(generateTransport.name, () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - const mockTransport: Transport = { RodzajTransportu: { _text: '1' }, TransportInny: { _text: '0' }, @@ -61,6 +52,7 @@ describe(generateTransport.name, () => { } as any; beforeEach(() => { + vi.clearAllMocks(); vi.mocked(PDFFunctions.createHeader).mockReturnValue('header' as any); vi.mocked(PDFFunctions.createLabelText).mockReturnValue('label' as any); vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any); @@ -70,9 +62,6 @@ describe(generateTransport.name, () => { vi.mocked(PDFFunctions.getTable).mockReturnValue([]); vi.mocked(PDFFunctions.hasValue).mockReturnValue(true); vi.mocked(PrzewoznikModule.generatePrzewoznik).mockReturnValue('przewoznik' as any); - vi.mocked(CommonFunctions.getDateTimeWithoutSeconds).mockReturnValue('2024-01-01 10:00'); - vi.mocked(CommonFunctions.getRodzajTransportuString).mockReturnValue('Road'); - vi.mocked(CommonFunctions.getOpisTransportuString).mockReturnValue('Opis'); }); it('should call createHeader with "Transport"', () => { @@ -83,6 +72,7 @@ describe(generateTransport.name, () => { it('should call createSection and return result', () => { const mockSection = { section: 'test' }; + vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); const result = generateTransport(mockTransport); @@ -98,18 +88,15 @@ describe(generateTransport.name, () => { RodzajTransportu: { _text: '1' }, } as any; - vi.mocked(CommonFunctions.getRodzajTransportuString).mockReturnValue('Road'); - generateTransport(data); - expect(CommonFunctions.getRodzajTransportuString).toHaveBeenCalledWith(data.RodzajTransportu); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Road'); + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Transport morski'); }); it('should add transport inny when TransportInny is "1"', () => { const data = { ...mockTransport, - RodzajTransportu: { _text: '' }, + RodzajTransportu: null, TransportInny: { _text: '1' }, OpisInnegoTransportu: { _text: 'Custom transport' }, } as any; @@ -137,6 +124,7 @@ describe(generateTransport.name, () => { const transportInnyCall = calls.find( (call) => call[0] === 'Rodzaj transportu: ' && call[1] === 'Transport inny' ); + expect(transportInnyCall).toBeUndefined(); }); }); @@ -157,7 +145,10 @@ describe(generateTransport.name, () => { generateTransport(mockTransport); expect(PDFFunctions.hasValue).toHaveBeenCalledWith(mockTransport.OpisLadunku); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Opis ładunku: ', 'Opis'); + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( + 'Opis ładunku: ', + translateMap(mockTransport.OpisLadunku, TypLadunku) + ); }); it('should not add opis ladunku when hasValue returns false', () => { @@ -167,6 +158,7 @@ describe(generateTransport.name, () => { const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const ladunekCall = calls.find((call) => call[0] === 'Opis ładunku: '); + expect(ladunekCall).toBeUndefined(); }); @@ -201,6 +193,7 @@ describe(generateTransport.name, () => { const ladunekInnyCall = calls.find( (call) => call[0] === 'Opis ładunku: ' && call[1] === 'Ładunek inny' ); + expect(ladunekInnyCall).toBeUndefined(); }); @@ -321,6 +314,7 @@ describe(generateTransport.name, () => { const wysylkaDoCall = calls.find( (call) => call[0] === 'Adres miejsca docelowego, do którego został zlecony transport' ); + expect(wysylkaDoCall).toBeUndefined(); }); @@ -424,6 +418,7 @@ describe(generateTransport.name, () => { const calls = vi.mocked(PDFFunctions.createSubHeader).mock.calls; const wysylkaPrzezCalls = calls.filter((call) => call[0] === 'Adres pośredni wysyłki'); + expect(wysylkaPrzezCalls).toHaveLength(0); }); }); @@ -439,6 +434,7 @@ describe(generateTransport.name, () => { generateTransport(mockTransport); const calls = vi.mocked(PDFFunctions.generateTwoColumns).mock.calls; + expect(calls.length).toBeGreaterThanOrEqual(2); }); diff --git a/src/lib-public/generators/FA2/Transport.ts b/src/lib-public/generators/FA2/Transport.ts index 5c7d7d11..00a0115c 100644 --- a/src/lib-public/generators/FA2/Transport.ts +++ b/src/lib-public/generators/FA2/Transport.ts @@ -1,5 +1,5 @@ import { Content } from 'pdfmake/interfaces'; -import { Kraj } from '../../../shared/consts/const'; +import { Kraj, RodzajTransportu, TypLadunku } from '../../../shared/consts/FA.const'; import { createHeader, createLabelText, @@ -12,11 +12,7 @@ import { } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { Adres, Transport } from '../../types/fa2.types'; -import { - getDateTimeWithoutSeconds, - getOpisTransportuString, - getRodzajTransportuString, -} from '../../../shared/generators/common/functions'; +import { getDateTimeWithoutSeconds, translateMap } from '@shared/generators/common/functions'; import { generatePrzewoznik } from './Przewoznik'; export function generateTransport(transport: Transport, index?: number | null): Content { @@ -32,7 +28,7 @@ export function generateTransport(transport: Transport, index?: number | null): table.push(createHeader(index ? `Transport ${index}` : 'Transport')); if (transport.RodzajTransportu?._text) { columns.transport.push( - createLabelText('Rodzaj transportu: ', getRodzajTransportuString(transport.RodzajTransportu)) + createLabelText('Rodzaj transportu: ', translateMap(transport.RodzajTransportu, RodzajTransportu)) ); } else if (transport.TransportInny?._text == '1' && transport.OpisInnegoTransportu?._text) { columns.transport.push(createLabelText('Rodzaj transportu: ', 'Transport inny')); @@ -42,7 +38,7 @@ export function generateTransport(transport: Transport, index?: number | null): } columns.dane.push(createLabelText('Numer zlecenia transportu: ', transport.NrZleceniaTransportu)); if (hasValue(transport.OpisLadunku)) { - columns.dane.push(createLabelText('Opis ładunku: ', getOpisTransportuString(transport.OpisLadunku))); + columns.dane.push(createLabelText('Opis ładunku: ', translateMap(transport.OpisLadunku, TypLadunku))); if (transport.LadunekInny?._text === '1' && transport.OpisInnegoLadunku?._text) { columns.dane.push(createLabelText('Opis ładunku: ', 'Ładunek inny')); columns.dane.push(createLabelText('Opis innego ładunku: ', transport.OpisInnegoLadunku)); diff --git a/src/lib-public/generators/FA2/WarunkiTransakcji.ts b/src/lib-public/generators/FA2/WarunkiTransakcji.ts index a7270840..5ab84df9 100644 --- a/src/lib-public/generators/FA2/WarunkiTransakcji.ts +++ b/src/lib-public/generators/FA2/WarunkiTransakcji.ts @@ -25,11 +25,11 @@ export function generateWarunkiTransakcji(warunkiTransakcji: WarunkiTransakcji | const zamowienia: Zamowienia[] = getTable(warunkiTransakcji?.Zamowienia); const partiaTowaru: FP[] = getTable(warunkiTransakcji?.NrPartiiTowaru); const definedHeaderUmowy: HeaderDefine[] = [ - { name: 'DataUmowy', title: 'Data umowy', format: FormatTyp.Default }, + { name: 'DataUmowy', title: 'Data umowy', format: FormatTyp.Date }, { name: 'NrUmowy', title: 'Numer umowy', format: FormatTyp.Default }, ]; const definedHeaderZamowienia: HeaderDefine[] = [ - { name: 'DataZamowienia', title: 'Data zamówienia', format: FormatTyp.Default }, + { name: 'DataZamowienia', title: 'Data zamówienia', format: FormatTyp.Date }, { name: 'NrZamowienia', title: 'Numer zamówienia', format: FormatTyp.Default }, ]; const definedHeaderPartiaTowaru: HeaderDefine[] = [ @@ -102,4 +102,4 @@ export function generateWarunkiTransakcji(warunkiTransakcji: WarunkiTransakcji | } return createSection(table, true); -} +} \ No newline at end of file diff --git a/src/lib-public/generators/FA2/Wiersze.spec.ts b/src/lib-public/generators/FA2/Wiersze.spec.ts index 664896aa..03329297 100644 --- a/src/lib-public/generators/FA2/Wiersze.spec.ts +++ b/src/lib-public/generators/FA2/Wiersze.spec.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa } from '../../types/fa2.types'; import { generateWiersze } from './Wiersze'; @@ -37,7 +37,7 @@ describe(generateWiersze.name, () => { RodzajFaktury: { _text: TRodzajFaktury.VAT }, } as any; - const setupBasicMocks = (p15Value: string, rodzajFakturyValue: string, currencyValue: string = 'PLN') => { + const setupBasicMocks = (p15Value: string, rodzajFakturyValue: string, currencyValue = 'PLN') => { vi.mocked(PDFFunctions.getTable).mockReturnValue([ { NrWierszaFa: { _text: '1' }, @@ -52,9 +52,15 @@ describe(generateWiersze.name, () => { }); vi.mocked(PDFFunctions.getValue).mockImplementation((field: any) => { - if (field === mockFaVat.P_15 || field?._text === p15Value) return p15Value; - if (field === mockFaVat.RodzajFaktury || field?._text === rodzajFakturyValue) return rodzajFakturyValue; - if (field === mockFaVat.KodWaluty || field?._text === currencyValue) return currencyValue; + if (field === mockFaVat.P_15 || field?._text === p15Value) { + return p15Value; + } + if (field === mockFaVat.RodzajFaktury || field?._text === rodzajFakturyValue) { + return rodzajFakturyValue; + } + if (field === mockFaVat.KodWaluty || field?._text === currencyValue) { + return currencyValue; + } return undefined; }); @@ -99,40 +105,6 @@ describe(generateWiersze.name, () => { }); describe('price text formatting', () => { - it('should display "netto" when P_11 is in fieldsWithValue', () => { - setupBasicMocks('200', TRodzajFaktury.VAT); - - generateWiersze(mockFaVat); - - expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('netto'), [ - FormatTyp.Label, - FormatTyp.MarginBottom8, - ]); - }); - - it('should display "brutto" when P_11 is not in fieldsWithValue', () => { - vi.mocked(PDFFunctions.getTable).mockReturnValue([ - { NrWierszaFa: { _text: '1' }, P_12: { _text: '23' } }, - ] as any); - - vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ - content: { table: {} } as any, - fieldsWithValue: ['P_11A', 'P_7'], - }); - - vi.mocked(PDFFunctions.getValue).mockReturnValue('0'); - vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted text' as any); - vi.mocked(PDFFunctions.createHeader).mockReturnValue(['Header'] as any); - vi.mocked(PDFFunctions.createSection).mockReturnValue({ section: 'content' } as any); - - generateWiersze(mockFaVat); - - expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('brutto'), [ - FormatTyp.Label, - FormatTyp.MarginBottom8, - ]); - }); - it('should include currency code in price text', () => { setupBasicMocks('200', TRodzajFaktury.VAT); @@ -210,6 +182,7 @@ describe(generateWiersze.name, () => { generateWiersze(mockFaVat); const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0]; + expect(sectionCall).not.toContain('\n'); expect(sectionCall.filter((item: any) => item?.table === 'table2')).toHaveLength(0); }); @@ -304,9 +277,15 @@ describe(generateWiersze.name, () => { }); vi.mocked(PDFFunctions.getValue).mockImplementation((field: any) => { - if (field === mockFaVat.P_15) return '200'; - if (field === mockFaVat.RodzajFaktury) return TRodzajFaktury.VAT; - if (field === mockFaVat.KodWaluty) return undefined; + if (field === mockFaVat.P_15) { + return '200'; + } + if (field === mockFaVat.RodzajFaktury) { + return TRodzajFaktury.VAT; + } + if (field === mockFaVat.KodWaluty) { + return undefined; + } return undefined; }); @@ -335,14 +314,12 @@ describe(generateWiersze.name, () => { generateWiersze(mockFaVat); - expect(PDFFunctions.createSection).toHaveBeenCalledWith( - expect.arrayContaining(['Header', 'formatted text']), - true - ); + expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.arrayContaining(['Header']), true); }); it('should return the result of createSection', () => { const mockSection = { section: 'test' }; + setupBasicMocks('200', TRodzajFaktury.VAT); vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); diff --git a/src/lib-public/generators/FA2/Wiersze.ts b/src/lib-public/generators/FA2/Wiersze.ts index 97dc36d8..49c48561 100644 --- a/src/lib-public/generators/FA2/Wiersze.ts +++ b/src/lib-public/generators/FA2/Wiersze.ts @@ -1,4 +1,4 @@ -import { Content, ContentText } from 'pdfmake/interfaces'; +import { Content } from 'pdfmake/interfaces'; import { createHeader, createLabelTextArray, @@ -11,7 +11,7 @@ import { getValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa, FP } from '../../types/fa2.types'; import FormatTyp, { Position } from '../../../shared/enums/common.enum'; import { TableWithFields } from '../../types/fa1-additional-types'; @@ -68,10 +68,10 @@ export function generateWiersze(faVat: Fa): Content { { name: 'PKWiU', title: 'PKWiU', format: FormatTyp.Default, width: 'auto' }, { name: 'CN', title: 'CN', format: FormatTyp.Default, width: 'auto' }, { name: 'PKOB', title: 'PKOB', format: FormatTyp.Default, width: 'auto' }, - { name: 'KwotaAkcyzy', title: 'KwotaAkcyzy', format: FormatTyp.Default, width: 'auto' }, + { name: 'KwotaAkcyzy', title: 'Kwota podatku akcyzowego', format: FormatTyp.Default, width: 'auto' }, { name: 'GTU', title: 'GTU', format: FormatTyp.Default, width: 'auto' }, { name: 'Procedura', title: 'Procedura', format: FormatTyp.Default, width: '*' }, - { name: 'P_6A', title: 'Data dostawy / wykonania', format: FormatTyp.Default, width: 'auto' }, + { name: 'P_6A', title: 'Data dostawy / wykonania', format: FormatTyp.Date, width: 'auto' }, { name: 'Indeks', title: 'Indeks', format: FormatTyp.Default, width: 'auto' }, ]; let content: TableWithFields = getContentTable<(typeof faWiersze)[0]>( @@ -79,10 +79,10 @@ export function generateWiersze(faVat: Fa): Content { faWiersze, 'auto' ); - const ceny: string | ContentText = formatText( - `Faktura wystawiona w cenach ${content.fieldsWithValue.includes('P_11') ? 'netto' : 'brutto'} w walucie ${faVat.KodWaluty?._text}`, - [FormatTyp.Label, FormatTyp.MarginBottom8] - ); + const ceny = formatText(`Faktura wystawiona w walucie ${faVat.KodWaluty?._text}`, [ + FormatTyp.Label, + FormatTyp.MarginBottom8, + ]); const p_15: string | number | undefined = getValue(faVat.P_15); let opis: Content = ''; diff --git a/src/lib-public/generators/FA2/Zamowienia.spec.ts b/src/lib-public/generators/FA2/Zamowienia.spec.ts index 048363c4..76c1cb16 100644 --- a/src/lib-public/generators/FA2/Zamowienia.spec.ts +++ b/src/lib-public/generators/FA2/Zamowienia.spec.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Zamowienie } from '../../types/fa2.types'; import { generateZamowienie } from './Zamowienie'; import { ZamowienieKorekta } from '../../enums/invoice.enums'; @@ -13,7 +13,7 @@ vi.mock('../../../shared/PDF-functions', () => ({ getContentTable: vi.fn(), getTable: vi.fn(), getValue: vi.fn(), - getTStawkaPodatku: vi.fn() + getTStawkaPodatku: vi.fn(), })); describe(generateZamowienie.name, () => { @@ -100,6 +100,7 @@ describe(generateZamowienie.name, () => { ); const tableCall = vi.mocked(PDFFunctions.getTable).mock.results[0].value; + expect(tableCall[0].NrWierszaZam._text).toBe('1'); }); @@ -121,6 +122,7 @@ describe(generateZamowienie.name, () => { const calls = vi.mocked(PDFFunctions.getContentTable).mock.calls; const header = calls[0][0]; const cenaNetto = header.find((h: any) => h.name === 'P_9AZ'); + expect(cenaNetto?.format).toBe(FormatTyp.CurrencyAbs); }); @@ -141,6 +143,7 @@ describe(generateZamowienie.name, () => { const calls = vi.mocked(PDFFunctions.getContentTable).mock.calls; const header = calls[0][0]; const cenaNetto = header.find((h: any) => h.name === 'P_9AZ'); + expect(cenaNetto?.format).toBe(FormatTyp.Currency); }); }); @@ -204,7 +207,7 @@ describe(generateZamowienie.name, () => { ); expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([ - { value: 'Otrzymana kwota zapłaty (zaliczki): ', formatTyp: FormatTyp.LabelGreater }, + { value: 'Kwota zapłaty (zaliczki) dokumentowana fakturą: ', formatTyp: FormatTyp.LabelGreater }, { value: '100', formatTyp: FormatTyp.CurrencyGreater }, ]); }); @@ -229,32 +232,16 @@ describe(generateZamowienie.name, () => { ); expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([ - { value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater }, + { + value: 'Korekta kwoty zapłaty (zaliczki) dokumentowana fakturą: ', + formatTyp: FormatTyp.LabelGreater, + }, { value: '150', formatTyp: FormatTyp.CurrencyGreater }, ]); }); }); describe('price text', () => { - it('should display "netto" when P_11 is in fieldsWithValue', () => { - vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ - content: { table: {} } as any, - fieldsWithValue: ['P_11', 'P_7Z'], - }); - - const result = generateZamowienie( - mockOrderData, - ZamowienieKorekta.BeforeCorrection, - '100', - TRodzajFaktury.ZAL, - 'EUR' - ); - - const stack = (result[0] as any).stack; - expect(stack[1]).toContain('netto'); - expect(stack[1]).toContain('EUR'); - }); - it('should display "brutto" when P_11 is not in fieldsWithValue', () => { vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ content: { table: {} } as any, @@ -270,7 +257,7 @@ describe(generateZamowienie.name, () => { ); const stack = (result[0] as any).stack; - expect(stack[1]).toContain('brutto'); + expect(stack[1]).toContain('USD'); }); }); diff --git a/src/lib-public/generators/FA2/Zamowienie.ts b/src/lib-public/generators/FA2/Zamowienie.ts index 72845306..0c2c62a8 100644 --- a/src/lib-public/generators/FA2/Zamowienie.ts +++ b/src/lib-public/generators/FA2/Zamowienie.ts @@ -9,7 +9,7 @@ import { getValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { FP, Zamowienie } from '../../types/fa2.types'; import FormatTyp, { Position } from '../../../shared/enums/common.enum'; import { TableWithFields } from '../../types/fa1-additional-types'; @@ -103,13 +103,13 @@ export function generateZamowienie( table.push(content.content); } } - const ceny = `Faktura wystawiona w cenach ${content.fieldsWithValue.includes('P_11') ? 'netto' : 'brutto'} w walucie ${KodWaluty}`; + const ceny = `Faktura wystawiona w walucie ${KodWaluty}`; let opis: Content = ''; if (Number(p_15) > 0 && rodzajFaktury == TRodzajFaktury.ZAL) { opis = { stack: createLabelTextArray([ - { value: 'Otrzymana kwota zapłaty (zaliczki): ', formatTyp: FormatTyp.LabelGreater }, + { value: 'Kwota zapłaty (zaliczki) dokumentowana fakturą: ', formatTyp: FormatTyp.LabelGreater }, { value: p_15, formatTyp: FormatTyp.CurrencyGreater }, ]), alignment: Position.RIGHT, @@ -122,7 +122,10 @@ export function generateZamowienie( ) { opis = { stack: createLabelTextArray([ - { value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater }, + { + value: 'Korekta kwoty zapłaty (zaliczki) dokumentowana fakturą: ', + formatTyp: FormatTyp.LabelGreater, + }, { value: p_15, formatTyp: FormatTyp.CurrencyGreater }, ]), alignment: Position.RIGHT, diff --git a/src/lib-public/generators/FA3/Platnosc.spec.ts b/src/lib-public/generators/FA3/Platnosc.spec.ts index b7c98574..010bc546 100644 --- a/src/lib-public/generators/FA3/Platnosc.spec.ts +++ b/src/lib-public/generators/FA3/Platnosc.spec.ts @@ -10,8 +10,9 @@ import { getContentTable, hasValue, } from '../../../shared/PDF-functions'; -import { getFormaPlatnosciString } from '../../../shared/generators/common/functions'; import { generujRachunekBankowy } from './RachunekBankowy'; +import { translateMap } from '@shared/generators/common/functions'; +import { FormaPlatnosci } from '@shared/consts/FA.const'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]), @@ -24,10 +25,8 @@ vi.mock('../../../shared/PDF-functions', () => ({ getContentTable: vi.fn(() => ({ content: [{ text: 'mockTable' }] })), hasValue: vi.fn((v: any) => !!v), getValue: vi.fn((v: any) => !!v), -})); - -vi.mock('../../../shared/generators/common/functions', () => ({ - getFormaPlatnosciString: vi.fn((v: any) => `Forma: ${v}`), + formatText: vi.fn((text: string, style?: any): Content => ({ text, style })), + makeBreakable: vi.fn(), })); vi.mock('./RachunekBankowy', () => ({ @@ -76,7 +75,7 @@ describe(generatePlatnosc.name, () => { const result = generatePlatnosc(platnosc as Platnosc); expect(hasValue).toHaveBeenCalledWith({ _text: 'Karta' }); - expect(getFormaPlatnosciString).toHaveBeenCalledWith({ _text: 'Karta' }); + expect(translateMap).toHaveBeenCalledWith({ _text: 'Karta' }, FormaPlatnosci); }); it('generuje tabelę zapłaty częściowej i terminów płatności', () => { diff --git a/src/lib-public/generators/FA3/Platnosc.ts b/src/lib-public/generators/FA3/Platnosc.ts index 2f9c8121..6a427c2e 100644 --- a/src/lib-public/generators/FA3/Platnosc.ts +++ b/src/lib-public/generators/FA3/Platnosc.ts @@ -1,4 +1,4 @@ -import { Content, ContentText } from 'pdfmake/interfaces'; +import { Content, ContentTable, ContentText, TableCell } from 'pdfmake/interfaces'; import { createHeader, createLabelText, @@ -9,12 +9,14 @@ import { getTable, getValue, hasValue, + makeBreakable, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; import { Platnosc } from '../../types/fa3.types'; -import { getFormaPlatnosciString } from '../../../shared/generators/common/functions'; import { generujRachunekBankowy } from './RachunekBankowy'; import FormatTyp from '../../../shared/enums/common.enum'; +import { translateMap } from '@shared/generators/common/functions'; +import { DEFAULT_TABLE_LAYOUT, FormaPlatnosci } from '@shared/consts/FA.const'; export function generatePlatnosc(platnosc: Platnosc | undefined): Content { if (!platnosc) { @@ -26,24 +28,14 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { { name: 'Termin', title: 'Termin płatności', - format: FormatTyp.Default, + format: FormatTyp.Date, }, ]; if (terminPlatnosci.some((termin) => termin.TerminOpis)) { - zaplataCzesciowaHeader.push({ name: 'TerminOpis', title: 'Opis płatności', format: FormatTyp.Default }); + zaplataCzesciowaHeader.push({ name: 'TerminOpis', title: 'Opis płatności', format: FormatTyp.Date }); } - const zaplataCzesciowaNaglowek: HeaderDefine[] = [ - { - name: 'DataZaplatyCzesciowej', - title: 'Data zapłaty częściowej', - format: FormatTyp.Default, - }, - { name: 'KwotaZaplatyCzesciowej', title: 'Kwota zapłaty częściowej', format: FormatTyp.Currency }, - { name: 'FormaPlatnosci', title: 'Forma płatności', format: FormatTyp.FormOfPayment }, - ]; - const table: Content[] = [generateLine(), ...createHeader('Płatność')]; // TODO: Add to FA2 and FA1? (KSEF20-15289) @@ -68,7 +60,7 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { } if (hasValue(platnosc.FormaPlatnosci)) { - table.push(createLabelText('Forma płatności: ', getFormaPlatnosciString(platnosc.FormaPlatnosci))); + table.push(createLabelText('Forma płatności: ', translateMap(platnosc.FormaPlatnosci, FormaPlatnosci))); } else { if (platnosc.OpisPlatnosci?._text) { table.push(createLabelText('Forma płatności: ', 'Płatność inna')); @@ -77,13 +69,46 @@ export function generatePlatnosc(platnosc: Platnosc | undefined): Content { } const zaplataCzesciowa = getTable(platnosc.ZaplataCzesciowa); - const tableZaplataCzesciowa = getContentTable<(typeof zaplataCzesciowa)[0]>( - zaplataCzesciowaNaglowek, - zaplataCzesciowa, - '*', - undefined, - 20 - ); + const tableZaplataCzesciowa = prepareContentTable(); + + function prepareContentTable(): { content: ContentTable } { + const definedHeader: Content[] = [ + formatText(makeBreakable('Data zapłaty częściowej', 20), FormatTyp.GrayBoldTitle), + formatText(makeBreakable('Kwota zapłaty częściowej', 20), FormatTyp.GrayBoldTitle), + formatText(makeBreakable('Forma płatności', 20), FormatTyp.GrayBoldTitle), + ]; + + const defineTableBody: TableCell[] = zaplataCzesciowa.map((item) => { + const value = []; + + value.push( + formatText(getValue(item.DataZaplatyCzesciowej), FormatTyp.Date), + formatText(getValue(item.KwotaZaplatyCzesciowej), FormatTyp.Currency) + ); + + if (item.PlatnoscInna) { + value.push(formatText(makeBreakable(getValue(item.OpisPlatnosci) ?? '', 20), FormatTyp.Default)); + } else { + value.push(formatText(getValue(item.FormaPlatnosci), FormatTyp.FormOfPayment)); + } + + return value; + }); + + return { + content: { + table: { + headerRows: 1, + keepWithHeaderRows: 1, + widths: ['*', '*', '*'], + body: [definedHeader, ...defineTableBody] as TableCell[][], + }, + margin: [0, 0, 0, 8], + layout: DEFAULT_TABLE_LAYOUT, + }, + }; + } + const terminPatnosciContent = terminPlatnosci.map((platnosc) => { if (!terminPlatnosci.some((termin) => termin.TerminOpis)) { return platnosc; diff --git a/src/lib-public/generators/FA3/Podmiot1.ts b/src/lib-public/generators/FA3/Podmiot1.ts index 5041dfb0..d2287adf 100644 --- a/src/lib-public/generators/FA3/Podmiot1.ts +++ b/src/lib-public/generators/FA3/Podmiot1.ts @@ -5,7 +5,7 @@ import { Podmiot1 } from '../../types/fa3.types'; import { generateAdres } from './Adres'; import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; -import { TAXPAYER_STATUS } from '../../../shared/consts/const'; +import { TAXPAYER_STATUS } from '../../../shared/consts/FA.const'; export function generatePodmiot1(podmiot1: Podmiot1): Content[] { const result: Content[] = createHeader('Sprzedawca'); diff --git a/src/lib-public/generators/FA3/Podmiot1Podmiot1K.ts b/src/lib-public/generators/FA3/Podmiot1Podmiot1K.ts index 6a03b5c0..eede0d94 100644 --- a/src/lib-public/generators/FA3/Podmiot1Podmiot1K.ts +++ b/src/lib-public/generators/FA3/Podmiot1Podmiot1K.ts @@ -13,7 +13,7 @@ import { Podmiot1, Podmiot1K } from '../../types/fa2.types'; import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; import { generateAdres } from './Adres'; -import { TAXPAYER_STATUS } from '../../../shared/consts/const'; +import { TAXPAYER_STATUS } from '../../../shared/consts/FA.const'; export function generatePodmiot1Podmiot1K(podmiot1: Podmiot1, podmiot1K: Podmiot1K): Content[] { const result: Content[] = createHeader('Sprzedawca'); diff --git a/src/lib-public/generators/FA3/Podmiot2.ts b/src/lib-public/generators/FA3/Podmiot2.ts index fde41422..8c3cf12d 100644 --- a/src/lib-public/generators/FA3/Podmiot2.ts +++ b/src/lib-public/generators/FA3/Podmiot2.ts @@ -1,5 +1,5 @@ import { Content } from 'pdfmake/interfaces'; -import { createHeader, createLabelText, formatText, getTable } from '../../../shared/PDF-functions'; +import { createHeader, createLabelText, formatText, getValue } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { Podmiot2 } from '../../types/fa3.types'; import { generateAdres } from './Adres'; @@ -37,23 +37,29 @@ export function generatePodmiot2(podmiot2: Podmiot2): Content[] { ...generateDaneKontaktowe(podmiot2.DaneKontaktowe ?? []), createLabelText('Numer klienta: ', podmiot2.NrKlienta) ); + } - const daneKontaktowe = getTable(podmiot2.DaneKontaktowe); + if (podmiot2?.JST) { + result.push( + createLabelText( + 'Faktura dotyczy jednostki podrzędnej JST: ', + getValue(podmiot2?.JST)?.toString().trim() === '1' ? 'TAK' : 'NIE', + FormatTyp.Default, + { marginTop: 8 } + ) + ); + } - if (daneKontaktowe.length) { - result.push( - createLabelText( - 'Faktura dotyczy jednostki podrzędnej JST: ', - daneKontaktowe[0].JST?._text === '1' ? 'TAK' : 'NIE' - ) - ); - result.push( - createLabelText( - 'Faktura dotyczy członka grupy GV: ', - daneKontaktowe[0].GV?._text === '1' ? 'TAK' : 'NIE' - ) - ); - } + if (podmiot2?.GV) { + result.push( + createLabelText( + 'Faktura dotyczy członka grupy GV: ', + getValue(podmiot2?.GV)?.toString().trim() === '1' ? 'TAK' : 'NIE', + FormatTyp.Default, + podmiot2?.JST ? {} : { marginTop: 8 } + ) + ); } + return result; } diff --git a/src/lib-public/generators/FA3/Podmiot3.spec.ts b/src/lib-public/generators/FA3/Podmiot3.spec.ts index f2bcc295..1a5ac7cf 100644 --- a/src/lib-public/generators/FA3/Podmiot3.spec.ts +++ b/src/lib-public/generators/FA3/Podmiot3.spec.ts @@ -5,17 +5,14 @@ import * as PDFFunctions from '../../../shared/PDF-functions'; import * as AdresModule from './Adres'; import * as DaneIdModule from './PodmiotDaneIdentyfikacyjneTPodmiot3Dto'; import * as DaneKontaktoweModule from './PodmiotDaneKontaktowe'; -import * as CommonFunctions from '../../../shared/generators/common/functions'; vi.mock('../../../shared/PDF-functions'); vi.mock('./Adres'); vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot3Dto'); vi.mock('./PodmiotDaneKontaktowe'); -vi.mock('../../../shared/generators/common/functions'); describe(generatePodmiot3.name, () => { beforeEach(() => { - vi.clearAllMocks(); vi.mocked(PDFFunctions.createHeader).mockImplementation((text) => [{ text }]); vi.mocked(PDFFunctions.createLabelText).mockImplementation((label, value) => [ { text: `${label}${value ?? ''}` }, @@ -29,7 +26,6 @@ describe(generatePodmiot3.name, () => { { text: 'mockDaneIdentyfikacyjne' }, ]); vi.mocked(DaneKontaktoweModule.generateDaneKontaktowe).mockReturnValue([{ text: 'mockDaneKontaktowe' }]); - vi.mocked(CommonFunctions.getRolaString).mockReturnValue('mockRola'); }); it('generates minimal podmiot3 structure', () => { @@ -119,9 +115,6 @@ describe(generatePodmiot3.name, () => { expect( flatColumn1.some((c) => typeof c.text === 'string' && c.text.includes('Identyfikator nabywcy: ID1')) ).toBe(true); - expect(flatColumn1.some((c) => typeof c.text === 'string' && c.text.includes('Rola: mockRola'))).toBe( - true - ); expect( flatColumn1.some((c) => typeof c.text === 'string' && c.text.includes('Rola inna: opisRoli')) ).toBe(true); diff --git a/src/lib-public/generators/FA3/Podmiot3.ts b/src/lib-public/generators/FA3/Podmiot3.ts index 4ec7e812..d92ac8ae 100644 --- a/src/lib-public/generators/FA3/Podmiot3.ts +++ b/src/lib-public/generators/FA3/Podmiot3.ts @@ -10,8 +10,9 @@ import FormatTyp from '../../../shared/enums/common.enum'; import { Podmiot3 } from '../../types/fa3.types'; import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot3Dto'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; -import { getRolaString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; import { generateAdres } from '../FA2/Adres'; +import { FA3RolaPodmiotu3 } from '../../../shared/consts/FA.const'; export function generatePodmiot3(podmiot: Podmiot3, index: number): Content[] { const result: Content[] = []; @@ -22,7 +23,7 @@ export function generatePodmiot3(podmiot: Podmiot3, index: number): Content[] { createLabelText('Identyfikator nabywcy: ', podmiot.IDNabywcy), createLabelText('Numer EORI: ', podmiot.NrEORI), ...generateDaneIdentyfikacyjneTPodmiot3Dto(podmiot.DaneIdentyfikacyjne), - createLabelText('Rola: ', getRolaString(podmiot.Rola, 3)), + createLabelText('Rola: ', translateMap(podmiot.Rola, FA3RolaPodmiotu3)), createLabelText('Rola inna: ', podmiot.OpisRoli), createLabelText('Udział: ', podmiot.Udzial, [FormatTyp.Percentage]), ]; diff --git a/src/lib-public/generators/FA3/Podmiot3Podmiot2k.spec.ts b/src/lib-public/generators/FA3/Podmiot3Podmiot2k.spec.ts index 81e7a19f..c07ca926 100644 --- a/src/lib-public/generators/FA3/Podmiot3Podmiot2k.spec.ts +++ b/src/lib-public/generators/FA3/Podmiot3Podmiot2k.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './Podmiot3Podmiot2k'; -import type {Content} from "pdfmake/interfaces"; +import { Content } from 'pdfmake/interfaces'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn((text: string) => [`header:${text}`]), @@ -11,10 +11,6 @@ vi.mock('../../../shared/PDF-functions', () => ({ generateLine: vi.fn((): Content[] => [{ line: true } as any]), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getRolaString: vi.fn((rola: any) => `rola:${rola}`), -})); - vi.mock('./PodmiotAdres', () => ({ generatePodmiotAdres: vi.fn((adres: any, label?: string) => [`adres:${label ?? ''}`]), })); @@ -30,6 +26,7 @@ vi.mock('./PodmiotDaneKontaktowe', () => ({ describe(generateDaneIdentyfikacyjneTPodmiot3Dto.name, () => { it('should return empty array when input is undefined', () => { const result = generateDaneIdentyfikacyjneTPodmiot3Dto(undefined, 0); + expect(result).toEqual([]); }); @@ -56,9 +53,11 @@ describe(generateDaneIdentyfikacyjneTPodmiot3Dto.name, () => { const result = generateDaneIdentyfikacyjneTPodmiot3Dto(podmiotDto as any, 0); - expect(result[0]).toEqual([{ - "line": true, - }]); + expect(result[0]).toEqual([ + { + line: true, + }, + ]); expect(result.some((r: any) => r.columns)).toBe(true); }); @@ -71,8 +70,10 @@ describe(generateDaneIdentyfikacyjneTPodmiot3Dto.name, () => { const result = generateDaneIdentyfikacyjneTPodmiot3Dto(podmiotDto as any, 1); - expect(result[0]).toEqual([{ - "line": true, - }]); + expect(result[0]).toEqual([ + { + line: true, + }, + ]); }); }); diff --git a/src/lib-public/generators/FA3/Podmiot3Podmiot2k.ts b/src/lib-public/generators/FA3/Podmiot3Podmiot2k.ts index 464d538e..5e73dd32 100644 --- a/src/lib-public/generators/FA3/Podmiot3Podmiot2k.ts +++ b/src/lib-public/generators/FA3/Podmiot3Podmiot2k.ts @@ -9,13 +9,14 @@ import { } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { Podmiot3Podmiot2KDto } from '../../types/fa2-additional-types'; -import { getRolaString } from '../../../shared/generators/common/functions'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjneTPodmiot2Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot2Dto'; import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; import { Podmiot1DaneKontaktowe, Podmiot3 } from '../../types/fa3.types'; import { Podmiot2K } from '../../types/fa2.types'; import { Adres } from '../../types/fa1.types'; +import { FA3RolaPodmiotu3 } from '../../../shared/consts/FA.const'; +import { translateMap } from '@shared/generators/common/functions'; export function generateDaneIdentyfikacyjneTPodmiot3Dto( podmiot2KDto: Podmiot3Podmiot2KDto | undefined, @@ -41,7 +42,7 @@ export function generateDaneIdentyfikacyjneTPodmiot3Dto( result.push( ...createHeader('Dane identyfikacyjne'), createLabelText('Numer EORI: ', podmiot1.NrEORI), - createLabelText('Rola: ', getRolaString(podmiot1.Rola, 3)), + createLabelText('Rola: ', translateMap(podmiot1.Rola, FA3RolaPodmiotu3)), createLabelText('Rola inna: ', podmiot1.OpisRoli), createLabelText('Udział: ', podmiot1.Udzial, FormatTyp.Percentage) ); diff --git a/src/lib-public/generators/FA3/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts b/src/lib-public/generators/FA3/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts index 6c9cc9b5..f32a20ad 100644 --- a/src/lib-public/generators/FA3/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts +++ b/src/lib-public/generators/FA3/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts @@ -44,7 +44,22 @@ describe(generateDaneIdentyfikacyjneTPodmiot2Dto.name, () => { expect(result.some((r: any) => r.text.includes('Numer VAT-UE'))).toBe(true); }); - it('adds KodKraju section when KodKraju is present', () => { + it('adds NrID section when KodKraju is not present', () => { + const data: DaneIdentyfikacyjneTPodmiot2Dto = { + ...baseData, + NrID: { _text: '999999' }, + }; + const result = generateDaneIdentyfikacyjneTPodmiot2Dto(data); + expect(createLabelTextArray).toHaveBeenCalledWith([ + { value: 'Identyfikator podatkowy inny: ', formatTyp: FormatTyp.Label }, + { value: '', formatTyp: FormatTyp.Value }, + { value: ' ' }, + { value: data.NrID, formatTyp: FormatTyp.Value }, + ]); + expect(result.some((r: any) => (r as any).text.includes('Identyfikator podatkowy inny'))).toBe(true); + }); + + it('adds NrID section when KodKraju is present', () => { const data: DaneIdentyfikacyjneTPodmiot2Dto = { ...baseData, KodKraju: { _text: 'DE' }, diff --git a/src/lib-public/generators/FA3/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts b/src/lib-public/generators/FA3/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts index a3dd45e4..18362dee 100644 --- a/src/lib-public/generators/FA3/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts +++ b/src/lib-public/generators/FA3/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts @@ -19,11 +19,11 @@ export function generateDaneIdentyfikacyjneTPodmiot2Dto( ]) ); } - if (daneIdentyfikacyjne.KodKraju?._text) { + if (daneIdentyfikacyjne.NrID?._text) { result.push( createLabelTextArray([ { value: 'Identyfikator podatkowy inny: ', formatTyp: FormatTyp.Label }, - { value: daneIdentyfikacyjne.KodKraju, formatTyp: FormatTyp.Value }, + { value: daneIdentyfikacyjne.KodKraju || '', formatTyp: FormatTyp.Value }, { value: ' ' }, { value: daneIdentyfikacyjne.NrID, formatTyp: FormatTyp.Value }, ]) diff --git a/src/lib-public/generators/FA3/PodmiotDaneKontaktowe.ts b/src/lib-public/generators/FA3/PodmiotDaneKontaktowe.ts index 2b261451..1b292293 100644 --- a/src/lib-public/generators/FA3/PodmiotDaneKontaktowe.ts +++ b/src/lib-public/generators/FA3/PodmiotDaneKontaktowe.ts @@ -4,9 +4,13 @@ import { Podmiot1DaneKontaktowe } from '../../types/fa3.types'; export function generateDaneKontaktowe(daneKontaktowe: Podmiot1DaneKontaktowe[]): Content[] { return getTable(daneKontaktowe)?.map((daneKontaktowe) => { - return [ - createLabelText('E-mail: ', daneKontaktowe.Email), - createLabelText('Tel.: ', daneKontaktowe.Telefon), - ]; + if (daneKontaktowe?.Email || daneKontaktowe?.Telefon) { + return [ + createLabelText('E-mail: ', daneKontaktowe.Email), + createLabelText('Tel.: ', daneKontaktowe.Telefon), + ]; + } else { + return '-'; + } }); } diff --git a/src/lib-public/generators/FA3/PodmiotUpowazniony.spec.ts b/src/lib-public/generators/FA3/PodmiotUpowazniony.spec.ts index a84f0272..f4976dea 100644 --- a/src/lib-public/generators/FA3/PodmiotUpowazniony.spec.ts +++ b/src/lib-public/generators/FA3/PodmiotUpowazniony.spec.ts @@ -6,7 +6,8 @@ import { createHeader, createLabelText, hasValue } from '../../../shared/PDF-fun import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; import { generatePodmiotUpowaznionyDaneKontaktowe } from './PodmiotUpowaznionyDaneKontaktowe'; -import { getRolaUpowaznionegoString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; +import { TRolaPodmiotuUpowaznionegoFA3 } from '@shared/consts/FA.const'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn(() => [{ text: 'Header: Podmiot upoważniony' }]), @@ -26,10 +27,6 @@ vi.mock('./PodmiotUpowaznionyDaneKontaktowe', () => ({ generatePodmiotUpowaznionyDaneKontaktowe: vi.fn(() => [{ text: 'Kontakt content' }]), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getRolaUpowaznionegoString: vi.fn(() => 'Agent celny'), -})); - describe(generatePodmiotUpowazniony.name, () => { const baseData: PodmiotUpowazniony = { RolaPU: { _text: '1' }, @@ -46,26 +43,29 @@ describe(generatePodmiotUpowazniony.name, () => { it('returns empty array when podmiotUpowazniony is undefined', () => { const result = generatePodmiotUpowazniony(undefined); + expect(result).toEqual([]); expect(createHeader).not.toHaveBeenCalled(); }); it('always starts with header', () => { const result = generatePodmiotUpowazniony(baseData); + expect(createHeader).toHaveBeenCalledWith('Podmiot upoważniony'); expect(result[0]).toEqual({ text: 'Header: Podmiot upoważniony' }); }); it('adds Rola when RolaPU has value', () => { const result = generatePodmiotUpowazniony(baseData); + expect(hasValue).toHaveBeenCalledWith(baseData.RolaPU); - expect(getRolaUpowaznionegoString).toHaveBeenCalledWith(baseData.RolaPU, 3); - expect(createLabelText).toHaveBeenCalledWith('Rola: ', 'Agent celny'); - expect(result.some((r) => (r as any).text?.includes('Rola: Agent celny'))).toBe(true); + expect(translateMap).toHaveBeenCalledWith(baseData.RolaPU, TRolaPodmiotuUpowaznionegoFA3); + expect(result.some((r) => (r as any).text?.includes('Rola: Organ egzekucyjny'))).toBe(true); }); it('adds EORI when NrEORI has value', () => { const result = generatePodmiotUpowazniony(baseData); + expect(hasValue).toHaveBeenCalledWith(baseData.NrEORI); expect(createLabelText).toHaveBeenCalledWith('Numer EORI: ', baseData.NrEORI); expect(result.some((r) => (r as any).text?.includes('Numer EORI: EORI123'))).toBe(true); @@ -73,24 +73,29 @@ describe(generatePodmiotUpowazniony.name, () => { it('adds DaneIdentyfikacyjne when present', () => { const result = generatePodmiotUpowazniony(baseData); + expect(generateDaneIdentyfikacyjneTPodmiot1Dto).toHaveBeenCalledWith(baseData.DaneIdentyfikacyjne); expect(result.flat().some((r) => (r as any).text === 'Dane identyfikacyjne content')).toBe(true); }); it('adds Adres, AdresKoresp, and DaneKontaktowe sections', () => { const result = generatePodmiotUpowazniony(baseData); + expect(generatePodmiotAdres).toHaveBeenCalledWith(baseData.Adres); expect(generatePodmiotAdres).toHaveBeenCalledWith(baseData.AdresKoresp, 'Adres korespondencyjny'); expect(generatePodmiotUpowaznionyDaneKontaktowe).toHaveBeenCalledWith(baseData.DaneKontaktowe); const flattened = result.flat(); + expect(flattened.some((r) => (r as any).text === 'Adres content')).toBe(true); expect(flattened.some((r) => (r as any).text === 'Kontakt content')).toBe(true); }); it('handles missing optional fields gracefully', () => { const data: PodmiotUpowazniony = { Adres: { miejscowosc: 'Gdańsk' } } as any; + (hasValue as any).mockReturnValue(false); const result = generatePodmiotUpowazniony(data); + expect(result[0]).toEqual({ text: 'Header: Podmiot upoważniony' }); expect(createLabelText).not.toHaveBeenCalled(); expect(generateDaneIdentyfikacyjneTPodmiot1Dto).not.toHaveBeenCalled(); diff --git a/src/lib-public/generators/FA3/PodmiotUpowazniony.ts b/src/lib-public/generators/FA3/PodmiotUpowazniony.ts index 70d8e8a6..8185f450 100644 --- a/src/lib-public/generators/FA3/PodmiotUpowazniony.ts +++ b/src/lib-public/generators/FA3/PodmiotUpowazniony.ts @@ -4,7 +4,8 @@ import { PodmiotUpowazniony } from '../../types/fa3.types'; import { generatePodmiotAdres } from './PodmiotAdres'; import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; import { generatePodmiotUpowaznionyDaneKontaktowe } from './PodmiotUpowaznionyDaneKontaktowe'; -import { getRolaUpowaznionegoString } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; +import { TRolaPodmiotuUpowaznionegoFA3 } from '@shared/consts/FA.const'; export function generatePodmiotUpowazniony(podmiotUpowazniony: PodmiotUpowazniony | undefined): Content[] { if (!podmiotUpowazniony) { @@ -13,7 +14,9 @@ export function generatePodmiotUpowazniony(podmiotUpowazniony: PodmiotUpowaznion const result: Content[] = createHeader('Podmiot upoważniony'); if (hasValue(podmiotUpowazniony.RolaPU)) { - result.push(createLabelText('Rola: ', getRolaUpowaznionegoString(podmiotUpowazniony.RolaPU, 3))); + result.push( + createLabelText('Rola: ', translateMap(podmiotUpowazniony.RolaPU, TRolaPodmiotuUpowaznionegoFA3)) + ); } if (hasValue(podmiotUpowazniony.NrEORI)) { result.push(createLabelText('Numer EORI: ', podmiotUpowazniony.NrEORI)); diff --git a/src/lib-public/generators/FA3/Rabat.ts b/src/lib-public/generators/FA3/Rabat.ts index 89e00fdf..21dab316 100644 --- a/src/lib-public/generators/FA3/Rabat.ts +++ b/src/lib-public/generators/FA3/Rabat.ts @@ -16,7 +16,7 @@ export function generateRabat(invoice: Fa): Content[] { const faRows = getTable(invoice!.FaWiersz); const result: Content[] = []; const definedHeader: HeaderDefine[] = [ - { name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default }, + { name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default, width: 'auto' }, { name: 'P_7', title: 'Nazwa towaru lub usługi', format: FormatTyp.Default }, { name: 'P_8B', title: 'Ilość', format: FormatTyp.Default }, { name: 'P_8A', title: 'Miara', format: FormatTyp.Default }, diff --git a/src/lib-public/generators/FA3/RachunekBankowy.spec.ts b/src/lib-public/generators/FA3/RachunekBankowy.spec.ts index 6a7a918e..fdaeb375 100644 --- a/src/lib-public/generators/FA3/RachunekBankowy.spec.ts +++ b/src/lib-public/generators/FA3/RachunekBankowy.spec.ts @@ -4,7 +4,6 @@ import * as PDFFunctions from '../../../shared/PDF-functions'; import { makeBreakable } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { RachunekBankowy } from '../../types/fa3.types'; -import * as CommonFunctions from '../../../shared/generators/common/functions'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn(), @@ -15,10 +14,6 @@ vi.mock('../../../shared/PDF-functions', () => ({ hasValue: (val: any) => val?._text || '', })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getTypRachunkowWlasnych: vi.fn(), -})); - describe(generujRachunekBankowy.name, () => { beforeEach(() => { vi.clearAllMocks(); @@ -36,7 +31,6 @@ describe(generujRachunekBankowy.name, () => { vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any); vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any); vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted' as any); - vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak'); }); describe('when accounts is undefined or empty', () => { @@ -92,12 +86,9 @@ describe(generujRachunekBankowy.name, () => { }); it('should format "Rachunek własny banku" field', () => { - vi.mocked(CommonFunctions.getTypRachunkowWlasnych).mockReturnValue('Tak'); - generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); expect(PDFFunctions.formatText).toHaveBeenCalledWith('Rachunek własny banku', FormatTyp.GrayBoldTitle); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku); expect(PDFFunctions.formatText).toHaveBeenCalledWith( mockAccount.NazwaBanku?._text ? makeBreakable('Bank', 20) : 'Tak', FormatTyp.Default @@ -131,7 +122,7 @@ describe(generujRachunekBankowy.name, () => { const tableContent = sectionCall[0][1]; expect(tableContent).toHaveProperty('table'); - expect(tableContent.table).toHaveProperty('widths', ['*', 'auto']); + expect(tableContent.table).toHaveProperty('widths', ['auto', '*']); }); it('should create table structure with unbreakable property', () => { @@ -218,10 +209,6 @@ describe(generujRachunekBankowy.name, () => { } as any; generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy'); - - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledTimes(2); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(mockAccount.RachunekWlasnyBanku); - expect(CommonFunctions.getTypRachunkowWlasnych).toHaveBeenCalledWith(account2.RachunekWlasnyBanku); }); it('should format all fields for all accounts', () => { diff --git a/src/lib-public/generators/FA3/RachunekBankowy.ts b/src/lib-public/generators/FA3/RachunekBankowy.ts index 67ac3215..979a4f5f 100644 --- a/src/lib-public/generators/FA3/RachunekBankowy.ts +++ b/src/lib-public/generators/FA3/RachunekBankowy.ts @@ -9,7 +9,8 @@ import { } from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { RachunekBankowy } from '../../types/fa3.types'; -import { getTypRachunkowWlasnych } from '../../../shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; +import { TypRachunkowWlasnych } from '@shared/consts/FA.const'; export const generujRachunekBankowy = (accounts?: RachunekBankowy[], title?: string): Content[] => { const result: Content[] = []; @@ -35,7 +36,10 @@ export const generujRachunekBankowy = (accounts?: RachunekBankowy[], title?: str ]); table.push([ formatText('Rachunek własny banku', FormatTyp.GrayBoldTitle), - formatText(makeBreakable(getTypRachunkowWlasnych(account.RachunekWlasnyBanku), 20), FormatTyp.Default), + formatText( + makeBreakable(translateMap(account.RachunekWlasnyBanku, TypRachunkowWlasnych), 20), + FormatTyp.Default + ), ]); table.push([ formatText('Nazwa banku', FormatTyp.GrayBoldTitle), @@ -61,7 +65,7 @@ export const generujRachunekBankowy = (accounts?: RachunekBankowy[], title?: str unbreakable: true, table: { body: table, - widths: ['*', 'auto'], + widths: ['auto', '*'], }, layout: { hLineWidth: () => 1, diff --git a/src/lib-public/generators/FA3/Szczegoly.spec.ts b/src/lib-public/generators/FA3/Szczegoly.spec.ts index 00957b93..eb64fdd7 100644 --- a/src/lib-public/generators/FA3/Szczegoly.spec.ts +++ b/src/lib-public/generators/FA3/Szczegoly.spec.ts @@ -1,8 +1,8 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { generateSzczegoly } from './Szczegoly'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa } from '../../types/fa3.types'; vi.mock('../../../shared/PDF-functions', () => ({ @@ -59,6 +59,10 @@ describe(generateSzczegoly.name, () => { vi.mocked(PDFFunctions.hasValue).mockReturnValue(false); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it('should call createHeader with "Szczegóły"', () => { generateSzczegoly(mockFaVat); @@ -67,6 +71,7 @@ describe(generateSzczegoly.name, () => { it('should call createSection and return result', () => { const mockSection = 'section'; + vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); const result = generateSzczegoly(mockFaVat); @@ -136,6 +141,13 @@ describe(generateSzczegoly.name, () => { describe('P_6 scope', () => { it('should generate P_6 scope when both P_6_Od and P_6_Do exist', () => { + vi.mocked(PDFFunctions.getValue).mockImplementation((item: any) => { + if (typeof item === 'object') { + return item._text; + } + + return item; + }); const data = { ...mockFaVat, OkresFa: { @@ -154,13 +166,20 @@ describe(generateSzczegoly.name, () => { { value: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', }, - { value: data.OkresFa.P_6_Od, formatTyp: FormatTyp.Value }, + { value: '01.01.2024', formatTyp: FormatTyp.Value }, { value: ' do ' }, - { value: data.OkresFa.P_6_Do, formatTyp: FormatTyp.Value }, + { value: '31.01.2024', formatTyp: FormatTyp.Value }, ]); }); it('should generate P_6 scope when only P_6_Od exists', () => { + vi.mocked(PDFFunctions.getValue).mockImplementation((item: any) => { + if (typeof item === 'object') { + return item._text; + } + + return item; + }); const data = { ...mockFaVat, OkresFa: { @@ -175,11 +194,18 @@ describe(generateSzczegoly.name, () => { expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', - data.OkresFa.P_6_Od + '01.01.2024' ); }); it('should generate P_6 scope when only P_6_Do exists', () => { + vi.mocked(PDFFunctions.getValue).mockImplementation((item: any) => { + if (typeof item === 'object') { + return item._text; + } + + return item; + }); const data = { ...mockFaVat, OkresFa: { @@ -194,7 +220,7 @@ describe(generateSzczegoly.name, () => { expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', - data.OkresFa.P_6_Do + '31.01.2024' ); }); @@ -208,62 +234,20 @@ describe(generateSzczegoly.name, () => { const p6Call = calls.find((call) => call[0].some((item: any) => item.value?.includes('Data dokonania lub zakończenia')) ); + expect(p6Call).toBeUndefined(); }); }); describe('ceny labels', () => { - it('should add "netto" label when P_11 exists in FaWiersz', () => { - vi.mocked(PDFFunctions.getTable).mockReturnValue([]); - - vi.mocked(PDFFunctions.hasColumnsValue).mockImplementation((column: string) => column === 'P_11'); - - generateSzczegoly(mockFaVat); - - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'netto'); - }); - - it('should add "netto" label when P_11 exists in ZamowienieWiersz', () => { - vi.mocked(PDFFunctions.getTable).mockReturnValue([]); - - vi.mocked(PDFFunctions.hasColumnsValue).mockImplementation((column: string) => column === 'P_11'); - - generateSzczegoly(mockFaVat); - - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'netto'); - }); - - it('should add "brutto" label when P_11 does not exist', () => { - vi.mocked(PDFFunctions.getTable).mockReturnValue([]); - vi.mocked(PDFFunctions.hasColumnsValue).mockReturnValue(false); - - generateSzczegoly(mockFaVat); - - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Faktura wystawiona w cenach: ', 'brutto'); - }); - it('should add currency code label', () => { vi.mocked(PDFFunctions.getTable).mockReturnValue([]); + vi.mocked(PDFFunctions.hasValue).mockReturnValue(true); generateSzczegoly(mockFaVat); expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Kod waluty: ', mockFaVat.KodWaluty); }); - - it('should not add ceny labels when FaWiersz or ZamowienieWiersz exist', () => { - vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === mockFaVat.FaWiersz) return [{}]; - return []; - }); - - vi.mocked(PDFFunctions.createLabelText).mockClear(); - - generateSzczegoly(mockFaVat); - - const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; - const cenyCall = calls.find((call) => call[0] === 'Faktura wystawiona w cenach: '); - expect(cenyCall).toBeUndefined(); - }); }); describe('P_12_XII label', () => { @@ -291,6 +275,7 @@ describe(generateSzczegoly.name, () => { const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const ossCall = calls.find((call) => call[0] === 'Procedura One Stop Shop'); + expect(ossCall).toBeUndefined(); }); }); @@ -358,6 +343,7 @@ describe(generateSzczegoly.name, () => { const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const currencyCall = calls.find((call) => call[0] === 'Kurs waluty: '); + expect(currencyCall).toBeUndefined(); }); @@ -385,6 +371,7 @@ describe(generateSzczegoly.name, () => { const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const currencyCall = calls.find((call) => call[0] === 'Kurs waluty: '); + expect(currencyCall).toBeUndefined(); }); }); @@ -427,9 +414,11 @@ describe(generateSzczegoly.name, () => { generateSzczegoly(mockFaVat); const calls = vi.mocked(PDFFunctions.generateTwoColumns).mock.calls; + expect(calls.length).toBeGreaterThanOrEqual(1); const firstCall = calls[0]; + expect(Array.isArray(firstCall[0])).toBe(true); expect(Array.isArray(firstCall[1])).toBe(true); }); @@ -449,7 +438,7 @@ describe(generateSzczegoly.name, () => { } as any; vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === data.ZaliczkaCzesciowa) + if (field === data.ZaliczkaCzesciowa) { return [ { P_6Z: { _text: '2024-01-01' }, @@ -457,6 +446,7 @@ describe(generateSzczegoly.name, () => { KursWalutyZW: { _text: '4.50' }, }, ] as any; + } return []; }); @@ -506,7 +496,9 @@ describe(generateSzczegoly.name, () => { } as any; vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === data.ZaliczkaCzesciowa) return [{ P_6Z: { _text: '' } }] as any; + if (field === data.ZaliczkaCzesciowa) { + return [{ P_6Z: { _text: '' } }] as any; + } return []; }); @@ -533,12 +525,13 @@ describe(generateSzczegoly.name, () => { } as any; vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === data.FakturaZaliczkowa) + if (field === data.FakturaZaliczkowa) { return [ { NrKSeFFaZaliczkowej: { _text: 'FA001' }, }, ] as any; + } return []; }); @@ -590,7 +583,9 @@ describe(generateSzczegoly.name, () => { } as any; vi.mocked(PDFFunctions.getTable).mockImplementation((field: any) => { - if (field === data.FakturaZaliczkowa) return [{ NrKSeFFaZaliczkowej: { _text: '' } }] as any; + if (field === data.FakturaZaliczkowa) { + return [{ NrKSeFFaZaliczkowej: { _text: '' } }] as any; + } return []; }); diff --git a/src/lib-public/generators/FA3/Szczegoly.ts b/src/lib-public/generators/FA3/Szczegoly.ts index e03c6ed0..712e370d 100644 --- a/src/lib-public/generators/FA3/Szczegoly.ts +++ b/src/lib-public/generators/FA3/Szczegoly.ts @@ -13,11 +13,12 @@ import { hasValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa, ZaliczkaCzesciowa } from '../../types/fa3.types'; import { ObjectKeysOfFP, TypesOfValues } from '../../../shared/types/universal.types'; import FormatTyp from '../../../shared/enums/common.enum'; import { FA3FakturaZaliczkowaData } from '../../types/common.types'; +import { formatDateTime } from '@shared/generators/common/functions'; export function generateSzczegoly(faVat: Fa): Content[] { const faWiersze = getTable(faVat.FaWiersz); @@ -32,14 +33,7 @@ export function generateSzczegoly(faVat: Fa): Content[] { const cenyLabel1: Content[] = []; const cenyLabel2: Content[] = []; - if (!(faWiersze.length > 0 || zamowieniaWiersze.length > 0)) { - const Any_P_11 = hasColumnsValue('P_11', faWiersze) || hasColumnsValue('P_11', zamowieniaWiersze); - - if (Any_P_11) { - cenyLabel1.push(createLabelText('Faktura wystawiona w cenach: ', 'netto')); - } else { - cenyLabel1.push(createLabelText('Faktura wystawiona w cenach: ', 'brutto')); - } + if (hasValue(faVat.KodWaluty)) { cenyLabel2.push(createLabelText('Kod waluty: ', faVat.KodWaluty)); } @@ -117,18 +111,24 @@ function generateP_6Scope(P_6_Od: TypesOfValues, P_6_Do: TypesOfValues): Content { value: 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', }, - { value: P_6_Od, formatTyp: FormatTyp.Value }, + { value: formatDateTime(getValue(P_6_Od) as string, true, true), formatTyp: FormatTyp.Value }, { value: ' do ' }, - { value: P_6_Do, formatTyp: FormatTyp.Value }, + { value: formatDateTime(getValue(P_6_Do) as string, true, true), formatTyp: FormatTyp.Value }, ]) ); } else if (hasValue(P_6_Od)) { table.push( - createLabelText('Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', P_6_Od) + createLabelText( + 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: od ', + formatDateTime(getValue(P_6_Od) as string, true, true) + ) ); } else if (hasValue(P_6_Do)) { table.push( - createLabelText('Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', P_6_Do) + createLabelText( + 'Data dokonania lub zakończenia dostawy towarów lub wykonania usługi: do ', + formatDateTime(getValue(P_6_Do) as string, true, true) + ) ); } return table; @@ -142,7 +142,7 @@ function generateZaliczkaCzesciowa(zaliczkaCzesciowaData: ZaliczkaCzesciowa[] | const table: Content[] = []; const zaplataCzesciowaHeader: HeaderDefine[] = [ - { name: 'P_6Z', title: 'Data otrzymania płatności', format: FormatTyp.Default }, + { name: 'P_6Z', title: 'Data otrzymania płatności', format: FormatTyp.Date }, { name: 'P_15Z', title: 'Kwota płatności', format: FormatTyp.Default }, { name: 'KursWalutyZW', title: 'Kurs waluty', format: FormatTyp.Currency6 }, ]; @@ -164,19 +164,18 @@ function generateFakturaZaliczkowa(fakturaZaliczkowaData: ObjectKeysOfFP[] | und return []; } const fakturaZaliczkowa = getTable(fakturaZaliczkowaData) as unknown as FA3FakturaZaliczkowaData[]; - const fakturaZaliczkowaMapped = fakturaZaliczkowa.map(item => { - const fp = - ( - 'NrFaZaliczkowej' in item && item.NrFaZaliczkowej - ) ? item.NrFaZaliczkowej : ('NrKSeFFaZaliczkowej' in item ? item.NrKSeFFaZaliczkowej : undefined ); - - return{ - ...item, - NrFaZaliczkowej : fp ?? { _text: ''}, - }; - }) - const table: Content[] = []; + const fakturaZaliczkowaMapped = fakturaZaliczkowa.map((item) => { + if ('NrFaZaliczkowej' in item && item.NrFaZaliczkowej) { + return { ...item, NrFaZaliczkowej: item.NrFaZaliczkowej }; + } + if ('NrKSeFFaZaliczkowej' in item && item.NrKSeFFaZaliczkowej) { + return { ...item, NrFaZaliczkowej: item.NrKSeFFaZaliczkowej }; + } + + return { ...item, NrFaZaliczkowej: { _text: '' } }; + }); + const table: Content[] = []; const fakturaZaliczkowaHeader: HeaderDefine[] = [ { name: 'NrFaZaliczkowej', diff --git a/src/lib-public/generators/FA3/Transport.spec.ts b/src/lib-public/generators/FA3/Transport.spec.ts index f79f120d..1ccc932c 100644 --- a/src/lib-public/generators/FA3/Transport.spec.ts +++ b/src/lib-public/generators/FA3/Transport.spec.ts @@ -3,9 +3,10 @@ import { generateTransport } from './Transport'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; import { Transport } from '../../types/fa3.types'; -import { Kraj } from '../../../shared/consts/const'; +import { Kraj, TypLadunku } from '../../../shared/consts/FA.const'; import * as PrzewoznikModule from './Przewoznik'; -import * as CommonFunctions from '../../../shared/generators/common/functions'; +import * as CommonFunctions from '@shared/generators/common/functions'; +import { translateMap } from '@shared/generators/common/functions'; vi.mock('../../../shared/PDF-functions', () => ({ createHeader: vi.fn(), @@ -22,12 +23,6 @@ vi.mock('./Przewoznik', () => ({ generatePrzewoznik: vi.fn(), })); -vi.mock('../../../shared/generators/common/functions', () => ({ - getDateTimeWithoutSeconds: vi.fn(), - getRodzajTransportuString: vi.fn(), - getOpisTransportuString: vi.fn(), -})); - describe(generateTransport.name, () => { beforeEach(() => { vi.clearAllMocks(); @@ -71,8 +66,6 @@ describe(generateTransport.name, () => { vi.mocked(PDFFunctions.hasValue).mockReturnValue(true); vi.mocked(PrzewoznikModule.generatePrzewoznik).mockReturnValue('przewoznik' as any); vi.mocked(CommonFunctions.getDateTimeWithoutSeconds).mockReturnValue('2024-01-01 10:00'); - vi.mocked(CommonFunctions.getRodzajTransportuString).mockReturnValue('Road'); - vi.mocked(CommonFunctions.getOpisTransportuString).mockReturnValue('Opis'); }); it('should call createHeader with "Transport"', () => { @@ -83,6 +76,7 @@ describe(generateTransport.name, () => { it('should call createSection and return result', () => { const mockSection = { section: 'test' }; + vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); const result = generateTransport(mockTransport); @@ -98,12 +92,9 @@ describe(generateTransport.name, () => { RodzajTransportu: { _text: '1' }, } as any; - vi.mocked(CommonFunctions.getRodzajTransportuString).mockReturnValue('Road'); - generateTransport(data); - expect(CommonFunctions.getRodzajTransportuString).toHaveBeenCalledWith(data.RodzajTransportu); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Road'); + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Rodzaj transportu: ', 'Transport morski'); }); it('should add transport inny when TransportInny is "1"', () => { @@ -137,6 +128,7 @@ describe(generateTransport.name, () => { const transportInnyCall = calls.find( (call) => call[0] === 'Rodzaj transportu: ' && call[1] === 'Transport inny' ); + expect(transportInnyCall).toBeUndefined(); }); }); @@ -157,7 +149,10 @@ describe(generateTransport.name, () => { generateTransport(mockTransport); expect(PDFFunctions.hasValue).toHaveBeenCalledWith(mockTransport.OpisLadunku); - expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Opis ładunku: ', 'Opis'); + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( + 'Opis ładunku: ', + translateMap(mockTransport.OpisLadunku, TypLadunku) + ); }); it('should not add opis ladunku when hasValue returns false', () => { @@ -167,6 +162,7 @@ describe(generateTransport.name, () => { const calls = vi.mocked(PDFFunctions.createLabelText).mock.calls; const ladunekCall = calls.find((call) => call[0] === 'Opis ładunku: '); + expect(ladunekCall).toBeUndefined(); }); @@ -201,6 +197,7 @@ describe(generateTransport.name, () => { const ladunekInnyCall = calls.find( (call) => call[0] === 'Opis ładunku: ' && call[1] === 'Ładunek inny' ); + expect(ladunekInnyCall).toBeUndefined(); }); @@ -321,6 +318,7 @@ describe(generateTransport.name, () => { const wysylkaDoCall = calls.find( (call) => call[0] === 'Adres miejsca docelowego, do którego został zlecony transport' ); + expect(wysylkaDoCall).toBeUndefined(); }); @@ -424,6 +422,7 @@ describe(generateTransport.name, () => { const calls = vi.mocked(PDFFunctions.createSubHeader).mock.calls; const wysylkaPrzezCalls = calls.filter((call) => call[0] === 'Adres pośredni wysyłki'); + expect(wysylkaPrzezCalls).toHaveLength(0); }); }); @@ -439,6 +438,7 @@ describe(generateTransport.name, () => { generateTransport(mockTransport); const calls = vi.mocked(PDFFunctions.generateTwoColumns).mock.calls; + expect(calls.length).toBeGreaterThanOrEqual(2); }); diff --git a/src/lib-public/generators/FA3/Transport.ts b/src/lib-public/generators/FA3/Transport.ts index 157a38a0..9b996459 100644 --- a/src/lib-public/generators/FA3/Transport.ts +++ b/src/lib-public/generators/FA3/Transport.ts @@ -1,5 +1,5 @@ import { Content } from 'pdfmake/interfaces'; -import { Kraj } from '../../../shared/consts/const'; +import { Kraj, RodzajTransportu, TypLadunku } from '../../../shared/consts/FA.const'; import { createHeader, createLabelText, @@ -11,11 +11,7 @@ import { hasValue, } from '../../../shared/PDF-functions'; import { Transport } from '../../types/fa3.types'; -import { - getDateTimeWithoutSeconds, - getOpisTransportuString, - getRodzajTransportuString, -} from '../../../shared/generators/common/functions'; +import { getDateTimeWithoutSeconds, translateMap } from '@shared/generators/common/functions'; import { generatePrzewoznik } from './Przewoznik'; import FormatTyp from '../../../shared/enums/common.enum'; @@ -32,7 +28,7 @@ export function generateTransport(transport: Transport, index?: number | null): table.push(createHeader(index ? `Transport ${index}` : 'Transport')); if (transport.RodzajTransportu?._text) { columns.transport.push( - createLabelText('Rodzaj transportu: ', getRodzajTransportuString(transport.RodzajTransportu)) + createLabelText('Rodzaj transportu: ', translateMap(transport.RodzajTransportu, RodzajTransportu)) ); } else if (transport.TransportInny?._text == '1' && transport.OpisInnegoTransportu?._text) { columns.transport.push(createLabelText('Rodzaj transportu: ', 'Transport inny')); @@ -42,7 +38,7 @@ export function generateTransport(transport: Transport, index?: number | null): } columns.dane.push(createLabelText('Numer zlecenia transportu: ', transport.NrZleceniaTransportu)); if (hasValue(transport.OpisLadunku)) { - columns.dane.push(createLabelText('Opis ładunku: ', getOpisTransportuString(transport.OpisLadunku))); + columns.dane.push(createLabelText('Opis ładunku: ', translateMap(transport.OpisLadunku, TypLadunku))); if (transport.LadunekInny?._text === '1' && transport.OpisInnegoLadunku?._text) { columns.dane.push(createLabelText('Opis ładunku: ', 'Ładunek inny')); columns.dane.push(createLabelText('Opis innego ładunku: ', transport.OpisInnegoLadunku)); diff --git a/src/lib-public/generators/FA3/WarunkiTransakcji.ts b/src/lib-public/generators/FA3/WarunkiTransakcji.ts index abb53471..556d5242 100644 --- a/src/lib-public/generators/FA3/WarunkiTransakcji.ts +++ b/src/lib-public/generators/FA3/WarunkiTransakcji.ts @@ -24,11 +24,11 @@ export function generateWarunkiTransakcji(warunkiTransakcji: WarunkiTransakcji | const zamowienia: Zamowienia[] = getTable(warunkiTransakcji?.Zamowienia); const partiaTowaru: FP[] = getTable(warunkiTransakcji?.NrPartiiTowaru); const definedHeaderUmowy: HeaderDefine[] = [ - { name: 'DataUmowy', title: 'Data umowy', format: FormatTyp.Default }, + { name: 'DataUmowy', title: 'Data umowy', format: FormatTyp.Date }, { name: 'NrUmowy', title: 'Numer umowy', format: FormatTyp.Default }, ]; const definedHeaderZamowienia: HeaderDefine[] = [ - { name: 'DataZamowienia', title: 'Data zamówienia', format: FormatTyp.Default }, + { name: 'DataZamowienia', title: 'Data zamówienia', format: FormatTyp.Date }, { name: 'NrZamowienia', title: 'Numer zamówienia', format: FormatTyp.Default }, ]; const definedHeaderPartiaTowaru: HeaderDefine[] = [ @@ -103,4 +103,4 @@ export function generateWarunkiTransakcji(warunkiTransakcji: WarunkiTransakcji | } return createSection(table, true); -} +} \ No newline at end of file diff --git a/src/lib-public/generators/FA3/Wiersze.spec.ts b/src/lib-public/generators/FA3/Wiersze.spec.ts index 62db2994..77800b97 100644 --- a/src/lib-public/generators/FA3/Wiersze.spec.ts +++ b/src/lib-public/generators/FA3/Wiersze.spec.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa } from '../../types/fa3.types'; import { generateWiersze } from './Wiersze'; @@ -37,7 +37,7 @@ describe(generateWiersze.name, () => { RodzajFaktury: { _text: TRodzajFaktury.VAT }, } as any; - const setupBasicMocks = (p15Value: string, rodzajFakturyValue: string, currencyValue: string = 'PLN') => { + const setupBasicMocks = (p15Value: string, rodzajFakturyValue: string, currencyValue = 'PLN') => { vi.mocked(PDFFunctions.getTable).mockReturnValue([ { NrWierszaFa: { _text: '1' }, @@ -52,9 +52,15 @@ describe(generateWiersze.name, () => { }); vi.mocked(PDFFunctions.getValue).mockImplementation((field: any) => { - if (field === mockFaVat.P_15 || field?._text === p15Value) return p15Value; - if (field === mockFaVat.RodzajFaktury || field?._text === rodzajFakturyValue) return rodzajFakturyValue; - if (field === mockFaVat.KodWaluty || field?._text === currencyValue) return currencyValue; + if (field === mockFaVat.P_15 || field?._text === p15Value) { + return p15Value; + } + if (field === mockFaVat.RodzajFaktury || field?._text === rodzajFakturyValue) { + return rodzajFakturyValue; + } + if (field === mockFaVat.KodWaluty || field?._text === currencyValue) { + return currencyValue; + } return undefined; }); @@ -100,40 +106,6 @@ describe(generateWiersze.name, () => { }); describe('price text formatting', () => { - it('should display "netto" when P_11 is in fieldsWithValue', () => { - setupBasicMocks('200', TRodzajFaktury.VAT); - - generateWiersze(mockFaVat); - - expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('netto'), [ - FormatTyp.Label, - FormatTyp.MarginBottom8, - ]); - }); - - it('should display "brutto" when P_11 is not in fieldsWithValue', () => { - vi.mocked(PDFFunctions.getTable).mockReturnValue([ - { NrWierszaFa: { _text: '1' }, P_12: { _text: '23' } }, - ] as any); - - vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ - content: { table: {} } as any, - fieldsWithValue: ['P_11A', 'P_7', 'P12'], - }); - - vi.mocked(PDFFunctions.getValue).mockReturnValue('0'); - vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted text' as any); - vi.mocked(PDFFunctions.createHeader).mockReturnValue(['Header'] as any); - vi.mocked(PDFFunctions.createSection).mockReturnValue({ section: 'content' } as any); - - generateWiersze(mockFaVat); - - expect(PDFFunctions.formatText).toHaveBeenCalledWith(expect.stringContaining('brutto'), [ - FormatTyp.Label, - FormatTyp.MarginBottom8, - ]); - }); - it('should include currency code in price text', () => { setupBasicMocks('200', TRodzajFaktury.VAT); @@ -211,6 +183,7 @@ describe(generateWiersze.name, () => { generateWiersze(mockFaVat); const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0]; + expect(sectionCall).not.toContain('\n'); expect(sectionCall.filter((item: any) => item?.table === 'table2')).toHaveLength(0); }); @@ -305,9 +278,15 @@ describe(generateWiersze.name, () => { }); vi.mocked(PDFFunctions.getValue).mockImplementation((field: any) => { - if (field === mockFaVat.P_15) return '200'; - if (field === mockFaVat.RodzajFaktury) return TRodzajFaktury.VAT; - if (field === mockFaVat.KodWaluty) return undefined; + if (field === mockFaVat.P_15) { + return '200'; + } + if (field === mockFaVat.RodzajFaktury) { + return TRodzajFaktury.VAT; + } + if (field === mockFaVat.KodWaluty) { + return undefined; + } return undefined; }); @@ -336,14 +315,12 @@ describe(generateWiersze.name, () => { generateWiersze(mockFaVat); - expect(PDFFunctions.createSection).toHaveBeenCalledWith( - expect.arrayContaining(['Header', 'formatted text']), - true - ); + expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.arrayContaining(['Header']), true); }); it('should return the result of createSection', () => { const mockSection = { section: 'test' }; + setupBasicMocks('200', TRodzajFaktury.VAT); vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); diff --git a/src/lib-public/generators/FA3/Wiersze.ts b/src/lib-public/generators/FA3/Wiersze.ts index bc406ffa..29623800 100644 --- a/src/lib-public/generators/FA3/Wiersze.ts +++ b/src/lib-public/generators/FA3/Wiersze.ts @@ -11,7 +11,7 @@ import { getValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa, FP } from '../../types/fa3.types'; import FormatTyp, { Position } from '../../../shared/enums/common.enum'; import { addMarza } from '../common/Wiersze'; @@ -68,10 +68,10 @@ export function generateWiersze(faVat: Fa): Content { { name: 'PKWiU', title: 'PKWiU', format: FormatTyp.Default, width: 'auto' }, { name: 'CN', title: 'CN', format: FormatTyp.Default, width: 'auto' }, { name: 'PKOB', title: 'PKOB', format: FormatTyp.Default, width: 'auto' }, - { name: 'KwotaAkcyzy', title: 'KwotaAkcyzy', format: FormatTyp.Default, width: 'auto' }, + { name: 'KwotaAkcyzy', title: 'Kwota podatku akcyzowego', format: FormatTyp.Default, width: 'auto' }, { name: 'GTU', title: 'GTU', format: FormatTyp.Default, width: 'auto' }, { name: 'Procedura', title: 'Procedura', format: FormatTyp.Default, width: '*' }, - { name: 'P_6A', title: 'Data dostawy / wykonania', format: FormatTyp.Default, width: 'auto' }, + { name: 'P_6A', title: 'Data dostawy / wykonania', format: FormatTyp.Date, width: 'auto' }, { name: 'Indeks', title: 'Indeks', format: FormatTyp.Default, width: 'auto' }, ]; let content = getContentTable<(typeof faWiersze)[0]>( @@ -79,10 +79,10 @@ export function generateWiersze(faVat: Fa): Content { faWiersze, 'auto' ); - const ceny = formatText( - `Faktura wystawiona w cenach ${content.fieldsWithValue.includes('P_11') ? 'netto' : 'brutto'} w walucie ${faVat.KodWaluty?._text}`, - [FormatTyp.Label, FormatTyp.MarginBottom8] - ); + const ceny = formatText(`Faktura wystawiona w walucie ${faVat.KodWaluty?._text}`, [ + FormatTyp.Label, + FormatTyp.MarginBottom8, + ]); const p_15 = getValue(faVat.P_15); let opis: Content = ''; diff --git a/src/lib-public/generators/FA3/Zamowienia.spec.ts b/src/lib-public/generators/FA3/Zamowienia.spec.ts index 7e8e7cff..57518cc5 100644 --- a/src/lib-public/generators/FA3/Zamowienia.spec.ts +++ b/src/lib-public/generators/FA3/Zamowienia.spec.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as PDFFunctions from '../../../shared/PDF-functions'; import FormatTyp from '../../../shared/enums/common.enum'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '@shared/consts/FA.const'; import { Zamowienie } from '../../types/fa3.types'; import { generateZamowienie } from './Zamowienie'; import { ZamowienieKorekta } from '../../enums/invoice.enums'; @@ -13,7 +13,7 @@ vi.mock('../../../shared/PDF-functions', () => ({ getContentTable: vi.fn(), getTable: vi.fn(), getValue: vi.fn(), - getTStawkaPodatku: vi.fn() + getTStawkaPodatku: vi.fn(), })); describe(generateZamowienie.name, () => { @@ -100,6 +100,7 @@ describe(generateZamowienie.name, () => { ); const tableCall = vi.mocked(PDFFunctions.getTable).mock.results[0].value; + expect(tableCall[0].NrWierszaZam._text).toBe('1'); }); @@ -121,6 +122,7 @@ describe(generateZamowienie.name, () => { const calls = vi.mocked(PDFFunctions.getContentTable).mock.calls; const header = calls[0][0]; const cenaNetto = header.find((h: any) => h.name === 'P_9AZ'); + expect(cenaNetto?.format).toBe(FormatTyp.CurrencyAbs); }); @@ -141,6 +143,7 @@ describe(generateZamowienie.name, () => { const calls = vi.mocked(PDFFunctions.getContentTable).mock.calls; const header = calls[0][0]; const cenaNetto = header.find((h: any) => h.name === 'P_9AZ'); + expect(cenaNetto?.format).toBe(FormatTyp.Currency); }); }); @@ -204,7 +207,7 @@ describe(generateZamowienie.name, () => { ); expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([ - { value: 'Otrzymana kwota zapłaty (zaliczki): ', formatTyp: FormatTyp.LabelGreater }, + { value: 'Kwota zapłaty (zaliczki) dokumentowana fakturą: ', formatTyp: FormatTyp.LabelGreater }, { value: '100', formatTyp: FormatTyp.CurrencyGreater }, ]); }); @@ -229,62 +232,39 @@ describe(generateZamowienie.name, () => { ); expect(PDFFunctions.createLabelTextArray).toHaveBeenCalledWith([ - { value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater }, + { + value: 'Korekta kwoty zapłaty (zaliczki) dokumentowana fakturą: ', + formatTyp: FormatTyp.LabelGreater, + }, { value: '150', formatTyp: FormatTyp.CurrencyGreater }, ]); }); }); describe('price text', () => { - it('should display "netto" when P_11 is in fieldsWithValue', () => { - vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ - content: { table: {} } as any, - fieldsWithValue: ['P_11', 'P_7Z'], - }); - - const result = generateZamowienie( + it('should call createHeader with correct parameter', () => { + generateZamowienie( mockOrderData, ZamowienieKorekta.BeforeCorrection, '100', TRodzajFaktury.ZAL, - 'EUR' + 'PLN' ); - const stack = (result[0] as any).stack; - expect(stack[1]).toContain('netto'); - expect(stack[1]).toContain('EUR'); + expect(PDFFunctions.createHeader).toHaveBeenCalledWith(ZamowienieKorekta.BeforeCorrection); }); - it('should display "brutto" when P_11 is not in fieldsWithValue', () => { - vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ - content: { table: {} } as any, - fieldsWithValue: ['P_7Z', 'P_9AZ'], - }); - - const result = generateZamowienie( + it('should format order value', () => { + generateZamowienie( mockOrderData, ZamowienieKorekta.BeforeCorrection, '100', TRodzajFaktury.ZAL, - 'USD' + 'PLN' ); - const stack = (result[0] as any).stack; - expect(stack[1]).toContain('brutto'); - expect(stack[1]).toContain('USD'); + expect(PDFFunctions.formatText).toHaveBeenCalledWith('200', FormatTyp.Currency); }); }); - - it('should call createHeader with correct parameter', () => { - generateZamowienie(mockOrderData, ZamowienieKorekta.BeforeCorrection, '100', TRodzajFaktury.ZAL, 'PLN'); - - expect(PDFFunctions.createHeader).toHaveBeenCalledWith(ZamowienieKorekta.BeforeCorrection); - }); - - it('should format order value', () => { - generateZamowienie(mockOrderData, ZamowienieKorekta.BeforeCorrection, '100', TRodzajFaktury.ZAL, 'PLN'); - - expect(PDFFunctions.formatText).toHaveBeenCalledWith('200', FormatTyp.Currency); - }); }); }); diff --git a/src/lib-public/generators/FA3/Zamowienie.ts b/src/lib-public/generators/FA3/Zamowienie.ts index e3c5a64d..b8cadbb6 100644 --- a/src/lib-public/generators/FA3/Zamowienie.ts +++ b/src/lib-public/generators/FA3/Zamowienie.ts @@ -9,7 +9,7 @@ import { getValue, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Zamowienie } from '../../types/fa3.types'; import FormatTyp, { Position } from '../../../shared/enums/common.enum'; import { ZamowienieKorekta } from '../../enums/invoice.enums'; @@ -101,13 +101,13 @@ export function generateZamowienie( table.push(content.content); } } - const ceny = `Faktura wystawiona w cenach ${content.fieldsWithValue.includes('P_11') ? 'netto' : 'brutto'} w walucie ${KodWaluty}`; + const ceny = `Faktura wystawiona w walucie ${KodWaluty}`; let opis: Content = ''; if (Number(p_15) > 0 && rodzajFaktury == TRodzajFaktury.ZAL) { opis = { stack: createLabelTextArray([ - { value: 'Otrzymana kwota zapłaty (zaliczki): ', formatTyp: FormatTyp.LabelGreater }, + { value: 'Kwota zapłaty (zaliczki) dokumentowana fakturą: ', formatTyp: FormatTyp.LabelGreater }, { value: p_15, formatTyp: FormatTyp.CurrencyGreater }, ]), alignment: Position.RIGHT, @@ -120,7 +120,10 @@ export function generateZamowienie( ) { opis = { stack: createLabelTextArray([ - { value: 'Kwota należności ogółem: ', formatTyp: FormatTyp.LabelGreater }, + { + value: 'Korekta kwoty zapłaty (zaliczki) dokumentowana fakturą: ', + formatTyp: FormatTyp.LabelGreater, + }, { value: p_15, formatTyp: FormatTyp.CurrencyGreater }, ]), alignment: Position.RIGHT, diff --git a/src/lib-public/generators/FA_RR/Adres.spec.ts b/src/lib-public/generators/FA_RR/Adres.spec.ts new file mode 100644 index 00000000..8fd93db5 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Adres.spec.ts @@ -0,0 +1,46 @@ +import { beforeEach, describe, expect, it, test, vi } from 'vitest'; +import { generateAdres } from './Adres'; +import { createLabelText, formatText } from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; + +vi.mock('../../../shared/PDF-functions', () => ({ + formatText: vi.fn((text: string, style: string) => ({ text, style })), + getKraj: vi.fn((code: string) => `Kraj: ${code}`), + createLabelText: vi.fn((label: string, value: any) => [{ text: `${label}${value ?? ''}` }]), + getValue: vi.fn((v) => v?._text || v), +})); + +describe(generateAdres.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + test.each([ + [{ AdresL1: { _text: 'Ulica Testowa 1' } }, ['Ulica Testowa 1']], + [{ AdresL2: { _text: '00-001 Warszawa' } }, ['00-001 Warszawa']], + [{ KodKraju: { _text: 'PL' } }, ['Kraj: PL']], + [ + { AdresL1: { _text: 'Ulica 1' }, AdresL2: { _text: 'Miasto' }, KodKraju: { _text: 'DE' } }, + ['Ulica 1', 'Miasto', 'Kraj: DE'], + ], + ])('generuje dane adresowe dla %s', (adres, expectedTexts) => { + const result = generateAdres(adres as any); + + expect(formatText).toHaveBeenCalledTimes(expectedTexts.length); + expectedTexts.forEach((text) => { + expect(formatText).toHaveBeenCalledWith(text, FormatTyp.Value); + }); + + expect(createLabelText).toHaveBeenCalledWith('GLN: ', (adres as any).GLN); + expect((result[result.length - 1] as any).text).toContain('GLN:'); + }); + + it('zwraca tylko GLN gdy brak innych pól', () => { + const adres = { GLN: '1234567890' }; + const result = generateAdres(adres as any); + expect(formatText).not.toHaveBeenCalled(); + expect(createLabelText).toHaveBeenCalledWith('GLN: ', '1234567890'); + expect(result).toHaveLength(1); + expect((result[0] as any).text).toContain('GLN:'); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Adres.ts b/src/lib-public/generators/FA_RR/Adres.ts new file mode 100644 index 00000000..6b2a836e --- /dev/null +++ b/src/lib-public/generators/FA_RR/Adres.ts @@ -0,0 +1,20 @@ +import { Content } from 'pdfmake/interfaces'; +import { createLabelText, formatText, getKraj, getValue } from '../../../shared/PDF-functions'; +import { Adres } from '../../types/FaRR.types'; +import FormatTyp from '../../../shared/enums/common.enum'; + +export function generateAdres(adres: Adres): Content[] { + const result: Content[] = []; + + if (adres?.AdresL1) { + result.push(formatText(getValue(adres.AdresL1), FormatTyp.Value)); + } + if (adres?.AdresL2) { + result.push(formatText(getValue(adres.AdresL2), FormatTyp.Value)); + } + if (adres?.KodKraju) { + result.push(formatText(getKraj((getValue(adres.KodKraju) as string) ?? ''), FormatTyp.Value)); + } + result.push(...createLabelText('GLN: ', adres.GLN)); + return result; +} diff --git a/src/lib-public/generators/FA_RR/DodatkoweInformacje.spec.ts b/src/lib-public/generators/FA_RR/DodatkoweInformacje.spec.ts new file mode 100644 index 00000000..f440f10e --- /dev/null +++ b/src/lib-public/generators/FA_RR/DodatkoweInformacje.spec.ts @@ -0,0 +1,64 @@ +import { describe, it, expect, vi, beforeEach, test } from 'vitest'; +import { generateDodatkoweInformacje } from './DodatkoweInformacje'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createHeader: vi.fn((text: string) => [{ text, style: 'header' }]), + createSection: vi.fn((content: any, bordered: boolean) => [{ section: content, bordered }]), + createSubHeader: vi.fn((text: string) => [{ text, style: 'subheader' }]), + formatText: vi.fn((text: string) => ({ text, style: 'value' })), + getValue: vi.fn((obj: any) => obj?._text ?? obj ?? ''), + getTable: vi.fn((data: any) => data ?? []), + getContentTable: vi.fn(() => ({ content: { text: 'mockTable' } })), +})); + +import { + createSection, + createSubHeader, + formatText, + getTable, + getContentTable, +} from '../../../shared/PDF-functions'; + +describe(generateDodatkoweInformacje.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('dodaje dodatkowy opis jeśli istnieje DodatkowyOpis', () => { + (getTable as any).mockReturnValueOnce([ + { NrWiersza: { _text: '1' }, Klucz: { _text: 'Info' }, Wartosc: { _text: 'Opis' } }, + ]); + + const faVat = { + DodatkowyOpis: [{ NrWiersza: { _text: '1' }, Klucz: { _text: 'Info' }, Wartosc: { _text: 'Opis' } }], + }; + + const result = generateDodatkoweInformacje(faVat as any); + + expect(createSubHeader).toHaveBeenCalledWith('Dodatkowy opis'); + expect(getContentTable).toHaveBeenCalled(); + expect(result.length).toBeGreaterThan(0); + expect(createSection).toHaveBeenCalled(); + }); + + it('dodaje dokumenty zapłaty jeśli istnieje DokumentZaplaty', () => { + (getTable as any).mockReturnValueOnce([ + { NrDokumentu: { _text: '1' }, DataDokumentu: { _text: '2031-10-15' } }, + ]); + + const faVat = { + DokumentZaplaty: [{ NrDokumentu: { _text: '1' }, DataDokumentu: { _text: '2031-10-15' } }], + }; + + const result = generateDodatkoweInformacje(faVat as any); + expect(createSubHeader).toHaveBeenCalledWith('Dokumenty Zapłaty', [0, 0, 0, 4]); + expect(getContentTable).toHaveBeenCalled(); + expect(result.length).toBeGreaterThan(0); + expect(createSection).toHaveBeenCalled(); + }); + + it('zwraca pustą tablicę gdy brak danych wejściowych', () => { + const result = generateDodatkoweInformacje({} as any); + expect(result).toEqual([]); + }); +}); diff --git a/src/lib-public/generators/FA_RR/DodatkoweInformacje.ts b/src/lib-public/generators/FA_RR/DodatkoweInformacje.ts new file mode 100644 index 00000000..7ea64a3b --- /dev/null +++ b/src/lib-public/generators/FA_RR/DodatkoweInformacje.ts @@ -0,0 +1,113 @@ +import { Content } from 'pdfmake/interfaces'; +import { + createHeader, + createSection, + createSubHeader, + getContentTable, + getTable, +} from '../../../shared/PDF-functions'; +import { HeaderDefine } from '../../../shared/types/pdf-types'; +import { DodatkowyOpi, DokumentZaplaty, FakturaRR as Fa } from '../../types/FaRR.types'; +import FormatTyp from '../../../shared/enums/common.enum'; + +export function generateDodatkoweInformacje(fa: Fa): Content[] { + const table: Content[] = [ + ...createHeader('Dodatkowe informacje'), + ...generateDokumentyZaplaty(fa.DokumentZaplaty), + ...generateDodatkowyOpis(fa.DodatkowyOpis), + ]; + + return table.length > 1 ? createSection(table, true) : []; +} + +function generateDokumentyZaplaty(dokumentZaplaty: DokumentZaplaty[] | undefined): Content[] { + if (!dokumentZaplaty) { + return []; + } + const dokumentZaplatyTable = getTable(dokumentZaplaty)?.map((item, index) => ({ + ...item, + lp: { _text: index + 1 }, + })); + const table: Content[] = createSubHeader('Dokumenty Zapłaty', [0, 0, 0, 4]); + + const dokumentZaplatyHeader: HeaderDefine[] = [ + { + name: 'lp', + title: 'Lp.', + format: FormatTyp.Default, + width: 'auto', + }, + { + name: 'NrDokumentu', + title: 'Numer dokumentu', + format: FormatTyp.Default, + width: '*', + }, + { + name: 'DataDokumentu', + title: 'Data dokumentu', + format: FormatTyp.Date, + width: 'auto', + }, + ]; + const dokumentZaplatyTableContent = getContentTable<(typeof dokumentZaplatyTable)[0]>( + dokumentZaplatyHeader, + dokumentZaplatyTable, + '*', + [0, 0, 0, 0] + ); + + if (dokumentZaplatyTableContent.content) { + table.push(dokumentZaplatyTableContent.content); + } + return table; +} + +function generateDodatkowyOpis(dodatkowyOpis: DodatkowyOpi[] | undefined): Content[] { + if (!dodatkowyOpis) { + return []; + } + const dodatkowyOpisTable = getTable(dodatkowyOpis)?.map((item, index) => ({ + ...item, + lp: { _text: index + 1 }, + })); + const table: Content[] = createSubHeader('Dodatkowy opis'); + + const dodatkowyOpisHeader: HeaderDefine[] = [ + { + name: 'lp', + title: 'Lp.', + format: FormatTyp.Default, + width: 'auto', + }, + { + name: 'NrWiersza', + title: 'Numer wiersza', + format: FormatTyp.Default, + width: 'auto', + }, + { + name: 'Klucz', + title: 'Rodzaj informacji', + format: FormatTyp.Default, + width: 'auto', + }, + { + name: 'Wartosc', + title: 'Treść informacji', + format: FormatTyp.Default, + width: '*', + }, + ]; + const dodatkowyOpisTableContent = getContentTable<(typeof dodatkowyOpisTable)[0]>( + dodatkowyOpisHeader, + dodatkowyOpisTable, + '*', + [0, 0, 0, 0] + ); + + if (dodatkowyOpisTableContent.content) { + table.push(dodatkowyOpisTableContent.content); + } + return table; +} diff --git a/src/lib-public/generators/FA_RR/Naglowek.spec.ts b/src/lib-public/generators/FA_RR/Naglowek.spec.ts new file mode 100644 index 00000000..7f91109d --- /dev/null +++ b/src/lib-public/generators/FA_RR/Naglowek.spec.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; +import { FakturaRR } from '../../types/FaRR.types'; +import { generateNaglowek } from './Naglowek'; + +describe('generateNaglowek', () => { + it('generates header for correction invoice VAT RR', () => { + const fa: FakturaRR = { + RodzajFaktury: { _text: TRodzajFaktury.KOR_VAT_RR }, + } as any; + + const result = generateNaglowek(fa); + + expect( + result.some( + (c) => + typeof c === 'object' && + c !== null && + 'text' in c && + typeof (c as any).text === 'string' && + (c as any).text.includes('Faktura korygująca VAT RR') + ) + ).toBe(true); + }); + + it('generates header with empty string for unknown invoice type', () => { + const fa: FakturaRR = { + RodzajFaktury: { _text: 'UNKNOWN' }, + } as any; + + const result = generateNaglowek(fa); + + expect( + result.some( + (c) => + typeof c === 'object' && + c !== null && + 'text' in c && + typeof (c as any).text === 'string' && + (c as any).text.includes('') + ) + ).toBe(true); + }); + + it('generates header even when fa is undefined', () => { + const result = generateNaglowek(); + + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBeGreaterThan(0); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Naglowek.ts b/src/lib-public/generators/FA_RR/Naglowek.ts new file mode 100644 index 00000000..47c377b2 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Naglowek.ts @@ -0,0 +1,49 @@ +import { Content, ContentText } from 'pdfmake/interfaces'; +import { formatText, generateLine, getValue } from '../../../shared/PDF-functions'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; +import { FakturaRR as Fa } from '../../types/FaRR.types'; +import FormatTyp, { Position } from '../../../shared/enums/common.enum'; +import { AdditionalDataTypes } from '../../types/common.types'; + +export function generateNaglowek(fa?: Fa, additionalData?: AdditionalDataTypes): Content[] { + let invoiceName = ''; + + switch (getValue(fa?.RodzajFaktury)) { + case TRodzajFaktury.VAT_RR: + invoiceName = 'Faktura pierwotna VAT RR'; + break; + case TRodzajFaktury.KOR_VAT_RR: + invoiceName = 'Faktura korygująca VAT RR'; + break; + } + + return [ + { + text: [ + { text: 'Krajowy System ', fontSize: 18 }, + { text: 'e', color: 'red', bold: true, fontSize: 18 }, + { text: '-Faktur', bold: true, fontSize: 18 }, + ], + }, + { ...(formatText('Numer Faktury:', FormatTyp.ValueMedium) as ContentText), alignment: Position.RIGHT }, + { + ...(formatText(getValue(fa?.P_4C), FormatTyp.HeaderPosition) as ContentText), + alignment: Position.RIGHT, + }, + { + ...(formatText(invoiceName, [FormatTyp.ValueMedium, FormatTyp.Default]) as ContentText), + alignment: Position.RIGHT, + }, + ...(additionalData?.nrKSeF + ? [ + { + text: [ + formatText('Numer KSeF:', FormatTyp.LabelMedium) as ContentText, + formatText(additionalData?.nrKSeF, FormatTyp.ValueMedium), + ], + alignment: Position.RIGHT, + } as Content, + ] + : []), + ]; +} diff --git a/src/lib-public/generators/FA_RR/Platnosc.spec.ts b/src/lib-public/generators/FA_RR/Platnosc.spec.ts new file mode 100644 index 00000000..a886215c --- /dev/null +++ b/src/lib-public/generators/FA_RR/Platnosc.spec.ts @@ -0,0 +1,62 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { generatePlatnosc } from './Platnosc'; +import type { Platnosc, RachunekBankowy } from '../../types/FaRR.types'; +import type { Content } from 'pdfmake/interfaces'; +import { createHeader, createLabelText } from '../../../shared/PDF-functions'; +import { generujRachunekBankowy } from './RachunekBankowy'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]), + createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]), + generateLine: vi.fn((): Content[] => [{ line: true } as any]), + generateTwoColumns: vi.fn((left: any[], right: any[], margins?: number[]): Content[] => [ + { twoColumns: { left, right }, margins } as any, + ]), + getTable: vi.fn((data: any): any[] => data ?? []), + getContentTable: vi.fn(() => ({ content: [{ text: 'mockTable' }] })), + hasValue: vi.fn((v: any) => !!v), + getValue: vi.fn((v: any) => !!v), +})); + +vi.mock('./RachunekBankowy', () => ({ + generujRachunekBankowy: vi.fn((data: any, label: string): Content[] => [{ text: label }]), +})); +const mockedCreateLabelText = vi.mocked(createLabelText); + +describe(generatePlatnosc.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('dodaje informacje o formie zapłaty przelew jezeli istnieje FormaPlatnosci', () => { + const platnosc: Partial = { FormaPlatnosci: { _text: 'Przelew' } }; + const result = generatePlatnosc(platnosc as Platnosc); + + expect(createLabelText).toHaveBeenCalledWith('Forma zapłaty: ', 'Przelew'); + }); + + it('dodaje informacje o formie zapłaty inna jezeli istnieje PlatnoscInna', () => { + const platnosc: Partial = { PlatnoscInna: { _text: '1' }, OpisPlatnosci: { _text: 'opis' } }; + const result = generatePlatnosc(platnosc as Platnosc); + + expect(createLabelText).toHaveBeenCalledWith('Forma zapłaty: ', 'Inna'); + }); + + it('dodaje rachunki bankowe', () => { + const platnosc: Partial = { + RachunekBankowy1: ['123'] as RachunekBankowy[], + RachunekBankowy2: ['456'] as RachunekBankowy[], + }; + + const result: Content = generatePlatnosc(platnosc as Platnosc); + + expect(generujRachunekBankowy).toHaveBeenCalledTimes(2); + expect(createHeader).toHaveBeenCalledWith('Płatność'); + }); + + it('zwraca pustą tablicę jeśli platnosc undefined', () => { + const result = generatePlatnosc(undefined); + + expect(result).toEqual([]); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Platnosc.ts b/src/lib-public/generators/FA_RR/Platnosc.ts new file mode 100644 index 00000000..2d542b61 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Platnosc.ts @@ -0,0 +1,61 @@ +import { Content, ContentText } from 'pdfmake/interfaces'; +import { + createHeader, + createLabelText, + formatText, + generateLine, + generateTwoColumns, + getTable, + getValue, + hasValue, +} from '../../../shared/PDF-functions'; +import { Platnosc } from '../../types/FaRR.types'; +import { generujRachunekBankowy } from './RachunekBankowy'; +import FormatTyp from '../../../shared/enums/common.enum'; + +export function generatePlatnosc(platnosc: Platnosc | undefined): Content { + if (!platnosc) { + return []; + } + const table: Content[] = [generateLine(), ...createHeader('Płatność')]; + + if (hasValue(platnosc.FormaPlatnosci)) { + table.push(createLabelText('Forma zapłaty: ', 'Przelew')); + } else { + if (hasValue(platnosc.OpisPlatnosci)) { + table.push(createLabelText('Forma zapłaty: ', 'Inna')); + table.push(createLabelText('Opis: ', platnosc.OpisPlatnosci)); + } + } + + if (hasValue(platnosc.LinkDoPlatnosci)) { + table.push(formatText('Link do płatności bezgotówkowej: ', FormatTyp.Label)); + table.push({ + text: formatText(getValue(platnosc.LinkDoPlatnosci), FormatTyp.Link), + link: formatText(getValue(platnosc.LinkDoPlatnosci), FormatTyp.Link), + } as ContentText); + } + if (hasValue(platnosc.IPKSeF)) { + table.push(createLabelText('Identyfikator płatności Krajowego Systemu e-Faktur: ', platnosc.IPKSeF)); + } + + const rachunekBankowy1: Content[][] = getTable(platnosc.RachunekBankowy1).map((rachunek) => + generujRachunekBankowy([rachunek], 'Rachunek bankowy rolnika') + ); + const rachunekBankowy2: Content[][] = getTable(platnosc.RachunekBankowy2).map((rachunek) => + generujRachunekBankowy([rachunek], 'Rachunek bankowy nabywcy') + ); + const rachunekBankowy: Content[][] = [...rachunekBankowy1, ...rachunekBankowy2]; + + if (rachunekBankowy.length > 0) { + rachunekBankowy.forEach((rachunek, index) => { + if (index % 2 === 0) { + table.push(generateTwoColumns(rachunek, rachunekBankowy[index + 1] ?? [])); + } + }); + } + + table.push({ margin: [0, 8, 0, 0], text: '' }); + + return table; +} diff --git a/src/lib-public/generators/FA_RR/Podmiot1.spec.ts b/src/lib-public/generators/FA_RR/Podmiot1.spec.ts new file mode 100644 index 00000000..60c1c6e5 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot1.spec.ts @@ -0,0 +1,73 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { generatePodmiot1 } from './Podmiot1'; +import type { Podmiot1Class } from '../../types/FaRR.types'; +import type { Content } from 'pdfmake/interfaces'; +import { createLabelText, formatText } from '../../../shared/PDF-functions'; +import { generateAdres } from './Adres'; +import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; +import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]), + createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]), + formatText: vi.fn((text: string, style?: any): Content => ({ text, style })), + getValue: vi.fn((val) => val?._text || ''), + hasValue: vi.fn((val) => Boolean(val && val._text)), +})); + +vi.mock('./Adres', () => ({ + generateAdres: vi.fn((adres: any): Content[] => [{ text: 'mockAdres' }]), +})); + +vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot1Dto', () => ({ + generateDaneIdentyfikacyjneTPodmiot1Dto: vi.fn((data: any): Content[] => [ + { text: 'mockDaneIdentyfikacyjne' }, + ]), +})); + +vi.mock('./PodmiotDaneKontaktowe', () => ({ + generateDaneKontaktowe: vi.fn((data: any): Content[] => [{ text: 'mockDaneKontaktowe' }]), +})); + +describe(generatePodmiot1.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('generates identification data if present', () => { + const podmiot: Partial = { + DaneIdentyfikacyjne: { NIP: { _text: 'nip' }, Nazwa: { _text: 'Nazwa' } }, + }; + const result = generatePodmiot1(podmiot as Podmiot1Class); + + expect(generateDaneIdentyfikacyjneTPodmiot1Dto).toHaveBeenCalledWith({ + NIP: { _text: 'nip' }, + Nazwa: { _text: 'Nazwa' }, + }); + expect(result.some((c) => (c as any).text === 'mockDaneIdentyfikacyjne')).toBe(true); + }); + + it('generates address and correspondence address', () => { + const podmiot: Partial = { + Adres: { KodKraju: { _text: 'Ulica 1' } }, + AdresKoresp: { KodKraju: { _text: 'Ulica 2' } }, + }; + const result = generatePodmiot1(podmiot as Podmiot1Class); + + expect(generateAdres).toHaveBeenCalledWith({ KodKraju: { _text: 'Ulica 1' } }); + expect(generateAdres).toHaveBeenCalledWith({ KodKraju: { _text: 'Ulica 2' } }); + expect(result.some((c) => (c as any).text === 'mockAdres')).toBe(true); + expect(formatText).toHaveBeenCalledWith('Adres', ['Label', 'LabelMargin']); + expect(formatText).toHaveBeenCalledWith('Adres do korespondencji', ['Label', 'LabelMargin']); + }); + + it('generates contact data', () => { + const podmiot: Partial = { + DaneKontaktowe: [{ Telefon: { _text: '123' } }], + }; + const result = generatePodmiot1(podmiot as Podmiot1Class); + + expect(generateDaneKontaktowe).toHaveBeenCalledWith([{ Telefon: { _text: '123' } }]); + expect(result.some((c) => (c as any).text === 'mockDaneKontaktowe')).toBe(true); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Podmiot1.ts b/src/lib-public/generators/FA_RR/Podmiot1.ts new file mode 100644 index 00000000..beb86430 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot1.ts @@ -0,0 +1,36 @@ +import { Content } from 'pdfmake/interfaces'; +import { createHeader, createLabelText, formatText, getValue, hasValue } from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { Podmiot1Class } from '../../types/FaRR.types'; +import { generateAdres } from './Adres'; +import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; +import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; + +export function generatePodmiot1(podmiot1: Podmiot1Class): Content[] { + const result: Content[] = createHeader('Sprzedawca'); + + if (podmiot1.DaneIdentyfikacyjne) { + result.push(...generateDaneIdentyfikacyjneTPodmiot1Dto(podmiot1.DaneIdentyfikacyjne)); + } + + if (podmiot1.Adres) { + result.push(formatText('Adres', [FormatTyp.Label, FormatTyp.LabelMargin]), generateAdres(podmiot1.Adres)); + } + if (podmiot1.AdresKoresp) { + result.push( + formatText('Adres do korespondencji', [FormatTyp.Label, FormatTyp.LabelMargin]), + ...generateAdres(podmiot1.AdresKoresp) + ); + } + if (podmiot1.DaneKontaktowe) { + result.push( + formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin]), + ...generateDaneKontaktowe(podmiot1.DaneKontaktowe) + ); + } + + if (hasValue(podmiot1.NrKontrahenta)) { + result.push(createLabelText('Numer kontrahenta: ', getValue(podmiot1.NrKontrahenta))); + } + return result; +} diff --git a/src/lib-public/generators/FA_RR/Podmiot1Podmiot1K.spec.ts b/src/lib-public/generators/FA_RR/Podmiot1Podmiot1K.spec.ts new file mode 100644 index 00000000..f2d6ad55 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot1Podmiot1K.spec.ts @@ -0,0 +1,76 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Content } from 'pdfmake/interfaces'; +import type { Podmiot1Class, Podmiot1KClass } from '../../types/FaRR.types'; +import { generatePodmiot1Podmiot1K } from './Podmiot1Podmiot1K'; +import { generateAdres } from './Adres'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]), + createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]), + getTable: vi.fn((data: any) => data || []), + formatText: vi.fn((text: string, style?: any): Content => ({ text, style })), + verticalSpacing: vi.fn((margin: number) => ({ margin })), + generateColumns: vi.fn((left, right) => ({ columns: [left, right] })), + hasValue: vi.fn((value: any) => value !== undefined && value !== null), +})); + +vi.mock('./Adres', () => ({ + generateAdres: vi.fn((adres: any): Content[] => [{ text: 'mockAddress' }]), +})); + +describe(generatePodmiot1Podmiot1K.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('generates main header and columns', () => { + const podmiot1: Podmiot1Class = { + DaneIdentyfikacyjne: { + NIP: { _text: '1111111' }, + Nazwa: { _text: 'a' }, + }, + }; + const podmiot1K: Podmiot1KClass = {} as any; + const result: any = generatePodmiot1Podmiot1K(podmiot1, podmiot1K); + expect(result[0]).toEqual({ text: 'Sprzedawca', style: 'header' }); + + expect(result[1]).toHaveProperty('columns'); + expect(Array.isArray(result[1].columns[0])).toBe(true); + expect(Array.isArray(result[1].columns[1])).toBe(true); + expect(result[1].columns[0].length).toBeGreaterThan(0); + expect(result[1].columns[1].length).toBe(0); + + expect(result[2]).toHaveProperty('columns'); + expect(Array.isArray(result[2].columns[0])).toBe(true); + expect(result[2].columns[0].length).toBeGreaterThan(0); + + expect(result[3]).toEqual({ margin: 1 }); + }); + + it('calls generateAdres if AdresKoresp exists', () => { + const podmiot1: Podmiot1Class = { AdresKoresp: { AdresL1: { _text: 'Ulica Testowa 1' } } }; + const podmiot1K: Podmiot1KClass = {}; + generatePodmiot1Podmiot1K(podmiot1, podmiot1K); + expect(generateAdres).toHaveBeenCalledWith(podmiot1.AdresKoresp); + }); + + it('handles all fields together', () => { + const podmiot1: Podmiot1Class = { + DaneIdentyfikacyjne: { NIP: { _text: '1111111' }, Nazwa: { _text: 'Firma' } }, + DaneKontaktowe: [{ Telefon: { _text: '123' } }], + StatusInfoPodatnika: { _text: 'active' }, + AdresKoresp: { AdresL1: { _text: 'Ulica Testowa 1' } }, + }; + const podmiot1K: Podmiot1KClass = { + DaneIdentyfikacyjne: { NIP: { _text: '1111111' }, Nazwa: { _text: 'Firma' } }, + }; + const result: any = generatePodmiot1Podmiot1K(podmiot1, podmiot1K); + + expect(result.length).toBe(4); + expect(result[0]).toEqual({ text: 'Sprzedawca', style: 'header' }); + expect(result[1].columns[0]).toBeInstanceOf(Array); + expect(result[1].columns[1]).toBeInstanceOf(Array); + expect(result[2].columns[0]).toBeInstanceOf(Array); + expect(result[3]).toEqual({ margin: 1 }); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Podmiot1Podmiot1K.ts b/src/lib-public/generators/FA_RR/Podmiot1Podmiot1K.ts new file mode 100644 index 00000000..5ec86c9e --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot1Podmiot1K.ts @@ -0,0 +1,78 @@ +import { Content } from 'pdfmake/interfaces'; +import { + createHeader, + createLabelText, + formatText, + generateColumns, + getTable, + getValue, + hasValue, + verticalSpacing, +} from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; +import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; +import { generateAdres } from './Adres'; +import { Podmiot1Class, Podmiot1KClass } from '../../types/FaRR.types'; + +export function generatePodmiot1Podmiot1K(podmiot1: Podmiot1Class, podmiot1K: Podmiot1KClass): Content[] { + const result: Content[] = createHeader('Sprzedawca'); + let firstColumn: Content[] = []; + let secondColumn: Content[] = []; + + if (podmiot1.DaneIdentyfikacyjne) { + firstColumn.push( + createHeader('Dane identyfikacyjne'), + ...generateDaneIdentyfikacyjneTPodmiot1Dto(podmiot1.DaneIdentyfikacyjne) + ); + } + + if (podmiot1.DaneKontaktowe) { + const daneKontaktowe = generateDaneKontaktowe(getTable(podmiot1.DaneKontaktowe)); + + if (daneKontaktowe.length) { + firstColumn.push(createHeader('Dane kontaktowe')); + firstColumn.push(daneKontaktowe); + } + } + if (hasValue(podmiot1.NrKontrahenta)) { + firstColumn.push(createLabelText('Numer kontrahenta: ', getValue(podmiot1.NrKontrahenta))); + } + + if (firstColumn.length) { + result.push({ + columns: [firstColumn, []], + columnGap: 20, + }); + } + firstColumn = generateCorrectedContent(podmiot1K, 'Treść korygowana'); + secondColumn = generateCorrectedContent(podmiot1, 'Treść korygująca'); + + if (podmiot1.AdresKoresp) { + secondColumn.push( + formatText('Adres do korespondencji', [FormatTyp.Label, FormatTyp.LabelMargin]), + generateAdres(podmiot1.AdresKoresp) + ); + } + if (firstColumn.length || secondColumn.length) { + result.push(generateColumns([firstColumn, secondColumn])); + } + if (result.length) { + result.push(verticalSpacing(1)); + } + return result; +} + +export function generateCorrectedContent(podmiot: Podmiot1Class | Podmiot1KClass, header: string): Content[] { + const result: Content[] = []; + + result.push(createHeader(header)); + + if (podmiot.DaneIdentyfikacyjne) { + result.push(...generateDaneIdentyfikacyjneTPodmiot1Dto(podmiot.DaneIdentyfikacyjne)); + } + if (podmiot.Adres) { + result.push(formatText('Adres', [FormatTyp.Label, FormatTyp.LabelMargin]), generateAdres(podmiot.Adres)); + } + return result; +} diff --git a/src/lib-public/generators/FA_RR/Podmiot2.spec.ts b/src/lib-public/generators/FA_RR/Podmiot2.spec.ts new file mode 100644 index 00000000..3bf39b93 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot2.spec.ts @@ -0,0 +1,102 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Content } from 'pdfmake/interfaces'; +import type { Podmiot1Class } from '../../types/FaRR.types'; +import { generatePodmiot2 } from './Podmiot2'; +import { createHeader, formatText } from '../../../shared/PDF-functions'; +import { generateAdres } from './Adres'; +import { generateDaneIdentyfikacyjneTPodmiot2Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot2Dto'; +import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' } as Content]), + createLabelText: vi.fn((label: string, value: any): Content[] => [ + { text: `${label}${value ?? ''}` } as Content, + ]), + formatText: vi.fn((text: any, style?: any): Content => ({ text: String(text), style }) as Content), + getTable: vi.fn((data: any) => (data ? (Array.isArray(data) ? data : [data]) : [])), + hasValue: vi.fn((val) => Boolean(val && val._text)), +})); + +vi.mock('./Adres', async () => { + const actual = await vi.importActual('./Adres'); + + return { + ...actual, + generateAdres: vi.fn((): Content[] => [{ text: 'mockAddress' } as Content]), + }; +}); + +vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot2Dto', async () => { + const actual = await vi.importActual('./PodmiotDaneIdentyfikacyjneTPodmiot2Dto'); + + return { + ...actual, + generateDaneIdentyfikacyjneTPodmiot2Dto: vi.fn((): Content[] => [ + { text: 'mockDaneIdentyfikacyjne' } as Content, + ]), + }; +}); + +vi.mock('./PodmiotDaneKontaktowe', async () => { + const actual = await vi.importActual('./PodmiotDaneKontaktowe'); + + return { + ...actual, + generateDaneKontaktowe: vi.fn((): Content[] => [{ text: 'mockDaneKontaktowe' } as Content]), + }; +}); + +describe(generatePodmiot2.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('generates basic header and identifiers', () => { + const podmiot: Partial = { + DaneIdentyfikacyjne: { NIP: { _text: 'nip' }, Nazwa: { _text: 'Nazwa' } }, + }; + const result = generatePodmiot2(podmiot as Podmiot1Class); + + expect(createHeader).toHaveBeenCalledWith('Nabywca'); + expect(generateDaneIdentyfikacyjneTPodmiot2Dto).toHaveBeenCalledWith({ + NIP: { _text: 'nip' }, + Nazwa: { _text: 'Nazwa' }, + }); + expect(result.some((c) => (c as any).text === 'mockDaneIdentyfikacyjne')).toBe(true); + expect(result[0]).toEqual({ text: 'Nabywca', style: 'header' }); + }); + + it('generates identification data if provided', () => { + const podmiot: Partial = { + DaneIdentyfikacyjne: { NIP: { _text: 'nip' }, Nazwa: { _text: 'Nazwa' } }, + }; + const result = generatePodmiot2(podmiot as Podmiot1Class); + + expect(generateDaneIdentyfikacyjneTPodmiot2Dto).toHaveBeenCalledWith(podmiot.DaneIdentyfikacyjne); + expect(result.some((c: any) => c.text === 'mockDaneIdentyfikacyjne')).toBe(true); + }); + + it('generates address and correspondence address', () => { + const podmiot: Partial = { + Adres: { KodKraju: { _text: 'PL' } }, + AdresKoresp: { KodKraju: { _text: 'PL-K' } }, + }; + const result = generatePodmiot2(podmiot as Podmiot1Class); + + expect(generateAdres).toHaveBeenCalledWith(podmiot.Adres); + expect(generateAdres).toHaveBeenCalledWith(podmiot.AdresKoresp); + expect(formatText).toHaveBeenCalledWith('Adres', ['Label', 'LabelMargin']); + expect(formatText).toHaveBeenCalledWith('Adres do korespondencji', ['Label', 'LabelMargin']); + expect(result.some((c: any) => c.text === 'mockAddress')).toBe(true); + }); + + it('generates contact data', () => { + const podmiot: Partial = { + DaneKontaktowe: [{ Telefon: { _text: '123' } }], + }; + const result = generatePodmiot2(podmiot as Podmiot1Class); + + expect(generateDaneKontaktowe).toHaveBeenCalledWith(podmiot.DaneKontaktowe); + expect(result.some((c: any) => c.text === 'mockDaneKontaktowe')).toBe(true); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Podmiot2.ts b/src/lib-public/generators/FA_RR/Podmiot2.ts new file mode 100644 index 00000000..2645ce41 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot2.ts @@ -0,0 +1,38 @@ +import { Content } from 'pdfmake/interfaces'; +import { createHeader, createLabelText, formatText, getValue, hasValue } from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { generateAdres } from './Adres'; +import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; +import { Podmiot1Class } from '../../types/FaRR.types'; +import { TAXPAYER_STATUS } from '../../../shared/consts/FA.const'; +import { generateDaneIdentyfikacyjneTPodmiot2Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot2Dto'; + +export function generatePodmiot2(podmiot2: Podmiot1Class): Content[] { + const result: Content[] = createHeader('Nabywca'); + + if (podmiot2.DaneIdentyfikacyjne) { + result.push(...generateDaneIdentyfikacyjneTPodmiot2Dto(podmiot2.DaneIdentyfikacyjne)); + } + + if (podmiot2.Adres) { + result.push(formatText('Adres', [FormatTyp.Label, FormatTyp.LabelMargin]), generateAdres(podmiot2.Adres)); + } + if (podmiot2.AdresKoresp) { + result.push( + formatText('Adres do korespondencji', [FormatTyp.Label, FormatTyp.LabelMargin]), + ...generateAdres(podmiot2.AdresKoresp) + ); + } + if (podmiot2.DaneKontaktowe) { + result.push( + formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin]), + ...generateDaneKontaktowe(podmiot2.DaneKontaktowe) + ); + } + if (hasValue(podmiot2.StatusInfoPodatnika)) { + const statusInfo: string = TAXPAYER_STATUS[getValue(podmiot2.StatusInfoPodatnika)!]; + + result.push(createLabelText('Status podatnika: ', statusInfo)); + } + return result; +} diff --git a/src/lib-public/generators/FA_RR/Podmiot2Podmiot2k.spec.ts b/src/lib-public/generators/FA_RR/Podmiot2Podmiot2k.spec.ts new file mode 100644 index 00000000..6046ac20 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot2Podmiot2k.spec.ts @@ -0,0 +1,119 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Content } from 'pdfmake/interfaces'; +import { Adres, Podmiot1Class, Podmiot1KClass } from '../../types/FaRR.types'; +import { generateAdres } from './Adres'; +import { generatePodmiot2Podmiot2K } from './Podmiot2Podmiot2k'; +import { generatePodmiot2 } from './Podmiot2'; +import { generatePodmiot1Podmiot1K } from './Podmiot1Podmiot1K'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createHeader: vi.fn((text: string): Content[] => [{ text, style: 'header' }]), + createLabelText: vi.fn((label: string, value: any): Content[] => [{ text: `${label}${value ?? ''}` }]), + formatText: vi.fn((text: string, style?: any): Content => ({ text, style })), + getTable: vi.fn((data: any) => data || []), + hasValue: vi.fn((value: any) => value !== undefined && value !== null), + verticalSpacing: vi.fn((margin: number) => ({ margin })), + generateColumns: vi.fn((left, right) => ({ columns: [left, right] })), +})); + +vi.mock('./Adres', () => ({ + generateAdres: vi.fn((adres: any): Content[] => [{ text: 'mockAddress' }]), +})); + +vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot2Dto', () => ({ + generateDaneIdentyfikacyjneTPodmiot2Dto: vi.fn((data: any): Content[] => [ + { text: 'mockDaneIdentyfikacyjne' }, + ]), +})); + +vi.mock('./PodmiotDaneKontaktowe', () => ({ + generateDaneKontaktowe: vi.fn((data: any): Content[] => [{ text: 'mockDaneKontaktowe' }]), +})); + +describe(generatePodmiot2Podmiot2K.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('generates main header and columns', () => { + const podmiot2: Podmiot1Class = { + DaneIdentyfikacyjne: { + NIP: { _text: '1111111' }, + Nazwa: { _text: 'a' }, + }, + }; + const podmiot2K: Podmiot1KClass = {}; + const result: any = generatePodmiot2Podmiot2K(podmiot2, podmiot2K); + expect(result[0]).toEqual({ text: 'Nabywca', style: 'header' }); + + expect(result[1]).toHaveProperty('columns'); + expect(Array.isArray(result[1].columns[0])).toBe(true); + expect(Array.isArray(result[1].columns[1])).toBe(true); + expect(result[1].columns[0].length).toBeGreaterThan(0); + expect(result[1].columns[1].length).toBe(0); + + expect(result[2]).toHaveProperty('columns'); + expect(Array.isArray(result[2].columns[0])).toBe(true); + expect(result[2].columns[0].length).toBeGreaterThan(0); + + expect(result[3]).toEqual({ margin: 1 }); + }); + + it('calls generateAdres if AdresKoresp exists', () => { + const podmiot2: Podmiot1Class = { AdresKoresp: { AdresL1: { _text: 'Ulica Testowa 1' } } }; + const podmiot2K: Podmiot1KClass = {}; + const result = generatePodmiot2(podmiot2); + + generatePodmiot2Podmiot2K(podmiot2, podmiot2K); + expect(generateAdres).toHaveBeenCalledWith(podmiot2.AdresKoresp); + }); + + it('generates corrected content columns', () => { + const podmiot2: Podmiot1Class = { + DaneIdentyfikacyjne: { + NIP: { _text: '1111111' }, + Nazwa: { _text: 'a' }, + }, + Adres: { AdresL1: { _text: 'Ulica Testowa 2' } }, + }; + const podmiot2K: Podmiot1KClass = { + Adres: { AdresL1: { _text: 'Ulica Testowa 1' } }, + }; + const result = generatePodmiot2Podmiot2K(podmiot2, podmiot2K) as any; + + expect(Array.isArray(result[2].columns[0])).toBe(true); + expect(result[2].columns[0].length).toBeGreaterThanOrEqual(0); + }); + + it('adds vertical spacing at the end', () => { + const podmiot2: Podmiot1Class = { + DaneIdentyfikacyjne: { + NIP: { _text: '1111111' }, + Nazwa: { _text: 'a' }, + }, + }; + const podmiot2K: Podmiot1KClass = {}; + const result = generatePodmiot2Podmiot2K(podmiot2, podmiot2K); + + expect(result[result.length - 1]).toEqual({ margin: 1 }); + }); + + it('handles all fields together', () => { + const podmiot2: Podmiot1Class = { + DaneIdentyfikacyjne: { NIP: { _text: '1111111' } }, + DaneKontaktowe: [{ Telefon: { _text: '123' } }], + Adres: { AdresL1: { _text: 'Ulica Testowa 1' } }, + }; + const podmiot2K: Podmiot1KClass = { Adres: { AdresL1: { _text: 'Ulica Testowa 2' } } }; + const result = generatePodmiot2Podmiot2K(podmiot2, podmiot2K) as any; + + expect(result.length).toBeGreaterThan(3); + expect(result[0]).toEqual({ text: 'Nabywca', style: 'header' }); + + expect(result[2]).toHaveProperty('columns'); + expect(Array.isArray(result[2].columns[0])).toBe(true); + expect(Array.isArray(result[2].columns[1])).toBe(false); + expect(result[2].columns[0].length).toBeGreaterThanOrEqual(0); + expect(result[result.length - 1]).toHaveProperty('margin'); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Podmiot2Podmiot2k.ts b/src/lib-public/generators/FA_RR/Podmiot2Podmiot2k.ts new file mode 100644 index 00000000..b3bb8a14 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot2Podmiot2k.ts @@ -0,0 +1,80 @@ +import { Content } from 'pdfmake/interfaces'; +import { + createHeader, + createLabelText, + formatText, + generateColumns, + getTable, + getValue, + hasValue, + verticalSpacing, +} from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; +import { generateAdres } from '../FA2/Adres'; +import { Podmiot1Class, Podmiot1KClass } from '../../types/FaRR.types'; +import { generateDaneIdentyfikacyjneTPodmiot2Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot2Dto'; +import { TAXPAYER_STATUS } from '../../../shared/consts/FA.const'; + +export function generatePodmiot2Podmiot2K(podmiot2: Podmiot1Class, podmiot2K: Podmiot1KClass): Content[] { + const result: Content[] = createHeader('Nabywca'); + let firstColumn: Content[] = []; + let secondColumn: Content[] = []; + + if (podmiot2.DaneIdentyfikacyjne) { + firstColumn.push( + createHeader('Dane identyfikacyjne'), + ...generateDaneIdentyfikacyjneTPodmiot2Dto(podmiot2.DaneIdentyfikacyjne) + ); + } + + if (podmiot2.DaneKontaktowe) { + firstColumn.push(formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin])); + firstColumn.push(generateDaneKontaktowe(getTable(podmiot2.DaneKontaktowe))); + } + + if (hasValue(podmiot2.StatusInfoPodatnika)) { + const statusInfo: string = TAXPAYER_STATUS[getValue(podmiot2.StatusInfoPodatnika)!]; + + firstColumn.push(createLabelText('Status podatnika: ', statusInfo)); + } + + if (firstColumn.length) { + result.push({ + columns: [firstColumn, []], + columnGap: 20, + }); + } + firstColumn = generateCorrectedContent(podmiot2K, 'Treść korygowana'); + secondColumn = generateCorrectedContent(podmiot2, 'Treść korygująca'); + + if (podmiot2.AdresKoresp) { + secondColumn.push( + formatText('Adres do korespondencji', [FormatTyp.Label, FormatTyp.LabelMargin]), + generateAdres(podmiot2.AdresKoresp) + ); + } + + if (firstColumn.length || secondColumn.length) { + result.push(generateColumns([firstColumn, secondColumn])); + } + if (result.length) { + result.push(verticalSpacing(1)); + } + + return result; +} + +export function generateCorrectedContent(podmiot: Podmiot1Class | Podmiot1KClass, header: string): Content[] { + const result: Content[] = []; + + result.push(createHeader(header)); + + if (podmiot.DaneIdentyfikacyjne) { + result.push(...generateDaneIdentyfikacyjneTPodmiot2Dto(podmiot.DaneIdentyfikacyjne)); + } + if (podmiot.Adres) { + result.push(formatText('Adres', [FormatTyp.Label, FormatTyp.LabelMargin]), generateAdres(podmiot.Adres)); + } + return result; +} diff --git a/src/lib-public/generators/FA_RR/Podmiot3.spec.ts b/src/lib-public/generators/FA_RR/Podmiot3.spec.ts new file mode 100644 index 00000000..c95da7e2 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot3.spec.ts @@ -0,0 +1,116 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { generatePodmiot3 } from './Podmiot3'; +import type { Podmiot3 } from '../../types/FaRR.types'; +import * as PDFFunctions from '../../../shared/PDF-functions'; +import * as AdresModule from './Adres'; +import * as DaneIdModule from './PodmiotDaneIdentyfikacyjneTPodmiot3Dto'; +import * as DaneKontaktoweModule from './PodmiotDaneKontaktowe'; + +vi.mock('../../../shared/PDF-functions'); +vi.mock('./Adres'); +vi.mock('./PodmiotDaneIdentyfikacyjneTPodmiot3Dto'); +vi.mock('./PodmiotDaneKontaktowe'); + +describe(generatePodmiot3.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(PDFFunctions.createHeader).mockImplementation((text) => [{ text }]); + vi.mocked(PDFFunctions.createLabelText).mockImplementation((label, value) => [ + { text: `${label}${value ?? ''}` }, + ]); + vi.mocked(PDFFunctions.formatText).mockImplementation((text, style) => ({ text, style }) as any); + vi.mocked(PDFFunctions.generateLine).mockReturnValue({ line: true } as any); + vi.mocked(PDFFunctions.generateTwoColumns).mockImplementation((c1, c2) => ({ columns: [c1, c2] })); + + vi.mocked(AdresModule.generateAdres).mockReturnValue([{ text: 'mockAddress' }]); + vi.mocked(DaneIdModule.generateDaneIdentyfikacyjneTPodmiot3Dto).mockReturnValue([ + { text: 'mockDaneIdentyfikacyjne' }, + ]); + vi.mocked(DaneKontaktoweModule.generateDaneKontaktowe).mockReturnValue([{ text: 'mockDaneKontaktowe' }]); + }); + + it('generates minimal podmiot3 structure', () => { + const podmiot: Podmiot3 = { DaneIdentyfikacyjne: {} }; + const result = generatePodmiot3(podmiot, 0) as any; + + expect(result[0]).toEqual({ line: true }); + expect(result[1]).toHaveProperty('columns'); + expect(result[1].columns[0].length).toBeGreaterThan(0); + expect(result[1].columns[1].length).toBe(0); + }); + + it('adds column2 when Adres is present', () => { + const podmiot: Podmiot3 = { + DaneIdentyfikacyjne: {}, + Adres: {}, + }; + + const result = generatePodmiot3(podmiot, 0) as any; + const column2 = result[1].columns[1]; + + expect(column2[0]).toHaveProperty('text', 'Adres'); + }); + + it('adds column2 when AdresKoresp is present', () => { + const podmiot: Podmiot3 = { + DaneIdentyfikacyjne: {}, + AdresKoresp: {}, + }; + + const result = generatePodmiot3(podmiot, 0) as any; + const column2 = result[1].columns[1]; + + expect(column2[0]).toHaveProperty('text', 'Adres do korespondencji'); + }); + + it('adds column2 when DaneKontaktowe is present', () => { + const podmiot: Podmiot3 = { + DaneIdentyfikacyjne: {}, + DaneKontaktowe: [{}], + }; + + const result = generatePodmiot3(podmiot, 0) as any; + const column2 = result[1].columns[1]; + + expect(column2[0]).toHaveProperty('text', 'Dane kontaktowe'); + expect(column2[1]).toEqual({ text: 'mockDaneKontaktowe' }); + }); + + it('handles all fields together', () => { + const podmiot: Podmiot3 = { + DaneIdentyfikacyjne: {}, + Rola: '1', + OpisRoli: 'opisRoli', + Adres: {}, + AdresKoresp: {}, + DaneKontaktowe: [{}], + } as any; + + const result = generatePodmiot3(podmiot, 1) as any; + + const column1 = result[1].columns[0] as any; + const column2 = result[1].columns[1] as any; + + const flattenContent = (arr: any[]): any[] => + arr.flatMap((item) => (Array.isArray(item) ? flattenContent(item) : item)); + + const flatColumn1 = flattenContent(column1); + const flatColumn2 = flattenContent(column2); + + expect(flatColumn1.some((c) => typeof c.text === 'string' && c.text.includes('Podmiot inny 2'))).toBe( + true + ); + expect( + flatColumn1.some( + (c) => + typeof c.text === 'string' && + c.text.includes('Rola: Faktor - w przypadku gdy na fakturze występują dane faktora') + ) + ).toBe(true); + expect( + flatColumn1.some((c) => typeof c.text === 'string' && c.text.includes('Opis roli: opisRoli')) + ).toBe(true); + + expect(flatColumn2.some((c) => c.text === 'mockDaneKontaktowe')).toBe(true); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Podmiot3.ts b/src/lib-public/generators/FA_RR/Podmiot3.ts new file mode 100644 index 00000000..70361bbc --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmiot3.ts @@ -0,0 +1,47 @@ +import { Content } from 'pdfmake/interfaces'; +import { + createHeader, + createLabelText, + formatText, + generateLine, + generateTwoColumns, +} from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { Podmiot3 } from '../../types/FaRR.types'; +import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot3Dto'; +import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; +import { translateMap } from '@shared/generators/common/functions'; +import { generateAdres } from './Adres'; +import { FARRRolaPodmiotu3 } from '../../../shared/consts/FARR.const'; + +export function generatePodmiot3(podmiot: Podmiot3, index: number): Content[] { + const result: Content[] = []; + + result.push(generateLine()); + const column1: Content[] = [ + ...createHeader(`Podmiot inny ${index + 1}`), + ...generateDaneIdentyfikacyjneTPodmiot3Dto(podmiot.DaneIdentyfikacyjne), + createLabelText('Rola: ', translateMap(podmiot.Rola, FARRRolaPodmiotu3)), + createLabelText('Opis roli: ', podmiot.OpisRoli), + ]; + + const column2: Content[] = []; + + if (podmiot.Adres) { + column2.push(formatText('Adres', [FormatTyp.Label, FormatTyp.LabelMargin]), generateAdres(podmiot.Adres)); + } + if (podmiot.AdresKoresp) { + column2.push( + formatText('Adres do korespondencji', [FormatTyp.Label, FormatTyp.LabelMargin]), + ...generateAdres(podmiot.AdresKoresp) + ); + } + if (podmiot.DaneKontaktowe) { + column2.push( + formatText('Dane kontaktowe', [FormatTyp.Label, FormatTyp.LabelMargin]), + ...generateDaneKontaktowe(podmiot.DaneKontaktowe) + ); + } + result.push(generateTwoColumns(column1, column2)); + return result; +} diff --git a/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot1Dto.spec.ts b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot1Dto.spec.ts new file mode 100644 index 00000000..3f6eb0ca --- /dev/null +++ b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot1Dto.spec.ts @@ -0,0 +1,37 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Podmiot1KDaneIdentyfikacyjne, FP } from '../../types/FaRR.types'; +import { createLabelText } from '../../../shared/PDF-functions'; +import { generateDaneIdentyfikacyjneTPodmiot1Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot1Dto'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createLabelText: vi.fn((label: string, value: FP) => ({ text: `${label}${value._text}` })), +})); + +describe(generateDaneIdentyfikacyjneTPodmiot1Dto.name, () => { + const mockData: Podmiot1KDaneIdentyfikacyjne = { + NIP: { _text: '1234567891' }, + Nazwa: { _text: 'Nazwa' }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('calls createLabelText twice with correct arguments', () => { + const result = generateDaneIdentyfikacyjneTPodmiot1Dto(mockData); + expect(createLabelText).toHaveBeenCalledTimes(2); + expect(createLabelText).toHaveBeenCalledWith('NIP: ', mockData.NIP); + expect(createLabelText).toHaveBeenCalledWith('Nazwa: ', mockData.Nazwa); + expect(result).toEqual([{ text: 'NIP: 1234567891' }, { text: 'Nazwa: Nazwa' }]); + }); + + it('handles empty values correctly', () => { + const result = generateDaneIdentyfikacyjneTPodmiot1Dto({ + NIP: { _text: '' }, + Nazwa: { _text: '' }, + } as Podmiot1KDaneIdentyfikacyjne); + expect(createLabelText).toHaveBeenCalledWith('NIP: ', { _text: '' }); + expect(createLabelText).toHaveBeenCalledWith('Nazwa: ', { _text: '' }); + expect(result).toEqual([{ text: 'NIP: ' }, { text: 'Nazwa: ' }]); + }); +}); diff --git a/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot1Dto.ts b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot1Dto.ts new file mode 100644 index 00000000..9e062769 --- /dev/null +++ b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot1Dto.ts @@ -0,0 +1,12 @@ +import { Content } from 'pdfmake/interfaces'; +import { createLabelText } from '../../../shared/PDF-functions'; +import { Podmiot1KDaneIdentyfikacyjne } from '../../types/FaRR.types'; + +export function generateDaneIdentyfikacyjneTPodmiot1Dto( + daneIdentyfikacyjne: Podmiot1KDaneIdentyfikacyjne +): Content[] { + return [ + createLabelText('NIP: ', daneIdentyfikacyjne.NIP), + createLabelText('Nazwa: ', daneIdentyfikacyjne.Nazwa), + ]; +} diff --git a/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts new file mode 100644 index 00000000..16e776ff --- /dev/null +++ b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.spec.ts @@ -0,0 +1,27 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createLabelText } from '../../../shared/PDF-functions'; +import { generateDaneIdentyfikacyjneTPodmiot2Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot2Dto'; +import { Podmiot1KDaneIdentyfikacyjne } from '../../types/FaRR.types'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createLabelText: vi.fn((label: string, value: any) => ({ text: `${label}${value?._text ?? value}` })), +})); + +describe(generateDaneIdentyfikacyjneTPodmiot2Dto.name, () => { + const baseData: Podmiot1KDaneIdentyfikacyjne = { + NIP: { _text: '1234567890' }, + Nazwa: { _text: 'Test Company' }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('always includes NIP and Nazwa', () => { + const result = generateDaneIdentyfikacyjneTPodmiot2Dto(baseData); + expect(createLabelText).toHaveBeenCalledWith('NIP: ', baseData.NIP); + expect(createLabelText).toHaveBeenCalledWith('Nazwa: ', baseData.Nazwa); + expect(result).toContainEqual({ text: 'NIP: 1234567890' }); + expect(result).toContainEqual({ text: 'Nazwa: Test Company' }); + }); +}); diff --git a/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts new file mode 100644 index 00000000..e0efebb9 --- /dev/null +++ b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot2Dto.ts @@ -0,0 +1,12 @@ +import { Content } from 'pdfmake/interfaces'; +import { createLabelText } from '../../../shared/PDF-functions'; +import { Podmiot1KDaneIdentyfikacyjne } from '../../types/FaRR.types'; + +export function generateDaneIdentyfikacyjneTPodmiot2Dto( + daneIdentyfikacyjne: Podmiot1KDaneIdentyfikacyjne +): Content[] { + return [ + createLabelText('NIP: ', daneIdentyfikacyjne.NIP), + createLabelText('Nazwa: ', daneIdentyfikacyjne.Nazwa), + ]; +} diff --git a/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot3Dto.spec.ts b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot3Dto.spec.ts new file mode 100644 index 00000000..6391b9c5 --- /dev/null +++ b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot3Dto.spec.ts @@ -0,0 +1,71 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { generateDaneIdentyfikacyjneTPodmiot3Dto } from './PodmiotDaneIdentyfikacyjneTPodmiot3Dto'; +import { createLabelText, createLabelTextArray, hasValue } from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { Podmiot3DaneIdentyfikacyjne } from '../../types/FaRR.types'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createLabelText: vi.fn((label: string, value: any, type: FormatTyp) => ({ + text: `${label}${value?._text ?? value}`, + type, + })), + createLabelTextArray: vi.fn((arr: any[]) => ({ + text: arr.map((a) => a.value?._text ?? a.value).join(''), + })), + hasValue: vi.fn((val: any) => Boolean(val && val._text)), + getValue: vi.fn((obj: any) => obj?._text ?? obj ?? ''), +})); + +describe(generateDaneIdentyfikacyjneTPodmiot3Dto.name, () => { + const baseData: Podmiot3DaneIdentyfikacyjne = { + NIP: { _text: '1234567890' }, + Nazwa: { _text: 'Test Company' }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns empty array when daneIdentyfikacyjne is undefined', () => { + const result = generateDaneIdentyfikacyjneTPodmiot3Dto(undefined); + expect(result).toEqual([]); + }); + + it('uses NIP when provided', () => { + const result = generateDaneIdentyfikacyjneTPodmiot3Dto(baseData); + expect(hasValue).toHaveBeenCalledWith(baseData.NIP); + expect(createLabelText).toHaveBeenCalledWith('NIP: ', baseData.NIP, FormatTyp.Default); + expect(createLabelText).toHaveBeenCalledWith('Nazwa: ', baseData.Nazwa, FormatTyp.Default); + expect(result.some((r) => (r as any).text.includes('NIP: 1234567890'))).toBe(true); + }); + + it('uses IDWew when NIP is not provided', () => { + const data: Podmiot3DaneIdentyfikacyjne = { + IDWew: { _text: 'INT-001' }, + Nazwa: { _text: 'Internal' }, + }; + (hasValue as any).mockImplementation((val: any) => Boolean(val && val._text)); + const result = generateDaneIdentyfikacyjneTPodmiot3Dto(data); + expect(createLabelText).toHaveBeenCalledWith('Identyfikator wewnętrzny: ', data.IDWew, FormatTyp.Default); + expect(result.some((r) => (r as any).text.includes('Identyfikator wewnętrzny'))).toBe(true); + }); + + it('adds "Brak identyfikatora" when BrakID._text is "1"', () => { + const data: Podmiot3DaneIdentyfikacyjne = { + BrakID: { _text: '1' }, + Nazwa: { _text: 'Missing ID' }, + }; + (hasValue as any).mockReturnValue(false); + const result = generateDaneIdentyfikacyjneTPodmiot3Dto(data); + expect(createLabelText).toHaveBeenCalledWith('Brak identyfikatora', ' ', FormatTyp.Default); + expect(result.some((r) => (r as any).text.includes('Brak identyfikatora'))).toBe(true); + }); + + it('adds Nazwa when present', () => { + const data: Podmiot3DaneIdentyfikacyjne = { Nazwa: { _text: 'Only Name' } } as any; + (hasValue as any).mockImplementation((val: any) => Boolean(val && val._text)); + const result = generateDaneIdentyfikacyjneTPodmiot3Dto(data); + expect(createLabelText).toHaveBeenCalledWith('Nazwa: ', data.Nazwa, FormatTyp.Default); + expect(result.some((r) => (r as any).text.includes('Nazwa: Only Name'))).toBe(true); + }); +}); diff --git a/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot3Dto.ts b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot3Dto.ts new file mode 100644 index 00000000..1466280a --- /dev/null +++ b/src/lib-public/generators/FA_RR/PodmiotDaneIdentyfikacyjneTPodmiot3Dto.ts @@ -0,0 +1,26 @@ +import { Content } from 'pdfmake/interfaces'; +import { createLabelText, getValue, hasValue } from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { Podmiot3DaneIdentyfikacyjne } from '../../types/FaRR.types'; + +export function generateDaneIdentyfikacyjneTPodmiot3Dto( + daneIdentyfikacyjne: Podmiot3DaneIdentyfikacyjne | undefined +): Content[] { + if (!daneIdentyfikacyjne) { + return []; + } + const result: Content[] = []; + + if (hasValue(daneIdentyfikacyjne.NIP)) { + result.push(createLabelText('NIP: ', daneIdentyfikacyjne.NIP, FormatTyp.Default)); + } else if (hasValue(daneIdentyfikacyjne.IDWew)) { + result.push(createLabelText('Identyfikator wewnętrzny: ', daneIdentyfikacyjne.IDWew, FormatTyp.Default)); + } else if (getValue(daneIdentyfikacyjne.BrakID) === '1') { + result.push(createLabelText('Brak identyfikatora', ' ', FormatTyp.Default)); + } + + if (hasValue(daneIdentyfikacyjne.Nazwa)) { + result.push(createLabelText('Nazwa: ', daneIdentyfikacyjne.Nazwa, FormatTyp.Default)); + } + return result; +} diff --git a/src/lib-public/generators/FA_RR/PodmiotDaneKontaktowe.spec.ts b/src/lib-public/generators/FA_RR/PodmiotDaneKontaktowe.spec.ts new file mode 100644 index 00000000..e0402302 --- /dev/null +++ b/src/lib-public/generators/FA_RR/PodmiotDaneKontaktowe.spec.ts @@ -0,0 +1,43 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { generateDaneKontaktowe } from './PodmiotDaneKontaktowe'; +import type { DaneKontaktowe } from '../../types/FaRR.types'; +import { createLabelText, getTable } from '../../../shared/PDF-functions'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createLabelText: vi.fn((label: string, value: any) => ({ text: `${label}${value?._text ?? value}` })), + getTable: vi.fn((data: any) => data), +})); + +describe(generateDaneKontaktowe.name, () => { + const mockData: DaneKontaktowe[] = [ + { Email: { _text: 'test@example.com' }, Telefon: { _text: '123456789' } }, + { Email: { _text: 'info@example.com' }, Telefon: { _text: '987654321' } }, + ] as any; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns mapped Content array when daneKontaktowe is provided', () => { + const result = generateDaneKontaktowe(mockData); + expect(getTable).toHaveBeenCalledWith(mockData); + expect(createLabelText).toHaveBeenCalledWith('E-mail: ', mockData[0].Email); + expect(createLabelText).toHaveBeenCalledWith('Tel.: ', mockData[0].Telefon); + expect(Array.isArray(result)).toBe(true); + expect(result?.length).toBe(2); + expect(result?.[0]).toEqual([{ text: 'E-mail: test@example.com' }, { text: 'Tel.: 123456789' }]); + }); + + it('returns undefined when getTable returns undefined', () => { + (getTable as any).mockReturnValueOnce(undefined); + const result = generateDaneKontaktowe(mockData); + expect(result).toBeUndefined(); + expect(createLabelText).not.toHaveBeenCalled(); + }); + + it('handles empty daneKontaktowe array', () => { + const result = generateDaneKontaktowe([]); + expect(getTable).toHaveBeenCalledWith([]); + expect(result).toEqual([]); + }); +}); diff --git a/src/lib-public/generators/FA_RR/PodmiotDaneKontaktowe.ts b/src/lib-public/generators/FA_RR/PodmiotDaneKontaktowe.ts new file mode 100644 index 00000000..e07a4250 --- /dev/null +++ b/src/lib-public/generators/FA_RR/PodmiotDaneKontaktowe.ts @@ -0,0 +1,12 @@ +import { Content } from 'pdfmake/interfaces'; +import { createLabelText, getTable } from '../../../shared/PDF-functions'; +import { DaneKontaktowe } from '../../types/FaRR.types'; + +export function generateDaneKontaktowe(daneKontaktowe: DaneKontaktowe[]): Content[] { + return getTable(daneKontaktowe)?.map((daneKontaktowe) => { + return [ + createLabelText('E-mail: ', daneKontaktowe.Email), + createLabelText('Tel.: ', daneKontaktowe.Telefon), + ]; + }); +} diff --git a/src/lib-public/generators/FA_RR/Podmioty.spec.ts b/src/lib-public/generators/FA_RR/Podmioty.spec.ts new file mode 100644 index 00000000..65b317c7 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmioty.spec.ts @@ -0,0 +1,83 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { generatePodmioty } from './Podmioty'; +import { createSection } from '../../../shared/PDF-functions'; +import { generatePodmiot1Podmiot1K } from './Podmiot1Podmiot1K'; +import { generatePodmiot2Podmiot2K } from './Podmiot2Podmiot2k'; +import { generatePodmiot3 } from './Podmiot3'; +import { FaRR } from '../../types/FaRR.types'; +import { generatePodmiot1 } from './Podmiot1'; +import { generatePodmiot2 } from './Podmiot2'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createSection: vi.fn((content: any, flag: boolean) => ({ content, flag })), + getTable: vi.fn((data: any) => data || []), + getValue: vi.fn((val: any) => val?._text ?? val), + generateColumns: vi.fn((left, right) => ({ columns: [left, right] })), +})); + +vi.mock('./Podmiot1', () => ({ + generatePodmiot1: vi.fn((data: any) => ({ text: 'Podmiot1 content' })), +})); + +vi.mock('./Podmiot1Podmiot1K', () => ({ + generatePodmiot1Podmiot1K: vi.fn((podmiot1: any, podmiot1K: any) => ({ text: 'Podmiot1K content' })), +})); + +vi.mock('./Podmiot2', () => ({ + generatePodmiot2: vi.fn((data: any) => ({ text: 'Podmiot2 content' })), +})); + +vi.mock('./Podmiot2Podmiot2k', () => ({ + generatePodmiot2Podmiot2K: vi.fn((podmiot2: any, podmiot2K: any) => ({ text: 'Podmiot2K content' })), +})); + +vi.mock('./Podmiot3', () => ({ + generatePodmiot3: vi.fn((data: any, idx: number) => ({ text: `Podmiot3 content ${idx}` })), +})); + +describe(generatePodmioty.name, () => { + const baseInvoice: FaRR = { + FakturaRR: { + Podmiot1K: { DaneIdentyfikacyjne: {} }, + Podmiot2K: { DaneIdentyfikacyjne: {} }, + }, + Podmiot1: { DaneIdentyfikacyjne: {} }, + Podmiot2: { DaneIdentyfikacyjne: {} }, + Podmiot3: [{ Rola: { _text: '4' } }], + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('creates section with all podmioty when Podmiot1K and Podmiot2K are present', () => { + const result = generatePodmioty(baseInvoice) as any; + const content = result.content; + + expect(generatePodmiot1Podmiot1K).toHaveBeenCalledWith( + baseInvoice.Podmiot1, + baseInvoice.FakturaRR?.Podmiot1K + ); + expect(generatePodmiot2Podmiot2K).toHaveBeenCalledWith( + baseInvoice.Podmiot2, + baseInvoice.FakturaRR?.Podmiot2K + ); + expect(generatePodmiot3).toHaveBeenCalled(); + expect(createSection).toHaveBeenCalled(); + expect(result.flag).toBe(true); + expect(content).toHaveLength(3); + expect(content[0]).toEqual({ text: 'Podmiot1K content' }); + expect(content[1]).toEqual({ text: 'Podmiot2K content' }); + expect(content[2]).toEqual({ text: 'Podmiot3 content 0' }); + }); + + it('calls generatePodmiot1 and generatePodmiot2 when no Podmiot1K and Podmiot2K', () => { + const invoice: FaRR = { Podmiot1: {}, Podmiot2: {} } as any; + const result = generatePodmioty(invoice) as any; + + expect(generatePodmiot1).toHaveBeenCalledWith(invoice.Podmiot1); + expect(generatePodmiot2).toHaveBeenCalledWith(invoice.Podmiot2); + expect(result.flag).toBe(true); + expect(result.content[0].length).toBeGreaterThan(0); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Podmioty.ts b/src/lib-public/generators/FA_RR/Podmioty.ts new file mode 100644 index 00000000..295ec62a --- /dev/null +++ b/src/lib-public/generators/FA_RR/Podmioty.ts @@ -0,0 +1,45 @@ +import { Content } from 'pdfmake/interfaces'; +import { createSection, generateColumns, getTable } from '../../../shared/PDF-functions'; +import { generatePodmiot1 } from './Podmiot1'; +import { generatePodmiot2 } from './Podmiot2'; +import { FaRR, Podmiot3 } from '../../types/FaRR.types'; +import { generatePodmiot3 } from './Podmiot3'; +import { generatePodmiot1Podmiot1K } from './Podmiot1Podmiot1K'; +import { generatePodmiot2Podmiot2K } from './Podmiot2Podmiot2k'; + +export function generatePodmioty(invoice: FaRR): Content[] { + const result: Content[] = []; + const podmiot3: Podmiot3[] = getTable(invoice.Podmiot3); + + if (invoice.FakturaRR?.Podmiot1K || invoice.FakturaRR?.Podmiot2K) { + if (invoice.FakturaRR?.Podmiot1K) { + result.push(generatePodmiot1Podmiot1K(invoice.Podmiot1 ?? {}, invoice.FakturaRR?.Podmiot1K)); + } else if (invoice.Podmiot1 != null) { + result.push(generatePodmiot1(invoice.Podmiot1)); + } + + if (invoice.FakturaRR?.Podmiot2K) { + result.push(generatePodmiot2Podmiot2K(invoice.Podmiot2 ?? {}, invoice.FakturaRR?.Podmiot2K)); + } else if (invoice.Podmiot2) { + result.push(createSection(generatePodmiot2(invoice.Podmiot2!), true)); + } + } else { + result.push([ + generateColumns([generatePodmiot1(invoice.Podmiot1!), generatePodmiot2(invoice.Podmiot2!)], { + margin: [0, 0, 0, 8], + columnGap: 20, + }), + ]); + } + + if (podmiot3.length > 0) { + podmiot3.forEach((podmiot, index) => { + result.push(generatePodmiot3(podmiot, index)); + if (index < podmiot3.length - 1) { + result.push({ margin: [0, 8, 0, 0], text: '' }); + } + }); + } + + return createSection(result, true, [0, 0, 0, 0]); +} diff --git a/src/lib-public/generators/FA_RR/RachunekBankowy.spec.ts b/src/lib-public/generators/FA_RR/RachunekBankowy.spec.ts new file mode 100644 index 00000000..ae36f80d --- /dev/null +++ b/src/lib-public/generators/FA_RR/RachunekBankowy.spec.ts @@ -0,0 +1,234 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { generujRachunekBankowy } from './RachunekBankowy'; +import * as PDFFunctions from '../../../shared/PDF-functions'; +import { makeBreakable } from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { RachunekBankowy } from '../../types/FaRR.types'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createHeader: vi.fn(), + createSection: vi.fn(), + formatText: vi.fn(), + makeBreakable: vi.fn(), + getValue: (val: any) => val?._text || '', + hasValue: (val: any) => val?._text || '', +})); + +describe(generujRachunekBankowy.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const mockAccount: RachunekBankowy = { + NrRB: { _text: '12345678901234567890123456' }, + SWIFT: { _text: 'BPKOPLPW' }, + NazwaBanku: { _text: 'PKO Bank Polski' }, + OpisRachunku: { _text: 'Rachunek główny' }, + }; + + beforeEach(() => { + vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any); + vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any); + vi.mocked(PDFFunctions.formatText).mockReturnValue('formatted' as any); + }); + + describe('when accounts is undefined or empty', () => { + it('should return empty array when accounts is undefined', () => { + const result = generujRachunekBankowy(undefined, 'Rachunek bankowy'); + + expect(result).toEqual([]); + }); + + it('should return empty array when accounts is empty array', () => { + const result = generujRachunekBankowy([], 'Rachunek bankowy'); + + expect(result).toEqual([]); + }); + }); + + describe('when accounts exist', () => { + it('should call createHeader with provided title', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Rachunek bankowy ', [0, 8, 0, 8]); + }); + + it('should call createHeader with empty string when title is not provided', () => { + generujRachunekBankowy([mockAccount]); + + expect(PDFFunctions.createHeader).toHaveBeenCalledWith('', [0, 8, 0, 8]); + }); + + it('should call createSection and return result', () => { + const mockSection = 'section'; + + vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); + + const result = generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), false, [0, 0, 0, 0]); + expect(result).toEqual(mockSection); + }); + + it('should format "Pełny numer rachunku" field', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + expect(PDFFunctions.formatText).toHaveBeenCalledWith('Pełny numer rachunku', FormatTyp.GrayBoldTitle); + expect(PDFFunctions.formatText).toHaveBeenCalledWith(mockAccount.NrRB?._text, FormatTyp.Default); + }); + + it('should format "Kod SWIFT" field', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + expect(PDFFunctions.formatText).toHaveBeenCalledWith('Kod SWIFT', FormatTyp.GrayBoldTitle); + expect(PDFFunctions.formatText).toHaveBeenCalledWith(mockAccount.SWIFT?._text, FormatTyp.Default); + }); + + it('should format "Pełny numer rachunku" field', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + expect(PDFFunctions.formatText).toHaveBeenCalledWith('Pełny numer rachunku', FormatTyp.GrayBoldTitle); + expect(PDFFunctions.formatText).toHaveBeenCalledWith( + mockAccount.NazwaBanku?._text ? makeBreakable('Bank', 20) : 'Tak', + FormatTyp.Default + ); + }); + + it('should format "Nazwa banku" field', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + expect(PDFFunctions.formatText).toHaveBeenCalledWith('Nazwa banku', FormatTyp.GrayBoldTitle); + expect(PDFFunctions.formatText).toHaveBeenCalledWith( + mockAccount.NazwaBanku?._text ? makeBreakable('Bank', 20) : 'Nazwa banku', + FormatTyp.Default + ); + }); + + it('should format "Opis rachunku" field', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + expect(PDFFunctions.formatText).toHaveBeenCalledWith('Opis rachunku', FormatTyp.GrayBoldTitle); + expect(PDFFunctions.formatText).toHaveBeenCalledWith( + mockAccount.NazwaBanku?._text ? makeBreakable('Bank', 20) : 'Nazwa banku', + FormatTyp.Default + ); + }); + + it('should create table structure with correct widths', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[]; + const tableContent = sectionCall[0][1]; + + expect(tableContent).toHaveProperty('table'); + expect(tableContent.table).toHaveProperty('widths', ['auto', '*']); + }); + + it('should create table structure with unbreakable property', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[]; + const tableContent = sectionCall[0][1]; + + expect(tableContent).toHaveProperty('unbreakable', true); + }); + + it('should create table with correct layout', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[]; + const tableContent = sectionCall[0][1]; + + expect(tableContent).toHaveProperty('layout'); + expect(tableContent.layout).toHaveProperty('hLineWidth'); + expect(tableContent.layout).toHaveProperty('hLineColor'); + expect(tableContent.layout).toHaveProperty('vLineWidth'); + expect(tableContent.layout).toHaveProperty('vLineColor'); + }); + + it('should create table with correct line colors', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[]; + const tableContent = sectionCall[0][1]; + + expect(tableContent.layout.hLineColor()).toBe('#BABABA'); + expect(tableContent.layout.vLineColor()).toBe('#BABABA'); + }); + + it('should create table with correct line widths', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[]; + const tableContent = sectionCall[0][1]; + + expect(tableContent.layout.hLineWidth()).toBe(1); + expect(tableContent.layout.vLineWidth()).toBe(1); + }); + + it('should handle account with undefined fields', () => { + const accountWithUndefined: RachunekBankowy = { + NrRB: { _text: undefined }, + SWIFT: { _text: undefined }, + NazwaBanku: { _text: undefined }, + OpisRachunku: { _text: undefined }, + }; + + generujRachunekBankowy([accountWithUndefined], 'Rachunek bankowy'); + + expect(PDFFunctions.formatText).toHaveBeenCalledWith('', FormatTyp.Default); + }); + }); + + describe('when multiple accounts exist', () => { + it('should create multiple tables for multiple accounts', () => { + const account2: RachunekBankowy = { + NrRB: { _text: '98765432109876543210987654' }, + SWIFT: { _text: 'PKOPPLPW' }, + NazwaBanku: { _text: 'mBank' }, + OpisRachunku: { _text: 'Rachunek pomocniczy' }, + }; + + generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy'); + + const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[]; + + expect(sectionCall.length).toBe(2); + }); + + it('should format all fields for all accounts', () => { + const account2: RachunekBankowy = { + NrRB: { _text: '98765432109876543210987654' }, + SWIFT: { _text: 'PKOPPLPW' }, + NazwaBanku: { _text: 'mBank' }, + OpisRachunku: { _text: 'Rachunek pomocniczy' }, + }; + + generujRachunekBankowy([mockAccount, account2], 'Rachunek bankowy'); + + expect(PDFFunctions.formatText).toHaveBeenCalledWith(mockAccount.NrRB?._text, FormatTyp.Default); + expect(PDFFunctions.formatText).toHaveBeenCalledWith(account2.NrRB?._text, FormatTyp.Default); + }); + }); + + describe('table structure', () => { + it('should create table with 5 rows', () => { + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[]; + const tableContent = sectionCall[0][1]; + + expect(tableContent.table.body.length).toBe(4); + }); + + it('should include header in result array', () => { + vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header-content'] as any); + + generujRachunekBankowy([mockAccount], 'Rachunek bankowy'); + + const sectionCall = vi.mocked(PDFFunctions.createSection).mock.calls[0][0] as any[]; + + expect(sectionCall[0][0]).toBe('header-content'); + }); + }); +}); diff --git a/src/lib-public/generators/FA_RR/RachunekBankowy.ts b/src/lib-public/generators/FA_RR/RachunekBankowy.ts new file mode 100644 index 00000000..3778be9d --- /dev/null +++ b/src/lib-public/generators/FA_RR/RachunekBankowy.ts @@ -0,0 +1,72 @@ +import { Content, ContentTable } from 'pdfmake/interfaces'; +import { + createHeader, + createSection, + formatText, + getValue, + hasValue, + makeBreakable, +} from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { RachunekBankowy } from '../../types/FaRR.types'; + +export const generujRachunekBankowy = (accounts?: RachunekBankowy[], title?: string): Content[] => { + const result: Content[] = []; + + if (!accounts?.length) { + return []; + } + + accounts.forEach((account, index) => { + const table: Content[][] = []; + const base: Content[] = createHeader( + title ? `${title} ${accounts?.length > 1 ? ++index : ''}` : '', + [0, 8, 0, 8] + ); + + table.push([ + formatText('Pełny numer rachunku', FormatTyp.GrayBoldTitle), + formatText(getValue(account.NrRB), FormatTyp.Default), + ]); + table.push([ + formatText('Kod SWIFT', FormatTyp.GrayBoldTitle), + formatText(getValue(account.SWIFT), FormatTyp.Default), + ]); + table.push([ + formatText('Nazwa banku', FormatTyp.GrayBoldTitle), + formatText( + hasValue(account.NazwaBanku) + ? makeBreakable(getValue(account.NazwaBanku), 20) + : getValue(account.NazwaBanku), + FormatTyp.Default + ), + ]); + table.push([ + formatText('Opis rachunku', FormatTyp.GrayBoldTitle), + formatText( + hasValue(account.OpisRachunku) + ? makeBreakable(getValue(account.OpisRachunku), 20) + : getValue(account.OpisRachunku), + FormatTyp.Default + ), + ]); + result.push([ + ...base, + { + unbreakable: true, + table: { + body: table, + widths: ['auto', '*'], + }, + layout: { + hLineWidth: () => 1, + hLineColor: () => '#BABABA', + vLineWidth: () => 1, + vLineColor: () => '#BABABA', + }, + } as ContentTable, + ]); + }); + + return createSection(result, false, [0, 0, 0, 0]); +}; diff --git a/src/lib-public/generators/FA_RR/Szczegoly.spec.ts b/src/lib-public/generators/FA_RR/Szczegoly.spec.ts new file mode 100644 index 00000000..6c12dc92 --- /dev/null +++ b/src/lib-public/generators/FA_RR/Szczegoly.spec.ts @@ -0,0 +1,113 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { generateSzczegoly } from './Szczegoly'; +import * as PDFFunctions from '../../../shared/PDF-functions'; +import FormatTyp from '../../../shared/enums/common.enum'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; +import { FakturaRR } from '../../types/FaRR.types'; + +vi.mock('../../../shared/PDF-functions', () => ({ + createHeader: vi.fn(), + createLabelText: vi.fn(), + createLabelTextArray: vi.fn(), + createSection: vi.fn(), + generateTwoColumns: vi.fn(), + getContentTable: vi.fn(), + getDifferentColumnsValue: vi.fn(), + getTable: vi.fn(), + getValue: vi.fn(), + hasColumnsValue: vi.fn(), + hasValue: vi.fn(), +})); + +describe(generateSzczegoly.name, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const mockFaVat: FakturaRR = { + P_4B: { _text: '2024-01-01' }, + P_1M: { _text: 'Warsaw' }, + P_4A: { _text: '2024-01-15' }, + KodWaluty: { _text: 'PLN' }, + }; + + beforeEach(() => { + vi.mocked(PDFFunctions.getTable).mockReturnValue([]); + vi.mocked(PDFFunctions.createHeader).mockReturnValue(['header'] as any); + vi.mocked(PDFFunctions.createLabelText).mockReturnValue('label' as any); + vi.mocked(PDFFunctions.createLabelTextArray).mockReturnValue('labelArray' as any); + vi.mocked(PDFFunctions.createSection).mockReturnValue('section' as any); + vi.mocked(PDFFunctions.generateTwoColumns).mockReturnValue('columns' as any); + vi.mocked(PDFFunctions.getContentTable).mockReturnValue({ + content: null, + fieldsWithValue: [], + }); + vi.mocked(PDFFunctions.getDifferentColumnsValue).mockReturnValue([]); + vi.mocked(PDFFunctions.getValue).mockReturnValue('PLN'); + vi.mocked(PDFFunctions.hasColumnsValue).mockReturnValue(false); + vi.mocked(PDFFunctions.hasValue).mockReturnValue(false); + }); + + it('should call createHeader with "Szczegóły"', () => { + generateSzczegoly(mockFaVat); + + expect(PDFFunctions.createHeader).toHaveBeenCalledWith('Szczegóły'); + }); + + it('should call createSection and return result', () => { + const mockSection = 'section'; + vi.mocked(PDFFunctions.createSection).mockReturnValue(mockSection as any); + + const result = generateSzczegoly(mockFaVat); + + expect(PDFFunctions.createSection).toHaveBeenCalledWith(expect.any(Array), true); + expect(result).toEqual(mockSection); + }); + + describe('standard labels', () => { + it('should add data wystawienia label', () => { + generateSzczegoly(mockFaVat); + + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( + 'Data wystawienia: ', + mockFaVat.P_4B, + FormatTyp.Date + ); + }); + + it('should add miejsce wystawienia label', () => { + generateSzczegoly(mockFaVat); + + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith('Miejsce wystawienia: ', mockFaVat.P_1M); + }); + + it('should add data dokonania nabycia label', () => { + generateSzczegoly(mockFaVat); + + expect(PDFFunctions.createLabelText).toHaveBeenCalledWith( + 'Data dokonania nabycia: ', + mockFaVat.P_4A, + FormatTyp.Date + ); + }); + }); + + describe('columns generation', () => { + it('should call generateTwoColumns', () => { + generateSzczegoly(mockFaVat); + + expect(PDFFunctions.generateTwoColumns).toHaveBeenCalledWith(expect.any(Array), expect.any(Array)); + }); + + it('should distribute labels between two columns', () => { + generateSzczegoly(mockFaVat); + + const calls = vi.mocked(PDFFunctions.generateTwoColumns).mock.calls; + expect(calls.length).toBeGreaterThanOrEqual(1); + + const firstCall = calls[0]; + expect(Array.isArray(firstCall[0])).toBe(true); + expect(Array.isArray(firstCall[1])).toBe(true); + }); + }); +}); diff --git a/src/lib-public/generators/FA_RR/Szczegoly.ts b/src/lib-public/generators/FA_RR/Szczegoly.ts new file mode 100644 index 00000000..b596b84d --- /dev/null +++ b/src/lib-public/generators/FA_RR/Szczegoly.ts @@ -0,0 +1,40 @@ +import { Content } from 'pdfmake/interfaces'; +import { + createHeader, + createLabelText, + createSection, + generateTwoColumns, + getDifferentColumnsValue, + getTable, + getValue, + hasValue, +} from '../../../shared/PDF-functions'; +import { FakturaRR as Fa } from '../../types/FaRR.types'; +import FormatTyp from '../../../shared/enums/common.enum'; + +export function generateSzczegoly(fa: Fa): Content[] { + const faWiersze = getTable(fa.FakturaRRWiersz); + + const kursWalutyLabel: Content[] = []; + + if (hasValue(fa.KodWaluty) && getValue(fa.KodWaluty) != 'PLN') { + const Common_KursWaluty = getDifferentColumnsValue('KursWaluty', faWiersze); + + if (Common_KursWaluty.length === 1) { + kursWalutyLabel.push(createLabelText('Kurs waluty: ', Common_KursWaluty[0].value, FormatTyp.Currency6)); + } + } + + const columns1: Content[] = [ + createLabelText('Data wystawienia: ', fa.P_4B, FormatTyp.Date), + createLabelText('Data dokonania nabycia: ', fa.P_4A, FormatTyp.Date), + kursWalutyLabel, + ].filter((el) => el.length > 0); + const columns2: Content[] = [createLabelText('Miejsce wystawienia: ', fa.P_1M)].filter( + (el) => el.length > 0 + ); + + const table: Content[] = [...createHeader('Szczegóły'), generateTwoColumns(columns1, columns2)]; + + return createSection(table, true); +} diff --git a/src/lib-public/generators/FA_RR/Wiersze.ts b/src/lib-public/generators/FA_RR/Wiersze.ts new file mode 100644 index 00000000..ce41f6dc --- /dev/null +++ b/src/lib-public/generators/FA_RR/Wiersze.ts @@ -0,0 +1,147 @@ +import { Content } from 'pdfmake/interfaces'; +import { + createHeader, + createLabelTextArray, + createSection, + getContentTable, + getDifferentColumnsValue, + getTable, + getTStawkaPodatku, + getValue, +} from '../../../shared/PDF-functions'; +import { HeaderDefine } from '../../../shared/types/pdf-types'; +import { FakturaRR as Fa, FP } from '../../types/FaRR.types'; +import FormatTyp, { Position } from '../../../shared/enums/common.enum'; + +export function generateWiersze(fa: Fa): Content { + const table: Content[] = []; + const faWiersze: Record[] = getTable(fa.FakturaRRWiersz).map( + (wiersz: Record): Record => { + if (getValue(wiersz.P_9)) { + wiersz.P_9._text = getTStawkaPodatku(getValue(wiersz.P_9) as string, 'RR'); + } + return { ...wiersz }; + } + ); + const definedHeaderLp: HeaderDefine[] = [ + { name: 'NrWierszaFa', title: 'Lp.', format: FormatTyp.Default, width: 'auto' }, + ]; + const definedHeader1: HeaderDefine[] = [ + { name: 'P_5', title: 'Nazwa', format: FormatTyp.Default, width: '*' }, + { name: 'P_6A', title: 'Miara', format: FormatTyp.Default, width: 'auto' }, + { name: 'P_6B', title: 'Ilość', format: FormatTyp.Number, width: 'auto' }, + { name: 'P_6C', title: 'Opis klasy lub jakości', format: FormatTyp.Default, width: 'auto' }, + { name: 'P_7', title: 'Cena jedn.', format: FormatTyp.Currency, width: 'auto' }, + { name: 'P_8', title: 'Wartość bez ZZP', format: FormatTyp.Currency, width: 'auto' }, + { name: 'P_9', title: 'Stawka ZZP', format: FormatTyp.Default, width: 'auto' }, + { name: 'P_10', title: 'Kwota ZZP', format: FormatTyp.Currency, width: 'auto' }, + { name: 'P_11', title: 'Wartość z ZZP', format: FormatTyp.Currency, width: 'auto' }, + ]; + + if (getDifferentColumnsValue('KursWaluty', faWiersze).length !== 1) { + definedHeader1.push({ + name: 'KursWaluty', + title: 'Kurs waluty', + format: FormatTyp.Currency6, + width: 'auto', + }); + } + definedHeader1.push({ name: 'StanPrzed', title: 'Stan przed', format: FormatTyp.Boolean, width: 'auto' }); + const definedHeader2: HeaderDefine[] = [ + { name: 'P_4AA', title: 'Data dokonania nabycia', format: FormatTyp.Date, width: 'auto' }, + { name: 'GTIN', title: 'GTIN', format: FormatTyp.Default, width: 'auto' }, + { name: 'PKWiU', title: 'PKWiU', format: FormatTyp.Default, width: 'auto' }, + { name: 'CN', title: 'CN', format: FormatTyp.Default, width: 'auto' }, + ]; + let content = getContentTable<(typeof faWiersze)[0]>( + [...definedHeaderLp, ...definedHeader1, ...definedHeader2], + faWiersze, + 'auto' + ); + + const opis: Content = { + stack: [ + { + stack: [ + createLabelTextArray([ + { + value: 'Wartość nabytych produktów rolnych lub wykonanych usług rolniczych: ', + }, + { + value: getValue(fa.P_11_1), + formatTyp: [FormatTyp.Currency, FormatTyp.Value], + currency: getValue(fa.KodWaluty)?.toString() ?? '', + }, + ]), + ], + margin: [0, 0, 0, 8], + }, + { + stack: [ + createLabelTextArray([ + { + value: 'Kwota zryczałtowanego zwrotu podatku: ', + formatTyp: [FormatTyp.LabelMargin, FormatTyp.Label], + }, + { + value: getValue(fa.P_11_2), + formatTyp: [FormatTyp.Currency, FormatTyp.Value, FormatTyp.LabelMargin], + currency: getValue(fa.KodWaluty)?.toString() ?? '', + }, + ]), + ], + margin: [0, 0, 0, 8], + }, + { + stack: [ + createLabelTextArray([ + { + value: 'Kwota należności ogółem: ', + formatTyp: [FormatTyp.LabelGreater, FormatTyp.LabelMargin], + }, + { + value: getValue(fa.P_12_1), + formatTyp: [FormatTyp.Currency, FormatTyp.ValueMedium, FormatTyp.LabelMargin], + currency: getValue(fa.KodWaluty)?.toString() ?? '', + }, + + getValue(fa.KodWaluty)?.toString() === 'PLN' + ? { value: '' } + : { + value: ` (${getValue(fa.P_12_1W)} PLN)`, + formatTyp: [FormatTyp.Currency, FormatTyp.ValueMedium, FormatTyp.LabelMargin], + }, + ]), + ], + margin: [0, 0, 0, 8], + }, + createLabelTextArray([ + { value: 'Słownie: ', formatTyp: [FormatTyp.LabelMargin, FormatTyp.Label] }, + { + value: getValue(fa.P_12_2), + formatTyp: [FormatTyp.Value], + }, + ]), + ], + alignment: Position.RIGHT, + margin: [0, 8, 0, 0], + }; + + if (content.fieldsWithValue.length <= 8 && content.content) { + table.push(content.content); + } else { + content = getContentTable<(typeof faWiersze)[0]>([...definedHeaderLp, ...definedHeader1], faWiersze, '*'); + if (content.content) { + table.push(content.content); + } + content = getContentTable<(typeof faWiersze)[0]>([...definedHeaderLp, ...definedHeader2], faWiersze, '*'); + if (content.content && content.fieldsWithValue.length > 1) { + table.push('\n'); + table.push(content.content); + } + } + if (table.length < 1) { + return []; + } + return createSection([...createHeader('Pozycje'), ...table, opis], true); +} diff --git a/src/lib-public/generators/UPO4_2/Dokumenty.ts b/src/lib-public/generators/UPO4_2/Dokumenty.ts index d4ad1302..033e24a7 100644 --- a/src/lib-public/generators/UPO4_2/Dokumenty.ts +++ b/src/lib-public/generators/UPO4_2/Dokumenty.ts @@ -12,7 +12,7 @@ import { HeaderDefine } from '../../../shared/types/pdf-types'; import { Dokument, IDKontekstu, Potwierdzenie } from '../../types/upo-v4_2.types'; import FormatTyp from '../../../shared/enums/common.enum'; import { FormContentState } from '../../../shared/types/additional-data.types'; -import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/FA.const'; export function generateDokumnetUPO(potwierdzenie: Potwierdzenie): Content[] { const dokumenty: Dokument[] = getTable(potwierdzenie.Dokument); diff --git a/src/lib-public/generators/UPO4_3/Dokumenty.ts b/src/lib-public/generators/UPO4_3/Dokumenty.ts index eceec819..f5025c44 100644 --- a/src/lib-public/generators/UPO4_3/Dokumenty.ts +++ b/src/lib-public/generators/UPO4_3/Dokumenty.ts @@ -11,7 +11,7 @@ import { import { HeaderDefine } from '../../../shared/types/pdf-types'; import FormatTyp from '../../../shared/enums/common.enum'; import { FormContentState } from '../../../shared/types/additional-data.types'; -import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT } from '../../../shared/consts/FA.const'; import { Dokument, IDKontekstu, Potwierdzenie } from '../../types/upo-v4_3.types'; export function generateDokumentUPO(potwierdzenie: Potwierdzenie): Content[] { diff --git a/src/lib-public/generators/common/DaneFaKorygowanej.spec.ts b/src/lib-public/generators/common/DaneFaKorygowanej.spec.ts index 0ed8e042..1b365fba 100644 --- a/src/lib-public/generators/common/DaneFaKorygowanej.spec.ts +++ b/src/lib-public/generators/common/DaneFaKorygowanej.spec.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as PDFFunctions from '../../../shared/PDF-functions'; -import { TypKorekty } from '../../../shared/consts/const'; +import { TypKorekty } from '../../../shared/consts/FA.const'; import { generateDaneFaKorygowanej } from './DaneFaKorygowanej'; describe('generateDaneFaKorygowanej', () => { diff --git a/src/lib-public/generators/common/DaneFaKorygowanej.ts b/src/lib-public/generators/common/DaneFaKorygowanej.ts index 4439c4d8..7c500f95 100644 --- a/src/lib-public/generators/common/DaneFaKorygowanej.ts +++ b/src/lib-public/generators/common/DaneFaKorygowanej.ts @@ -1,17 +1,20 @@ import { Content } from 'pdfmake/interfaces'; -import { TypKorekty } from '../../../shared/consts/const'; +import { TRodzajFaktury, TypKorekty } from '../../../shared/consts/FA.const'; +import { FARRTypKorekty } from '../../../shared/consts/FARR.const'; import { createHeader, createLabelText, createSection, generateTwoColumns, getTable, + getValue, } from '../../../shared/PDF-functions'; import { DaneFaKorygowanej, Fa as Fa1 } from '../../types/fa1.types'; import { Fa as Fa2 } from '../../types/fa2.types'; import { Fa as Fa3 } from '../../types/fa3.types'; +import { FakturaRR as FaRR } from '../../types/FaRR.types'; -export function generateDaneFaKorygowanej(invoice?: Fa1 | Fa2 | Fa3): Content[] { +export function generateDaneFaKorygowanej(invoice?: Fa1 | Fa2 | Fa3 | FaRR): Content[] { const result: Content[] = []; let firstColumn: Content[] = []; let secondColumn: Content[] = []; @@ -29,7 +32,13 @@ export function generateDaneFaKorygowanej(invoice?: Fa1 | Fa2 | Fa3): Content[] ); } if (invoice.TypKorekty?._text) { - firstColumn.push(createLabelText('Typ skutku korekty: ', TypKorekty[invoice.TypKorekty._text])); + const isFaRR = [TRodzajFaktury.VAT_RR, TRodzajFaktury.KOR_VAT_RR].includes( + getValue(invoice?.RodzajFaktury) as string + ); + const typKorekty = getValue(invoice.TypKorekty) as string; + firstColumn.push( + createLabelText('Typ skutku korekty: ', isFaRR ? FARRTypKorekty[typKorekty] : TypKorekty[typKorekty]) + ); } if (firstColumn.length) { diff --git a/src/lib-public/generators/common/Naglowek.spec.ts b/src/lib-public/generators/common/Naglowek.spec.ts index ab7ea6c7..19f00d79 100644 --- a/src/lib-public/generators/common/Naglowek.spec.ts +++ b/src/lib-public/generators/common/Naglowek.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa as Fa1 } from '../../types/fa1.types'; import { Fa as Fa3 } from '../../types/fa3.types'; import { generateNaglowek } from './Naglowek'; @@ -26,7 +26,7 @@ describe('generateNaglowek', () => { ).toBe(true); }); - it('generates header with ??? for unknown invoice type', () => { + it('generates header with empty string for unknown invoice type', () => { const fa: Fa1 = { RodzajFaktury: { _text: 'UNKNOWN' }, P_2: { _text: 'FUNK/2025/99' }, @@ -41,7 +41,7 @@ describe('generateNaglowek', () => { c !== null && 'text' in c && typeof (c as any).text === 'string' && - (c as any).text.includes('???') + (c as any).text.includes('') ) ).toBe(true); }); diff --git a/src/lib-public/generators/common/Naglowek.ts b/src/lib-public/generators/common/Naglowek.ts index b2faaae4..f1550fa9 100644 --- a/src/lib-public/generators/common/Naglowek.ts +++ b/src/lib-public/generators/common/Naglowek.ts @@ -1,6 +1,6 @@ import { Content, ContentText } from 'pdfmake/interfaces'; -import { formatText, generateLine } from '../../../shared/PDF-functions'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { formatText, generateLine, getValue } from '../../../shared/PDF-functions'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { Fa as Fa1 } from '../../types/fa1.types'; import { Fa as Fa2 } from '../../types/fa2.types'; import { Fa as Fa3, Zalacznik } from '../../types/fa3.types'; @@ -12,9 +12,9 @@ export function generateNaglowek( additionalData?: AdditionalDataTypes, zalacznik?: Zalacznik ): Content[] { - let invoiceName = '???'; + let invoiceName = ''; - switch (fa?.RodzajFaktury?._text) { + switch (getValue(fa?.RodzajFaktury)) { case TRodzajFaktury.VAT: invoiceName = 'Faktura podstawowa'; break; @@ -51,7 +51,10 @@ export function generateNaglowek( ], }, { ...(formatText('Numer Faktury:', FormatTyp.ValueMedium) as ContentText), alignment: Position.RIGHT }, - { ...(formatText(fa?.P_2?._text, FormatTyp.HeaderPosition) as ContentText), alignment: Position.RIGHT }, + { + ...(formatText(getValue(fa?.P_2), FormatTyp.HeaderPosition) as ContentText), + alignment: Position.RIGHT, + }, { ...(formatText(invoiceName, [FormatTyp.ValueMedium, FormatTyp.Default]) as ContentText), alignment: Position.RIGHT, diff --git a/src/lib-public/generators/common/Rozliczenie.ts b/src/lib-public/generators/common/Rozliczenie.ts index bb03472b..9b09143a 100644 --- a/src/lib-public/generators/common/Rozliczenie.ts +++ b/src/lib-public/generators/common/Rozliczenie.ts @@ -13,11 +13,12 @@ import { import { HeaderDefine } from '../../../shared/types/pdf-types'; import { Rozliczenie as Rozliczenie1, Zenia } from '../../types/fa1.types'; import { Rozliczenie as Rozliczenie2, Rozliczenie as Rozliczenie3 } from '../../types/fa2.types'; +import { Rozliczenie as RozliczenieRR } from '../../types/FaRR.types'; import FormatTyp, { Position } from '../../../shared/enums/common.enum'; import { FormContentState } from '../../../shared/types/additional-data.types'; export function generateRozliczenie( - rozliczenie: Rozliczenie1 | Rozliczenie2 | Rozliczenie3 | undefined, + rozliczenie: Rozliczenie1 | Rozliczenie2 | Rozliczenie3 | RozliczenieRR | undefined, KodWaluty: string ): Content[] { if (!rozliczenie) { @@ -31,11 +32,13 @@ export function generateRozliczenie( title: 'Powód odliczenia', name: 'Powod', format: FormatTyp.Default, + width: '*', }, { - title: 'Kwota odliczenia', + title: 'Kwota', name: 'Kwota', format: FormatTyp.Currency, + width: 'auto', }, ]; const headerObciazenia: HeaderDefine[] = [ @@ -43,11 +46,13 @@ export function generateRozliczenie( name: 'Powod', title: 'Powód obciążenia', format: FormatTyp.Default, + width: '*', }, { name: 'Kwota', - title: 'Kwota obciążenia', + title: 'Kwota', format: FormatTyp.Currency, + width: 'auto', }, ]; const tableObciazenia: FormContentState = getContentTable<(typeof obciazenia)[0]>( diff --git a/src/lib-public/generators/common/Stopka.ts b/src/lib-public/generators/common/Stopka.ts index 52f648d7..f0e20f20 100644 --- a/src/lib-public/generators/common/Stopka.ts +++ b/src/lib-public/generators/common/Stopka.ts @@ -32,6 +32,7 @@ export function generateStopka( const rejestry: Content[] = generateRejestry(stopka); const informacje: Content[] = generateInformacje(stopka); const qrCode: Content[] = generateQRCodeData(additionalData); + const qr2Code: Content[] = generateQR2CodeData(additionalData); const zalaczniki: Content[] = !additionalData?.isMobile ? generateZalaczniki(zalacznik) : []; const result: Content = [ @@ -43,6 +44,7 @@ export function generateStopka( ...informacje, ...(zalaczniki.length ? zalaczniki : []), { stack: [...qrCode], unbreakable: true }, + { stack: [...qr2Code], unbreakable: true }, createSection( [ { @@ -50,7 +52,7 @@ export function generateStopka( margin: [0, 8, 0, 0], }, ], - true, + false, [0, 0, 0, 0] ), ]; @@ -87,7 +89,9 @@ function generateRejestry(stopka?: Stopka): Content[] { const content: FormContentState = getContentTable<(typeof faWiersze)[0]>( [...definedHeader], faWiersze, - '*' + '*', + undefined, + 30 ); if (content.fieldsWithValue.length && content.content) { @@ -129,12 +133,11 @@ function generateQRCodeData(additionalData?: AdditionalDataTypes): Content[] { { stack: [ qrCode, - { stack: [formatText(additionalData.nrKSeF, FormatTyp.Default)], width: 'auto', - alignment: 'center', - marginLeft: 10, + alignment: 'left', + marginLeft: 0, marginRight: 10, marginTop: 10, } as ContentStack, @@ -145,7 +148,7 @@ function generateQRCodeData(additionalData?: AdditionalDataTypes): Content[] { stack: [ formatText( 'Nie możesz zeskanować kodu z obrazka? Kliknij w link weryfikacyjny i przejdź do weryfikacji faktury!', - FormatTyp.Value + FormatTyp.Label ), { stack: [formatText(additionalData.qrCode, FormatTyp.Link)], @@ -163,3 +166,49 @@ function generateQRCodeData(additionalData?: AdditionalDataTypes): Content[] { } return createSection(result, true); } +function generateQR2CodeData(additionalData?: AdditionalDataTypes): Content[] { + const result: Content = []; + + if (additionalData?.qr2Code) { + const qrCode: ContentQr | undefined = generateQRCode(additionalData.qr2Code); + + result.push(createHeader('Zweryfikuj dostawcę faktury')); + if (qrCode) { + result.push({ + columns: [ + { + stack: [ + qrCode, + { + stack: [formatText('CERTYFIKAT', FormatTyp.Default)], + width: 'auto', + alignment: 'left', + marginLeft: 0, + marginRight: 10, + marginTop: 10, + } as ContentStack, + ], + width: 150, + } as ContentStack, + { + stack: [ + formatText( + 'Nie możesz zeskanować kodu z obrazka? Kliknij w link weryfikacyjny i przejdź do weryfikacji wystawcy faktury!', + FormatTyp.Label + ), + { + stack: [formatText(additionalData.qr2Code, FormatTyp.Link)], + marginTop: 5, + link: additionalData.qrCode, + }, + ], + + margin: [10, (qrCode.fit ?? 120) / 2 - 30, 0, 0], + width: 'auto', + } as ContentStack, + ], + }); + } + } + return createSection(result, true); +} diff --git a/src/lib-public/generators/common/Wiersze.spec.ts b/src/lib-public/generators/common/Wiersze.spec.ts index 2b89880c..4a637095 100644 --- a/src/lib-public/generators/common/Wiersze.spec.ts +++ b/src/lib-public/generators/common/Wiersze.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { addMarza } from './Wiersze'; -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; const getMockFaVat = (mockedObjects?: Record) => ({ diff --git a/src/lib-public/generators/common/Wiersze.ts b/src/lib-public/generators/common/Wiersze.ts index 8782e0ad..0536b266 100644 --- a/src/lib-public/generators/common/Wiersze.ts +++ b/src/lib-public/generators/common/Wiersze.ts @@ -1,4 +1,4 @@ -import { TRodzajFaktury } from '../../../shared/consts/const'; +import { TRodzajFaktury } from '../../../shared/consts/FA.const'; import { FP } from '../../types/fa1.types'; import { getValue } from '../../../shared/PDF-functions'; diff --git a/src/lib-public/generators/common/Zalaczniki.ts b/src/lib-public/generators/common/Zalaczniki.ts index 4a12de08..48919a89 100644 --- a/src/lib-public/generators/common/Zalaczniki.ts +++ b/src/lib-public/generators/common/Zalaczniki.ts @@ -1,5 +1,5 @@ import { Content, ContentTable, ContentText, TableCell } from 'pdfmake/interfaces'; -import { DEFAULT_TABLE_LAYOUT, TableDataType } from '../../../shared/consts/const'; +import { DEFAULT_TABLE_LAYOUT, TableDataType } from '../../../shared/consts/FA.const'; import { createHeader, createLabelText, @@ -9,6 +9,7 @@ import { getContentTable, getTable, hasValue, + makeBreakable, } from '../../../shared/PDF-functions'; import { HeaderDefine } from '../../../shared/types/pdf-types'; import { BlokDanych, FP, Kol, MetaDane, Tabela, TMetaDane, Wiersz, Zalacznik } from '../../types/fa3.types'; @@ -135,7 +136,7 @@ function createTable( totalLength: number ): ContentTable { const definedHeader: Content[] = cols.map((item: Kol): string | ContentText => - formatText(item.NKom?._text, FormatTyp.GrayBoldTitle) + formatText(makeBreakable(item.NKom?._text), FormatTyp.GrayBoldTitle) ); const tableBody: TableCell[] = []; @@ -150,31 +151,35 @@ function createTable( if (cuttedRows.length >= subTableIndex + 1) { tableBody.push( cuttedRows[subTableIndex].map((subItem: FP, index: number): TableCell => { - return formatText( - subItem._text ?? '', - cols[index]._attributes?.Typ ? TableDataType[cols[index]._attributes.Typ] : FormatTyp.Value - ) as TableCell; + let formatType = FormatTyp.Value; + const typeKey = cols[index]._attributes?.Typ; + const formatTypeAttribute = typeKey ? TableDataType[typeKey] : undefined; + + if (formatTypeAttribute && ![FormatTyp.Date, FormatTyp.DateTime].includes(formatTypeAttribute)) { + formatType = formatTypeAttribute; + } + + return formatText(subItem._text ? makeBreakable(subItem._text) : '', formatType) as TableCell; }) ); } }); - const widths: Content[] = definedHeader.map((index: Content): string[] => { - if (index) { - return ['*']; - } else { - return ['auto']; + + const widths: string[] = definedHeader.map((_, i): string => { + if (i === 0) { + return '*'; } + return 'auto'; }); return { table: { headerRows: 1, widths: [...widths] as never[], - heights: 8, body: [[...definedHeader], ...tableBody] as TableCell[][], }, layout: DEFAULT_TABLE_LAYOUT, - marginTop: 8, + margin: [0, 8, 0, 0], }; } diff --git a/src/lib-public/types/FaRR.types.ts b/src/lib-public/types/FaRR.types.ts new file mode 100644 index 00000000..d2b7e905 --- /dev/null +++ b/src/lib-public/types/FaRR.types.ts @@ -0,0 +1,175 @@ +export interface FaRR { + _attributes?: FaRRAttributes; + Naglowek?: Naglowek; + Podmiot1?: Podmiot1Class; + Podmiot2?: Podmiot1Class; + Podmiot3?: Podmiot3[]; + FakturaRR?: FakturaRR; + Stopka?: Stopka; +} + +export interface FakturaRR { + KodWaluty?: FP; + P_1M?: FP; + P_4A?: FP; + P_4B?: FP; + P_4C?: FP; + P_11_1?: FP; + P_11_1W?: FP; + P_11_2?: FP; + P_11_2W?: FP; + P_12_1?: FP; + P_12_1W?: FP; + P_12_2?: FP; + RodzajFaktury?: FP; + PrzyczynaKorekty?: FP; + TypKorekty?: FP; + DaneFaKorygowanej?: DaneFaKorygowanej[]; + NrFaKorygowany?: FP; + Podmiot1K?: Podmiot1KClass; + Podmiot2K?: Podmiot1KClass; + DokumentZaplaty?: DokumentZaplaty[]; + DodatkowyOpis?: DodatkowyOpi[]; + FakturaRRWiersz?: Record[]; + Rozliczenie?: Rozliczenie; + Platnosc?: Platnosc; +} + +export interface DaneFaKorygowanej { + DataWystFaKorygowanej?: FP; + NrFaKorygowanej?: FP; + NrKSeFFaKorygowanej?: FP; +} + +export interface FP { + _text?: string; +} + +export interface DodatkowyOpi { + NrWiersza?: FP; + Klucz?: FP; + Wartosc?: FP; +} + +export interface DokumentZaplaty { + NrDokumentu?: FP; + DataDokumentu?: FP; +} + +export interface Platnosc { + FormaPlatnosci?: FP; + PlatnoscInna?: FP; + OpisPlatnosci?: FP; + RachunekBankowy1?: RachunekBankowy[]; + RachunekBankowy2?: RachunekBankowy[]; + IPKSeF?: FP; + LinkDoPlatnosci?: FP; +} + +export interface RachunekBankowy { + NrRB?: FP; + SWIFT?: FP; + NazwaBanku?: FP; + OpisRachunku?: FP; +} + +export interface Podmiot1KClass { + DaneIdentyfikacyjne?: Podmiot1KDaneIdentyfikacyjne; + Adres?: Adres; +} + +export interface Adres { + KodKraju?: FP; + AdresL1?: FP; + AdresL2?: FP; + GLN?: FP; +} + +export interface Podmiot1KDaneIdentyfikacyjne { + NIP?: FP; + Nazwa?: FP; +} + +export interface Rozliczenie { + Obciazenia?: Zenia[]; + SumaObciazen?: FP; + Odliczenia?: Zenia[]; + SumaOdliczen?: FP; + DoZaplaty?: FP; + DoRozliczenia?: FP; +} + +export interface Zenia { + Kwota?: FP; + Powod?: FP; +} + +export interface Naglowek { + KodFormularza?: KodFormularza; + WariantFormularza?: FP; + DataWytworzeniaFa?: FP; + SystemInfo?: FP; +} + +export interface KodFormularza { + _attributes?: KodFormularzaAttributes; + _text?: string; +} + +export interface KodFormularzaAttributes { + kodSystemowy?: string; + wersjaSchemy?: string; +} + +export interface Podmiot1Class { + DaneIdentyfikacyjne?: Podmiot1KDaneIdentyfikacyjne; + Adres?: Adres; + AdresKoresp?: Adres; + DaneKontaktowe?: DaneKontaktowe[]; + NrKontrahenta?: FP; + StatusInfoPodatnika?: FP; +} + +export interface DaneKontaktowe { + Email?: FP; + Telefon?: FP; +} + +export interface Podmiot3 { + DaneIdentyfikacyjne?: Podmiot3DaneIdentyfikacyjne; + Adres?: Adres; + AdresKoresp?: Adres; + DaneKontaktowe?: DaneKontaktowe[]; + Rola?: FP; + RolaInna?: FP; + OpisRoli?: FP; +} + +export interface Podmiot3DaneIdentyfikacyjne { + NIP?: FP; + IDWew?: FP; + BrakID?: FP; + Nazwa?: FP; +} + +export interface Stopka { + Informacje?: Informacje[]; + Rejestry?: Rejestry[]; +} + +export interface Informacje { + StopkaFaktury?: FP; +} + +export interface Rejestry { + PelnaNazwa?: FP; + KRS?: FP; + REGON?: FP; + BDO?: FP; +} + +export interface FaRRAttributes { + tns?: string; + xsi?: string; + schemaLocation?: string; +} diff --git a/src/lib-public/types/common.types.ts b/src/lib-public/types/common.types.ts index cc4b1eb0..1e1e13f4 100644 --- a/src/lib-public/types/common.types.ts +++ b/src/lib-public/types/common.types.ts @@ -25,5 +25,6 @@ export type FA2FakturaZaliczkowaData = FA2FakturaZaliczkowaDataSpozaKSeF | FA2Fa export interface AdditionalDataTypes { nrKSeF: string; qrCode?: string; + qr2Code?: string; isMobile?: boolean; } diff --git a/src/server/index.test.ts b/src/server/index.test.ts new file mode 100644 index 00000000..c9552fdc --- /dev/null +++ b/src/server/index.test.ts @@ -0,0 +1,157 @@ +import request from 'supertest'; +import { app } from './index'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Create a mock for runTask that we can manipulate +const { mockRunTask } = vi.hoisted(() => ({ + mockRunTask: vi.fn(), +})); + +// Mock the worker pool module +vi.mock('./worker-pool', () => ({ + WorkerPool: vi.fn().mockImplementation(() => ({ + runTask: mockRunTask, + shutdown: vi.fn().mockResolvedValue(undefined), + on: vi.fn(), + })), +})); + +describe('Server API', () => { + beforeEach(() => { + vi.clearAllMocks(); + // Default success behavior + mockRunTask.mockResolvedValue(Buffer.from('mock-pdf')); + }); + + const validMetadata = JSON.stringify({ + nrKSeF: '1234567890', + qrCode: 'some-qr-code', + isMobile: true, + }); + + const mockFile = Buffer.from('mock invoice'); + + describe('POST /api/generate-invoice', () => { + it('should return 200 for valid metadata and file', async () => { + const response = await request(app) + .post('/api/generate-invoice') + .field('metadata', validMetadata) + .attach('file', mockFile, 'invoice.xml'); + + expect(response.status).toBe(200); + expect(response.type).toBe('application/pdf'); + expect(response.headers['content-disposition']).toContain('attachment; filename="1234567890.pdf"'); + expect(mockRunTask).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'invoice', + xmlContent: expect.any(String), + additionalData: expect.objectContaining({ nrKSeF: '1234567890' }), + }) + ); + }); + + it('should return 400 if metadata is missing', async () => { + const response = await request(app) + .post('/api/generate-invoice') + .attach('file', mockFile, 'invoice.xml'); + + expect(response.status).toBe(400); + expect(response.body.error).toBe('Missing metadata'); + }); + + it('should return 400 if file is missing', async () => { + const response = await request(app).post('/api/generate-invoice').field('metadata', validMetadata); + + expect(response.status).toBe(400); + expect(response.body.error).toBe('Missing XML file'); + }); + + it('should return 400 if file type is invalid (not XML)', async () => { + const response = await request(app) + .post('/api/generate-invoice') + .field('metadata', validMetadata) + .attach('file', Buffer.from('image content'), 'image.png'); + + expect(response.status).toBe(400); + expect(response.body.error).toContain('Upload error'); + }); + + it('should return 400 for invalid JSON in metadata', async () => { + const response = await request(app) + .post('/api/generate-invoice') + .field('metadata', '{invalid-json') + .attach('file', mockFile, 'invoice.xml'); + + expect(response.status).toBe(400); + expect(response.body.error).toContain('Invalid JSON format'); + }); + + it('should return 400 if metadata is missing required fields (nrKSeF)', async () => { + const invalidMetadata = JSON.stringify({ + qrCode: 'some-qr', + }); + + const response = await request(app) + .post('/api/generate-invoice') + .field('metadata', invalidMetadata) + .attach('file', mockFile, 'invoice.xml'); + + expect(response.status).toBe(400); + expect(response.body.error).toContain('Validation error'); + expect(response.body.error).toContain('nrKSeF'); + }); + + it('should return 500 if worker fails', async () => { + mockRunTask.mockRejectedValueOnce(new Error('Worker crashed')); + + const response = await request(app) + .post('/api/generate-invoice') + .field('metadata', validMetadata) + .attach('file', mockFile, 'invoice.xml'); + + expect(response.status).toBe(500); + expect(response.body.error).toBe('Internal Server Error'); + }); + }); + + describe('POST /api/generate-upo', () => { + it('should return 200 for valid file', async () => { + const response = await request(app).post('/api/generate-upo').attach('file', mockFile, 'upo.xml'); + + expect(response.status).toBe(200); + expect(response.type).toBe('application/pdf'); + expect(response.headers['content-disposition']).toContain('attachment; filename=upo.pdf'); + expect(mockRunTask).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'upo', + xmlContent: expect.any(String), + }) + ); + }); + + it('should return 400 if file is missing', async () => { + const response = await request(app).post('/api/generate-upo'); + + expect(response.status).toBe(400); + expect(response.body.error).toBe('Missing XML file'); + }); + + it('should return 400 if file type is invalid', async () => { + const response = await request(app) + .post('/api/generate-upo') + .attach('file', Buffer.from('text'), 'test.txt'); + + expect(response.status).toBe(400); + expect(response.body.error).toContain('Upload error'); + }); + + it('should return 500 if worker fails', async () => { + mockRunTask.mockRejectedValueOnce(new Error('Worker crashed')); + + const response = await request(app).post('/api/generate-upo').attach('file', mockFile, 'upo.xml'); + + expect(response.status).toBe(500); + expect(response.body.error).toBe('Internal Server Error'); + }); + }); +}); diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 00000000..dd7210bb --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1,220 @@ +import express, { NextFunction, Request, Response } from 'express'; +import { z } from 'zod'; +import multer from 'multer'; +import { cpus } from 'os'; +import path from 'path'; +import fs from 'node:fs'; +import { AdditionalDataTypes } from '../lib-public/types/common.types'; +import { AdditionalDataSchema } from './validation'; +import swaggerUi from 'swagger-ui-express'; +import { swaggerSpec } from './swagger'; +import { WorkerPool } from './worker-pool'; +import type { PdfTaskData } from './pdf-worker'; + +const pdfWorkerJsPath = path.resolve(__dirname, 'pdf-worker.js'); +const pdfWorkerTsPath = path.resolve(__dirname, 'pdf-worker.ts'); +const pdfWorkerPath = fs.existsSync(pdfWorkerJsPath) ? pdfWorkerJsPath : pdfWorkerTsPath; + +const pdfWorkerPool = new WorkerPool(pdfWorkerPath, { + maxWorkers: Math.max(1, cpus().length - 1), +}); + +export const app = express(); + +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + +const storage = multer.memoryStorage(); + +const upload = multer({ + storage, + limits: { + fileSize: 5 * 1024 * 1024, + }, + fileFilter: (_req, file, cb) => { + // Basic MIME type check (not bulletproof, but a good first line of defense) + if (file.mimetype === 'text/xml' || file.mimetype === 'application/xml') { + cb(null, true); + } else { + cb(new Error('Invalid file type. Only XML is allowed.')); + } + }, +}); + +/** + * @swagger + * /api/generate-invoice: + * post: + * summary: Generate an invoice PDF from an XML file and metadata + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * properties: + * metadata: + * type: string + * description: JSON string + * file: + * type: string + * format: binary + * responses: + * 200: + * description: The generated PDF invoice + * content: + * application/pdf: + * schema: + * type: string + * format: binary + * 400: + * description: Missing metadata or file + * 500: + * description: Something went wrong + */ +app.post( + '/api/generate-invoice', + upload.single('file'), + async (req: Request, res: Response, next: NextFunction) => { + try { + if (!req.file) { + return res.status(400).json({ error: 'Missing XML file' }); + } + if (!req.body.metadata) { + return res.status(400).json({ error: 'Missing metadata' }); + } + + let additionalData: AdditionalDataTypes; + + try { + const parsed = JSON.parse(req.body.metadata); + + additionalData = AdditionalDataSchema.parse(parsed); + } catch (error) { + if (error instanceof z.ZodError) { + return res.status(400).json({ + error: + 'Validation error: ' + error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '), + }); + } + return res.status(400).json({ error: 'Invalid JSON format in metadata ' + error }); + } + + const xmlContent = req.file.buffer.toString('utf-8'); + const result = await pdfWorkerPool.runTask({ + type: 'invoice', + xmlContent, + additionalData, + }); + const pdfBuffer = Buffer.from(result); + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', 'attachment; filename="' + additionalData.nrKSeF + '.pdf"'); + res.send(pdfBuffer); + } catch (error) { + next(error); + } + } +); + +/** + * @swagger + * /api/generate-upo: + * post: + * summary: Generate a UPO PDF from an XML file + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * properties: + * file: + * type: string + * format: binary + * responses: + * 200: + * description: The generated PDF UPO + * content: + * application/pdf: + * schema: + * type: string + * format: binary + * 400: + * description: Missing file + * 500: + * description: Something went wrong + */ +app.post( + '/api/generate-upo', + upload.single('file'), + async (req: Request, res: Response, next: NextFunction) => { + try { + if (!req.file) { + return res.status(400).json({ error: 'Missing XML file' }); + } + + const xmlContent = req.file.buffer.toString('utf-8'); + const result = await pdfWorkerPool.runTask({ + type: 'upo', + xmlContent, + }); + + const pdfBuffer = Buffer.from(result); + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', 'attachment; filename=upo.pdf'); + res.send(pdfBuffer); + } catch (error) { + next(error); + } + } +); + +app.use((err: any, req: Request, res: Response, next: NextFunction) => { + if (err instanceof multer.MulterError) { + return res.status(400).json({ error: `Upload error: ${err.message}` }); + } + if (err.message === 'Invalid file type. Only XML is allowed.') { + return res.status(400).json({ error: `Upload error: ${err.message}` }); + } + console.error(err.stack); + + if (res.headersSent) { + return next(err); + } + + res.status(500).json({ error: 'Internal Server Error' }); +}); + +if (require.main === module || process.env.npm_lifecycle_event === 'start:server') { + const port = process.env.PORT || 3000; // Use env var for port + + const server = app.listen(port, () => { + console.log(`Server listening at http://localhost:${port}`); + }); + + const shutdown = async (signal: string): Promise => { + console.log(`\n${signal} received, shutting down gracefully...`); + + // Shutdown worker pool first + try { + await pdfWorkerPool.shutdown(); + console.log('Worker pool shut down'); + } catch (error) { + console.error('Error shutting down worker pool:', error); + } + + server.close(() => { + console.log('Server closed'); + process.exit(0); + }); + + setTimeout(() => { + console.error('Forced shutdown after timeout'); + process.exit(1); + }, 10000); + }; + + process.on('SIGTERM', () => shutdown('SIGTERM')); + process.on('SIGINT', () => shutdown('SIGINT')); +} diff --git a/src/server/pdf-worker.ts b/src/server/pdf-worker.ts new file mode 100644 index 00000000..4f130271 --- /dev/null +++ b/src/server/pdf-worker.ts @@ -0,0 +1,36 @@ +import { generateInvoice, generatePDFUPO } from '../lib-public'; +import type { AdditionalDataTypes } from '../lib-public/types/common.types'; + +export interface InvoiceTaskData { + type: 'invoice'; + xmlContent: string; + additionalData: AdditionalDataTypes; +} + +export interface UpoTaskData { + type: 'upo'; + xmlContent: string; +} + +export type PdfTaskData = InvoiceTaskData | UpoTaskData; + +async function blobToBuffer(blob: Blob): Promise { + return Buffer.from(await blob.arrayBuffer()); +} + +export default async function (task: PdfTaskData): Promise { + try { + if (task.type === 'invoice') { + const file = new File([task.xmlContent], 'invoice.xml', { type: 'text/xml' }); + const pdfBlob = await generateInvoice(file, task.additionalData, 'blob'); + return blobToBuffer(pdfBlob); + } else if (task.type === 'upo') { + const file = new File([task.xmlContent], 'upo.xml', { type: 'text/xml' }); + const pdfBlob = await generatePDFUPO(file); + return blobToBuffer(pdfBlob); + } + throw new Error('Unknown task type'); + } catch (error) { + throw error instanceof Error ? error : new Error(String(error)); + } +} diff --git a/src/server/swagger.ts b/src/server/swagger.ts new file mode 100644 index 00000000..87c7cbd2 --- /dev/null +++ b/src/server/swagger.ts @@ -0,0 +1,16 @@ +import swaggerJsdoc from 'swagger-jsdoc'; + +const options = { + definition: { + openapi: '3.0.0', + info: { + title: 'KSeF-FE Invoice Converter API', + version: '1.0.0', + description: 'API to generate PDF visualizations of invoices and UPO from XML files', + }, + }, + + apis: ['./src/**/*.ts', './dist/**/*.js'], +}; + +export const swaggerSpec = swaggerJsdoc(options); diff --git a/src/server/validation.ts b/src/server/validation.ts new file mode 100644 index 00000000..ba55f993 --- /dev/null +++ b/src/server/validation.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { AdditionalDataTypes } from '../lib-public/types/common.types'; + +export const AdditionalDataSchema: z.ZodType = z.object({ + nrKSeF: z.string().min(1, 'nrKSeF is required'), + qrCode: z.string().optional(), + isMobile: z.boolean().optional(), +}); diff --git a/src/server/worker-bootstrap.cjs b/src/server/worker-bootstrap.cjs new file mode 100644 index 00000000..1a2d7e8c --- /dev/null +++ b/src/server/worker-bootstrap.cjs @@ -0,0 +1,14 @@ +const { workerData } = require('node:worker_threads'); + +const workerPath = workerData?.workerScript; +if (!workerPath) { + throw new Error('Missing workerData.workerScript'); +} + +if (workerPath.endsWith('.ts')) { + const { createJiti } = require('jiti'); + const jiti = createJiti(__filename, { interopDefault: true, esmResolve: true }); + module.exports = jiti(workerPath); +} else { + module.exports = require(workerPath); +} \ No newline at end of file diff --git a/src/server/worker-pool.test.ts b/src/server/worker-pool.test.ts new file mode 100644 index 00000000..e6e36c90 --- /dev/null +++ b/src/server/worker-pool.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from 'vitest'; +import { WorkerPool } from './worker-pool'; +import path from 'path'; + +describe('WorkerPool', () => { + describe('initialization', () => { + it('should create pool with default options', () => { + const pool = new WorkerPool(path.resolve(__dirname, 'worker-bootstrap.cjs'), {}); + + expect(pool).toBeInstanceOf(WorkerPool); + }); + + it('should accept custom maxWorkers option', () => { + const pool = new WorkerPool(path.resolve(__dirname, 'worker-bootstrap.cjs'), { maxWorkers: 2 }); + + expect(pool.maxThreads).toBe(2); + }); + }); + + describe('shutdown', () => { + it('should reject tasks when shutting down', async () => { + const pool = new WorkerPool(path.resolve(__dirname, 'worker-bootstrap.cjs'), { maxWorkers: 1 }); + + // Start shutdown + const shutdownPromise = pool.shutdown(); + + // Try to run a task - should be rejected + await expect(pool.runTask({ test: 'data' })).rejects.toThrow('shutting down'); + + await shutdownPromise; + }); + }); +}); diff --git a/src/server/worker-pool.ts b/src/server/worker-pool.ts new file mode 100644 index 00000000..e044387e --- /dev/null +++ b/src/server/worker-pool.ts @@ -0,0 +1,59 @@ +import { EventEmitter } from 'events'; +import Piscina from 'piscina'; +import path from 'node:path'; + +export interface WorkerPoolOptions { + maxWorkers?: number; + workerOptions?: Record; +} + +/** + * A wrapper around Piscina to provide a similar interface to the previous custom WorkerPool. + */ +export class WorkerPool extends EventEmitter { + private pool: Piscina; + private isShuttingDown = false; + + constructor(workerPath: string, options: WorkerPoolOptions = {}) { + super(); + + const bootstrapPath = path.resolve(__dirname, 'worker-bootstrap.cjs'); + + this.pool = new Piscina({ + filename: bootstrapPath, + maxThreads: options.maxWorkers, + workerData: { + workerScript: workerPath, + }, + ...options.workerOptions, + }); + } + + /** + * Run a task in a worker thread. + * Returns a promise that resolves with the result or rejects with an error. + */ + public async runTask(data: TData): Promise { + if (this.isShuttingDown) { + throw new Error('Worker pool is shutting down'); + } + + try { + return await this.pool.run(data); + } catch (error) { + throw error instanceof Error ? error : new Error(String(error)); + } + } + + /** + * Gracefully shut down all workers. + */ + public async shutdown(): Promise { + this.isShuttingDown = true; + await this.pool.destroy(); + } + + public get maxThreads(): number { + return this.pool.options.maxThreads; + } +} diff --git a/src/shared/PDF-functions.ts b/src/shared/PDF-functions.ts index ae32b598..d655b22a 100644 --- a/src/shared/PDF-functions.ts +++ b/src/shared/PDF-functions.ts @@ -12,17 +12,19 @@ import { } from 'pdfmake/interfaces'; import { DEFAULT_TABLE_LAYOUT, + FormaPlatnosci, Kraj, TStawkaPodatku_FA1, TStawkaPodatku_FA2, TStawkaPodatku_FA3, -} from './consts/const'; -import { formatDateTime, formatTime, getFormaPlatnosciString } from './generators/common/functions'; +} from './consts/FA.const'; +import { formatDateTime, formatTime, translateMap } from './generators/common/functions'; import { HeaderDefine, PdfFP, PdfOptionField } from './types/pdf-types'; import { FP } from '../lib-public/types/fa3.types'; import { DifferentValues, FilteredKeysOfValues, TypesOfValues } from './types/universal.types'; import { CreateLabelTextData } from './types/additional-data.types'; import FormatTyp, { Answer, Position } from './enums/common.enum'; +import { TStawkaPodatku_FARR } from './consts/FARR.const'; export function formatText( value: number | string | undefined | null, @@ -54,12 +56,18 @@ export function formatText( export function generateTable(array: T[], keys: Partial>): Content { const faRows: NonNullable[] = getTable(array); - const headers: { name: string; title: string; format: FormatTyp }[] = Object.entries(keys).map( - ([key, value]: [string, unknown]): { name: string; title: string; format: FormatTyp } => { + const headers: { name: string; title: string; format: FormatTyp; width?: string }[] = Object.entries( + keys + ).map( + ( + [key, value]: [string, unknown], + index + ): { name: string; title: string; format: FormatTyp; width?: string } => { return { name: key, title: value as string, format: FormatTyp.Default, + ...(index === 0 ? { width: 'auto' } : {}), }; } ); @@ -67,7 +75,9 @@ export function generateTable(array: T[], keys: Partial { HeaderContent: { fontSize: 10, bold: true, + color: '#343A40', }, SubHeaderContent: { fontSize: 7, bold: true, + color: '#343A40', }, TitleContent: { fontSize: 10, @@ -479,6 +491,7 @@ export function generateTwoColumns(kol1: Column, kol2: Column, margin?: Margins) ], margin: margin ?? [0, 0, 0, 0], columnGap: 20, + unbreakable: true, }; } @@ -516,7 +529,7 @@ export function getKraj(code: string): string { return code; } -export function getTStawkaPodatku(code: string, version: 1 | 2 | 3, P_PMarzy?: string): string { +export function getTStawkaPodatku(code: string, version: 1 | 2 | 3 | 'RR', P_PMarzy?: string): string { let TStawkaPodatkuVersioned: Record = {}; switch (version) { @@ -529,6 +542,9 @@ export function getTStawkaPodatku(code: string, version: 1 | 2 | 3, P_PMarzy?: s case 3: TStawkaPodatkuVersioned = TStawkaPodatku_FA3; break; + case 'RR': + TStawkaPodatkuVersioned = TStawkaPodatku_FARR; + break; } if (!code && P_PMarzy === '1') { return 'marża'; diff --git a/src/shared/XML-parser.spec.ts b/src/shared/XML-parser.spec.ts index 79ab9a6b..f5d516cf 100644 --- a/src/shared/XML-parser.spec.ts +++ b/src/shared/XML-parser.spec.ts @@ -1,71 +1,23 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { parseXML, stripPrefixes } from './XML-parser'; +import { describe, expect, it } from 'vitest'; +import { parseXML, stripPrefix } from './XML-parser'; describe('stripPrefixes', () => { it('removes prefixes from object keys', () => { - const input = { - 'ns:name': 'test', - other: { - 'x:value': 10, - array: [{ 'a:item': 1 }, { 'a:item': 2 }], - }, - }; + const input = 'ns:name'; - const expected = { - name: 'test', - other: { - value: 10, - array: [{ item: 1 }, { item: 2 }], - }, - }; - - expect(stripPrefixes(input)).toEqual(expected); + expect(stripPrefix(input)).toEqual('name'); }); it('returns primitives unchanged', () => { - expect(stripPrefixes('string')).toBe('string'); - expect(stripPrefixes(123)).toBe(123); - expect(stripPrefixes(null)).toBeNull(); - expect(stripPrefixes(undefined)).toBeUndefined(); - }); - - it('recursively processes arrays', () => { - const input = [{ 'ns:key1': 'value1' }, { 'ns:key2': 'value2' }]; - - const expected = [{ key1: 'value1' }, { key2: 'value2' }]; - - expect(stripPrefixes(input)).toEqual(expected); + expect(stripPrefix('string')).toBe('string'); }); }); describe('parseXML', () => { const xmlSample = `test`; const file = new File([xmlSample], 'test.xml', { type: 'text/xml' }); - - beforeEach(() => { - vi.stubGlobal( - 'FileReader', - class { - onload: ((this: FileReader, ev: ProgressEvent) => any) | null = null; - public readAsText(): void { - const event = { target: { result: xmlSample } } as ProgressEvent; - - setTimeout( - () => - (this.onload as ((this: FileReader, ev: ProgressEvent) => void) | null)?.call( - this as unknown as FileReader, - event - ), - 0 - ); - } - } - ); - }); - - afterEach(() => { - vi.unstubAllGlobals(); - }); + const errorXmlSample = `ssssstttt`; + const errorFile = new File([errorXmlSample], 'test.xml', { type: 'text/xml' }); it('correctly parses XML into a JSON object', async () => { const result: any = await parseXML(file); @@ -75,24 +27,6 @@ describe('parseXML', () => { }); it('rejects the promise with an error on invalid XML', async () => { - vi.stubGlobal( - 'FileReader', - class { - onload: ((this: FileReader, ev: ProgressEvent) => any) | null = null; - public readAsText(): void { - const event = { target: { result: '' } } as ProgressEvent; - - setTimeout( - () => - (this.onload as ((this: FileReader, ev: ProgressEvent) => void) | null)?.call( - this as unknown as FileReader, - event - ), - 0 - ); - } - } - ); - await expect(parseXML(file)).rejects.toThrow(); + await expect(parseXML(errorFile)).rejects.toThrow(); }); }); diff --git a/src/shared/XML-parser.ts b/src/shared/XML-parser.ts index 01372dc2..15cde460 100644 --- a/src/shared/XML-parser.ts +++ b/src/shared/XML-parser.ts @@ -1,34 +1,38 @@ import { xml2js } from 'xml-js'; import { Faktura } from '../lib-public/types/fa2.types'; -export function stripPrefixes(obj: T): T { - if (Array.isArray(obj)) { - return obj.map(stripPrefixes) as T; - } else if (typeof obj === 'object' && obj !== null) { - return Object.fromEntries( - Object.entries(obj).map(([key, value]: [string, T]): [string, T] => [ - key.includes(':') ? key.split(':')[1] : key, - stripPrefixes(value), - ]) - ) as T; - } - return obj; +export function stripPrefix(key: string): string { + return key.includes(':') ? key.split(':')[1] : key; } -export function parseXML(file: File): Promise { - return new Promise((resolve, reject): void => { +async function readBlobAsText(blob: Blob): Promise { + if (typeof blob.text === 'function') { + return blob.text(); + } + if (typeof blob.arrayBuffer === 'function') { + return new TextDecoder().decode(await blob.arrayBuffer()); + } + return new Promise((resolve, reject) => { + if (typeof FileReader === 'undefined') { + reject(new Error('Cannot read Blob as text in this environment')); + return; + } const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = () => reject(reader.error ?? new Error('FileReader failed')); + reader.readAsText(blob); + }); +} - reader.onload = function (e: ProgressEvent): void { - try { - const xmlStr: string = e.target?.result as string; - const jsonDoc: Faktura = stripPrefixes(xml2js(xmlStr, { compact: true })) as Faktura; +export async function parseXML(file: File): Promise { + const xmlStr = await readBlobAsText(file); + const jsonDoc: Faktura = xml2js(xmlStr, { + compact: true, + cdataKey: '_text', + trim: true, + elementNameFn: stripPrefix, + attributeNameFn: stripPrefix, + }) as Faktura; - resolve(jsonDoc); - } catch (error) { - reject(error); - } - }; - reader.readAsText(file); - }); + return jsonDoc; } diff --git a/src/shared/consts/FA.const.ts b/src/shared/consts/FA.const.ts new file mode 100644 index 00000000..a8fd0ec1 --- /dev/null +++ b/src/shared/consts/FA.const.ts @@ -0,0 +1,462 @@ +import FormatTyp from '../enums/common.enum'; +import { RodzajFaktury } from '../enums/invoice.enums'; + +export const TRodzajFaktury: Record = Object.keys(RodzajFaktury).reduce( + (acc, key) => { + const typedKey = key as keyof typeof RodzajFaktury; + + acc[typedKey] = typedKey; + return acc; + }, + {} as Record +); + +export const TypKorekty: Record = { + '1': 'Korekta skutkująca w dacie ujęcia faktury pierwotnej', + '2': 'Korekta skutkująca w dacie wystawienia faktury korygującej', + '3': 'Korekta skutkująca w dacie innej, w tym gdy dla różnych pozycji faktury korygującej daty te są różne', +}; +export const TStawkaPodatku_FA1: Record = { + '23': '23%', + '22': '22%', + '8': '8%', + '7': '7%', + '5': '5%', + '4': '4% lub 3% lub oo', + '3': '4% lub 3% lub oo', + '0': '0%', + + zw: 'zwolnione z opodatkowania', + oo: '4% lub 3% lub oo\n' + 'UWAGA: oo jest wykazywane łącznie z 4% lub 3%', + np: 'niepodlegające opodatkowaniu-transakcje dostawy towarów oraz świadczenia usług poza terytorium kraju', +}; + +export const TStawkaPodatku_FA2: Record = { + '23': '23%', + '22': '22%', + '8': '8%', + '7': '7%', + '5': '5%', + '4': '4%', + '3': '3%', + '0': '0%', + + zw: 'zwolnione od podatku', + oo: 'odwrotne obciążenie', + np: 'niepodlegające opodatkowaniu-transakcje dostawy towarów oraz świadczenia usług poza terytorium kraju', +}; + +export const TStawkaPodatku_FA3: Record = { + '23': '23%', + '22': '22%', + '8': '8%', + '7': '7%', + '5': '5%', + '4': '4%', + '3': '3%', + + '0 KR': '0% - KR', + '0 WDT': '0% - WDT', + '0 EX': '0% - EX', + + zw: 'zw', + oo: 'oo', + + 'np I': 'np I', + 'np II': 'np II', +}; + +export const Kraj: Record = { + AF: 'Afganistan', + AX: 'Aland Islands', + AL: 'Albania', + DZ: 'Algieria', + AD: 'Andora', + AO: 'Angola', + AI: 'Anguilla', + AQ: 'Antarktyda', + AG: 'Antigua i Barbuda', + AN: 'Antyle Holenderskie', + SA: 'Arabia Saudyjska', + AR: 'Argentyna', + AM: 'Armenia', + AW: 'Aruba', + AU: 'Australia', + AT: 'Austria', + AZ: 'Azerbejdżan', + BS: 'Bahamy', + BH: 'Bahrajn', + BD: 'Bangladesz', + BB: 'Barbados', + BE: 'Belgia', + BZ: 'Belize', + BJ: 'Benin', + BM: 'Bermudy', + BT: 'Bhutan', + BY: 'Białoruś', + BO: 'Boliwia', + BQ: 'Bonaire, Sint Eustatius i Saba', + BA: 'Bośnia i Hercegowina', + BW: 'Botswana', + BR: 'Brazylia', + BN: 'Brunei Darussalam', + IO: 'Brytyjskie Terytorium Oceanu Indyjskiego', + BG: 'Bułgaria', + BF: 'Burkina faso', + BI: 'Burundi', + XC: 'Ceuta', + CL: 'Chile', + CN: 'Chiny', + HR: 'Chorwacja', + CW: 'Curaçao', + CY: 'Cypr', + TD: 'Czad', + ME: 'Czarnogóra', + DK: 'Dania', + DM: 'Dominika', + DO: 'Dominikana', + DJ: 'Dżibuti', + EG: 'Egipt', + EC: 'Ekwador', + ER: 'Erytrea', + EE: 'Estonia', + ET: 'Etiopia', + FK: 'Falklandy', + FJ: 'Fidżi Republika', + PH: 'Filipiny', + FI: 'Finlandia', + FR: 'Francja', + TF: 'Francuskie Terytorium Południowe', + GA: 'Gabon', + GM: 'Gambia', + GH: 'Ghana', + GI: 'Gibraltar', + GR: 'Grecja', + GD: 'Grenada', + GL: 'Grenlandia', + GE: 'Gruzja', + GU: 'Guam', + GG: 'Guernsey', + GY: 'Gujana', + GF: 'Gujana Francuska', + GP: 'Gwadelupa', + GT: 'Gwatemala', + GN: 'Gwinea', + GQ: 'Gwinea Równikowa', + GW: 'Gwinea Bissau', + HT: 'Haiti', + ES: 'Hiszpania', + HN: 'Honduras', + HK: 'Hongkong', + IN: 'Indie', + ID: 'Indonezja', + IQ: 'Irak', + IR: 'Iran', + IE: 'Irlandia', + IS: 'Islandia', + IL: 'Izrael', + JM: 'Jamajka', + JP: 'Japonia', + YE: 'Jemen', + JE: 'Jersey', + JO: 'Jordania', + KY: 'Kajmany', + KH: 'Kambodża', + CM: 'Kamerun', + CA: 'Kanada', + QA: 'Katar', + KZ: 'Kazachstan', + KE: 'Kenia', + KG: 'Kirgistan', + KI: 'Kiribati', + CO: 'Kolumbia', + KM: 'Komory', + CG: 'Kongo', + CD: 'Kongo, Republika Demokratyczna', + KP: 'Koreańska Republika Ludowo-Demokratyczna', + XK: 'Kosowo', + CR: 'Kostaryka', + CU: 'Kuba', + KW: 'Kuwejt', + LA: 'Laos', + LS: 'Lesotho', + LB: 'Liban', + LR: 'Liberia', + LY: 'Libia', + LI: 'Liechtenstein', + LT: 'Litwa', + LV: 'Łotwa', + LU: 'Luksemburg', + MK: 'Macedonia', + MG: 'Madagaskar', + YT: 'Majotta', + MO: 'Makau', + MW: 'Malawi', + MV: 'Malediwy', + MY: 'Malezja', + ML: 'Mali', + MT: 'Malta', + MP: 'Mariany Północne', + MA: 'Maroko', + MQ: 'Martynika', + MR: 'Mauretania', + MU: 'Mauritius', + MX: 'Meksyk', + XL: 'Melilla', + FM: 'Mikronezja', + UM: 'Minor', + MD: 'Mołdowa', + MC: 'Monako', + MN: 'Mongolia', + MS: 'Montserrat', + MZ: 'Mozambik', + MM: 'Myanmar (Burma)', + NA: 'Namibia', + NR: 'Nauru', + NP: 'Nepal', + NL: 'Niderlandy (Holandia)', + DE: 'Niemcy', + NE: 'Niger', + NG: 'Nigeria', + NI: 'Nikaragua', + NU: 'Niue', + NF: 'Norfolk', + NO: 'Norwegia', + NC: 'Nowa Kaledonia', + NZ: 'Nowa Zelandia', + PS: 'Okupowane Terytorium Palestyny', + OM: 'Oman', + PK: 'Pakistan', + PW: 'Palau', + PA: 'Panama', + PG: 'Papua Nowa Gwinea', + PY: 'Paragwaj', + PE: 'Peru', + PN: 'Pitcairn', + PF: 'Polinezja Francuska', + PL: 'Polska', + GS: 'Południowa Georgia i Połud.Wyspy Sandwich', + PT: 'Portugalia', + PR: 'Portoryko', + CF: 'Rep.środkowoafrykańska', + CZ: 'Republika Czeska', + KR: 'Republika Korei', + ZA: 'Republika Południowej Afryki', + RE: 'Reunion', + RU: 'Rosja', + RO: 'Rumunia', + RW: 'Rwanda', + EH: 'Sahara Zachodnia', + BL: 'Saint Barthelemy', + KN: 'Saint Kitts i Nevis', + LC: 'Saint Lucia', + MF: 'Saint Martin', + VC: 'Saint Vincent i Grenadyny', + SV: 'Salwador', + WS: 'Samoa', + AS: 'Samoa Amerykańskie', + SM: 'San Marino', + SN: 'Senegal', + RS: 'Serbia', + SC: 'Seszele', + SL: 'Sierra Leone', + SG: 'Singapur', + SK: 'Słowacja', + SI: 'Słowenia', + SO: 'Somalia', + LK: 'Sri Lanka', + PM: 'Saint Pierre i Miquelon', + US: 'Stany Zjednoczone Ameryki', + SZ: 'Suazi', + SD: 'Sudan', + SS: 'Sudan Południowy', + SR: 'Surinam', + SJ: 'Svalbard i Jan Mayen', + SH: 'Święta Helena', + SY: 'Syria', + CH: 'Szwajcaria', + SE: 'Szwecja', + TJ: 'Tadżykistan', + TH: 'Tajlandia', + TW: 'Tajwan', + TZ: 'Tanzania', + TG: 'Togo', + TK: 'Tokelau', + TO: 'Tonga', + TT: 'Trynidad i Tobago', + TN: 'Tunezja', + TR: 'Turcja', + TM: 'Turkmenistan', + TV: 'Tuvalu', + UG: 'Uganda', + UA: 'Ukraina', + UY: 'Urugwaj', + UZ: 'Uzbekistan', + VU: 'Vanuatu', + WF: 'Wallis i Futuna', + VA: 'Watykan', + HU: 'Węgry', + VE: 'Wenezuela', + GB: 'Wielka Brytania', + VN: 'Wietnam', + IT: 'Włochy', + TL: 'Wschodni Timor', + CI: 'Wybrzeże Kości Słoniowej', + BV: 'Wyspa Bouveta', + CX: 'Wyspa Bożego Narodzenia', + IM: 'Wyspa Man', + SX: 'Wyspa Sint Maarten (część holenderska wyspy)', + CK: 'Wyspy Cooka', + VI: 'Wyspy Dziewicze-USA', + VG: 'Wyspy Dziewicze-W.B.', + HM: 'Wyspy Heard i Mcdonald', + CC: 'Wyspy Kokosowe (Keelinga)', + MH: 'Wyspy Marshalla', + FO: 'Wyspy Owcze', + SB: 'Wyspy Salomona', + ST: 'Wyspy Świętego Tomasza i Książęca', + TC: 'Wyspy Turks i Caicos', + ZM: 'Zambia', + CV: 'Zielony Przylądek', + ZW: 'Zimbabwe', + AE: 'Zjednoczone Emiraty Arabskie', + XI: 'Zjednoczone Królestwo (Irlandia Północna)', +}; + +export const FA3RolaPodmiotu3: Record = { + '1': 'Faktor - w przypadku gdy na fakturze występują dane faktora', + '2': 'Odbiorca - w przypadku gdy na fakturze występują dane jednostek wewnętrznych, oddziałów, wyodrębnionych w ramach nabywcy, które same nie stanowią nabywcy w rozumieniu ustawy', + '3': 'Podmiot pierwotny - w przypadku gdy na fakturze występują dane podmiotu będącego w stosunku do podatnika podmiotem przejętym lub przekształconym, który dokonywał dostawy lub świadczył usługę. Z wyłączeniem przypadków, o których mowa w art. 106j ust.2 pkt 3 ustawy, gdy dane te wykazywane są w części Podmiot1K', + '4': 'Dodatkowy nabywca - w przypadku gdy na fakturze występują dane kolejnych (innych niż wymieniony w części Podmiot2) nabywców', + '5': 'Wystawca faktury - w przypadku gdy na fakturze występują dane podmiotu wystawiającego fakturę w imieniu podatnika. Nie dotyczy przypadku, gdy wystawcą faktury jest nabywca', + '6': 'Dokonujący płatności - w przypadku gdy na fakturze występują dane podmiotu regulującego zobowiązanie w miejsce nabywcy', + '7': 'Jednostka samorządu terytorialnego - wystawca', + '8': 'Jednostka samorządu terytorialnego - odbiorca', + '9': 'Członek grupy VAT - wystawca', + '10': 'Członek grupy VAT - odbiorca', + '11': 'Pracownik', +}; + +export const FA2RolaPodmiotu3: Record = { + '1': 'Faktor - w przypadku, gdy na fakturze występują dane faktora', + '2': 'Odbiorca - w przypadku, gdy na fakturze występują dane jednostek wewnętrznych, oddziałów, wyodrębnionych w ramach nabywcy, które same nie stanowią nabywcy w rozumieniu ustawy', + '3': 'Podmiot pierwotny - w przypadku, gdy na fakturze występują dane podmiotu będącego w stosunku do podatnika podmiotem przejętym lub przekształconym, który świadczył usługę lub dokonywał dostawy. Z wyłączeniem przypadków, o których mowa w art. 106j ust.2 pkt 3 ustawy, gdy dane te wykazywane są w części Podmiot1K', + '4': 'Dodatkowy nabywca - w przypadku, gdy na fakturze występują dane kolejnych (innych niż wymieniony w części Podmiot2) nabywców', + '5': 'Wystawca faktury - w przypadku, gdy na fakturze występują dane podmiotu wystawiającego fakturę w imieniu podatnika. Nie dotyczy przypadku, gdy wystawcą faktury jest nabywca', + '6': 'Dokonujący płatności - w przypadku, gdy na fakturze występują dane podmiotu regulującego zobowiązanie w miejsce nabywcy', + '7': 'Jednostka samorządu terytorialnego - wystawca', + '8': 'Jednostka samorządu terytorialnego - odbiorca', + '9': 'Członek grupy VAT - wystawca', + '10': 'Członek grupy VAT - odbiorca', +}; + +export const FA1RolaPodmiotu3: Record = { + '1': 'Faktor - w przypadku, gdy na fakturze występują dane faktora', + '2': 'Odbiorca - w przypadku, gdy na fakturze występują dane jednostek wewnętrznych, oddziałów, wyodrębnionych w ramach nabywcy, które same nie stanowią nabywcy w rozumieniu ustawy', + '3': 'Podmiot pierwotny - w przypadku, gdy na fakturze występują dane podmiotu będącego w stosunku do podatnika podmiotem przejętym lub przekształconym, który świadczył usługę lub dokonywał dostawy. Z wyłączeniem przypadków, o których mowa w art. 106j ust.2 pkt 3 ustawy, gdy dane te wykazywane są w części Podmiot1K', + '4': 'Dodatkowy nabywca - w przypadku, gdy na fakturze występują dane kolejnych (innych niż wymieniony w części Podmiot2) nabywców', + '5': 'Wystawca faktury - w przypadku, gdy na fakturze występują dane podmiotu wystawiającego fakturę w imieniu podatnika. Nie dotyczy przypadku, gdy wystawcą faktury jest nabywca', + '6': 'Dokonujący płatności - w przypadku, gdy na fakturze występują dane podmiotu regulującego zobowiązanie w miejsce nabywcy', +}; + +export const TRolaPodmiotuUpowaznionegoFA3: Record = { + '1': 'Organ egzekucyjny', + '2': 'Komornik sądowy', + '3': 'Przedstawiciel podatkowy', +}; +export const TRolaPodmiotuUpowaznionegoFA2: Record = { + '1': 'Organ egzekucyjny - w przypadku, o którym mowa w art. 106c pkt 1 ustawy', + '2': 'Komornik sądowy - w przypadku, o którym mowa w art. 106c pkt 2 ustawy', + '3': 'Przedstawiciel podatkowy - w przypadku, gdy na fakturze występują dane przedstawiciela podatkowego, o którym mowa w przepisach art. 18a - 18d ustawy', +}; +export const TRolaPodmiotuUpowaznionegoFA1: Record = { + '1': 'Organ egzekucyjny - w przypadku, o którym mowa w art. 106c pkt 1 ustawy', + '2': 'Komornik sądowy - w przypadku, o którym mowa w art. 106c pkt 2 ustawy', + '3': 'Przedstawiciel podatkowy - w przypadku, gdy w fakturze występują dane przedstawiciela podatkowego, o którym mowa w przepisach art. 18a - 18d ustawy', +}; +export const FormaPlatnosci: Record = { + '1': 'Gotówka', + '2': 'Karta', + '3': 'Bon', + '4': 'Czek', + '5': 'Kredyt', + '6': 'Przelew', + '7': 'Mobilna', +}; + +export const RodzajTransportu: Record = { + '1': 'Transport morski', + '2': 'Transport kolejowy', + '3': 'Transport drogowy', + '4': 'Transport lotniczy', + '5': 'Przesyłka pocztowa', + '7': 'Stałe instalacje przesyłowe', + '8': 'Żegluga śródlądowa', +}; + +export const TypRachunkowWlasnych: Record = { + '1': 'Rachunek banku lub rachunek spółdzielczej kasy oszczędnościowo-kredytowej służący do dokonywania rozliczeń z tytułu nabywanych przez ten bank lub tę kasę wierzytelności pieniężnych', + '2': 'Rachunek banku lub rachunek spółdzielczej kasy oszczędnościowo-kredytowej wykorzystywany przez ten bank lub tę kasę do pobrania należności od nabywcy towarów lub usług za dostawę towarów lub świadczenie usług, potwierdzone fakturą, i przekazania jej w całości albo części dostawcy towarów lub usługodawcy', + '3': 'Rachunek banku lub rachunek spółdzielczej kasy oszczędnościowo-kredytowej prowadzony przez ten bank lub tę kasę w ramach gospodarki własnej, niebędący rachunkiem rozliczeniowym', +}; + +export const Procedura: Record = { + '1': 'Stawka 0% stosowana w ramach sprzedaży krajowej', + '2': 'Stawka 0% - wewnątrzwspólnotowa dostawa towarów', + '3': 'Stawka 0% - eksport towarów', + '4': 'Dostawa towarów oraz świadczenie usług opodatkowane poza terytorium kraju', + '5': 'Świadczenie usług z art. 100 ust. 1 pkt 4 ustawy', + '6': 'Towar/usługa wymienione w załączniku 15', + '7': 'Pozostała sprzedaż krajowa', +}; + +export const TableDataType: Record = { + date: FormatTyp.Date, + datetime: FormatTyp.DateTime, + dec: FormatTyp.Currency, + int: FormatTyp.Currency, + time: FormatTyp.Time, + txt: FormatTyp.Value, +}; + +export const TypLadunku: Record = { + '1': 'Bańka', + '2': 'Beczka', + '3': 'Butla', + '4': 'Karton', + '5': 'Kanister', + '6': 'Klatka', + '7': 'Kontener', + '8': 'Kosz/koszyk', + '9': 'Łubianka', + '10': 'Opakowanie zbiorcze', + '11': 'Paczka', + '12': 'Pakiet', + '13': 'Paleta', + '14': 'Pojemnik', + '15': 'Pojemnik do ładunków masowych stałych', + '16': 'Pojemnik do ładunków masowych w postaci płynnej', + '17': 'Pudełko', + '18': 'Puszka', + '19': 'Skrzynia', + '20': 'Worek', +}; + +export const DEFAULT_TABLE_LAYOUT: { + hLineWidth: () => number; + hLineColor: () => string; + vLineWidth: () => number; + vLineColor: () => string; +} = { + hLineWidth: (): number => 1, + hLineColor: (): string => '#BABABA', + vLineWidth: (): number => 1, + vLineColor: (): string => '#BABABA', +}; + +export const TAXPAYER_STATUS: Record = { + '1': 'Stan likwidacji', + '2': 'Postępowanie restrukturyzacyjne', + '3': 'Stan upadłości', + '4': 'Przedsiębiorstwo w spadku', +}; diff --git a/src/shared/consts/FARR.const.ts b/src/shared/consts/FARR.const.ts new file mode 100644 index 00000000..194740b9 --- /dev/null +++ b/src/shared/consts/FARR.const.ts @@ -0,0 +1,24 @@ +export const FARRRolaPodmiotu3: Record = { + '1': 'Faktor - w przypadku gdy na fakturze występują dane faktora', + '2': 'Odbiorca - w przypadku gdy na fakturze występują dane jednostek wewnętrznych, oddziałów, wyodrębnionych w ramach nabywcy, które same nie stanowią nabywcy w rozumieniu ustawy', + '3': 'Podmiot pierwotny - w przypadku gdy na fakturze występują dane podmiotu będącego w stosunku do podatnika podmiotem przejętym lub przekształconym, który dokonywał dostawy lub świadczył usługę. Z wyłączeniem przypadków, o których mowa w art. 106j ust.2 pkt 3 ustawy, gdy dane te wykazywane są w części Podmiot1K', + '5': 'Wystawca faktury - w przypadku gdy na fakturze występują dane podmiotu wystawiającego fakturę w imieniu podatnika. Nie dotyczy przypadku, gdy wystawcą faktury jest nabywca', + '6': 'Dokonujący płatności - w przypadku gdy na fakturze występują dane podmiotu regulującego zobowiązanie w miejsce nabywcy', + '7': 'Jednostka samorządu terytorialnego - wystawca', + '8': 'Jednostka samorządu terytorialnego - odbiorca', + '9': 'Członek grupy VAT - wystawca', + '10': 'Członek grupy VAT - odbiorca', + '11': 'Pracownik', +}; + +export const FARRTypKorekty: Record = { + '1': 'Korekta skutkująca w dacie ujęcia faktury pierwotnej', + '2': 'Korekta skutkująca w dacie wystawienia faktury korygującej', + '3': 'Korekta skutkująca w dacie innej', + '4': 'Korekta skutkująca w dacie dokonania zwrotu całości lub części należności', +}; + +export const TStawkaPodatku_FARR: Record = { + '7': '7%', + '6.5': '6.5%', +}; diff --git a/src/shared/enums/invoice.enums.ts b/src/shared/enums/invoice.enums.ts index 8eb965a9..d30de4d1 100644 --- a/src/shared/enums/invoice.enums.ts +++ b/src/shared/enums/invoice.enums.ts @@ -6,4 +6,6 @@ export enum RodzajFaktury { UPR = 'Faktura, o której mowa w art. 106e ust. 5 pkt 3 ustawy', KOR_ZAL = 'Faktura korygująca fakturę dokumentującą otrzymanie zapłaty lub jej części przed dokonaniem czynności oraz fakturę wystawioną w związku z art. 106f ust. 4 ustawy', KOR_ROZ = 'Faktura korygująca fakturę wystawioną w związku z art. 106f ust. 3 ustawy', + VAT_RR = 'Faktura pierwotna', + KOR_VAT_RR = 'Faktura korygująca' } diff --git a/src/shared/generators/common/functions.spec.ts b/src/shared/generators/common/functions.spec.ts index 2310f2d6..becbcbfa 100644 --- a/src/shared/generators/common/functions.spec.ts +++ b/src/shared/generators/common/functions.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { FA1RolaPodmiotu3, @@ -6,113 +6,76 @@ import { FA3RolaPodmiotu3, FormaPlatnosci, RodzajTransportu, - TRolaPodmiotuUpowaznionegoFA1, - TRolaPodmiotuUpowaznionegoFA2, - TRolaPodmiotuUpowaznionegoFA3, TypRachunkowWlasnych, -} from '../../consts/const'; -import { - formatDateTime, - getDateTimeWithoutSeconds, - getFormaPlatnosciString, - getRodzajTransportuString, - getRolaString, - getRolaUpowaznionegoString, - getTypRachunkowWlasnych, -} from './functions'; - -describe('getRolaString', () => { +} from '../../consts/FA.const'; +import { formatDateTime, getDateTimeWithoutSeconds, translateMap } from '@shared/generators/common/functions'; + +vi.unmock('@shared/generators/common/functions'); + +describe('translateMap RolaPodmimotu', () => { it('returns empty string if rola undefined or _text missing', () => { - expect(getRolaString(undefined, 1)).toBe(''); - expect(getRolaString({} as any, 1)).toBe(''); + expect(translateMap(undefined, FA1RolaPodmiotu3)).toBe(''); + expect(translateMap({} as any, FA1RolaPodmiotu3)).toBe(''); }); it('returns correct string for FA=1', () => { const key = Object.keys(FA1RolaPodmiotu3)[0]; const expected = FA1RolaPodmiotu3[key as keyof typeof FA1RolaPodmiotu3]; - expect(getRolaString({ _text: key } as any, 1)).toBe(expected); + expect(translateMap({ _text: key } as any, FA1RolaPodmiotu3)).toBe(expected); }); it('returns correct string for FA=2', () => { const key = Object.keys(FA2RolaPodmiotu3)[0]; const expected = FA2RolaPodmiotu3[key as keyof typeof FA2RolaPodmiotu3]; - expect(getRolaString({ _text: key } as any, 2)).toBe(expected); + expect(translateMap({ _text: key } as any, FA2RolaPodmiotu3)).toBe(expected); }); it('returns correct string for FA=3', () => { const key = Object.keys(FA3RolaPodmiotu3)[0]; const expected = FA3RolaPodmiotu3[key as keyof typeof FA3RolaPodmiotu3]; - expect(getRolaString({ _text: key } as any, 3)).toBe(expected); + expect(translateMap({ _text: key } as any, FA3RolaPodmiotu3)).toBe(expected); }); }); -describe('getRolaUpowaznionegoString', () => { - it('returns empty string if rola undefined or _text missing', () => { - expect(getRolaUpowaznionegoString(undefined, 1)).toBe(''); - expect(getRolaUpowaznionegoString({} as any, 1)).toBe(''); - }); - - it('returns correct string for FA=1', () => { - const key = Object.keys(TRolaPodmiotuUpowaznionegoFA1)[0]; - const expected = TRolaPodmiotuUpowaznionegoFA1[key].split('-')[0]; - - expect(getRolaUpowaznionegoString({ _text: key } as any, 1)).toBe(expected); - }); - - it('returns correct string for FA=2', () => { - const key = Object.keys(TRolaPodmiotuUpowaznionegoFA2)[0]; - const expected = TRolaPodmiotuUpowaznionegoFA2[key].split('-')[0]; - - expect(getRolaUpowaznionegoString({ _text: key } as any, 2)).toBe(expected); - }); - - it('returns correct string for FA=3', () => { - const key = Object.keys(TRolaPodmiotuUpowaznionegoFA3)[0]; - const expected = TRolaPodmiotuUpowaznionegoFA3[key].split('-')[0]; - - expect(getRolaUpowaznionegoString({ _text: key } as any, 3)).toBe(expected); - }); -}); - -describe('getFormaPlatnosciString', () => { +describe('FormaPlatnosci', () => { it('returns empty string if undefined or no _text', () => { - expect(getFormaPlatnosciString(undefined)).toBe(''); + expect(translateMap(undefined, FormaPlatnosci)).toBe(''); }); it('returns correct string for known key', () => { const key = Object.keys(FormaPlatnosci)[0]; const expected = FormaPlatnosci[key as keyof typeof FormaPlatnosci]; - expect(getFormaPlatnosciString({ _text: key } as any)).toBe(expected); + expect(translateMap({ _text: key } as any, FormaPlatnosci)).toBe(expected); }); }); describe('getRodzajTransportuString', () => { it('returns empty string if undefined or no _text', () => { - expect(getRodzajTransportuString(undefined)).toBe(''); + expect(translateMap(undefined, RodzajTransportu)).toBe(''); }); it('returns correct string for known key', () => { const key = Object.keys(RodzajTransportu)[0]; const expected = RodzajTransportu[key as keyof typeof RodzajTransportu]; - expect(getRodzajTransportuString({ _text: key } as any)).toBe(expected); + expect(translateMap({ _text: key } as any, RodzajTransportu)).toBe(expected); }); }); describe('getTypRachunkowWlasnych', () => { it('returns empty string if undefined or no _text', () => { - expect(getTypRachunkowWlasnych(undefined)).toBe(''); + expect(translateMap(undefined, TypRachunkowWlasnych)).toBe(''); }); it('returns correct string for known key', () => { const key = Object.keys(TypRachunkowWlasnych)[0]; const expected = TypRachunkowWlasnych[key as keyof typeof TypRachunkowWlasnych]; - expect(getTypRachunkowWlasnych({ _text: key } as any)).toBe(expected); + expect(translateMap({ _text: key } as any, TypRachunkowWlasnych)).toBe(expected); }); }); @@ -139,6 +102,10 @@ describe('formatDateTime', () => { expect(formatDateTime(date, true)).toBe('03.10.2025 14:15'); }); + + it('uses Europe/Warsaw for ISO with offset (not process TZ / UTC in Docker)', () => { + expect(formatDateTime('2026-04-02T13:49:00+02:00')).toBe('02.04.2026 13:49:00'); + }); }); describe('getDateTimeWithoutSeconds', () => { diff --git a/src/shared/generators/common/functions.ts b/src/shared/generators/common/functions.ts index 2d334476..542a1ef4 100644 --- a/src/shared/generators/common/functions.ts +++ b/src/shared/generators/common/functions.ts @@ -1,71 +1,53 @@ -import { - FA1RolaPodmiotu3, - FA2RolaPodmiotu3, - FA3RolaPodmiotu3, - FormaPlatnosci, - RodzajTransportu, - TRolaPodmiotuUpowaznionegoFA1, - TRolaPodmiotuUpowaznionegoFA2, - TRolaPodmiotuUpowaznionegoFA3, - TypLadunku, - TypRachunkowWlasnych, -} from '../../consts/const'; import { FP as FP2 } from '../../../lib-public/types/fa2.types'; -export function getRolaString(rola: FP2 | undefined, FA: 1 | 2 | 3): string { - if (!rola || !rola._text) { - return ''; - } - switch (FA) { - case 1: - return FA1RolaPodmiotu3[rola._text as keyof typeof FA1RolaPodmiotu3]; - case 2: - return FA2RolaPodmiotu3[rola._text as keyof typeof FA2RolaPodmiotu3]; - case 3: - return FA3RolaPodmiotu3[rola._text as keyof typeof FA3RolaPodmiotu3]; - } -} - -export function getRolaUpowaznionegoString(rola: FP2 | undefined, FA: 1 | 2 | 3): string { - if (!rola || !rola._text) { - return ''; - } - switch (FA) { - case 1: - return TRolaPodmiotuUpowaznionegoFA1[rola._text].split('-')[0] ?? ''; - case 2: - return TRolaPodmiotuUpowaznionegoFA2[rola._text].split('-')[0] ?? ''; - case 3: - return TRolaPodmiotuUpowaznionegoFA3[rola._text].split('-')[0] ?? ''; - } -} +/** Polish civil time for PDF; avoids Date#getHours() which follows process TZ (e.g. UTC in Docker). */ +const DISPLAY_TZ = 'Europe/Warsaw'; -export function getFormaPlatnosciString(formaPlatnosci: FP2 | undefined): string { - if (!formaPlatnosci || !formaPlatnosci._text) { - return ''; +function getWarsawDateOnly(date: Date): { day: string; month: string; year: string } { + const parts = new Intl.DateTimeFormat('en-GB', { + timeZone: DISPLAY_TZ, + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).formatToParts(date); + const m: Record = {}; + for (const p of parts) { + if (p.type !== 'literal') { + m[p.type] = p.value; + } } - return FormaPlatnosci[formaPlatnosci._text as keyof typeof FormaPlatnosci] ?? ''; + return { day: m.day ?? '', month: m.month ?? '', year: m.year ?? '' }; } -export function getRodzajTransportuString(rodzajTransportu: FP2 | undefined): string { - if (!rodzajTransportu || !rodzajTransportu._text) { - return ''; +function getWarsawParts(date: Date, includeSeconds: boolean): Record { + const opts: Intl.DateTimeFormatOptions = { + timeZone: DISPLAY_TZ, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hourCycle: 'h23', + ...(includeSeconds ? { second: '2-digit' } : {}), + }; + const parts = new Intl.DateTimeFormat('en-GB', opts).formatToParts(date); + const m: Record = {}; + for (const p of parts) { + if (p.type !== 'literal') { + m[p.type] = p.value; + } } - return RodzajTransportu[rodzajTransportu._text as keyof typeof RodzajTransportu] ?? ''; + return m; } -export function getOpisTransportuString(opisTransportu: FP2 | undefined): string { - if (!opisTransportu || !opisTransportu._text) { - return ''; - } - return TypLadunku[opisTransportu._text as keyof typeof TypLadunku] ?? ''; -} +export function translateMap(value: FP2 | string | undefined, map: Record): string { + let valueToTranslate = typeof value === 'string' ? value : value?._text; -export function getTypRachunkowWlasnych(typRachonkowWlasnych: FP2 | undefined): string { - if (!typRachonkowWlasnych || !typRachonkowWlasnych._text) { + valueToTranslate = valueToTranslate?.trim(); + if (!valueToTranslate || !map[valueToTranslate]) { return ''; } - return TypRachunkowWlasnych[typRachonkowWlasnych._text as keyof typeof TypRachunkowWlasnych] ?? ''; + return map[valueToTranslate]; } export function formatDateTime(data?: string, withoutSeconds?: boolean, withoutTime?: boolean): string { @@ -78,19 +60,19 @@ export function formatDateTime(data?: string, withoutSeconds?: boolean, withoutT return data; } - const year: number = dateTime.getFullYear(); - const month: string = (dateTime.getMonth() + 1).toString().padStart(2, '0'); - const day: string = dateTime.getDate().toString().padStart(2, '0'); - const hours: string = dateTime.getHours().toString().padStart(2, '0'); - const minutes: string = dateTime.getMinutes().toString().padStart(2, '0'); - const seconds: string = dateTime.getSeconds().toString().padStart(2, '0'); - if (withoutTime) { + const { day, month, year } = getWarsawDateOnly(dateTime); return `${day}.${month}.${year}`; - } else if (withoutSeconds) { - return `${day}.${month}.${year} ${hours}:${minutes}`; } - return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`; + + const withSec = !withoutSeconds; + const p = getWarsawParts(dateTime, withSec); + const { day, month, year, hour, minute, second } = p; + + if (withoutSeconds) { + return `${day}.${month}.${year} ${hour}:${minute}`; + } + return `${day}.${month}.${year} ${hour}:${minute}:${second ?? '00'}`; } export function getDateTimeWithoutSeconds(isoDate?: FP2): string { @@ -99,22 +81,25 @@ export function getDateTimeWithoutSeconds(isoDate?: FP2): string { } return formatDateTime(isoDate._text, true); } + export function formatTime(data?: string, withoutSeconds?: boolean): string { - if (!data) { - return ''; - } - const dateTime: Date = new Date(data); + if (!data) { + return ''; + } + const dateTime: Date = new Date(data); - if (isNaN(dateTime.getTime())) { - return data; - } + if (isNaN(dateTime.getTime())) { + return data; + } - const hours: string = dateTime.getHours().toString().padStart(2, '0'); - const minutes: string = dateTime.getMinutes().toString().padStart(2, '0'); - const seconds: string = dateTime.getSeconds().toString().padStart(2, '0'); + const withSec = !withoutSeconds; + const p = getWarsawParts(dateTime, withSec); + const hour = p.hour ?? ''; + const minute = p.minute ?? ''; + const second = p.second ?? '00'; - if (withoutSeconds) { - return `${hours}:${minutes}`; - } - return `${hours}:${minutes}:${seconds}`; -} \ No newline at end of file + if (withoutSeconds) { + return `${hour}:${minute}`; + } + return `${hour}:${minute}:${second}`; +} diff --git a/src/shared/mocks/functions.mock.ts b/src/shared/mocks/functions.mock.ts new file mode 100644 index 00000000..9f666151 --- /dev/null +++ b/src/shared/mocks/functions.mock.ts @@ -0,0 +1,13 @@ +import { vi } from 'vitest'; + +vi.mock('@shared/generators/common/functions.ts', async (importOriginal) => { + const original = await importOriginal(); + + return { + ...original, + formatDateTime: vi.fn(original.formatDateTime), + getDateTimeWithoutSeconds: vi.fn(original.getDateTimeWithoutSeconds), + formatTime: vi.fn(original.formatTime), + translateMap: vi.fn(original.translateMap), + }; +}); diff --git a/tsconfig.base.json b/tsconfig.base.json index c6835a2b..545bbac2 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -13,7 +13,6 @@ "paths": { "@lib-public/*": ["lib-public/*"], "@lib-private/*": ["lib-private/*"] - }, - "include": ["src"] + } } } diff --git a/tsconfig.server.json b/tsconfig.server.json new file mode 100644 index 00000000..6ac9c7a6 --- /dev/null +++ b/tsconfig.server.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "module": "commonjs", + "target": "es2016" + }, + "include": ["src/server"] +}