diff --git a/backend/.gitignore b/backend/.gitignore deleted file mode 100644 index 4b56acf..0000000 --- a/backend/.gitignore +++ /dev/null @@ -1,56 +0,0 @@ -# compiled output -/dist -/node_modules -/build - -# Logs -logs -*.log -npm-debug.log* -pnpm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# OS -.DS_Store - -# Tests -/coverage -/.nyc_output - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# temp directory -.temp -.tmp - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/backend/.prettierrc b/backend/.prettierrc deleted file mode 100644 index a20502b..0000000 --- a/backend/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all" -} diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index 8f0f65f..0000000 --- a/backend/README.md +++ /dev/null @@ -1,98 +0,0 @@ -

- Nest Logo -

- -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest - -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Discord -Backers on Open Collective -Sponsors on Open Collective - Donate us - Support us - Follow us on Twitter -

- - -## Description - -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. - -## Project setup - -```bash -$ npm install -``` - -## Compile and run the project - -```bash -# development -$ npm run start - -# watch mode -$ npm run start:dev - -# production mode -$ npm run start:prod -``` - -## Run tests - -```bash -# unit tests -$ npm run test - -# e2e tests -$ npm run test:e2e - -# test coverage -$ npm run test:cov -``` - -## Deployment - -When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. - -If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: - -```bash -$ npm install -g @nestjs/mau -$ mau deploy -``` - -With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. - -## Resources - -Check out a few resources that may come in handy when working with NestJS: - -- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. -- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). -- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). -- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. -- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). -- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). -- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). -- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). - -## Support - -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). - -## Stay in touch - -- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) - -## License - -Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/backend/database.sqlite b/backend/database.sqlite deleted file mode 100644 index 5724627..0000000 Binary files a/backend/database.sqlite and /dev/null differ diff --git a/backend/eslint.config.mjs b/backend/eslint.config.mjs deleted file mode 100644 index 4e9f827..0000000 --- a/backend/eslint.config.mjs +++ /dev/null @@ -1,35 +0,0 @@ -// @ts-check -import eslint from '@eslint/js'; -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; -import globals from 'globals'; -import tseslint from 'typescript-eslint'; - -export default tseslint.config( - { - ignores: ['eslint.config.mjs'], - }, - eslint.configs.recommended, - ...tseslint.configs.recommendedTypeChecked, - eslintPluginPrettierRecommended, - { - languageOptions: { - globals: { - ...globals.node, - ...globals.jest, - }, - sourceType: 'commonjs', - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - }, - { - rules: { - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/no-unsafe-argument': 'warn', - "prettier/prettier": ["error", { endOfLine: "auto" }], - }, - }, -); diff --git a/backend/nest-cli.json b/backend/nest-cli.json deleted file mode 100644 index f9aa683..0000000 --- a/backend/nest-cli.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/nest-cli", - "collection": "@nestjs/schematics", - "sourceRoot": "src", - "compilerOptions": { - "deleteOutDir": true - } -} diff --git a/backend/package-lock.json b/backend/package-lock.json deleted file mode 100644 index d03f13b..0000000 --- a/backend/package-lock.json +++ /dev/null @@ -1,12237 +0,0 @@ -{ - "name": "backend", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "backend", - "version": "0.0.1", - "license": "UNLICENSED", - "dependencies": { - "@nestjs/common": "^11.0.1", - "@nestjs/config": "^4.0.3", - "@nestjs/core": "^11.0.1", - "@nestjs/jwt": "^11.0.2", - "@nestjs/passport": "^11.0.5", - "@nestjs/platform-express": "^11.0.1", - "@nestjs/typeorm": "^11.0.0", - "bcryptjs": "^3.0.3", - "class-validator": "^0.15.1", - "passport": "^0.7.0", - "passport-jwt": "^4.0.1", - "pg": "^8.20.0", - "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1", - "sqlite3": "^5.1.7", - "typeorm": "^0.3.28" - }, - "devDependencies": { - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "^9.18.0", - "@nestjs/cli": "^11.0.0", - "@nestjs/schematics": "^11.0.0", - "@nestjs/testing": "^11.0.1", - "@types/express": "^5.0.0", - "@types/jest": "^30.0.0", - "@types/node": "^22.10.7", - "@types/supertest": "^6.0.2", - "eslint": "^9.18.0", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-prettier": "^5.2.2", - "globals": "^16.0.0", - "jest": "^30.0.0", - "prettier": "^3.4.2", - "source-map-support": "^0.5.21", - "supertest": "^7.0.0", - "ts-jest": "^29.2.5", - "ts-loader": "^9.5.2", - "ts-node": "^10.9.2", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.7.3", - "typescript-eslint": "^8.20.0" - } - }, - "node_modules/@angular-devkit/core": { - "version": "19.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.19.tgz", - "integrity": "sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@angular-devkit/core/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/schematics": { - "version": "19.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.19.tgz", - "integrity": "sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "19.2.19", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.17", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics-cli": { - "version": "19.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.19.tgz", - "integrity": "sha512-7q9UY6HK6sccL9F3cqGRUwKhM7b/XfD2YcVaZ2WD7VMaRlRm85v6mRjSrfKIAwxcQU0UK27kMc79NIIqaHjzxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "19.2.19", - "@angular-devkit/schematics": "19.2.19", - "@inquirer/prompts": "7.3.2", - "ansi-colors": "4.1.3", - "symbol-observable": "4.0.0", - "yargs-parser": "21.1.1" - }, - "bin": { - "schematics": "bin/schematics.js" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/@inquirer/prompts": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz", - "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^4.1.2", - "@inquirer/confirm": "^5.1.6", - "@inquirer/editor": "^4.2.7", - "@inquirer/expand": "^4.0.9", - "@inquirer/input": "^4.1.6", - "@inquirer/number": "^3.0.9", - "@inquirer/password": "^4.0.9", - "@inquirer/rawlist": "^4.0.9", - "@inquirer/search": "^3.0.9", - "@inquirer/select": "^4.0.9" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@borewit/text-codec": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", - "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@emnapi/core": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", - "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", - "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "license": "MIT", - "optional": true - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", - "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/checkbox": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", - "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/confirm": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", - "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", - "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/editor": { - "version": "4.2.23", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", - "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/external-editor": "^1.0.3", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/expand": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", - "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", - "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/number": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", - "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/password": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", - "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/prompts": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", - "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^4.3.2", - "@inquirer/confirm": "^5.1.21", - "@inquirer/editor": "^4.2.23", - "@inquirer/expand": "^4.0.23", - "@inquirer/input": "^4.3.1", - "@inquirer/number": "^3.0.23", - "@inquirer/password": "^4.0.23", - "@inquirer/rawlist": "^4.1.11", - "@inquirer/search": "^3.2.2", - "@inquirer/select": "^4.4.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", - "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/search": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", - "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/select": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", - "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", - "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", - "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.3.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.3.0", - "jest-config": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-resolve-dependencies": "30.3.0", - "jest-runner": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "jest-watcher": "30.3.0", - "pretty-format": "30.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", - "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", - "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-mock": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "30.3.0", - "jest-snapshot": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", - "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", - "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@sinonjs/fake-timers": "^15.0.0", - "@types/node": "*", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", - "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/types": "30.3.0", - "jest-mock": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", - "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", - "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", - "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.3.0", - "@jest/types": "30.3.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", - "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.3.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", - "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.3.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", - "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@lukeed/csprng": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", - "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@nestjs/cli": { - "version": "11.0.16", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.16.tgz", - "integrity": "sha512-P0H+Vcjki6P5160E5QnMt3Q0X5FTg4PZkP99Ig4lm/4JWqfw32j3EXv3YBTJ2DmxLwOQ/IS9F7dzKpMAgzKTGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "19.2.19", - "@angular-devkit/schematics": "19.2.19", - "@angular-devkit/schematics-cli": "19.2.19", - "@inquirer/prompts": "7.10.1", - "@nestjs/schematics": "^11.0.1", - "ansis": "4.2.0", - "chokidar": "4.0.3", - "cli-table3": "0.6.5", - "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "9.1.0", - "glob": "13.0.0", - "node-emoji": "1.11.0", - "ora": "5.4.1", - "tsconfig-paths": "4.2.0", - "tsconfig-paths-webpack-plugin": "4.2.0", - "typescript": "5.9.3", - "webpack": "5.104.1", - "webpack-node-externals": "3.0.0" - }, - "bin": { - "nest": "bin/nest.js" - }, - "engines": { - "node": ">= 20.11" - }, - "peerDependencies": { - "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0", - "@swc/core": "^1.3.62" - }, - "peerDependenciesMeta": { - "@swc/cli": { - "optional": true - }, - "@swc/core": { - "optional": true - } - } - }, - "node_modules/@nestjs/cli/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@nestjs/cli/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@nestjs/cli/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/@nestjs/cli/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@nestjs/cli/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@nestjs/cli/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/@nestjs/cli/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/cli/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/cli/node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/@nestjs/cli/node_modules/webpack": { - "version": "5.104.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", - "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.4", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.4.4", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/@nestjs/common": { - "version": "11.1.16", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.16.tgz", - "integrity": "sha512-JSIeW+USuMJkkcNbiOdcPkVCeI3TSnXstIVEPpp3HiaKnPRuSbUUKm9TY9o/XpIcPHWUOQItAtC5BiAwFdVITQ==", - "license": "MIT", - "dependencies": { - "file-type": "21.3.0", - "iterare": "1.2.1", - "load-esm": "1.0.3", - "tslib": "2.8.1", - "uid": "2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "class-transformer": ">=0.4.1", - "class-validator": ">=0.13.2", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" - }, - "peerDependenciesMeta": { - "class-transformer": { - "optional": true - }, - "class-validator": { - "optional": true - } - } - }, - "node_modules/@nestjs/config": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.3.tgz", - "integrity": "sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==", - "license": "MIT", - "dependencies": { - "dotenv": "17.2.3", - "dotenv-expand": "12.0.3", - "lodash": "4.17.23" - }, - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "rxjs": "^7.1.0" - } - }, - "node_modules/@nestjs/core": { - "version": "11.1.16", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.16.tgz", - "integrity": "sha512-tXWXyCiqWthelJjrE0KLFjf0O98VEt+WPVx5CrqCf+059kIxJ8y1Vw7Cy7N4fwQafWNrmFL2AfN87DDMbVAY0w==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@nuxt/opencollective": "0.4.1", - "fast-safe-stringify": "2.1.1", - "iterare": "1.2.1", - "path-to-regexp": "8.3.0", - "tslib": "2.8.1", - "uid": "2.0.2" - }, - "engines": { - "node": ">= 20" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "@nestjs/common": "^11.0.0", - "@nestjs/microservices": "^11.0.0", - "@nestjs/platform-express": "^11.0.0", - "@nestjs/websockets": "^11.0.0", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" - }, - "peerDependenciesMeta": { - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - }, - "@nestjs/websockets": { - "optional": true - } - } - }, - "node_modules/@nestjs/jwt": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", - "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==", - "license": "MIT", - "dependencies": { - "@types/jsonwebtoken": "9.0.10", - "jsonwebtoken": "9.0.3" - }, - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" - } - }, - "node_modules/@nestjs/passport": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", - "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" - } - }, - "node_modules/@nestjs/platform-express": { - "version": "11.1.16", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.16.tgz", - "integrity": "sha512-IOegr5+ZfUiMKgk+garsSU4MOkPRhm46e6w8Bp1GcO4vCdl9Piz6FlWAzKVfa/U3Hn/DdzSVJOW3TWcQQFdBDw==", - "license": "MIT", - "dependencies": { - "cors": "2.8.6", - "express": "5.2.1", - "multer": "2.1.1", - "path-to-regexp": "8.3.0", - "tslib": "2.8.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "@nestjs/common": "^11.0.0", - "@nestjs/core": "^11.0.0" - } - }, - "node_modules/@nestjs/schematics": { - "version": "11.0.9", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.9.tgz", - "integrity": "sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "19.2.17", - "@angular-devkit/schematics": "19.2.17", - "comment-json": "4.4.1", - "jsonc-parser": "3.3.1", - "pluralize": "8.0.0" - }, - "peerDependencies": { - "typescript": ">=4.8.2" - } - }, - "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.17.tgz", - "integrity": "sha512-Ah008x2RJkd0F+NLKqIpA34/vUGwjlprRCkvddjDopAWRzYn6xCkz1Tqwuhn0nR1Dy47wTLKYD999TYl5ONOAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.17.tgz", - "integrity": "sha512-ADfbaBsrG8mBF6Mfs+crKA/2ykB8AJI50Cv9tKmZfwcUcyAdmTr+vVvhsBCfvUAEokigSsgqgpYxfkJVxhJYeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "19.2.17", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.17", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@nestjs/schematics/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@nestjs/schematics/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/@nestjs/schematics/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@nestjs/testing": { - "version": "11.1.16", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.16.tgz", - "integrity": "sha512-E7/aUCxzeMSJV80L5GWGIuiMyR/1ncS7uOIetAImfbS4ATE1/h2GBafk0qpk+vjFtPIbtoh9BWDGICzUEU5jDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "@nestjs/common": "^11.0.0", - "@nestjs/core": "^11.0.0", - "@nestjs/microservices": "^11.0.0", - "@nestjs/platform-express": "^11.0.0" - }, - "peerDependenciesMeta": { - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - } - } - }, - "node_modules/@nestjs/typeorm": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", - "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "@nestjs/core": "^10.0.0 || ^11.0.0", - "reflect-metadata": "^0.1.13 || ^0.2.0", - "rxjs": "^7.2.0", - "typeorm": "^0.3.0" - } - }, - "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/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", - "optional": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@nuxt/opencollective": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz", - "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==", - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - }, - "bin": { - "opencollective": "bin/opencollective.js" - }, - "engines": { - "node": "^14.18.0 || >=16.10.0", - "npm": ">=5.10.0" - } - }, - "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", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", - "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sqltools/formatter": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", - "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", - "license": "MIT" - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "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/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", - "dev": true, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "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/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", - "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "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/validator": { - "version": "13.15.10", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", - "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", - "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/type-utils": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.57.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", - "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", - "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.0", - "@typescript-eslint/types": "^8.57.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", - "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", - "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", - "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", - "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", - "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.57.0", - "@typescript-eslint/tsconfig-utils": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", - "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", - "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC", - "optional": true - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "devOptional": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "optional": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ansis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", - "license": "ISC", - "engines": { - "node": ">=14" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, - "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/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "license": "ISC", - "optional": true - }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-timsort": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", - "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", - "dev": true, - "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/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/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/babel-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", - "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.3.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.3.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", - "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", - "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "30.3.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "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.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "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/baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/bcryptjs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", - "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", - "license": "BSD-3-Clause", - "bin": { - "bcrypt": "bin/bcrypt" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "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/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cacache/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacache/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001779", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz", - "integrity": "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/class-validator": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.15.1.tgz", - "integrity": "sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==", - "license": "MIT", - "dependencies": { - "@types/validator": "^13.15.3", - "libphonenumber-js": "^1.11.1", - "validator": "^13.15.22" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "optional": true, - "bin": { - "color-support": "bin.js" - } - }, - "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": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/comment-json": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.4.1.tgz", - "integrity": "sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-timsort": "^1.0.3", - "core-util-is": "^1.0.3", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "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", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC", - "optional": true - }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "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.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/dayjs": { - "version": "1.11.20", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", - "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "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/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT", - "optional": true - }, - "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/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", - "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "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/electron-to-chromium": { - "version": "1.5.313", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", - "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "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/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "license": "MIT", - "optional": true - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "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/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", - "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.1", - "synckit": "^0.11.12" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "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/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.3.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "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==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/file-type": { - "version": "21.3.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", - "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz", - "integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "chalk": "^4.1.2", - "chokidar": "^4.0.1", - "cosmiconfig": "^8.2.0", - "deepmerge": "^4.2.2", - "fs-extra": "^10.0.0", - "memfs": "^3.4.1", - "minimatch": "^3.0.4", - "node-abort-controller": "^3.0.1", - "schema-utils": "^3.1.1", - "semver": "^7.3.5", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "typescript": ">3.6.0", - "webpack": "^5.11.0" - } - }, - "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/form-data/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.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": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/fs-monkey": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", - "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", - "dev": true, - "license": "Unlicense" - }, - "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==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/gauge/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC", - "optional": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/glob": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", - "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/glob/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC", - "optional": true - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause", - "optional": true - }, - "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": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "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": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "license": "ISC", - "optional": true - }, - "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.", - "devOptional": true, - "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/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "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-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "license": "MIT", - "optional": true - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/iterare": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", - "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", - "license": "ISC", - "engines": { - "node": ">=6" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", - "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.3.0", - "@jest/types": "30.3.0", - "import-local": "^3.2.0", - "jest-cli": "30.3.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", - "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.3.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", - "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "p-limit": "^3.1.0", - "pretty-format": "30.3.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-cli": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", - "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", - "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.3.0", - "@jest/types": "30.3.0", - "babel-jest": "30.3.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-circus": "30.3.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-runner": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "parse-json": "^5.2.0", - "pretty-format": "30.3.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/jest-config/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-config/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/jest-config/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-config/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.3.0", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", - "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "jest-util": "30.3.0", - "pretty-format": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", - "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", - "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", - "picomatch": "^4.0.3", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" - } - }, - "node_modules/jest-haste-map/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-leak-detector": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", - "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", - "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.3.0", - "pretty-format": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", - "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.3.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3", - "pretty-format": "30.3.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-mock": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", - "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-util": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", - "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", - "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", - "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.3.0", - "@jest/environment": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-leak-detector": "30.3.0", - "jest-message-util": "30.3.0", - "jest-resolve": "30.3.0", - "jest-runtime": "30.3.0", - "jest-util": "30.3.0", - "jest-watcher": "30.3.0", - "jest-worker": "30.3.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runtime": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", - "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/globals": "30.3.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-runtime/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/jest-runtime/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-runtime/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-snapshot": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", - "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.3.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.3.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "pretty-format": "30.3.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", - "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", - "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", - "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.3.0", - "string-length": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", - "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.3.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/libphonenumber-js": { - "version": "1.12.40", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.40.tgz", - "integrity": "sha512-HKGs7GowShNls3Zh+7DTr6wYpPk5jC78l508yQQY3e8ZgJChM3A9JZghmMJZuK+5bogSfuTafpjksGSR3aMIEg==", - "license": "MIT" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/load-esm": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", - "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - }, - { - "type": "buymeacoffee", - "url": "https://buymeacoffee.com/borewit" - } - ], - "license": "MIT", - "engines": { - "node": ">=13.2.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "license": "ISC", - "optional": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-fetch-happen/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/make-fetch-happen/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "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.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "license": "MIT", - "optional": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/minipass-fetch/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-fetch/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/multer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz", - "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "type-is": "^1.6.18" - }, - "engines": { - "node": ">= 10.16.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/multer/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/multer/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/multer/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/multer/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/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-abi": { - "version": "3.88.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.88.0.tgz", - "integrity": "sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "license": "MIT", - "optional": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "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/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/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/passport": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", - "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", - "license": "MIT", - "dependencies": { - "passport-strategy": "1.x.x", - "pause": "0.0.1", - "utils-merge": "^1.0.1" - }, - "engines": { - "node": ">= 0.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" - } - }, - "node_modules/passport-jwt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", - "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", - "license": "MIT", - "dependencies": { - "jsonwebtoken": "^9.0.0", - "passport-strategy": "^1.0.0" - } - }, - "node_modules/passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" - }, - "node_modules/pg": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", - "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.12.0", - "pg-pool": "^3.13.0", - "pg-protocol": "^1.13.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.3.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", - "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", - "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", - "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", - "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", - "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "license": "ISC", - "optional": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "license": "MIT", - "optional": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "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", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC", - "optional": true - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "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/sha.js": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", - "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", - "license": "(MIT AND BSD-3-Clause)", - "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1", - "to-buffer": "^1.2.0" - }, - "bin": { - "sha.js": "bin.js" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "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/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "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/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "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": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "optional": true, - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/sql-highlight": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", - "integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==", - "funding": [ - "https://github.com/scriptcoded/sql-highlight?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/scriptcoded" - } - ], - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/sqlite3": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", - "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1", - "tar": "^6.1.11" - }, - "optionalDependencies": { - "node-gyp": "8.x" - }, - "peerDependencies": { - "node-gyp": "8.x" - }, - "peerDependenciesMeta": { - "node-gyp": { - "optional": true - } - } - }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/ssri/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ssri/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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/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.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/superagent": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", - "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", - "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.5", - "formidable": "^3.5.4", - "methods": "^1.1.2", - "mime": "2.6.0", - "qs": "^6.14.1" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/supertest": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", - "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cookie-signature": "^1.2.2", - "methods": "^1.1.2", - "superagent": "^10.3.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-observable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", - "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", - "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-buffer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", - "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", - "license": "MIT", - "dependencies": { - "isarray": "^2.0.5", - "safe-buffer": "^5.2.1", - "typed-array-buffer": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "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/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.3", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-loader": { - "version": "9.5.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", - "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", - "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tapable": "^2.2.1", - "tsconfig-paths": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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/typeorm": { - "version": "0.3.28", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", - "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", - "license": "MIT", - "dependencies": { - "@sqltools/formatter": "^1.2.5", - "ansis": "^4.2.0", - "app-root-path": "^3.1.0", - "buffer": "^6.0.3", - "dayjs": "^1.11.19", - "debug": "^4.4.3", - "dedent": "^1.7.0", - "dotenv": "^16.6.1", - "glob": "^10.5.0", - "reflect-metadata": "^0.2.2", - "sha.js": "^2.4.12", - "sql-highlight": "^6.1.0", - "tslib": "^2.8.1", - "uuid": "^11.1.0", - "yargs": "^17.7.2" - }, - "bin": { - "typeorm": "cli.js", - "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", - "typeorm-ts-node-esm": "cli-ts-node-esm.js" - }, - "engines": { - "node": ">=16.13.0" - }, - "funding": { - "url": "https://opencollective.com/typeorm" - }, - "peerDependencies": { - "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "@sap/hana-client": "^2.14.22", - "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", - "ioredis": "^5.0.4", - "mongodb": "^5.8.0 || ^6.0.0", - "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", - "mysql2": "^2.2.5 || ^3.0.1", - "oracledb": "^6.3.0", - "pg": "^8.5.1", - "pg-native": "^3.0.0", - "pg-query-stream": "^4.0.0", - "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", - "sql.js": "^1.4.0", - "sqlite3": "^5.0.3", - "ts-node": "^10.7.0", - "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" - }, - "peerDependenciesMeta": { - "@google-cloud/spanner": { - "optional": true - }, - "@sap/hana-client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mssql": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "oracledb": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-native": { - "optional": true - }, - "pg-query-stream": { - "optional": true - }, - "redis": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "ts-node": { - "optional": true - }, - "typeorm-aurora-data-api-driver": { - "optional": true - } - } - }, - "node_modules/typeorm/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typeorm/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "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": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/typeorm/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/typeorm/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/typeorm/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", - "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.0", - "@typescript-eslint/parser": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uid": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", - "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", - "license": "MIT", - "dependencies": { - "@lukeed/csprng": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "license": "ISC", - "optional": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "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/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.0" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "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/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/validator": { - "version": "13.15.26", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", - "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", - "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/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webpack": { - "version": "5.105.4", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", - "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.16.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.20.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.17", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.4" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-node-externals": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", - "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", - "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/webpack/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==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/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==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "optional": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?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/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.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/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", - "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/backend/package.json b/backend/package.json deleted file mode 100644 index 3a413dc..0000000 --- a/backend/package.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "name": "backend", - "version": "0.0.1", - "description": "", - "author": "", - "private": true, - "license": "UNLICENSED", - "scripts": { - "build": "nest build", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "start": "nest start", - "start:dev": "nest start --watch", - "start:debug": "nest start --debug --watch", - "start:prod": "node dist/main", - "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "test": "jest", - "test:watch": "jest --watch", - "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" - }, - "dependencies": { - "@nestjs/common": "^11.0.1", - "@nestjs/config": "^4.0.3", - "@nestjs/core": "^11.0.1", - "@nestjs/jwt": "^11.0.2", - "@nestjs/passport": "^11.0.5", - "@nestjs/platform-express": "^11.0.1", - "@nestjs/typeorm": "^11.0.0", - "bcryptjs": "^3.0.3", - "class-validator": "^0.15.1", - "passport": "^0.7.0", - "passport-jwt": "^4.0.1", - "pg": "^8.20.0", - "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1", - "sqlite3": "^5.1.7", - "typeorm": "^0.3.28" - }, - "devDependencies": { - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "^9.18.0", - "@nestjs/cli": "^11.0.0", - "@nestjs/schematics": "^11.0.0", - "@nestjs/testing": "^11.0.1", - "@types/express": "^5.0.0", - "@types/jest": "^30.0.0", - "@types/node": "^22.10.7", - "@types/supertest": "^6.0.2", - "eslint": "^9.18.0", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-prettier": "^5.2.2", - "globals": "^16.0.0", - "jest": "^30.0.0", - "prettier": "^3.4.2", - "source-map-support": "^0.5.21", - "supertest": "^7.0.0", - "ts-jest": "^29.2.5", - "ts-loader": "^9.5.2", - "ts-node": "^10.9.2", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.7.3", - "typescript-eslint": "^8.20.0" - }, - "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], - "rootDir": "src", - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], - "coverageDirectory": "../coverage", - "testEnvironment": "node" - } -} diff --git a/backend/src/app.controller.spec.ts b/backend/src/app.controller.spec.ts deleted file mode 100644 index d22f389..0000000 --- a/backend/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - - appController = app.get(AppController); - }); - - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); - }); -}); diff --git a/backend/src/app.controller.ts b/backend/src/app.controller.ts deleted file mode 100644 index cce879e..0000000 --- a/backend/src/app.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Get() - getHello(): string { - return this.appService.getHello(); - } -} diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts deleted file mode 100644 index 10b8738..0000000 --- a/backend/src/app.module.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConfigModule } from '@nestjs/config'; -import { databaseConfig } from './config/database.config'; -import { AuthModule } from './modules/auth/auth.module'; -import { UsersModule } from './modules/users/users.module'; -import { RolesModule } from './modules/roles/roles.module'; -import { PermissionsModule } from './modules/permissions/permissions.module'; -import { CustomersModule } from './modules/customers/customers.module'; -import { SuppliersModule } from './modules/suppliers/suppliers.module'; -import { SubcontractorsModule } from './modules/subcontractors/subcontractors.module'; -import { ProjectsModule } from './modules/projects/projects.module'; -import { QuotationsModule } from './modules/quotations/quotations.module'; -import { PurchasesModule } from './modules/purchases/purchases.module'; -import { SubcontractsModule } from './modules/subcontracts/subcontracts.module'; -import { ContractsModule } from './modules/contracts/contracts.module'; -import { FinanceModule } from './modules/finance/finance.module'; -import { ProductsModule } from './modules/products/products.module'; -import { SeedService } from './database/seed'; - -@Module({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - }), - TypeOrmModule.forRoot(databaseConfig), - AuthModule, - UsersModule, - RolesModule, - PermissionsModule, - CustomersModule, - SuppliersModule, - SubcontractorsModule, - ProjectsModule, - QuotationsModule, - PurchasesModule, - SubcontractsModule, - ContractsModule, - FinanceModule, - ProductsModule, - ], - controllers: [], - providers: [SeedService], -}) -export class AppModule {} \ No newline at end of file diff --git a/backend/src/app.service.ts b/backend/src/app.service.ts deleted file mode 100644 index 927d7cc..0000000 --- a/backend/src/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getHello(): string { - return 'Hello World!'; - } -} diff --git a/backend/src/config/database.config.ts b/backend/src/config/database.config.ts deleted file mode 100644 index c1c8a3b..0000000 --- a/backend/src/config/database.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { TypeOrmModuleOptions } from '@nestjs/typeorm'; -import * as dotenv from 'dotenv'; -import * as path from 'path'; - -dotenv.config(); - -export const databaseConfig: TypeOrmModuleOptions = { - type: 'sqlite', - database: path.join(__dirname, '..', '..', 'database.sqlite'), - entities: [__dirname + '/../**/*.entity{.ts,.js}'], - synchronize: true, - logging: true, -}; diff --git a/backend/src/database/seed.ts b/backend/src/database/seed.ts deleted file mode 100644 index 5fe3484..0000000 --- a/backend/src/database/seed.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Permission } from '../modules/permissions/permission.entity'; -import { Role } from '../modules/roles/role.entity'; -import { User } from '../modules/users/user.entity'; -import * as bcrypt from 'bcryptjs'; - -@Injectable() -export class SeedService implements OnModuleInit { - constructor( - @InjectRepository(Permission) private permissionRepo: Repository, - @InjectRepository(Role) private roleRepo: Repository, - @InjectRepository(User) private userRepo: Repository, - ) {} - - async onModuleInit() { - await this.seedPermissions(); - await this.seedRoles(); - await this.seedAdminUser(); - } - - private async seedPermissions() { - const permissions = [ - { name: 'user:create', description: '创建用户' }, - { name: 'user:read', description: '读取用户' }, - { name: 'user:update', description: '更新用户' }, - { name: 'user:delete', description: '删除用户' }, - { name: 'role:create', description: '创建角色' }, - { name: 'role:read', description: '读取角色' }, - { name: 'role:update', description: '更新角色' }, - { name: 'role:delete', description: '删除角色' }, - { name: 'permission:create', description: '创建权限' }, - { name: 'permission:read', description: '读取权限' }, - { name: 'permission:update', description: '更新权限' }, - { name: 'permission:delete', description: '删除权限' }, - { name: 'customer:create', description: '创建客户' }, - { name: 'customer:read', description: '读取客户' }, - { name: 'customer:update', description: '更新客户' }, - { name: 'customer:delete', description: '删除客户' }, - { name: 'supplier:create', description: '创建供应商' }, - { name: 'supplier:read', description: '读取供应商' }, - { name: 'supplier:update', description: '更新供应商' }, - { name: 'supplier:delete', description: '删除供应商' }, - { name: 'subcontractor:create', description: '创建分包商' }, - { name: 'subcontractor:read', description: '读取分包商' }, - { name: 'subcontractor:update', description: '更新分包商' }, - { name: 'subcontractor:delete', description: '删除分包商' }, - { name: 'project:create', description: '创建项目' }, - { name: 'project:read', description: '读取项目' }, - { name: 'project:update', description: '更新项目' }, - { name: 'project:delete', description: '删除项目' }, - { name: 'finance:read', description: '读取财务' }, - { name: 'finance:update', description: '更新财务' }, - { name: 'construction:read', description: '读取施工' }, - { name: 'construction:update', description: '更新施工' }, - { name: 'exchange:read', description: '读取汇率' }, - { name: 'exchange:update', description: '更新汇率' }, - { name: 'product:create', description: '创建商品' }, - { name: 'product:read', description: '读取商品' }, - { name: 'product:update', description: '更新商品' }, - { name: 'product:delete', description: '删除商品' }, - { name: 'quotation:create', description: '创建预算报价' }, - { name: 'quotation:read', description: '读取预算报价' }, - { name: 'quotation:update', description: '更新预算报价' }, - { name: 'quotation:delete', description: '删除预算报价' }, - ]; - - for (const permissionData of permissions) { - const existing = await this.permissionRepo.findOne({ where: { name: permissionData.name } }); - if (!existing) { - const permission = this.permissionRepo.create(permissionData); - await this.permissionRepo.save(permission); - } - } - } - - private async seedRoles() { - const allPermissions = await this.permissionRepo.find(); - - const roles = [ - { - name: '超级管理员', - description: '拥有所有权限', - permissions: allPermissions, - }, - { - name: '管理员', - description: '拥有大部分管理权限', - permissions: allPermissions.filter(p => !p.name.includes('role:') && !p.name.includes('permission:')), - }, - { - name: '财务人员', - description: '拥有财务管理权限', - permissions: allPermissions.filter(p => p.name.includes('finance:') || p.name.includes('exchange:')), - }, - { - name: '项目经理', - description: '拥有项目管理权限', - permissions: allPermissions.filter(p => p.name.includes('project:') || p.name.includes('customer:')), - }, - { - name: '采购人员', - description: '拥有采购管理权限', - permissions: allPermissions.filter(p => p.name.includes('supplier:') || p.name.includes('product:')), - }, - { - name: '施工人员', - description: '拥有施工管理权限', - permissions: allPermissions.filter(p => p.name.includes('construction:')), - }, - ]; - - for (const roleData of roles) { - const existing = await this.roleRepo.findOne({ where: { name: roleData.name } }); - if (!existing) { - const role = this.roleRepo.create(roleData); - await this.roleRepo.save(role); - } - } - } - - private async seedAdminUser() { - const adminRole = await this.roleRepo.findOne({ where: { name: '超级管理员' } }); - if (!adminRole) return; - - const existingAdmin = await this.userRepo.findOne({ where: { username: 'admin' } }); - if (!existingAdmin) { - const hashedPassword = await bcrypt.hash('admin123', 10); - const adminUser = this.userRepo.create({ - username: 'admin', - password: hashedPassword, - name: '系统管理员', - email: 'admin@example.com', - role: adminRole, - }); - await this.userRepo.save(adminUser); - } - } -} diff --git a/backend/src/main.ts b/backend/src/main.ts deleted file mode 100644 index 1c96483..0000000 --- a/backend/src/main.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app.module'; - -async function bootstrap() { - const app = await NestFactory.create(AppModule); - app.enableCors({ - origin: ['http://localhost:5173', 'http://localhost:4173', 'http://localhost:5174'], - methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], - allowedHeaders: ['Content-Type', 'Authorization'], - }); - await app.listen(process.env.PORT ?? 3000); -} -bootstrap(); diff --git a/backend/src/modules/auth/auth.controller.ts b/backend/src/modules/auth/auth.controller.ts deleted file mode 100644 index b81b453..0000000 --- a/backend/src/modules/auth/auth.controller.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Controller, Post, Body } from '@nestjs/common'; -import { AuthService } from './auth.service'; - -@Controller('api/v1/auth') -export class AuthController { - constructor(private readonly authService: AuthService) {} - - @Post('register') - async register( - @Body('username') username: string, - @Body('password') password: string, - @Body('name') name: string, - @Body('email') email: string, - @Body('roleId') roleId: string, - ) { - return this.authService.register(username, password, name, email, roleId); - } - - @Post('login') - async login( - @Body('username') username: string, - @Body('password') password: string, - ) { - return this.authService.login(username, password); - } -} diff --git a/backend/src/modules/auth/auth.module.ts b/backend/src/modules/auth/auth.module.ts deleted file mode 100644 index 43a075b..0000000 --- a/backend/src/modules/auth/auth.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Module } from '@nestjs/common'; -import { JwtModule } from '@nestjs/jwt'; -import { PassportModule } from '@nestjs/passport'; -import { AuthService } from './auth.service'; -import { AuthController } from './auth.controller'; -import { JwtStrategy } from './jwt.strategy'; -import { UsersModule } from '../users/users.module'; -import * as dotenv from 'dotenv'; - -dotenv.config(); - -@Module({ - imports: [ - UsersModule, - PassportModule, - JwtModule.register({ - secret: process.env.JWT_SECRET || 'your-secret-key-here', - signOptions: { - expiresIn: '3600s', - }, - }), - ], - providers: [AuthService, JwtStrategy], - controllers: [AuthController], - exports: [AuthService], -}) -export class AuthModule {} diff --git a/backend/src/modules/auth/auth.service.ts b/backend/src/modules/auth/auth.service.ts deleted file mode 100644 index 1abefa7..0000000 --- a/backend/src/modules/auth/auth.service.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { UsersService } from '../users/users.service'; -import * as bcrypt from 'bcryptjs'; - -@Injectable() -export class AuthService { - constructor( - private readonly usersService: UsersService, - private readonly jwtService: JwtService, - ) {} - - async register(username: string, password: string, name: string, email: string, roleId: string) { - // 检查用户名是否已存在 - const existingUser = await this.usersService.findByUsername(username); - if (existingUser) { - throw new Error('Username already exists'); - } - - // 检查邮箱是否已存在 - const existingEmail = await this.usersService.findByEmail(email); - if (existingEmail) { - throw new Error('Email already exists'); - } - - // 哈希密码 - const hashedPassword = await bcrypt.hash(password, 10); - - // 创建用户 - const user = await this.usersService.create({ - username, - password: hashedPassword, - name, - email, - roleId, - }); - - return user; - } - - async login(username: string, password: string) { - // 查找用户 - console.log('Login attempt for user:', username); - const user = await this.usersService.findByUsername(username); - console.log('Found user:', user); - if (!user) { - throw new UnauthorizedException('Invalid credentials'); - } - - // 验证密码 - const isPasswordValid = await bcrypt.compare(password, user.password); - console.log('Password valid:', isPasswordValid); - if (!isPasswordValid) { - throw new UnauthorizedException('Invalid credentials'); - } - - // 生成JWT token - const payload = { sub: user.id, username: user.username, role: user.role.name }; - const token = this.jwtService.sign(payload); - - const response = { - access_token: token, - user: { - id: user.id, - username: user.username, - name: user.name, - email: user.email, - role: user.role.name, - }, - }; - console.log('Login response:', response); - return response; - } -} diff --git a/backend/src/modules/auth/jwt.strategy.ts b/backend/src/modules/auth/jwt.strategy.ts deleted file mode 100644 index 4bf4189..0000000 --- a/backend/src/modules/auth/jwt.strategy.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { ExtractJwt, Strategy } from 'passport-jwt'; -import { UsersService } from '../users/users.service'; -import * as dotenv from 'dotenv'; - -dotenv.config(); - -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private readonly usersService: UsersService) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: process.env.JWT_SECRET || 'your-secret-key-here', - ignoreExpiration: false, - }); - } - - async validate(payload: any) { - const user = await this.usersService.findById(payload.sub); - if (!user) { - throw new UnauthorizedException(); - } - return user; - } -} diff --git a/backend/src/modules/contracts/contract.entity.ts b/backend/src/modules/contracts/contract.entity.ts deleted file mode 100644 index 7f7310e..0000000 --- a/backend/src/modules/contracts/contract.entity.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { Project } from '../projects/project.entity'; -import { PaymentTerm } from './payment-term.entity'; -import { Purchase } from '../purchases/purchase.entity'; -import { Subcontract } from '../subcontracts/subcontract.entity'; - -@Entity('contracts') -export class Contract { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: true }) - project_id: string; - - @ManyToOne(() => Project, project => project.contracts) - project: Project; - - @Column({ type: 'varchar', length: 50, nullable: false }) - type: string; - - @Column({ type: 'varchar', length: 100, unique: true, nullable: false }) - contract_number: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - contract_amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'date', nullable: true }) - start_date: Date; - - @Column({ type: 'date', nullable: true }) - end_date: Date; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => PaymentTerm, paymentTerm => paymentTerm.contract) - payment_terms: PaymentTerm[]; - - @OneToMany(() => Purchase, purchase => purchase.contract) - purchases: Purchase[]; - - @OneToMany(() => Subcontract, subcontract => subcontract.contract) - subcontracts: Subcontract[]; -} diff --git a/backend/src/modules/contracts/contracts.module.ts b/backend/src/modules/contracts/contracts.module.ts deleted file mode 100644 index c22c8b4..0000000 --- a/backend/src/modules/contracts/contracts.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Contract } from './contract.entity'; -import { PaymentTerm } from './payment-term.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([Contract, PaymentTerm])], - exports: [TypeOrmModule], -}) -export class ContractsModule {} \ No newline at end of file diff --git a/backend/src/modules/contracts/payment-term.entity.ts b/backend/src/modules/contracts/payment-term.entity.ts deleted file mode 100644 index 00c7e0d..0000000 --- a/backend/src/modules/contracts/payment-term.entity.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { Contract } from './contract.entity'; -import { FinancialRecord } from '../finance/financial-record.entity'; - -@Entity('payment_terms') -export class PaymentTerm { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: true }) - contract_id: string; - - @ManyToOne(() => Contract, contract => contract.payment_terms) - contract: Contract; - - @Column({ type: 'integer', nullable: false }) - sequence: number; - - @Column({ type: 'text', nullable: false }) - description: string; - - @Column({ type: 'decimal', precision: 5, scale: 2, nullable: false }) - percentage: number; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'date', nullable: true }) - due_date: Date; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => FinancialRecord, financialRecord => financialRecord.payment_term) - financial_records: FinancialRecord[]; -} diff --git a/backend/src/modules/customers/customer.entity.ts b/backend/src/modules/customers/customer.entity.ts deleted file mode 100644 index cee3e5e..0000000 --- a/backend/src/modules/customers/customer.entity.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm'; -import { Project } from '../projects/project.entity'; -import { Quotation } from '../quotations/quotation.entity'; - -@Entity('customers') -export class Customer { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - name: string; - - @Column({ type: 'varchar', length: 255, nullable: true }) - contact_person: string; - - @Column({ type: 'varchar', length: 50, nullable: true }) - phone: string; - - @Column({ type: 'varchar', length: 255, nullable: true }) - email: string; - - @Column({ type: 'text', nullable: true }) - address: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => Project, project => project.customer) - projects: Project[]; - - @OneToMany(() => Quotation, quotation => quotation.customer) - quotations: Quotation[]; -} diff --git a/backend/src/modules/customers/customers.controller.ts b/backend/src/modules/customers/customers.controller.ts deleted file mode 100644 index 24556cf..0000000 --- a/backend/src/modules/customers/customers.controller.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; -import { CustomersService } from './customers.service'; -import { Customer } from './customer.entity'; - -@Controller('api/v1/customers') -export class CustomersController { - constructor(private readonly customersService: CustomersService) {} - - @Post() - create(@Body() customerData: Partial) { - return this.customersService.create(customerData); - } - - @Get() - findAll() { - return this.customersService.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.customersService.findOne(id); - } - - @Get(':id/projects') - findWithProjects(@Param('id') id: string) { - return this.customersService.findWithProjects(id); - } - - @Get(':id/quotations') - findWithQuotations(@Param('id') id: string) { - return this.customersService.findWithQuotations(id); - } - - @Patch(':id') - update(@Param('id') id: string, @Body() customerData: Partial) { - return this.customersService.update(id, customerData); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.customersService.remove(id); - } -} diff --git a/backend/src/modules/customers/customers.module.ts b/backend/src/modules/customers/customers.module.ts deleted file mode 100644 index 2376bcf..0000000 --- a/backend/src/modules/customers/customers.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Customer } from './customer.entity'; -import { CustomersController } from './customers.controller'; -import { CustomersService } from './customers.service'; - -@Module({ - imports: [TypeOrmModule.forFeature([Customer])], - controllers: [CustomersController], - providers: [CustomersService], - exports: [CustomersService], -}) -export class CustomersModule {} \ No newline at end of file diff --git a/backend/src/modules/customers/customers.service.ts b/backend/src/modules/customers/customers.service.ts deleted file mode 100644 index 3fa9ef6..0000000 --- a/backend/src/modules/customers/customers.service.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Customer } from './customer.entity'; - -@Injectable() -export class CustomersService { - constructor( - @InjectRepository(Customer) - private customersRepository: Repository, - ) {} - - async create(customerData: Partial): Promise { - const customer = this.customersRepository.create(customerData); - return this.customersRepository.save(customer); - } - - async findAll(): Promise { - return this.customersRepository.find(); - } - - async findOne(id: string): Promise { - const customer = await this.customersRepository.findOne({ where: { id } }); - if (!customer) { - throw new NotFoundException(`Customer with id ${id} not found`); - } - return customer; - } - - async update(id: string, customerData: Partial): Promise { - const customer = await this.findOne(id); - Object.assign(customer, customerData); - return this.customersRepository.save(customer); - } - - async remove(id: string): Promise { - const result = await this.customersRepository.delete(id); - if (result.affected === 0) { - throw new NotFoundException(`Customer with id ${id} not found`); - } - } - - async findWithProjects(id: string): Promise { - const customer = await this.customersRepository.findOne({ - where: { id }, - relations: ['projects'], - }); - if (!customer) { - throw new NotFoundException(`Customer with id ${id} not found`); - } - return customer; - } - - async findWithQuotations(id: string): Promise { - const customer = await this.customersRepository.findOne({ - where: { id }, - relations: ['quotations'], - }); - if (!customer) { - throw new NotFoundException(`Customer with id ${id} not found`); - } - return customer; - } -} diff --git a/backend/src/modules/finance/advance-payment.entity.ts b/backend/src/modules/finance/advance-payment.entity.ts deleted file mode 100644 index ee28daa..0000000 --- a/backend/src/modules/finance/advance-payment.entity.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm'; -import { User } from '../users/user.entity'; -import { Project } from '../projects/project.entity'; - -@Entity('advance_payments') -export class AdvancePayment { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: false }) - user_id: string; - - @ManyToOne(() => User) - user: User; - - @Column({ type: 'varchar', length: 20, nullable: false }) - expense_type: string; // company or project - - @Column({ type: 'uuid', nullable: true }) - project_id: string; - - @ManyToOne(() => Project, project => project.advance_payments) - project: Project; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'varchar', length: 255, nullable: false }) - purpose: string; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @Column({ type: 'uuid', nullable: true }) - approver_id: string; - - @ManyToOne(() => User) - approver: User; - - @Column({ type: 'text', nullable: true }) - description: string; - - @Column({ type: 'datetime', nullable: true }) - submitted_at: Date; - - @Column({ type: 'datetime', nullable: true }) - approved_at: Date; - - @Column({ type: 'datetime', nullable: true }) - rejected_at: Date; - - @Column({ type: 'decimal', precision: 18, scale: 2, default: 0 }) - used_amount: number; - - @Column({ type: 'decimal', precision: 18, scale: 2, default: 0 }) - remaining_amount: number; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/finance/bank-account.entity.ts b/backend/src/modules/finance/bank-account.entity.ts deleted file mode 100644 index 77a396a..0000000 --- a/backend/src/modules/finance/bank-account.entity.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm'; -import { FinancialTransaction } from './financial-transaction.entity'; - -@Entity('bank_accounts') -export class BankAccount { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - name: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - bank_name: string; - - @Column({ type: 'varchar', length: 100, unique: true, nullable: false }) - account_number: string; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, default: 0 }) - balance: number; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => FinancialTransaction, financialTransaction => financialTransaction.bank_account) - financial_transactions: FinancialTransaction[]; -} diff --git a/backend/src/modules/finance/currency-rate.entity.ts b/backend/src/modules/finance/currency-rate.entity.ts deleted file mode 100644 index fedf43d..0000000 --- a/backend/src/modules/finance/currency-rate.entity.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; - -@Entity() -export class CurrencyRate { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 10, nullable: false }) // 源币种代码,如 CNY, LAK, THB, USD - from_currency: string; - - @Column({ type: 'varchar', length: 10, nullable: false }) // 目标币种代码 - to_currency: string; - - @Column({ type: 'decimal', precision: 12, scale: 6, nullable: false }) // 汇率值 - rate: number; - - @Column({ type: 'boolean', default: true }) // 是否启用 - is_active: boolean; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/finance/expense-claim-detail.entity.ts b/backend/src/modules/finance/expense-claim-detail.entity.ts deleted file mode 100644 index a3660b9..0000000 --- a/backend/src/modules/finance/expense-claim-detail.entity.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; -import { ExpenseClaim } from './expense-claim.entity'; - -@Entity('expense_claim_details') -export class ExpenseClaimDetail { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: false }) - expense_claim_id: string; - - @ManyToOne(() => ExpenseClaim, expense_claim => expense_claim.details) - expense_claim: ExpenseClaim; - - @Column({ type: 'varchar', length: 50, nullable: false }) - category: string; // 支出分类 - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'varchar', length: 255, nullable: true }) - description: string; - - @Column({ type: 'date', nullable: false }) - expense_date: Date; - - @Column({ type: 'text', nullable: true }) - attachments: string; // JSON格式存储附件信息 -} diff --git a/backend/src/modules/finance/expense-claim.entity.ts b/backend/src/modules/finance/expense-claim.entity.ts deleted file mode 100644 index 59e2bd4..0000000 --- a/backend/src/modules/finance/expense-claim.entity.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { User } from '../users/user.entity'; -import { Project } from '../projects/project.entity'; -import { ExpenseClaimDetail } from './expense-claim-detail.entity'; - -@Entity('expense_claims') -export class ExpenseClaim { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: false }) - user_id: string; - - @ManyToOne(() => User) - user: User; - - @Column({ type: 'varchar', length: 20, nullable: false }) - expense_type: string; // company or project - - @Column({ type: 'uuid', nullable: true }) - project_id: string; - - @ManyToOne(() => Project, project => project.expense_claims) - project: Project; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'varchar', length: 255, nullable: false }) - purpose: string; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @Column({ type: 'uuid', nullable: true }) - approver_id: string; - - @ManyToOne(() => User) - approver: User; - - @Column({ type: 'text', nullable: true }) - description: string; - - @Column({ type: 'datetime', nullable: true }) - submitted_at: Date; - - @Column({ type: 'datetime', nullable: true }) - approved_at: Date; - - @Column({ type: 'datetime', nullable: true }) - rejected_at: Date; - - @OneToMany(() => ExpenseClaimDetail, detail => detail.expense_claim, { cascade: true }) - details: ExpenseClaimDetail[]; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/finance/finance-approval.entity.ts b/backend/src/modules/finance/finance-approval.entity.ts deleted file mode 100644 index 2190256..0000000 --- a/backend/src/modules/finance/finance-approval.entity.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm'; -import { User } from '../users/user.entity'; -import { ExpenseClaim } from './expense-claim.entity'; -import { AdvancePayment } from './advance-payment.entity'; -import { FinancialRecord } from './financial-record.entity'; - -@Entity('finance_approvals') -export class FinanceApproval { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 50, nullable: false }) - approval_type: string; - - @Column({ type: 'uuid', nullable: true }) - expense_claim_id: string; - - @ManyToOne(() => ExpenseClaim) - expense_claim: ExpenseClaim; - - @Column({ type: 'uuid', nullable: true }) - advance_payment_id: string; - - @ManyToOne(() => AdvancePayment) - advance_payment: AdvancePayment; - - @Column({ type: 'uuid', nullable: true }) - financial_record_id: string; - - @ManyToOne(() => FinancialRecord) - financial_record: FinancialRecord; - - @Column({ type: 'uuid', nullable: false }) - requester_id: string; - - @ManyToOne(() => User) - requester: User; - - @Column({ type: 'uuid', nullable: true }) - approver_id: string; - - @ManyToOne(() => User) - approver: User; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @Column({ type: 'text', nullable: true }) - request_notes: string; - - @Column({ type: 'text', nullable: true }) - approval_notes: string; - - @Column({ type: 'datetime', nullable: true }) - submitted_at: Date; - - @Column({ type: 'datetime', nullable: true }) - approved_at: Date; - - @Column({ type: 'datetime', nullable: true }) - rejected_at: Date; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/finance/finance.controller.ts b/backend/src/modules/finance/finance.controller.ts deleted file mode 100644 index 2b897d7..0000000 --- a/backend/src/modules/finance/finance.controller.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards, Request } from '@nestjs/common'; -import { FinanceService } from './finance.service'; -import { AuthGuard } from '@nestjs/passport'; - -@Controller('api/v1/finance') -export class FinanceController { - constructor(private financeService: FinanceService) {} - - // 报销申请相关接口 - @UseGuards(AuthGuard('jwt')) - @Post('expense-claims') - async createExpenseClaim(@Body() data: any, @Request() req) { - data.user_id = req.user.id; - return this.financeService.createExpenseClaim(data); - } - - @UseGuards(AuthGuard('jwt')) - @Get('expense-claims') - async getExpenseClaims(@Query() filter: any, @Request() req) { - // 普通用户只能查看自己的报销申请 - if (req.user.role.name !== 'admin') { - filter.user_id = req.user.id; - } - return this.financeService.getExpenseClaims(filter); - } - - @UseGuards(AuthGuard('jwt')) - @Get('expense-claims/:id') - async getExpenseClaimById(@Param('id') id: string, @Request() req) { - const expenseClaim = await this.financeService.getExpenseClaimById(id); - // 普通用户只能查看自己的报销申请 - if (req.user.role.name !== 'admin' && expenseClaim.user_id !== req.user.id) { - throw new Error('Access denied'); - } - return expenseClaim; - } - - @UseGuards(AuthGuard('jwt')) - @Put('expense-claims/:id/approve') - async approveExpenseClaim(@Param('id') id: string, @Body() data: any, @Request() req) { - // 只有管理员可以审批 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.approveExpenseClaim(id, req.user.id, data.notes); - } - - @UseGuards(AuthGuard('jwt')) - @Put('expense-claims/:id/reject') - async rejectExpenseClaim(@Param('id') id: string, @Body() data: any, @Request() req) { - // 只有管理员可以审批 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.rejectExpenseClaim(id, req.user.id, data.notes); - } - - // 预支款相关接口 - @UseGuards(AuthGuard('jwt')) - @Post('advance-payments') - async createAdvancePayment(@Body() data: any, @Request() req) { - data.user_id = req.user.id; - return this.financeService.createAdvancePayment(data); - } - - @UseGuards(AuthGuard('jwt')) - @Get('advance-payments') - async getAdvancePayments(@Query() filter: any, @Request() req) { - // 普通用户只能查看自己的预支款 - if (req.user.role.name !== 'admin') { - filter.user_id = req.user.id; - } - return this.financeService.getAdvancePayments(filter); - } - - @UseGuards(AuthGuard('jwt')) - @Get('advance-payments/:id') - async getAdvancePaymentById(@Param('id') id: string, @Request() req) { - const advancePayment = await this.financeService.getAdvancePaymentById(id); - // 普通用户只能查看自己的预支款 - if (req.user.role.name !== 'admin' && advancePayment.user_id !== req.user.id) { - throw new Error('Access denied'); - } - return advancePayment; - } - - @UseGuards(AuthGuard('jwt')) - @Put('advance-payments/:id/approve') - async approveAdvancePayment(@Param('id') id: string, @Body() data: any, @Request() req) { - // 只有管理员可以审批 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.approveAdvancePayment(id, req.user.id, data.notes); - } - - @UseGuards(AuthGuard('jwt')) - @Put('advance-payments/:id/reject') - async rejectAdvancePayment(@Param('id') id: string, @Body() data: any, @Request() req) { - // 只有管理员可以审批 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.rejectAdvancePayment(id, req.user.id, data.notes); - } - - @UseGuards(AuthGuard('jwt')) - @Put('advance-payments/:id/usage') - async updateAdvancePaymentUsage(@Param('id') id: string, @Body() data: any, @Request() req) { - const advancePayment = await this.financeService.getAdvancePaymentById(id); - // 普通用户只能更新自己的预支款使用情况 - if (req.user.role.name !== 'admin' && advancePayment.user_id !== req.user.id) { - throw new Error('Access denied'); - } - return this.financeService.updateAdvancePaymentUsage(id, data.used_amount); - } - - // 凭证相关接口 - @UseGuards(AuthGuard('jwt')) - @Post('vouchers') - async uploadVoucher(@Body() data: any, @Request() req) { - data.uploaded_by = req.user.id; - return this.financeService.uploadVoucher(data); - } - - @UseGuards(AuthGuard('jwt')) - @Get('vouchers') - async getVouchers(@Query() filter: any) { - return this.financeService.getVouchers(filter); - } - - // 财务审批相关接口 - @UseGuards(AuthGuard('jwt')) - @Post('approvals') - async createFinanceApproval(@Body() data: any, @Request() req) { - data.requester_id = req.user.id; - return this.financeService.createFinanceApproval(data); - } - - @UseGuards(AuthGuard('jwt')) - @Get('approvals') - async getFinanceApprovals(@Query() filter: any, @Request() req) { - // 普通用户只能查看自己的审批请求 - if (req.user.role.name !== 'admin') { - filter.requester_id = req.user.id; - } - return this.financeService.getFinanceApprovals(filter); - } - - @UseGuards(AuthGuard('jwt')) - @Put('approvals/:id/approve') - async approveFinanceApproval(@Param('id') id: string, @Body() data: any, @Request() req) { - // 只有管理员可以审批 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.approveFinanceApproval(id, req.user.id, data.notes); - } - - @UseGuards(AuthGuard('jwt')) - @Put('approvals/:id/reject') - async rejectFinanceApproval(@Param('id') id: string, @Body() data: any, @Request() req) { - // 只有管理员可以审批 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.rejectFinanceApproval(id, req.user.id, data.notes); - } - - // 公司账目管理接口 - @UseGuards(AuthGuard('jwt')) - @Get('company-records') - async getCompanyFinancialRecords(@Query() filter: any, @Request() req) { - // 只有管理员可以查看公司账目 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.getCompanyFinancialRecords(filter); - } - - // 项目财务记录接口 - @UseGuards(AuthGuard('jwt')) - @Get('project-records/:projectId') - async getProjectFinancialRecords(@Param('projectId') projectId: string) { - return this.financeService.getProjectFinancialRecords(projectId); - } - - // 员工个人记账接口 - @UseGuards(AuthGuard('jwt')) - @Get('employee-records') - async getEmployeeFinancialRecords(@Request() req) { - return this.financeService.getEmployeeFinancialRecords(req.user.id); - } - - // 汇率管理相关接口 - @UseGuards(AuthGuard('jwt')) - @Get('currency-rates') - async getCurrencyRates(@Request() req) { - // 只有管理员可以查看汇率设置 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.getCurrencyRates(); - } - - @UseGuards(AuthGuard('jwt')) - @Post('currency-rates') - async createOrUpdateCurrencyRate(@Body() data: any, @Request() req) { - // 只有管理员可以设置汇率 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.createOrUpdateCurrencyRate(data); - } - - @UseGuards(AuthGuard('jwt')) - @Put('currency-rates/:id/status') - async updateCurrencyRateStatus(@Param('id') id: string, @Body() data: any, @Request() req) { - // 只有管理员可以更新汇率状态 - if (req.user.role.name !== 'admin') { - throw new Error('Access denied'); - } - return this.financeService.updateCurrencyRateStatus(id, data.is_active); - } - - @UseGuards(AuthGuard('jwt')) - @Get('currencies') - async getSupportedCurrencies() { - return this.financeService.getSupportedCurrencies(); - } - - @UseGuards(AuthGuard('jwt')) - @Post('convert-currency') - async convertCurrency(@Body() data: any) { - const { amount, from_currency, to_currency } = data; - return this.financeService.convertCurrency(amount, from_currency, to_currency); - } -} diff --git a/backend/src/modules/finance/finance.module.ts b/backend/src/modules/finance/finance.module.ts deleted file mode 100644 index f9ae766..0000000 --- a/backend/src/modules/finance/finance.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { FinancialRecord } from './financial-record.entity'; -import { FinancialTransaction } from './financial-transaction.entity'; -import { BankAccount } from './bank-account.entity'; -import { ExpenseClaim } from './expense-claim.entity'; -import { AdvancePayment } from './advance-payment.entity'; -import { Voucher } from './voucher.entity'; -import { FinanceApproval } from './finance-approval.entity'; -import { CurrencyRate } from './currency-rate.entity'; -import { FinanceController } from './finance.controller'; -import { FinanceService } from './finance.service'; - -@Module({ - imports: [TypeOrmModule.forFeature([ - FinancialRecord, - FinancialTransaction, - BankAccount, - ExpenseClaim, - AdvancePayment, - Voucher, - FinanceApproval, - CurrencyRate - ])], - controllers: [FinanceController], - providers: [FinanceService], - exports: [TypeOrmModule, FinanceService], -}) -export class FinanceModule {} \ No newline at end of file diff --git a/backend/src/modules/finance/finance.service.spec.ts b/backend/src/modules/finance/finance.service.spec.ts deleted file mode 100644 index 8fa57a2..0000000 --- a/backend/src/modules/finance/finance.service.spec.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { FinanceService } from './finance.service'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { ExpenseClaim } from './expense-claim.entity'; -import { AdvancePayment } from './advance-payment.entity'; -import { Voucher } from './voucher.entity'; -import { FinanceApproval } from './finance-approval.entity'; -import { FinancialRecord } from './financial-record.entity'; -import { BankAccount } from './bank-account.entity'; -import { FinancialTransaction } from './financial-transaction.entity'; -import { CurrencyRate } from './currency-rate.entity'; - -describe('FinanceService', () => { - let financeService: FinanceService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - FinanceService, - { - provide: getRepositoryToken(FinancialRecord), - useValue: { - create: jest.fn(), - save: jest.fn(), - findOne: jest.fn(), - find: jest.fn(), - createQueryBuilder: jest.fn().mockReturnValue({ - leftJoinAndSelect: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - getMany: jest.fn(), - }), - }, - }, - { - provide: getRepositoryToken(ExpenseClaim), - useValue: { - create: jest.fn().mockReturnValue({ id: '1' }), - save: jest.fn().mockReturnValue({ id: '1', status: 'pending' }), - findOne: jest.fn().mockReturnValue({ id: '1', status: 'pending' }), - find: jest.fn(), - createQueryBuilder: jest.fn().mockReturnValue({ - leftJoinAndSelect: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - getMany: jest.fn(), - }), - }, - }, - { - provide: getRepositoryToken(AdvancePayment), - useValue: { - create: jest.fn().mockReturnValue({ id: '1' }), - save: jest.fn().mockReturnValue({ id: '1', status: 'pending' }), - findOne: jest.fn().mockReturnValue({ id: '1', status: 'pending' }), - find: jest.fn(), - createQueryBuilder: jest.fn().mockReturnValue({ - leftJoinAndSelect: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - getMany: jest.fn(), - }), - }, - }, - { - provide: getRepositoryToken(Voucher), - useValue: { - create: jest.fn(), - save: jest.fn(), - find: jest.fn(), - createQueryBuilder: jest.fn().mockReturnValue({ - leftJoinAndSelect: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - getMany: jest.fn(), - }), - }, - }, - { - provide: getRepositoryToken(FinanceApproval), - useValue: { - create: jest.fn(), - save: jest.fn(), - findOne: jest.fn(), - find: jest.fn(), - createQueryBuilder: jest.fn().mockReturnValue({ - leftJoinAndSelect: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - getMany: jest.fn(), - }), - }, - }, - { - provide: getRepositoryToken(BankAccount), - useValue: { - create: jest.fn(), - save: jest.fn(), - findOne: jest.fn(), - find: jest.fn(), - }, - }, - { - provide: getRepositoryToken(FinancialTransaction), - useValue: { - create: jest.fn(), - save: jest.fn(), - findOne: jest.fn(), - find: jest.fn(), - }, - }, - { - provide: getRepositoryToken(CurrencyRate), - useValue: { - create: jest.fn(), - save: jest.fn(), - findOne: jest.fn(), - find: jest.fn(), - }, - }, - ], - }).compile(); - - financeService = module.get(FinanceService); - }); - - describe('支出类型和项目选择', () => { - it('应该支持公司支出和项目支出类型', async () => { - // 测试创建报销申请时支持支出类型 - const expenseClaimData = { - user_id: '1', - expense_type: 'company', // 公司支出 - amount: 1000, - currency: 'CNY', - rmb_equivalent: 1000, - purpose: '测试报销', - }; - - const result = await financeService.createExpenseClaim(expenseClaimData); - expect(result).toBeDefined(); - }); - - it('选择项目支出时必须提供项目ID', async () => { - // 测试创建报销申请时选择项目支出 - const expenseClaimData = { - user_id: '1', - expense_type: 'project', // 项目支出 - project_id: '1', - amount: 1000, - currency: 'CNY', - rmb_equivalent: 1000, - purpose: '测试项目报销', - }; - - const result = await financeService.createExpenseClaim(expenseClaimData); - expect(result).toBeDefined(); - }); - }); - - describe('支出分类', () => { - it('项目支出应包含正确的支出分类选项', () => { - // 测试项目支出的分类选项 - const projectExpenseCategories = [ - '住宿', '餐饮', '加油', '零散材料', '客户关系', '分包关系', 'EDL关系', '额外施工', '其他' - ]; - expect(projectExpenseCategories.length).toBeGreaterThan(0); - }); - - it('公司支出应包含正确的支出分类选项', () => { - // 测试公司支出的分类选项 - const companyExpenseCategories = [ - '通用运营(房租 / 耗材)', '交通通勤', '业扩营销', '电力系统关系', '员工福利', '快递物流', '其他' - ]; - expect(companyExpenseCategories.length).toBeGreaterThan(0); - }); - }); - - describe('币种统一', () => { - it('应该支持统一币种', async () => { - // 测试币种转换功能 - const amount = 100; - const fromCurrency = 'USD'; - const toCurrency = 'CNY'; - - // 模拟汇率 - const mockRate = { - from_currency: 'USD', - to_currency: 'CNY', - rate: 7.0, - is_active: true, - }; - - // 测试币种转换 - try { - const result = await financeService.convertCurrency(amount, fromCurrency, toCurrency); - expect(result).toBeDefined(); - } catch (error) { - // 汇率不存在时会抛出异常,这是正常的 - expect(error).toBeDefined(); - } - }); - }); - - describe('审批流程', () => { - it('提交后状态应变为待审批', async () => { - // 测试创建报销申请后状态 - const expenseClaimData = { - user_id: '1', - expense_type: 'company', - amount: 1000, - currency: 'CNY', - rmb_equivalent: 1000, - purpose: '测试报销', - }; - - const result = await financeService.createExpenseClaim(expenseClaimData); - expect(result).toBeDefined(); - }); - - it('审批退回时状态应变为待编辑', async () => { - // 测试拒绝报销申请 - const approverId = '2'; - - try { - const result = await financeService.rejectExpenseClaim('1', approverId, '拒绝理由'); - expect(result).toBeDefined(); - } catch (error) { - // 记录不存在时会抛出异常,这是正常的 - expect(error).toBeDefined(); - } - }); - }); -}); \ No newline at end of file diff --git a/backend/src/modules/finance/finance.service.ts b/backend/src/modules/finance/finance.service.ts deleted file mode 100644 index 67bead6..0000000 --- a/backend/src/modules/finance/finance.service.ts +++ /dev/null @@ -1,377 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { FinancialRecord } from './financial-record.entity'; -import { ExpenseClaim } from './expense-claim.entity'; -import { AdvancePayment } from './advance-payment.entity'; -import { Voucher } from './voucher.entity'; -import { FinanceApproval } from './finance-approval.entity'; -import { BankAccount } from './bank-account.entity'; -import { FinancialTransaction } from './financial-transaction.entity'; -import { CurrencyRate } from './currency-rate.entity'; - -@Injectable() -export class FinanceService { - constructor( - @InjectRepository(FinancialRecord) private financialRecordRepo: Repository, - @InjectRepository(ExpenseClaim) private expenseClaimRepo: Repository, - @InjectRepository(AdvancePayment) private advancePaymentRepo: Repository, - @InjectRepository(Voucher) private voucherRepo: Repository, - @InjectRepository(FinanceApproval) private financeApprovalRepo: Repository, - @InjectRepository(BankAccount) private bankAccountRepo: Repository, - @InjectRepository(FinancialTransaction) private financialTransactionRepo: Repository, - @InjectRepository(CurrencyRate) private currencyRateRepo: Repository - ) {} - - // 报销申请相关 - async createExpenseClaim(data: any): Promise { - // 验证支出类型 - if (!data.expense_type || !['company', 'project'].includes(data.expense_type)) { - throw new Error('Invalid expense type'); - } - - // 项目支出必须提供项目ID - if (data.expense_type === 'project' && !data.project_id) { - throw new Error('Project ID is required for project expense'); - } - - const expenseClaim = this.expenseClaimRepo.create({ - ...data, - status: 'pending', - submitted_at: new Date() - }); - const result = await this.expenseClaimRepo.save(expenseClaim); - return result as unknown as ExpenseClaim; - } - - async getExpenseClaims(filter?: any): Promise { - const query = this.expenseClaimRepo.createQueryBuilder('ec') - .leftJoinAndSelect('ec.user', 'user') - .leftJoinAndSelect('ec.approver', 'approver') - .leftJoinAndSelect('ec.project', 'project'); - - if (filter) { - if (filter.user_id) query.where('ec.user_id = :user_id', { user_id: filter.user_id }); - if (filter.project_id) query.where('ec.project_id = :project_id', { project_id: filter.project_id }); - if (filter.status) query.where('ec.status = :status', { status: filter.status }); - } - - return query.getMany(); - } - - async getExpenseClaimById(id: string): Promise { - const expenseClaim = await this.expenseClaimRepo.findOne({ - where: { id }, - relations: ['user', 'approver', 'project', 'vouchers'] - }); - if (!expenseClaim) throw new NotFoundException('Expense claim not found'); - return expenseClaim; - } - - async approveExpenseClaim(id: string, approverId: string, notes?: string): Promise { - const expenseClaim = await this.getExpenseClaimById(id); - expenseClaim.status = 'approved'; - expenseClaim.approver_id = approverId; - expenseClaim.approved_at = new Date(); - return this.expenseClaimRepo.save(expenseClaim); - } - - async rejectExpenseClaim(id: string, approverId: string, notes?: string): Promise { - const expenseClaim = await this.getExpenseClaimById(id); - expenseClaim.status = 'rejected'; - expenseClaim.approver_id = approverId; - expenseClaim.rejected_at = new Date(); - return this.expenseClaimRepo.save(expenseClaim); - } - - // 预支款相关 - async createAdvancePayment(data: any): Promise { - // 验证支出类型 - if (!data.expense_type || !['company', 'project'].includes(data.expense_type)) { - throw new Error('Invalid expense type'); - } - - // 项目支出必须提供项目ID - if (data.expense_type === 'project' && !data.project_id) { - throw new Error('Project ID is required for project expense'); - } - - const advancePayment = this.advancePaymentRepo.create({ - ...data, - status: 'pending', - submitted_at: new Date(), - used_amount: 0, - remaining_amount: data.amount - }); - const result = await this.advancePaymentRepo.save(advancePayment); - return result as unknown as AdvancePayment; - } - - async getAdvancePayments(filter?: any): Promise { - const query = this.advancePaymentRepo.createQueryBuilder('ap') - .leftJoinAndSelect('ap.user', 'user') - .leftJoinAndSelect('ap.approver', 'approver') - .leftJoinAndSelect('ap.project', 'project'); - - if (filter) { - if (filter.user_id) query.where('ap.user_id = :user_id', { user_id: filter.user_id }); - if (filter.project_id) query.where('ap.project_id = :project_id', { project_id: filter.project_id }); - if (filter.status) query.where('ap.status = :status', { status: filter.status }); - } - - return query.getMany(); - } - - async getAdvancePaymentById(id: string): Promise { - const advancePayment = await this.advancePaymentRepo.findOne({ - where: { id }, - relations: ['user', 'approver', 'project', 'vouchers'] - }); - if (!advancePayment) throw new NotFoundException('Advance payment not found'); - return advancePayment; - } - - async approveAdvancePayment(id: string, approverId: string, notes?: string): Promise { - const advancePayment = await this.getAdvancePaymentById(id); - advancePayment.status = 'approved'; - advancePayment.approver_id = approverId; - advancePayment.approved_at = new Date(); - return this.advancePaymentRepo.save(advancePayment); - } - - async rejectAdvancePayment(id: string, approverId: string, notes?: string): Promise { - const advancePayment = await this.getAdvancePaymentById(id); - advancePayment.status = 'rejected'; - advancePayment.approver_id = approverId; - advancePayment.rejected_at = new Date(); - return this.advancePaymentRepo.save(advancePayment); - } - - async updateAdvancePaymentUsage(id: string, usedAmount: number): Promise { - const advancePayment = await this.getAdvancePaymentById(id); - advancePayment.used_amount = usedAmount; - advancePayment.remaining_amount = advancePayment.amount - usedAmount; - return this.advancePaymentRepo.save(advancePayment); - } - - // 凭证相关 - async uploadVoucher(data: any): Promise { - const voucher = this.voucherRepo.create(data); - const result = await this.voucherRepo.save(voucher); - return result as unknown as Voucher; - } - - async getVouchers(filter?: any): Promise { - const query = this.voucherRepo.createQueryBuilder('v') - .leftJoinAndSelect('v.uploader', 'uploader'); - - if (filter) { - if (filter.expense_claim_id) query.where('v.expense_claim_id = :expense_claim_id', { expense_claim_id: filter.expense_claim_id }); - if (filter.advance_payment_id) query.where('v.advance_payment_id = :advance_payment_id', { advance_payment_id: filter.advance_payment_id }); - if (filter.financial_record_id) query.where('v.financial_record_id = :financial_record_id', { financial_record_id: filter.financial_record_id }); - } - - return query.getMany(); - } - - // 财务审批相关 - async createFinanceApproval(data: any): Promise { - const approval = this.financeApprovalRepo.create({ - ...data, - status: 'pending', - submitted_at: new Date() - }); - const result = await this.financeApprovalRepo.save(approval); - return result as unknown as FinanceApproval; - } - - async getFinanceApprovals(filter?: any): Promise { - const query = this.financeApprovalRepo.createQueryBuilder('fa') - .leftJoinAndSelect('fa.requester', 'requester') - .leftJoinAndSelect('fa.approver', 'approver'); - - if (filter) { - if (filter.requester_id) query.where('fa.requester_id = :requester_id', { requester_id: filter.requester_id }); - if (filter.approver_id) query.where('fa.approver_id = :approver_id', { approver_id: filter.approver_id }); - if (filter.status) query.where('fa.status = :status', { status: filter.status }); - if (filter.approval_type) query.where('fa.approval_type = :approval_type', { approval_type: filter.approval_type }); - } - - return query.getMany(); - } - - async approveFinanceApproval(id: string, approverId: string, notes?: string): Promise { - const approval = await this.financeApprovalRepo.findOne({ - where: { id }, - relations: ['expense_claim', 'advance_payment'] - }); - if (!approval) throw new NotFoundException('Finance approval not found'); - - approval.status = 'approved'; - approval.approver_id = approverId; - approval.approval_notes = notes || ''; - approval.approved_at = new Date(); - - // 同时更新关联的报销申请或预支款状态 - if (approval.expense_claim) { - approval.expense_claim.status = 'approved'; - approval.expense_claim.approver_id = approverId; - approval.expense_claim.approved_at = new Date(); - await this.expenseClaimRepo.save(approval.expense_claim); - } - - if (approval.advance_payment) { - approval.advance_payment.status = 'approved'; - approval.advance_payment.approver_id = approverId; - approval.advance_payment.approved_at = new Date(); - await this.advancePaymentRepo.save(approval.advance_payment); - } - - return this.financeApprovalRepo.save(approval); - } - - async rejectFinanceApproval(id: string, approverId: string, notes?: string): Promise { - const approval = await this.financeApprovalRepo.findOne({ - where: { id }, - relations: ['expense_claim', 'advance_payment'] - }); - if (!approval) throw new NotFoundException('Finance approval not found'); - - approval.status = 'rejected'; - approval.approver_id = approverId; - approval.approval_notes = notes || ''; - approval.rejected_at = new Date(); - - // 同时更新关联的报销申请或预支款状态 - if (approval.expense_claim) { - approval.expense_claim.status = 'rejected'; - approval.expense_claim.approver_id = approverId; - approval.expense_claim.rejected_at = new Date(); - await this.expenseClaimRepo.save(approval.expense_claim); - } - - if (approval.advance_payment) { - approval.advance_payment.status = 'rejected'; - approval.advance_payment.approver_id = approverId; - approval.advance_payment.rejected_at = new Date(); - await this.advancePaymentRepo.save(approval.advance_payment); - } - - return this.financeApprovalRepo.save(approval); - } - - // 公司账目管理 - async getCompanyFinancialRecords(filter?: any): Promise { - const query = this.financialRecordRepo.createQueryBuilder('fr') - .leftJoinAndSelect('fr.project', 'project'); - - if (filter) { - if (filter.type) query.where('fr.type = :type', { type: filter.type }); - if (filter.status) query.where('fr.status = :status', { status: filter.status }); - if (filter.start_date) query.where('fr.created_at >= :start_date', { start_date: filter.start_date }); - if (filter.end_date) query.where('fr.created_at <= :end_date', { end_date: filter.end_date }); - } - - return query.getMany(); - } - - // 项目财务记录 - async getProjectFinancialRecords(projectId: string): Promise { - return this.financialRecordRepo.find({ - where: { project_id: projectId }, - relations: ['financial_transactions', 'financial_transactions.bank_account'] - }); - } - - // 员工个人记账 - async getEmployeeFinancialRecords(userId: string): Promise { - const expenseClaims = await this.expenseClaimRepo.find({ - where: { user_id: userId }, - relations: ['project', 'vouchers'] - }); - - const advancePayments = await this.advancePaymentRepo.find({ - where: { user_id: userId }, - relations: ['project', 'vouchers'] - }); - - return [...expenseClaims, ...advancePayments]; - } - - // 汇率管理相关 - async getCurrencyRates(): Promise { - return this.currencyRateRepo.find({ where: { is_active: true } }); - } - - async getCurrencyRate(fromCurrency: string, toCurrency: string): Promise { - return this.currencyRateRepo.findOne({ - where: { from_currency: fromCurrency, to_currency: toCurrency, is_active: true } - }); - } - - async createOrUpdateCurrencyRate(data: { - from_currency: string; - to_currency: string; - rate: number; - }): Promise { - const { from_currency, to_currency, rate } = data; - - // 检查是否已存在该货币对的汇率 - let currencyRate = await this.currencyRateRepo.findOne({ - where: { from_currency, to_currency } - }); - - if (currencyRate) { - // 更新现有汇率 - currencyRate.rate = rate; - currencyRate.is_active = true; - return this.currencyRateRepo.save(currencyRate); - } else { - // 创建新汇率 - currencyRate = this.currencyRateRepo.create({ - from_currency, - to_currency, - rate, - is_active: true - }); - return this.currencyRateRepo.save(currencyRate); - } - } - - async updateCurrencyRateStatus(id: string, is_active: boolean): Promise { - const currencyRate = await this.currencyRateRepo.findOne({ where: { id } }); - if (!currencyRate) throw new NotFoundException('Currency rate not found'); - currencyRate.is_active = is_active; - return this.currencyRateRepo.save(currencyRate); - } - - // 金额换算方法 - async convertCurrency(amount: number, fromCurrency: string, toCurrency: string): Promise { - if (fromCurrency === toCurrency) return amount; - - // 尝试直接获取汇率 - let rate = await this.getCurrencyRate(fromCurrency, toCurrency); - if (rate) { - return amount * rate.rate; - } - - // 尝试反向汇率 - const reverseRate = await this.getCurrencyRate(toCurrency, fromCurrency); - if (reverseRate) { - return amount / reverseRate.rate; - } - - // 如果都没有,抛出异常 - throw new NotFoundException(`Exchange rate not found for ${fromCurrency} to ${toCurrency}`); - } - - // 获取所有支持的币种 - getSupportedCurrencies(): Array<{ code: string; name: string; symbol: string }> { - return [ - { code: 'CNY', name: '人民币', symbol: '¥' }, - { code: 'LAK', name: '老挝基普', symbol: '₭' }, - { code: 'THB', name: '泰铢', symbol: '฿' }, - { code: 'USD', name: '美元', symbol: '$' } - ]; - } -} diff --git a/backend/src/modules/finance/financial-record.entity.ts b/backend/src/modules/finance/financial-record.entity.ts deleted file mode 100644 index d942860..0000000 --- a/backend/src/modules/finance/financial-record.entity.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { Project } from '../projects/project.entity'; -import { PaymentTerm } from '../contracts/payment-term.entity'; -import { FinancialTransaction } from './financial-transaction.entity'; - -@Entity('financial_records') -export class FinancialRecord { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 50, nullable: false }) - type: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'uuid', nullable: true }) - project_id: string; - - @ManyToOne(() => Project, project => project.financial_records) - project: Project; - - @Column({ type: 'uuid', nullable: true }) - related_id: string; - - @Column({ type: 'varchar', length: 50, nullable: true }) - related_type: string; - - @Column({ type: 'uuid', nullable: true }) - payment_term_id: string; - - @ManyToOne(() => PaymentTerm, paymentTerm => paymentTerm.financial_records) - payment_term: PaymentTerm; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @Column({ type: 'text', nullable: true }) - description: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => FinancialTransaction, financialTransaction => financialTransaction.financial_record) - financial_transactions: FinancialTransaction[]; -} diff --git a/backend/src/modules/finance/financial-transaction.entity.ts b/backend/src/modules/finance/financial-transaction.entity.ts deleted file mode 100644 index 69c1f57..0000000 --- a/backend/src/modules/finance/financial-transaction.entity.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm'; -import { FinancialRecord } from './financial-record.entity'; -import { BankAccount } from './bank-account.entity'; - -@Entity('financial_transactions') -export class FinancialTransaction { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: true }) - financial_record_id: string; - - @ManyToOne(() => FinancialRecord, financialRecord => financialRecord.financial_transactions) - financial_record: FinancialRecord; - - @Column({ type: 'uuid', nullable: true }) - bank_account_id: string; - - @ManyToOne(() => BankAccount, bankAccount => bankAccount.financial_transactions) - bank_account: BankAccount; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'date', nullable: false }) - transaction_date: Date; - - @Column({ type: 'varchar', length: 50, nullable: false }) - transaction_type: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/finance/voucher.entity.ts b/backend/src/modules/finance/voucher.entity.ts deleted file mode 100644 index 2be15a4..0000000 --- a/backend/src/modules/finance/voucher.entity.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm'; -import { User } from '../users/user.entity'; -import { FinancialRecord } from './financial-record.entity'; -import { ExpenseClaim } from './expense-claim.entity'; -import { AdvancePayment } from './advance-payment.entity'; - -@Entity('vouchers') -export class Voucher { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - file_path: string; - - @Column({ type: 'varchar', length: 100, nullable: false }) - file_name: string; - - @Column({ type: 'varchar', length: 50, nullable: false }) - file_type: string; - - @Column({ type: 'uuid', nullable: true }) - financial_record_id: string; - - @ManyToOne(() => FinancialRecord) - financial_record: FinancialRecord; - - @Column({ type: 'uuid', nullable: true }) - expense_claim_id: string; - - @ManyToOne(() => ExpenseClaim) - expense_claim: ExpenseClaim; - - @Column({ type: 'uuid', nullable: true }) - advance_payment_id: string; - - @ManyToOne(() => AdvancePayment) - advance_payment: AdvancePayment; - - @Column({ type: 'uuid', nullable: false }) - uploaded_by: string; - - @ManyToOne(() => User) - uploader: User; - - @Column({ type: 'text', nullable: true }) - description: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/permissions/permission.entity.ts b/backend/src/modules/permissions/permission.entity.ts deleted file mode 100644 index e52d4bd..0000000 --- a/backend/src/modules/permissions/permission.entity.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm'; - -@Entity('permissions') -export class Permission { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ unique: true, nullable: false }) - name: string; - - @Column({ type: 'text', nullable: true }) - description: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/permissions/permissions.controller.ts b/backend/src/modules/permissions/permissions.controller.ts deleted file mode 100644 index af71cab..0000000 --- a/backend/src/modules/permissions/permissions.controller.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards } from '@nestjs/common'; -import { PermissionsService } from './permissions.service'; -import { AuthGuard } from '@nestjs/passport'; - -@Controller('api/v1/permissions') -export class PermissionsController { - constructor(private readonly permissionsService: PermissionsService) {} - - @Post() - @UseGuards(AuthGuard('jwt')) - async create( - @Body('name') name: string, - @Body('description') description: string, - ) { - return this.permissionsService.create(name, description); - } - - @Get() - @UseGuards(AuthGuard('jwt')) - async findAll() { - return this.permissionsService.findAll(); - } - - @Get(':id') - @UseGuards(AuthGuard('jwt')) - async findById(@Param('id') id: string) { - return this.permissionsService.findById(id); - } - - @Put(':id') - @UseGuards(AuthGuard('jwt')) - async update( - @Param('id') id: string, - @Body('name') name: string, - @Body('description') description: string, - ) { - return this.permissionsService.update(id, name, description); - } - - @Delete(':id') - @UseGuards(AuthGuard('jwt')) - async delete(@Param('id') id: string) { - return this.permissionsService.delete(id); - } -} diff --git a/backend/src/modules/permissions/permissions.module.ts b/backend/src/modules/permissions/permissions.module.ts deleted file mode 100644 index 0e9d0cf..0000000 --- a/backend/src/modules/permissions/permissions.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Permission } from './permission.entity'; -import { PermissionsService } from './permissions.service'; -import { PermissionsController } from './permissions.controller'; - -@Module({ - imports: [TypeOrmModule.forFeature([Permission])], - providers: [PermissionsService], - controllers: [PermissionsController], - exports: [PermissionsService, TypeOrmModule.forFeature([Permission])], -}) -export class PermissionsModule {} diff --git a/backend/src/modules/permissions/permissions.service.ts b/backend/src/modules/permissions/permissions.service.ts deleted file mode 100644 index f4b9f8b..0000000 --- a/backend/src/modules/permissions/permissions.service.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Permission } from './permission.entity'; - -@Injectable() -export class PermissionsService { - constructor( - @InjectRepository(Permission) - private readonly permissionsRepository: Repository, - ) {} - - async create(name: string, description: string) { - const permission = this.permissionsRepository.create({ - name, - description, - }); - return this.permissionsRepository.save(permission); - } - - async findById(id: string) { - return this.permissionsRepository.findOne({ - where: { id }, - }); - } - - async findAll() { - return this.permissionsRepository.find(); - } - - async update(id: string, name: string, description: string) { - await this.permissionsRepository.update(id, { - name, - description, - }); - return this.findById(id); - } - - async delete(id: string) { - return this.permissionsRepository.delete(id); - } -} diff --git a/backend/src/modules/products/product-category.entity.ts b/backend/src/modules/products/product-category.entity.ts deleted file mode 100644 index 5d4681d..0000000 --- a/backend/src/modules/products/product-category.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { Product } from './product.entity'; - -@Entity('product_categories') -export class ProductCategory { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - name: string; - - @Column({ type: 'uuid', nullable: true }) - parent_id: string; - - @ManyToOne(() => ProductCategory, productCategory => productCategory.children) - parent: ProductCategory; - - @OneToMany(() => ProductCategory, productCategory => productCategory.parent) - children: ProductCategory[]; - - @OneToMany(() => Product, product => product.category) - products: Product[]; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/products/product.entity.ts b/backend/src/modules/products/product.entity.ts deleted file mode 100644 index af95f87..0000000 --- a/backend/src/modules/products/product.entity.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { ProductCategory } from './product-category.entity'; -import { PurchaseItem } from '../purchases/purchase-item.entity'; -import { QuotationItem } from '../quotations/quotation-item.entity'; - -@Entity('products') -export class Product { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - name: string; - - @Column({ type: 'uuid', nullable: true }) - category_id: string; - - @ManyToOne(() => ProductCategory, productCategory => productCategory.products) - category: ProductCategory; - - @Column({ type: 'varchar', length: 50, nullable: false }) - unit: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: true }) - cost_price: number; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => PurchaseItem, purchaseItem => purchaseItem.product) - purchase_items: PurchaseItem[]; - - @OneToMany(() => QuotationItem, quotationItem => quotationItem.product) - quotation_items: QuotationItem[]; -} diff --git a/backend/src/modules/products/products.module.ts b/backend/src/modules/products/products.module.ts deleted file mode 100644 index e4040bd..0000000 --- a/backend/src/modules/products/products.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Product } from './product.entity'; -import { ProductCategory } from './product-category.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([Product, ProductCategory])], - exports: [TypeOrmModule], -}) -export class ProductsModule {} \ No newline at end of file diff --git a/backend/src/modules/projects/dto/create-project.dto.ts b/backend/src/modules/projects/dto/create-project.dto.ts deleted file mode 100644 index 6e607a5..0000000 --- a/backend/src/modules/projects/dto/create-project.dto.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { IsString, IsNumber, IsDate, IsOptional, IsUUID, IsDecimal } from 'class-validator'; - -export class CreateProjectDto { - @IsString() - name: string; - - @IsUUID() - @IsOptional() - customer_id: string; - - @IsString() - contract_number: string; - - @IsNumber() - contract_amount: number; - - @IsString() - currency: string; - - @IsNumber() - rmb_equivalent: number; - - @IsDate() - @IsOptional() - start_date: Date; - - @IsDate() - @IsOptional() - end_date: Date; - - @IsString() - status: string; - - @IsString() - @IsOptional() - settlement_type: string; // 'lump_sum' 或 'unit_price' - - @IsNumber() - @IsOptional() - estimated_quantity: number; - - @IsNumber() - @IsOptional() - unit_price: number; -} diff --git a/backend/src/modules/projects/dto/index.ts b/backend/src/modules/projects/dto/index.ts deleted file mode 100644 index 05f9529..0000000 --- a/backend/src/modules/projects/dto/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './create-project.dto'; -export * from './update-project.dto'; diff --git a/backend/src/modules/projects/dto/update-project.dto.ts b/backend/src/modules/projects/dto/update-project.dto.ts deleted file mode 100644 index 5e72e69..0000000 --- a/backend/src/modules/projects/dto/update-project.dto.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { IsString, IsNumber, IsDate, IsOptional, IsUUID } from 'class-validator'; - -export class UpdateProjectDto { - @IsString() - @IsOptional() - name: string; - - @IsUUID() - @IsOptional() - customer_id: string; - - @IsString() - @IsOptional() - contract_number: string; - - @IsNumber() - @IsOptional() - contract_amount: number; - - @IsString() - @IsOptional() - currency: string; - - @IsNumber() - @IsOptional() - rmb_equivalent: number; - - @IsDate() - @IsOptional() - start_date: Date; - - @IsDate() - @IsOptional() - end_date: Date; - - @IsString() - @IsOptional() - status: string; - - @IsString() - @IsOptional() - settlement_type: string; // 'lump_sum' 或 'unit_price' - - @IsNumber() - @IsOptional() - estimated_quantity: number; - - @IsNumber() - @IsOptional() - unit_price: number; -} diff --git a/backend/src/modules/projects/project.entity.ts b/backend/src/modules/projects/project.entity.ts deleted file mode 100644 index 51a8c70..0000000 --- a/backend/src/modules/projects/project.entity.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { Customer } from '../customers/customer.entity'; -import { Contract } from '../contracts/contract.entity'; -import { Purchase } from '../purchases/purchase.entity'; -import { Subcontract } from '../subcontracts/subcontract.entity'; -import { FinancialRecord } from '../finance/financial-record.entity'; -import { ExpenseClaim } from '../finance/expense-claim.entity'; -import { AdvancePayment } from '../finance/advance-payment.entity'; - -@Entity('projects') -export class Project { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - name: string; - - @Column({ type: 'uuid', nullable: true }) - customer_id: string; - - @ManyToOne(() => Customer, customer => customer.projects) - customer: Customer; - - @Column({ type: 'varchar', length: 100, unique: true, nullable: false }) - contract_number: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - contract_amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'date', nullable: true }) - start_date: Date; - - @Column({ type: 'date', nullable: true }) - end_date: Date; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @Column({ type: 'varchar', length: 20, nullable: true }) - settlement_type: string; // 'lump_sum' 或 'unit_price' - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: true }) - estimated_quantity: number; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: true }) - unit_price: number; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => Contract, contract => contract.project) - contracts: Contract[]; - - @OneToMany(() => Purchase, purchase => purchase.project) - purchases: Purchase[]; - - @OneToMany(() => Subcontract, subcontract => subcontract.project) - subcontracts: Subcontract[]; - - @OneToMany(() => FinancialRecord, financialRecord => financialRecord.project) - financial_records: FinancialRecord[]; - - @OneToMany(() => ExpenseClaim, expenseClaim => expenseClaim.project) - expense_claims: ExpenseClaim[]; - - @OneToMany(() => AdvancePayment, advancePayment => advancePayment.project) - advance_payments: AdvancePayment[]; -} diff --git a/backend/src/modules/projects/projects.controller.ts b/backend/src/modules/projects/projects.controller.ts deleted file mode 100644 index 461b2ae..0000000 --- a/backend/src/modules/projects/projects.controller.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common'; -import { ProjectsService } from './projects.service'; -import { CreateProjectDto, UpdateProjectDto } from './dto'; - -@Controller('projects') -export class ProjectsController { - constructor(private readonly projectsService: ProjectsService) {} - - @Get() - async findAll(@Query() query) { - return this.projectsService.findAll(query); - } - - @Get(':id') - async findOne(@Param('id') id: string) { - return this.projectsService.findOne(id); - } - - @Post() - async create(@Body() createProjectDto: CreateProjectDto) { - return this.projectsService.create(createProjectDto); - } - - @Put(':id') - async update(@Param('id') id: string, @Body() updateProjectDto: UpdateProjectDto) { - return this.projectsService.update(id, updateProjectDto); - } - - @Delete(':id') - async remove(@Param('id') id: string) { - return this.projectsService.remove(id); - } - - @Get(':id/calculate-duration') - async calculateDuration(@Param('id') id: string) { - return this.projectsService.calculateDuration(id); - } - - @Put(':id/update-status') - async updateStatus(@Param('id') id: string, @Body('status') status: string) { - return this.projectsService.updateStatus(id, status); - } -} diff --git a/backend/src/modules/projects/projects.module.ts b/backend/src/modules/projects/projects.module.ts deleted file mode 100644 index 32926cb..0000000 --- a/backend/src/modules/projects/projects.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Project } from './project.entity'; -import { ProjectsController } from './projects.controller'; -import { ProjectsService } from './projects.service'; - -@Module({ - imports: [TypeOrmModule.forFeature([Project])], - controllers: [ProjectsController], - providers: [ProjectsService], - exports: [ProjectsService], -}) -export class ProjectsModule {} \ No newline at end of file diff --git a/backend/src/modules/projects/projects.service.ts b/backend/src/modules/projects/projects.service.ts deleted file mode 100644 index 15dc1bb..0000000 --- a/backend/src/modules/projects/projects.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Project } from './project.entity'; -import { CreateProjectDto, UpdateProjectDto } from './dto'; - -@Injectable() -export class ProjectsService { - constructor( - @InjectRepository(Project) - private projectsRepository: Repository, - ) {} - - async findAll(query) { - const { page = 1, limit = 10, status, customer_id } = query; - const skip = (page - 1) * limit; - - const queryBuilder = this.projectsRepository.createQueryBuilder('project'); - - if (status) { - queryBuilder.where('project.status = :status', { status }); - } - - if (customer_id) { - queryBuilder.where('project.customer_id = :customer_id', { customer_id }); - } - - const [projects, total] = await queryBuilder - .leftJoinAndSelect('project.customer', 'customer') - .skip(skip) - .take(limit) - .getManyAndCount(); - - return { - projects, - total, - page, - limit, - }; - } - - async findOne(id: string) { - const project = await this.projectsRepository.findOne({ - where: { id }, - relations: ['customer', 'contracts', 'purchases', 'subcontracts'], - }); - - if (!project) { - throw new NotFoundException(`Project with ID ${id} not found`); - } - - return project; - } - - async create(createProjectDto: CreateProjectDto) { - const project = this.projectsRepository.create(createProjectDto); - return this.projectsRepository.save(project); - } - - async update(id: string, updateProjectDto: UpdateProjectDto) { - const project = await this.findOne(id); - Object.assign(project, updateProjectDto); - return this.projectsRepository.save(project); - } - - async remove(id: string) { - const project = await this.findOne(id); - return this.projectsRepository.remove(project); - } - - async calculateDuration(id: string) { - const project = await this.findOne(id); - - if (!project.start_date || !project.end_date) { - return { message: 'Start date or end date is not set' }; - } - - const start = new Date(project.start_date); - const end = new Date(project.end_date); - const duration = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); - - return { duration_days: duration }; - } - - async updateStatus(id: string, status: string) { - const project = await this.findOne(id); - project.status = status; - return this.projectsRepository.save(project); - } -} diff --git a/backend/src/modules/purchases/purchase-item.entity.ts b/backend/src/modules/purchases/purchase-item.entity.ts deleted file mode 100644 index 2a1486b..0000000 --- a/backend/src/modules/purchases/purchase-item.entity.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; -import { Purchase } from './purchase.entity'; -import { Product } from '../products/product.entity'; - -@Entity('purchase_items') -export class PurchaseItem { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: true }) - purchase_id: string; - - @ManyToOne(() => Purchase, purchase => purchase.purchase_items) - purchase: Purchase; - - @Column({ type: 'uuid', nullable: true }) - product_id: string; - - @ManyToOne(() => Product, product => product.purchase_items) - product: Product; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - quantity: number; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - unit_price: number; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - total_price: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; -} diff --git a/backend/src/modules/purchases/purchase.entity.ts b/backend/src/modules/purchases/purchase.entity.ts deleted file mode 100644 index 6206249..0000000 --- a/backend/src/modules/purchases/purchase.entity.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { Project } from '../projects/project.entity'; -import { Supplier } from '../suppliers/supplier.entity'; -import { Contract } from '../contracts/contract.entity'; -import { PurchaseItem } from './purchase-item.entity'; - -@Entity('purchases') -export class Purchase { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: true }) - project_id: string; - - @ManyToOne(() => Project, project => project.purchases) - project: Project; - - @Column({ type: 'uuid', nullable: true }) - supplier_id: string; - - @ManyToOne(() => Supplier, supplier => supplier.purchases) - supplier: Supplier; - - @Column({ type: 'uuid', nullable: true }) - contract_id: string; - - @ManyToOne(() => Contract, contract => contract.purchases) - contract: Contract; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - total_amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'date', nullable: true }) - expected_delivery_date: Date; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => PurchaseItem, purchaseItem => purchaseItem.purchase) - purchase_items: PurchaseItem[]; -} diff --git a/backend/src/modules/purchases/purchases.module.ts b/backend/src/modules/purchases/purchases.module.ts deleted file mode 100644 index be07834..0000000 --- a/backend/src/modules/purchases/purchases.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Purchase } from './purchase.entity'; -import { PurchaseItem } from './purchase-item.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([Purchase, PurchaseItem])], - exports: [TypeOrmModule], -}) -export class PurchasesModule {} \ No newline at end of file diff --git a/backend/src/modules/quotations/quotation-item.entity.ts b/backend/src/modules/quotations/quotation-item.entity.ts deleted file mode 100644 index 071ad46..0000000 --- a/backend/src/modules/quotations/quotation-item.entity.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; -import { Quotation } from './quotation.entity'; -import { Product } from '../products/product.entity'; - -@Entity('quotation_items') -export class QuotationItem { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: true }) - quotation_id: string; - - @ManyToOne(() => Quotation, quotation => quotation.quotation_items) - quotation: Quotation; - - @Column({ type: 'uuid', nullable: true }) - product_id: string; - - @ManyToOne(() => Product, product => product.quotation_items) - product: Product; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - quantity: number; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - unit_price: number; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - total_price: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; -} diff --git a/backend/src/modules/quotations/quotation.entity.ts b/backend/src/modules/quotations/quotation.entity.ts deleted file mode 100644 index 5d9523a..0000000 --- a/backend/src/modules/quotations/quotation.entity.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; -import { Customer } from '../customers/customer.entity'; -import { QuotationItem } from './quotation-item.entity'; - -@Entity('quotations') -export class Quotation { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: true }) - customer_id: string; - - @ManyToOne(() => Customer, customer => customer.quotations) - customer: Customer; - - @Column({ type: 'varchar', length: 255, nullable: false }) - project_name: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - total_amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => QuotationItem, quotationItem => quotationItem.quotation) - quotation_items: QuotationItem[]; -} diff --git a/backend/src/modules/quotations/quotations.module.ts b/backend/src/modules/quotations/quotations.module.ts deleted file mode 100644 index f162bfe..0000000 --- a/backend/src/modules/quotations/quotations.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Quotation } from './quotation.entity'; -import { QuotationItem } from './quotation-item.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([Quotation, QuotationItem])], - exports: [TypeOrmModule], -}) -export class QuotationsModule {} \ No newline at end of file diff --git a/backend/src/modules/roles/role.entity.ts b/backend/src/modules/roles/role.entity.ts deleted file mode 100644 index 103fa8d..0000000 --- a/backend/src/modules/roles/role.entity.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, ManyToMany, JoinTable, CreateDateColumn, UpdateDateColumn } from 'typeorm'; -import { Permission } from '../permissions/permission.entity'; - -@Entity('roles') -export class Role { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ unique: true, nullable: false }) - name: string; - - @Column({ type: 'text', nullable: true }) - description: string; - - @ManyToMany(() => Permission) - @JoinTable({ - name: 'role_permissions', - joinColumn: { name: 'role_id' }, - inverseJoinColumn: { name: 'permission_id' }, - }) - permissions: Permission[]; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/roles/roles.controller.ts b/backend/src/modules/roles/roles.controller.ts deleted file mode 100644 index fdd7917..0000000 --- a/backend/src/modules/roles/roles.controller.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards } from '@nestjs/common'; -import { RolesService } from './roles.service'; -import { AuthGuard } from '@nestjs/passport'; - -@Controller('api/v1/roles') -export class RolesController { - constructor(private readonly rolesService: RolesService) {} - - @Post() - @UseGuards(AuthGuard('jwt')) - async create( - @Body('name') name: string, - @Body('description') description: string, - @Body('permissionIds') permissionIds: string[], - ) { - return this.rolesService.create(name, description, permissionIds); - } - - @Get() - @UseGuards(AuthGuard('jwt')) - async findAll() { - return this.rolesService.findAll(); - } - - @Get(':id') - @UseGuards(AuthGuard('jwt')) - async findById(@Param('id') id: string) { - return this.rolesService.findById(id); - } - - @Put(':id') - @UseGuards(AuthGuard('jwt')) - async update( - @Param('id') id: string, - @Body('name') name: string, - @Body('description') description: string, - @Body('permissionIds') permissionIds: string[], - ) { - return this.rolesService.update(id, name, description, permissionIds); - } - - @Delete(':id') - @UseGuards(AuthGuard('jwt')) - async delete(@Param('id') id: string) { - return this.rolesService.delete(id); - } - - @Post(':id/permissions') - @UseGuards(AuthGuard('jwt')) - async assignPermissions( - @Param('id') roleId: string, - @Body('permissionIds') permissionIds: string[], - ) { - return this.rolesService.assignPermissions(roleId, permissionIds); - } -} diff --git a/backend/src/modules/roles/roles.module.ts b/backend/src/modules/roles/roles.module.ts deleted file mode 100644 index 1cba8f3..0000000 --- a/backend/src/modules/roles/roles.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Role } from './role.entity'; -import { Permission } from '../permissions/permission.entity'; -import { RolesService } from './roles.service'; -import { RolesController } from './roles.controller'; - -@Module({ - imports: [TypeOrmModule.forFeature([Role, Permission])], - providers: [RolesService], - controllers: [RolesController], - exports: [RolesService, TypeOrmModule.forFeature([Role, Permission])], -}) -export class RolesModule {} diff --git a/backend/src/modules/roles/roles.service.ts b/backend/src/modules/roles/roles.service.ts deleted file mode 100644 index dcb71a7..0000000 --- a/backend/src/modules/roles/roles.service.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Role } from './role.entity'; -import { Permission } from '../permissions/permission.entity'; - -@Injectable() -export class RolesService { - constructor( - @InjectRepository(Role) - private readonly rolesRepository: Repository, - @InjectRepository(Permission) - private readonly permissionsRepository: Repository, - ) {} - - async create(name: string, description: string, permissionIds: string[]) { - const permissions = await this.permissionsRepository.findByIds(permissionIds); - const role = this.rolesRepository.create({ - name, - description, - permissions, - }); - return this.rolesRepository.save(role); - } - - async findById(id: string) { - return this.rolesRepository.findOne({ - where: { id }, - relations: ['permissions'], - }); - } - - async findAll() { - return this.rolesRepository.find({ - relations: ['permissions'], - }); - } - - async update(id: string, name: string, description: string, permissionIds: string[]) { - const permissions = await this.permissionsRepository.findByIds(permissionIds); - await this.rolesRepository.update(id, { - name, - description, - }); - const role = await this.findById(id); - if (!role) return null; - role.permissions = permissions; - return this.rolesRepository.save(role); - } - - async delete(id: string) { - return this.rolesRepository.delete(id); - } - - async assignPermissions(roleId: string, permissionIds: string[]) { - const role = await this.findById(roleId); - if (!role) return null; - const permissions = await this.permissionsRepository.findByIds(permissionIds); - role.permissions = permissions; - return this.rolesRepository.save(role); - } -} diff --git a/backend/src/modules/subcontractors/subcontractor.entity.ts b/backend/src/modules/subcontractors/subcontractor.entity.ts deleted file mode 100644 index 5134876..0000000 --- a/backend/src/modules/subcontractors/subcontractor.entity.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm'; -import { Subcontract } from '../subcontracts/subcontract.entity'; - -@Entity('subcontractors') -export class Subcontractor { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - name: string; - - @Column({ type: 'varchar', length: 255, nullable: true }) - contact_person: string; - - @Column({ type: 'varchar', length: 50, nullable: true }) - phone: string; - - @Column({ type: 'varchar', length: 255, nullable: true }) - email: string; - - @Column({ type: 'text', nullable: true }) - address: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => Subcontract, subcontract => subcontract.subcontractor) - subcontracts: Subcontract[]; -} diff --git a/backend/src/modules/subcontractors/subcontractors.controller.ts b/backend/src/modules/subcontractors/subcontractors.controller.ts deleted file mode 100644 index 4ce1a4e..0000000 --- a/backend/src/modules/subcontractors/subcontractors.controller.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; -import { SubcontractorsService } from './subcontractors.service'; -import { Subcontractor } from './subcontractor.entity'; - -@Controller('api/v1/subcontractors') -export class SubcontractorsController { - constructor(private readonly subcontractorsService: SubcontractorsService) {} - - @Post() - create(@Body() subcontractorData: Partial) { - return this.subcontractorsService.create(subcontractorData); - } - - @Get() - findAll() { - return this.subcontractorsService.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.subcontractorsService.findOne(id); - } - - @Get(':id/subcontracts') - findWithSubcontracts(@Param('id') id: string) { - return this.subcontractorsService.findWithSubcontracts(id); - } - - @Patch(':id') - update(@Param('id') id: string, @Body() subcontractorData: Partial) { - return this.subcontractorsService.update(id, subcontractorData); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.subcontractorsService.remove(id); - } -} diff --git a/backend/src/modules/subcontractors/subcontractors.module.ts b/backend/src/modules/subcontractors/subcontractors.module.ts deleted file mode 100644 index ffce923..0000000 --- a/backend/src/modules/subcontractors/subcontractors.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Subcontractor } from './subcontractor.entity'; -import { SubcontractorsController } from './subcontractors.controller'; -import { SubcontractorsService } from './subcontractors.service'; - -@Module({ - imports: [TypeOrmModule.forFeature([Subcontractor])], - controllers: [SubcontractorsController], - providers: [SubcontractorsService], - exports: [SubcontractorsService], -}) -export class SubcontractorsModule {} \ No newline at end of file diff --git a/backend/src/modules/subcontractors/subcontractors.service.ts b/backend/src/modules/subcontractors/subcontractors.service.ts deleted file mode 100644 index 9d8b72a..0000000 --- a/backend/src/modules/subcontractors/subcontractors.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Subcontractor } from './subcontractor.entity'; - -@Injectable() -export class SubcontractorsService { - constructor( - @InjectRepository(Subcontractor) - private subcontractorsRepository: Repository, - ) {} - - async create(subcontractorData: Partial): Promise { - const subcontractor = this.subcontractorsRepository.create(subcontractorData); - return this.subcontractorsRepository.save(subcontractor); - } - - async findAll(): Promise { - return this.subcontractorsRepository.find(); - } - - async findOne(id: string): Promise { - const subcontractor = await this.subcontractorsRepository.findOne({ where: { id } }); - if (!subcontractor) { - throw new NotFoundException(`Subcontractor with id ${id} not found`); - } - return subcontractor; - } - - async update(id: string, subcontractorData: Partial): Promise { - const subcontractor = await this.findOne(id); - Object.assign(subcontractor, subcontractorData); - return this.subcontractorsRepository.save(subcontractor); - } - - async remove(id: string): Promise { - const result = await this.subcontractorsRepository.delete(id); - if (result.affected === 0) { - throw new NotFoundException(`Subcontractor with id ${id} not found`); - } - } - - async findWithSubcontracts(id: string): Promise { - const subcontractor = await this.subcontractorsRepository.findOne({ - where: { id }, - relations: ['subcontracts'], - }); - if (!subcontractor) { - throw new NotFoundException(`Subcontractor with id ${id} not found`); - } - return subcontractor; - } -} diff --git a/backend/src/modules/subcontracts/subcontract.entity.ts b/backend/src/modules/subcontracts/subcontract.entity.ts deleted file mode 100644 index c7dd684..0000000 --- a/backend/src/modules/subcontracts/subcontract.entity.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm'; -import { Project } from '../projects/project.entity'; -import { Subcontractor } from '../subcontractors/subcontractor.entity'; -import { Contract } from '../contracts/contract.entity'; - -@Entity('subcontracts') -export class Subcontract { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'uuid', nullable: true }) - project_id: string; - - @ManyToOne(() => Project, project => project.subcontracts) - project: Project; - - @Column({ type: 'uuid', nullable: true }) - subcontractor_id: string; - - @ManyToOne(() => Subcontractor, subcontractor => subcontractor.subcontracts) - subcontractor: Subcontractor; - - @Column({ type: 'uuid', nullable: true }) - contract_id: string; - - @ManyToOne(() => Contract, contract => contract.subcontracts) - contract: Contract; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - total_amount: number; - - @Column({ type: 'varchar', length: 10, nullable: false }) - currency: string; - - @Column({ type: 'decimal', precision: 18, scale: 2, nullable: false }) - rmb_equivalent: number; - - @Column({ type: 'varchar', length: 50, nullable: false }) - status: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/subcontracts/subcontracts.module.ts b/backend/src/modules/subcontracts/subcontracts.module.ts deleted file mode 100644 index e49c6a6..0000000 --- a/backend/src/modules/subcontracts/subcontracts.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Subcontract } from './subcontract.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([Subcontract])], - exports: [TypeOrmModule], -}) -export class SubcontractsModule {} \ No newline at end of file diff --git a/backend/src/modules/suppliers/supplier.entity.ts b/backend/src/modules/suppliers/supplier.entity.ts deleted file mode 100644 index 05a8f7f..0000000 --- a/backend/src/modules/suppliers/supplier.entity.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm'; -import { Purchase } from '../purchases/purchase.entity'; - -@Entity('suppliers') -export class Supplier { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 255, nullable: false }) - name: string; - - @Column({ type: 'varchar', length: 255, nullable: true }) - contact_person: string; - - @Column({ type: 'varchar', length: 50, nullable: true }) - phone: string; - - @Column({ type: 'varchar', length: 255, nullable: true }) - email: string; - - @Column({ type: 'text', nullable: true }) - address: string; - - @Column({ type: 'varchar', length: 50, nullable: true }) - type: string; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; - - @OneToMany(() => Purchase, purchase => purchase.supplier) - purchases: Purchase[]; -} diff --git a/backend/src/modules/suppliers/suppliers.controller.ts b/backend/src/modules/suppliers/suppliers.controller.ts deleted file mode 100644 index 642d302..0000000 --- a/backend/src/modules/suppliers/suppliers.controller.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; -import { SuppliersService } from './suppliers.service'; -import { Supplier } from './supplier.entity'; - -@Controller('api/v1/suppliers') -export class SuppliersController { - constructor(private readonly suppliersService: SuppliersService) {} - - @Post() - create(@Body() supplierData: Partial) { - return this.suppliersService.create(supplierData); - } - - @Get() - findAll() { - return this.suppliersService.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.suppliersService.findOne(id); - } - - @Get(':id/purchases') - findWithPurchases(@Param('id') id: string) { - return this.suppliersService.findWithPurchases(id); - } - - @Patch(':id') - update(@Param('id') id: string, @Body() supplierData: Partial) { - return this.suppliersService.update(id, supplierData); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.suppliersService.remove(id); - } -} diff --git a/backend/src/modules/suppliers/suppliers.module.ts b/backend/src/modules/suppliers/suppliers.module.ts deleted file mode 100644 index 080ca47..0000000 --- a/backend/src/modules/suppliers/suppliers.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Supplier } from './supplier.entity'; -import { SuppliersController } from './suppliers.controller'; -import { SuppliersService } from './suppliers.service'; - -@Module({ - imports: [TypeOrmModule.forFeature([Supplier])], - controllers: [SuppliersController], - providers: [SuppliersService], - exports: [SuppliersService], -}) -export class SuppliersModule {} \ No newline at end of file diff --git a/backend/src/modules/suppliers/suppliers.service.ts b/backend/src/modules/suppliers/suppliers.service.ts deleted file mode 100644 index 3bfd1cf..0000000 --- a/backend/src/modules/suppliers/suppliers.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Supplier } from './supplier.entity'; - -@Injectable() -export class SuppliersService { - constructor( - @InjectRepository(Supplier) - private suppliersRepository: Repository, - ) {} - - async create(supplierData: Partial): Promise { - const supplier = this.suppliersRepository.create(supplierData); - return this.suppliersRepository.save(supplier); - } - - async findAll(): Promise { - return this.suppliersRepository.find(); - } - - async findOne(id: string): Promise { - const supplier = await this.suppliersRepository.findOne({ where: { id } }); - if (!supplier) { - throw new NotFoundException(`Supplier with id ${id} not found`); - } - return supplier; - } - - async update(id: string, supplierData: Partial): Promise { - const supplier = await this.findOne(id); - Object.assign(supplier, supplierData); - return this.suppliersRepository.save(supplier); - } - - async remove(id: string): Promise { - const result = await this.suppliersRepository.delete(id); - if (result.affected === 0) { - throw new NotFoundException(`Supplier with id ${id} not found`); - } - } - - async findWithPurchases(id: string): Promise { - const supplier = await this.suppliersRepository.findOne({ - where: { id }, - relations: ['purchases'], - }); - if (!supplier) { - throw new NotFoundException(`Supplier with id ${id} not found`); - } - return supplier; - } -} diff --git a/backend/src/modules/users/user.entity.ts b/backend/src/modules/users/user.entity.ts deleted file mode 100644 index 3fe4c11..0000000 --- a/backend/src/modules/users/user.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm'; -import { Role } from '../roles/role.entity'; - -@Entity('users') -export class User { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ unique: true, nullable: false }) - username: string; - - @Column({ nullable: false }) - password: string; - - @Column({ nullable: false }) - name: string; - - @Column({ unique: true, nullable: false }) - email: string; - - @ManyToOne(() => Role) - role: Role; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; -} diff --git a/backend/src/modules/users/users.controller.ts b/backend/src/modules/users/users.controller.ts deleted file mode 100644 index f5c9a5c..0000000 --- a/backend/src/modules/users/users.controller.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards } from '@nestjs/common'; -import { UsersService } from './users.service'; -import { AuthGuard } from '@nestjs/passport'; - -@Controller('api/v1/users') -export class UsersController { - constructor(private readonly usersService: UsersService) {} - - @Post() - async create( - @Body('username') username: string, - @Body('password') password: string, - @Body('name') name: string, - @Body('email') email: string, - @Body('roleId') roleId: string, - ) { - return this.usersService.create({ username, password, name, email, roleId }); - } - - @Get() - @UseGuards(AuthGuard('jwt')) - async findAll() { - return this.usersService.findAll(); - } - - @Get(':id') - @UseGuards(AuthGuard('jwt')) - async findById(@Param('id') id: string) { - return this.usersService.findById(id); - } - - @Put(':id') - @UseGuards(AuthGuard('jwt')) - async update( - @Param('id') id: string, - @Body() userData: any, - ) { - return this.usersService.update(id, userData); - } - - @Delete(':id') - @UseGuards(AuthGuard('jwt')) - async delete(@Param('id') id: string) { - return this.usersService.delete(id); - } -} diff --git a/backend/src/modules/users/users.module.ts b/backend/src/modules/users/users.module.ts deleted file mode 100644 index 93908db..0000000 --- a/backend/src/modules/users/users.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { User } from './user.entity'; -import { UsersService } from './users.service'; -import { UsersController } from './users.controller'; - -@Module({ - imports: [TypeOrmModule.forFeature([User])], - providers: [UsersService], - controllers: [UsersController], - exports: [UsersService, TypeOrmModule.forFeature([User])], -}) -export class UsersModule {} diff --git a/backend/src/modules/users/users.service.spec.ts b/backend/src/modules/users/users.service.spec.ts deleted file mode 100644 index 39429f0..0000000 --- a/backend/src/modules/users/users.service.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UsersService } from './users.service'; -import { User } from './user.entity'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import * as bcrypt from 'bcryptjs'; - -describe('UsersService', () => { - let usersService: UsersService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - UsersService, - { - provide: getRepositoryToken(User), - useValue: { - findOne: jest.fn(), - }, - }, - ], - }).compile(); - - usersService = module.get(UsersService); - }); - - describe('validatePassword', () => { - it('should return true for valid password', async () => { - const user = new User(); - user.password = bcrypt.hashSync('password123', 10); - - const result = await usersService.validatePassword(user, 'password123'); - expect(result).toBe(true); - }); - - it('should return false for invalid password', async () => { - const user = new User(); - user.password = bcrypt.hashSync('password123', 10); - - const result = await usersService.validatePassword(user, 'wrongpassword'); - expect(result).toBe(false); - }); - }); -}); \ No newline at end of file diff --git a/backend/src/modules/users/users.service.ts b/backend/src/modules/users/users.service.ts deleted file mode 100644 index c641420..0000000 --- a/backend/src/modules/users/users.service.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { User } from './user.entity'; -import * as bcrypt from 'bcryptjs'; - -@Injectable() -export class UsersService { - constructor( - @InjectRepository(User) - private readonly usersRepository: Repository, - ) {} - - async create(userData: { - username: string; - password: string; - name: string; - email: string; - roleId: string; - }) { - const user = this.usersRepository.create({ - ...userData, - role: { id: userData.roleId } as any, - }); - return this.usersRepository.save(user); - } - - async findById(id: string) { - return this.usersRepository.findOne({ - where: { id }, - relations: ['role', 'role.permissions'], - }); - } - - async findByUsername(username: string) { - return this.usersRepository.findOne({ - where: { username }, - relations: ['role', 'role.permissions'], - }); - } - - async findByEmail(email: string) { - return this.usersRepository.findOne({ - where: { email }, - }); - } - - async findAll() { - return this.usersRepository.find({ - relations: ['role'], - }); - } - - async update(id: string, userData: Partial) { - await this.usersRepository.update(id, userData); - return this.findById(id); - } - - async delete(id: string) { - return this.usersRepository.delete(id); - } - - async validatePassword(user: User, password: string): Promise { - return bcrypt.compare(password, user.password); - } -} diff --git a/backend/test/app.e2e-spec.ts b/backend/test/app.e2e-spec.ts deleted file mode 100644 index 36852c5..0000000 --- a/backend/test/app.e2e-spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import request from 'supertest'; -import { App } from 'supertest/types'; -import { AppModule } from './../src/app.module'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); - }); -}); diff --git a/backend/test/jest-e2e.json b/backend/test/jest-e2e.json deleted file mode 100644 index e9d912f..0000000 --- a/backend/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} diff --git a/backend/tsconfig.build.json b/backend/tsconfig.build.json deleted file mode 100644 index 64f86c6..0000000 --- a/backend/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] -} diff --git a/backend/tsconfig.json b/backend/tsconfig.json deleted file mode 100644 index aba29b0..0000000 --- a/backend/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "module": "nodenext", - "moduleResolution": "nodenext", - "resolvePackageJsonExports": true, - "esModuleInterop": true, - "isolatedModules": true, - "declaration": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "ES2023", - "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", - "incremental": true, - "skipLibCheck": true, - "strictNullChecks": true, - "forceConsistentCasingInFileNames": true, - "noImplicitAny": false, - "strictBindCallApply": false, - "noFallthroughCasesInSwitch": false - } -} diff --git a/backup_20260327/company-finance-system/.gitignore b/backup_20260327/company-finance-system/.gitignore new file mode 100644 index 0000000..be1c71e --- /dev/null +++ b/backup_20260327/company-finance-system/.gitignore @@ -0,0 +1,60 @@ +# Dependencies +node_modules/ +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Production builds +dist/ +build/ +*.exe + +# Database files +*.db +*.db-journal +*.sqlite +*.sqlite3 + +# Environment variables +.env +.env.local +.env.*.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Testing +coverage/ +.nyc_output/ + +# Temporary files +tmp/ +temp/ +*.tmp + +# Uploads (用户上传的文件) +uploads/ +public/uploads/ + +# Backup files +backups/ +*.bak + +# Cache +.cache/ +*.cache diff --git a/backup_20260327/company-finance-system/README.md b/backup_20260327/company-finance-system/README.md new file mode 100644 index 0000000..c3dcd59 --- /dev/null +++ b/backup_20260327/company-finance-system/README.md @@ -0,0 +1,37 @@ +# 轻远电力老挝ERP系统 + +## 系统访问 +- **前端地址**: http://43.161.248.209:3001/ +- **后端API**: http://43.161.248.209:3000/ + +## 测试账号 +| 用户名 | 密码 | 角色 | +|--------|------|------| +| admin | 123456 | 系统管理员 | +| finance1 | 123456 | 财务专员 | +| shejianjun | 123456 | 管理员 | + +## 功能模块 +1. **仪表板** - 数据概览和统计 +2. **项目管理** - 项目创建、查看、编辑 +3. **财务管理** - 预支申请、报销申请 +4. **报表中心** - 财务报表查看 + +## 技术栈 +- 前端:React + TypeScript + Ant Design + Vite +- 后端:Node.js + Express +- 数据库:PostgreSQL + +## 服务管理 +```bash +# 查看服务状态 +netstat -tlnp | grep -E ':3000|:3001' + +# 重启后端 +cd /opt/qingyuan-erp/backend +node api-complete.js & + +# 重启前端 +cd /opt/qingyuan-erp/frontend +npm run dev -- --host 0.0.0.0 & +``` diff --git a/backup_20260327/company-finance-system/backend/.env.example b/backup_20260327/company-finance-system/backend/.env.example new file mode 100644 index 0000000..2b7cc34 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/.env.example @@ -0,0 +1,19 @@ +# 数据库配置 +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=company_finance_db +DB_USER=postgres +DB_PASSWORD=postgres + +# 服务器配置 +PORT=3000 +NODE_ENV=development + +# 生产环境配置示例 +# DB_HOST=your-production-db-host +# DB_PORT=5432 +# DB_NAME=company_finance_prod +# DB_USER=production_user +# DB_PASSWORD=strong_password +# PORT=8080 +# NODE_ENV=production \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/.env.production b/backup_20260327/company-finance-system/backend/.env.production new file mode 100644 index 0000000..728bce1 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/.env.production @@ -0,0 +1,26 @@ +# 生产环境配置 +NODE_ENV=production +PORT=5000 + +# 生产数据库配置 +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=company_finance_db +DB_USER=finance_user +DB_PASSWORD=FinanceDB2026! + +# 安全配置 +JWT_SECRET=your-production-jwt-secret-key-change-this +SESSION_SECRET=your-production-session-secret-change-this + +# 日志配置 +LOG_LEVEL=info +LOG_FILE=/var/log/company-finance-api.log + +# CORS配置 +CORS_ORIGIN=https://your-domain.com +CORS_CREDENTIALS=true + +# 性能配置 +REQUEST_TIMEOUT=30000 +BODY_PARSER_LIMIT=10mb \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/IMPLEMENTATION_REPORT.md b/backup_20260327/company-finance-system/backend/IMPLEMENTATION_REPORT.md new file mode 100644 index 0000000..1ba6009 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/IMPLEMENTATION_REPORT.md @@ -0,0 +1,223 @@ +# 客户管理API实现报告 + +## 任务完成情况 + +已成功在 `/opt/company-finance-system/backend` 目录下实现客户管理完整CRUD API,基于现有架构扩展。 + +## 实现功能 + +### 1. API端点列表(全部实现) + +| 方法 | 端点 | 功能描述 | 状态 | +|------|------|----------|------| +| GET | `/api/customers` | 获取客户列表(支持分页、搜索、状态过滤) | ✅ | +| GET | `/api/customers/:id` | 获取单个客户详情 | ✅ | +| POST | `/api/customers` | 创建新客户 | ✅ | +| PUT | `/api/customers/:id` | 更新客户信息 | ✅ | +| DELETE | `/api/customers/:id` | 删除客户 | ✅ | +| GET | `/api/customers/:id/contacts` | 获取客户联系人列表 | ✅ | +| GET | `/health` | 健康检查端点 | ✅ | + +### 2. 数据库设计 +使用PostgreSQL数据库 `company_finance_db`,包含以下表: + +#### customers表(客户表) +- `id` - 主键,自增 +- `name` - 客户名称(必填) +- `email` - 邮箱(必填,唯一) +- `phone` - 电话 +- `address` - 地址 +- `company` - 公司名称 +- `tax_id` - 税号 +- `status` - 状态(active/inactive) +- `created_at` - 创建时间 +- `updated_at` - 更新时间 + +#### contacts表(联系人表) +- `id` - 主键,自增 +- `customer_id` - 外键,关联customers表 +- `name` - 联系人姓名 +- `position` - 职位 +- `email` - 邮箱 +- `phone` - 电话 +- `is_primary` - 是否主要联系人 +- `created_at` - 创建时间 +- `updated_at` - 更新时间 + +### 3. 数据验证和错误处理 + +#### 验证规则 +- **创建客户**:名称和邮箱必填,邮箱格式验证,状态值验证 +- **更新客户**:邮箱格式验证(如果提供),状态值验证 +- **查询参数**:页码、每页数量、ID参数验证 +- **唯一性约束**:邮箱地址唯一性检查 + +#### 错误处理 +- 统一错误响应格式 +- 适当的HTTP状态码(200, 201, 400, 404, 409, 500) +- 详细的错误信息(开发环境) +- 验证错误数组格式 + +### 4. 功能特性 +- ✅ 完整的分页支持(page, limit参数) +- ✅ 全文搜索(name, email, company字段) +- ✅ 状态过滤(active/inactive) +- ✅ 部分更新支持(PATCH语义) +- ✅ 级联删除(删除客户时自动删除联系人) +- ✅ 数据库索引优化 +- ✅ 连接池管理 +- ✅ 跨域支持(CORS) + +## 测试方法 + +### 1. 快速测试脚本 +```bash +# 使脚本可执行 +chmod +x test-api.sh + +# 运行完整测试 +./test-api.sh +``` + +### 2. 手动curl测试 +```bash +# 1. 启动服务器 +npm run dev + +# 2. 测试各个端点 +curl http://localhost:3000/health +curl "http://localhost:3000/api/customers?page=1&limit=5" +curl "http://localhost:3000/api/customers?search=张" +curl -X POST http://localhost:3000/api/customers \ + -H "Content-Type: application/json" \ + -d '{"name":"测试","email":"test@example.com"}' +curl http://localhost:3000/api/customers/1 +curl -X PUT http://localhost:3000/api/customers/1 \ + -H "Content-Type: application/json" \ + -d '{"phone":"13888888888"}' +curl -X DELETE http://localhost:3000/api/customers/1 +curl http://localhost:3000/api/customers/1/contacts +``` + +### 3. Postman测试 +导入 `postman-collection.json` 文件,设置环境变量: +- `base_url`: `http://localhost:3000` + +### 4. 数据库初始化测试 +```bash +# 初始化数据库(包含示例数据) +sudo -u postgres psql -f init-db.sql +``` + +## 项目文件结构 + +``` +/opt/company-finance-system/backend/ +├── server-complete.js # 主服务器文件(客户管理API) +├── db.js # 数据库连接配置 +├── package.json # 依赖配置 +├── package-lock.json # 依赖锁文件 +├── .env # 环境变量配置 +├── .env.example # 环境变量示例 +├── init-db.sql # 数据库初始化脚本(包含示例数据) +├── test-api.sh # 自动化测试脚本 +├── start-server.sh # 服务器启动脚本 +├── README.md # 完整项目文档 +├── IMPLEMENTATION_REPORT.md # 本实现报告 +├── postman-collection.json # Postman测试集合 +└── node_modules/ # 依赖模块 +``` + +## 技术实现细节 + +### 1. 架构设计 +- **MVC模式**:清晰的分层结构 +- **RESTful设计**:符合REST原则的API设计 +- **中间件架构**:使用Express中间件处理验证、错误等 + +### 2. 数据库层 +- **连接池**:使用pg连接池管理数据库连接 +- **事务准备**:代码结构支持事务处理(可扩展) +- **索引优化**:关键字段添加索引 +- **外键约束**:保证数据完整性 + +### 3. 业务逻辑层 +- **验证中间件**:使用express-validator +- **错误处理中间件**:统一错误响应 +- **分页逻辑**:支持灵活的分页和搜索 +- **数据转换**:请求/响应数据格式化 + +### 4. 安全考虑 +- **输入验证**:所有输入都经过验证 +- **SQL注入防护**:使用参数化查询 +- **错误信息控制**:生产环境隐藏详细错误 +- **CORS配置**:跨域请求控制 + +## 部署和运行 + +### 1. 环境要求 +- Node.js 14+ +- PostgreSQL 12+ +- npm 6+ + +### 2. 安装步骤 +```bash +# 1. 进入项目目录 +cd /opt/company-finance-system/backend + +# 2. 安装依赖 +npm install + +# 3. 初始化数据库 +sudo -u postgres psql -f init-db.sql + +# 4. 启动服务器 +npm start +# 或开发模式 +npm run dev +``` + +### 3. 环境配置 +默认使用 `.env` 文件配置: +```env +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=company_finance_db +DB_USER=postgres +DB_PASSWORD=postgres +PORT=3000 +NODE_ENV=development +``` + +## 扩展性和维护性 + +### 1. 易于扩展 +- 模块化代码结构 +- 清晰的API端点定义 +- 可配置的数据库连接 +- 支持环境变量配置 + +### 2. 易于维护 +- 完整的错误处理 +- 详细的日志输出 +- 全面的测试脚本 +- 完整的文档 + +### 3. 监控和调试 +- 健康检查端点 +- 详细的错误信息 +- 请求/响应日志 +- 数据库连接状态监控 + +## 总结 + +已成功实现客户管理完整CRUD API,满足所有要求: + +1. ✅ 在指定目录工作 +2. ✅ 基于现有架构扩展 +3. ✅ 实现6个完整的API端点 +4. ✅ 使用PostgreSQL数据库 +5. ✅ 包含数据验证和错误处理 +6. ✅ 提供完整的测试方法和文档 + +API现已就绪,可通过多种方式进行测试和集成。 \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/PROJECT_SUMMARY.md b/backup_20260327/company-finance-system/backend/PROJECT_SUMMARY.md new file mode 100644 index 0000000..80d6bde --- /dev/null +++ b/backup_20260327/company-finance-system/backend/PROJECT_SUMMARY.md @@ -0,0 +1,210 @@ +# 客户管理API项目总结 + +## 项目信息 +- **项目名称**: 公司财务系统 - 客户管理API +- **项目目录**: `/opt/company-finance-system/backend` +- **完成时间**: 2026-03-09 +- **技术栈**: Node.js + Express + PostgreSQL + +## 核心文件 + +### 1. 主服务器文件 +- **server-complete.js** (402行) - 完整的客户管理API实现 + - 6个核心API端点 + - 数据验证和错误处理 + - 分页、搜索、过滤功能 + +### 2. 数据库相关 +- **db.js** - PostgreSQL数据库连接配置 +- **init-db.sql** (78行) - 数据库初始化脚本 + - 创建customers和contacts表 + - 插入示例数据 + - 创建索引优化 + +### 3. 测试文件 +- **test-api.sh** (138行) - 完整的API测试脚本 +- **quick-test.js** - 快速验证脚本 +- **postman-collection.json** - Postman测试集合 + +### 4. 文档文件 +- **README.md** (309行) - 完整的项目文档 +- **IMPLEMENTATION_REPORT.md** (222行) - 实现报告 +- **PROJECT_SUMMARY.md** - 本项目总结 + +### 5. 配置和工具 +- **package.json** - 项目依赖配置 +- **.env** - 环境变量配置 +- **start-server.sh** - 服务器启动脚本 + +## API端点总览 + +### 健康检查 +- `GET /health` - 服务器状态检查 + +### 客户管理 (核心功能) +1. `GET /api/customers` - 获取客户列表 + - 支持分页 (`page`, `limit`) + - 支持搜索 (`search`) + - 支持状态过滤 (`status`) + +2. `GET /api/customers/:id` - 获取单个客户 + +3. `POST /api/customers` - 创建客户 + - 必填: `name`, `email` + - 邮箱格式验证 + - 邮箱唯一性检查 + +4. `PUT /api/customers/:id` - 更新客户 + - 支持部分更新 + - 邮箱唯一性检查 + +5. `DELETE /api/customers/:id` - 删除客户 + - 级联删除联系人 + +6. `GET /api/customers/:id/contacts` - 获取客户联系人 + +## 数据库设计 + +### customers表 +```sql +id SERIAL PRIMARY KEY +name VARCHAR(100) NOT NULL +email VARCHAR(100) UNIQUE NOT NULL +phone VARCHAR(20) +address TEXT +company VARCHAR(100) +tax_id VARCHAR(50) +status VARCHAR(20) DEFAULT 'active' +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +``` + +### contacts表 +```sql +id SERIAL PRIMARY KEY +customer_id INTEGER REFERENCES customers(id) ON DELETE CASCADE +name VARCHAR(100) NOT NULL +position VARCHAR(100) +email VARCHAR(100) +phone VARCHAR(20) +is_primary BOOLEAN DEFAULT false +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +``` + +## 测试方法 + +### 快速测试 +```bash +# 启动服务器 +npm run dev + +# 运行快速测试 +node quick-test.js +``` + +### 完整测试 +```bash +# 运行完整测试套件 +./test-api.sh +``` + +### 手动测试 +```bash +# 健康检查 +curl http://localhost:3000/health + +# 获取客户列表 +curl "http://localhost:3000/api/customers?page=1&limit=5" + +# 创建客户 +curl -X POST http://localhost:3000/api/customers \ + -H "Content-Type: application/json" \ + -d '{"name":"测试","email":"test@example.com"}' +``` + +## 部署步骤 + +### 1. 环境准备 +```bash +# 安装Node.js和npm +# 安装PostgreSQL + +# 进入项目目录 +cd /opt/company-finance-system/backend +``` + +### 2. 安装依赖 +```bash +npm install +``` + +### 3. 初始化数据库 +```bash +sudo -u postgres psql -f init-db.sql +``` + +### 4. 启动服务 +```bash +# 开发模式 +npm run dev + +# 生产模式 +npm start + +# 或使用启动脚本 +./start-server.sh +``` + +## 技术特点 + +### 1. 代码质量 +- 模块化设计 +- 清晰的错误处理 +- 完整的输入验证 +- 统一的响应格式 + +### 2. 性能优化 +- 数据库连接池 +- 关键字段索引 +- 分页查询优化 +- 参数化查询防止SQL注入 + +### 3. 安全性 +- 输入验证和清理 +- 错误信息控制 +- CORS配置 +- 环境变量配置 + +### 4. 可维护性 +- 完整的文档 +- 测试套件 +- 清晰的代码结构 +- 详细的注释 + +## 扩展建议 + +### 短期扩展 +1. 添加JWT身份验证 +2. 添加请求日志记录 +3. 添加API速率限制 + +### 中期扩展 +1. 添加Redis缓存 +2. 添加文件上传功能 +3. 添加数据导出功能 + +### 长期扩展 +1. 微服务架构拆分 +2. 添加消息队列 +3. 添加监控和告警 + +## 项目状态 + +✅ **已完成** - 所有要求的API端点 +✅ **已完成** - 数据库设计和初始化 +✅ **已完成** - 数据验证和错误处理 +✅ **已完成** - 测试套件和文档 +✅ **已完成** - 部署和运行指南 + +项目已完全实现并准备好用于生产环境。 \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/README.md b/backup_20260327/company-finance-system/backend/README.md new file mode 100644 index 0000000..d421cc3 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/README.md @@ -0,0 +1,310 @@ +# 公司财务系统 - 客户管理API + +## 项目概述 +客户管理完整CRUD API,基于Express.js和PostgreSQL。实现了完整的客户管理功能,包括分页、搜索、数据验证和错误处理。 + +## 技术栈 +- Node.js + Express.js +- PostgreSQL + pg客户端 +- express-validator (数据验证) +- cors (跨域支持) +- dotenv (环境变量管理) + +## 安装和运行 + +### 1. 安装依赖 +```bash +cd /opt/company-finance-system/backend +npm install +``` + +### 2. 配置数据库 +确保PostgreSQL服务正在运行,然后初始化数据库: +```bash +# 启动PostgreSQL服务(如果未运行) +sudo systemctl start postgresql + +# 创建数据库和表(使用postgres用户) +sudo -u postgres psql -f init-db.sql +``` + +或者手动执行: +```bash +# 登录PostgreSQL +sudo -u postgres psql + +# 在psql中执行 +\i init-db.sql +``` + +### 3. 环境变量配置 +已提供 `.env` 文件,包含默认配置: +```env +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=company_finance_db +DB_USER=postgres +DB_PASSWORD=postgres +PORT=3000 +NODE_ENV=development +``` + +### 4. 启动服务器 +```bash +# 开发模式(使用nodemon,自动重启) +npm run dev + +# 生产模式 +npm start +``` + +服务器将在 http://localhost:3000 启动。 + +## API端点列表 + +### 健康检查 +- `GET /health` - 检查服务器状态 + +### 客户管理API + +1. **获取客户列表** (分页、搜索、过滤) + - `GET /api/customers` + - 查询参数: + - `page` - 页码 (默认: 1) + - `limit` - 每页数量 (默认: 10, 最大: 100) + - `search` - 搜索关键词 (在名称、邮箱、公司中搜索) + - `status` - 状态过滤 (active/inactive) + +2. **获取单个客户** + - `GET /api/customers/:id` + - 路径参数:`id` - 客户ID + +3. **创建客户** + - `POST /api/customers` + - 请求体 (JSON): + ```json + { + "name": "客户名称", // 必填 + "email": "client@example.com", // 必填,有效邮箱格式 + "phone": "13800138000", // 可选 + "address": "地址", // 可选 + "company": "公司名称", // 可选 + "tax_id": "税号", // 可选 + "status": "active" // 可选,默认: active + } + ``` + +4. **更新客户** + - `PUT /api/customers/:id` + - 路径参数:`id` - 客户ID + - 请求体:需要更新的字段(部分更新支持) + +5. **删除客户** + - `DELETE /api/customers/:id` + - 路径参数:`id` - 客户ID + +6. **获取客户联系人** + - `GET /api/customers/:id/contacts` + - 路径参数:`id` - 客户ID + +## 数据验证和错误处理 + +### 数据验证 +使用express-validator进行全面的数据验证: +1. **创建/更新客户时**: + - 名称:必填,去空格 + - 邮箱:必填,有效邮箱格式,唯一性检查 + - 状态:必须是 'active' 或 'inactive' + - 所有字段:适当的长度和格式验证 + +2. **查询参数验证**: + - 页码:最小值为1 + - 每页数量:1-100之间 + - ID参数:必须是正整数 + +### 错误处理 +统一的错误响应格式: +```json +{ + "success": false, + "message": "错误描述", + "errors": [{"msg": "详细验证错误", "param": "字段名", "location": "body"}] +} +``` + +HTTP状态码: +- `200` - 成功 +- `201` - 创建成功 +- `400` - 请求参数错误/验证失败 +- `404` - 资源未找到 +- `409` - 资源冲突(邮箱已存在) +- `500` - 服务器内部错误 + +## 测试方法 + +### 1. 使用测试脚本(推荐) +```bash +# 确保服务器正在运行 +npm run dev + +# 在另一个终端运行完整测试 +chmod +x test-api.sh +./test-api.sh +``` + +### 2. 使用curl手动测试 +```bash +# 健康检查 +curl http://localhost:3000/health + +# 获取客户列表(分页) +curl "http://localhost:3000/api/customers?page=1&limit=5" + +# 搜索客户 +curl "http://localhost:3000/api/customers?search=张" + +# 创建客户 +curl -X POST http://localhost:3000/api/customers \ + -H "Content-Type: application/json" \ + -d '{"name":"测试客户","email":"test@example.com","phone":"12345678901"}' + +# 获取单个客户 +curl http://localhost:3000/api/customers/1 + +# 更新客户 +curl -X PUT http://localhost:3000/api/customers/1 \ + -H "Content-Type: application/json" \ + -d '{"phone":"13888888888"}' + +# 删除客户 +curl -X DELETE http://localhost:3000/api/customers/1 + +# 获取客户联系人 +curl http://localhost:3000/api/customers/1/contacts +``` + +### 3. 使用Postman +导入 `postman-collection.json` 文件到Postman,设置环境变量 `base_url = http://localhost:3000` + +## 数据库表结构 + +### customers表(客户表) +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | SERIAL | PRIMARY KEY | 自增主键 | +| name | VARCHAR(100) | NOT NULL | 客户名称 | +| email | VARCHAR(100) | UNIQUE, NOT NULL | 邮箱(唯一) | +| phone | VARCHAR(20) | | 联系电话 | +| address | TEXT | | 地址 | +| company | VARCHAR(100) | | 公司名称 | +| tax_id | VARCHAR(50) | | 税号 | +| status | VARCHAR(20) | DEFAULT 'active' | 状态:active/inactive | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 | + +### contacts表(联系人表) +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | SERIAL | PRIMARY KEY | 自增主键 | +| customer_id | INTEGER | REFERENCES customers(id) ON DELETE CASCADE | 客户ID(外键) | +| name | VARCHAR(100) | NOT NULL | 联系人姓名 | +| position | VARCHAR(100) | | 职位 | +| email | VARCHAR(100) | | 邮箱 | +| phone | VARCHAR(20) | | 电话 | +| is_primary | BOOLEAN | DEFAULT false | 是否主要联系人 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 | + +### 索引 +- `idx_customers_email` - 邮箱索引(加速查询和唯一性检查) +- `idx_customers_status` - 状态索引(加速状态过滤) +- `idx_contacts_customer_id` - 客户ID索引(加速关联查询) + +## 示例数据 +初始化脚本已包含示例数据: +- 5个示例客户(3个active,1个inactive) +- 7个示例联系人 +- 包含中文数据,便于测试搜索功能 + +## 注意事项 + +1. **数据库连接**:确保PostgreSQL服务正在运行,默认使用postgres用户 +2. **环境安全**:生产环境请修改默认密码,使用更安全的认证方式 +3. **性能考虑**: + - 分页查询避免大数据量传输 + - 重要字段已添加索引 + - 使用连接池管理数据库连接 +4. **数据完整性**: + - 邮箱唯一性约束 + - 外键约束保证数据一致性 + - 级联删除(删除客户时自动删除联系人) + +## 故障排除 + +### 常见问题 + +1. **数据库连接失败** + ```bash + # 检查PostgreSQL服务状态 + sudo systemctl status postgresql + + # 检查连接配置 + cat .env + + # 测试数据库连接 + sudo -u postgres psql -l + ``` + +2. **API返回500错误** + - 检查服务器控制台输出 + - 验证数据库表是否存在:`sudo -u postgres psql -d company_finance_db -c "\dt"` + - 检查请求数据格式是否正确 + +3. **邮箱已存在错误(409)** + - 每个客户必须有唯一的邮箱地址 + - 更新操作时也要确保邮箱唯一性 + +4. **验证错误(400)** + - 检查请求体JSON格式 + - 确保必填字段已提供 + - 验证邮箱格式是否正确 + +### 日志查看 +- 服务器启动日志:控制台输出 +- 数据库错误:服务器控制台和PostgreSQL日志 +- API请求日志:服务器控制台 + +## 扩展建议 + +1. **添加身份验证**:使用JWT实现API认证 +2. **添加日志系统**:使用winston或morgan记录请求日志 +3. **添加缓存**:对频繁查询的数据添加Redis缓存 +4. **添加监控**:集成Prometheus监控指标 +5. **API文档**:使用Swagger/OpenAPI生成文档 + +## 项目结构 +``` +/opt/company-finance-system/backend/ +├── server-complete.js # 主服务器文件(客户管理API) +├── db.js # 数据库连接配置 +├── package.json # 依赖配置 +├── .env # 环境变量 +├── .env.example # 环境变量示例 +├── init-db.sql # 数据库初始化脚本 +├── test-api.sh # API测试脚本 +├── README.md # 项目文档 +└── postman-collection.json # Postman集合 +``` + +## 完成状态 +✅ 所有要求的API端点已实现: +1. ✅ GET /api/customers - 获取客户列表(分页、搜索) +2. ✅ GET /api/customers/:id - 获取单个客户 +3. ✅ POST /api/customers - 创建客户 +4. ✅ PUT /api/customers/:id - 更新客户 +5. ✅ DELETE /api/customers/:id - 删除客户 +6. ✅ GET /api/customers/:id/contacts - 获取客户联系人 + +✅ 使用PostgreSQL数据库,连接现有company_finance_db +✅ 包含数据验证和错误处理 +✅ 提供完整的测试方法和文档 \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/add_source_column.sql b/backup_20260327/company-finance-system/backend/add_source_column.sql new file mode 100644 index 0000000..a4572cb --- /dev/null +++ b/backup_20260327/company-finance-system/backend/add_source_column.sql @@ -0,0 +1,2 @@ +-- 添加source字段到products表 +ALTER TABLE products ADD COLUMN source TEXT DEFAULT '老挝'; diff --git a/backup_20260327/company-finance-system/backend/api-complete.js b/backup_20260327/company-finance-system/backend/api-complete.js new file mode 100644 index 0000000..4935b23 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/api-complete.js @@ -0,0 +1,1151 @@ +const express = require('express'); +const cors = require('cors'); +const { body, param, query, validationResult } = require('express-validator'); +const multer = require('multer'); +const xlsx = require('xlsx'); +require('dotenv').config(); +const db = require('./db-sqlite'); + +// 文件上传配置 +const storage = multer.memoryStorage(); +const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 } }); +const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']; + +const app = express(); +const PORT = process.argv[2] || process.env.PORT || 3000; + +// 中间件 +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// 验证错误处理 +const validate = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ success: false, errors: errors.array() }); + } + next(); +}; + +// ==================== 健康检查 ==================== +app.get('/health', (req, res) => { + res.json({ status: 'healthy', timestamp: new Date().toISOString() }); +}); + +app.get('/', (req, res) => { + res.json({ + success: true, + message: '轻远电力老挝ERP API', + version: '2.0.0', + endpoints: { + health: '/api/health', + auth: '/api/auth', + users: '/api/users', + projects: '/api/projects', + products: '/api/products', + customers: '/api/customers', + suppliers: '/api/suppliers', + advances: '/api/advances', + reimbursements: '/api/reimbursements', + exchangeRates: '/api/exchange-rates' + } + }); +}); + +// ==================== 用户认证 ==================== +app.post('/api/auth/login', [ + body('username').notEmpty(), + body('password').notEmpty() +], validate, async (req, res) => { + try { + const { username, password } = req.body; + const result = await db.query( + 'SELECT id, username, role, name FROM users WHERE username = ? AND password = ?', + [username, password] + ); + + if (result.rows.length === 0) { + return res.status(401).json({ success: false, message: '用户名或密码错误' }); + } + + res.json({ success: true, data: result.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// 兼容前端的登录路径 +app.post('/api/users/login', [ + body('username').notEmpty(), + body('password').notEmpty() +], validate, async (req, res) => { + try { + console.log('收到登录请求:', req.body); + const { username, password } = req.body; + + const result = await db.query( + 'SELECT id, username, role, name FROM users WHERE username = ? AND password = ?', + [username, password] + ); + + console.log('查询结果:', result.rows); + + if (result.rows.length === 0) { + console.log('登录失败: 用户不存在或密码错误'); + return res.status(401).json({ user: null, token: null, message: '用户名或密码错误' }); + } + + // 返回前端期望的格式 + const responseData = { user: result.rows[0], token: 'dummy-token-' + Date.now() }; + console.log('登录成功,返回数据:', responseData); + res.json(responseData); + } catch (error) { + console.error('登录错误:', error); + res.status(500).json({ user: null, token: null, message: error.message }); + } +}); + +// ==================== 项目管理 ==================== +app.get('/api/projects', async (req, res) => { + try { + const result = await db.query('SELECT * FROM projects ORDER BY created_at DESC'); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.post('/api/projects', [ + body('name').notEmpty(), + body('code').notEmpty() +], validate, async (req, res) => { + try { + const { name, code, customer_id, contract_amount, start_date, end_date, description } = req.body; + const result = await db.query( + `INSERT INTO projects (name, code, customer_id, contract_amount, start_date, end_date, description, status) + VALUES (?, ?, ?, ?, ?, ?, ?, 'active')`, + [name, code, customer_id, contract_amount, start_date, end_date, description] + ); + // SQLite不支持RETURNING,所以需要查询刚插入的数据 + const lastInsert = await db.query('SELECT * FROM projects ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 客户管理 ==================== +app.get('/api/customers', async (req, res) => { + try { + const result = await db.query('SELECT * FROM customers ORDER BY created_at DESC'); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.post('/api/customers', [ + body('name').notEmpty() +], validate, async (req, res) => { + try { + const { name, contact, phone, email, address, type } = req.body; + const result = await db.query( + `INSERT INTO customers (name, contact, phone, email, address, type) + VALUES (?, ?, ?, ?, ?, ?)`, + [name, contact, phone, email, address, type || 'corporate'] + ); + // SQLite不支持RETURNING,所以需要查询刚插入的数据 + const lastInsert = await db.query('SELECT * FROM customers ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 供应商管理 ==================== +app.get('/api/suppliers', async (req, res) => { + try { + const result = await db.query('SELECT * FROM suppliers ORDER BY created_at DESC'); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 产品管理 ==================== +app.get('/api/products', async (req, res) => { + try { + // 获取所有分类信息 + const categoriesResult = await db.query('SELECT * FROM categories'); + const categoriesMap = new Map(); + categoriesResult.rows.forEach(cat => { + categoriesMap.set(cat.id, cat); + }); + + // 获取所有商品 + const result = await db.query('SELECT * FROM products ORDER BY created_at DESC'); + + // 转换数据结构以匹配前端期望 + const products = result.rows.map((product, index) => { + let category_name = product.category; + let parent_category_name = null; + + // 根据category_id获取分类信息 + if (product.category_id && categoriesMap.has(product.category_id)) { + const category = categoriesMap.get(product.category_id); + category_name = category.name; + + // 获取父分类信息 + if (category.parent_id && categoriesMap.has(category.parent_id)) { + const parentCategory = categoriesMap.get(category.parent_id); + parent_category_name = parentCategory.name; + } + } + + return { + id: product.id, + name: product.name, + model: product.model || product.code, // 如果model为空,使用code作为fallback + category_id: product.category_id || index + 1, // 使用实际的category_id + category_name: category_name, + parent_category_name: parent_category_name, + unit: '个', // 默认单位 + quantity: 0, // 默认数量 + cost_price: product.price, + thumbnail: null, // 默认无缩略图 + specification: product.description, // 使用description作为specification + brand: null, // 默认无品牌 + remark: null, // 默认无备注 + source: product.source || '老挝', // 确保source字段存在 + created_at: product.created_at, + updated_at: product.updated_at + }; + }); + + res.json({ success: true, data: products, total: products.length }); + } catch (error) { + console.error('获取商品失败:', error); + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 预支管理 ==================== +app.get('/api/advances', async (req, res) => { + try { + const result = await db.query(` + SELECT a.*, u.name as user_name, p.name as project_name + FROM advances a + LEFT JOIN users u ON a.user_id = u.id + LEFT JOIN projects p ON a.project_id = p.id + ORDER BY a.created_at DESC + `); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 报销管理 ==================== +app.get('/api/reimbursements', async (req, res) => { + try { + const result = await db.query(` + SELECT r.*, u.name as user_name, p.name as project_name + FROM reimbursements r + LEFT JOIN users u ON r.user_id = u.id + LEFT JOIN projects p ON r.project_id = p.id + ORDER BY r.created_at DESC + `); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 汇率管理 ==================== +app.get('/api/exchange-rates', async (req, res) => { + try { + const result = await db.query(` + SELECT * FROM exchange_rates + WHERE is_active = true + ORDER BY effective_date DESC + LIMIT 10 + `); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 预算报价管理 ==================== +app.get('/api/budget-projects', async (req, res) => { + try { + const result = await db.query(` + SELECT b.*, + (SELECT json_group_array(json_object( + 'id', q.id, + 'version', q.version, + 'quotation_date', q.quotation_date, + 'amount', q.amount, + 'currency', q.currency, + 'status', q.status, + 'file_url', q.file_url, + 'remark', q.remark, + 'created_at', q.created_at + )) FROM quotations q WHERE q.project_id = b.id) as quotations + FROM budget_projects b + ORDER BY b.created_at DESC + `); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 施工管理 ==================== +app.get('/api/construction/my-projects', async (req, res) => { + try { + const result = await db.query(` + SELECT p.*, + c.name as customer_name, + (SELECT json_object( + 'id', cl.id, + 'log_date', cl.log_date, + 'weather', cl.weather, + 'work_content', cl.work_content + ) FROM construction_logs cl WHERE cl.project_id = p.id ORDER BY cl.log_date DESC LIMIT 1) as latest_log + FROM projects p + LEFT JOIN customers c ON p.customer_id = c.id + WHERE p.status IN ('active', 'pending') + ORDER BY p.created_at DESC + `); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 文件上传API (模拟) ==================== +app.post('/api/upload/single', upload.single('file'), async (req, res) => { + try { + if (!req.file) return res.status(400).json({ success: false, error: '没有上传文件' }); + + const ext = req.file.originalname.split('.').pop().toLowerCase(); + const timestamp = Date.now(); + const randomStr = Math.random().toString(36).substring(2, 8); + const filename = 'uploads/' + timestamp + '_' + randomStr + '.' + ext; + + // 模拟文件上传,返回模拟的文件URL + const fileUrl = 'https://example.com/' + filename; + + res.json({ + success: true, + data: { + url: fileUrl, + name: req.file.originalname, + size: req.file.size, + type: req.file.mimetype, + isImage: imageFormats.includes(ext) + } + }); + } catch (error) { + console.error('上传异常:', error); + res.status(500).json({ success: false, error: '上传失败' }); + } +}); + +app.post('/api/upload/multiple', upload.array('files', 10), async (req, res) => { + try { + if (!req.files || req.files.length === 0) { + return res.status(400).json({ success: false, error: '没有上传文件' }); + } + + const results = req.files.map(file => { + const ext = file.originalname.split('.').pop().toLowerCase(); + const filename = 'uploads/' + Date.now() + '_' + Math.random().toString(36).substring(2, 8) + '.' + ext; + const fileUrl = 'https://example.com/' + filename; + return { + url: fileUrl, + name: file.originalname, + size: file.size, + isImage: imageFormats.includes(ext) + }; + }); + + res.json({ success: true, data: results }); + } catch (error) { + console.error('批量上传失败:', error); + res.status(500).json({ success: false, error: '上传失败' }); + } +}); + +// 处理分类的函数 +async function handleCategory(firstCategory, secondCategory = null) { + try { + // 检查一级分类是否存在 + let firstCategoryId; + const firstCategoryResult = await db.query( + 'SELECT id FROM categories WHERE name = ? AND parent_id IS NULL', + [firstCategory] + ); + + if (firstCategoryResult.rows.length === 0) { + // 创建一级分类 + await db.query( + 'INSERT INTO categories (name, parent_id, level, created_at, updated_at) VALUES (?, NULL, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', + [firstCategory] + ); + + // 获取刚创建的一级分类ID + const newFirstCategoryResult = await db.query( + 'SELECT id FROM categories WHERE name = ? AND parent_id IS NULL ORDER BY id DESC LIMIT 1', + [firstCategory] + ); + firstCategoryId = newFirstCategoryResult.rows[0].id; + } else { + firstCategoryId = firstCategoryResult.rows[0].id; + } + + // 如果有二级分类,检查并创建 + if (secondCategory) { + let secondCategoryId; + const secondCategoryResult = await db.query( + 'SELECT id FROM categories WHERE name = ? AND parent_id = ?', + [secondCategory, firstCategoryId] + ); + + if (secondCategoryResult.rows.length === 0) { + // 创建二级分类 + await db.query( + 'INSERT INTO categories (name, parent_id, level, created_at, updated_at) VALUES (?, ?, 2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', + [secondCategory, firstCategoryId] + ); + + // 获取刚创建的二级分类ID + const newSecondCategoryResult = await db.query( + 'SELECT id FROM categories WHERE name = ? AND parent_id = ? ORDER BY id DESC LIMIT 1', + [secondCategory, firstCategoryId] + ); + secondCategoryId = newSecondCategoryResult.rows[0].id; + } else { + secondCategoryId = secondCategoryResult.rows[0].id; + } + + return secondCategoryId; + } + + return firstCategoryId; + } catch (error) { + throw new Error('处理分类失败: ' + error.message); + } +} + +// ==================== 商品批量上传API ==================== +app.post('/api/products/batch-import', upload.single('file'), async (req, res) => { + try { + console.log('收到批量导入请求'); + if (!req.file) { + console.error('没有上传文件'); + return res.status(400).json({ success: false, error: '没有上传文件' }); + } + + console.log('文件信息:', req.file.originalname, req.file.size); + const ext = req.file.originalname.split('.').pop().toLowerCase(); + if (!['xlsx', 'xls'].includes(ext)) { + console.error('文件类型不支持:', ext); + return res.status(400).json({ success: false, error: '只支持Excel文件' }); + } + + // 解析Excel文件 + console.log('开始解析Excel文件'); + const workbook = xlsx.read(req.file.buffer, { type: 'buffer' }); + const sheetName = workbook.SheetNames[0]; + console.log('工作表名称:', sheetName); + const worksheet = workbook.Sheets[sheetName]; + const data = xlsx.utils.sheet_to_json(worksheet); + console.log('解析成功,数据条数:', data.length); + + if (data.length === 0) { + console.error('Excel文件为空'); + return res.status(400).json({ success: false, error: 'Excel文件为空' }); + } + console.log('前5条数据:', data.slice(0, 5)); + + // 批量导入商品 + let successCount = 0; + let errorCount = 0; + const errors = []; + + for (const item of data) { + try { + // 验证必填字段 + if (!item['商品名称']) { + throw new Error('商品名称不能为空'); + } + if (!item['一级分类']) { + throw new Error('一级分类不能为空'); + } + + // 处理分类 + const categoryId = await handleCategory(item['一级分类'], item['二级分类']); + + // 处理来源字段,默认为老挝 + const source = item['来源'] || '老挝'; + + // 生成唯一的code + const baseCode = item['型号'] || item['商品名称']; + const uniqueCode = baseCode + '_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // 确定分类名称 + let categoryName = item['一级分类']; + if (item['二级分类']) { + categoryName = item['二级分类']; + } + + // 插入商品 + await db.query( + `INSERT INTO products (name, code, model, category_id, category, price, description, source, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + [ + item['商品名称'], + uniqueCode, + item['型号'], + categoryId, + categoryName, + item['成本单价'] || 0, + item['规格参数'] || item['备注'] || '', + source + ] + ); + successCount++; + } catch (error) { + errorCount++; + console.error('导入商品失败:', item['商品名称'], error.message); + errors.push({ item: item['商品名称'], error: error.message }); + } + } + + res.json({ + success: true, + message: `导入完成,成功 ${successCount} 条,失败 ${errorCount} 条`, + data: { + successCount, + errorCount, + errors + } + }); + } catch (error) { + console.error('批量导入失败:', error); + res.status(500).json({ success: false, error: '导入失败: ' + error.message }); + } +}); + +// ==================== 下载商品模板 ==================== +app.get('/api/products/template', async (req, res) => { + try { + // 创建模板数据 + const templateData = [ + { '商品名称': '示例商品1', '型号': 'MODEL001', '一级分类': '电器', '二级分类': '家用电器', '单位': '个', '成本单价': 100.00, '品牌': '品牌A', '规格参数': '220V', '来源': '老挝', '备注': '示例商品' }, + { '商品名称': '示例商品2', '型号': 'MODEL002', '一级分类': '工具', '二级分类': '手动工具', '单位': '把', '成本单价': 50.00, '品牌': '品牌B', '规格参数': '标准型', '来源': '中国', '备注': '示例商品' } + ]; + + // 创建工作簿和工作表 + const workbook = xlsx.utils.book_new(); + const worksheet = xlsx.utils.json_to_sheet(templateData); + xlsx.utils.book_append_sheet(workbook, worksheet, '商品模板'); + + // 设置响应头 + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', 'attachment; filename=' + encodeURIComponent('商品导入模板.xlsx')); + + // 发送文件 + const buffer = xlsx.write(workbook, { bookType: 'xlsx', type: 'buffer' }); + res.send(buffer); + } catch (error) { + console.error('下载模板失败:', error); + res.status(500).json({ success: false, error: '下载模板失败' }); + } +}); + +// ==================== 付款申请 ==================== +app.get('/api/payment-requests', async (req, res) => { + try { + const result = await db.query('SELECT * FROM payment_requests ORDER BY created_at DESC'); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.post('/api/payment-requests', async (req, res) => { + try { + const { applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, detail_items, attachments } = req.body; + const requestCode = 'PAY' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + Math.floor(1000 + Math.random() * 9000); + + await db.query( + 'INSERT INTO payment_requests (request_code, applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, detail_items, attachments, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [requestCode, applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, JSON.stringify(detail_items), JSON.stringify(attachments), 'pending'] + ); + + const lastInsert = await db.query('SELECT * FROM payment_requests ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.put('/api/payment-requests/:id', async (req, res) => { + try { + const { id } = req.params; + const { applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, detail_items, attachments } = req.body; + + await db.query( + 'UPDATE payment_requests SET applicant = ?, payee = ?, bank_account = ?, bank_name = ?, amount = ?, amount_cny = ?, currency = ?, payment_date = ?, reason = ?, detail_items = ?, attachments = ? WHERE id = ?', + [applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, JSON.stringify(detail_items), JSON.stringify(attachments), id] + ); + + const updatedResult = await db.query('SELECT * FROM payment_requests WHERE id = ?', [id]); + res.json({ success: true, data: updatedResult.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.delete('/api/payment-requests/:id', async (req, res) => { + try { + const { id } = req.params; + await db.query('DELETE FROM payment_requests WHERE id = ?', [id]); + res.json({ success: true, message: '删除成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.post('/api/payment-requests/:id/withdraw', async (req, res) => { + try { + const { id } = req.params; + await db.query('UPDATE payment_requests SET status = ? WHERE id = ?', ['withdrawn', id]); + res.json({ success: true, message: '已撤回' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 核销申请 ==================== +app.get('/api/verifications', async (req, res) => { + try { + const result = await db.query('SELECT * FROM verifications ORDER BY created_at DESC'); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.post('/api/verifications', async (req, res) => { + try { + const { applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, detail_items, attachments } = req.body; + const verificationCode = 'VER' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + Math.floor(1000 + Math.random() * 9000); + + await db.query( + 'INSERT INTO verifications (verification_code, applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, detail_items, attachments, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [verificationCode, applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, JSON.stringify(detail_items), JSON.stringify(attachments), 'pending'] + ); + + const lastInsert = await db.query('SELECT * FROM verifications ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.put('/api/verifications/:id', async (req, res) => { + try { + const { id } = req.params; + const { applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, detail_items, attachments } = req.body; + + await db.query( + 'UPDATE verifications SET applicant = ?, advance_code = ?, advance_amount = ?, amount = ?, amount_cny = ?, currency = ?, verification_date = ?, reason = ?, detail_items = ?, attachments = ? WHERE id = ?', + [applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, JSON.stringify(detail_items), JSON.stringify(attachments), id] + ); + + const updatedResult = await db.query('SELECT * FROM verifications WHERE id = ?', [id]); + res.json({ success: true, data: updatedResult.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.delete('/api/verifications/:id', async (req, res) => { + try { + const { id } = req.params; + await db.query('DELETE FROM verifications WHERE id = ?', [id]); + res.json({ success: true, message: '删除成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.post('/api/verifications/:id/withdraw', async (req, res) => { + try { + const { id } = req.params; + await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['withdrawn', id]); + res.json({ success: true, message: '已撤回' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 分类管理API ==================== +app.get('/api/categories', async (req, res) => { + try { + const result = await db.query('SELECT * FROM categories ORDER BY level, parent_id, name'); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.post('/api/categories', async (req, res) => { + try { + const { name, parent_id, level } = req.body; + await db.query( + 'INSERT INTO categories (name, parent_id, level, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', + [name, parent_id, level] + ); + res.json({ success: true, message: '分类创建成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.put('/api/categories/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, parent_id, level } = req.body; + await db.query( + 'UPDATE categories SET name = ?, parent_id = ?, level = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', + [name, parent_id, level, id] + ); + res.json({ success: true, message: '分类更新成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.delete('/api/categories/:id', async (req, res) => { + try { + const { id } = req.params; + // 先检查是否有子分类 + const children = await db.query('SELECT id FROM categories WHERE parent_id = ?', [id]); + if (children.rows.length > 0) { + return res.status(400).json({ success: false, message: '该分类下有子分类,无法删除' }); + } + // 再检查是否有商品使用该分类 + const products = await db.query('SELECT id FROM products WHERE category_id = ?', [id]); + if (products.rows.length > 0) { + return res.status(400).json({ success: false, message: '该分类下有商品,无法删除' }); + } + await db.query('DELETE FROM categories WHERE id = ?', [id]); + res.json({ success: true, message: '分类删除成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 汇率API ==================== +app.get('/api/exchange-rates/latest', async (req, res) => { + try { + const result = await db.query('SELECT * FROM exchange_rates WHERE is_active = true ORDER BY effective_date DESC LIMIT 10'); + const rates = {}; + result.rows.forEach(row => { + rates[row.from_currency + '_' + row.to_currency] = row.rate; + }); + res.json({ success: true, data: rates }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.post('/api/exchange-rates', async (req, res) => { + try { + const { pair_key, rate, effective_date } = req.body; + + // 解析货币对 + const [from_currency, to_currency] = pair_key.split('_'); + + // 先将所有同货币对的汇率设置为非激活 + await db.query( + 'UPDATE exchange_rates SET is_active = 0 WHERE from_currency = ? AND to_currency = ?', + [from_currency, to_currency] + ); + + // 插入新的汇率记录 + await db.query( + 'INSERT INTO exchange_rates (from_currency, to_currency, rate, effective_date, is_active) VALUES (?, ?, ?, ?, ?)', + [from_currency, to_currency, rate, effective_date, 1] + ); + + res.json({ success: true, message: '汇率保存成功' }); + } catch (error) { + console.error('保存汇率失败:', error); + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.get('/api/exchange-rates/history', async (req, res) => { + try { + const limit = req.query.limit || 20; + const result = await db.query( + 'SELECT * FROM exchange_rates ORDER BY created_at DESC LIMIT ?', + [limit] + ); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// 启动服务器 +app.listen(PORT, '0.0.0.0', () => { + console.log(`轻远电力老挝ERP API运行在端口 ${PORT}`); +}); + +// ==================== 项目详情 ==================== +app.get('/api/projects/:id', async (req, res) => { + try { + const { id } = req.params; + const result = await db.query('SELECT * FROM projects WHERE id = ?', [id]); + if (result.rows.length === 0) { + return res.status(404).json({ success: false, message: '项目不存在' }); + } + res.json({ success: true, data: result.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 更新项目 ==================== +app.put('/api/projects/:id', [ + param('id').notEmpty() +], validate, async (req, res) => { + try { + const { id } = req.params; + const { name, manager_id, location, start_date, end_date, description, status, contract_amount } = req.body; + // SQLite不支持COALESCE和NOW()的组合使用,需要使用不同的方法 + await db.query( + 'UPDATE projects SET name = CASE WHEN ? IS NOT NULL THEN ? ELSE name END, manager_id = CASE WHEN ? IS NOT NULL THEN ? ELSE manager_id END, location = CASE WHEN ? IS NOT NULL THEN ? ELSE location END, start_date = CASE WHEN ? IS NOT NULL THEN ? ELSE start_date END, end_date = CASE WHEN ? IS NOT NULL THEN ? ELSE end_date END, description = CASE WHEN ? IS NOT NULL THEN ? ELSE description END, status = CASE WHEN ? IS NOT NULL THEN ? ELSE status END, contract_amount = CASE WHEN ? IS NOT NULL THEN ? ELSE contract_amount END, updated_at = CURRENT_TIMESTAMP WHERE id = ?', + [name, name, manager_id, manager_id, location, location, start_date, start_date, end_date, end_date, description, description, status, status, contract_amount, contract_amount, id] + ); + // 查询更新后的数据 + const updatedResult = await db.query('SELECT * FROM projects WHERE id = ?', [id]); + res.json({ success: true, data: updatedResult.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 删除项目 ==================== +app.delete('/api/projects/:id', async (req, res) => { + try { + const { id } = req.params; + await db.query('DELETE FROM projects WHERE id = ?', [id]); + res.json({ success: true, message: '项目已删除' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 预算项目管理API ==================== +app.post('/api/budget-projects', async (req, res) => { + try { + const { name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos } = req.body; + await db.query( + `INSERT INTO budget_projects (name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos, status, days_in_status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'negotiating', 0)`, + [name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos] + ); + res.json({ success: true, message: '预算项目创建成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.put('/api/budget-projects/:id/sign', async (req, res) => { + try { + const { id } = req.params; + await db.query('UPDATE budget_projects SET status = ? WHERE id = ?', ['signed', id]); + res.json({ success: true, message: '标记签约成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.put('/api/budget-projects/:id/unsigned', async (req, res) => { + try { + const { id } = req.params; + await db.query('UPDATE budget_projects SET status = ? WHERE id = ?', ['unsigned', id]); + res.json({ success: true, message: '标记未签约成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 报价管理API ==================== +app.post('/api/budget-projects/:projectId/quotations', async (req, res) => { + try { + const { projectId } = req.params; + const { version, quotation_date, amount, currency, status, file_url, remark } = req.body; + await db.query( + `INSERT INTO quotations (project_id, version, quotation_date, amount, currency, status, file_url, remark) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [projectId, version, quotation_date, amount, currency, status, file_url, remark] + ); + res.json({ success: true, message: '报价版本创建成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.delete('/api/budget-projects/:projectId/quotations/:quotationId', async (req, res) => { + try { + const { quotationId } = req.params; + await db.query('DELETE FROM quotations WHERE id = ?', [quotationId]); + res.json({ success: true, message: '报价版本删除成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 项目合同API ==================== +app.post('/api/projects/:projectId/contracts', async (req, res) => { + try { + const { projectId } = req.params; + const { contract_code, contract_amount, currency, settlement_method, contract_period, start_date, end_date, warranty_deposit_percentage, warranty_period, contract_file } = req.body; + await db.query( + `INSERT INTO project_contracts (project_id, contract_code, contract_amount, currency, settlement_method, contract_period, start_date, end_date, warranty_deposit_percentage, warranty_period, contract_file) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [projectId, contract_code, contract_amount, currency, settlement_method, contract_period, start_date, end_date, warranty_deposit_percentage, warranty_period, contract_file] + ); + res.json({ success: true, message: '合同创建成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.get('/api/projects/:projectId/contracts', async (req, res) => { + try { + const { projectId } = req.params; + const result = await db.query('SELECT * FROM project_contracts WHERE project_id = ?', [projectId]); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 分包管理API ==================== +app.post('/api/projects/:projectId/subcontracts', async (req, res) => { + try { + const { projectId } = req.params; + const { subcontractor_name, contract_amount, start_date, end_date, contract_file } = req.body; + await db.query( + `INSERT INTO subcontracts (project_id, subcontractor_name, contract_amount, start_date, end_date, contract_file) + VALUES (?, ?, ?, ?, ?, ?)`, + [projectId, subcontractor_name, contract_amount, start_date, end_date, contract_file] + ); + res.json({ success: true, message: '分包合同创建成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.get('/api/projects/:projectId/subcontracts', async (req, res) => { + try { + const { projectId } = req.params; + const result = await db.query('SELECT * FROM subcontracts WHERE project_id = ?', [projectId]); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 材料管理API ==================== +app.post('/api/projects/:projectId/materials', async (req, res) => { + try { + const { projectId } = req.params; + const { product_id, product_name, unit, budget_quantity, purchase_quantity, used_quantity, average_price, total_amount } = req.body; + await db.query( + `INSERT INTO project_materials (project_id, product_id, product_name, unit, budget_quantity, purchase_quantity, used_quantity, average_price, total_amount) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [projectId, product_id, product_name, unit, budget_quantity, purchase_quantity, used_quantity, average_price, total_amount] + ); + res.json({ success: true, message: '材料添加成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.get('/api/projects/:projectId/materials', async (req, res) => { + try { + const { projectId } = req.params; + const result = await db.query('SELECT * FROM project_materials WHERE project_id = ?', [projectId]); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 施工节点API ==================== +app.post('/api/projects/:projectId/milestones', async (req, res) => { + try { + const { projectId } = req.params; + const { milestone_name, percentage, amount, completion_progress, status, expected_date, voucher } = req.body; + await db.query( + `INSERT INTO project_milestones (project_id, milestone_name, percentage, amount, completion_progress, status, expected_date, voucher) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [projectId, milestone_name, percentage, amount, completion_progress, status, expected_date, voucher] + ); + res.json({ success: true, message: '节点添加成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.get('/api/projects/:projectId/milestones', async (req, res) => { + try { + const { projectId } = req.params; + const result = await db.query('SELECT * FROM project_milestones WHERE project_id = ?', [projectId]); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 财务信息API ==================== +app.post('/api/projects/:projectId/finances', async (req, res) => { + try { + const { projectId } = req.params; + const { payment_type, amount, currency, payment_date, status, description } = req.body; + await db.query( + `INSERT INTO project_finances (project_id, payment_type, amount, currency, payment_date, status, description) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + [projectId, payment_type, amount, currency, payment_date, status, description] + ); + res.json({ success: true, message: '财务记录添加成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.get('/api/projects/:projectId/finances', async (req, res) => { + try { + const { projectId } = req.params; + const result = await db.query('SELECT * FROM project_finances WHERE project_id = ?', [projectId]); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 质保金API ==================== +app.post('/api/projects/:projectId/warranty-deposits', async (req, res) => { + try { + const { projectId } = req.params; + const { amount, currency, warranty_period, start_date, end_date } = req.body; + await db.query( + `INSERT INTO warranty_deposits (project_id, amount, currency, warranty_period, start_date, end_date) + VALUES (?, ?, ?, ?, ?, ?)`, + [projectId, amount, currency, warranty_period, start_date, end_date] + ); + res.json({ success: true, message: '质保金设置成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.get('/api/projects/:projectId/warranty-deposits', async (req, res) => { + try { + const { projectId } = req.params; + const result = await db.query('SELECT * FROM warranty_deposits WHERE project_id = ?', [projectId]); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 施工日志API ==================== +app.post('/api/projects/:projectId/construction-logs', async (req, res) => { + try { + const { projectId } = req.params; + const { log_date, weather, work_content, photos } = req.body; + await db.query( + `INSERT INTO construction_logs (project_id, log_date, weather, work_content, photos) + VALUES (?, ?, ?, ?, ?)`, + [projectId, log_date, weather, work_content, photos] + ); + res.json({ success: true, message: '施工日志添加成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.get('/api/projects/:projectId/construction-logs', async (req, res) => { + try { + const { projectId } = req.params; + const result = await db.query('SELECT * FROM construction_logs WHERE project_id = ? ORDER BY log_date DESC', [projectId]); + res.json({ success: true, data: result.rows }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +app.delete('/api/construction-logs/:logId', async (req, res) => { + try { + const { logId } = req.params; + await db.query('DELETE FROM construction_logs WHERE id = ?', [logId]); + res.json({ success: true, message: '施工日志删除成功' }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 创建预支申请 ==================== +app.post('/api/advances', [ + body('amount').isFloat({ min: 0.01 }), + body('reason').notEmpty() +], validate, async (req, res) => { + try { + const { amount, reason, project_id } = req.body; + const user_id = 1; // 临时使用admin用户 + const result = await db.query( + 'INSERT INTO advances (user_id, project_id, amount, reason, status) VALUES (?, ?, ?, ?, ?)', + [user_id, project_id, amount, reason, 'pending'] + ); + // SQLite不支持RETURNING,所以需要查询刚插入的数据 + const lastInsert = await db.query('SELECT * FROM advances ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 创建报销申请 ==================== +app.post('/api/reimbursements', [ + body('amount').isFloat({ min: 0.01 }), + body('type').notEmpty() +], validate, async (req, res) => { + try { + const { amount, type, description, project_id } = req.body; + const user_id = 1; // 临时使用admin用户 + const result = await db.query( + 'INSERT INTO reimbursements (user_id, project_id, amount, type, description, status) VALUES (?, ?, ?, ?, ?, ?)', + [user_id, project_id, amount, type, description, 'pending'] + ); + // SQLite不支持RETURNING,所以需要查询刚插入的数据 + const lastInsert = await db.query('SELECT * FROM reimbursements ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + res.status(500).json({ success: false, message: error.message }); + } +}); + +console.log('轻远电力老挝ERP API服务已启动 - 完整版'); diff --git a/backup_20260327/company-finance-system/backend/check-db-status.js b/backup_20260327/company-finance-system/backend/check-db-status.js new file mode 100644 index 0000000..9876fba --- /dev/null +++ b/backup_20260327/company-finance-system/backend/check-db-status.js @@ -0,0 +1,240 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); + +const dbPath = path.join(__dirname, 'company_finance.db'); +const db = new sqlite3.Database(dbPath); + +console.log('检查数据库状态...'); + +db.all("SELECT name FROM sqlite_master WHERE type='table'", (err, rows) => { + if (err) { + console.error('查询失败:', err.message); + } else { + console.log('当前表:'); + rows.forEach(row => console.log('-', row.name)); + } + + // 检查category_tree表 + db.get("SELECT name FROM sqlite_master WHERE type='table' AND name='category_tree'", (err, row) => { + if (row) { + console.log('\ncategory_tree表已存在,删除后重新创建...'); + db.run('DROP TABLE IF EXISTS category_tree', (err) => { + if (err) console.error('删除category_tree失败:', err.message); + recreateTables(); + }); + } else { + recreateTables(); + } + }); +}); + +function recreateTables() { + console.log('\n开始创建表结构...'); + + // 创建category_tree表 + const createCategoryTree = ` + CREATE TABLE category_tree ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_id INTEGER DEFAULT NULL, + level INTEGER DEFAULT 1, + sort_order INTEGER DEFAULT 0, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (parent_id) REFERENCES category_tree(id) ON DELETE CASCADE + ) + `; + + db.run(createCategoryTree, (err) => { + if (err) { + console.error('创建category_tree失败:', err.message); + return; + } + console.log('✓ category_tree表创建成功'); + + // 创建索引 + db.run('CREATE INDEX IF NOT EXISTS idx_category_parent ON category_tree(parent_id)'); + db.run('CREATE INDEX IF NOT EXISTS idx_category_level ON category_tree(level)'); + + // 检查products表 + db.get("SELECT name FROM sqlite_master WHERE type='table' AND name='products'", (err, row) => { + if (row) { + console.log('\nproducts表已存在,重建...'); + db.run('DROP TABLE IF EXISTS products_backup', (err) => { + db.run('ALTER TABLE products RENAME TO products_backup', (err) => { + createProductsTable(); + }); + }); + } else { + createProductsTable(); + } + }); + }); +} + +function createProductsTable() { + const createProducts = ` + CREATE TABLE products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + model TEXT, + category_id INTEGER, + category_name TEXT, + unit TEXT DEFAULT '件', + cost_price REAL, + price REAL DEFAULT 0, + brand TEXT, + specification TEXT, + source TEXT DEFAULT '老挝', + remark TEXT, + stock_quantity REAL DEFAULT 0, + stock_warning REAL DEFAULT 0, + status TEXT DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (category_id) REFERENCES category_tree(id) + ) + `; + + db.run(createProducts, (err) => { + if (err) { + console.error('创建products失败:', err.message); + return; + } + console.log('✓ products表创建成功'); + + // 创建索引 + db.run('CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id)'); + db.run('CREATE INDEX IF NOT EXISTS idx_products_name ON products(name)'); + db.run('CREATE INDEX IF NOT EXISTS idx_products_status ON products(status)'); + + // 插入默认分类数据 + insertDefaultCategories(); + }); +} + +function insertDefaultCategories() { + console.log('\n插入默认分类数据...'); + + // 一级分类 + const level1Categories = [ + ['电杆横担', null, 1, 1, '电杆、横担及相关配件'], + ['电缆电线', null, 1, 2, '各类电缆、电线产品'], + ['变压器', null, 1, 3, '变压器及相关设备'], + ['开关设备', null, 1, 4, '开关、断路器等设备'], + ['金具', null, 1, 5, '电力金具、连接件'], + ['工具仪器', null, 1, 6, '施工工具、检测仪器'], + ['劳保用品', null, 1, 7, '安全防护用品'], + ['其他材料', null, 1, 99, '其他未分类材料'] + ]; + + let insertedCount = 0; + level1Categories.forEach((cat, index) => { + db.run( + 'INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, ?, ?, ?, ?)', + cat, + function(err) { + if (err) console.error('插入一级分类失败:', err.message); + insertedCount++; + if (insertedCount === level1Categories.length) { + insertLevel2Categories(); + } + } + ); + }); +} + +function insertLevel2Categories() { + // 先获取一级分类的ID + db.all('SELECT id, name FROM category_tree WHERE level = 1', (err, level1Cats) => { + if (err) { + console.error('查询一级分类失败:', err.message); + finish(); + return; + } + + const level2Map = { + '电杆横担': [ + ['混凝土电杆', 1, '混凝土材质电杆'], + ['钢管电杆', 2, '钢管材质电杆'], + ['横担', 3, '各类横担'], + ['抱箍', 4, '电杆抱箍'] + ], + '电缆电线': [ + ['高压电缆', 1, '高压电力电缆'], + ['低压电缆', 2, '低压电力电缆'], + ['架空导线', 3, '架空绝缘导线'], + ['控制电缆', 4, '控制用电缆'] + ], + '变压器': [ + ['配电变压器', 1, '配电用变压器'], + ['箱式变电站', 2, '箱式变电站'] + ], + '开关设备': [ + ['断路器', 1, '各类断路器'], + ['隔离开关', 2, '隔离开关'], + ['熔断器', 3, '熔断器'] + ], + '金具': [ + ['耐张线夹', 1, '耐张线夹'], + ['悬垂线夹', 2, '悬垂线夹'], + ['连接金具', 3, '连接金具'] + ], + '工具仪器': [ + ['施工工具', 1, '电力施工工具'], + ['检测仪器', 2, '检测测试仪器'] + ], + '劳保用品': [ + ['安全帽', 1, '安全帽'], + ['安全带', 2, '安全带'], + ['绝缘手套', 3, '绝缘手套'], + ['绝缘鞋', 4, '绝缘鞋'] + ], + '其他材料': [ + ['标识标牌', 1, '标识标牌'], + ['接地材料', 2, '接地装置材料'], + ['其他', 99, '其他未分类'] + ] + }; + + let totalToInsert = 0; + let insertedCount = 0; + + level1Cats.forEach(level1 => { + const level2Items = level2Map[level1.name] || []; + totalToInsert += level2Items.length; + + level2Items.forEach(item => { + db.run( + 'INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, ?, 2, ?, ?)', + [item[0], level1.id, item[1], item[2]], + function(err) { + if (err) console.error(`插入二级分类${item[0]}失败:`, err.message); + insertedCount++; + if (insertedCount === totalToInsert) { + finish(); + } + } + ); + }); + }); + + if (totalToInsert === 0) finish(); + }); +} + +function finish() { + console.log('\n✓ 数据库迁移完成!'); + + db.all('SELECT * FROM category_tree ORDER BY level, sort_order', (err, rows) => { + if (!err) { + console.log(`\n已创建 ${rows.length} 个分类:`); + rows.forEach(row => { + const indent = ' '.repeat(row.level - 1); + console.log(`${indent}${row.name}`); + }); + } + db.close(); + }); +} diff --git a/backup_20260327/company-finance-system/backend/check-db.js b/backup_20260327/company-finance-system/backend/check-db.js new file mode 100644 index 0000000..7504710 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/check-db.js @@ -0,0 +1,24 @@ +const db = require('./db-sqlite'); + +async function checkDatabase() { + try { + // 查询advances表 + const advancesResult = await db.query('SELECT * FROM advances'); + console.log('Advances data:', advancesResult.rows); + + // 查询reimbursements表 + const reimbursementsResult = await db.query('SELECT * FROM reimbursements'); + console.log('Reimbursements data:', reimbursementsResult.rows); + + // 查询users表 + const usersResult = await db.query('SELECT * FROM users'); + console.log('Users data:', usersResult.rows); + + process.exit(0); + } catch (error) { + console.error('Error querying database:', error); + process.exit(1); + } +} + +checkDatabase(); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/check-projects.js b/backup_20260327/company-finance-system/backend/check-projects.js new file mode 100644 index 0000000..3426cfb --- /dev/null +++ b/backup_20260327/company-finance-system/backend/check-projects.js @@ -0,0 +1,38 @@ +const db = require('./db-sqlite'); + +async function checkProjects() { + try { + console.log('检查项目数据...'); + + // 检查项目表 + const projectsResult = await db.query('SELECT * FROM projects'); + console.log('项目列表:'); + projectsResult.rows.forEach(project => { + console.log(`ID: ${project.id}, 名称: ${project.name}, 代码: ${project.code}, 状态: ${project.status}`); + }); + + // 检查预算项目表 + const budgetProjectsResult = await db.query('SELECT * FROM budget_projects'); + console.log('\n预算项目列表:'); + budgetProjectsResult.rows.forEach(project => { + console.log(`ID: ${project.id}, 名称: ${project.name}, 状态: ${project.status}`); + }); + + // 检查合同表 + const contractsResult = await db.query('SELECT * FROM project_contracts'); + console.log('\n合同列表:'); + contractsResult.rows.forEach(contract => { + console.log(`ID: ${contract.id}, 项目ID: ${contract.project_id}, 合同编号: ${contract.contract_code}`); + }); + + console.log('\n✅ 检查完成!'); + process.exit(0); + + } catch (error) { + console.error('检查失败:', error); + process.exit(1); + } +} + +// 执行检查 +checkProjects(); diff --git a/backup_20260327/company-finance-system/backend/check-schema.js b/backup_20260327/company-finance-system/backend/check-schema.js new file mode 100644 index 0000000..13c61ca --- /dev/null +++ b/backup_20260327/company-finance-system/backend/check-schema.js @@ -0,0 +1,26 @@ +const db = require('./db-sqlite'); + +// 检查customers表结构 +db.query('PRAGMA table_info(customers)').then(result => { + console.log('customers表结构:', result.rows); + + // 检查suppliers表结构 + return db.query('PRAGMA table_info(suppliers)'); +}).then(result => { + console.log('suppliers表结构:', result.rows); + + // 检查subcontractors表结构 + return db.query('PRAGMA table_info(subcontractors)'); +}).then(result => { + console.log('subcontractors表结构:', result.rows); + + // 检查projects表结构 + return db.query('PRAGMA table_info(projects)'); +}).then(result => { + console.log('projects表结构:', result.rows); + + process.exit(0); +}).catch(error => { + console.error('查询失败:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/check-tables.js b/backup_20260327/company-finance-system/backend/check-tables.js new file mode 100644 index 0000000..b2f8926 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/check-tables.js @@ -0,0 +1,35 @@ +const sqlite3 = require('sqlite3').verbose(); +const db = new sqlite3.Database('company_finance.db'); + +db.all("SELECT name FROM sqlite_master WHERE type='table'", (err, rows) => { + if (err) { + console.error('查询失败:', err); + db.close(); + return; + } + + console.log('数据库中的表:'); + console.log('================'); + rows.forEach((row, index) => { + console.log(`${index + 1}. ${row.name}`); + }); + + // 检查是否有商品相关表 + const productTables = rows.filter(r => + r.name.includes('product') || + r.name.includes('category') || + r.name.includes('goods') + ); + + console.log('\n商品相关表:'); + console.log('================'); + if (productTables.length === 0) { + console.log('没有找到商品相关表'); + } else { + productTables.forEach((row, index) => { + console.log(`${index + 1}. ${row.name}`); + }); + } + + db.close(); +}); diff --git a/backup_20260327/company-finance-system/backend/clear-finance-data.js b/backup_20260327/company-finance-system/backend/clear-finance-data.js new file mode 100644 index 0000000..c39bdc7 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/clear-finance-data.js @@ -0,0 +1,96 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); + +// 创建SQLite数据库连接 +const dbPath = path.join(__dirname, 'company_finance.db'); +const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + console.error('数据库连接失败:', err.message); + } else { + console.log('SQLite数据库连接成功'); + clearFinanceData(); + } +}); + +// 清空财务数据 +async function clearFinanceData() { + try { + // 开始事务 + await new Promise((resolve, reject) => { + db.run('BEGIN TRANSACTION', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + // 清空执行记录 + await new Promise((resolve, reject) => { + db.run('DELETE FROM executions', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + console.log('执行记录已清空'); + + // 清空核销申请 + await new Promise((resolve, reject) => { + db.run('DELETE FROM verifications', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + console.log('核销申请已清空'); + + // 清空报销申请 + await new Promise((resolve, reject) => { + db.run('DELETE FROM reimbursements', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + console.log('报销申请已清空'); + + // 清空付款申请 + await new Promise((resolve, reject) => { + db.run('DELETE FROM payment_requests', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + console.log('付款申请已清空'); + + // 清空预支申请 + await new Promise((resolve, reject) => { + db.run('DELETE FROM advances', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + console.log('预支申请已清空'); + + // 提交事务 + await new Promise((resolve, reject) => { + db.run('COMMIT', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + console.log('所有财务数据已成功清空'); + } catch (error) { + // 回滚事务 + await new Promise((resolve) => { + db.run('ROLLBACK', resolve); + }); + console.error('清空财务数据失败:', error.message); + } finally { + // 关闭数据库连接 + db.close((err) => { + if (err) { + console.error('数据库连接关闭失败:', err.message); + } else { + console.log('数据库连接已关闭'); + } + }); + } +} diff --git a/backup_20260327/company-finance-system/backend/clear-other-data.js b/backup_20260327/company-finance-system/backend/clear-other-data.js new file mode 100644 index 0000000..b30c9b4 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/clear-other-data.js @@ -0,0 +1,71 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); + +// 数据库文件路径 +const dbPath = path.join(__dirname, 'company_finance.db'); + +// 连接数据库 +const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + console.error('数据库连接失败:', err.message); + return; + } + console.log('SQLite数据库连接成功'); + clearData(); +}); + +// 清除数据的函数 +function clearData() { + console.log('开始清除除合作伙伴、商品分类和商品外的所有数据...'); + + // 需要保留的表 + const tablesToKeep = ['suppliers', 'product_categories', 'products']; + + // 需要清除的表(根据常见的ERP系统表结构) + const tablesToClear = [ + 'purchase_requests', + 'purchase_request_items', + 'inventory_records', + 'payment_requests', + 'expense_claims', + 'expense_claim_details', + 'projects', + 'customers', + 'subcontractors', + 'quotations', + 'quotation_items', + 'contracts', + 'payment_terms', + 'advances', + 'reimbursements', + 'financial_records', + 'financial_transactions', + 'vouchers', + 'exchange_rates', + 'users', + 'roles', + 'permissions' + ]; + + // 执行清除操作 + let completed = 0; + const total = tablesToClear.length; + + tablesToClear.forEach(table => { + db.run(`DELETE FROM ${table}`, (err) => { + if (err) { + console.warn(`清除${table}表数据失败:`, err.message); + } else { + console.log(`✓ 已清除${table}表数据`); + } + + completed++; + if (completed === total) { + console.log('\n数据清除完成!'); + console.log('已保留以下表的数据:'); + tablesToKeep.forEach(table => console.log(`- ${table}`)); + db.close(); + } + }); + }); +} diff --git a/backup_20260327/company-finance-system/backend/clear-projects.js b/backup_20260327/company-finance-system/backend/clear-projects.js new file mode 100644 index 0000000..382469d --- /dev/null +++ b/backup_20260327/company-finance-system/backend/clear-projects.js @@ -0,0 +1,39 @@ +const db = require('./db-sqlite'); + +async function clearAllData() { + try { + console.log('开始删除所有测试数据...'); + + // 先删除关联表数据 + await db.query('DELETE FROM project_materials'); + await db.query('DELETE FROM project_milestones'); + await db.query('DELETE FROM project_finances'); + await db.query('DELETE FROM warranty_deposits'); + await db.query('DELETE FROM project_contracts'); + await db.query('DELETE FROM subcontracts'); + await db.query('DELETE FROM construction_logs'); + await db.query('DELETE FROM budget_quotations'); + await db.query('DELETE FROM budget_projects'); + await db.query('DELETE FROM projects'); + await db.query('DELETE FROM contacts'); + await db.query('DELETE FROM suppliers'); + await db.query('DELETE FROM subcontractors'); + await db.query('DELETE FROM customers'); + await db.query('DELETE FROM products'); + await db.query('DELETE FROM categories'); + await db.query('DELETE FROM exchange_rates'); + + // 保留用户数据,因为需要登录 + // await db.query('DELETE FROM users'); + + console.log('所有测试数据删除成功!'); + console.log('现在您可以使用真实数据进行全流程测试。'); + } catch (error) { + console.error('删除数据失败:', error); + } finally { + // 关闭数据库连接 + db.close(); + } +} + +clearAllData(); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/company_finance_20260325_010001.db.backup b/backup_20260327/company-finance-system/backend/company_finance_20260325_010001.db.backup new file mode 100644 index 0000000..df7fcc5 Binary files /dev/null and b/backup_20260327/company-finance-system/backend/company_finance_20260325_010001.db.backup differ diff --git a/backup_20260327/company-finance-system/backend/company_finance_20260325_012124.db.backup b/backup_20260327/company-finance-system/backend/company_finance_20260325_012124.db.backup new file mode 100644 index 0000000..66ef818 Binary files /dev/null and b/backup_20260327/company-finance-system/backend/company_finance_20260325_012124.db.backup differ diff --git a/backup_20260327/company-finance-system/backend/cos-upload.js b/backup_20260327/company-finance-system/backend/cos-upload.js new file mode 100644 index 0000000..cea992f --- /dev/null +++ b/backup_20260327/company-finance-system/backend/cos-upload.js @@ -0,0 +1,33 @@ +// COS配置 - 香港区域 +const COS = require('cos-nodejs-sdk-v5'); + +const cosConfig = { + SecretId: process.env.TENCENT_SECRET_ID || '', + SecretKey: process.env.TENCENT_SECRET_KEY || '', + Bucket: 'qingyuan-erp-files-1257307187', + Region: 'ap-hongkong' +}; + +const cos = new COS(cosConfig); +const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']; + +// 上传到COS +function uploadToCOS(buffer, filename, mimetype) { + return new Promise((resolve, reject) => { + cos.putObject({ + Bucket: cosConfig.Bucket, + Region: cosConfig.Region, + Key: filename, + Body: buffer, + ContentType: mimetype + }, (err, data) => { + if (err) reject(err); + else { + const url = 'https://' + cosConfig.Bucket + '.cos.' + cosConfig.Region + '.myqcloud.com/' + filename; + resolve({ url, filename }); + } + }); + }); +} + +module.exports = { cos, cosConfig, uploadToCOS, imageFormats }; diff --git a/backup_20260327/company-finance-system/backend/create-executions-table.js b/backup_20260327/company-finance-system/backend/create-executions-table.js new file mode 100644 index 0000000..88990e4 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/create-executions-table.js @@ -0,0 +1,47 @@ +const sqlite3 = require('sqlite3').verbose(); +const db = new sqlite3.Database('company_finance.db'); + +// 创建执行记录表 +db.serialize(() => { + console.log('开始创建执行记录表...'); + + db.run(` + CREATE TABLE IF NOT EXISTS executions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + apply_id INTEGER NOT NULL, + apply_type TEXT NOT NULL, + action TEXT NOT NULL, + execute_method TEXT, + voucher_no TEXT, + remark TEXT, + reject_reason TEXT, + operator TEXT NOT NULL, + operator_role TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建执行记录表失败:', err.message); + } else { + console.log('执行记录表创建成功'); + } + }); + + // 创建索引 + db.run(`CREATE INDEX IF NOT EXISTS idx_executions_apply ON executions(apply_id, apply_type)`, (err) => { + if (err) { + console.error('创建索引失败:', err.message); + } else { + console.log('索引创建成功'); + } + }); + + // 关闭数据库连接 + db.close((err) => { + if (err) { + console.error('关闭数据库失败:', err.message); + } else { + console.log('数据库连接已关闭'); + } + }); +}); diff --git a/backup_20260327/company-finance-system/backend/create-tables.js b/backup_20260327/company-finance-system/backend/create-tables.js new file mode 100644 index 0000000..2bb537c --- /dev/null +++ b/backup_20260327/company-finance-system/backend/create-tables.js @@ -0,0 +1,63 @@ +const db = require('./db-sqlite'); + +async function createMissingTables() { + console.log('开始创建缺失的表...'); + + try { + // 创建付款申请表 + console.log('1. 创建付款申请表'); + await db.query(` + CREATE TABLE IF NOT EXISTS payment_requests ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_code TEXT UNIQUE NOT NULL, + applicant TEXT NOT NULL, + payee TEXT NOT NULL, + bank_account TEXT NOT NULL, + bank_name TEXT NOT NULL, + amount REAL NOT NULL, + amount_cny REAL DEFAULT 0, + currency TEXT DEFAULT 'CNY', + payment_date DATE NOT NULL, + reason TEXT NOT NULL, + detail_items TEXT, + attachments TEXT, + status TEXT DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `); + console.log('✓ 付款申请表创建成功'); + + // 创建核销申请表 + console.log('2. 创建核销申请表'); + await db.query(` + CREATE TABLE IF NOT EXISTS verifications ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + verification_code TEXT UNIQUE NOT NULL, + applicant TEXT NOT NULL, + advance_code TEXT NOT NULL, + advance_amount REAL NOT NULL, + amount REAL NOT NULL, + amount_cny REAL DEFAULT 0, + currency TEXT DEFAULT 'CNY', + verification_date DATE NOT NULL, + reason TEXT NOT NULL, + detail_items TEXT, + attachments TEXT, + status TEXT DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `); + console.log('✓ 核销申请表创建成功'); + + console.log('\n所有表创建完成!'); + } catch (error) { + console.error('✗ 创建表失败:', error.message); + } finally { + db.close(); + } +} + +// 运行创建表的函数 +createMissingTables(); diff --git a/backup_20260327/company-finance-system/backend/db-sqlite.js b/backup_20260327/company-finance-system/backend/db-sqlite.js new file mode 100644 index 0000000..19b9d2d --- /dev/null +++ b/backup_20260327/company-finance-system/backend/db-sqlite.js @@ -0,0 +1,1274 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); + +// 创建SQLite数据库连接 +const dbPath = path.join(__dirname, 'company_finance.db'); +const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + console.error('数据库连接失败:', err.message); + } else { + console.log('SQLite数据库连接成功'); + initializeDatabase(); + } +}); + +// 初始化数据库 +function initializeDatabase() { + // 创建用户表 + db.run(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + name TEXT, + role TEXT NOT NULL DEFAULT 'user', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建用户表失败:', err.message); + } else { + // 创建项目表 + db.run(` + CREATE TABLE IF NOT EXISTS projects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + code TEXT UNIQUE NOT NULL, + customer_id INTEGER, + manager_id INTEGER, + contract_amount REAL DEFAULT 0.0, + start_date DATE, + end_date DATE, + description TEXT, + status TEXT DEFAULT 'active', + location TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (manager_id) REFERENCES users(id) + ) + `, (err) => { + if (err) { + console.error('创建项目表失败:', err.message); + } else { + // 创建客户表 + db.run(` + CREATE TABLE IF NOT EXISTS customers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + contact TEXT, + position TEXT, + phone TEXT, + email TEXT, + address TEXT, + remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建客户表失败:', err.message); + } else { + // 创建供应商表 + db.run(` + CREATE TABLE IF NOT EXISTS suppliers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + contact TEXT, + position TEXT, + phone TEXT, + email TEXT, + address TEXT, + supply_category TEXT, + country TEXT, + remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建供应商表失败:', err.message); + } else { + // 创建分包商表 + db.run(` + CREATE TABLE IF NOT EXISTS subcontractors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + contact TEXT, + position TEXT, + phone TEXT, + email TEXT, + address TEXT, + scope TEXT, + features TEXT, + country TEXT, + remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建分包商表失败:', err.message); + } else { + // 创建商品表 + db.run(` + CREATE TABLE IF NOT EXISTS products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + category_id INTEGER, + unit TEXT, + price REAL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建商品表失败:', err.message); + } else { + // 创建分类表 + db.run(` + CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_id INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建分类表失败:', err.message); + } else { + // 创建预算项目表 + db.run(` + CREATE TABLE IF NOT EXISTS budget_projects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + customer_id INTEGER, + manager_id INTEGER, + location TEXT, + survey_date DATE, + intermediary TEXT, + intermediary_fee_type TEXT, + intermediary_fee_value REAL, + customer_requirements TEXT, + project_overview TEXT, + attachments TEXT, + survey_photos TEXT, + status TEXT DEFAULT 'negotiating', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (customer_id) REFERENCES customers(id), + FOREIGN KEY (manager_id) REFERENCES users(id) + ) + `, (err) => { + if (err) { + console.error('创建预算项目表失败:', err.message); + } else { + // 创建报价表 + db.run(` + CREATE TABLE IF NOT EXISTS budget_quotations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + version INTEGER, + quotation_date DATE, + amount REAL, + currency TEXT DEFAULT 'CNY', + status TEXT DEFAULT 'draft', + file_url TEXT, + remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES budget_projects(id) + ) + `, (err) => { + if (err) { + console.error('创建报价表失败:', err.message); + } else { + // 创建项目合同表 + db.run(` + CREATE TABLE IF NOT EXISTS project_contracts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + contract_code TEXT UNIQUE NOT NULL, + contract_amount REAL NOT NULL, + currency TEXT DEFAULT 'CNY', + settlement_method TEXT, + contract_period INTEGER, + start_date DATE, + end_date DATE, + warranty_deposit_percentage REAL DEFAULT 5, + warranty_period INTEGER DEFAULT 12, + contract_file TEXT, + other_info TEXT, + tax_included INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id) + ) + `, (err) => { + if (err) { + console.error('创建项目合同表失败:', err.message); + } else { + // 创建分包合同表 + db.run(` + CREATE TABLE IF NOT EXISTS subcontracts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + subcontractor_id INTEGER, + subcontractor_name TEXT, + contract_amount REAL NOT NULL, + currency TEXT DEFAULT 'CNY', + settlement_type TEXT DEFAULT 'lump_sum', + other_terms TEXT, + payment_description TEXT, + start_date DATE, + end_date DATE, + paid_amount REAL DEFAULT 0, + status TEXT DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id), + FOREIGN KEY (subcontractor_id) REFERENCES subcontractors(id) + ) + `, (err) => { + if (err) { + console.error('创建分包合同表失败:', err.message); + } else { + // 添加新列到分包合同表 + db.run(`ALTER TABLE subcontracts ADD COLUMN settlement_type TEXT DEFAULT 'lump_sum'`, (err) => { + if (err) { + // 忽略列已存在的错误 + } + }); + db.run(`ALTER TABLE subcontracts ADD COLUMN other_terms TEXT`, (err) => { + if (err) { + // 忽略列已存在的错误 + } + }); + db.run(`ALTER TABLE subcontracts ADD COLUMN payment_description TEXT`, (err) => { + if (err) { + // 忽略列已存在的错误 + } + }); + db.run(`ALTER TABLE subcontracts ADD COLUMN unit_price_items TEXT`, (err) => { + if (err) { + // 忽略列已存在的错误 + } + }); + db.run(`ALTER TABLE subcontracts ADD COLUMN work_days INTEGER`, (err) => { + if (err) { + // 忽略列已存在的错误 + } + }); + + // 创建项目材料表 + db.run(` + CREATE TABLE IF NOT EXISTS project_materials ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + product_id INTEGER, + product_name TEXT, + unit TEXT, + budget_quantity REAL, + purchase_quantity REAL, + used_quantity REAL, + average_price REAL, + total_amount REAL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id), + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `, (err) => { + if (err) { + console.error('创建项目材料表失败:', err.message); + } else { + // 创建施工节点表 + db.run(` + CREATE TABLE IF NOT EXISTS project_milestones ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + milestone_name TEXT, + condition TEXT, + percentage REAL, + amount REAL, + expected_date DATE, + actual_date DATE, + completion_progress INTEGER DEFAULT 0, + status TEXT DEFAULT 'pending', + voucher TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id) + ) + `, (err) => { + if (err) { + console.error('创建施工节点表失败:', err.message); + } else { + // 添加condition列(如果不存在) + db.run(`ALTER TABLE project_milestones ADD COLUMN condition TEXT`, (err) => { + if (err) { + // 忽略列已存在的错误 + } + }); + // 创建项目财务信息表 + db.run(` + CREATE TABLE IF NOT EXISTS project_finances ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + payment_type TEXT, + amount REAL DEFAULT 0, + currency TEXT DEFAULT 'CNY', + payment_date DATE, + status TEXT DEFAULT 'pending', + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id) + ) + `, (err) => { + if (err) { + console.error('创建项目财务信息表失败:', err.message); + } else { + // 创建质保金表 + db.run(` + CREATE TABLE IF NOT EXISTS warranty_deposits ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + amount REAL NOT NULL, + currency TEXT DEFAULT 'CNY', + warranty_period INTEGER DEFAULT 12, + start_date DATE, + end_date DATE, + status TEXT DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id) + ) + `, (err) => { + if (err) { + console.error('创建质保金表失败:', err.message); + } else { + // 创建施工日志表 + db.run(` + CREATE TABLE IF NOT EXISTS construction_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + log_date DATE, + weather TEXT, + work_content TEXT, + photos TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id) + ) + `, (err) => { + if (err) { + console.error('创建施工日志表失败:', err.message); + } else { + // 创建联系人表 + db.run(` + CREATE TABLE IF NOT EXISTS contacts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + entity_id INTEGER NOT NULL, + entity_type TEXT NOT NULL, + name TEXT NOT NULL, + position TEXT, + phone TEXT, + is_primary INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建联系人表失败:', err.message); + } else { + // 创建汇率表 + db.run(` + CREATE TABLE IF NOT EXISTS exchange_rates ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pair_key TEXT NOT NULL, + rate REAL NOT NULL, + effective_date DATE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建汇率表失败:', err.message); + } else { + // 创建预支款表 + db.run(` + CREATE TABLE IF NOT EXISTS advances ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + project_id INTEGER, + amount REAL NOT NULL, + currency TEXT DEFAULT 'CNY', + amount_cny REAL DEFAULT 0, + total_reimbursed REAL DEFAULT 0, + reason TEXT NOT NULL, + advance_date DATE, + advance_code TEXT UNIQUE NOT NULL, + status TEXT DEFAULT 'pending', + applicant TEXT, + attachments TEXT, + approval_remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (project_id) REFERENCES projects(id) + ) + `, (err) => { + if (err) { + console.error('创建预支款表失败:', err.message); + } else { + // 创建报销表 + db.run(` + CREATE TABLE IF NOT EXISTS reimbursements ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + project_id INTEGER, + amount REAL NOT NULL, + currency TEXT DEFAULT 'CNY', + amount_cny REAL DEFAULT 0, + reason TEXT NOT NULL, + reimbursement_date DATE, + reimbursement_code TEXT UNIQUE NOT NULL, + status TEXT DEFAULT 'pending', + applicant TEXT, + expense_type TEXT NOT NULL, + detail_items TEXT, + attachments TEXT, + approval_remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (project_id) REFERENCES projects(id) + ) + `, (err) => { + if (err) { + console.error('创建报销表失败:', err.message); + } else { + // 创建付款申请表 + db.run(` + CREATE TABLE IF NOT EXISTS payment_requests ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_code TEXT UNIQUE NOT NULL, + applicant TEXT NOT NULL, + payee TEXT NOT NULL, + bank_account TEXT NOT NULL, + bank_name TEXT NOT NULL, + amount REAL NOT NULL, + amount_cny REAL DEFAULT 0, + currency TEXT DEFAULT 'CNY', + payment_date DATE NOT NULL, + reason TEXT NOT NULL, + detail_items TEXT, + attachments TEXT, + status TEXT DEFAULT 'pending', + approval_remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建付款申请表失败:', err.message); + } else { + // 创建核销申请表 + db.run(` + CREATE TABLE IF NOT EXISTS verifications ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + verification_code TEXT UNIQUE NOT NULL, + applicant TEXT NOT NULL, + advance_code TEXT NOT NULL, + advance_amount REAL NOT NULL, + amount REAL NOT NULL, + amount_cny REAL DEFAULT 0, + currency TEXT DEFAULT 'CNY', + verification_date DATE NOT NULL, + reason TEXT NOT NULL, + expense_type TEXT NOT NULL, + project_id INTEGER, + settlement INTEGER DEFAULT 0, + settlement_amount REAL DEFAULT 0, + detail_items TEXT, + attachments TEXT, + status TEXT DEFAULT 'pending', + approval_remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id) + ) + `, (err) => { + if (err) { + console.error('创建核销申请表失败:', err.message); + } else { + // 添加approval_remark字段到所有表 + db.run(`ALTER TABLE advances ADD COLUMN approval_remark TEXT`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加advances表approval_remark字段失败:', err.message); + } + }); + db.run(`ALTER TABLE reimbursements ADD COLUMN approval_remark TEXT`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加reimbursements表approval_remark字段失败:', err.message); + } + }); + db.run(`ALTER TABLE payment_requests ADD COLUMN approval_remark TEXT`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加payment_requests表approval_remark字段失败:', err.message); + } + }); + db.run(`ALTER TABLE verifications ADD COLUMN approval_remark TEXT`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表approval_remark字段失败:', err.message); + } + }); + + // 添加execute_date和execute_method字段到所有申请表 + db.run(`ALTER TABLE advances ADD COLUMN execute_date DATE`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加advances表execute_date字段失败:', err.message); + } + }); + db.run(`ALTER TABLE advances ADD COLUMN execute_method TEXT`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加advances表execute_method字段失败:', err.message); + } + }); + + // 添加total_reimbursed字段到advances表 + db.run(`ALTER TABLE advances ADD COLUMN total_reimbursed REAL DEFAULT 0`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加advances表total_reimbursed字段失败:', err.message); + } + }); + + db.run(`ALTER TABLE reimbursements ADD COLUMN execute_date DATE`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加reimbursements表execute_date字段失败:', err.message); + } + }); + db.run(`ALTER TABLE reimbursements ADD COLUMN execute_method TEXT`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加reimbursements表execute_method字段失败:', err.message); + } + }); + + db.run(`ALTER TABLE payment_requests ADD COLUMN execute_date DATE`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加payment_requests表execute_date字段失败:', err.message); + } + }); + db.run(`ALTER TABLE payment_requests ADD COLUMN execute_method TEXT`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加payment_requests表execute_method字段失败:', err.message); + } + }); + + db.run(`ALTER TABLE verifications ADD COLUMN execute_date DATE`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表execute_date字段失败:', err.message); + } + }); + db.run(`ALTER TABLE verifications ADD COLUMN execute_method TEXT`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表execute_method字段失败:', err.message); + } + }); + + // 添加expense_type和project_id字段到verifications表 + db.run(`ALTER TABLE verifications ADD COLUMN expense_type TEXT DEFAULT 'company'`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表expense_type字段失败:', err.message); + } + }); + + db.run(`ALTER TABLE verifications ADD COLUMN project_id INTEGER`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表project_id字段失败:', err.message); + } + }); + + // 添加user_id字段到verifications表 + db.run(`ALTER TABLE verifications ADD COLUMN user_id INTEGER`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表user_id字段失败:', err.message); + } + }); + + // 添加advance_id字段到verifications表 + db.run(`ALTER TABLE verifications ADD COLUMN advance_id INTEGER`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表advance_id字段失败:', err.message); + } + }); + + // 添加settlement和settlement_amount字段到verifications表 + db.run(`ALTER TABLE verifications ADD COLUMN settlement INTEGER DEFAULT 0`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表settlement字段失败:', err.message); + } + }); + + db.run(`ALTER TABLE verifications ADD COLUMN settlement_amount REAL DEFAULT 0`, (err) => { + // 忽略字段已存在的错误 + if (err && !err.message.includes('duplicate column name')) { + console.error('添加verifications表settlement_amount字段失败:', err.message); + } + }); + console.log('所有表创建成功'); + // 暂时注释掉测试数据插入,以便用户使用真实数据 + // insertTestData(); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); + } + }); +} + +// 插入测试数据 +function insertTestData() { + // 检查是否已有用户数据 + db.get('SELECT COUNT(*) as count FROM users', (err, row) => { + if (err) { + console.error('查询用户数据失败:', err.message); + return; + } + + if (row.count === 0) { + // 插入测试用户(与前端界面一致) + const users = [ + ['admin', 'X123c321@', '系统管理员', 'admin'], + ['finance', 'X123c321@', '财务专员', 'finance'], + ['manager', 'X123c321@', '项目经理', 'manager'], + ['employee', 'X123c321@', '普通员工', 'employee'] + ]; + + users.forEach(user => { + db.run( + 'INSERT INTO users (username, password, name, role) VALUES (?, ?, ?, ?)', + user, + (err) => { + if (err) { + console.error('插入用户数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试客户数据 + db.get('SELECT COUNT(*) as count FROM customers', (err, row) => { + if (err) { + console.error('查询客户数据失败:', err.message); + return; + } + + if (row.count === 0) { + const customers = [ + ['老挝电力公司', '张三', '13800138001', 'zhangsan@example.com', '老挝万象市'], + ['泰国能源集团', '李四', '13900139001', 'lisi@example.com', '泰国曼谷市'], + ['越南电力局', '王五', '13700137001', 'wangwu@example.com', '越南河内市'] + ]; + + customers.forEach(customer => { + db.run( + 'INSERT INTO customers (name, contact, phone, email, address) VALUES (?, ?, ?, ?, ?)', + customer, + (err) => { + if (err) { + console.error('插入客户数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试供应商数据 + db.get('SELECT COUNT(*) as count FROM suppliers', (err, row) => { + if (err) { + console.error('查询供应商数据失败:', err.message); + return; + } + + if (row.count === 0) { + const suppliers = [ + ['中国电力设备有限公司', '赵六', '13600136001', 'zhaoliu@example.com', '中国北京市'], + ['东南亚建材贸易公司', '孙七', '13500135001', 'sunqi@example.com', '泰国曼谷市'], + ['老挝本地供应商', '周八', '13400134001', 'zhouba@example.com', '老挝万象市'] + ]; + + suppliers.forEach(supplier => { + db.run( + 'INSERT INTO suppliers (name, contact, phone, email, address) VALUES (?, ?, ?, ?, ?)', + supplier, + (err) => { + if (err) { + console.error('插入供应商数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试分包商数据 + db.get('SELECT COUNT(*) as count FROM subcontractors', (err, row) => { + if (err) { + console.error('查询分包商数据失败:', err.message); + return; + } + + if (row.count === 0) { + const subcontractors = [ + ['老挝施工队A', '吴九', '13300133001', 'wujing@example.com', '老挝万象市'], + ['泰国施工队B', '郑十', '13200132001', 'zhengshi@example.com', '泰国清迈市'], + ['越南施工队C', '王十一', '13100131001', 'wangshiyi@example.com', '越南河内市'] + ]; + + subcontractors.forEach(subcontractor => { + db.run( + 'INSERT INTO subcontractors (name, contact, phone, email, address) VALUES (?, ?, ?, ?, ?)', + subcontractor, + (err) => { + if (err) { + console.error('插入分包商数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试分类数据 + db.get('SELECT COUNT(*) as count FROM categories', (err, row) => { + if (err) { + console.error('查询分类数据失败:', err.message); + return; + } + + if (row.count === 0) { + const categories = [ + ['电线电缆', null], + ['高压绝缘线', 1], + ['低压电缆', 1], + ['钢绞线', 1], + ['绝缘子', null], + ['陶瓷绝缘子', 5], + ['复合绝缘子', 5], + ['金具', null], + ['线夹', 8], + ['间隔棒', 8] + ]; + + categories.forEach(category => { + db.run( + 'INSERT INTO categories (name, parent_id) VALUES (?, ?)', + category, + (err) => { + if (err) { + console.error('插入分类数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试商品数据 + db.get('SELECT COUNT(*) as count FROM products', (err, row) => { + if (err) { + console.error('查询商品数据失败:', err.message); + return; + } + + if (row.count === 0) { + const products = [ + ['JKLYJ-35-22kV', 2, '米', 15.5, '高压绝缘线'], + ['JKLYJ-50-22kV', 2, '米', 18.8, '高压绝缘线'], + ['VV-3x25+1x16', 3, '米', 22.5, '低压电缆'], + ['GJ-35', 4, '米', 8.2, '钢绞线'], + ['XP-70', 6, '个', 25.0, '陶瓷绝缘子'], + ['FXBW-10/70', 7, '个', 85.0, '复合绝缘子'], + ['NLL-1', 9, '个', 12.5, '线夹'], + ['JGX-35', 9, '个', 18.0, '线夹'], + ['FJB-2', 10, '个', 22.0, '间隔棒'] + ]; + + products.forEach(product => { + db.run( + 'INSERT INTO products (name, category_id, unit, price, description) VALUES (?, ?, ?, ?, ?)', + product, + (err) => { + if (err) { + console.error('插入商品数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试项目数据 + db.get('SELECT COUNT(*) as count FROM projects', (err, row) => { + if (err) { + console.error('查询项目数据失败:', err.message); + return; + } + + if (row.count === 0) { + const projects = [ + ['老挝万象市电力线路改造项目', 'PROJ-2024-001', 1, 5000000.0, '2024-01-01', '2024-06-30', '对老挝万象市的电力线路进行改造升级'], + ['泰国清迈市变电站建设项目', 'PROJ-2024-002', 2, 8000000.0, '2024-02-01', '2024-08-31', '在泰国清迈市建设一座新的变电站'], + ['越南河内市电网扩容项目', 'PROJ-2024-003', 3, 6500000.0, '2024-03-01', '2024-09-30', '对越南河内市的电网进行扩容升级'] + ]; + + projects.forEach(project => { + db.run( + 'INSERT INTO projects (name, code, customer_id, contract_amount, start_date, end_date, description) VALUES (?, ?, ?, ?, ?, ?, ?)', + project, + (err) => { + if (err) { + console.error('插入项目数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试预算项目数据 + db.get('SELECT COUNT(*) as count FROM budget_projects', (err, row) => { + if (err) { + console.error('查询预算项目数据失败:', err.message); + return; + } + + if (row.count === 0) { + const budgetProjects = [ + ['老挝琅勃拉邦电力线路项目', 1, 1, '老挝琅勃拉邦市', '2024-01-15', '张三', 'fixed', 50000.0, '需要建设10公里电力线路', '项目位于老挝琅勃拉邦市,需要建设10公里的110kV电力线路'], + ['泰国普吉岛变电站项目', 2, 2, '泰国普吉岛', '2024-02-10', '李四', 'percentage', 5.0, '需要建设一座35kV变电站', '项目位于泰国普吉岛,需要建设一座35kV变电站,满足当地旅游区的用电需求'], + ['越南胡志明市电网改造项目', 3, 3, '越南胡志明市', '2024-03-05', '王五', 'fixed', 80000.0, '需要对现有电网进行改造升级', '项目位于越南胡志明市,需要对现有10kV电网进行改造升级,提高供电可靠性'] + ]; + + budgetProjects.forEach(project => { + db.run( + 'INSERT INTO budget_projects (name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + project, + (err) => { + if (err) { + console.error('插入预算项目数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试报价数据 + db.get('SELECT COUNT(*) as count FROM budget_quotations', (err, row) => { + if (err) { + console.error('查询报价数据失败:', err.message); + return; + } + + if (row.count === 0) { + const quotations = [ + [1, 1, '2024-01-20', 4500000.0, 'CNY', 'sent', null, '初始报价'], + [1, 2, '2024-01-25', 4200000.0, 'CNY', 'approved', null, '最终报价'], + [2, 1, '2024-02-15', 7500000.0, 'CNY', 'draft', null, '初始报价'], + [3, 1, '2024-03-10', 6000000.0, 'CNY', 'sent', null, '初始报价'] + ]; + + quotations.forEach(quotation => { + db.run( + 'INSERT INTO budget_quotations (project_id, version, quotation_date, amount, currency, status, file_url, remark) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + quotation, + (err) => { + if (err) { + console.error('插入报价数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试项目合同数据 + db.get('SELECT COUNT(*) as count FROM project_contracts', (err, row) => { + if (err) { + console.error('查询项目合同数据失败:', err.message); + return; + } + + if (row.count === 0) { + const contracts = [ + [1, 'CONTRACT-2024-001', 5000000.0, 'CNY', '按月结算', 180, '2024-01-01', '2024-06-30', 5, 12, null], + [2, 'CONTRACT-2024-002', 8000000.0, 'CNY', '按节点结算', 210, '2024-02-01', '2024-08-31', 5, 12, null], + [3, 'CONTRACT-2024-003', 6500000.0, 'CNY', '按进度结算', 210, '2024-03-01', '2024-09-30', 5, 12, null] + ]; + + contracts.forEach(contract => { + db.run( + 'INSERT INTO project_contracts (project_id, contract_code, contract_amount, currency, settlement_method, contract_period, start_date, end_date, warranty_deposit_percentage, warranty_period, contract_file) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + contract, + (err) => { + if (err) { + console.error('插入项目合同数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试分包合同数据 + db.get('SELECT COUNT(*) as count FROM subcontracts', (err, row) => { + if (err) { + console.error('查询分包合同数据失败:', err.message); + return; + } + + if (row.count === 0) { + const subcontracts = [ + [1, 1, '老挝施工队A', 1500000.0, 'CNY', '2024-01-01', '2024-06-30', 500000.0, 'active'], + [1, 2, '泰国施工队B', 1000000.0, 'CNY', '2024-01-15', '2024-06-15', 300000.0, 'active'], + [2, 1, '老挝施工队A', 2500000.0, 'CNY', '2024-02-01', '2024-08-31', 800000.0, 'active'], + [3, 3, '越南施工队C', 2000000.0, 'CNY', '2024-03-01', '2024-09-30', 600000.0, 'active'] + ]; + + subcontracts.forEach(subcontract => { + db.run( + 'INSERT INTO subcontracts (project_id, subcontractor_id, subcontractor_name, contract_amount, currency, start_date, end_date, paid_amount, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + subcontract, + (err) => { + if (err) { + console.error('插入分包合同数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试项目材料数据 + db.get('SELECT COUNT(*) as count FROM project_materials', (err, row) => { + if (err) { + console.error('查询项目材料数据失败:', err.message); + return; + } + + if (row.count === 0) { + const materials = [ + [1, 1, 'JKLYJ-35-22kV', '米', 10000, 10500, 5000, 15.5, 162750], + [1, 4, 'GJ-35', '米', 8000, 8500, 4000, 8.2, 69700], + [1, 5, 'XP-70', '个', 500, 520, 200, 25.0, 13000], + [2, 2, 'JKLYJ-50-22kV', '米', 15000, 15500, 6000, 18.8, 291400], + [2, 6, 'FXBW-10/70', '个', 300, 320, 100, 85.0, 27200], + [3, 3, 'VV-3x25+1x16', '米', 12000, 12500, 5000, 22.5, 281250], + [3, 7, 'NLL-1', '个', 800, 850, 300, 12.5, 10625], + [3, 9, 'FJB-2', '个', 400, 420, 150, 22.0, 9240] + ]; + + materials.forEach(material => { + db.run( + 'INSERT INTO project_materials (project_id, product_id, product_name, unit, budget_quantity, purchase_quantity, used_quantity, average_price, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + material, + (err) => { + if (err) { + console.error('插入项目材料数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试施工节点数据 + db.get('SELECT COUNT(*) as count FROM project_milestones', (err, row) => { + if (err) { + console.error('查询施工节点数据失败:', err.message); + return; + } + + if (row.count === 0) { + const milestones = [ + [1, '项目启动', 10, 500000.0, '2024-01-01', '2024-01-01', 100, 'completed', null], + [1, '基础施工', 30, 1500000.0, '2024-01-15', '2024-02-15', 100, 'completed', null], + [1, '线路架设', 40, 2000000.0, '2024-02-20', '2024-04-20', 60, 'in_progress', null], + [1, '竣工验收', 20, 1000000.0, '2024-06-15', null, 0, 'pending', null], + [2, '项目启动', 10, 800000.0, '2024-02-01', '2024-02-01', 100, 'completed', null], + [2, '基础施工', 30, 2400000.0, '2024-02-15', '2024-03-15', 100, 'completed', null], + [2, '设备安装', 40, 3200000.0, '2024-03-20', '2024-06-20', 70, 'in_progress', null], + [2, '竣工验收', 20, 1600000.0, '2024-08-15', null, 0, 'pending', null], + [3, '项目启动', 10, 650000.0, '2024-03-01', '2024-03-01', 100, 'completed', null], + [3, '线路改造', 50, 3250000.0, '2024-03-15', '2024-06-15', 80, 'in_progress', null], + [3, '设备升级', 30, 1950000.0, '2024-06-20', '2024-08-20', 30, 'in_progress', null], + [3, '竣工验收', 10, 650000.0, '2024-09-15', null, 0, 'pending', null] + ]; + + milestones.forEach(milestone => { + db.run( + 'INSERT INTO project_milestones (project_id, milestone_name, percentage, amount, expected_date, actual_date, completion_progress, status, voucher) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + milestone, + (err) => { + if (err) { + console.error('插入施工节点数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试项目财务信息数据 + db.get('SELECT COUNT(*) as count FROM project_finances', (err, row) => { + if (err) { + console.error('查询项目财务信息数据失败:', err.message); + return; + } + + if (row.count === 0) { + const finances = [ + [1, 'income', 500000.0, 'CNY', '2024-01-01', 'completed', '项目启动款'], + [1, 'income', 1500000.0, 'CNY', '2024-02-15', 'completed', '基础施工款'], + [1, 'expense', 500000.0, 'CNY', '2024-01-10', 'completed', '材料采购'], + [1, 'expense', 300000.0, 'CNY', '2024-02-20', 'completed', '分包款'], + [2, 'income', 800000.0, 'CNY', '2024-02-01', 'completed', '项目启动款'], + [2, 'income', 2400000.0, 'CNY', '2024-03-15', 'completed', '基础施工款'], + [2, 'expense', 800000.0, 'CNY', '2024-02-10', 'completed', '材料采购'], + [3, 'income', 650000.0, 'CNY', '2024-03-01', 'completed', '项目启动款'], + [3, 'expense', 600000.0, 'CNY', '2024-03-10', 'completed', '材料采购'] + ]; + + finances.forEach(finance => { + db.run( + 'INSERT INTO project_finances (project_id, payment_type, amount, currency, payment_date, status, description) VALUES (?, ?, ?, ?, ?, ?, ?)', + finance, + (err) => { + if (err) { + console.error('插入项目财务信息数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试质保金数据 + db.get('SELECT COUNT(*) as count FROM warranty_deposits', (err, row) => { + if (err) { + console.error('查询质保金数据失败:', err.message); + return; + } + + if (row.count === 0) { + const warrantyDeposits = [ + [1, 250000.0, 'CNY', 12, '2024-06-30', '2025-06-30', 'active'], + [2, 400000.0, 'CNY', 12, '2024-08-31', '2025-08-31', 'active'], + [3, 325000.0, 'CNY', 12, '2024-09-30', '2025-09-30', 'active'] + ]; + + warrantyDeposits.forEach(deposit => { + db.run( + 'INSERT INTO warranty_deposits (project_id, amount, currency, warranty_period, start_date, end_date, status) VALUES (?, ?, ?, ?, ?, ?, ?)', + deposit, + (err) => { + if (err) { + console.error('插入质保金数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试联系人数据 + db.get('SELECT COUNT(*) as count FROM contacts', (err, row) => { + if (err) { + console.error('查询联系人数据失败:', err.message); + return; + } + + if (row.count === 0) { + const contacts = [ + // 供应商联系人 + [1, 'supplier', '赵六', '经理', '13600136001', 1], + [1, 'supplier', '钱七', '销售', '13500135001', 0], + [2, 'supplier', '孙八', '技术', '13400134001', 1], + // 客户联系人 + [1, 'customer', '张三', '采购', '13800138001', 1], + [1, 'customer', '李四', '经理', '13900139001', 0], + [2, 'customer', '王五', '财务', '13700137001', 1], + // 分包商联系人 + [1, 'subcontractor', '吴九', '项目经理', '13300133001', 1], + [1, 'subcontractor', '郑十', '技术主管', '13200132001', 0], + [2, 'subcontractor', '王十一', '施工队长', '13100131001', 1] + ]; + + contacts.forEach(contact => { + db.run( + 'INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary) VALUES (?, ?, ?, ?, ?, ?)', + contact, + (err) => { + if (err) { + console.error('插入联系人数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试施工日志数据 + db.get('SELECT COUNT(*) as count FROM construction_logs', (err, row) => { + if (err) { + console.error('查询施工日志数据失败:', err.message); + return; + } + + if (row.count === 0) { + const logs = [ + [1, '2024-01-01', 'sunny', '项目启动,召开开工会议', ''], + [1, '2024-01-02', 'sunny', '开始基础施工,开挖基坑', ''], + [1, '2024-01-03', 'cloudy', '继续基础施工,浇筑混凝土', ''], + [2, '2024-02-01', 'sunny', '项目启动,召开开工会议', ''], + [2, '2024-02-02', 'rainy', '进行场地平整,准备施工材料', ''], + [3, '2024-03-01', 'sunny', '项目启动,召开开工会议', ''], + [3, '2024-03-02', 'sunny', '开始线路改造,拆除旧线路', ''] + ]; + + logs.forEach(log => { + db.run( + 'INSERT INTO construction_logs (project_id, log_date, weather, work_content, photos) VALUES (?, ?, ?, ?, ?)', + log, + (err) => { + if (err) { + console.error('插入施工日志数据失败:', err.message); + } + } + ); + }); + } + }); + + // 插入测试汇率数据 + db.get('SELECT COUNT(*) as count FROM exchange_rates', (err, row) => { + if (err) { + console.error('查询汇率数据失败:', err.message); + return; + } + + if (row.count === 0) { + const rates = [ + ['CNY_LAK', 2900, '2024-01-01'], + ['CNY_USD', 0.143, '2024-01-01'], + ['CNY_THB', 4.8, '2024-01-01'], + ['USD_LAK', 20300, '2024-01-01'], + ['THB_LAK', 604, '2024-01-01'] + ]; + + rates.forEach(rate => { + db.run( + 'INSERT INTO exchange_rates (pair_key, rate, effective_date) VALUES (?, ?, ?)', + rate, + (err) => { + if (err) { + console.error('插入汇率数据失败:', err.message); + } + } + ); + }); + } + }); +} + +// 为sqlite3.Database添加query方法,使其与PostgreSQL的接口兼容 +db.query = function(text, params) { + return new Promise((resolve, reject) => { + if (text.trim().startsWith('SELECT')) { + // 处理SELECT查询 + db.all(text, params, (err, rows) => { + if (err) { + reject(err); + } else { + resolve({ rows }); + } + }); + } else { + // 处理其他类型的查询 + db.run(text, params, function(err) { + if (err) { + reject(err); + } else { + resolve({ rows: [], lastID: this.lastID, changes: this.changes }); + } + }); + } + }); +}; + +// 导出数据库连接 +module.exports = db; \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/db.js b/backup_20260327/company-finance-system/backend/db.js new file mode 100644 index 0000000..3647827 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/db.js @@ -0,0 +1,28 @@ +const { Pool } = require('pg'); +require('dotenv').config(); + +const pool = new Pool({ + host: process.env.DB_HOST, + port: process.env.DB_PORT, + database: process.env.DB_NAME, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, +}); + +// 测试数据库连接 +pool.on('connect', () => { + console.log('Database connected successfully'); +}); + +pool.on('error', (err) => { + console.error('Unexpected error on idle client', err); + process.exit(-1); +}); + +module.exports = { + query: (text, params) => pool.query(text, params), + pool, +}; \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/ecosystem.config.js b/backup_20260327/company-finance-system/backend/ecosystem.config.js new file mode 100644 index 0000000..b4c68e7 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/ecosystem.config.js @@ -0,0 +1,23 @@ +module.exports = { + apps: [{ + name: 'company-finance-api', + script: 'server-complete.js', + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: '1G', + env: { + NODE_ENV: 'development', + PORT: 3000 + }, + env_production: { + NODE_ENV: 'production', + PORT: 5000, + DB_HOST: 'localhost', + DB_PORT: 5432, + DB_NAME: 'company_finance_db', + DB_USER: 'finance_user', + DB_PASSWORD: 'FinanceDB2026!' + } + }] +}; \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/execute-migration.js b/backup_20260327/company-finance-system/backend/execute-migration.js new file mode 100644 index 0000000..93c91f3 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/execute-migration.js @@ -0,0 +1,45 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); +const fs = require('fs'); + +// 数据库路径 +const dbPath = path.join(__dirname, 'company_finance.db'); +const migrationPath = path.join(__dirname, 'migrations', '001_create_category_tree.sql'); + +console.log('开始执行数据库迁移...'); +console.log('数据库文件:', dbPath); + +// 读取迁移脚本 +const migrationScript = fs.readFileSync(migrationPath, 'utf8'); + +// 连接数据库 +const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + console.error('数据库连接失败:', err.message); + process.exit(1); + } + console.log('数据库连接成功'); +}); + +// 执行迁移 +db.exec(migrationScript, (err) => { + if (err) { + console.error('迁移执行失败:', err.message); + process.exit(1); + } + console.log('迁移执行成功!'); + + // 验证结果 + db.all("SELECT name FROM sqlite_master WHERE type='table'", (err, rows) => { + if (err) { + console.error('查询表失败:', err.message); + } else { + console.log('当前数据库表:'); + rows.forEach(row => console.log('-', row.name)); + } + + db.close(() => { + console.log('数据库连接已关闭'); + }); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/execute-purchase-migration.js b/backup_20260327/company-finance-system/backend/execute-purchase-migration.js new file mode 100644 index 0000000..4bd4549 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/execute-purchase-migration.js @@ -0,0 +1,55 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); +const fs = require('fs'); + +const dbPath = path.join(__dirname, 'company_finance.db'); +const migrationPath = path.join(__dirname, 'migrations', '002_create_purchase_inventory.sql'); + +console.log('开始执行采购库存数据库迁移...'); +console.log('数据库文件:', dbPath); +console.log('迁移脚本:', migrationPath); + +const migrationScript = fs.readFileSync(migrationPath, 'utf8'); + +const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + console.error('数据库连接失败:', err.message); + process.exit(1); + } + console.log('数据库连接成功'); +}); + +db.serialize(() => { + const statements = migrationScript.split(';').filter(s => s.trim()); + + statements.forEach((stmt, index) => { + if (stmt.trim()) { + console.log(`执行语句 ${index + 1}/${statements.length}`); + db.run(stmt.trim(), (err) => { + if (err) { + if (err.message.includes('duplicate column name') || + err.message.includes('already exists')) { + console.log(' 跳过(已存在)'); + } else { + console.error(' 错误:', err.message); + } + } else { + console.log(' 成功'); + } + }); + } + }); +}); + +db.all("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name", (err, rows) => { + if (err) { + console.error('查询表失败:', err.message); + } else { + console.log('\n当前数据库表:'); + rows.forEach(row => console.log('-', row.name)); + } + + db.close(() => { + console.log('\n迁移完成!'); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/final-backend.js b/backup_20260327/company-finance-system/backend/final-backend.js new file mode 100644 index 0000000..a739693 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/final-backend.js @@ -0,0 +1,4665 @@ +const express = require('express'); +const cors = require('cors'); +const path = require('path'); +const dotenv = require('dotenv'); +const db = require('./db-sqlite'); +const multer = require('multer'); +const { body, validationResult } = require('express-validator'); + +// 验证错误处理中间件 +const validate = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + success: false, + errors: errors.array() + }); + } + next(); +}; + +// 加载环境变量 +dotenv.config(); + +const app = express(); +const PORT = 3005; + +// 中间件 +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// 静态文件服务 - 前端应用 +app.use(express.static(path.join(__dirname, '../frontend/dist'))); + +// ==================== 健康检查 ==================== +app.get('/api/health', (req, res) => { + res.json({ + success: true, + message: '公司财务管理系统 API', + version: '1.0.0', + timestamp: new Date().toISOString(), + endpoints: { + upload: "/api/upload", + health: '/api/health', + auth: '/api/auth', + customers: '/api/customers', + suppliers: '/api/suppliers', + projects: '/api/projects', + products: '/api/products', + payment_nodes: '/api/payment-nodes', + payment_records: '/api/payment-records', + exchange_rates: '/api/exchange-rates', + advances: '/api/advances', + reimbursements: '/api/reimbursements', + purchase_requests: '/api/purchase-requests', + inventory: '/api/inventory', + finance_stats: '/api/finance-stats' + } + }); +}); + +// ==================== 认证API ==================== +app.post('/api/auth/login', async (req, res) => { + try { + const { username, password } = req.body; + + // 简单认证逻辑(生产环境应使用JWT和密码哈希) + if (username === 'admin' && password === 'X123c321@') { + res.json({ + success: true, + data: { + id: 1, + username: 'admin', + name: '系统管理员', + role: 'admin', + department: '管理部' + } + }); + } else if (username === 'manager' && password === 'X123c321@') { + res.json({ + success: true, + data: { + id: 2, + username: 'manager', + name: '罗仕林', + role: 'manager', + department: '业务部' + } + }); + } else if (username === 'pm1' && password === 'X123c321@') { + res.json({ + success: true, + data: { + id: 3, + username: 'pm1', + name: '张三', + role: 'user', + department: '项目部' + } + }); + } else { + res.status(401).json({ + success: false, + message: '用户名或密码错误' + }); + } + } catch (error) { + console.error('登录失败:', error); + res.status(500).json({ + success: false, + message: '登录失败', + error: error.message + }); + } +}); + +// ==================== 用户管理API ==================== +app.get('/api/users', async (req, res) => { + try { + // 模拟用户数据 + const users = [ + { + id: 1, + username: 'admin', + name: '系统管理员', + role: 'admin', + department: '管理部' + }, + { + id: 2, + username: 'manager', + name: '罗仕林', + role: 'manager', + department: '业务部' + }, + { + id: 3, + username: 'pm1', + name: '张三', + role: 'user', + department: '项目部' + }, + { + id: 4, + username: 'pm2', + name: '李四', + role: 'user', + department: '项目部' + }, + { + id: 5, + username: 'finance', + name: '王五', + role: 'user', + department: '财务部' + } + ]; + + res.json({ + success: true, + data: users + }); + } catch (error) { + console.error('获取用户列表失败:', error); + res.status(500).json({ + success: false, + message: '获取用户列表失败', + error: error.message + }); + } +}); + +// ==================== 客户管理API ==================== +app.get('/api/customers', async (req, res) => { + try { + const result = await db.query(` + SELECT * FROM customers + ORDER BY created_at DESC + LIMIT 50 + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取客户失败:', error); + res.status(500).json({ + success: false, + message: '获取客户失败', + error: error.message + }); + } +}); + +// ==================== 客户管理API ==================== +app.get('/api/customers', async (req, res) => { + try { + const result = await db.query(` + SELECT * FROM customers + ORDER BY created_at DESC + LIMIT 50 + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取客户失败:', error); + res.status(500).json({ + success: false, + message: '获取客户失败', + error: error.message + }); + } +}); + +app.get('/api/customers/:id', async (req, res) => { + try { + const { id } = req.params; + + // 获取客户基本信息 + const customerResult = await db.query(` + SELECT * FROM customers + WHERE id = ? + `, [id]); + + if (customerResult.rows.length > 0) { + const customer = customerResult.rows[0]; + + // 获取客户的所有联系人 + const contactsResult = await db.query(` + SELECT * FROM contacts + WHERE entity_id = ? AND entity_type = 'customer' + ORDER BY is_primary DESC + `, [id]); + + // 转换联系人数据结构 + const contacts = contactsResult.rows.map(contact => ({ + name: contact.name || '未命名', + position: contact.position || '', + phone: contact.phone || '', + is_primary: contact.is_primary === 1 + })); + + // 转换数据结构以匹配前端期望 + const formattedCustomer = { + id: customer.id, + code: `C${String(customer.id).padStart(4, '0')}`, // 生成客户编号 + name: customer.name, + address: customer.address, + contacts: contacts.length > 0 ? contacts : [], // 使用从联系人表获取的联系人 + remark: customer.remark || '', // 默认为空 + total_contract_amount: 0, // 默认为0 + total_received: 0, // 默认为0 + total_receivable: 0, // 默认为0 + created_at: customer.created_at + }; + + res.json({ + success: true, + data: formattedCustomer + }); + } else { + res.status(404).json({ + success: false, + message: '客户不存在' + }); + } + } catch (error) { + console.error('获取客户详情失败:', error); + res.status(500).json({ + success: false, + message: '获取客户详情失败', + error: error.message + }); + } +}); + +app.post('/api/customers', async (req, res) => { + try { + const { name, address, remark, contacts } = req.body; + + // 从contacts中获取主联系人信息 + const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; + const contact = primaryContact?.name || ''; + const position = primaryContact?.position || ''; + const phone = primaryContact?.phone || ''; + const email = ''; // 前端没有email字段 + + const result = await db.query( + `INSERT INTO customers (name, address, contact, position, phone, email, remark, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [name, address, contact, position, phone, email, remark] + ); + + const customerId = result.lastID; + + // 插入联系人数据 + if (contacts && contacts.length > 0) { + for (const contactItem of contacts) { + await db.query( + `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [customerId, 'customer', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] + ); + } + } + + res.json({ + success: true, + message: '客户创建成功', + data: { + id: customerId, + code: `C${String(customerId).padStart(4, '0')}`, + name, + address, + contacts: contacts || [], + remark, + total_contract_amount: 0, + total_received: 0, + total_receivable: 0, + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('创建客户失败:', error); + res.status(500).json({ + success: false, + message: '创建客户失败', + error: error.message + }); + } +}); + +app.put('/api/customers/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, address, remark, contacts } = req.body; + + // 从contacts中获取主联系人信息 + const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; + const contact = primaryContact?.name || ''; + const position = primaryContact?.position || ''; + const phone = primaryContact?.phone || ''; + const email = ''; // 前端没有email字段 + + await db.query( + `UPDATE customers + SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, remark = ?, updated_at = datetime('now') + WHERE id = ?`, + [name, address, contact, position, phone, email, remark, id] + ); + + // 删除旧的联系人数据 + await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'customer'`, [id]); + + // 插入新的联系人数据 + if (contacts && contacts.length > 0) { + for (const contactItem of contacts) { + await db.query( + `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [id, 'customer', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] + ); + } + } + + res.json({ + success: true, + message: '客户更新成功', + data: { + id, + code: `C${String(id).padStart(4, '0')}`, + name, + address, + contacts: contacts || [], + remark, + total_contract_amount: 0, + total_received: 0, + total_receivable: 0, + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('更新客户失败:', error); + res.status(500).json({ + success: false, + message: '更新客户失败', + error: error.message + }); + } +}); + +app.delete('/api/customers/:id', async (req, res) => { + try { + const { id } = req.params; + + // 先删除关联的联系人数据 + await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'customer'`, [id]); + + // 再删除客户数据 + const result = await db.query(`DELETE FROM customers WHERE id = ?`, [id]); + + if (result.changes > 0) { + res.json({ + success: true, + message: '客户删除成功' + }); + } else { + res.status(404).json({ + success: false, + message: '客户不存在' + }); + } + } catch (error) { + console.error('删除客户失败:', error); + res.status(500).json({ + success: false, + message: '删除客户失败', + error: error.message + }); + } +}); + +// ==================== 供应商管理API ==================== +app.get('/api/suppliers', async (req, res) => { + try { + const result = await db.query(` + SELECT * FROM suppliers + ORDER BY created_at DESC + LIMIT 50 + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取供应商失败:', error); + res.status(500).json({ + success: false, + message: '获取供应商失败', + error: error.message + }); + } +}); + +app.get('/api/suppliers/:id', async (req, res) => { + try { + const { id } = req.params; + + // 获取供应商基本信息 + const supplierResult = await db.query(` + SELECT * FROM suppliers + WHERE id = ? + `, [id]); + + if (supplierResult.rows.length > 0) { + const supplier = supplierResult.rows[0]; + + // 获取供应商的所有联系人 + const contactsResult = await db.query(` + SELECT * FROM contacts + WHERE entity_id = ? AND entity_type = 'supplier' + ORDER BY is_primary DESC + `, [id]); + + // 转换联系人数据结构 + const contacts = contactsResult.rows.map(contact => ({ + name: contact.name || '未命名', + position: contact.position || '', + phone: contact.phone || '', + is_primary: contact.is_primary === 1 + })); + + // 转换数据结构以匹配前端期望 + const formattedSupplier = { + id: supplier.id, + code: `S${String(supplier.id).padStart(4, '0')}`, // 生成供应商编号 + name: supplier.name || '未命名', + supply_category: supplier.supply_category || '电力设备', // 默认为电力设备 + country: supplier.country || 'Laos', // 默认为老挝 + contacts: contacts.length > 0 ? contacts : [], // 使用从联系人表获取的联系人 + remark: supplier.remark || '', // 默认为空 + total_purchase_amount: 0, // 默认为0 + total_paid: 0, // 默认为0 + total_payable: 0, // 默认为0 + created_at: supplier.created_at + }; + + // 设置响应头确保UTF-8编码 + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.json({ + success: true, + data: formattedSupplier + }); + } else { + res.status(404).json({ + success: false, + message: '供应商不存在' + }); + } + } catch (error) { + console.error('获取供应商详情失败:', error); + res.status(500).json({ + success: false, + message: '获取供应商详情失败', + error: error.message + }); + } +}); + +app.post('/api/suppliers', async (req, res) => { + try { + const { name, supply_category, country, remark, contacts } = req.body; + + // 从contacts中获取主联系人信息 + const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; + const contact = primaryContact?.name || ''; + const position = primaryContact?.position || ''; + const phone = primaryContact?.phone || ''; + const email = ''; // 前端没有email字段 + const address = ''; // 前端没有address字段 + + const result = await db.query( + `INSERT INTO suppliers (name, address, contact, position, phone, email, supply_category, country, remark, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [name, address, contact, position, phone, email, supply_category, country, remark] + ); + + const supplierId = result.lastID; + + // 插入联系人数据 + if (contacts && contacts.length > 0) { + for (const contactItem of contacts) { + await db.query( + `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [supplierId, 'supplier', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] + ); + } + } + + res.json({ + success: true, + message: '供应商创建成功', + data: { + id: supplierId, + code: `S${String(supplierId).padStart(4, '0')}`, + name, + supply_category, + country, + contacts: contacts || [], + remark, + total_purchase_amount: 0, + total_paid: 0, + total_payable: 0, + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('创建供应商失败:', error); + res.status(500).json({ + success: false, + message: '创建供应商失败', + error: error.message + }); + } +}); + +app.put('/api/suppliers/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, supply_category, country, remark, contacts } = req.body; + + // 从contacts中获取主联系人信息 + const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; + const contact = primaryContact?.name || ''; + const position = primaryContact?.position || ''; + const phone = primaryContact?.phone || ''; + const email = ''; // 前端没有email字段 + const address = ''; // 前端没有address字段 + + await db.query( + `UPDATE suppliers + SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, supply_category = ?, country = ?, remark = ?, updated_at = datetime('now') + WHERE id = ?`, + [name, address, contact, position, phone, email, supply_category, country, remark, id] + ); + + // 删除旧的联系人数据 + await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'supplier'`, [id]); + + // 插入新的联系人数据 + if (contacts && contacts.length > 0) { + for (const contactItem of contacts) { + await db.query( + `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [id, 'supplier', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] + ); + } + } + + res.json({ + success: true, + message: '供应商更新成功', + data: { + id, + code: `S${String(id).padStart(4, '0')}`, + name, + supply_category, + country, + contacts: contacts || [], + remark, + total_purchase_amount: 0, + total_paid: 0, + total_payable: 0, + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('更新供应商失败:', error); + res.status(500).json({ + success: false, + message: '更新供应商失败', + error: error.message + }); + } +}); + +app.delete('/api/suppliers/:id', async (req, res) => { + try { + const { id } = req.params; + + // 先删除关联的联系人数据 + await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'supplier'`, [id]); + + // 再删除供应商数据 + const result = await db.query(`DELETE FROM suppliers WHERE id = ?`, [id]); + + if (result.changes > 0) { + res.json({ + success: true, + message: '供应商删除成功' + }); + } else { + res.status(404).json({ + success: false, + message: '供应商不存在' + }); + } + } catch (error) { + console.error('删除供应商失败:', error); + res.status(500).json({ + success: false, + message: '删除供应商失败', + error: error.message + }); + } +}); + +// ==================== 分包商管理API ==================== +app.get('/api/subcontractors', async (req, res) => { + try { + const result = await db.query(` + SELECT * FROM subcontractors + ORDER BY created_at DESC + LIMIT 50 + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取分包商失败:', error); + res.status(500).json({ + success: false, + message: '获取分包商失败', + error: error.message + }); + } +}); + +app.get('/api/subcontractors/:id', async (req, res) => { + try { + const { id } = req.params; + + // 获取分包商基本信息 + const subcontractorResult = await db.query(` + SELECT * FROM subcontractors + WHERE id = ? + `, [id]); + + if (subcontractorResult.rows.length > 0) { + const subcontractor = subcontractorResult.rows[0]; + + // 获取分包商的所有联系人 + const contactsResult = await db.query(` + SELECT * FROM contacts + WHERE entity_id = ? AND entity_type = 'subcontractor' + ORDER BY is_primary DESC + `, [id]); + + // 转换联系人数据结构 + const contacts = contactsResult.rows.map(contact => ({ + name: contact.name || '未命名', + position: contact.position || '', + phone: contact.phone || '', + is_primary: contact.is_primary === 1 + })); + + // 转换数据结构以匹配前端期望 + const formattedSubcontractor = { + id: subcontractor.id, + code: `SC${String(subcontractor.id).padStart(4, '0')}`, // 生成分包商编号 + name: subcontractor.name, + scope: subcontractor.scope || '', // 默认为空 + features: subcontractor.features || '', // 默认为空 + country: subcontractor.country || '', // 默认为空 + contacts: contacts.length > 0 ? contacts : [], // 使用从联系人表获取的联系人 + remark: subcontractor.remark || '', // 默认为空 + total_contract_amount: 0, // 默认为0 + total_paid: 0, // 默认为0 + total_payable: 0, // 默认为0 + created_at: subcontractor.created_at + }; + + res.json({ + success: true, + data: formattedSubcontractor + }); + } else { + res.status(404).json({ + success: false, + message: '分包商不存在' + }); + } + } catch (error) { + console.error('获取分包商详情失败:', error); + res.status(500).json({ + success: false, + message: '获取分包商详情失败', + error: error.message + }); + } +}); + +app.post('/api/subcontractors', async (req, res) => { + try { + const { name, scope, features, country, remark, contacts } = req.body; + + // 从contacts中获取主联系人信息 + const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; + const contact = primaryContact?.name || ''; + const position = primaryContact?.position || ''; + const phone = primaryContact?.phone || ''; + const email = ''; // 前端没有email字段 + const address = ''; // 前端没有address字段 + + const result = await db.query( + `INSERT INTO subcontractors (name, address, contact, position, phone, email, scope, features, country, remark, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [name, address, contact, position, phone, email, scope, features, country, remark] + ); + + const subcontractorId = result.lastID; + + // 插入联系人数据 + if (contacts && contacts.length > 0) { + for (const contactItem of contacts) { + await db.query( + `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [subcontractorId, 'subcontractor', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] + ); + } + } + + res.json({ + success: true, + message: '分包商创建成功', + data: { + id: subcontractorId, + code: `SC${String(subcontractorId).padStart(4, '0')}`, + name, + scope, + features, + country, + contacts: contacts || [], + remark, + total_contract_amount: 0, + total_paid: 0, + total_payable: 0, + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('创建分包商失败:', error); + res.status(500).json({ + success: false, + message: '创建分包商失败', + error: error.message + }); + } +}); + +app.put('/api/subcontractors/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, scope, features, country, remark, contacts } = req.body; + + // 从contacts中获取主联系人信息 + const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; + const contact = primaryContact?.name || ''; + const position = primaryContact?.position || ''; + const phone = primaryContact?.phone || ''; + const email = ''; // 前端没有email字段 + const address = ''; // 前端没有address字段 + + await db.query( + `UPDATE subcontractors + SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, scope = ?, features = ?, country = ?, remark = ?, updated_at = datetime('now') + WHERE id = ?`, + [name, address, contact, position, phone, email, scope, features, country, remark, id] + ); + + // 删除旧的联系人数据 + await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor'`, [id]); + + // 插入新的联系人数据 + if (contacts && contacts.length > 0) { + for (const contactItem of contacts) { + await db.query( + `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [id, 'subcontractor', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] + ); + } + } + + res.json({ + success: true, + message: '分包商更新成功', + data: { + id, + code: `SC${String(id).padStart(4, '0')}`, + name, + scope, + features, + country, + contacts: contacts || [], + remark, + total_contract_amount: 0, + total_paid: 0, + total_payable: 0, + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('更新分包商失败:', error); + res.status(500).json({ + success: false, + message: '更新分包商失败', + error: error.message + }); + } +}); + +app.delete('/api/subcontractors/:id', async (req, res) => { + try { + const { id } = req.params; + + // 先删除关联的联系人数据 + await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor'`, [id]); + + // 再删除分包商数据 + const result = await db.query(`DELETE FROM subcontractors WHERE id = ?`, [id]); + + if (result.changes > 0) { + res.json({ + success: true, + message: '分包商删除成功' + }); + } else { + res.status(404).json({ + success: false, + message: '分包商不存在' + }); + } + } catch (error) { + console.error('删除分包商失败:', error); + res.status(500).json({ + success: false, + message: '删除分包商失败', + error: error.message + }); + } +}); + +// ==================== 项目管理API ==================== +app.get('/api/projects', async (req, res) => { + try { + const result = await db.query(` + SELECT + p.*, + c.name as customer_name + FROM projects p + LEFT JOIN customers c ON p.customer_id = c.id + ORDER BY p.created_at DESC + LIMIT 50 + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取项目失败:', error); + res.status(500).json({ + success: false, + message: '获取项目失败', + error: error.message + }); + } +}); + +// ==================== 项目详情API ==================== +app.get('/api/projects/:id', async (req, res) => { + try { + const { id } = req.params; + + // 获取项目基本信息 + const projectResult = await db.query(` + SELECT + p.*, + c.name as customer_name + FROM projects p + LEFT JOIN customers c ON p.customer_id = c.id + WHERE p.id = ? + `, [id]); + + if (projectResult.rows.length > 0) { + const project = projectResult.rows[0]; + + // 获取项目合同信息 + const contractResult = await db.query(` + SELECT * FROM project_contracts + WHERE project_id = ? + ORDER BY created_at DESC + LIMIT 1 + `, [id]); + + const contract = contractResult.rows[0]; + + res.json({ + success: true, + data: { + id: project.id, + project_code: project.code || `PROJ-${String(project.id).padStart(4, '0')}`, + name: project.name, + customer_id: project.customer_id, + customer_name: project.customer_name || '未知客户', + status: project.status || 'planning', + budget: '0', + spent: '0', + start_date: project.start_date, + end_date: project.end_date, + description: project.description, + contract_type: 'lump_sum', + contract_amount: project.contract_amount?.toString() || '0', + currency: 'CNY', + contract_days: contract?.contract_period || 180, + project_manager_id: 1, + manager_name: '未知经理', + location: project.location || '', + work_quantity: '', + project_situation: project.description || '', + settlement_type: contract?.settlement_method || 'lump_sum', + has_warranty: true, + warranty_amount: (project.contract_amount * 0.05).toString(), + warranty_percent: '5', + warranty_months: 12, + warranty_start_date: project.end_date, + warranty_end_date: new Date(new Date(project.end_date).getTime() + 12 * 30 * 24 * 60 * 60 * 1000).toISOString(), + warranty_status: 'pending' + } + }); + } else { + res.status(404).json({ + success: false, + message: '项目不存在' + }); + } + } catch (error) { + console.error('获取项目详情失败:', error); + res.status(500).json({ + success: false, + message: '获取项目详情失败', + error: error.message + }); + } +}); + +// ==================== 项目合同API ==================== +app.get('/api/projects/:id/contracts', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query(` + SELECT * FROM project_contracts + WHERE project_id = ? + ORDER BY created_at DESC + `, [id]); + + res.json({ + success: true, + data: result.rows + }); + } catch (error) { + console.error('获取项目合同失败:', error); + res.status(500).json({ + success: false, + message: '获取项目合同失败', + error: error.message + }); + } +}); + +// ==================== 项目分包API ==================== +app.get('/api/projects/:id/subcontracts', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query(` + SELECT * FROM subcontracts + WHERE project_id = ? + ORDER BY created_at DESC + `, [id]); + + // 解析unit_price_items字段 + const subcontracts = result.rows.map(subcontract => { + if (subcontract.unit_price_items) { + try { + subcontract.unit_price_items = JSON.parse(subcontract.unit_price_items); + } catch (error) { + subcontract.unit_price_items = []; + } + } else { + subcontract.unit_price_items = []; + } + return subcontract; + }); + + res.json({ + success: true, + data: subcontracts + }); + } catch (error) { + console.error('获取项目分包失败:', error); + res.status(500).json({ + success: false, + message: '获取项目分包失败', + error: error.message + }); + } +}); + +// ==================== 新增项目分包API ==================== +app.post('/api/projects/:id/subcontracts', async (req, res) => { + try { + const { id } = req.params; + const { subcontractor_id, subcontractor_name, contract_amount, currency, settlement_type, other_terms, payment_description, unit_price_items, start_date, end_date, work_days, status } = req.body; + + const unitPriceItemsJson = unit_price_items ? JSON.stringify(unit_price_items) : null; + + const result = await db.query( + `INSERT INTO subcontracts (project_id, subcontractor_id, subcontractor_name, contract_amount, currency, settlement_type, other_terms, payment_description, unit_price_items, start_date, end_date, work_days, paid_amount, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [id, subcontractor_id, subcontractor_name, contract_amount, currency || 'CNY', settlement_type || 'lump_sum', other_terms, payment_description, unitPriceItemsJson, start_date, end_date, work_days, 0, status || 'active'] + ); + + const subcontractId = result.lastID; + + res.json({ + success: true, + message: '新增分包成功', + data: { + id: subcontractId, + project_id: id, + subcontractor_id, + subcontractor_name, + contract_amount, + currency: currency || 'CNY', + settlement_type: settlement_type || 'lump_sum', + other_terms, + payment_description, + unit_price_items, + start_date, + end_date, + work_days, + paid_amount: 0, + status: status || 'active', + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('新增项目分包失败:', error); + res.status(500).json({ + success: false, + message: '新增项目分包失败', + error: error.message + }); + } +}); + +// ==================== 项目材料API ==================== +app.get('/api/projects/:id/materials', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query(` + SELECT * FROM project_materials + WHERE project_id = ? + ORDER BY created_at DESC + `, [id]); + + res.json({ + success: true, + data: result.rows + }); + } catch (error) { + console.error('获取项目材料失败:', error); + res.status(500).json({ + success: false, + message: '获取项目材料失败', + error: error.message + }); + } +}); + +// ==================== 项目施工节点API ==================== +app.get('/api/projects/:id/milestones', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query(` + SELECT * FROM project_milestones + WHERE project_id = ? + ORDER BY expected_date ASC + `, [id]); + + res.json({ + success: true, + data: result.rows + }); + } catch (error) { + console.error('获取项目施工节点失败:', error); + res.status(500).json({ + success: false, + message: '获取项目施工节点失败', + error: error.message + }); + } +}); + +// ==================== 项目财务API ==================== +app.get('/api/projects/:id/finances', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query(` + SELECT * FROM project_finances + WHERE project_id = ? + ORDER BY payment_date DESC + `, [id]); + + res.json({ + success: true, + data: result.rows + }); + } catch (error) { + console.error('获取项目财务失败:', error); + res.status(500).json({ + success: false, + message: '获取项目财务失败', + error: error.message + }); + } +}); + +// ==================== 项目质保金API ==================== +app.get('/api/projects/:id/warranty-deposits', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query(` + SELECT * FROM warranty_deposits + WHERE project_id = ? + ORDER BY created_at DESC + `, [id]); + + res.json({ + success: true, + data: result.rows + }); + } catch (error) { + console.error('获取项目质保金失败:', error); + res.status(500).json({ + success: false, + message: '获取项目质保金失败', + error: error.message + }); + } +}); + +// ==================== 项目施工日志API ==================== +app.get('/api/projects/:id/construction-logs', async (req, res) => { + try { + const { id } = req.params; + + // 由于施工日志表可能不存在,返回空数组 + res.json({ + success: true, + data: [] + }); + } catch (error) { + console.error('获取项目施工日志失败:', error); + res.status(500).json({ + success: false, + message: '获取项目施工日志失败', + error: error.message + }); + } +}); + +// ==================== 项目删除API ==================== +app.delete('/api/projects/:id', checkAdmin, async (req, res) => { + try { + const { id } = req.params; + await db.query('DELETE FROM projects WHERE id = ?', [id]); + res.json({ success: true, message: '项目已删除' }); + } catch (error) { + console.error('删除项目失败:', error); + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 项目更新API ==================== +app.put('/api/projects/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, manager_id, location, start_date, end_date, description, status, contract_amount } = req.body; + + console.log('收到项目更新请求:', { id, name, manager_id, location, start_date, end_date, description }); + + // 更新项目信息 + await db.query( + 'UPDATE projects SET name = CASE WHEN ? IS NOT NULL THEN ? ELSE name END, manager_id = CASE WHEN ? IS NOT NULL THEN ? ELSE manager_id END, location = CASE WHEN ? IS NOT NULL THEN ? ELSE location END, start_date = CASE WHEN ? IS NOT NULL THEN ? ELSE start_date END, end_date = CASE WHEN ? IS NOT NULL THEN ? ELSE end_date END, description = CASE WHEN ? IS NOT NULL THEN ? ELSE description END, status = CASE WHEN ? IS NOT NULL THEN ? ELSE status END, contract_amount = CASE WHEN ? IS NOT NULL THEN ? ELSE contract_amount END, updated_at = CURRENT_TIMESTAMP WHERE id = ?', + [name, name, manager_id, manager_id, location, location, start_date, start_date, end_date, end_date, description, description, status, status, contract_amount, contract_amount, id] + ); + + // 如果提供了开始和结束日期,更新合同的工期信息 + if (start_date && end_date) { + const start = new Date(start_date); + const end = new Date(end_date); + const contractPeriod = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1; + + // 更新合同信息 + await db.query( + 'UPDATE project_contracts SET start_date = ?, end_date = ?, contract_period = ? WHERE project_id = ?', + [start_date, end_date, contractPeriod, id] + ); + } + + // 查询更新后的数据 + const updatedResult = await db.query('SELECT * FROM projects WHERE id = ?', [id]); + res.json({ success: true, data: updatedResult.rows[0] }); + } catch (error) { + console.error('更新项目失败:', error); + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 合同细节保存API ==================== +app.put('/api/projects/:id/contract', async (req, res) => { + try { + const { id } = req.params; + const { + project_overview, + settlement_type, + contract_total, + tax_included, + unit_price_items, + payment_nodes, + other_info, + contract_file + } = req.body; + + console.log('收到合同细节保存请求:', { id, project_overview, settlement_type, contract_total, tax_included, contract_file }); + + // 1. 更新项目基本信息 + await db.query( + `UPDATE projects + SET description = ?, contract_amount = ? + WHERE id = ?`, + [project_overview, contract_total, id] + ); + + // 2. 更新或创建项目合同 + const contractResult = await db.query( + `SELECT * FROM project_contracts WHERE project_id = ?`, + [id] + ); + + if (contractResult.rows.length > 0) { + // 更新现有合同 + await db.query( + `UPDATE project_contracts + SET settlement_method = ?, contract_amount = ?, contract_file = ?, other_info = ?, tax_included = ? + WHERE project_id = ?`, + [settlement_type, contract_total, contract_file, other_info, tax_included, id] + ); + } else { + // 创建新合同 + const contractCode = `CONTRACT-${Date.now()}`; + await db.query( + `INSERT INTO project_contracts (project_id, contract_code, contract_amount, settlement_method, contract_file, other_info, tax_included, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [id, contractCode, contract_total, settlement_type, contract_file, other_info, tax_included] + ); + } + + // 3. 处理付款节点 + if (payment_nodes && Array.isArray(payment_nodes)) { + // 删除旧的付款节点 + await db.query(`DELETE FROM project_milestones WHERE project_id = ?`, [id]); + + // 创建新的付款节点 + for (const node of payment_nodes) { + await db.query( + `INSERT INTO project_milestones (project_id, milestone_name, condition, percentage, amount, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [id, node.name, node.condition || '', node.percentage, node.amount, 'pending'] + ); + } + } + + // 4. 处理单价项 + if (unit_price_items && Array.isArray(unit_price_items) && settlement_type === 'unit_price') { + // 删除旧的材料项 + await db.query(`DELETE FROM project_materials WHERE project_id = ?`, [id]); + + // 创建新的材料项 + for (const item of unit_price_items) { + await db.query( + `INSERT INTO project_materials (project_id, product_name, unit, budget_quantity, average_price, total_amount, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [id, item.name, item.unit, item.quantity, item.price, item.total] + ); + } + } + + res.json({ + success: true, + message: '合同细节保存成功' + }); + } catch (error) { + console.error('保存合同细节失败:', error); + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 文件上传API ==================== +const fs = require('fs'); +const uploadDir = path.join(__dirname, 'uploads'); + +// 确保上传目录存在 +if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); +} + +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, uploadDir); + }, + filename: function (req, file, cb) { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); + cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); + } +}); + +const uploadLocal = multer({ storage: storage }); + +app.post('/api/upload/single', uploadLocal.single('file'), (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ success: false, message: '请选择文件' }); + } + + // 构建文件URL + const fileUrl = `/uploads/${req.file.filename}`; + + res.json({ + success: true, + data: { + url: fileUrl, + filename: req.file.filename + }, + message: '文件上传成功' + }); + } catch (error) { + console.error('文件上传失败:', error); + res.status(500).json({ success: false, message: '文件上传失败' }); + } +}); + +// 静态文件服务 - 上传文件 +app.use('/uploads', express.static(uploadDir)); + +// ==================== 预算报价管理 ==================== +app.get('/api/budget-projects', async (req, res) => { + try { + const { customer_id } = req.query; + let query = ` + SELECT b.*, + (SELECT json_group_array(json_object( + 'id', q.id, + 'version', q.version, + 'quotation_date', q.quotation_date, + 'amount', q.amount, + 'currency', q.currency, + 'status', q.status, + 'file_url', q.file_url, + 'remark', q.remark, + 'created_at', q.created_at + )) FROM budget_quotations q WHERE q.project_id = b.id) as quotations + FROM budget_projects b + `; + + if (customer_id) { + query += ` WHERE b.customer_id = ?`; + } + + query += ` ORDER BY b.created_at DESC`; + + const params = customer_id ? [customer_id] : []; + const result = await db.query(query, params); + + // 解析每个项目的附件和照片数据 + const projects = result.rows.map(project => { + try { + return { + ...project, + attachments: project.attachments ? JSON.parse(project.attachments) : [], + survey_photos: project.survey_photos ? JSON.parse(project.survey_photos) : [], + quotations: project.quotations ? JSON.parse(project.quotations) : [] + }; + } catch (error) { + console.error('解析项目数据失败:', error); + // 如果解析失败,返回原始数据,避免整个应用崩溃 + return { + ...project, + attachments: [], + survey_photos: [], + quotations: [] + }; + } + }); + + res.json({ success: true, data: projects }); + } catch (error) { + console.error('获取预算项目失败:', error); + res.status(500).json({ success: false, message: error.message }); + } +}); + +// 预算项目API已修改,支持按客户ID筛选 + +// ==================== 施工管理 ==================== +app.get('/api/construction/my-projects', async (req, res) => { + try { + const result = await db.query(` + SELECT p.*, + c.name as customer_name, + (SELECT json_object( + 'id', cl.id, + 'log_date', cl.log_date, + 'weather', cl.weather, + 'work_content', cl.work_content + ) FROM construction_logs cl WHERE cl.project_id = p.id ORDER BY cl.log_date DESC LIMIT 1) as latest_log + FROM projects p + LEFT JOIN customers c ON p.customer_id = c.id + WHERE p.status IN ('active', 'pending') + ORDER BY p.created_at DESC + `); + res.json({ success: true, data: result.rows }); + } catch (error) { + console.error('获取施工项目失败:', error); + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 分类管理API(树状结构)==================== + +// 获取分类树 +app.get('/api/categories/tree', async (req, res) => { + try { + const level = req.query.level; + let query = 'SELECT * FROM category_tree ORDER BY level, sort_order, id'; + const params = []; + + if (level) { + query = 'SELECT * FROM category_tree WHERE level = ? ORDER BY sort_order, id'; + params.push(parseInt(level)); + } + + const result = await db.query(query, params); + + if (level) { + res.json({ success: true, data: result.rows }); + } else { + const buildTree = (categories, parentId = null) => { + return categories + .filter(cat => cat.parent_id === parentId) + .map(cat => ({ + ...cat, + children: buildTree(categories, cat.id) + })); + }; + const tree = buildTree(result.rows); + res.json({ success: true, data: tree }); + } + } catch (error) { + console.error('获取分类树失败:', error); + res.status(500).json({ success: false, message: '获取分类失败', error: error.message }); + } +}); + +// 获取所有分类列表 +app.get('/api/categories', async (req, res) => { + try { + const result = await db.query('SELECT * FROM category_tree ORDER BY level, sort_order, id'); + res.json({ success: true, data: result.rows }); + } catch (error) { + console.error('获取分类失败:', error); + res.status(500).json({ success: false, message: '获取分类失败', error: error.message }); + } +}); + +// 获取单个分类 +app.get('/api/categories/:id', async (req, res) => { + try { + const { id } = req.params; + const result = await db.query('SELECT * FROM category_tree WHERE id = ?', [id]); + if (result.rows.length === 0) { + return res.status(404).json({ success: false, message: '分类不存在' }); + } + res.json({ success: true, data: result.rows[0] }); + } catch (error) { + console.error('获取分类失败:', error); + res.status(500).json({ success: false, message: '获取分类失败', error: error.message }); + } +}); + +// 创建分类 +app.post('/api/categories', async (req, res) => { + try { + const { name, parent_id, level, sort_order, description } = req.body; + + if (!name) { + return res.status(400).json({ success: false, message: '分类名称不能为空' }); + } + + const checkResult = await db.query( + 'SELECT id FROM category_tree WHERE name = ? AND (parent_id = ? OR (parent_id IS NULL AND ? IS NULL))', + [name, parent_id || null, parent_id || null] + ); + + if (checkResult.rows.length > 0) { + return res.status(400).json({ success: false, message: '该分类名称已存在' }); + } + + const result = await db.query( + 'INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, ?, ?, ?, ?)', + [name, parent_id || null, level || (parent_id ? 2 : 1), sort_order || 0, description || ''] + ); + + const newCategory = await db.query('SELECT * FROM category_tree WHERE id = ?', [result.lastID]); + res.json({ success: true, data: newCategory.rows[0], message: '创建成功' }); + } catch (error) { + console.error('创建分类失败:', error); + res.status(500).json({ success: false, message: '创建分类失败', error: error.message }); + } +}); + +// 更新分类 +app.put('/api/categories/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, parent_id, sort_order, description } = req.body; + + if (parent_id !== undefined) { + const checkLoop = async (currentId, targetParentId) => { + if (currentId === targetParentId) return true; + const children = await db.query('SELECT id FROM category_tree WHERE parent_id = ?', [currentId]); + for (const child of children.rows) { + if (await checkLoop(child.id, targetParentId)) return true; + } + return false; + }; + if (parent_id && await checkLoop(parseInt(id), parseInt(parent_id))) { + return res.status(400).json({ success: false, message: '不能将分类设置为自己的子分类' }); + } + } + + const updates = []; + const params = []; + if (name !== undefined) { updates.push('name = ?'); params.push(name); } + if (parent_id !== undefined) { updates.push('parent_id = ?'); params.push(parent_id || null); } + if (sort_order !== undefined) { updates.push('sort_order = ?'); params.push(sort_order); } + if (description !== undefined) { updates.push('description = ?'); params.push(description); } + + if (updates.length === 0) { + return res.status(400).json({ success: false, message: '没有要更新的字段' }); + } + + updates.push('updated_at = datetime(\'now\')'); + params.push(id); + + const result = await db.query( + `UPDATE category_tree SET ${updates.join(', ')} WHERE id = ?`, + params + ); + + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '分类不存在' }); + } + + const updated = await db.query('SELECT * FROM category_tree WHERE id = ?', [id]); + res.json({ success: true, data: updated.rows[0], message: '更新成功' }); + } catch (error) { + console.error('更新分类失败:', error); + res.status(500).json({ success: false, message: '更新分类失败', error: error.message }); + } +}); + +// 删除分类 +app.delete('/api/categories/:id', async (req, res) => { + try { + const { id } = req.params; + + const productCheck = await db.query('SELECT COUNT(*) as count FROM products WHERE category_id = ?', [id]); + if (productCheck.rows[0].count > 0) { + return res.status(400).json({ success: false, message: '该分类下还有商品,不能删除' }); + } + + const result = await db.query('DELETE FROM category_tree WHERE id = ?', [id]); + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '分类不存在' }); + } + + res.json({ success: true, message: '删除成功' }); + } catch (error) { + console.error('删除分类失败:', error); + res.status(500).json({ success: false, message: '删除分类失败', error: error.message }); + } +}); + +// ==================== 商品管理API ==================== + +// 获取商品列表 +app.get('/api/products', async (req, res) => { + try { + const { category_id, status, keyword } = req.query; + + let query = ` + SELECT p.*, ct.name as category_name, + (SELECT name FROM category_tree WHERE id = (SELECT parent_id FROM category_tree WHERE id = p.category_id)) as category_level1_name + FROM products p + LEFT JOIN category_tree ct ON p.category_id = ct.id + WHERE 1=1 + `; + const params = []; + + if (category_id) { + query += ' AND p.category_id = ?'; + params.push(category_id); + } + if (status) { + query += ' AND p.status = ?'; + params.push(status); + } + if (keyword) { + query += ' AND (p.name LIKE ? OR p.model LIKE ? OR p.brand LIKE ?)'; + const searchTerm = `%${keyword}%`; + params.push(searchTerm, searchTerm, searchTerm); + } + + query += ' ORDER BY p.created_at DESC'; + + const result = await db.query(query, params); + res.json({ success: true, data: result.rows, count: result.rows.length }); + } catch (error) { + console.error('获取商品失败:', error); + res.status(500).json({ success: false, message: '获取商品失败', error: error.message }); + } +}); + +// 下载商品导入模板(必须在 :id 路由之前定义) +app.get('/api/products/template', (req, res) => { + try { + const XLSX = require('xlsx'); + + const templateData = [ + { + '商品名称': 'JKLYJ-35-22kV', + '型号': 'Model-001', + '一级分类': '电缆电线', + '二级分类': '高压电缆', + '单位': '米', + '成本单价': 12.50, + '销售单价': 15.50, + '品牌': '云南线缆', + '规格参数': '35mm², 22kV', + '来源': '中国', + '备注': '示例商品' + }, + { + '商品名称': 'XP-70', + '型号': 'XP-70', + '一级分类': '电杆横担', + '二级分类': '横担', + '单位': '个', + '成本单价': 20.00, + '销售单价': 25.00, + '品牌': '江西电瓷', + '规格参数': '70kN', + '来源': '老挝', + '备注': '' + } + ]; + + const worksheet = XLSX.utils.json_to_sheet(templateData); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, '商品导入模板'); + + const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }); + + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', 'attachment; filename="product_template.xlsx"'); + res.send(buffer); + } catch (error) { + console.error('生成模板失败:', error); + res.status(500).json({ + success: false, + message: '生成模板失败', + error: error.message + }); + } +}); + +// 获取单个商品 +app.get('/api/products/:id', async (req, res) => { + try { + const { id } = req.params; + const result = await db.query(` + SELECT p.*, ct.name as category_name, + (SELECT name FROM category_tree WHERE id = (SELECT parent_id FROM category_tree WHERE id = p.category_id)) as category_level1_name + FROM products p + LEFT JOIN category_tree ct ON p.category_id = ct.id + WHERE p.id = ? + `, [id]); + + if (result.rows.length === 0) { + return res.status(404).json({ success: false, message: '商品不存在' }); + } + res.json({ success: true, data: result.rows[0] }); + } catch (error) { + console.error('获取商品失败:', error); + res.status(500).json({ success: false, message: '获取商品失败', error: error.message }); + } +}); + +// 创建商品 +app.post('/api/products', async (req, res) => { + try { + const { name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status } = req.body; + + if (!name) { + return res.status(400).json({ success: false, message: '商品名称不能为空' }); + } + + let categoryName = null; + if (category_id) { + const catResult = await db.query('SELECT name FROM category_tree WHERE id = ?', [category_id]); + if (catResult.rows.length > 0) { + categoryName = catResult.rows[0].name; + } + } + + const result = await db.query( + `INSERT INTO products (name, model, category_id, category_name, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + name, model || '', category_id || null, categoryName, + unit || '件', cost_price || null, price || 0, brand || '', + specification || '', source || '老挝', remark || '', + stock_quantity || 0, status || 'active' + ] + ); + + const newProduct = await db.query('SELECT * FROM products WHERE id = ?', [result.lastID]); + res.json({ success: true, data: newProduct.rows[0], message: '创建成功' }); + } catch (error) { + console.error('创建商品失败:', error); + res.status(500).json({ success: false, message: '创建商品失败', error: error.message }); + } +}); + +// 更新商品 +app.put('/api/products/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity, stock_warning, status } = req.body; + + let categoryName = null; + if (category_id !== undefined) { + if (category_id) { + const catResult = await db.query('SELECT name FROM category_tree WHERE id = ?', [category_id]); + if (catResult.rows.length > 0) { + categoryName = catResult.rows[0].name; + } + } + } + + const updates = []; + const params = []; + if (name !== undefined) { updates.push('name = ?'); params.push(name); } + if (model !== undefined) { updates.push('model = ?'); params.push(model || ''); } + if (category_id !== undefined) { + updates.push('category_id = ?'); + params.push(category_id || null); + updates.push('category_name = ?'); + params.push(categoryName); + } + if (unit !== undefined) { updates.push('unit = ?'); params.push(unit || '件'); } + if (cost_price !== undefined) { updates.push('cost_price = ?'); params.push(cost_price); } + if (price !== undefined) { updates.push('price = ?'); params.push(price || 0); } + if (brand !== undefined) { updates.push('brand = ?'); params.push(brand || ''); } + if (specification !== undefined) { updates.push('specification = ?'); params.push(specification || ''); } + if (source !== undefined) { updates.push('source = ?'); params.push(source || '老挝'); } + if (remark !== undefined) { updates.push('remark = ?'); params.push(remark || ''); } + if (stock_quantity !== undefined) { updates.push('stock_quantity = ?'); params.push(stock_quantity || 0); } + if (stock_warning !== undefined) { updates.push('stock_warning = ?'); params.push(stock_warning || 0); } + if (status !== undefined) { updates.push('status = ?'); params.push(status || 'active'); } + + if (updates.length === 0) { + return res.status(400).json({ success: false, message: '没有要更新的字段' }); + } + + updates.push('updated_at = datetime(\'now\')'); + params.push(id); + + const result = await db.query( + `UPDATE products SET ${updates.join(', ')} WHERE id = ?`, + params + ); + + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '商品不存在' }); + } + + const updated = await db.query('SELECT * FROM products WHERE id = ?', [id]); + res.json({ success: true, data: updated.rows[0], message: '更新成功' }); + } catch (error) { + console.error('更新商品失败:', error); + res.status(500).json({ success: false, message: '更新商品失败', error: error.message }); + } +}); + +// 删除商品 +app.delete('/api/products/:id', async (req, res) => { + try { + const { id } = req.params; + const result = await db.query('DELETE FROM products WHERE id = ?', [id]); + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '商品不存在' }); + } + res.json({ success: true, message: '删除成功' }); + } catch (error) { + console.error('删除商品失败:', error); + res.status(500).json({ success: false, message: '删除商品失败', error: error.message }); + } +}); + +// 批量导入商品(使用内存存储) +const memoryStorage = multer.memoryStorage(); +const uploadMemory = multer({ storage: memoryStorage, limits: { fileSize: 10 * 1024 * 1024 } }); + +app.post('/api/products/batch-import', uploadMemory.single('file'), async (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ + success: false, + message: '请选择要上传的文件' + }); + } + + const XLSX = require('xlsx'); + const workbook = XLSX.read(req.file.buffer, { type: 'buffer' }); + const sheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(worksheet); + + if (!data || data.length === 0) { + return res.status(400).json({ + success: false, + message: 'Excel文件为空或格式不正确' + }); + } + + const results = { + total: data.length, + success: 0, + failed: 0, + errors: [] + }; + + for (let i = 0; i < data.length; i++) { + const row = data[i]; + try { + const name = row['商品名称'] || row['name']; + if (!name) { + throw new Error('商品名称不能为空'); + } + + const model = row['型号'] || row['model'] || ''; + const categoryLevel1 = row['一级分类'] || row['category_level1'] || ''; + const categoryLevel2 = row['二级分类'] || row['category_level2'] || ''; + const unit = row['单位'] || row['unit'] || '件'; + const costPrice = parseFloat(row['成本单价'] || row['cost_price']) || null; + const price = parseFloat(row['销售单价'] || row['price']) || 0; + const brand = row['品牌'] || row['brand'] || ''; + const specification = row['规格参数'] || row['specification'] || ''; + const source = row['来源'] || row['source'] || '老挝'; + const remark = row['备注'] || row['remark'] || ''; + + let categoryId = null; + let categoryName = null; + + if (categoryLevel2) { + let level1 = await db.query('SELECT * FROM category_tree WHERE name = ? AND level = 1', [categoryLevel1]); + let level1Id; + if (level1.rows.length === 0) { + const newLevel1 = await db.query('INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, NULL, 1, 99, ?)', [categoryLevel1, '批量导入创建']); + level1Id = newLevel1.lastID; + } else { + level1Id = level1.rows[0].id; + } + + let level2 = await db.query('SELECT * FROM category_tree WHERE name = ? AND parent_id = ? AND level = 2', [categoryLevel2, level1Id]); + if (level2.rows.length === 0) { + const newLevel2 = await db.query('INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, ?, 2, 99, ?)', [categoryLevel2, level1Id, '批量导入创建']); + categoryId = newLevel2.lastID; + categoryName = categoryLevel2; + } else { + categoryId = level2.rows[0].id; + categoryName = categoryLevel2; + } + } else if (categoryLevel1) { + let level1 = await db.query('SELECT * FROM category_tree WHERE name = ? AND level = 1', [categoryLevel1]); + if (level1.rows.length === 0) { + const newLevel1 = await db.query('INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, NULL, 1, 99, ?)', [categoryLevel1, '批量导入创建']); + categoryId = newLevel1.lastID; + categoryName = categoryLevel1; + } else { + categoryId = level1.rows[0].id; + categoryName = categoryLevel1; + } + } + + const existingProduct = await db.query('SELECT id FROM products WHERE name = ? AND model = ?', [name, model]); + if (existingProduct.rows.length > 0) { + await db.query( + 'UPDATE products SET model = ?, category_id = ?, category_name = ?, unit = ?, cost_price = ?, price = ?, brand = ?, specification = ?, source = ?, remark = ?, updated_at = datetime(\'now\') WHERE id = ?', + [model, categoryId, categoryName, unit, costPrice, price, brand, specification, source, remark, existingProduct.rows[0].id] + ); + } else { + await db.query( + 'INSERT INTO products (name, model, category_id, category_name, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)', + [name, model, categoryId, categoryName, unit, costPrice, price, brand, specification, source, remark, 'active'] + ); + } + + results.success++; + } catch (error) { + results.failed++; + results.errors.push({ + row: i + 2, + item: name || `第${i + 1}行`, + error: error.message + }); + } + } + + res.json({ + success: true, + message: `导入完成:成功 ${results.success} 条,失败 ${results.failed} 条`, + data: results + }); + + } catch (error) { + console.error('批量导入商品失败:', error); + res.status(500).json({ + success: false, + message: '批量导入失败', + error: error.message + }); + } +}); + +// ==================== 付款节点API ==================== +app.get('/api/payment-nodes', async (req, res) => { + try { + const result = await db.query(` + SELECT + pn.*, + p.project_name, + p.project_code + FROM payment_nodes pn + LEFT JOIN projects p ON pn.project_id = p.project_id + ORDER BY pn.due_date ASC + LIMIT 50 + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取付款节点失败:', error); + res.status(500).json({ + success: false, + message: '获取付款节点失败', + error: error.message + }); + } +}); + +// ==================== 付款记录API ==================== +app.get('/api/payment-records', async (req, res) => { + try { + const result = await db.query(` + SELECT + pr.*, + pn.node_name, + p.project_name + FROM payment_records pr + LEFT JOIN payment_nodes pn ON pr.node_id = pn.node_id + LEFT JOIN projects p ON pn.project_id = p.project_id + ORDER BY pr.payment_date DESC + LIMIT 50 + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取付款记录失败:', error); + res.status(500).json({ + success: false, + message: '获取付款记录失败', + error: error.message + }); + } +}); + +// 权限检查中间件 +function checkAdmin(req, res, next) { + // 简单的权限检查,实际项目中应该从token中解析用户信息 + // 这里暂时假设只有管理员可以修改数据 + const userRole = req.headers['x-user-role'] || 'employee'; + if (userRole !== 'admin') { + return res.status(403).json({ success: false, message: '权限不足,仅管理员可操作' }); + } + next(); +} + +// ==================== 预算项目API ==================== +app.post('/api/budget-projects', checkAdmin, async (req, res) => { + try { + const { name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos } = req.body; + + // 确保 attachments 和 survey_photos 是数组 + const attachmentsArray = Array.isArray(attachments) ? attachments : []; + const surveyPhotosArray = Array.isArray(survey_photos) ? survey_photos : []; + + const result = await db.query( + `INSERT INTO budget_projects (name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, JSON.stringify(attachmentsArray), JSON.stringify(surveyPhotosArray), 'negotiating'] + ); + + const projectId = result.lastID; + + res.json({ + success: true, + message: '创建成功', + data: { + id: projectId, + name, + customer_id, + manager_id, + location, + survey_date, + intermediary, + intermediary_fee_type, + intermediary_fee_value, + customer_requirements, + project_overview, + attachments, + survey_photos, + status: 'negotiating', + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('创建预算项目失败:', error); + res.status(500).json({ + success: false, + message: '创建失败', + error: error.message + }); + } +}); + +// ==================== 预算项目详情API ==================== +app.get('/api/budget-projects/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query(` + SELECT b.*, + c.name as customer_name, + u.name as manager_name, + (SELECT json_group_array(json_object( + 'id', q.id, + 'version', q.version, + 'quotation_date', q.quotation_date, + 'amount', q.amount, + 'currency', q.currency, + 'status', q.status, + 'file_url', q.file_url, + 'remark', q.remark, + 'created_at', q.created_at + )) FROM budget_quotations q WHERE q.project_id = b.id) as quotations + FROM budget_projects b + LEFT JOIN customers c ON b.customer_id = c.id + LEFT JOIN users u ON b.manager_id = u.id + WHERE b.id = ? + `, [id]); + + if (result.rows.length > 0) { + const project = result.rows[0]; + try { + // 解析JSON字符串为数组 + project.attachments = project.attachments ? JSON.parse(project.attachments) : []; + project.survey_photos = project.survey_photos ? JSON.parse(project.survey_photos) : []; + project.quotations = project.quotations ? JSON.parse(project.quotations) : []; + } catch (error) { + console.error('解析项目数据失败:', error); + // 如果解析失败,设置默认值 + project.attachments = []; + project.survey_photos = []; + project.quotations = []; + } + res.json({ success: true, data: project }); + } else { + res.status(404).json({ success: false, message: '项目不存在' }); + } + } catch (error) { + console.error('获取预算项目详情失败:', error); + res.status(500).json({ success: false, message: error.message }); + } +}); + +// ==================== 预算报价API ==================== +app.post('/api/budget-projects/:projectId/quotations', checkAdmin, async (req, res) => { + try { + const { projectId } = req.params; + const { quotation_date, amount, currency, file_url, remark, version } = req.body; + + const result = await db.query( + `INSERT INTO budget_quotations (project_id, version, quotation_date, amount, currency, status, file_url, remark, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [projectId, version, quotation_date, amount, currency, 'draft', file_url, remark] + ); + + const quotationId = result.lastID; + + res.json({ + success: true, + message: '新增报价版本成功', + data: { + id: quotationId, + project_id: projectId, + version, + quotation_date, + amount, + currency, + status: 'draft', + file_url, + remark, + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('创建报价版本失败:', error); + res.status(500).json({ + success: false, + message: '创建失败', + error: error.message + }); + } +}); + +app.delete('/api/budget-projects/:projectId/quotations/:quotationId', checkAdmin, async (req, res) => { + try { + const { projectId, quotationId } = req.params; + + const result = await db.query( + `DELETE FROM budget_quotations WHERE id = ? AND project_id = ?`, + [quotationId, projectId] + ); + + if (result.changes > 0) { + res.json({ + success: true, + message: '删除成功' + }); + } else { + res.status(404).json({ + success: false, + message: '报价版本不存在' + }); + } + } catch (error) { + console.error('删除报价版本失败:', error); + res.status(500).json({ + success: false, + message: '删除失败', + error: error.message + }); + } +}); + +// ==================== 预算项目状态更新API ==================== +app.put('/api/budget-projects/:id/sign', checkAdmin, async (req, res) => { + try { + console.log('收到签约请求:', req.body); + const { id } = req.params; + const { + contract_code, + project_name, + contract_method, + currency, + contract_amount, + start_date, + end_date, + contract_period, + project_overview, + other_requirements, + warranty_deposit_percentage, + warranty_period, + contract_file, + payment_nodes, + unit_price_items + } = req.body; + + console.log('解析请求参数成功:', { + id, + contract_code, + project_name, + contract_method, + currency, + contract_amount, + start_date, + end_date, + contract_period, + project_overview, + other_requirements, + warranty_deposit_percentage, + warranty_period, + contract_file, + payment_nodes: payment_nodes?.length, + unit_price_items: unit_price_items?.length + }); + + // 1. 获取预算项目详细信息 + const budgetProjectResult = await db.query( + `SELECT b.*, + c.name as customer_name, + (SELECT json_group_array(json_object( + 'id', q.id, + 'version', q.version, + 'quotation_date', q.quotation_date, + 'amount', q.amount, + 'currency', q.currency, + 'status', q.status, + 'file_url', q.file_url, + 'remark', q.remark, + 'created_at', q.created_at + )) FROM budget_quotations q WHERE q.project_id = b.id ORDER BY q.version DESC LIMIT 1) as latest_quotation + FROM budget_projects b + LEFT JOIN customers c ON b.customer_id = c.id + WHERE b.id = ?`, + [id] + ); + + if (budgetProjectResult.rows.length === 0) { + return res.status(404).json({ success: false, message: '预算项目不存在' }); + } + + const budgetProject = budgetProjectResult.rows[0]; + + // 2. 获取最新报价信息 + let latestQuotation = null; + let defaultContractAmount = 0; + if (budgetProject.latest_quotation) { + try { + const quotations = JSON.parse(budgetProject.latest_quotation); + if (quotations && quotations.length > 0) { + latestQuotation = quotations[0]; + defaultContractAmount = parseFloat(latestQuotation.amount) || 0; + } + } catch (e) { + console.error('解析报价信息失败:', e); + } + } + + // 3. 生成项目代码 + const today = new Date(); + const dateStr = today.toISOString().split('T')[0].replace(/-/g, ''); + + // 获取当天项目数量,生成序号 + const projectCountResult = await db.query( + `SELECT COUNT(*) as count FROM projects WHERE DATE(created_at) = DATE('now')` + ); + + const projectCount = parseInt(projectCountResult.rows[0].count) || 0; + const sequence = String(projectCount + 1).padStart(3, '0'); + const projectCode = `PROJ-${dateStr}-${sequence}`; + + // 4. 计算项目时间 + const startDate = today.toISOString(); + const endDate = new Date(today.getTime() + 6 * 30 * 24 * 60 * 60 * 1000).toISOString(); + + // 5. 创建项目 + const finalContractAmount = contract_amount || defaultContractAmount; + const projectResult = await db.query( + `INSERT INTO projects (code, name, customer_id, manager_id, status, contract_amount, start_date, end_date, description, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [ + projectCode, + project_name || budgetProject.name, + budgetProject.customer_id, + budgetProject.manager_id, + 'active', + finalContractAmount, + start_date || startDate, + end_date || endDate, + project_overview || budgetProject.project_overview || '' + ] + ); + + const newProjectId = projectResult.lastID; + + // 6. 创建项目合同 + const contractCode = contract_code || `CONTRACT-${dateStr}-${sequence}`; + const finalContractMethod = contract_method || 'lump_sum'; + const finalContractPeriod = contract_period || (end_date && start_date ? Math.floor((new Date(end_date).getTime() - new Date(start_date).getTime()) / (1000 * 60 * 60 * 24)) : 180); + const finalWarrantyPercentage = warranty_deposit_percentage || 5; + const finalWarrantyPeriod = warranty_period || 12; + + await db.query( + `INSERT INTO project_contracts (project_id, contract_code, contract_amount, currency, settlement_method, contract_period, start_date, end_date, warranty_deposit_percentage, warranty_period, contract_file, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [ + newProjectId, + contractCode, + finalContractAmount, + currency || 'CNY', + finalContractMethod, + finalContractPeriod, + start_date || startDate, + end_date || endDate, + finalWarrantyPercentage, + finalWarrantyPeriod, + contract_file || null + ] + ); + + // 7. 创建付款节点 + if (payment_nodes && Array.isArray(payment_nodes)) { + for (const node of payment_nodes) { + await db.query( + `INSERT INTO project_milestones (project_id, milestone_name, percentage, amount, expected_date, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [ + newProjectId, + node.node_name || `节点${node.id}`, + node.percentage || 0, + node.amount || 0, + start_date || startDate, + 'pending' + ] + ); + } + } + + // 8. 创建单价项(如果是单价结算) + if (unit_price_items && Array.isArray(unit_price_items)) { + for (const item of unit_price_items) { + await db.query( + `INSERT INTO project_materials (project_id, product_name, unit, budget_quantity, average_price, total_amount, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, + [ + newProjectId, + item.name || `单项${item.id}`, + item.unit || '个', + item.quantity || 0, + item.price || 0, + item.total || 0 + ] + ); + } + } + + // 9. 更新预算项目状态 + await db.query( + `UPDATE budget_projects SET status = 'signed', updated_at = datetime('now') WHERE id = ?`, + [id] + ); + + res.json({ + success: true, + message: '标记签约成功,项目已自动创建', + data: { + project_id: newProjectId, + project_code: projectCode, + contract_code: contractCode + } + }); + } catch (error) { + console.error('标记签约失败:', error); + console.error('错误堆栈:', error.stack); + res.status(500).json({ + success: false, + message: '操作失败', + error: error.message, + stack: error.stack + }); + } +}); + +app.put('/api/budget-projects/:id/unsigned', checkAdmin, async (req, res) => { + try { + const { id } = req.params; + + await db.query( + `UPDATE budget_projects SET status = 'unsigned', updated_at = datetime('now') WHERE id = ?`, + [id] + ); + + res.json({ + success: true, + message: '标记未签约成功' + }); + } catch (error) { + console.error('标记未签约失败:', error); + res.status(500).json({ + success: false, + message: '操作失败', + error: error.message + }); + } +}); + +// ==================== 删除预算项目API ==================== +app.delete('/api/budget-projects/:id', checkAdmin, async (req, res) => { + try { + const { id } = req.params; + + // 先删除关联的报价 + await db.query(`DELETE FROM budget_quotations WHERE project_id = ?`, [id]); + + // 再删除预算项目 + const result = await db.query(`DELETE FROM budget_projects WHERE id = ?`, [id]); + + if (result.changes > 0) { + res.json({ + success: true, + message: '删除成功' + }); + } else { + res.status(404).json({ + success: false, + message: '项目不存在' + }); + } + } catch (error) { + console.error('删除预算项目失败:', error); + res.status(500).json({ + success: false, + message: '删除失败', + error: error.message + }); + } +}); + +// ==================== 汇率API ==================== +app.get('/api/exchange-rates/latest', async (req, res) => { + try { + const result = await db.query(` + SELECT pair_key, rate + FROM exchange_rates + WHERE effective_date <= DATE('now') + GROUP BY pair_key + ORDER BY effective_date DESC + `); + + const data = {}; + result.rows.forEach(row => { + data[row.pair_key] = row.rate; + }); + + // 如果没有数据,使用默认值 + if (Object.keys(data).length === 0) { + data.CNY_LAK = 2900; + data.CNY_USD = 0.143; + data.CNY_THB = 4.8; + data.USD_LAK = 20300; + data.THB_LAK = 604; + } + + res.json({ + success: true, + data: data, + updated_at: new Date().toISOString(), + date: new Date().toISOString().split('T')[0] + }); + } catch (error) { + console.error('获取汇率失败:', error); + res.status(500).json({ success: false, message: '获取汇率失败', error: error.message }); + } +}); + +app.get('/api/exchange-rates', async (req, res) => { + try { + const result = await db.query(` + SELECT * FROM exchange_rates + ORDER BY effective_date DESC + LIMIT 20 + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取汇率失败:', error); + res.status(500).json({ + success: false, + message: '获取汇率失败', + error: error.message + }); + } +}); + +app.get('/api/exchange-rates/history', async (req, res) => { + try { + const limit = req.query.limit || 20; + const result = await db.query(` + SELECT * FROM exchange_rates + ORDER BY created_at DESC + LIMIT ? + `, [limit]); + + // 转换数据格式以匹配前端期望 + const formattedData = result.rows.map(row => { + const [from_currency, to_currency] = row.pair_key.split('_'); + return { + ...row, + from_currency, + to_currency + }; + }); + + res.json({ + success: true, + data: formattedData + }); + } catch (error) { + console.error('获取历史汇率失败:', error); + res.status(500).json({ + success: false, + message: '获取历史汇率失败', + error: error.message + }); + } +}); + +app.post('/api/exchange-rates', async (req, res) => { + try { + const { pair_key, rate, effective_date } = req.body; + + if (!pair_key || rate === undefined || !effective_date) { + return res.status(400).json({ success: false, message: '缺少必要参数' }); + } + + const result = await db.query( + `INSERT INTO exchange_rates (pair_key, rate, effective_date, created_at, updated_at) + VALUES (?, ?, ?, datetime('now'), datetime('now'))`, + [pair_key, rate, effective_date] + ); + + res.json({ + success: true, + message: '汇率保存成功', + data: { + id: result.lastID, + pair_key, + rate, + effective_date, + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('保存汇率失败:', error); + res.status(500).json({ + success: false, + message: '保存汇率失败', + error: error.message + }); + } +}); + +// ==================== 预支款API ==================== +app.get('/api/advances', async (req, res) => { + try { + const result = await db.query(` + SELECT a.*, u.name as user_name, p.name as project_name + FROM advances a + LEFT JOIN users u ON a.user_id = u.id + LEFT JOIN projects p ON a.project_id = p.id + ORDER BY a.created_at DESC + `); + + // 解析每个预支申请的 attachments 字段为数组 + const data = result.rows.map(item => { + if (item.attachments) { + try { + item.attachments = JSON.parse(item.attachments); + } catch (error) { + item.attachments = []; + } + } else { + item.attachments = []; + } + return item; + }); + + res.json({ success: true, data, count: data.length }); + } catch (error) { + console.error('获取预支款失败:', error); + res.status(500).json({ + success: false, + message: '获取预支款失败', + error: error.message + }); + } +}); + +// ==================== 创建预支申请 ==================== +app.post('/api/advances', [ + body('amount').isFloat({ min: 0.01 }), + body('reason').notEmpty() +], validate, async (req, res) => { + try { + const { amount, reason, project_id, currency, advance_date, attachments, amount_cny, applicant, status } = req.body; + const user_id = 1; // 临时使用admin用户 + + // 生成预支编号 + const advanceCode = `ADV-${Date.now()}`; + + const result = await db.query( + 'INSERT INTO advances (user_id, project_id, amount, currency, amount_cny, reason, advance_date, advance_code, status, applicant, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [user_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, advance_date, advanceCode, status || 'pending', applicant, JSON.stringify(attachments || [])] + ); + + // SQLite不支持RETURNING,所以需要查询刚插入的数据 + const lastInsert = await db.query('SELECT * FROM advances ORDER BY id DESC LIMIT 1'); + const data = lastInsert.rows[0]; + // 解析 attachments 字段为数组 + if (data.attachments) { + try { + data.attachments = JSON.parse(data.attachments); + } catch (error) { + data.attachments = []; + } + } else { + data.attachments = []; + } + res.json({ success: true, data }); + } catch (error) { + console.error('创建预支申请失败:', error); + res.status(500).json({ success: false, message: '创建预支申请失败', error: error.message }); + } +}); + +// ==================== 获取单个预支申请 ==================== +app.get('/api/advances/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('SELECT * FROM advances WHERE id = ?', [id]); + + if (result.rows.length > 0) { + const data = result.rows[0]; + // 解析 attachments 字段为数组 + if (data.attachments) { + try { + data.attachments = JSON.parse(data.attachments); + } catch (error) { + data.attachments = []; + } + } else { + data.attachments = []; + } + res.json({ success: true, data }); + } else { + res.status(404).json({ success: false, message: '预支申请不存在' }); + } + } catch (error) { + console.error('获取预支申请失败:', error); + res.status(500).json({ success: false, message: '获取预支申请失败', error: error.message }); + } +}); + +// ==================== 更新预支申请 ==================== +app.put('/api/advances/:id', async (req, res) => { + try { + const { id } = req.params; + const { amount, reason, project_id, currency, advance_date, attachments, amount_cny, applicant, status } = req.body; + + const result = await db.query( + 'UPDATE advances SET amount = ?, reason = ?, project_id = ?, currency = ?, advance_date = ?, attachments = ?, amount_cny = ?, applicant = ?, status = ? WHERE id = ?', + [amount, reason, project_id, currency, advance_date, JSON.stringify(attachments || []), amount_cny, applicant, status, id] + ); + + if (result.changes > 0) { + res.json({ success: true, message: '更新成功' }); + } else { + res.status(404).json({ success: false, message: '预支申请不存在' }); + } + } catch (error) { + console.error('更新预支申请失败:', error); + res.status(500).json({ success: false, message: '更新预支申请失败', error: error.message }); + } +}); + +// ==================== 删除预支申请 ==================== +app.delete('/api/advances/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('DELETE FROM advances WHERE id = ?', [id]); + + if (result.changes > 0) { + res.json({ success: true, message: '删除成功' }); + } else { + res.status(404).json({ success: false, message: '预支申请不存在' }); + } + } catch (error) { + console.error('删除预支申请失败:', error); + res.status(500).json({ success: false, message: '删除预支申请失败', error: error.message }); + } +}); + +// ==================== 提交预支申请 ==================== +app.post('/api/advances/:id/submit', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE advances SET status = ? WHERE id = ?', ['pending', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '提交成功' }); + } else { + res.status(404).json({ success: false, message: '预支申请不存在' }); + } + } catch (error) { + console.error('提交预支申请失败:', error); + res.status(500).json({ success: false, message: '提交预支申请失败', error: error.message }); + } +}); + +// ==================== 撤回预支申请 ==================== +app.post('/api/advances/:id/withdraw', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE advances SET status = ? WHERE id = ?', ['withdrawn', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '撤回成功' }); + } else { + res.status(404).json({ success: false, message: '预支申请不存在' }); + } + } catch (error) { + console.error('撤回预支申请失败:', error); + res.status(500).json({ success: false, message: '撤回预支申请失败', error: error.message }); + } +}); + +// ==================== 审批预支申请 ==================== +app.post('/api/advances/:id/approve', async (req, res) => { + try { + const { id } = req.params; + const { remark } = req.body; + + const result = await db.query('UPDATE advances SET status = ?, approval_remark = ? WHERE id = ?', ['approved', remark, id]); + + if (result.changes > 0) { + res.json({ success: true, message: '审批通过成功' }); + } else { + res.status(404).json({ success: false, message: '预支申请不存在' }); + } + } catch (error) { + console.error('审批预支申请失败:', error); + res.status(500).json({ success: false, message: '审批预支申请失败', error: error.message }); + } +}); + +// ==================== 退回预支申请 ==================== +app.post('/api/advances/:id/reject', async (req, res) => { + try { + const { id } = req.params; + const { rejectReason } = req.body; + + const result = await db.query('UPDATE advances SET status = ? WHERE id = ?', ['pending_edit', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '退回成功' }); + } else { + res.status(404).json({ success: false, message: '预支申请不存在' }); + } + } catch (error) { + console.error('退回预支申请失败:', error); + res.status(500).json({ success: false, message: '退回预支申请失败', error: error.message }); + } +}); + +// ==================== 付款申请API ==================== +app.get('/api/payment-requests', async (req, res) => { + try { + const result = await db.query(` + SELECT * FROM payment_requests + ORDER BY created_at DESC + `); + + const data = result.rows.map(item => { + if (item.attachments) { + try { + item.attachments = JSON.parse(item.attachments); + } catch (error) { + item.attachments = []; + } + } else { + item.attachments = []; + } + if (item.detail_items) { + try { + item.detail_items = JSON.parse(item.detail_items); + } catch (error) { + item.detail_items = []; + } + } else { + item.detail_items = []; + } + return item; + }); + + res.json({ success: true, data, count: data.length }); + } catch (error) { + console.error('获取付款申请失败:', error); + res.status(500).json({ success: false, message: '获取付款申请失败', error: error.message }); + } +}); + +app.post('/api/payment-requests', async (req, res) => { + try { + const { + payment_date, payee, bank_account, bank_name, currency, reason, + detail_items, attachments, applicant, + payee_type, payee_id, expense_type, expense_category, project_id, amount + } = req.body; + + // 生成付款申请编号 + const requestCode = `PAY-${Date.now()}`; + + // 使用默认值处理可选字段 + const finalBankAccount = bank_account || ''; + const finalBankName = bank_name || ''; + const finalAmount = amount || 0; + + const result = await db.query( + `INSERT INTO payment_requests ( + payee, bank_account, bank_name, amount, currency, reason, payment_date, + request_code, status, applicant, detail_items, attachments, + payee_type, payee_id, expense_type, expense_category, project_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + payee, finalBankAccount, finalBankName, finalAmount, currency || 'CNY', + reason, payment_date, requestCode, 'pending', applicant, + JSON.stringify(detail_items || []), JSON.stringify(attachments || []), + payee_type || 'other', payee_id || null, expense_type || 'company', + expense_category || '', project_id || null + ] + ); + + // SQLite不支持RETURNING,所以需要查询刚插入的数据 + const lastInsert = await db.query('SELECT * FROM payment_requests ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + console.error('创建付款申请失败:', error); + res.status(500).json({ success: false, message: '创建付款申请失败', error: error.message }); + } +}); + +app.get('/api/payment-requests/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('SELECT * FROM payment_requests WHERE id = ?', [id]); + + if (result.rows.length > 0) { + const data = result.rows[0]; + if (data.attachments) { + try { + data.attachments = JSON.parse(data.attachments); + } catch (error) { + data.attachments = []; + } + } else { + data.attachments = []; + } + if (data.detail_items) { + try { + data.detail_items = JSON.parse(data.detail_items); + } catch (error) { + data.detail_items = []; + } + } else { + data.detail_items = []; + } + res.json({ success: true, data }); + } else { + res.status(404).json({ success: false, message: '付款申请不存在' }); + } + } catch (error) { + console.error('获取付款申请失败:', error); + res.status(500).json({ success: false, message: '获取付款申请失败', error: error.message }); + } +}); + +app.put('/api/payment-requests/:id', async (req, res) => { + try { + const { id } = req.params; + const { + payment_date, payee, bank_account, bank_name, currency, reason, + detail_items, attachments, applicant, status, + payee_type, payee_id, expense_type, expense_category, project_id, amount + } = req.body; + + // 构建动态更新SQL,只更新提供的字段 + const updates = []; + const params = []; + + if (payment_date !== undefined) { updates.push('payment_date = ?'); params.push(payment_date); } + if (payee !== undefined) { updates.push('payee = ?'); params.push(payee); } + if (bank_account !== undefined) { updates.push('bank_account = ?'); params.push(bank_account); } + if (bank_name !== undefined) { updates.push('bank_name = ?'); params.push(bank_name); } + if (amount !== undefined) { updates.push('amount = ?'); params.push(amount); } + if (currency !== undefined) { updates.push('currency = ?'); params.push(currency); } + if (reason !== undefined) { updates.push('reason = ?'); params.push(reason); } + if (detail_items !== undefined) { updates.push('detail_items = ?'); params.push(JSON.stringify(detail_items || [])); } + if (attachments !== undefined) { updates.push('attachments = ?'); params.push(JSON.stringify(attachments || [])); } + if (applicant !== undefined) { updates.push('applicant = ?'); params.push(applicant); } + if (status !== undefined) { updates.push('status = ?'); params.push(status); } + if (payee_type !== undefined) { updates.push('payee_type = ?'); params.push(payee_type); } + if (payee_id !== undefined) { updates.push('payee_id = ?'); params.push(payee_id); } + if (expense_type !== undefined) { updates.push('expense_type = ?'); params.push(expense_type); } + if (expense_category !== undefined) { updates.push('expense_category = ?'); params.push(expense_category); } + if (project_id !== undefined) { updates.push('project_id = ?'); params.push(project_id); } + + if (updates.length === 0) { + return res.status(400).json({ success: false, message: '没有要更新的字段' }); + } + + params.push(id); + + const result = await db.query( + `UPDATE payment_requests SET ${updates.join(', ')} WHERE id = ?`, + params + ); + + if (result.changes > 0) { + res.json({ success: true, message: '更新成功' }); + } else { + res.status(404).json({ success: false, message: '付款申请不存在' }); + } + } catch (error) { + console.error('更新付款申请失败:', error); + res.status(500).json({ success: false, message: '更新付款申请失败', error: error.message }); + } +}); + +app.delete('/api/payment-requests/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('DELETE FROM payment_requests WHERE id = ?', [id]); + + if (result.changes > 0) { + res.json({ success: true, message: '删除成功' }); + } else { + res.status(404).json({ success: false, message: '付款申请不存在' }); + } + } catch (error) { + console.error('删除付款申请失败:', error); + res.status(500).json({ success: false, message: '删除付款申请失败', error: error.message }); + } +}); + +// ==================== 提交付款申请 ==================== +app.post('/api/payment-requests/:id/submit', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE payment_requests SET status = ? WHERE id = ?', ['pending', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '提交成功' }); + } else { + res.status(404).json({ success: false, message: '付款申请不存在' }); + } + } catch (error) { + console.error('提交付款申请失败:', error); + res.status(500).json({ success: false, message: '提交付款申请失败', error: error.message }); + } +}); + +app.post('/api/payment-requests/:id/withdraw', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE payment_requests SET status = ? WHERE id = ?', ['withdrawn', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '撤回成功' }); + } else { + res.status(404).json({ success: false, message: '付款申请不存在' }); + } + } catch (error) { + console.error('撤回报销申请失败:', error); + res.status(500).json({ success: false, message: '撤回报销申请失败', error: error.message }); + } +}); + +app.post('/api/payment-requests/:id/approve', async (req, res) => { + try { + const { id } = req.params; + const { remark } = req.body; + + const result = await db.query('UPDATE payment_requests SET status = ?, approval_remark = ? WHERE id = ?', ['approved', remark, id]); + + if (result.changes > 0) { + res.json({ success: true, message: '审批通过成功' }); + } else { + res.status(404).json({ success: false, message: '付款申请不存在' }); + } + } catch (error) { + console.error('审批付款申请失败:', error); + res.status(500).json({ success: false, message: '审批付款申请失败', error: error.message }); + } +}); + +app.post('/api/payment-requests/:id/reject', async (req, res) => { + try { + const { id } = req.params; + const { rejectReason } = req.body; + + const result = await db.query('UPDATE payment_requests SET status = ? WHERE id = ?', ['pending_edit', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '退回成功' }); + } else { + res.status(404).json({ success: false, message: '付款申请不存在' }); + } + } catch (error) { + console.error('退回报销申请失败:', error); + res.status(500).json({ success: false, message: '退回报销申请失败', error: error.message }); + } +}); + +// ==================== 核销申请API ==================== +app.get('/api/verifications', async (req, res) => { + try { + const { advance_id } = req.query; + let query = ` + SELECT v.*, a.advance_code, a.applicant as advance_applicant + FROM verifications v + LEFT JOIN advances a ON v.advance_id = a.id + `; + const params = []; + + if (advance_id) { + query += ` WHERE v.advance_id = ?`; + params.push(advance_id); + } + + query += ` ORDER BY v.created_at DESC`; + + const result = await db.query(query, params); + + const data = result.rows.map(item => { + if (item.attachments) { + try { + item.attachments = JSON.parse(item.attachments); + } catch (error) { + item.attachments = []; + } + } else { + item.attachments = []; + } + if (item.detail_items) { + try { + item.detail_items = JSON.parse(item.detail_items); + } catch (error) { + item.detail_items = []; + } + } else { + item.detail_items = []; + } + return item; + }); + + res.json({ success: true, data, count: data.length }); + } catch (error) { + console.error('获取核销记录失败:', error); + res.status(500).json({ success: false, message: '获取核销记录失败', error: error.message }); + } +}); + +app.post('/api/verifications', async (req, res) => { + try { + const { verification_date, advance_id, advance_code, advance_amount, currency, reason, detail_items, attachments, applicant, expense_type, project_id, settlement, settlement_amount } = req.body; + + // 生成核销编号 + const verificationCode = `VER-${Date.now()}`; + const amount = detail_items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0; + + // 验证关联预支单 + if (!advance_id && !advance_code) { + return res.status(400).json({ success: false, message: '关联预支单是必填项' }); + } + + let finalAdvanceCode = advance_code; + let finalAdvanceId = advance_id; + + // 如果advance_code为空,根据advance_id查询预支单的advance_code + if (!finalAdvanceCode && finalAdvanceId) { + const advanceResult = await db.query('SELECT advance_code FROM advances WHERE id = ?', [finalAdvanceId]); + if (advanceResult.rows.length > 0) { + finalAdvanceCode = advanceResult.rows[0].advance_code; + } else { + return res.status(400).json({ success: false, message: '关联的预支单不存在' }); + } + } + + // 如果advance_id为空,根据advance_code查询预支单的id + if (!finalAdvanceId && finalAdvanceCode) { + const advanceResult = await db.query('SELECT id FROM advances WHERE advance_code = ?', [finalAdvanceCode]); + if (advanceResult.rows.length > 0) { + finalAdvanceId = advanceResult.rows[0].id; + } else { + return res.status(400).json({ success: false, message: '关联的预支单不存在' }); + } + } + + // 如果仍然为空,返回错误 + if (!finalAdvanceCode || !finalAdvanceId) { + return res.status(400).json({ success: false, message: '关联预支单不存在' }); + } + + // 开始事务 + await db.query('BEGIN TRANSACTION'); + + try { + // 插入核销申请 + const result = await db.query( + 'INSERT INTO verifications (advance_id, amount, currency, reason, verification_date, verification_code, status, applicant, advance_code, advance_amount, detail_items, attachments, expense_type, project_id, settlement, settlement_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [finalAdvanceId, amount, currency || 'CNY', reason, verification_date, verificationCode, 'pending', applicant, finalAdvanceCode, advance_amount || 0, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), expense_type || 'company', project_id, settlement ? 1 : 0, settlement_amount || 0] + ); + + // 提交事务 + await db.query('COMMIT'); + + // SQLite不支持RETURNING,所以需要查询刚插入的数据 + const lastInsert = await db.query('SELECT * FROM verifications ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + // 回滚事务 + await db.query('ROLLBACK'); + throw error; + } + } catch (error) { + console.error('创建核销申请失败:', error); + res.status(500).json({ success: false, message: '创建核销申请失败', error: error.message }); + } +}); + +app.get('/api/verifications/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('SELECT * FROM verifications WHERE id = ?', [id]); + + if (result.rows.length > 0) { + const data = result.rows[0]; + if (data.attachments) { + try { + data.attachments = JSON.parse(data.attachments); + } catch (error) { + data.attachments = []; + } + } else { + data.attachments = []; + } + if (data.detail_items) { + try { + data.detail_items = JSON.parse(data.detail_items); + } catch (error) { + data.detail_items = []; + } + } else { + data.detail_items = []; + } + res.json({ success: true, data }); + } else { + res.status(404).json({ success: false, message: '核销申请不存在' }); + } + } catch (error) { + console.error('获取核销申请失败:', error); + res.status(500).json({ success: false, message: '获取核销申请失败', error: error.message }); + } +}); + +app.put('/api/verifications/:id', async (req, res) => { + try { + const { id } = req.params; + const { verification_date, advance_id, advance_code, advance_amount, currency, reason, detail_items, attachments, applicant, status, expense_type, project_id, settlement, settlement_amount } = req.body; + const amount = detail_items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0; + + // 开始事务 + await db.query('BEGIN TRANSACTION'); + + try { + // 获取原核销金额 + const oldVerification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = ?', [id]); + const oldAmount = oldVerification.rows[0]?.amount || 0; + const oldAdvanceId = oldVerification.rows[0]?.advance_id; + + let finalAdvanceCode = advance_code; + + // 如果advance_code为空,根据advance_id查询预支单的advance_code + if (!finalAdvanceCode && advance_id) { + const advanceResult = await db.query('SELECT advance_code FROM advances WHERE id = ?', [advance_id]); + if (advanceResult.rows.length > 0) { + finalAdvanceCode = advanceResult.rows[0].advance_code; + } + } + + // 如果仍然为空,使用默认值 + if (!finalAdvanceCode) { + finalAdvanceCode = 'UNKNOWN'; + } + + // 更新核销申请 + const result = await db.query( + 'UPDATE verifications SET verification_date = ?, advance_id = ?, amount = ?, currency = ?, reason = ?, advance_code = ?, advance_amount = ?, detail_items = ?, attachments = ?, applicant = ?, status = ?, expense_type = ?, project_id = ?, settlement = ?, settlement_amount = ? WHERE id = ?', + [verification_date, advance_id, amount, currency, reason, finalAdvanceCode, advance_amount || 0, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), applicant, status, expense_type || 'company', project_id, settlement ? 1 : 0, settlement_amount || 0, id] + ); + + // 不在这里更新预支单已核销金额,而是在执行核销时更新 + // if (oldAdvanceId) { + // const amountDiff = amount - oldAmount; + // if (amountDiff !== 0) { + // await db.query( + // 'UPDATE advances SET total_reimbursed = total_reimbursed + ? WHERE id = ?', + // [amountDiff, oldAdvanceId] + // ); + // } + // } + + // 提交事务 + await db.query('COMMIT'); + + if (result.changes > 0) { + res.json({ success: true, message: '更新成功' }); + } else { + res.status(404).json({ success: false, message: '核销申请不存在' }); + } + } catch (error) { + // 回滚事务 + await db.query('ROLLBACK'); + throw error; + } + } catch (error) { + console.error('更新核销申请失败:', error); + res.status(500).json({ success: false, message: '更新核销申请失败', error: error.message }); + } +}); + +app.delete('/api/verifications/:id', async (req, res) => { + try { + const { id } = req.params; + + // 开始事务 + await db.query('BEGIN TRANSACTION'); + + try { + // 获取核销金额和预支单ID + const verification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = ?', [id]); + const amount = verification.rows[0]?.amount || 0; + const advanceId = verification.rows[0]?.advance_id; + + // 删除核销申请 + const result = await db.query('DELETE FROM verifications WHERE id = ?', [id]); + + // 不在这里恢复预支单已核销金额,因为只有执行的核销才会增加已核销金额 + // if (advanceId && amount > 0) { + // await db.query( + // 'UPDATE advances SET total_reimbursed = total_reimbursed - ? WHERE id = ?', + // [amount, advanceId] + // ); + // } + + // 提交事务 + await db.query('COMMIT'); + + if (result.changes > 0) { + res.json({ success: true, message: '删除成功' }); + } else { + res.status(404).json({ success: false, message: '核销申请不存在' }); + } + } catch (error) { + // 回滚事务 + await db.query('ROLLBACK'); + throw error; + } + } catch (error) { + console.error('删除核销申请失败:', error); + res.status(500).json({ success: false, message: '删除核销申请失败', error: error.message }); + } +}); + +// ==================== 提交核销申请 ==================== +app.post('/api/verifications/:id/submit', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['pending', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '提交成功' }); + } else { + res.status(404).json({ success: false, message: '核销申请不存在' }); + } + } catch (error) { + console.error('提交核销申请失败:', error); + res.status(500).json({ success: false, message: '提交核销申请失败', error: error.message }); + } +}); + +app.post('/api/verifications/:id/withdraw', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['withdrawn', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '撤回成功' }); + } else { + res.status(404).json({ success: false, message: '核销申请不存在' }); + } + } catch (error) { + console.error('撤回核销申请失败:', error); + res.status(500).json({ success: false, message: '撤回核销申请失败', error: error.message }); + } +}); + +app.post('/api/verifications/:id/approve', async (req, res) => { + try { + const { id } = req.params; + const { remark } = req.body; + + const result = await db.query('UPDATE verifications SET status = ?, approval_remark = ? WHERE id = ?', ['approved', remark, id]); + + if (result.changes > 0) { + res.json({ success: true, message: '审批通过成功' }); + } else { + res.status(404).json({ success: false, message: '核销申请不存在' }); + } + } catch (error) { + console.error('审批核销申请失败:', error); + res.status(500).json({ success: false, message: '审批核销申请失败', error: error.message }); + } +}); + +app.post('/api/verifications/:id/reject', async (req, res) => { + try { + const { id } = req.params; + const { rejectReason } = req.body; + + // 开始事务 + await db.query('BEGIN TRANSACTION'); + + try { + // 获取核销金额和预支单ID + const verification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = ?', [id]); + const amount = verification.rows[0]?.amount || 0; + const advanceId = verification.rows[0]?.advance_id; + + // 退回核销申请 + const result = await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['pending_edit', id]); + + // 不在这里恢复预支单已核销金额,因为只有执行的核销才会增加已核销金额 + // if (advanceId && amount > 0) { + // await db.query( + // 'UPDATE advances SET total_reimbursed = total_reimbursed - ? WHERE id = ?', + // [amount, advanceId] + // ); + // } + + // 提交事务 + await db.query('COMMIT'); + + if (result.changes > 0) { + res.json({ success: true, message: '退回成功' }); + } else { + res.status(404).json({ success: false, message: '核销申请不存在' }); + } + } catch (error) { + // 回滚事务 + await db.query('ROLLBACK'); + throw error; + } + } catch (error) { + console.error('退回核销申请失败:', error); + res.status(500).json({ success: false, message: '退回核销申请失败', error: error.message }); + } +}); + +// ==================== 执行管理API ==================== +app.get('/api/executions', async (req, res) => { + try { + const result = await db.query(` + SELECT * FROM executions + ORDER BY created_at DESC + `); + res.json({ success: true, data: result.rows, count: result.rows.length }); + } catch (error) { + console.error('获取执行记录失败:', error); + res.status(500).json({ success: false, message: '获取执行记录失败', error: error.message }); + } +}); + +app.get('/api/executions/pending', async (req, res) => { + try { + // 获取待执行的申请(已审批通过但未执行) + const advances = await db.query('SELECT * FROM advances WHERE status = ?', ['approved']); + const reimbursements = await db.query('SELECT * FROM reimbursements WHERE status = ?', ['approved']); + const payments = await db.query('SELECT * FROM payment_requests WHERE status = ?', ['approved']); + const verifications = await db.query('SELECT * FROM verifications WHERE status = ?', ['approved']); + + const pendingData = [ + ...advances.rows.map(item => ({ ...item, type: '预支申请', code: item.advance_code })), + ...reimbursements.rows.map(item => ({ ...item, type: '报销申请', code: item.reimbursement_code })), + ...payments.rows.map(item => ({ ...item, type: '付款申请', code: item.request_code })), + ...verifications.rows.map(item => ({ ...item, type: '核销申请', code: item.verification_code })) + ]; + + res.json({ success: true, data: pendingData, count: pendingData.length }); + } catch (error) { + console.error('获取待执行列表失败:', error); + res.status(500).json({ success: false, message: '获取待执行列表失败', error: error.message }); + } +}); + +app.get('/api/executions/executed', async (req, res) => { + try { + // 获取已执行的申请 + const advances = await db.query('SELECT * FROM advances WHERE status = ?', ['executed']); + const reimbursements = await db.query('SELECT * FROM reimbursements WHERE status = ?', ['executed']); + const payments = await db.query('SELECT * FROM payment_requests WHERE status = ?', ['executed']); + const verifications = await db.query('SELECT * FROM verifications WHERE status = ?', ['executed']); + + const executedData = [ + ...advances.rows.map(item => ({ ...item, type: '预支申请', code: item.advance_code })), + ...reimbursements.rows.map(item => ({ ...item, type: '报销申请', code: item.reimbursement_code })), + ...payments.rows.map(item => ({ ...item, type: '付款申请', code: item.request_code })), + ...verifications.rows.map(item => ({ ...item, type: '核销申请', code: item.verification_code })) + ]; + + res.json({ success: true, data: executedData, count: executedData.length }); + } catch (error) { + console.error('获取已执行列表失败:', error); + res.status(500).json({ success: false, message: '获取已执行列表失败', error: error.message }); + } +}); + +app.post('/api/executions', async (req, res) => { + try { + const { apply_id, apply_type, action, execute_method, voucher_no, remark, reject_reason, voucher_files } = req.body; + const operator = '系统管理员'; + const operator_role = 'admin'; + + // 记录执行操作 + await db.query( + 'INSERT INTO executions (apply_id, apply_type, action, execute_method, voucher_no, remark, reject_reason, voucher_files, operator, operator_role, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime(\'now\'))', + [apply_id, apply_type, action, execute_method, voucher_no, remark, reject_reason, JSON.stringify(voucher_files || []), operator, operator_role] + ); + + // 更新申请状态 + let status = action === 'execute' ? 'executed' : 'rejected'; + if (action === 'reject') { + status = 'pending_edit'; // 退回后状态改为待编辑 + } + + const executeDate = new Date().toISOString().split('T')[0]; + + switch (apply_type) { + case 'advance': + await db.query('UPDATE advances SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); + break; + case 'reimbursement': + await db.query('UPDATE reimbursements SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); + break; + case 'payment': + await db.query('UPDATE payment_requests SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); + break; + case 'verification': + // 开始事务 + await db.query('BEGIN TRANSACTION'); + + try { + // 更新核销申请状态 + await db.query('UPDATE verifications SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); + + // 获取核销申请信息 + const verification = await db.query('SELECT advance_id, settlement, amount FROM verifications WHERE id = ?', [apply_id]); + const advanceId = verification.rows[0]?.advance_id; + const isSettlement = verification.rows[0]?.settlement === 1; + const verificationAmount = verification.rows[0]?.amount || 0; + + // 更新预支单状态和已核销金额 + if (advanceId && status === 'executed') { + // 更新预支单已核销金额 + await db.query('UPDATE advances SET total_reimbursed = total_reimbursed + ? WHERE id = ?', [verificationAmount, advanceId]); + + if (isSettlement) { + // 如果是结算核销,将预支单状态改为已完成 + await db.query('UPDATE advances SET status = ? WHERE id = ?', ['completed', advanceId]); + } else { + // 如果不是结算核销,将预支单状态改为部分核销 + await db.query('UPDATE advances SET status = ? WHERE id = ?', ['partial_verification', advanceId]); + } + } + + // 提交事务 + await db.query('COMMIT'); + } catch (error) { + // 回滚事务 + await db.query('ROLLBACK'); + throw error; + } + break; + } + + res.json({ success: true, message: '执行操作成功' }); + } catch (error) { + console.error('执行操作失败:', error); + res.status(500).json({ success: false, message: '执行操作失败', error: error.message }); + } +}); + +app.get('/api/reimbursements', async (req, res) => { + try { + const result = await db.query(` + SELECT r.*, u.name as user_name, p.name as project_name + FROM reimbursements r + LEFT JOIN users u ON r.user_id = u.id + LEFT JOIN projects p ON r.project_id = p.id + ORDER BY r.created_at DESC + `); + + // 解析每个报销申请的 attachments 和 detail_items 字段为数组 + const data = result.rows.map(item => { + // 解析 attachments 字段 + if (item.attachments) { + try { + item.attachments = JSON.parse(item.attachments); + } catch (error) { + item.attachments = []; + } + } else { + item.attachments = []; + } + // 解析 detail_items 字段 + if (item.detail_items) { + try { + item.detail_items = JSON.parse(item.detail_items); + } catch (error) { + item.detail_items = []; + } + } else { + item.detail_items = []; + } + return item; + }); + + res.json({ success: true, data, count: data.length }); + } catch (error) { + console.error('获取报销记录失败:', error); + res.status(500).json({ + success: false, + message: '获取报销记录失败', + error: error.message + }); + } +}); + +// ==================== 创建报销申请 ==================== +app.post('/api/reimbursements', [ + body('amount').isFloat({ min: 0.01 }), + body('reason').notEmpty(), + body('expense_type').notEmpty() +], validate, async (req, res) => { + try { + const { amount, reason, project_id, currency, reimbursement_date, attachments, amount_cny, applicant, expense_type, detail_items } = req.body; + const user_id = 1; // 临时使用admin用户 + + // 生成报销编号 + const reimbursementCode = `REIMB-${Date.now()}`; + + const result = await db.query( + 'INSERT INTO reimbursements (user_id, project_id, amount, currency, amount_cny, reason, reimbursement_date, reimbursement_code, status, applicant, expense_type, detail_items, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [user_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, reimbursement_date, reimbursementCode, 'pending', applicant, expense_type, JSON.stringify(detail_items || []), JSON.stringify(attachments || [])] + ); + + // SQLite不支持RETURNING,所以需要查询刚插入的数据 + const lastInsert = await db.query('SELECT * FROM reimbursements ORDER BY id DESC LIMIT 1'); + res.json({ success: true, data: lastInsert.rows[0] }); + } catch (error) { + console.error('创建报销申请失败:', error); + res.status(500).json({ success: false, message: '创建报销申请失败', error: error.message }); + } +}); + +// ==================== 获取单个报销申请 ==================== +app.get('/api/reimbursements/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('SELECT * FROM reimbursements WHERE id = ?', [id]); + + if (result.rows.length > 0) { + const data = result.rows[0]; + // 解析 attachments 字段为数组 + if (data.attachments) { + try { + data.attachments = JSON.parse(data.attachments); + } catch (error) { + data.attachments = []; + } + } else { + data.attachments = []; + } + // 解析 detail_items 字段为数组 + if (data.detail_items) { + try { + data.detail_items = JSON.parse(data.detail_items); + } catch (error) { + data.detail_items = []; + } + } else { + data.detail_items = []; + } + res.json({ success: true, data }); + } else { + res.status(404).json({ success: false, message: '报销申请不存在' }); + } + } catch (error) { + console.error('获取报销申请失败:', error); + res.status(500).json({ success: false, message: '获取报销申请失败', error: error.message }); + } +}); + +// ==================== 更新报销申请 ==================== +app.put('/api/reimbursements/:id', async (req, res) => { + try { + const { id } = req.params; + const { amount, reason, project_id, currency, reimbursement_date, attachments, amount_cny, applicant, expense_type, detail_items, status } = req.body; + + const result = await db.query( + 'UPDATE reimbursements SET amount = ?, reason = ?, project_id = ?, currency = ?, reimbursement_date = ?, attachments = ?, amount_cny = ?, applicant = ?, expense_type = ?, detail_items = ?, status = ? WHERE id = ?', + [amount, reason, project_id, currency, reimbursement_date, JSON.stringify(attachments || []), amount_cny, applicant, expense_type, JSON.stringify(detail_items || []), status, id] + ); + + if (result.changes > 0) { + res.json({ success: true, message: '更新成功' }); + } else { + res.status(404).json({ success: false, message: '报销申请不存在' }); + } + } catch (error) { + console.error('更新报销申请失败:', error); + res.status(500).json({ success: false, message: '更新报销申请失败', error: error.message }); + } +}); + +// ==================== 删除报销申请 ==================== +app.delete('/api/reimbursements/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('DELETE FROM reimbursements WHERE id = ?', [id]); + + if (result.changes > 0) { + res.json({ success: true, message: '删除成功' }); + } else { + res.status(404).json({ success: false, message: '报销申请不存在' }); + } + } catch (error) { + console.error('删除报销申请失败:', error); + res.status(500).json({ success: false, message: '删除报销申请失败', error: error.message }); + } +}); + +// ==================== 撤回报销申请 ==================== +// ==================== 提交报销申请 ==================== +app.post('/api/reimbursements/:id/submit', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE reimbursements SET status = ? WHERE id = ?', ['pending', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '提交成功' }); + } else { + res.status(404).json({ success: false, message: '报销申请不存在' }); + } + } catch (error) { + console.error('提交报销申请失败:', error); + res.status(500).json({ success: false, message: '提交报销申请失败', error: error.message }); + } +}); + +app.post('/api/reimbursements/:id/withdraw', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE reimbursements SET status = ? WHERE id = ?', ['withdrawn', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '撤回成功' }); + } else { + res.status(404).json({ success: false, message: '报销申请不存在' }); + } + } catch (error) { + console.error('撤回报销申请失败:', error); + res.status(500).json({ success: false, message: '撤回报销申请失败', error: error.message }); + } +}); + +// ==================== 审批报销申请 ==================== +app.post('/api/reimbursements/:id/approve', async (req, res) => { + try { + const { id } = req.params; + const { remark } = req.body; + + const result = await db.query('UPDATE reimbursements SET status = ?, approval_remark = ? WHERE id = ?', ['approved', remark, id]); + + if (result.changes > 0) { + res.json({ success: true, message: '审批通过成功' }); + } else { + res.status(404).json({ success: false, message: '报销申请不存在' }); + } + } catch (error) { + console.error('审批报销申请失败:', error); + res.status(500).json({ success: false, message: '审批报销申请失败', error: error.message }); + } +}); + +// ==================== 退回报销申请 ==================== +app.post('/api/reimbursements/:id/reject', async (req, res) => { + try { + const { id } = req.params; + const { rejectReason } = req.body; + + const result = await db.query('UPDATE reimbursements SET status = ? WHERE id = ?', ['pending_edit', id]); + + if (result.changes > 0) { + res.json({ success: true, message: '退回成功' }); + } else { + res.status(404).json({ success: false, message: '报销申请不存在' }); + } + } catch (error) { + console.error('退回报销申请失败:', error); + res.status(500).json({ success: false, message: '退回报销申请失败', error: error.message }); + } +}); + +// ==================== 采购申请API ==================== +app.get('/api/purchase-requests', async (req, res) => { + try { + const { project_id, status } = req.query; + let query = ` + SELECT pr.*, p.name as project_name, s.name as supplier_name + FROM purchase_requests pr + LEFT JOIN projects p ON pr.project_id = p.id + LEFT JOIN suppliers s ON pr.supplier_id = s.id + `; + const params = []; + + if (project_id) { + query += ' WHERE pr.project_id = ?'; + params.push(project_id); + } + if (status) { + query += project_id ? ' AND pr.status = ?' : ' WHERE pr.status = ?'; + params.push(status); + } + + query += ' ORDER BY pr.created_at DESC'; + + const result = await db.query(query, params); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取采购申请列表失败:', error); + res.status(500).json({ + success: false, + message: '获取采购申请列表失败', + error: error.message + }); + } +}); + +app.get('/api/purchase-requests/:id', async (req, res) => { + try { + const { id } = req.params; + + const requestResult = await db.query(` + SELECT pr.*, p.name as project_name, s.name as supplier_name + FROM purchase_requests pr + LEFT JOIN projects p ON pr.project_id = p.id + LEFT JOIN suppliers s ON pr.supplier_id = s.id + WHERE pr.id = ? + `, [id]); + + if (requestResult.rows.length === 0) { + return res.status(404).json({ success: false, message: '采购申请不存在' }); + } + + const purchaseRequest = requestResult.rows[0]; + + const itemsResult = await db.query(` + SELECT * FROM purchase_request_items + WHERE purchase_request_id = ? + `, [id]); + + purchaseRequest.items = itemsResult.rows; + + res.json({ + success: true, + data: purchaseRequest + }); + } catch (error) { + console.error('获取采购申请详情失败:', error); + res.status(500).json({ + success: false, + message: '获取采购申请详情失败', + error: error.message + }); + } +}); + +app.post('/api/purchase-requests', async (req, res) => { + try { + const { + project_id, applicant, request_date, supplier_id, supplier_name, + expense_category, total_amount, currency, remark, attachments, items + } = req.body; + + const date = new Date(); + const requestCode = `PUR-${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}-${String(Math.floor(Math.random() * 1000)).padStart(3, '0')}`; + + const result = await db.query(` + INSERT INTO purchase_requests + (request_code, project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount, currency, remark, attachments, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', datetime('now'), datetime('now')) + `, [requestCode, project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount || 0, currency || 'CNY', remark, attachments ? JSON.stringify(attachments) : null]); + + const purchaseRequestId = result.lastID; + + if (items && items.length > 0) { + for (const item of items) { + await db.query(` + INSERT INTO purchase_request_items + (purchase_request_id, product_id, product_name, specification, unit, quantity, unit_price, total_price) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `, [purchaseRequestId, item.product_id || null, item.product_name, item.specification || null, item.unit || null, item.quantity || 0, item.unit_price || 0, item.total_price || 0]); + } + } + + res.json({ + success: true, + message: '采购申请创建成功', + data: { id: purchaseRequestId, request_code: requestCode } + }); + } catch (error) { + console.error('创建采购申请失败:', error); + res.status(500).json({ + success: false, + message: '创建采购申请失败', + error: error.message + }); + } +}); + +app.put('/api/purchase-requests/:id', async (req, res) => { + try { + const { id } = req.params; + const { + project_id, applicant, request_date, supplier_id, supplier_name, + expense_category, total_amount, currency, remark, attachments, items + } = req.body; + + const result = await db.query(` + UPDATE purchase_requests + SET project_id = ?, applicant = ?, request_date = ?, supplier_id = ?, supplier_name = ?, + expense_category = ?, total_amount = ?, currency = ?, remark = ?, attachments = ?, updated_at = datetime('now') + WHERE id = ? + `, [project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount, currency, remark, attachments ? JSON.stringify(attachments) : null, id]); + + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '采购申请不存在' }); + } + + if (items) { + await db.query('DELETE FROM purchase_request_items WHERE purchase_request_id = ?', [id]); + + for (const item of items) { + await db.query(` + INSERT INTO purchase_request_items + (purchase_request_id, product_id, product_name, specification, unit, quantity, unit_price, total_price) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `, [id, item.product_id || null, item.product_name, item.specification || null, item.unit || null, item.quantity || 0, item.unit_price || 0, item.total_price || 0]); + } + } + + res.json({ + success: true, + message: '采购申请更新成功' + }); + } catch (error) { + console.error('更新采购申请失败:', error); + res.status(500).json({ + success: false, + message: '更新采购申请失败', + error: error.message + }); + } +}); + +app.delete('/api/purchase-requests/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('DELETE FROM purchase_requests WHERE id = ?', [id]); + + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '采购申请不存在' }); + } + + res.json({ + success: true, + message: '采购申请删除成功' + }); + } catch (error) { + console.error('删除采购申请失败:', error); + res.status(500).json({ + success: false, + message: '删除采购申请失败', + error: error.message + }); + } +}); + +app.post('/api/purchase-requests/:id/submit', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['submitted', id]); + + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '采购申请不存在' }); + } + + res.json({ success: true, message: '提交成功' }); + } catch (error) { + console.error('提交采购申请失败:', error); + res.status(500).json({ success: false, message: '提交采购申请失败', error: error.message }); + } +}); + +app.post('/api/purchase-requests/:id/approve', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['approved', id]); + + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '采购申请不存在' }); + } + + res.json({ success: true, message: '审批通过成功' }); + } catch (error) { + console.error('审批采购申请失败:', error); + res.status(500).json({ success: false, message: '审批采购申请失败', error: error.message }); + } +}); + +app.post('/api/purchase-requests/:id/reject', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['rejected', id]); + + if (result.changes === 0) { + return res.status(404).json({ success: false, message: '采购申请不存在' }); + } + + res.json({ success: true, message: '驳回成功' }); + } catch (error) { + console.error('驳回采购申请失败:', error); + res.status(500).json({ success: false, message: '驳回采购申请失败', error: error.message }); + } +}); + +app.post('/api/purchase-requests/:id/execute', async (req, res) => { + try { + const { id } = req.params; + const { operator } = req.body; + + await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['executed', id]); + + const itemsResult = await db.query('SELECT * FROM purchase_request_items WHERE purchase_request_id = ?', [id]); + + for (const item of itemsResult.rows) { + await db.query(` + INSERT INTO inventory_records + (record_type, purchase_request_id, product_id, quantity, unit_price, total_amount, record_date, operator) + VALUES (?, ?, ?, ?, ?, ?, date('now'), ?) + `, ['in', id, item.product_id, item.quantity, item.unit_price, item.total_price, operator || '系统']); + } + + res.json({ success: true, message: '执行成功,已自动入库' }); + } catch (error) { + console.error('执行采购申请失败:', error); + res.status(500).json({ success: false, message: '执行采购申请失败', error: error.message }); + } +}); + +// ==================== 库存管理API ==================== +app.get('/api/inventory', async (req, res) => { + try { + const { product_id, project_id, record_type } = req.query; + let query = ` + SELECT ir.*, p.name as product_name, prj.name as project_name + FROM inventory_records ir + LEFT JOIN products p ON ir.product_id = p.id + LEFT JOIN projects prj ON ir.project_id = prj.id + `; + const params = []; + const conditions = []; + + if (product_id) { + conditions.push('ir.product_id = ?'); + params.push(product_id); + } + if (project_id) { + conditions.push('ir.project_id = ?'); + params.push(project_id); + } + if (record_type) { + conditions.push('ir.record_type = ?'); + params.push(record_type); + } + + if (conditions.length > 0) { + query += ' WHERE ' + conditions.join(' AND '); + } + + query += ' ORDER BY ir.created_at DESC'; + + const result = await db.query(query, params); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取库存记录失败:', error); + res.status(500).json({ + success: false, + message: '获取库存记录失败', + error: error.message + }); + } +}); + +app.get('/api/inventory/summary', async (req, res) => { + try { + const result = await db.query(` + SELECT + p.id as product_id, + p.name as product_name, + p.unit, + SUM(CASE WHEN ir.record_type = 'in' THEN ir.quantity ELSE 0 END) as total_in, + SUM(CASE WHEN ir.record_type = 'out' THEN ir.quantity ELSE 0 END) as total_out, + SUM(CASE WHEN ir.record_type = 'in' THEN ir.quantity ELSE -ir.quantity END) as current_quantity + FROM products p + LEFT JOIN inventory_records ir ON p.id = ir.product_id + GROUP BY p.id, p.name, p.unit + `); + + res.json({ + success: true, + data: result.rows + }); + } catch (error) { + console.error('获取库存汇总失败:', error); + res.status(500).json({ + success: false, + message: '获取库存汇总失败', + error: error.message + }); + } +}); + +app.post('/api/inventory/out', async (req, res) => { + try { + const { project_id, product_id, quantity, unit_price, total_amount, operator, remark } = req.body; + + const result = await db.query(` + INSERT INTO inventory_records + (record_type, project_id, product_id, quantity, unit_price, total_amount, record_date, operator, remark) + VALUES (?, ?, ?, ?, ?, ?, date('now'), ?, ?) + `, ['out', project_id || null, product_id, quantity, unit_price || null, total_amount || null, operator || '系统', remark || null]); + + res.json({ + success: true, + message: '出库成功', + data: { id: result.lastID } + }); + } catch (error) { + console.error('出库失败:', error); + res.status(500).json({ + success: false, + message: '出库失败', + error: error.message + }); + } +}); + +// ==================== 项目成本统计API ==================== +app.get('/api/projects/:id/cost-summary', async (req, res) => { + try { + const { id } = req.params; + + const purchaseResult = await db.query(` + SELECT + expense_category, + SUM(total_amount) as total_amount + FROM purchase_requests + WHERE project_id = ? AND status IN ('approved', 'executed') + GROUP BY expense_category + `, [id]); + + const paymentResult = await db.query(` + SELECT + SUM(amount) as total_payment + FROM payment_requests + WHERE project_id = ? AND status = 'approved' AND payment_type = 'company' + `, [id]); + + const projectResult = await db.query('SELECT * FROM projects WHERE id = ?', [id]); + + if (projectResult.rows.length === 0) { + return res.status(404).json({ success: false, message: '项目不存在' }); + } + + const project = projectResult.rows[0]; + const purchaseByCategory = {}; + let totalPurchase = 0; + + purchaseResult.rows.forEach(row => { + purchaseByCategory[row.expense_category] = row.total_amount; + totalPurchase += row.total_amount; + }); + + const totalPayment = paymentResult.rows[0]?.total_payment || 0; + + res.json({ + success: true, + data: { + project_name: project.name, + contract_amount: project.contract_amount || 0, + purchase_cost: { + total: totalPurchase, + by_category: purchaseByCategory + }, + payment_cost: totalPayment, + total_cost: totalPurchase + totalPayment, + profit: (project.contract_amount || 0) - (totalPurchase + totalPayment) + } + }); + } catch (error) { + console.error('获取项目成本统计失败:', error); + res.status(500).json({ + success: false, + message: '获取项目成本统计失败', + error: error.message + }); + } +}); + +// ==================== 财务统计API ==================== +app.get('/api/finance-stats', async (req, res) => { + try { + // 获取统计数据 + const [customers, suppliers, projects, paymentNodes, paymentRecords] = await Promise.all([ + db.query('SELECT COUNT(*) as count FROM customers'), + db.query('SELECT COUNT(*) as count FROM suppliers'), + db.query('SELECT COUNT(*) as count FROM projects'), + db.query('SELECT COUNT(*) as count FROM payment_nodes'), + db.query('SELECT COUNT(*) as count FROM payment_records') + ]); + + res.json({ + success: true, + data: { + summary: { + customers: parseInt(customers.rows[0].count) || 0, + suppliers: parseInt(suppliers.rows[0].count) || 0, + projects: parseInt(projects.rows[0].count) || 0, + payment_nodes: parseInt(paymentNodes.rows[0].count) || 0, + payment_records: parseInt(paymentRecords.rows[0].count) || 0 + }, + timestamp: new Date().toISOString() + } + }); + } catch (error) { + res.json({ + success: false, + message: '获取财务统计失败', + error: error.message + }); + } +}); + +// ==================== 系统状态页面 ==================== +app.get('/status', (req, res) => { + res.send(` + + + + 系统状态 - 公司财务管理系统 + + + + +
+

🏢 公司财务管理系统 - 生产环境状态

+

服务器: 43.161.248.209:3000 | 时间: ${new Date().toLocaleString()}

+ +
+
+
+
前端服务
+
端口: 3000
+
状态: 正常
+
+
+
+
后端API
+
12个端点
+
状态: 正常
+
+
+
+
数据库
+
PostgreSQL
+
状态: 已连接
+
+
+
+
网络访问
+
绑定: 0.0.0.0
+
状态: 已验证
+
+
+ +
+

🔧 端口访问说明

+

✅ 端口3000: 已验证可外部访问,所有服务运行正常

+

⚠️ 端口5000: 安全组已开放,但可能存在网络路由问题

+

🎯 解决方案: 使用已验证的3000端口作为生产环境

+
+ + +
+ + + `); +}); + +// ==================== 欢迎页面 ==================== +app.get('/welcome', (req, res) => { + res.send(` + + + + 欢迎 - 公司财务管理系统 + + + + +
+
+

🏢 公司财务管理系统

+
生产环境 v1.0.0 | 专为老挝电力公司定制
+
+ +
+
+
12
+
功能模块
+
+
+
4
+
多币种支持
+
+
+
100%
+
响应式设计
+
+
+
24/7
+
服务可用
+
+
+ +
+
+

🚀 立即开始

+

点击下方按钮进入系统,开始管理您的财务业务。

+ 进入系统主界面 + 查看系统状态 +
+ +
+

📊 核心功能

+
    +
  • 客户与供应商管理
  • +
  • 项目与合同管理
  • +
  • 付款节点与记录
  • +
  • 多币种汇率管理
  • +
  • 预支款与报销流程
  • +
  • 财务统计与报表
  • +
  • 移动端适配
  • +
  • 多语言支持
  • +
+
+ +
+

🔧 系统信息

+

服务器: 43.161.248.209:3000

+

技术栈: React + Node.js + PostgreSQL

+

部署时间: 2026-03-09

+

测试账号: admin / password

+ +
+
+ +
+

© 2026 老挝电力公司财务管理系统 | 技术支持: OpenClaw AI Assistant

+
+
+ + + `); +}); + +// ==================== 默认路由 ==================== +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, '../frontend/dist/index.html')); +}); + +// ==================== API文档页面 ==================== +app.get('/api-docs', (req, res) => { + res.send(` + + + API文档 + +

📚 API文档

+

这是API端点文档页面。如果您想使用业务界面,请访问:

+

👉 点击这里进入业务系统

+

或访问:欢迎页面

+ + + `); +}); + +// ==================== 文件上传API (腾讯云COS) ==================== +// 暂时注释掉腾讯云COS上传,使用本地文件存储 +/* +const COS = require('cos-nodejs-sdk-v5'); +const cosStorage = multer.memoryStorage(); +const upload = multer({ storage: cosStorage, limits: { fileSize: 10 * 1024 * 1024 } }); + +const cosConfig = { + SecretId: process.env.TENCENT_SECRET_ID || '', + SecretKey: process.env.TENCENT_SECRET_KEY || '', + Bucket: 'qingyuan-erp-files-1310040146', + Region: 'ap-hongkong' +}; +const cos = new COS(cosConfig); +const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']; + +app.post('/api/upload/single/cos', upload.single('file'), async (req, res) => { + try { + if (!req.file) return res.status(400).json({ success: false, error: '没有上传文件' }); + + console.log('接收到文件:', req.file.originalname); + + const ext = req.file.originalname.split('.').pop().toLowerCase(); + const timestamp = Date.now(); + const randomStr = Math.random().toString(36).substring(2, 8); + const filename = 'uploads/' + timestamp + '_' + randomStr + '.' + ext; + + console.log('准备上传到COS:', filename); + + cos.putObject({ + Bucket: cosConfig.Bucket, + Region: cosConfig.Region, + Key: filename, + Body: req.file.buffer, + ContentType: req.file.mimetype + }, (err, data) => { + if (err) { + console.error('COS上传失败:', err); + return res.status(500).json({ success: false, error: '上传失败' }); + } + + console.log('COS上传成功:', data); + + const fileUrl = 'https://' + cosConfig.Bucket + '.cos.' + cosConfig.Region + '.myqcloud.com/' + filename; + + res.json({ + success: true, + data: { + url: fileUrl, + name: req.file.originalname, + size: req.file.size, + type: req.file.mimetype, + isImage: imageFormats.includes(ext) + } + }); + }); + } catch (error) { + console.error('上传异常:', error); + res.status(500).json({ success: false, error: '上传失败' }); + } +}); + +app.post('/api/upload/multiple', upload.array('files', 10), async (req, res) => { + try { + if (!req.files || req.files.length === 0) { + return res.status(400).json({ success: false, error: '没有上传文件' }); + } + + const uploadPromises = req.files.map(file => { + return new Promise((resolve, reject) => { + const ext = file.originalname.split('.').pop().toLowerCase(); + const filename = 'uploads/' + Date.now() + '_' + Math.random().toString(36).substring(2, 8) + '.' + ext; + + cos.putObject({ + Bucket: cosConfig.Bucket, + Region: cosConfig.Region, + Key: filename, + Body: file.buffer, + ContentType: file.mimetype + }, (err, data) => { + if (err) reject(err); + else { + const fileUrl = 'https://' + cosConfig.Bucket + '.cos.' + cosConfig.Region + '.myqcloud.com/' + filename; + resolve({ + url: fileUrl, + name: file.originalname, + size: file.size, + isImage: imageFormats.includes(ext) + }); + } + }); + }); + }); + + const results = await Promise.all(uploadPromises); + res.json({ success: true, data: results }); + } catch (error) { + console.error('批量上传失败:', error); + res.status(500).json({ success: false, error: '上传失败' }); + } +}); +*/ + +// ==================== 404处理 ==================== +app.use((req, res) => { + res.status(404).json({ + success: false, + message: '端点未找到', + requested_url: req.originalUrl + }); +}); + +// ==================== 错误处理 ==================== +app.use((err, req, res, next) => { + console.error('服务器错误:', err.stack); + res.status(500).json({ + success: false, + message: '服务器内部错误', + error: process.env.NODE_ENV === 'development' ? err.message : undefined + }); +}); + +// ==================== 启动服务器 ==================== + +if (require.main === module) { + app.listen(PORT, '0.0.0.0', () => { + console.log(` + 🚀 公司财务管理系统 - 最终生产后端 + =========================================== + 📍 服务器地址: http://0.0.0.0:${PORT} + 🌐 外部访问: http://43.161.248.209:${PORT} + + 🔗 核心API端点: + - 健康检查: /api/health + - 客户管理: /api/customers + - 供应商管理: /api/suppliers + - 项目管理: /api/projects + - 商品管理: /api/products + - 采购申请: /api/purchase-requests + - 库存管理: /api/inventory + - 付款节点: /api/payment-nodes + - 付款记录: /api/payment-records + - 汇率管理: /api/exchange-rates + - 预支款管理: /api/advances + - 报销管理: /api/reimbursements + - 财务统计: /api/finance-stats + + 👤 测试账号: + - 用户名: admin + - 密码: X123c321@ + + ✅ 所有API已就绪 + ✅ 前端应用已集成 + ✅ 数据库已连接 + ✅ 等待用户访问 + + ⏰ 启动时间: ${new Date().toISOString()} + =========================================== + `); + }); +} + +module.exports = app; \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/final-production.js b/backup_20260327/company-finance-system/backend/final-production.js new file mode 100644 index 0000000..a4bed6c --- /dev/null +++ b/backup_20260327/company-finance-system/backend/final-production.js @@ -0,0 +1,139 @@ +const express = require('express'); +const path = require('path'); +const app = express(); +const PORT = 3000; // 使用已验证可访问的端口 + +// 中间件 +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// 静态文件服务 - 前端应用 +app.use('/app', express.static(path.join(__dirname, '../frontend/dist'))); + +// 健康检查 +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + service: 'company-finance-system', + timestamp: new Date().toISOString(), + version: '1.0.0', + port: PORT, + endpoints: { + frontend: '/app/index.html', + test: '/test', + api: '/api/health' + } + }); +}); + +app.get('/api/health', (req, res) => { + res.json({ + status: 'healthy', + message: 'API服务正常', + timestamp: new Date().toISOString() + }); +}); + +// 测试页面 +app.get('/test', (req, res) => { + res.send(` + + + + ✅ 系统测试 - 端口${PORT} + + + + +
+

🏢 公司财务管理系统 - 生产环境

+

服务器: 43.161.248.209:${PORT}

+

状态: ✅ 运行正常

+ +
+

🎉 恭喜!系统部署成功

+

所有服务已就绪,可以开始使用。

+
+ +

🚀 立即开始:

+

+ 进入系统 + 健康检查 +

+ +

📊 系统信息:

+
    +
  • 前端技术: React + TypeScript + Ant Design
  • +
  • 后端技术: Node.js + Express + PostgreSQL
  • +
  • 数据库: PostgreSQL 15 (已连接)
  • +
  • 部署端口: ${PORT} (已验证可访问)
  • +
  • 功能模块: 财务、客户、供应商、项目管理
  • +
+ +
+

📱 测试说明:

+

1. 此页面通过端口${PORT}访问(已确认开放)

+

2. 前端应用已集成到同一端口

+

3. 所有功能均可正常使用

+

4. 请现在测试:/app/index.html

+
+
+ + + + + `); +}); + +// 默认路由重定向到前端 +app.get('/', (req, res) => { + res.redirect('/app/index.html'); +}); + +// 404处理 +app.use((req, res) => { + res.status(404).send('页面未找到 - 请访问 前端应用'); +}); + +// 启动服务器 +app.listen(PORT, '0.0.0.0', () => { + console.log(` + 🎉 公司财务管理系统 - 最终生产部署 + ==================================== + 📍 服务器地址: http://0.0.0.0:${PORT} + 🌐 外部访问: http://43.161.248.209:${PORT} + + 🔗 重要链接: + - 前端应用: http://43.161.248.209:${PORT}/app/index.html + - 测试页面: http://43.161.248.209:${PORT}/test + - 健康检查: http://43.161.248.209:${PORT}/health + + ✅ 端口${PORT}已验证可访问 + ✅ 所有服务已集成 + ✅ 等待用户测试 + + ⏰ 部署时间: ${new Date().toISOString()} + ==================================== + `); +}); diff --git a/backup_20260327/company-finance-system/backend/finance-api.js b/backup_20260327/company-finance-system/backend/finance-api.js new file mode 100644 index 0000000..2bb05e9 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/finance-api.js @@ -0,0 +1,467 @@ +const express = require('express'); +const router = express.Router(); +const db = require('./db'); + +// 获取所有付款节点 +router.get('/payment-nodes', async (req, res) => { + try { + const result = await db.query(` + SELECT + pn.*, + pr.project_name, + pr.project_code + FROM payment_nodes pn + LEFT JOIN projects pr ON pn.project_id = pr.project_id + ORDER BY pn.due_date ASC + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取付款节点失败:', error); + res.status(500).json({ + success: false, + message: '获取付款节点失败', + error: error.message + }); + } +}); + +// 获取单个付款节点 +router.get('/payment-nodes/:id', async (req, res) => { + try { + const { id } = req.params; + const result = await db.query(` + SELECT + pn.*, + pr.project_name, + pr.project_code + FROM payment_nodes pn + LEFT JOIN projects pr ON pn.project_id = pr.project_id + WHERE pn.node_id = $1 + `, [id]); + + if (result.rows.length === 0) { + return res.status(404).json({ + success: false, + message: '付款节点不存在' + }); + } + + res.json({ + success: true, + data: result.rows[0] + }); + } catch (error) { + console.error('获取付款节点失败:', error); + res.status(500).json({ + success: false, + message: '获取付款节点失败', + error: error.message + }); + } +}); + +// 创建付款节点 +router.post('/payment-nodes', async (req, res) => { + try { + const { + project_id, + node_type, + node_name, + node_name_zh, + node_name_th, + node_name_en, + amount, + currency, + due_date, + status, + notes + } = req.body; + + const result = await db.query(` + INSERT INTO payment_nodes ( + project_id, node_type, node_name, + node_name_zh, node_name_th, node_name_en, + amount, currency, due_date, status, notes + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + RETURNING * + `, [ + project_id, node_type, node_name, + node_name_zh, node_name_th, node_name_en, + amount, currency, due_date, status || 'pending', notes + ]); + + res.status(201).json({ + success: true, + message: '付款节点创建成功', + data: result.rows[0] + }); + } catch (error) { + console.error('创建付款节点失败:', error); + res.status(500).json({ + success: false, + message: '创建付款节点失败', + error: error.message + }); + } +}); + +// 更新付款节点 +router.put('/payment-nodes/:id', async (req, res) => { + try { + const { id } = req.params; + const { + node_type, + node_name, + node_name_zh, + node_name_th, + node_name_en, + amount, + currency, + due_date, + status, + notes + } = req.body; + + const result = await db.query(` + UPDATE payment_nodes + SET + node_type = COALESCE($1, node_type), + node_name = COALESCE($2, node_name), + node_name_zh = COALESCE($3, node_name_zh), + node_name_th = COALESCE($4, node_name_th), + node_name_en = COALESCE($5, node_name_en), + amount = COALESCE($6, amount), + currency = COALESCE($7, currency), + due_date = COALESCE($8, due_date), + status = COALESCE($9, status), + notes = COALESCE($10, notes), + updated_at = CURRENT_TIMESTAMP + WHERE node_id = $11 + RETURNING * + `, [ + node_type, node_name, node_name_zh, node_name_th, node_name_en, + amount, currency, due_date, status, notes, id + ]); + + if (result.rows.length === 0) { + return res.status(404).json({ + success: false, + message: '付款节点不存在' + }); + } + + res.json({ + success: true, + message: '付款节点更新成功', + data: result.rows[0] + }); + } catch (error) { + console.error('更新付款节点失败:', error); + res.status(500).json({ + success: false, + message: '更新付款节点失败', + error: error.message + }); + } +}); + +// 删除付款节点 +router.delete('/payment-nodes/:id', async (req, res) => { + try { + const { id } = req.params; + + const result = await db.query( + 'DELETE FROM payment_nodes WHERE node_id = $1 RETURNING *', + [id] + ); + + if (result.rows.length === 0) { + return res.status(404).json({ + success: false, + message: '付款节点不存在' + }); + } + + res.json({ + success: true, + message: '付款节点删除成功' + }); + } catch (error) { + console.error('删除付款节点失败:', error); + res.status(500).json({ + success: false, + message: '删除付款节点失败', + error: error.message + }); + } +}); + +// 获取付款记录 +router.get('/payment-records', async (req, res) => { + try { + const { node_id, record_type, start_date, end_date } = req.query; + + let query = ` + SELECT + pr.*, + pn.node_name, + pn.project_id, + proj.project_name + FROM payment_records pr + LEFT JOIN payment_nodes pn ON pr.node_id = pn.node_id + LEFT JOIN projects proj ON pn.project_id = proj.project_id + WHERE 1=1 + `; + const params = []; + let paramIndex = 1; + + if (node_id) { + query += ` AND pr.node_id = $${paramIndex}`; + params.push(node_id); + paramIndex++; + } + + if (record_type) { + query += ` AND pr.record_type = $${paramIndex}`; + params.push(record_type); + paramIndex++; + } + + if (start_date) { + query += ` AND pr.payment_date >= $${paramIndex}`; + params.push(start_date); + paramIndex++; + } + + if (end_date) { + query += ` AND pr.payment_date <= $${paramIndex}`; + params.push(end_date); + paramIndex++; + } + + query += ` ORDER BY pr.payment_date DESC, pr.created_at DESC`; + + const result = await db.query(query, params); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取付款记录失败:', error); + res.status(500).json({ + success: false, + message: '获取付款记录失败', + error: error.message + }); + } +}); + +// 创建付款记录 +router.post('/payment-records', async (req, res) => { + try { + const { + node_id, + record_type, + amount, + currency, + exchange_rate, + payment_date, + payment_method, + reference_number, + status, + notes + } = req.body; + + const result = await db.query(` + INSERT INTO payment_records ( + node_id, record_type, amount, currency, exchange_rate, + payment_date, payment_method, reference_number, status, notes + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + RETURNING * + `, [ + node_id, record_type, amount, currency, exchange_rate || 1.0, + payment_date, payment_method, reference_number, status || 'completed', notes + ]); + + // 如果是付款记录,更新付款节点状态 + if (record_type === 'payment') { + await db.query(` + UPDATE payment_nodes + SET status = 'paid', updated_at = CURRENT_TIMESTAMP + WHERE node_id = $1 + `, [node_id]); + } + + res.status(201).json({ + success: true, + message: '付款记录创建成功', + data: result.rows[0] + }); + } catch (error) { + console.error('创建付款记录失败:', error); + res.status(500).json({ + success: false, + message: '创建付款记录失败', + error: error.message + }); + } +}); + +// 获取汇率 +router.get('/exchange-rates', async (req, res) => { + try { + const { from_currency, to_currency, effective_date } = req.query; + + let query = 'SELECT * FROM exchange_rates WHERE 1=1'; + const params = []; + let paramIndex = 1; + + if (from_currency) { + query += ` AND from_currency = $${paramIndex}`; + params.push(from_currency); + paramIndex++; + } + + if (to_currency) { + query += ` AND to_currency = $${paramIndex}`; + params.push(to_currency); + paramIndex++; + } + + if (effective_date) { + query += ` AND effective_date = $${paramIndex}`; + params.push(effective_date); + paramIndex++; + } + + query += ` ORDER BY effective_date DESC, created_at DESC`; + + const result = await db.query(query, params); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取汇率失败:', error); + res.status(500).json({ + success: false, + message: '获取汇率失败', + error: error.message + }); + } +}); + +// 创建/更新汇率 +router.post('/exchange-rates', async (req, res) => { + try { + const { from_currency, to_currency, rate, effective_date } = req.body; + + // 检查是否已存在 + const checkResult = await db.query(` + SELECT * FROM exchange_rates + WHERE from_currency = $1 AND to_currency = $2 AND effective_date = $3 + `, [from_currency, to_currency, effective_date]); + + let result; + if (checkResult.rows.length > 0) { + // 更新 + result = await db.query(` + UPDATE exchange_rates + SET rate = $1, updated_at = CURRENT_TIMESTAMP + WHERE from_currency = $2 AND to_currency = $3 AND effective_date = $4 + RETURNING * + `, [rate, from_currency, to_currency, effective_date]); + } else { + // 创建 + result = await db.query(` + INSERT INTO exchange_rates (from_currency, to_currency, rate, effective_date) + VALUES ($1, $2, $3, $4) + RETURNING * + `, [from_currency, to_currency, rate, effective_date]); + } + + res.status(201).json({ + success: true, + message: '汇率保存成功', + data: result.rows[0] + }); + } catch (error) { + console.error('保存汇率失败:', error); + res.status(500).json({ + success: false, + message: '保存汇率失败', + error: error.message + }); + } +}); + +// 财务统计 +router.get('/finance-stats', async (req, res) => { + try { + // 付款节点统计 + const nodesStats = await db.query(` + SELECT + COUNT(*) as total_nodes, + COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending_nodes, + COUNT(CASE WHEN status = 'paid' THEN 1 END) as paid_nodes, + COUNT(CASE WHEN status = 'overdue' THEN 1 END) as overdue_nodes, + SUM(amount) as total_amount, + SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) as pending_amount, + SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) as paid_amount + FROM payment_nodes + `); + + // 付款记录统计 + const recordsStats = await db.query(` + SELECT + COUNT(*) as total_records, + COUNT(CASE WHEN record_type = 'payment' THEN 1 END) as payment_count, + COUNT(CASE WHEN record_type = 'receipt' THEN 1 END) as receipt_count, + SUM(CASE WHEN record_type = 'payment' THEN amount ELSE 0 END) as total_payments, + SUM(CASE WHEN record_type = 'receipt' THEN amount ELSE 0 END) as total_receipts + FROM payment_records + `); + + // 货币分布 + const currencyStats = await db.query(` + SELECT + currency, + COUNT(*) as node_count, + SUM(amount) as total_amount + FROM payment_nodes + GROUP BY currency + ORDER BY total_amount DESC + `); + + res.json({ + success: true, + data: { + nodes: nodesStats.rows[0], + records: recordsStats.rows[0], + currencies: currencyStats.rows, + summary: { + net_cash_flow: (recordsStats.rows[0]?.total_receipts || 0) - (recordsStats.rows[0]?.total_payments || 0), + outstanding_amount: nodesStats.rows[0]?.pending_amount || 0 + } + } + }); + } catch (error) { + console.error('获取财务统计失败:', error); + res.status(500).json({ + success: false, + message: '获取财务统计失败', + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/fix-payment-requests-constraint.js b/backup_20260327/company-finance-system/backend/fix-payment-requests-constraint.js new file mode 100644 index 0000000..ba133a4 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/fix-payment-requests-constraint.js @@ -0,0 +1,89 @@ +const sqlite3 = require('sqlite3').verbose(); +const db = new sqlite3.Database('company_finance.db'); + +// 修改 payment_requests 表的约束,允许 bank_account 和 bank_name 为空 +db.serialize(() => { + console.log('开始修改 payment_requests 表约束...'); + + // 由于 SQLite 不支持直接修改列约束,我们需要创建新表并迁移数据 + db.run(` + CREATE TABLE IF NOT EXISTS payment_requests_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_code TEXT UNIQUE NOT NULL, + applicant TEXT NOT NULL, + payee TEXT NOT NULL, + bank_account TEXT DEFAULT '', + bank_name TEXT DEFAULT '', + amount REAL NOT NULL, + amount_cny REAL DEFAULT 0, + currency TEXT DEFAULT 'CNY', + payment_date DATE NOT NULL, + reason TEXT NOT NULL, + detail_items TEXT, + attachments TEXT, + status TEXT DEFAULT 'pending', + payee_type TEXT DEFAULT 'other', + payee_id INTEGER, + expense_type TEXT DEFAULT 'company', + expense_category TEXT DEFAULT '', + project_id INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + console.error('创建新表失败:', err.message); + db.close(); + return; + } + console.log('✓ 新表创建成功'); + + // 迁移数据 + db.run(` + INSERT INTO payment_requests_new ( + id, request_code, applicant, payee, bank_account, bank_name, amount, amount_cny, + currency, payment_date, reason, detail_items, attachments, status, + payee_type, payee_id, expense_type, expense_category, project_id, + created_at, updated_at + ) + SELECT + id, request_code, applicant, payee, + COALESCE(bank_account, ''), COALESCE(bank_name, ''), + amount, amount_cny, currency, payment_date, reason, + detail_items, attachments, status, + COALESCE(payee_type, 'other'), payee_id, + COALESCE(expense_type, 'company'), COALESCE(expense_category, ''), + project_id, created_at, updated_at + FROM payment_requests + `, (err) => { + if (err) { + console.error('迁移数据失败:', err.message); + db.close(); + return; + } + console.log('✓ 数据迁移成功'); + + // 删除旧表 + db.run('DROP TABLE payment_requests', (err) => { + if (err) { + console.error('删除旧表失败:', err.message); + db.close(); + return; + } + console.log('✓ 旧表删除成功'); + + // 重命名新表 + db.run('ALTER TABLE payment_requests_new RENAME TO payment_requests', (err) => { + if (err) { + console.error('重命名表失败:', err.message); + db.close(); + return; + } + console.log('✓ 表重命名成功'); + console.log('\n✓ 表约束修改完成'); + db.close(); + }); + }); + }); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/init-db.sql b/backup_20260327/company-finance-system/backend/init-db.sql new file mode 100644 index 0000000..aab15b1 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/init-db.sql @@ -0,0 +1,79 @@ +-- 初始化 company_finance_db 数据库 - 客户管理 +-- 运行: psql -U postgres -f init-db.sql + +-- 创建数据库(如果不存在) +SELECT 'CREATE DATABASE company_finance_db' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'company_finance_db')\gexec + +-- 连接到新数据库 +\c company_finance_db + +-- 删除现有表(如果存在) +DROP TABLE IF EXISTS contacts; +DROP TABLE IF EXISTS customers; + +-- 创建customers表 +CREATE TABLE customers ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) UNIQUE NOT NULL, + phone VARCHAR(20), + address TEXT, + company VARCHAR(100), + tax_id VARCHAR(50), + status VARCHAR(20) DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 创建contacts表 +CREATE TABLE contacts ( + id SERIAL PRIMARY KEY, + customer_id INTEGER REFERENCES customers(id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + position VARCHAR(100), + email VARCHAR(100), + phone VARCHAR(20), + is_primary BOOLEAN DEFAULT false, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 创建索引 +CREATE INDEX idx_customers_email ON customers(email); +CREATE INDEX idx_customers_status ON customers(status); +CREATE INDEX idx_contacts_customer_id ON contacts(customer_id); + +-- 插入示例客户数据 +INSERT INTO customers (name, email, phone, address, company, tax_id, status) VALUES +('张三', 'zhangsan@example.com', '13800138000', '北京市朝阳区', 'ABC科技有限公司', '91110108MA01ABCDEF', 'active'), +('李四', 'lisi@example.com', '13900139000', '上海市浦东新区', 'XYZ有限公司', '91310115MA01XYZ123', 'active'), +('王五', 'wangwu@example.com', '13700137000', '广州市天河区', 'DEF集团', '91440101MA01DEF456', 'inactive'), +('赵六', 'zhaoliu@example.com', '13600136000', '深圳市南山区', 'GHI有限公司', '91440300MA01GHI789', 'active'), +('钱七', 'qianqi@example.com', '13500135000', '杭州市西湖区', 'JKL集团', '91330100MA01JKL012', 'active'); + +-- 插入示例联系人数据 +INSERT INTO contacts (customer_id, name, position, email, phone, is_primary) VALUES +(1, '张三', '总经理', 'zhangsan@example.com', '13800138000', true), +(1, '李助理', '总经理助理', 'assistant@abc.com', '13800138001', false), +(2, '李四', '技术总监', 'lisi@example.com', '13900139000', true), +(2, '王经理', '销售经理', 'sales@xyz.com', '13900139001', false), +(3, '王五', '财务总监', 'wangwu@example.com', '13700137000', true), +(4, '赵六', '运营总监', 'zhaoliu@example.com', '13600136000', true), +(5, '钱七', '市场总监', 'qianqi@example.com', '13500135000', true); + +-- 显示表结构 +\d customers +\d contacts + +-- 显示数据统计 +SELECT 'Customers:' as table_name, COUNT(*) as record_count FROM customers +UNION ALL +SELECT 'Contacts:', COUNT(*) FROM contacts; + +-- 显示示例数据 +SELECT '=== Customers Table ===' as info; +SELECT * FROM customers ORDER BY id; + +SELECT '=== Contacts Table ===' as info; +SELECT * FROM contacts ORDER BY customer_id, is_primary DESC; \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/migrations/001_create_category_tree.sql b/backup_20260327/company-finance-system/backend/migrations/001_create_category_tree.sql new file mode 100644 index 0000000..f741cfd --- /dev/null +++ b/backup_20260327/company-finance-system/backend/migrations/001_create_category_tree.sql @@ -0,0 +1,226 @@ +-- ============================================ +-- 商品管理改造 - 数据库迁移脚本 +-- 支持二级分类树状结构 +-- ============================================ + +-- 1. 创建新的分类表(树状结构) +CREATE TABLE IF NOT EXISTS category_tree ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, -- 分类名称 + parent_id INTEGER DEFAULT NULL, -- 父分类ID(NULL表示一级分类) + level INTEGER DEFAULT 1, -- 层级(1=一级,2=二级) + sort_order INTEGER DEFAULT 0, -- 排序 + description TEXT, -- 描述 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (parent_id) REFERENCES category_tree(id) ON DELETE CASCADE +); + +-- 创建索引 +CREATE INDEX IF NOT EXISTS idx_category_parent ON category_tree(parent_id); +CREATE INDEX IF NOT EXISTS idx_category_level ON category_tree(level); + +-- 2. 修改商品表,支持新字段和二级分类 +-- 先备份原表 +ALTER TABLE products RENAME TO products_backup; + +-- 创建新的商品表 +CREATE TABLE products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, -- 商品名称 + model TEXT, -- 型号 + category_id INTEGER, -- 二级分类ID(关联category_tree) + category_name TEXT, -- 分类名称(冗余,方便查询) + unit TEXT DEFAULT '件', -- 单位 + cost_price REAL, -- 成本单价(可为空) + price REAL DEFAULT 0, -- 销售单价 + brand TEXT, -- 品牌 + specification TEXT, -- 规格参数 + source TEXT DEFAULT '老挝', -- 来源(中国/老挝) + remark TEXT, -- 备注 + stock_quantity REAL DEFAULT 0, -- 库存数量 + stock_warning REAL DEFAULT 0, -- 库存预警值 + status TEXT DEFAULT 'active', -- 状态:active/disabled + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (category_id) REFERENCES category_tree(id) +); + +-- 迁移旧数据(简化版,不处理旧数据迁移) +-- 直接创建空的新表结构 +-- INSERT INTO products ( +-- id, name, model, category_id, category_name, unit, +-- price, brand, specification, remark, status, created_at, updated_at +-- ) +-- SELECT +-- p.id, +-- p.name, +-- NULL as model, +-- NULL as category_id, +-- NULL as category_name, +-- COALESCE(p.unit, '件'), +-- COALESCE(p.price, 0), +-- NULL as brand, +-- NULL as specification, +-- p.remark, +-- 'active', +-- p.created_at, +-- p.updated_at +-- FROM products_backup p; + +-- 删除备份表 +DROP TABLE products_backup; + +-- 创建索引 +CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id); +CREATE INDEX IF NOT EXISTS idx_products_name ON products(name); +CREATE INDEX IF NOT EXISTS idx_products_status ON products(status); + +-- 3. 删除旧的categories表(数据已迁移到category_tree) +-- 注意:先检查是否还有其他表依赖categories +-- DROP TABLE IF EXISTS categories; + +-- ============================================ +-- 初始化默认分类数据 +-- ============================================ + +-- 插入一级分类 +INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES + ('电杆横担', NULL, 1, 1, '电杆、横担及相关配件'), + ('电缆电线', NULL, 1, 2, '各类电缆、电线产品'), + ('变压器', NULL, 1, 3, '变压器及相关设备'), + ('开关设备', NULL, 1, 4, '开关、断路器等设备'), + ('金具', NULL, 1, 5, '电力金具、连接件'), + ('工具仪器', NULL, 1, 6, '施工工具、检测仪器'), + ('劳保用品', NULL, 1, 7, '安全防护用品'), + ('其他材料', NULL, 1, 99, '其他未分类材料'); + +-- 插入二级分类(电杆横担) +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '混凝土电杆', id, 2, 1, '混凝土材质电杆' +FROM category_tree WHERE name = '电杆横担' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '钢管电杆', id, 2, 2, '钢管材质电杆' +FROM category_tree WHERE name = '电杆横担' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '横担', id, 2, 3, '各类横担' +FROM category_tree WHERE name = '电杆横担' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '抱箍', id, 2, 4, '电杆抱箍' +FROM category_tree WHERE name = '电杆横担' AND level = 1; + +-- 插入二级分类(电缆电线) +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '高压电缆', id, 2, 1, '高压电力电缆' +FROM category_tree WHERE name = '电缆电线' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '低压电缆', id, 2, 2, '低压电力电缆' +FROM category_tree WHERE name = '电缆电线' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '架空导线', id, 2, 3, '架空绝缘导线' +FROM category_tree WHERE name = '电缆电线' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '控制电缆', id, 2, 4, '控制用电缆' +FROM category_tree WHERE name = '电缆电线' AND level = 1; + +-- 插入二级分类(变压器) +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '配电变压器', id, 2, 1, '配电用变压器' +FROM category_tree WHERE name = '变压器' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '箱式变电站', id, 2, 2, '箱式变电站' +FROM category_tree WHERE name = '变压器' AND level = 1; + +-- 插入二级分类(开关设备) +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '断路器', id, 2, 1, '各类断路器' +FROM category_tree WHERE name = '开关设备' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '隔离开关', id, 2, 2, '隔离开关' +FROM category_tree WHERE name = '开关设备' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '熔断器', id, 2, 3, '熔断器' +FROM category_tree WHERE name = '开关设备' AND level = 1; + +-- 插入二级分类(金具) +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '耐张线夹', id, 2, 1, '耐张线夹' +FROM category_tree WHERE name = '金具' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '悬垂线夹', id, 2, 2, '悬垂线夹' +FROM category_tree WHERE name = '金具' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '连接金具', id, 2, 3, '连接金具' +FROM category_tree WHERE name = '金具' AND level = 1; + +-- 插入二级分类(工具仪器) +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '施工工具', id, 2, 1, '电力施工工具' +FROM category_tree WHERE name = '工具仪器' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '检测仪器', id, 2, 2, '检测测试仪器' +FROM category_tree WHERE name = '工具仪器' AND level = 1; + +-- 插入二级分类(劳保用品) +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '安全帽', id, 2, 1, '安全帽' +FROM category_tree WHERE name = '劳保用品' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '安全带', id, 2, 2, '安全带' +FROM category_tree WHERE name = '劳保用品' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '绝缘手套', id, 2, 3, '绝缘手套' +FROM category_tree WHERE name = '劳保用品' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '绝缘鞋', id, 2, 4, '绝缘鞋' +FROM category_tree WHERE name = '劳保用品' AND level = 1; + +-- 插入二级分类(其他材料) +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '标识标牌', id, 2, 1, '标识标牌' +FROM category_tree WHERE name = '其他材料' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '接地材料', id, 2, 2, '接地装置材料' +FROM category_tree WHERE name = '其他材料' AND level = 1; + +INSERT INTO category_tree (name, parent_id, level, sort_order, description) +SELECT '其他', id, 2, 99, '其他未分类' +FROM category_tree WHERE name = '其他材料' AND level = 1; + +-- ============================================ +-- 创建触发器:自动更新updated_at +-- ============================================ +CREATE TRIGGER IF NOT EXISTS update_category_tree_timestamp +AFTER UPDATE ON category_tree +BEGIN + UPDATE category_tree SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; +END; + +CREATE TRIGGER IF NOT EXISTS update_products_timestamp +AFTER UPDATE ON products +BEGIN + UPDATE products SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; +END; + +-- ============================================ +-- 迁移完成 +-- ============================================ +SELECT '数据库迁移完成' as message; +SELECT '商品表已更新,支持二级分类' as message; +SELECT '分类表已创建,支持树状结构' as message; diff --git a/backup_20260327/company-finance-system/backend/migrations/002_create_purchase_inventory.sql b/backup_20260327/company-finance-system/backend/migrations/002_create_purchase_inventory.sql new file mode 100644 index 0000000..09c2456 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/migrations/002_create_purchase_inventory.sql @@ -0,0 +1,77 @@ +-- 采购付款分离改造:数据库迁移脚本 +-- 创建时间:2026-03-25 + +-- ============================================ +-- 表1:采购申请表 (purchase_requests) +-- ============================================ +CREATE TABLE IF NOT EXISTS purchase_requests ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_code TEXT UNIQUE NOT NULL, + project_id INTEGER NOT NULL, + applicant TEXT NOT NULL, + request_date DATE NOT NULL, + supplier_id INTEGER, + supplier_name TEXT, + expense_category TEXT NOT NULL, + total_amount REAL NOT NULL DEFAULT 0, + currency TEXT DEFAULT 'CNY', + status TEXT DEFAULT 'pending', + remark TEXT, + attachments TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id), + FOREIGN KEY (supplier_id) REFERENCES suppliers(id) +); + +-- ============================================ +-- 表2:采购明细表 (purchase_request_items) +-- ============================================ +CREATE TABLE IF NOT EXISTS purchase_request_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + purchase_request_id INTEGER NOT NULL, + product_id INTEGER, + product_name TEXT NOT NULL, + specification TEXT, + unit TEXT, + quantity REAL NOT NULL DEFAULT 0, + unit_price REAL NOT NULL DEFAULT 0, + total_price REAL NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (purchase_request_id) REFERENCES purchase_requests(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) +); + +-- ============================================ +-- 表3:库存记录表 (inventory_records) +-- ============================================ +CREATE TABLE IF NOT EXISTS inventory_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + record_type TEXT NOT NULL, + project_id INTEGER, + purchase_request_id INTEGER, + product_id INTEGER NOT NULL, + quantity REAL NOT NULL DEFAULT 0, + unit_price REAL, + total_amount REAL, + record_date DATE NOT NULL, + operator TEXT, + remark TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (project_id) REFERENCES projects(id), + FOREIGN KEY (purchase_request_id) REFERENCES purchase_requests(id), + FOREIGN KEY (product_id) REFERENCES products(id) +); + +-- ============================================ +-- 修改:为付款申请表添加采购单关联字段 +-- ============================================ +ALTER TABLE payment_requests ADD COLUMN purchase_request_id INTEGER; +ALTER TABLE payment_requests ADD COLUMN payment_type TEXT DEFAULT 'company'; + +-- 创建索引 +CREATE INDEX IF NOT EXISTS idx_purchase_requests_project ON purchase_requests(project_id); +CREATE INDEX IF NOT EXISTS idx_purchase_requests_status ON purchase_requests(status); +CREATE INDEX IF NOT EXISTS idx_purchase_request_items_request ON purchase_request_items(purchase_request_id); +CREATE INDEX IF NOT EXISTS idx_inventory_records_product ON inventory_records(product_id); +CREATE INDEX IF NOT EXISTS idx_inventory_records_project ON inventory_records(project_id); diff --git a/backup_20260327/company-finance-system/backend/package.json b/backup_20260327/company-finance-system/backend/package.json new file mode 100644 index 0000000..88b88ea --- /dev/null +++ b/backup_20260327/company-finance-system/backend/package.json @@ -0,0 +1,24 @@ +{ + "name": "company-finance-system-backend", + "version": "1.0.0", + "description": "供应商管理CRUD API", + "main": "server-complete.js", + "scripts": { + "start": "node final-backend.js", + "dev": "nodemon final-backend.js" + }, + "dependencies": { + "cors": "^2.8.6", + "cos-nodejs-sdk-v5": "^2.15.4", + "dotenv": "^16.6.1", + "express": "^4.18.2", + "express-validator": "^7.3.1", + "multer": "^2.1.1", + "pg": "^8.11.3", + "sqlite3": "^6.0.1", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } +} diff --git a/backup_20260327/company-finance-system/backend/port3000-server.js b/backup_20260327/company-finance-system/backend/port3000-server.js new file mode 100644 index 0000000..df9c615 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/port3000-server.js @@ -0,0 +1,43 @@ +const express = require('express'); +const app = express(); +const PORT = 3000; + +app.get('/', (req, res) => { + res.send(` + + + 测试 - 端口3000 + +

✅ 公司财务管理系统 - 测试入口

+

服务器: 43.161.248.209:${PORT}

+

状态: 运行正常

+ +
+

🚀 立即访问系统:

+

👉 点击这里打开主应用

+

如果上方链接无法访问,请尝试:

+ +
+ +
+

🔧 如果端口5000无法访问:

+

1. 检查腾讯云安全组规则,确保端口5000已开放

+

2. 或使用此页面作为入口,系统功能正常

+
+ + + `); +}); + +// 重定向到5000端口 +app.get('/redirect', (req, res) => { + res.redirect('http://43.161.248.209:5000/app/index.html'); +}); + +app.listen(PORT, '0.0.0.0', () => { + console.log(`🔄 重定向服务器运行在: http://0.0.0.0:${PORT}`); + console.log(`🔗 访问: http://43.161.248.209:${PORT}`); +}); diff --git a/backup_20260327/company-finance-system/backend/port5000-server.js b/backup_20260327/company-finance-system/backend/port5000-server.js new file mode 100644 index 0000000..e233714 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/port5000-server.js @@ -0,0 +1,69 @@ +const express = require('express'); +const path = require('path'); +const app = express(); +const PORT = 5000; + +// 静态文件服务 +app.use(express.static(path.join(__dirname, '../frontend/dist'))); + +// 健康检查 +app.get('/api/health', (req, res) => { + res.json({ + success: true, + message: '端口5000测试服务', + version: '1.0.0', + timestamp: new Date().toISOString(), + bind_address: '0.0.0.0', + port: PORT, + status: 'running' + }); +}); + +// 测试页面 +app.get('/test-5000', (req, res) => { + res.send(` + + + 端口5000测试 + +

✅ 端口5000测试成功!

+

服务器: 43.161.248.209:${PORT}

+

绑定地址: 0.0.0.0

+

状态: 运行正常

+ +
+

🔗 系统链接:

+ +
+ + + `); +}); + +// 默认路由 +app.get('/', (req, res) => { + res.redirect('/test-5000'); +}); + +// 启动服务器 - 明确绑定到0.0.0.0 +const server = app.listen(PORT, '0.0.0.0', () => { + const address = server.address(); + console.log(` + 🔧 端口5000测试服务器 + ============================= + 📍 绑定地址: ${address.address}:${address.port} + 🌐 外部访问: http://43.161.248.209:${PORT} + 🔗 测试页面: http://43.161.248.209:${PORT}/test-5000 + ✅ 明确绑定到: 0.0.0.0 + ============================= + `); +}); + +// 错误处理 +server.on('error', (err) => { + console.error('服务器启动错误:', err); +}); diff --git a/backup_20260327/company-finance-system/backend/postman-collection.json b/backup_20260327/company-finance-system/backend/postman-collection.json new file mode 100644 index 0000000..b210d74 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/postman-collection.json @@ -0,0 +1,110 @@ +{ + "info": { + "name": "供应商管理API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "健康检查", + "request": { + "method": "GET", + "url": "{{base_url}}/health" + } + }, + { + "name": "获取供应商列表", + "request": { + "method": "GET", + "url": "{{base_url}}/api/suppliers", + "query": [ + { + "key": "page", + "value": "1", + "description": "页码" + }, + { + "key": "limit", + "value": "10", + "description": "每页数量" + }, + { + "key": "search", + "value": "", + "description": "搜索关键词" + }, + { + "key": "type", + "value": "", + "description": "供应商类型" + }, + { + "key": "status", + "value": "", + "description": "状态" + } + ] + } + }, + { + "name": "获取单个供应商", + "request": { + "method": "GET", + "url": "{{base_url}}/api/suppliers/1" + } + }, + { + "name": "创建供应商", + "request": { + "method": "POST", + "url": "{{base_url}}/api/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"新供应商有限公司\",\n \"code\": \"NEW001\",\n \"type\": \"manufacturer\",\n \"contact_person\": \"联系人\",\n \"phone\": \"13800138000\",\n \"email\": \"contact@new.com\",\n \"address\": \"地址\",\n \"tax_number\": \"911101087654321\",\n \"bank_account\": \"银行账户\",\n \"status\": \"active\",\n \"rating\": 4,\n \"notes\": \"备注\"\n}" + } + } + }, + { + "name": "更新供应商", + "request": { + "method": "PUT", + "url": "{{base_url}}/api/suppliers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"contact_person\": \"更新后的联系人\",\n \"phone\": \"13900139000\",\n \"email\": \"updated@example.com\"\n}" + } + } + }, + { + "name": "删除供应商", + "request": { + "method": "DELETE", + "url": "{{base_url}}/api/suppliers/1" + } + }, + { + "name": "获取供应商联系人", + "request": { + "method": "GET", + "url": "{{base_url}}/api/suppliers/1/contacts" + } + } + ], + "variable": [ + { + "key": "base_url", + "value": "http://localhost:3000" + } + ] +} \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/production-server.js b/backup_20260327/company-finance-system/backend/production-server.js new file mode 100644 index 0000000..5a89ea1 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/production-server.js @@ -0,0 +1,436 @@ +const express = require('express'); +const cors = require('cors'); +const path = require('path'); +const db = require('./db'); + +// 导入路由模块 +const financeRouter = require('./finance-api'); + +const app = express(); +const PORT = process.env.PORT || 5000; + +// 中间件 +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// 静态文件服务 - 前端应用 +app.use('/app', express.static(path.join(__dirname, '../frontend/dist'))); + +// 健康检查 +app.get('/health', async (req, res) => { + try { + // 测试数据库连接 + await db.query('SELECT 1'); + + res.json({ + status: 'healthy', + service: 'company-finance-system', + timestamp: new Date().toISOString(), + version: '1.0.0', + database: 'connected', + endpoints: { + frontend: '/app/index.html', + api: '/api/*', + finance: '/api/finance/*', + test: '/test' + } + }); + } catch (error) { + res.status(500).json({ + status: 'unhealthy', + service: 'company-finance-system', + timestamp: new Date().toISOString(), + database: 'disconnected', + error: error.message + }); + } +}); + +// API路由 +app.use('/api/finance', financeRouter); + +// 客户管理API +app.get('/api/customers', async (req, res) => { + try { + const result = await db.query(` + SELECT + c.*, + COUNT(ct.contact_id) as contact_count + FROM customers c + LEFT JOIN contacts ct ON c.customer_id = ct.customer_id + GROUP BY c.customer_id + ORDER BY c.created_at DESC + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取客户失败:', error); + res.status(500).json({ + success: false, + message: '获取客户失败', + error: error.message + }); + } +}); + +// 供应商管理API +app.get('/api/suppliers', async (req, res) => { + try { + const result = await db.query(` + SELECT + s.*, + COUNT(ct.contact_id) as contact_count + FROM suppliers s + LEFT JOIN contacts ct ON s.supplier_id = ct.supplier_id + GROUP BY s.supplier_id + ORDER BY s.created_at DESC + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取供应商失败:', error); + res.status(500).json({ + success: false, + message: '获取供应商失败', + error: error.message + }); + } +}); + +// 项目管理API +app.get('/api/projects', async (req, res) => { + try { + const result = await db.query(` + SELECT + p.*, + c.company_name as customer_name, + s.company_name as supplier_name, + COUNT(pn.node_id) as payment_node_count, + SUM(pn.amount) as total_amount + FROM projects p + LEFT JOIN customers c ON p.customer_id = c.customer_id + LEFT JOIN suppliers s ON p.supplier_id = s.supplier_id + LEFT JOIN payment_nodes pn ON p.project_id = pn.project_id + GROUP BY p.project_id, c.company_name, s.company_name + ORDER BY p.created_at DESC + `); + + res.json({ + success: true, + data: result.rows, + count: result.rows.length + }); + } catch (error) { + console.error('获取项目失败:', error); + res.status(500).json({ + success: false, + message: '获取项目失败', + error: error.message + }); + } +}); + +// 测试数据API(用于演示) +app.get('/api/test-data', async (req, res) => { + try { + // 获取各种统计数据 + const [customers, suppliers, projects, paymentNodes, paymentRecords] = await Promise.all([ + db.query('SELECT COUNT(*) as count FROM customers'), + db.query('SELECT COUNT(*) as count FROM suppliers'), + db.query('SELECT COUNT(*) as count FROM projects'), + db.query('SELECT COUNT(*) as count FROM payment_nodes'), + db.query('SELECT COUNT(*) as count FROM payment_records') + ]); + + res.json({ + success: true, + data: { + customers: customers.rows[0].count, + suppliers: suppliers.rows[0].count, + projects: projects.rows[0].count, + paymentNodes: paymentNodes.rows[0].count, + paymentRecords: paymentRecords.rows[0].count, + timestamp: new Date().toISOString() + } + }); + } catch (error) { + res.json({ + success: false, + message: '获取测试数据失败', + error: error.message + }); + } +}); + +// 测试页面 +app.get('/test', (req, res) => { + res.send(` + + + + 公司财务管理系统 - 生产环境测试 + + + + +
+
+

🏢 公司财务管理系统

+
生产环境 v1.0.0 | 服务器: 43.161.248.209:${PORT}
+
+ +
+
+

🚀 立即使用

+

访问完整的前端应用程序,开始管理您的财务。

+ +
+ +
+

🔧 系统测试

+

测试各个组件是否正常工作。

+ +
+
+ +
+

📊 系统状态

+
检查中...
+ +
+
+ +
+

📋 系统信息

+
+
+
服务器IP
+
43.161.248.209
+
+
+
服务端口
+
${PORT}
+
+
+
数据库
+
PostgreSQL
+
+
+
前端技术
+
React + Ant Design
+
+
+ +
+

🔗 快速链接

+ +
+
+
+ + + + + `); +}); + +// 默认路由重定向到前端 +app.get('/', (req, res) => { + res.redirect('/app/index.html'); +}); + +// 404处理 +app.use((req, res) => { + res.status(404).send(` + + + 页面未找到 + +

404 - 页面未找到

+

您访问的页面不存在。

+

返回首页 | 测试页面

+ + + `); +}); + +// 错误处理中间件 +app.use((err, req, res, next) => { + console.error(err.stack); + res.status(500).json({ + success: false, + message: '服务器内部错误', + error: process.env.NODE_ENV === 'development' ? err.message : undefined + }); +}); + +// 启动服务器 +const server = app.listen(PORT, '0.0.0.0', () => { + console.log(` + 🚀 公司财务管理系统 - 生产服务器 + ==================================== + 📍 服务器地址: http://0.0.0.0:${PORT} + 🌐 外部访问: http://43.161.248.209:${PORT} + + 🔗 重要链接: + - 前端应用: http://43.161.248.209:${PORT}/app/index.html + - 测试页面: http://43.161.248.209:${PORT}/test + - 健康检查: http://43.161.248.209:${PORT}/health + - API文档: http://43.161.248.209:${PORT}/api/* + + 📊 已启用的模块: + ✅ 财务模块 (付款节点、付款记录、汇率) + ✅ 客户管理 + ✅ 供应商管理 + ✅ 项目管理 + ✅ 静态文件服务 + + ⏰ 启动时间: ${new Date().toISOString()} + ==================================== + `); +}); + +// 优雅关闭 +process.on('SIGTERM', () => { + console.log('收到SIGTERM信号,正在关闭服务器...'); + server.close(() => { + console.log('服务器已关闭'); + process.exit(0); + }); +}); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/quick-test.js b/backup_20260327/company-finance-system/backend/quick-test.js new file mode 100644 index 0000000..0961890 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/quick-test.js @@ -0,0 +1,136 @@ +// 快速测试脚本 - 验证API端点 +const http = require('http'); + +const BASE_URL = 'http://localhost:3000'; +const TEST_CUSTOMER = { + name: '快速测试客户', + email: 'quick-test@example.com', + phone: '12345678901', + company: '测试公司' +}; + +async function testEndpoint(method, path, data = null) { + return new Promise((resolve, reject) => { + const options = { + hostname: 'localhost', + port: 3000, + path, + method, + headers: { + 'Content-Type': 'application/json' + } + }; + + const req = http.request(options, (res) => { + let responseData = ''; + res.on('data', (chunk) => { + responseData += chunk; + }); + res.on('end', () => { + try { + const parsed = JSON.parse(responseData); + resolve({ + statusCode: res.statusCode, + data: parsed + }); + } catch (e) { + resolve({ + statusCode: res.statusCode, + data: responseData + }); + } + }); + }); + + req.on('error', (err) => { + reject(err); + }); + + if (data) { + req.write(JSON.stringify(data)); + } + req.end(); + }); +} + +async function runTests() { + console.log('=== 客户管理API快速测试 ===\n'); + + try { + // 1. 测试健康检查 + console.log('1. 测试健康检查...'); + const health = await testEndpoint('GET', '/health'); + console.log(` 状态码: ${health.statusCode}, 响应: ${JSON.stringify(health.data)}\n`); + + // 2. 测试获取客户列表 + console.log('2. 测试获取客户列表...'); + const list = await testEndpoint('GET', '/api/customers?limit=2'); + console.log(` 状态码: ${list.statusCode}, 获取到 ${list.data.data?.length || 0} 个客户\n`); + + // 3. 测试创建客户 + console.log('3. 测试创建客户...'); + const create = await testEndpoint('POST', '/api/customers', TEST_CUSTOMER); + console.log(` 状态码: ${create.statusCode}, 客户ID: ${create.data.data?.id || 'N/A'}`); + + let customerId = create.data.data?.id; + + if (customerId) { + // 4. 测试获取单个客户 + console.log(`\n4. 测试获取单个客户 (ID=${customerId})...`); + const getOne = await testEndpoint('GET', `/api/customers/${customerId}`); + console.log(` 状态码: ${getOne.statusCode}, 客户名称: ${getOne.data.data?.name || 'N/A'}\n`); + + // 5. 测试更新客户 + console.log('5. 测试更新客户...'); + const update = await testEndpoint('PUT', `/api/customers/${customerId}`, { + phone: '13888888888', + company: '更新后的公司' + }); + console.log(` 状态码: ${update.statusCode}, 更新成功: ${update.data.success || false}\n`); + + // 6. 测试获取客户联系人 + console.log('6. 测试获取客户联系人...'); + const contacts = await testEndpoint('GET', `/api/customers/${customerId}/contacts`); + console.log(` 状态码: ${contacts.statusCode}, 联系人数量: ${contacts.data.data?.length || 0}\n`); + + // 7. 测试删除客户 + console.log('7. 测试删除客户...'); + const del = await testEndpoint('DELETE', `/api/customers/${customerId}`); + console.log(` 状态码: ${del.statusCode}, 删除成功: ${del.data.success || false}\n`); + } + + // 8. 测试搜索功能 + console.log('8. 测试搜索功能 (搜索"张")...'); + const search = await testEndpoint('GET', '/api/customers?search=张'); + console.log(` 状态码: ${search.statusCode}, 搜索结果数量: ${search.data.data?.length || 0}\n`); + + // 9. 测试验证错误 + console.log('9. 测试验证错误 (无效邮箱)...'); + const invalid = await testEndpoint('POST', '/api/customers', { + name: '无效客户', + email: 'invalid-email' + }); + console.log(` 状态码: ${invalid.statusCode}, 验证错误: ${invalid.statusCode === 400}\n`); + + console.log('=== 测试完成 ==='); + console.log('所有端点基本功能验证完成。'); + console.log('如需完整测试,请运行: ./test-api.sh'); + + } catch (error) { + console.error('测试过程中发生错误:', error.message); + console.log('请确保服务器正在运行: npm run dev'); + } +} + +// 检查服务器是否运行 +testEndpoint('GET', '/health') + .then(() => { + runTests(); + }) + .catch(() => { + console.log('服务器未运行或无法连接。请先启动服务器:'); + console.log('1. cd /opt/company-finance-system/backend'); + console.log('2. npm run dev'); + console.log('\n然后在另一个终端运行此测试:'); + console.log('node quick-test.js'); + }); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/reset-data.js b/backup_20260327/company-finance-system/backend/reset-data.js new file mode 100644 index 0000000..47258d7 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/reset-data.js @@ -0,0 +1,62 @@ +const db = require('./db-sqlite'); + +async function resetData() { + try { + console.log('开始重置数据...'); + + // 1. 清空项目管理里的数据 + console.log('清空项目相关数据...'); + + // 删除质保金数据 + await db.query('DELETE FROM warranty_deposits'); + console.log('已删除质保金数据'); + + // 删除项目财务信息数据 + await db.query('DELETE FROM project_finances'); + console.log('已删除项目财务信息数据'); + + // 删除施工节点数据 + await db.query('DELETE FROM project_milestones'); + console.log('已删除施工节点数据'); + + // 删除项目材料数据 + await db.query('DELETE FROM project_materials'); + console.log('已删除项目材料数据'); + + // 删除分包合同数据 + await db.query('DELETE FROM subcontracts'); + console.log('已删除分包合同数据'); + + // 删除项目合同数据 + await db.query('DELETE FROM project_contracts'); + console.log('已删除项目合同数据'); + + // 删除项目数据 + await db.query('DELETE FROM projects'); + console.log('已删除项目数据'); + + // 2. 修改预算项目的状态 + console.log('修改预算项目状态...'); + + // 把所有预算项目状态改为商谈中 + await db.query('UPDATE budget_projects SET status = ?', ['negotiating']); + console.log('已将所有预算项目状态改为商谈中'); + + // 3. 检查预算项目列表 + const budgetProjectsResult = await db.query('SELECT * FROM budget_projects'); + console.log('\n预算项目列表:'); + budgetProjectsResult.rows.forEach(project => { + console.log(`ID: ${project.id}, 名称: ${project.name}, 状态: ${project.status}`); + }); + + console.log('\n✅ 数据重置完成!'); + process.exit(0); + + } catch (error) { + console.error('重置数据失败:', error); + process.exit(1); + } +} + +// 执行重置 +resetData(); diff --git a/backup_20260327/company-finance-system/backend/serve-frontend.js b/backup_20260327/company-finance-system/backend/serve-frontend.js new file mode 100644 index 0000000..219ee8d --- /dev/null +++ b/backup_20260327/company-finance-system/backend/serve-frontend.js @@ -0,0 +1,75 @@ +const express = require('express'); +const path = require('path'); +const app = express(); +const PORT = 5000; + +// 静态文件服务 - 前端 +app.use('/app', express.static(path.join(__dirname, '../frontend/dist'))); + +// API路由 +app.get('/api/health', (req, res) => { + res.json({ + status: 'healthy', + service: 'company-finance-system', + timestamp: new Date().toISOString(), + version: '1.0.0', + endpoints: { + frontend: '/app/index.html', + api: '/api/*', + test: '/test' + } + }); +}); + +// 测试页面 +app.get('/test', (req, res) => { + res.send(` + + + 系统测试 + +

✅ 公司财务管理系统 - 统一访问入口

+

服务器: 43.161.248.209:5000

+ +
+

🚀 立即访问:

+

👉 点击这里打开前端应用

+

或复制链接:http://43.161.248.209:5000/app/index.html

+
+ +
+

🔗 其他链接:

+ +
+ +
+

📱 测试说明:

+

1. 此页面通过端口5000访问(已确认开放)

+

2. 前端应用已集成到同一端口

+

3. 无需担心8080端口问题

+

4. 请现在测试:/app/index.html

+
+ + + `); +}); + +// 默认路由重定向到前端 +app.get('/', (req, res) => { + res.redirect('/app/index.html'); +}); + +// 404处理 +app.use((req, res) => { + res.status(404).send('页面未找到 - 请访问 前端应用'); +}); + +app.listen(PORT, '0.0.0.0', () => { + console.log(`🚀 统一服务器运行在: http://0.0.0.0:${PORT}`); + console.log(`🌐 前端应用: http://0.0.0.0:${PORT}/app/index.html`); + console.log(`🔧 测试页面: http://0.0.0.0:${PORT}/test`); +}); diff --git a/backup_20260327/company-finance-system/backend/server-complete.js b/backup_20260327/company-finance-system/backend/server-complete.js new file mode 100644 index 0000000..16852ae --- /dev/null +++ b/backup_20260327/company-finance-system/backend/server-complete.js @@ -0,0 +1,403 @@ +const express = require('express'); +const cors = require('cors'); +const { body, param, query, validationResult } = require('express-validator'); +require('dotenv').config(); + +const db = require('./db'); + +const app = express(); +const PORT = process.env.PORT || 3002; + +// 中间件 +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// 验证错误处理中间件 +const validate = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + success: false, + errors: errors.array() + }); + } + next(); +}; + +// 健康检查端点 +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + service: 'Customer Management API' + }); +}); + +// ==================== 客户管理 API ==================== + +// 1. GET /api/customers - 获取客户列表(分页、搜索) +app.get('/api/customers', + [ + query('page').optional().isInt({ min: 1 }).toInt(), + query('limit').optional().isInt({ min: 1, max: 100 }).toInt(), + query('search').optional().trim(), + query('status').optional().trim() + ], + validate, + async (req, res) => { + try { + const page = req.query.page || 1; + const limit = req.query.limit || 10; + const offset = (page - 1) * limit; + const search = req.query.search || ''; + const status = req.query.status || ''; + + let query = 'SELECT * FROM customers WHERE 1=1'; + let queryParams = []; + let paramCount = 1; + + if (search) { + query += ` AND (name ILIKE $${paramCount} OR email ILIKE $${paramCount} OR company ILIKE $${paramCount})`; + queryParams.push(`%${search}%`); + paramCount++; + } + + if (status) { + query += ` AND status = $${paramCount}`; + queryParams.push(status); + paramCount++; + } + + // 获取总数 + const countQuery = query.replace('SELECT *', 'SELECT COUNT(*) as total'); + const countResult = await db.query(countQuery, queryParams); + const total = parseInt(countResult.rows[0].total); + + // 获取分页数据 + query += ` ORDER BY created_at DESC LIMIT $${paramCount} OFFSET $${paramCount + 1}`; + queryParams.push(limit, offset); + + const result = await db.query(query, queryParams); + + res.json({ + success: true, + data: result.rows, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total, + totalPages: Math.ceil(total / limit) + } + }); + } catch (error) { + console.error('Error fetching customers:', error); + res.status(500).json({ + success: false, + message: 'Failed to fetch customers', + error: error.message + }); + } + } +); + +// 2. GET /api/customers/:id - 获取单个客户 +app.get('/api/customers/:id', + [ + param('id').isInt({ min: 1 }) + ], + validate, + async (req, res) => { + try { + const { id } = req.params; + const result = await db.query('SELECT * FROM customers WHERE id = $1', [id]); + + if (result.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Customer not found' + }); + } + + res.json({ + success: true, + data: result.rows[0] + }); + } catch (error) { + console.error('Error fetching customer:', error); + res.status(500).json({ + success: false, + message: 'Failed to fetch customer', + error: error.message + }); + } + } +); + +// 3. POST /api/customers - 创建客户 +app.post('/api/customers', + [ + body('name').notEmpty().trim().withMessage('Name is required'), + body('email').notEmpty().trim().isEmail().withMessage('Valid email is required'), + body('phone').optional().trim(), + body('address').optional().trim(), + body('company').optional().trim(), + body('tax_id').optional().trim(), + body('status').optional().isIn(['active', 'inactive']).withMessage('Status must be active or inactive') + ], + validate, + async (req, res) => { + try { + const { name, email, phone, address, company, tax_id, status = 'active' } = req.body; + + const result = await db.query( + `INSERT INTO customers (name, email, phone, address, company, tax_id, status) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING *`, + [name, email, phone, address, company, tax_id, status] + ); + + res.status(201).json({ + success: true, + message: 'Customer created successfully', + data: result.rows[0] + }); + } catch (error) { + console.error('Error creating customer:', error); + + // 处理唯一约束错误 + if (error.code === '23505') { // unique_violation + return res.status(409).json({ + success: false, + message: 'Email already exists' + }); + } + + res.status(500).json({ + success: false, + message: 'Failed to create customer', + error: error.message + }); + } + } +); + +// 4. PUT /api/customers/:id - 更新客户 +app.put('/api/customers/:id', + [ + param('id').isInt({ min: 1 }), + body('name').optional().trim(), + body('email').optional().trim().isEmail().withMessage('Valid email is required if provided'), + body('phone').optional().trim(), + body('address').optional().trim(), + body('company').optional().trim(), + body('tax_id').optional().trim(), + body('status').optional().isIn(['active', 'inactive']).withMessage('Status must be active or inactive') + ], + validate, + async (req, res) => { + try { + const { id } = req.params; + const { name, email, phone, address, company, tax_id, status } = req.body; + + // 检查客户是否存在 + const checkResult = await db.query('SELECT * FROM customers WHERE id = $1', [id]); + if (checkResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Customer not found' + }); + } + + // 构建更新字段 + const updateFields = []; + const values = []; + let paramCount = 1; + + if (name !== undefined) { + updateFields.push(`name = $${paramCount}`); + values.push(name); + paramCount++; + } + + if (email !== undefined) { + updateFields.push(`email = $${paramCount}`); + values.push(email); + paramCount++; + } + + if (phone !== undefined) { + updateFields.push(`phone = $${paramCount}`); + values.push(phone); + paramCount++; + } + + if (address !== undefined) { + updateFields.push(`address = $${paramCount}`); + values.push(address); + paramCount++; + } + + if (company !== undefined) { + updateFields.push(`company = $${paramCount}`); + values.push(company); + paramCount++; + } + + if (tax_id !== undefined) { + updateFields.push(`tax_id = $${paramCount}`); + values.push(tax_id); + paramCount++; + } + + if (status !== undefined) { + updateFields.push(`status = $${paramCount}`); + values.push(status); + paramCount++; + } + + // 添加更新时间 + updateFields.push(`updated_at = CURRENT_TIMESTAMP`); + + if (updateFields.length === 1) { // 只有updated_at被更新 + return res.status(400).json({ + success: false, + message: 'No fields to update' + }); + } + + values.push(id); + const query = `UPDATE customers SET ${updateFields.join(', ')} WHERE id = $${paramCount} RETURNING *`; + + const result = await db.query(query, values); + + res.json({ + success: true, + message: 'Customer updated successfully', + data: result.rows[0] + }); + } catch (error) { + console.error('Error updating customer:', error); + + // 处理唯一约束错误 + if (error.code === '23505') { // unique_violation + return res.status(409).json({ + success: false, + message: 'Email already exists' + }); + } + + res.status(500).json({ + success: false, + message: 'Failed to update customer', + error: error.message + }); + } + } +); + +// 5. DELETE /api/customers/:id - 删除客户 +app.delete('/api/customers/:id', + [ + param('id').isInt({ min: 1 }) + ], + validate, + async (req, res) => { + try { + const { id } = req.params; + + // 检查客户是否存在 + const checkResult = await db.query('SELECT * FROM customers WHERE id = $1', [id]); + if (checkResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Customer not found' + }); + } + + await db.query('DELETE FROM customers WHERE id = $1', [id]); + + res.json({ + success: true, + message: 'Customer deleted successfully' + }); + } catch (error) { + console.error('Error deleting customer:', error); + res.status(500).json({ + success: false, + message: 'Failed to delete customer', + error: error.message + }); + } + } +); + +// 6. GET /api/customers/:id/contacts - 获取客户联系人 +app.get('/api/customers/:id/contacts', + [ + param('id').isInt({ min: 1 }) + ], + validate, + async (req, res) => { + try { + const { id } = req.params; + + // 检查客户是否存在 + const customerResult = await db.query('SELECT * FROM customers WHERE id = $1', [id]); + if (customerResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Customer not found' + }); + } + + const result = await db.query( + 'SELECT * FROM contacts WHERE customer_id = $1 ORDER BY is_primary DESC, created_at DESC', + [id] + ); + + res.json({ + success: true, + data: result.rows + }); + } catch (error) { + console.error('Error fetching customer contacts:', error); + res.status(500).json({ + success: false, + message: 'Failed to fetch customer contacts', + error: error.message + }); + } + } +); + +// 错误处理中间件 +app.use((err, req, res, next) => { + console.error(err.stack); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: process.env.NODE_ENV === 'development' ? err.message : undefined + }); +}); + +// 404处理 +app.use((req, res) => { + res.status(404).json({ + success: false, + message: 'Endpoint not found' + }); +}); + +// 启动服务器 +app.listen(PORT, () => { + console.log(`Customer Management API server running on port ${PORT}`); + console.log('Available endpoints:'); + console.log(' GET /health'); + console.log(' GET /api/customers'); + console.log(' GET /api/customers/:id'); + console.log(' POST /api/customers'); + console.log(' PUT /api/customers/:id'); + console.log(' DELETE /api/customers/:id'); + console.log(' GET /api/customers/:id/contacts'); +}); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/server-complete.js.backup b/backup_20260327/company-finance-system/backend/server-complete.js.backup new file mode 100644 index 0000000..586cc9b --- /dev/null +++ b/backup_20260327/company-finance-system/backend/server-complete.js.backup @@ -0,0 +1,403 @@ +const express = require('express'); +const cors = require('cors'); +const { body, param, query, validationResult } = require('express-validator'); +require('dotenv').config(); + +const db = require('./db'); + +const app = express(); +const PORT = process.env.PORT || 3000; + +// 中间件 +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// 验证错误处理中间件 +const validate = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + success: false, + errors: errors.array() + }); + } + next(); +}; + +// 健康检查端点 +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + service: 'Customer Management API' + }); +}); + +// ==================== 客户管理 API ==================== + +// 1. GET /api/customers - 获取客户列表(分页、搜索) +app.get('/api/customers', + [ + query('page').optional().isInt({ min: 1 }).toInt(), + query('limit').optional().isInt({ min: 1, max: 100 }).toInt(), + query('search').optional().trim(), + query('status').optional().trim() + ], + validate, + async (req, res) => { + try { + const page = req.query.page || 1; + const limit = req.query.limit || 10; + const offset = (page - 1) * limit; + const search = req.query.search || ''; + const status = req.query.status || ''; + + let query = 'SELECT * FROM customers WHERE 1=1'; + let queryParams = []; + let paramCount = 1; + + if (search) { + query += ` AND (name ILIKE $${paramCount} OR email ILIKE $${paramCount} OR company ILIKE $${paramCount})`; + queryParams.push(`%${search}%`); + paramCount++; + } + + if (status) { + query += ` AND status = $${paramCount}`; + queryParams.push(status); + paramCount++; + } + + // 获取总数 + const countQuery = query.replace('SELECT *', 'SELECT COUNT(*) as total'); + const countResult = await db.query(countQuery, queryParams); + const total = parseInt(countResult.rows[0].total); + + // 获取分页数据 + query += ` ORDER BY created_at DESC LIMIT $${paramCount} OFFSET $${paramCount + 1}`; + queryParams.push(limit, offset); + + const result = await db.query(query, queryParams); + + res.json({ + success: true, + data: result.rows, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total, + totalPages: Math.ceil(total / limit) + } + }); + } catch (error) { + console.error('Error fetching customers:', error); + res.status(500).json({ + success: false, + message: 'Failed to fetch customers', + error: error.message + }); + } + } +); + +// 2. GET /api/customers/:id - 获取单个客户 +app.get('/api/customers/:id', + [ + param('id').isInt({ min: 1 }) + ], + validate, + async (req, res) => { + try { + const { id } = req.params; + const result = await db.query('SELECT * FROM customers WHERE id = $1', [id]); + + if (result.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Customer not found' + }); + } + + res.json({ + success: true, + data: result.rows[0] + }); + } catch (error) { + console.error('Error fetching customer:', error); + res.status(500).json({ + success: false, + message: 'Failed to fetch customer', + error: error.message + }); + } + } +); + +// 3. POST /api/customers - 创建客户 +app.post('/api/customers', + [ + body('name').notEmpty().trim().withMessage('Name is required'), + body('email').notEmpty().trim().isEmail().withMessage('Valid email is required'), + body('phone').optional().trim(), + body('address').optional().trim(), + body('company').optional().trim(), + body('tax_id').optional().trim(), + body('status').optional().isIn(['active', 'inactive']).withMessage('Status must be active or inactive') + ], + validate, + async (req, res) => { + try { + const { name, email, phone, address, company, tax_id, status = 'active' } = req.body; + + const result = await db.query( + `INSERT INTO customers (name, email, phone, address, company, tax_id, status) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING *`, + [name, email, phone, address, company, tax_id, status] + ); + + res.status(201).json({ + success: true, + message: 'Customer created successfully', + data: result.rows[0] + }); + } catch (error) { + console.error('Error creating customer:', error); + + // 处理唯一约束错误 + if (error.code === '23505') { // unique_violation + return res.status(409).json({ + success: false, + message: 'Email already exists' + }); + } + + res.status(500).json({ + success: false, + message: 'Failed to create customer', + error: error.message + }); + } + } +); + +// 4. PUT /api/customers/:id - 更新客户 +app.put('/api/customers/:id', + [ + param('id').isInt({ min: 1 }), + body('name').optional().trim(), + body('email').optional().trim().isEmail().withMessage('Valid email is required if provided'), + body('phone').optional().trim(), + body('address').optional().trim(), + body('company').optional().trim(), + body('tax_id').optional().trim(), + body('status').optional().isIn(['active', 'inactive']).withMessage('Status must be active or inactive') + ], + validate, + async (req, res) => { + try { + const { id } = req.params; + const { name, email, phone, address, company, tax_id, status } = req.body; + + // 检查客户是否存在 + const checkResult = await db.query('SELECT * FROM customers WHERE id = $1', [id]); + if (checkResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Customer not found' + }); + } + + // 构建更新字段 + const updateFields = []; + const values = []; + let paramCount = 1; + + if (name !== undefined) { + updateFields.push(`name = $${paramCount}`); + values.push(name); + paramCount++; + } + + if (email !== undefined) { + updateFields.push(`email = $${paramCount}`); + values.push(email); + paramCount++; + } + + if (phone !== undefined) { + updateFields.push(`phone = $${paramCount}`); + values.push(phone); + paramCount++; + } + + if (address !== undefined) { + updateFields.push(`address = $${paramCount}`); + values.push(address); + paramCount++; + } + + if (company !== undefined) { + updateFields.push(`company = $${paramCount}`); + values.push(company); + paramCount++; + } + + if (tax_id !== undefined) { + updateFields.push(`tax_id = $${paramCount}`); + values.push(tax_id); + paramCount++; + } + + if (status !== undefined) { + updateFields.push(`status = $${paramCount}`); + values.push(status); + paramCount++; + } + + // 添加更新时间 + updateFields.push(`updated_at = CURRENT_TIMESTAMP`); + + if (updateFields.length === 1) { // 只有updated_at被更新 + return res.status(400).json({ + success: false, + message: 'No fields to update' + }); + } + + values.push(id); + const query = `UPDATE customers SET ${updateFields.join(', ')} WHERE id = $${paramCount} RETURNING *`; + + const result = await db.query(query, values); + + res.json({ + success: true, + message: 'Customer updated successfully', + data: result.rows[0] + }); + } catch (error) { + console.error('Error updating customer:', error); + + // 处理唯一约束错误 + if (error.code === '23505') { // unique_violation + return res.status(409).json({ + success: false, + message: 'Email already exists' + }); + } + + res.status(500).json({ + success: false, + message: 'Failed to update customer', + error: error.message + }); + } + } +); + +// 5. DELETE /api/customers/:id - 删除客户 +app.delete('/api/customers/:id', + [ + param('id').isInt({ min: 1 }) + ], + validate, + async (req, res) => { + try { + const { id } = req.params; + + // 检查客户是否存在 + const checkResult = await db.query('SELECT * FROM customers WHERE id = $1', [id]); + if (checkResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Customer not found' + }); + } + + await db.query('DELETE FROM customers WHERE id = $1', [id]); + + res.json({ + success: true, + message: 'Customer deleted successfully' + }); + } catch (error) { + console.error('Error deleting customer:', error); + res.status(500).json({ + success: false, + message: 'Failed to delete customer', + error: error.message + }); + } + } +); + +// 6. GET /api/customers/:id/contacts - 获取客户联系人 +app.get('/api/customers/:id/contacts', + [ + param('id').isInt({ min: 1 }) + ], + validate, + async (req, res) => { + try { + const { id } = req.params; + + // 检查客户是否存在 + const customerResult = await db.query('SELECT * FROM customers WHERE id = $1', [id]); + if (customerResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Customer not found' + }); + } + + const result = await db.query( + 'SELECT * FROM contacts WHERE customer_id = $1 ORDER BY is_primary DESC, created_at DESC', + [id] + ); + + res.json({ + success: true, + data: result.rows + }); + } catch (error) { + console.error('Error fetching customer contacts:', error); + res.status(500).json({ + success: false, + message: 'Failed to fetch customer contacts', + error: error.message + }); + } + } +); + +// 错误处理中间件 +app.use((err, req, res, next) => { + console.error(err.stack); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: process.env.NODE_ENV === 'development' ? err.message : undefined + }); +}); + +// 404处理 +app.use((req, res) => { + res.status(404).json({ + success: false, + message: 'Endpoint not found' + }); +}); + +// 启动服务器 +app.listen(PORT, () => { + console.log(`Customer Management API server running on port ${PORT}`); + console.log('Available endpoints:'); + console.log(' GET /health'); + console.log(' GET /api/customers'); + console.log(' GET /api/customers/:id'); + console.log(' POST /api/customers'); + console.log(' PUT /api/customers/:id'); + console.log(' DELETE /api/customers/:id'); + console.log(' GET /api/customers/:id/contacts'); +}); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/simple-frontend.html b/backup_20260327/company-finance-system/backend/simple-frontend.html new file mode 100644 index 0000000..01acaac --- /dev/null +++ b/backup_20260327/company-finance-system/backend/simple-frontend.html @@ -0,0 +1,224 @@ + + + + + + 公司财务管理系统 - 测试页面 + + + +
+
+

公司财务管理系统

+
生产环境测试页面 - 版本 1.0.0
+
+ +
+
+
检查中...
+
API服务状态
+
+
+
检查中...
+
数据库状态
+
+
+
检查中...
+
前端服务
+
+
+
43.161.248.209
+
服务器IP
+
+
+ + + +
+
+
后端API服务
+
RESTful API接口,提供数据服务
+
检查中...
+ 健康检查 + +
+ +
+
前端Web应用
+
React单页应用,用户界面
+
检查中...
+ 访问前端 + +
+ +
+
数据库服务
+
PostgreSQL数据库,数据存储
+
检查中...
+ +
+
+
+ + + + diff --git a/backup_20260327/company-finance-system/backend/simple-test-server.js b/backup_20260327/company-finance-system/backend/simple-test-server.js new file mode 100644 index 0000000..4b24a7f --- /dev/null +++ b/backup_20260327/company-finance-system/backend/simple-test-server.js @@ -0,0 +1,38 @@ +const express = require('express'); +const app = express(); +const PORT = 80; + +// 简单测试页面 +app.get('/', (req, res) => { + res.send(` + + + 测试 - 端口80 + +

✅ 端口80测试成功!

+

服务器: 43.161.248.209:80

+

时间: ${new Date().toISOString()}

+

🔗 系统访问链接:

+ +

🔧 问题诊断:

+

如果端口5000无法访问,可能是安全组阻止。请检查腾讯云安全组规则,确保端口5000已开放。

+ + + `); +}); + +// 启动服务器(需要root权限) +if (PORT === 80) { + console.log('⚠️ 端口80需要root权限,使用sudo运行'); + app.listen(PORT, '0.0.0.0', () => { + console.log(`测试服务器运行在: http://0.0.0.0:${PORT}`); + }); +} else { + app.listen(PORT, '0.0.0.0', () => { + console.log(`测试服务器运行在: http://0.0.0.0:${PORT}`); + }); +} diff --git a/backup_20260327/company-finance-system/backend/start-server.sh b/backup_20260327/company-finance-system/backend/start-server.sh new file mode 100644 index 0000000..79a2c0f --- /dev/null +++ b/backup_20260327/company-finance-system/backend/start-server.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +echo "=== 启动客户管理API服务器 ===" +echo + +# 检查Node.js是否安装 +if ! command -v node &> /dev/null; then + echo "错误: Node.js未安装" + exit 1 +fi + +# 检查npm是否安装 +if ! command -v npm &> /dev/null; then + echo "错误: npm未安装" + exit 1 +fi + +# 检查依赖是否安装 +if [ ! -d "node_modules" ]; then + echo "依赖未安装,正在安装..." + npm install +fi + +# 检查PostgreSQL服务 +echo "检查PostgreSQL服务..." +if ! systemctl is-active --quiet postgresql 2>/dev/null; then + echo "警告: PostgreSQL服务未运行" + echo "请手动启动: sudo systemctl start postgresql" + echo "或使用默认配置继续(如果数据库在其他地方运行)" + read -p "是否继续?(y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +# 检查数据库 +echo "检查数据库..." +if ! sudo -u postgres psql -lqt | cut -d \| -f 1 | grep -qw company_finance_db; then + echo "数据库不存在,正在初始化..." + sudo -u postgres psql -f init-db.sql +fi + +# 启动服务器 +echo "启动服务器..." +echo "服务器将在 http://localhost:3000 运行" +echo "按 Ctrl+C 停止服务器" +echo + +npm start \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/test-api.sh b/backup_20260327/company-finance-system/backend/test-api.sh new file mode 100644 index 0000000..9debc59 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-api.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +# API测试脚本 - 客户管理 +BASE_URL="http://localhost:3000" + +echo "=== 测试客户管理API ===" +echo + +# 检查jq是否安装 +if ! command -v jq &> /dev/null; then + echo "错误: jq未安装。安装命令: dnf install -y jq" + echo "将使用curl原始输出..." + USE_JQ=false +else + USE_JQ=true +fi + +# 格式化输出函数 +format_output() { + if [ "$USE_JQ" = true ]; then + jq . + else + cat + fi +} + +# 1. 测试健康检查 +echo "1. 测试健康检查:" +curl -s "$BASE_URL/health" | format_output +echo + +# 2. 测试获取客户列表 +echo "2. 测试获取客户列表 (分页):" +curl -s "$BASE_URL/api/customers?page=1&limit=3" | format_output +echo + +# 3. 测试搜索客户 +echo "3. 测试搜索客户 (搜索'张'):" +curl -s "$BASE_URL/api/customers?search=张" | format_output +echo + +# 4. 测试按状态过滤 +echo "4. 测试按状态过滤 (active):" +curl -s "$BASE_URL/api/customers?status=active&limit=5" | format_output +echo + +# 5. 测试创建新客户 +echo "5. 测试创建新客户:" +curl -s -X POST "$BASE_URL/api/customers" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "测试客户", + "email": "test@example.com", + "phone": "12345678901", + "address": "测试地址", + "company": "测试公司", + "tax_id": "TEST123456", + "status": "active" + }' | format_output +echo + +# 6. 测试获取单个客户 +echo "6. 测试获取单个客户 (ID=1):" +curl -s "$BASE_URL/api/customers/1" | format_output +echo + +# 7. 测试更新客户 +echo "7. 测试更新客户 (ID=1):" +curl -s -X PUT "$BASE_URL/api/customers/1" \ + -H "Content-Type: application/json" \ + -d '{ + "phone": "13888888888", + "company": "更新后的ABC科技" + }' | format_output +echo + +# 8. 测试获取客户联系人 +echo "8. 测试获取客户联系人 (ID=1):" +curl -s "$BASE_URL/api/customers/1/contacts" | format_output +echo + +# 9. 测试验证错误 +echo "9. 测试验证错误 (无效邮箱):" +curl -s -X POST "$BASE_URL/api/customers" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "无效客户", + "email": "invalid-email", + "phone": "12345678901" + }' | format_output +echo + +# 10. 测试唯一约束错误 +echo "10. 测试唯一约束错误 (重复邮箱):" +curl -s -X POST "$BASE_URL/api/customers" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "重复客户", + "email": "zhangsan@example.com", + "phone": "12345678901" + }' | format_output +echo + +# 11. 测试删除客户 +echo "11. 测试删除客户 (将创建测试客户然后删除):" +# 先创建测试客户 +CREATE_RESPONSE=$(curl -s -X POST "$BASE_URL/api/customers" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "待删除客户", + "email": "delete-me@example.com", + "phone": "11111111111" + }') +echo "创建响应:" +echo "$CREATE_RESPONSE" | format_output + +# 提取客户ID +if [ "$USE_JQ" = true ]; then + CUSTOMER_ID=$(echo "$CREATE_RESPONSE" | jq -r '.data.id') +else + # 简单提取ID + CUSTOMER_ID=$(echo "$CREATE_RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) +fi + +if [ -n "$CUSTOMER_ID" ] && [ "$CUSTOMER_ID" != "null" ] && [ "$CUSTOMER_ID" != "" ]; then + echo "删除客户 ID=$CUSTOMER_ID:" + curl -s -X DELETE "$BASE_URL/api/customers/$CUSTOMER_ID" | format_output +else + echo "无法获取客户ID,跳过删除测试" +fi +echo + +# 12. 测试不存在的客户 +echo "12. 测试不存在的客户 (ID=999):" +curl -s "$BASE_URL/api/customers/999" | format_output +echo + +echo "=== API测试完成 ===" +echo "所有端点测试完成。查看上面的响应以验证API功能。" \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/test-complete-flow.js b/backup_20260327/company-finance-system/backend/test-complete-flow.js new file mode 100644 index 0000000..21918f6 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-complete-flow.js @@ -0,0 +1,503 @@ +const request = require('supertest'); +const fs = require('fs'); +const path = require('path'); + +// 测试基础URL +const BASE_URL = 'http://localhost:3005'; + +// 测试文件路径 +const TEST_IMAGE_PATH = path.join(__dirname, 'uploads', 'test-image.png'); + +// 确保测试文件存在 +if (!fs.existsSync(TEST_IMAGE_PATH)) { + console.log('测试文件不存在,创建模拟测试文件...'); + if (!fs.existsSync(path.join(__dirname, 'uploads'))) { + fs.mkdirSync(path.join(__dirname, 'uploads'), { recursive: true }); + } + // 创建一个简单的文本文件作为测试附件 + fs.writeFileSync(TEST_IMAGE_PATH, 'Test image content'); + console.log('测试文件创建成功'); +} + +// 测试数据 +const testUser = { + username: 'admin', + password: 'X123c321@' +}; + +let authToken = ''; + +// 测试结果 +const testResults = []; + +// 登录函数 +async function login() { + console.log('\n🔐 登录测试...'); + const response = await request(BASE_URL) + .post('/api/auth/login') + .send(testUser); + + if (response.status === 200 && response.body.success) { + authToken = response.body.token; + testResults.push({ test: '登录', status: '✅ 成功' }); + console.log('✅ 登录成功,获取到token'); + } else { + testResults.push({ test: '登录', status: '❌ 失败', message: response.body.message || '登录失败' }); + console.log('❌ 登录失败:', response.body.message); + } +} + +// 测试预支申请流程 +async function testAdvanceFlow() { + console.log('\n💸 测试预支申请流程...'); + let advanceId = ''; + + // 1. 创建预支申请 + console.log('1. 创建预支申请...'); + const createResponse = await request(BASE_URL) + .post('/api/advances') + .set('Authorization', `Bearer ${authToken}`) + .field('advance_date', '2026-03-24') + .field('amount', 5000) + .field('currency', 'CNY') + .field('reason', '项目差旅预支') + .field('expense_type', 'project') + .field('project_id', '1') + .field('applicant', '测试用户') + .attach('attachments', TEST_IMAGE_PATH); + + if (createResponse.status === 200 && createResponse.body.success) { + advanceId = createResponse.body.data.id; + testResults.push({ test: '创建预支申请', status: '✅ 成功' }); + console.log('✅ 预支申请创建成功,ID:', advanceId); + } else { + testResults.push({ test: '创建预支申请', status: '❌ 失败', message: createResponse.body.message || '创建失败' }); + console.log('❌ 预支申请创建失败:', createResponse.body.message); + return; + } + + // 2. 提交预支申请 + console.log('2. 提交预支申请...'); + const submitResponse = await request(BASE_URL) + .put(`/api/advances/${advanceId}/submit`) + .set('Authorization', `Bearer ${authToken}`); + + if (submitResponse.status === 200 && submitResponse.body.success) { + testResults.push({ test: '提交预支申请', status: '✅ 成功' }); + console.log('✅ 预支申请提交成功'); + } else { + testResults.push({ test: '提交预支申请', status: '❌ 失败', message: submitResponse.body.message || '提交失败' }); + console.log('❌ 预支申请提交失败:', submitResponse.body.message); + return; + } + + // 3. 审批预支申请 + console.log('3. 审批预支申请...'); + const approveResponse = await request(BASE_URL) + .put(`/api/advances/${advanceId}/approve`) + .set('Authorization', `Bearer ${authToken}`) + .send({ approver: '系统管理员' }); + + if (approveResponse.status === 200 && approveResponse.body.success) { + testResults.push({ test: '审批预支申请', status: '✅ 成功' }); + console.log('✅ 预支申请审批成功'); + } else { + testResults.push({ test: '审批预支申请', status: '❌ 失败', message: approveResponse.body.message || '审批失败' }); + console.log('❌ 预支申请审批失败:', approveResponse.body.message); + return; + } + + // 4. 执行预支付款 + console.log('4. 执行预支付款...'); + const executeResponse = await request(BASE_URL) + .post('/api/executions') + .set('Authorization', `Bearer ${authToken}`) + .send({ + apply_id: advanceId, + apply_type: 'advance', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + if (executeResponse.status === 200 && executeResponse.body.success) { + testResults.push({ test: '执行预支付款', status: '✅ 成功' }); + console.log('✅ 预支付款执行成功'); + } else { + testResults.push({ test: '执行预支付款', status: '❌ 失败', message: executeResponse.body.message || '执行失败' }); + console.log('❌ 预支付款执行失败:', executeResponse.body.message); + } +} + +// 测试报销申请流程 +async function testReimbursementFlow() { + console.log('\n🧾 测试报销申请流程...'); + let reimbursementId = ''; + + // 1. 创建报销申请 + console.log('1. 创建报销申请...'); + const createResponse = await request(BASE_URL) + .post('/api/reimbursements') + .set('Authorization', `Bearer ${authToken}`) + .field('reimbursement_date', '2026-03-24') + .field('amount', 3500) + .field('currency', 'CNY') + .field('reason', '项目差旅报销') + .field('expense_type', 'project') + .field('project_id', '1') + .field('applicant', '测试用户') + .field('detail_items', JSON.stringify([ + { + description: '住宿费', + amount: 2000, + category: 'accommodation', + attachments: [] + }, + { + description: '餐饮费', + amount: 1500, + category: 'food', + attachments: [] + } + ])) + .attach('attachments', TEST_IMAGE_PATH); + + if (createResponse.status === 200 && createResponse.body.success) { + reimbursementId = createResponse.body.data.id; + testResults.push({ test: '创建报销申请', status: '✅ 成功' }); + console.log('✅ 报销申请创建成功,ID:', reimbursementId); + } else { + testResults.push({ test: '创建报销申请', status: '❌ 失败', message: createResponse.body.message || '创建失败' }); + console.log('❌ 报销申请创建失败:', createResponse.body.message); + return; + } + + // 2. 提交报销申请 + console.log('2. 提交报销申请...'); + const submitResponse = await request(BASE_URL) + .put(`/api/reimbursements/${reimbursementId}/submit`) + .set('Authorization', `Bearer ${authToken}`); + + if (submitResponse.status === 200 && submitResponse.body.success) { + testResults.push({ test: '提交报销申请', status: '✅ 成功' }); + console.log('✅ 报销申请提交成功'); + } else { + testResults.push({ test: '提交报销申请', status: '❌ 失败', message: submitResponse.body.message || '提交失败' }); + console.log('❌ 报销申请提交失败:', submitResponse.body.message); + return; + } + + // 3. 审批报销申请 + console.log('3. 审批报销申请...'); + const approveResponse = await request(BASE_URL) + .put(`/api/reimbursements/${reimbursementId}/approve`) + .set('Authorization', `Bearer ${authToken}`) + .send({ approver: '系统管理员' }); + + if (approveResponse.status === 200 && approveResponse.body.success) { + testResults.push({ test: '审批报销申请', status: '✅ 成功' }); + console.log('✅ 报销申请审批成功'); + } else { + testResults.push({ test: '审批报销申请', status: '❌ 失败', message: approveResponse.body.message || '审批失败' }); + console.log('❌ 报销申请审批失败:', approveResponse.body.message); + return; + } + + // 4. 执行报销付款 + console.log('4. 执行报销付款...'); + const executeResponse = await request(BASE_URL) + .post('/api/executions') + .set('Authorization', `Bearer ${authToken}`) + .send({ + apply_id: reimbursementId, + apply_type: 'reimbursement', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + if (executeResponse.status === 200 && executeResponse.body.success) { + testResults.push({ test: '执行报销付款', status: '✅ 成功' }); + console.log('✅ 报销付款执行成功'); + } else { + testResults.push({ test: '执行报销付款', status: '❌ 失败', message: executeResponse.body.message || '执行失败' }); + console.log('❌ 报销付款执行失败:', executeResponse.body.message); + } +} + +// 测试付款申请流程 +async function testPaymentFlow() { + console.log('\n💰 测试付款申请流程...'); + let paymentId = ''; + + // 1. 创建付款申请 + console.log('1. 创建付款申请...'); + const createResponse = await request(BASE_URL) + .post('/api/payments') + .set('Authorization', `Bearer ${authToken}`) + .field('payment_date', '2026-03-24') + .field('amount', 50000) + .field('currency', 'CNY') + .field('reason', '设备采购款') + .field('expense_type', 'company') + .field('payee', '测试供应商') + .field('applicant', '测试用户') + .attach('attachments', TEST_IMAGE_PATH); + + if (createResponse.status === 200 && createResponse.body.success) { + paymentId = createResponse.body.data.id; + testResults.push({ test: '创建付款申请', status: '✅ 成功' }); + console.log('✅ 付款申请创建成功,ID:', paymentId); + } else { + testResults.push({ test: '创建付款申请', status: '❌ 失败', message: createResponse.body.message || '创建失败' }); + console.log('❌ 付款申请创建失败:', createResponse.body.message); + return; + } + + // 2. 提交付款申请 + console.log('2. 提交付款申请...'); + const submitResponse = await request(BASE_URL) + .put(`/api/payments/${paymentId}/submit`) + .set('Authorization', `Bearer ${authToken}`); + + if (submitResponse.status === 200 && submitResponse.body.success) { + testResults.push({ test: '提交付款申请', status: '✅ 成功' }); + console.log('✅ 付款申请提交成功'); + } else { + testResults.push({ test: '提交付款申请', status: '❌ 失败', message: submitResponse.body.message || '提交失败' }); + console.log('❌ 付款申请提交失败:', submitResponse.body.message); + return; + } + + // 3. 审批付款申请 + console.log('3. 审批付款申请...'); + const approveResponse = await request(BASE_URL) + .put(`/api/payments/${paymentId}/approve`) + .set('Authorization', `Bearer ${authToken}`) + .send({ approver: '系统管理员' }); + + if (approveResponse.status === 200 && approveResponse.body.success) { + testResults.push({ test: '审批付款申请', status: '✅ 成功' }); + console.log('✅ 付款申请审批成功'); + } else { + testResults.push({ test: '审批付款申请', status: '❌ 失败', message: approveResponse.body.message || '审批失败' }); + console.log('❌ 付款申请审批失败:', approveResponse.body.message); + return; + } + + // 4. 执行付款 + console.log('4. 执行付款...'); + const executeResponse = await request(BASE_URL) + .post('/api/executions') + .set('Authorization', `Bearer ${authToken}`) + .send({ + apply_id: paymentId, + apply_type: 'payment', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + if (executeResponse.status === 200 && executeResponse.body.success) { + testResults.push({ test: '执行付款', status: '✅ 成功' }); + console.log('✅ 付款执行成功'); + } else { + testResults.push({ test: '执行付款', status: '❌ 失败', message: executeResponse.body.message || '执行失败' }); + console.log('❌ 付款执行失败:', executeResponse.body.message); + } +} + +// 测试核销申请流程 +async function testVerificationFlow() { + console.log('\n📋 测试核销申请流程...'); + let verificationId = ''; + + // 1. 创建核销申请 + console.log('1. 创建核销申请...'); + const createResponse = await request(BASE_URL) + .post('/api/verifications') + .set('Authorization', `Bearer ${authToken}`) + .field('verification_date', '2026-03-24') + .field('advance_id', '1') + .field('actual_amount', 4500) + .field('balance', 500) + .field('reason', '项目差旅核销') + .field('applicant', '测试用户') + .field('detail_items', JSON.stringify([ + { + description: '住宿费', + amount: 2500, + category: 'accommodation', + attachments: [] + }, + { + description: '餐饮费', + amount: 2000, + category: 'food', + attachments: [] + } + ])) + .attach('attachments', TEST_IMAGE_PATH); + + if (createResponse.status === 200 && createResponse.body.success) { + verificationId = createResponse.body.data.id; + testResults.push({ test: '创建核销申请', status: '✅ 成功' }); + console.log('✅ 核销申请创建成功,ID:', verificationId); + } else { + testResults.push({ test: '创建核销申请', status: '❌ 失败', message: createResponse.body.message || '创建失败' }); + console.log('❌ 核销申请创建失败:', createResponse.body.message); + return; + } + + // 2. 提交核销申请 + console.log('2. 提交核销申请...'); + const submitResponse = await request(BASE_URL) + .put(`/api/verifications/${verificationId}/submit`) + .set('Authorization', `Bearer ${authToken}`); + + if (submitResponse.status === 200 && submitResponse.body.success) { + testResults.push({ test: '提交核销申请', status: '✅ 成功' }); + console.log('✅ 核销申请提交成功'); + } else { + testResults.push({ test: '提交核销申请', status: '❌ 失败', message: submitResponse.body.message || '提交失败' }); + console.log('❌ 核销申请提交失败:', submitResponse.body.message); + return; + } + + // 3. 审批核销申请 + console.log('3. 审批核销申请...'); + const approveResponse = await request(BASE_URL) + .put(`/api/verifications/${verificationId}/approve`) + .set('Authorization', `Bearer ${authToken}`) + .send({ approver: '系统管理员' }); + + if (approveResponse.status === 200 && approveResponse.body.success) { + testResults.push({ test: '审批核销申请', status: '✅ 成功' }); + console.log('✅ 核销申请审批成功'); + } else { + testResults.push({ test: '审批核销申请', status: '❌ 失败', message: approveResponse.body.message || '审批失败' }); + console.log('❌ 核销申请审批失败:', approveResponse.body.message); + return; + } + + // 4. 执行核销 + console.log('4. 执行核销...'); + const executeResponse = await request(BASE_URL) + .post('/api/executions') + .set('Authorization', `Bearer ${authToken}`) + .send({ + apply_id: verificationId, + apply_type: 'verification', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + if (executeResponse.status === 200 && executeResponse.body.success) { + testResults.push({ test: '执行核销', status: '✅ 成功' }); + console.log('✅ 核销执行成功'); + } else { + testResults.push({ test: '执行核销', status: '❌ 失败', message: executeResponse.body.message || '执行失败' }); + console.log('❌ 核销执行失败:', executeResponse.body.message); + } +} + +// 测试执行记录查询 +async function testExecutionRecords() { + console.log('\n📊 测试执行记录查询...'); + + // 1. 查询执行记录列表 + console.log('1. 查询执行记录列表...'); + const recordsResponse = await request(BASE_URL) + .get('/api/executions') + .set('Authorization', `Bearer ${authToken}`); + + if (recordsResponse.status === 200 && recordsResponse.body.success) { + testResults.push({ test: '查询执行记录列表', status: '✅ 成功', message: `找到 ${recordsResponse.body.data.length} 条记录` }); + console.log('✅ 执行记录查询成功,找到', recordsResponse.body.data.length, '条记录'); + } else { + testResults.push({ test: '查询执行记录列表', status: '❌ 失败', message: recordsResponse.body.message || '查询失败' }); + console.log('❌ 执行记录查询失败:', recordsResponse.body.message); + } + + // 2. 查询待执行列表 + console.log('2. 查询待执行列表...'); + const pendingResponse = await request(BASE_URL) + .get('/api/executions/pending') + .set('Authorization', `Bearer ${authToken}`); + + if (pendingResponse.status === 200 && pendingResponse.body.success) { + testResults.push({ test: '查询待执行列表', status: '✅ 成功', message: `找到 ${pendingResponse.body.data.length} 条记录` }); + console.log('✅ 待执行列表查询成功,找到', pendingResponse.body.data.length, '条记录'); + } else { + testResults.push({ test: '查询待执行列表', status: '❌ 失败', message: pendingResponse.body.message || '查询失败' }); + console.log('❌ 待执行列表查询失败:', pendingResponse.body.message); + } + + // 3. 查询已执行列表 + console.log('3. 查询已执行列表...'); + const executedResponse = await request(BASE_URL) + .get('/api/executions/executed') + .set('Authorization', `Bearer ${authToken}`); + + if (executedResponse.status === 200 && executedResponse.body.success) { + testResults.push({ test: '查询已执行列表', status: '✅ 成功', message: `找到 ${executedResponse.body.data.length} 条记录` }); + console.log('✅ 已执行列表查询成功,找到', executedResponse.body.data.length, '条记录'); + } else { + testResults.push({ test: '查询已执行列表', status: '❌ 失败', message: executedResponse.body.message || '查询失败' }); + console.log('❌ 已执行列表查询失败:', executedResponse.body.message); + } +} + +// 运行所有测试 +async function runAllTests() { + console.log('🚀 开始执行完整财务流程测试\n'); + + await login(); + + if (authToken) { + await testAdvanceFlow(); + await testReimbursementFlow(); + await testPaymentFlow(); + await testVerificationFlow(); + await testExecutionRecords(); + } + + // 输出测试结果 + console.log('\n📋 测试结果汇总:'); + console.log('============================================='); + + let successCount = 0; + let failureCount = 0; + + testResults.forEach(result => { + console.log(`${result.test}: ${result.status}`); + if (result.status.includes('✅')) { + successCount++; + } else { + failureCount++; + } + }); + + console.log('============================================='); + console.log(`总测试数: ${testResults.length}`); + console.log(`成功: ${successCount}`); + console.log(`失败: ${failureCount}`); + + if (failureCount === 0) { + console.log('\n🎉 所有测试通过!完整财务流程运行正常。'); + } else { + console.log('\n⚠️ 部分测试失败,需要检查问题。'); + } + + console.log('\n测试完成。'); +} + +// 启动测试 +runAllTests().catch(error => { + console.error('测试过程中出现错误:', error); +}); diff --git a/backup_20260327/company-finance-system/backend/test-execution-flow.js b/backup_20260327/company-finance-system/backend/test-execution-flow.js new file mode 100644 index 0000000..6654915 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-execution-flow.js @@ -0,0 +1,139 @@ +const axios = require('axios'); + +// 测试配置 +const API_BASE = 'http://localhost:3005/api'; + +// 测试用户信息 +const user = { + username: 'admin', + password: 'X123c321@' +}; + +let token = ''; + +// 测试步骤 +async function runTest() { + console.log('=== 开始测试申请-审批-执行流程 ===\n'); + + try { + // 1. 登录获取token + console.log('1. 登录系统...'); + const loginResponse = await axios.post(`${API_BASE}/auth/login`, user); + if (loginResponse.data.success) { + console.log('✓ 登录成功'); + token = loginResponse.data.data; + } else { + console.log('✗ 登录失败'); + return; + } + + // 2. 创建一个测试预支申请 + console.log('\n2. 创建测试预支申请...'); + const advanceResponse = await axios.post(`${API_BASE}/advances`, { + amount: 1000, + reason: '测试预支申请', + currency: 'CNY', + advance_date: new Date().toISOString(), + applicant: '测试用户', + attachments: [] + }); + + if (advanceResponse.data.success) { + console.log('✓ 预支申请创建成功'); + const advanceId = advanceResponse.data.data.id; + console.log(` 预支申请ID: ${advanceId}`); + + // 3. 审批通过预支申请 + console.log('\n3. 审批通过预支申请...'); + const approveResponse = await axios.post(`${API_BASE}/advances/${advanceId}/approve`); + if (approveResponse.data.success) { + console.log('✓ 预支申请审批通过'); + } else { + console.log('✗ 预支申请审批失败'); + return; + } + + // 4. 检查执行管理中的待执行列表 + console.log('\n4. 检查执行管理待执行列表...'); + const pendingResponse = await axios.get(`${API_BASE}/executions/pending`); + if (pendingResponse.data.success) { + console.log('✓ 获取待执行列表成功'); + console.log(` 待执行数量: ${pendingResponse.data.data.length}`); + const pendingAdvance = pendingResponse.data.data.find(item => item.id === advanceId); + if (pendingAdvance) { + console.log('✓ 测试预支申请在待执行列表中'); + } else { + console.log('✗ 测试预支申请不在待执行列表中'); + } + } else { + console.log('✗ 获取待执行列表失败'); + return; + } + + // 5. 执行预支申请 + console.log('\n5. 执行预支申请...'); + const executeResponse = await axios.post(`${API_BASE}/executions`, { + apply_id: advanceId, + apply_type: 'advance', + action: 'execute', + execute_method: 'bank', + voucher_no: 'TEST-001', + remark: '测试执行' + }); + + if (executeResponse.data.success) { + console.log('✓ 预支申请执行成功'); + } else { + console.log('✗ 预支申请执行失败'); + return; + } + + // 6. 检查执行管理中的已执行列表 + console.log('\n6. 检查执行管理已执行列表...'); + const executedResponse = await axios.get(`${API_BASE}/executions/executed`); + if (executedResponse.data.success) { + console.log('✓ 获取已执行列表成功'); + console.log(` 已执行数量: ${executedResponse.data.data.length}`); + const executedAdvance = executedResponse.data.data.find(item => item.id === advanceId); + if (executedAdvance) { + console.log('✓ 测试预支申请在已执行列表中'); + } else { + console.log('✗ 测试预支申请不在已执行列表中'); + } + } else { + console.log('✗ 获取已执行列表失败'); + return; + } + + // 7. 检查执行记录 + console.log('\n7. 检查执行记录...'); + const executionsResponse = await axios.get(`${API_BASE}/executions`); + if (executionsResponse.data.success) { + console.log('✓ 获取执行记录成功'); + console.log(` 执行记录数量: ${executionsResponse.data.data.length}`); + const executionRecord = executionsResponse.data.data.find(item => item.apply_id === advanceId.toString()); + if (executionRecord) { + console.log('✓ 测试预支申请有执行记录'); + } else { + console.log('✗ 测试预支申请没有执行记录'); + } + } else { + console.log('✗ 获取执行记录失败'); + return; + } + + console.log('\n=== 测试完成 ==='); + console.log('✓ 申请-审批-执行流程测试成功'); + + } else { + console.log('✗ 预支申请创建失败'); + } + + } catch (error) { + console.error('测试过程中发生错误:', error.message); + console.error('错误详情:', error.response ? error.response.data : error); + } +} + +// 运行测试 +runTest(); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/test-finance-complete.spec.js b/backup_20260327/company-finance-system/backend/test-finance-complete.spec.js new file mode 100644 index 0000000..aaa658e --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-finance-complete.spec.js @@ -0,0 +1,338 @@ +const request = require('supertest'); +const fs = require('fs'); +const path = require('path'); + +// 测试基础URL +const BASE_URL = 'http://localhost:3005'; + +// 测试文件路径 +const TEST_IMAGE_PATH = path.join(__dirname, 'uploads', 'test-image.png'); + +// 测试数据 +const testUser = { + username: 'admin', + password: 'admin123' +}; + +let authToken = ''; + +// 测试套件 +describe('财务流程完整测试', () => { + // 登录获取token + beforeAll(async () => { + const response = await request(BASE_URL) + .post('/api/auth/login') + .send(testUser); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + authToken = response.body.token; + }); + + // 测试预支申请流程 + describe('预支申请流程', () => { + let advanceId = ''; + let advanceCode = ''; + + test('1. 创建预支申请', async () => { + const response = await request(BASE_URL) + .post('/api/advances') + .set('Authorization', `Bearer ${authToken}`) + .field('advance_date', '2026-03-24') + .field('amount', 5000) + .field('currency', 'CNY') + .field('reason', '项目差旅预支') + .field('expense_type', 'project') + .field('project_id', '1') + .field('applicant', '测试用户') + .attach('attachments', TEST_IMAGE_PATH); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + advanceId = response.body.data.id; + advanceCode = response.body.data.advance_code; + }); + + test('2. 提交预支申请到审批', async () => { + const response = await request(BASE_URL) + .put(`/api/advances/${advanceId}/submit`) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('3. 审批预支申请', async () => { + const response = await request(BASE_URL) + .put(`/api/advances/${advanceId}/approve`) + .set('Authorization', `Bearer ${authToken}`) + .send({ approver: '系统管理员' }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('4. 执行预支付款', async () => { + const response = await request(BASE_URL) + .post('/api/executions') + .set('Authorization', `Bearer ${authToken}`) + .send({ + apply_id: advanceId, + apply_type: 'advance', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('5. 验证预支执行状态', async () => { + const response = await request(BASE_URL) + .get(`/api/advances/${advanceId}`) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(response.body.data.status).toBe('executed'); + }); + }); + + // 测试报销申请流程 + describe('报销申请流程', () => { + let reimbursementId = ''; + + test('1. 创建报销申请', async () => { + const response = await request(BASE_URL) + .post('/api/reimbursements') + .set('Authorization', `Bearer ${authToken}`) + .field('reimbursement_date', '2026-03-24') + .field('amount', 3500) + .field('currency', 'CNY') + .field('reason', '项目差旅报销') + .field('expense_type', 'project') + .field('project_id', '1') + .field('applicant', '测试用户') + .field('detail_items', JSON.stringify([ + { + description: '住宿费', + amount: 2000, + category: 'accommodation', + attachments: [] + }, + { + description: '餐饮费', + amount: 1500, + category: 'food', + attachments: [] + } + ])) + .attach('attachments', TEST_IMAGE_PATH); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + reimbursementId = response.body.data.id; + }); + + test('2. 提交报销申请到审批', async () => { + const response = await request(BASE_URL) + .put(`/api/reimbursements/${reimbursementId}/submit`) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('3. 审批报销申请', async () => { + const response = await request(BASE_URL) + .put(`/api/reimbursements/${reimbursementId}/approve`) + .set('Authorization', `Bearer ${authToken}`) + .send({ approver: '系统管理员' }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('4. 执行报销付款', async () => { + const response = await request(BASE_URL) + .post('/api/executions') + .set('Authorization', `Bearer ${authToken}`) + .send({ + apply_id: reimbursementId, + apply_type: 'reimbursement', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + }); + + // 测试付款申请流程 + describe('付款申请流程', () => { + let paymentId = ''; + + test('1. 创建付款申请', async () => { + const response = await request(BASE_URL) + .post('/api/payments') + .set('Authorization', `Bearer ${authToken}`) + .field('payment_date', '2026-03-24') + .field('amount', 50000) + .field('currency', 'CNY') + .field('reason', '设备采购款') + .field('expense_type', 'company') + .field('payee', '测试供应商') + .field('applicant', '测试用户') + .attach('attachments', TEST_IMAGE_PATH); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + paymentId = response.body.data.id; + }); + + test('2. 提交付款申请到审批', async () => { + const response = await request(BASE_URL) + .put(`/api/payments/${paymentId}/submit`) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('3. 审批付款申请', async () => { + const response = await request(BASE_URL) + .put(`/api/payments/${paymentId}/approve`) + .set('Authorization', `Bearer ${authToken}`) + .send({ approver: '系统管理员' }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('4. 执行付款', async () => { + const response = await request(BASE_URL) + .post('/api/executions') + .set('Authorization', `Bearer ${authToken}`) + .send({ + apply_id: paymentId, + apply_type: 'payment', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + }); + + // 测试核销申请流程 + describe('核销申请流程', () => { + let verificationId = ''; + + test('1. 创建核销申请', async () => { + const response = await request(BASE_URL) + .post('/api/verifications') + .set('Authorization', `Bearer ${authToken}`) + .field('verification_date', '2026-03-24') + .field('advance_id', '1') + .field('actual_amount', 4500) + .field('balance', 500) + .field('reason', '项目差旅核销') + .field('applicant', '测试用户') + .field('detail_items', JSON.stringify([ + { + description: '住宿费', + amount: 2500, + category: 'accommodation', + attachments: [] + }, + { + description: '餐饮费', + amount: 2000, + category: 'food', + attachments: [] + } + ])) + .attach('attachments', TEST_IMAGE_PATH); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + verificationId = response.body.data.id; + }); + + test('2. 提交核销申请到审批', async () => { + const response = await request(BASE_URL) + .put(`/api/verifications/${verificationId}/submit`) + .set('Authorization', `Bearer ${authToken}`); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('3. 审批核销申请', async () => { + const response = await request(BASE_URL) + .put(`/api/verifications/${verificationId}/approve`) + .set('Authorization', `Bearer ${authToken}`) + .send({ approver: '系统管理员' }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('4. 执行核销', async () => { + const response = await request(BASE_URL) + .post('/api/executions') + .set('Authorization', `Bearer ${authToken}`) + .send({ + apply_id: verificationId, + apply_type: 'verification', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + }); + + // 测试执行记录查询 + describe('执行记录查询', () => { + test('查询执行记录列表', async () => { + const response = await request(BASE_URL) + .get('/api/executions') + .set('Authorization', `Bearer ${authToken}`); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.data)).toBe(true); + }); + + test('查询待执行列表', async () => { + const response = await request(BASE_URL) + .get('/api/executions/pending') + .set('Authorization', `Bearer ${authToken}`); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.data)).toBe(true); + }); + + test('查询已执行列表', async () => { + const response = await request(BASE_URL) + .get('/api/executions/executed') + .set('Authorization', `Bearer ${authToken}`); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.data)).toBe(true); + }); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/test-finance-flow.js b/backup_20260327/company-finance-system/backend/test-finance-flow.js new file mode 100644 index 0000000..04d6a5d --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-finance-flow.js @@ -0,0 +1,401 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +// 测试基础URL +const BASE_URL = 'http://localhost:3005'; + +// 测试文件路径 +const TEST_IMAGE_PATH = path.join(__dirname, 'uploads', 'test-image.png'); + +// 测试数据 +const testUser = { + username: 'admin', + password: 'X123c321@' +}; + +let authToken = ''; + +// HTTP请求函数 +function request(method, url, data = null, headers = {}, file = null) { + return new Promise((resolve, reject) => { + const options = { + method, + headers: { + 'Content-Type': 'application/json', + ...headers + } + }; + + if (file) { + // 处理文件上传 + const boundary = '--------------------------' + Date.now().toString(16); + options.headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary; + } + + const req = http.request(url, options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + try { + resolve(JSON.parse(data)); + } catch (e) { + resolve({ success: false, error: 'Invalid JSON response' }); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + if (data) { + req.write(JSON.stringify(data)); + } + + req.end(); + }); +} + +// 测试套件 +async function runTests() { + console.log('=== 开始财务流程测试 ===\n'); + + try { + // 1. 登录系统... + console.log('1. 登录系统...'); + const loginResponse = await request('POST', `${BASE_URL}/api/auth/login`, testUser); + if (loginResponse.success) { + console.log('✓ 登录成功'); + } else { + console.log('✗ 登录失败:', loginResponse.error); + return; + } + + // 2. 测试预支申请流程 + console.log('\n2. 测试预支申请流程...'); + await testAdvanceFlow(); + + // 3. 测试报销申请流程 + console.log('\n3. 测试报销申请流程...'); + await testReimbursementFlow(); + + // 4. 测试付款申请流程 + console.log('\n4. 测试付款申请流程...'); + await testPaymentFlow(); + + // 5. 测试核销申请流程 + console.log('\n5. 测试核销申请流程...'); + await testVerificationFlow(); + + // 6. 测试执行记录查询 + console.log('\n6. 测试执行记录查询...'); + await testExecutionQueries(); + + console.log('\n=== 测试完成 ==='); + + } catch (error) { + console.error('测试过程中发生错误:', error); + } +} + +// 测试预支申请流程 +async function testAdvanceFlow() { + console.log(' - 创建预支申请...'); + const advanceResponse = await request('POST', `${BASE_URL}/api/advances`, { + advance_date: '2026-03-24', + amount: 5000, + currency: 'CNY', + reason: '项目差旅预支', + expense_type: 'project', + project_id: '1', + applicant: '测试用户', + attachments: [] + }); + + if (advanceResponse.success) { + const advanceId = advanceResponse.data.id; + console.log(' ✓ 预支申请创建成功'); + + console.log(' - 提交预支申请到审批...'); + const submitResponse = await request('PUT', `${BASE_URL}/api/advances/${advanceId}/submit`, {}); + + if (submitResponse.success) { + console.log(' ✓ 预支申请提交成功'); + + console.log(' - 审批预支申请...'); + const approveResponse = await request('PUT', `${BASE_URL}/api/advances/${advanceId}/approve`, { + approver: '系统管理员' + }); + + if (approveResponse.success) { + console.log(' ✓ 预支申请审批成功'); + + console.log(' - 执行预支付款...'); + const executeResponse = await request('POST', `${BASE_URL}/api/executions`, { + apply_id: advanceId, + apply_type: 'advance', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + if (executeResponse.success) { + console.log(' ✓ 预支付款执行成功'); + } else { + console.log(' ✗ 预支付款执行失败:', executeResponse.error); + } + } else { + console.log(' ✗ 预支申请审批失败:', approveResponse.error); + } + } else { + console.log(' ✗ 预支申请提交失败:', submitResponse.error); + } + } else { + console.log(' ✗ 预支申请创建失败:', advanceResponse.error); + } +} + +// 测试报销申请流程 +async function testReimbursementFlow() { + console.log(' - 创建报销申请...'); + const reimbursementResponse = await request('POST', `${BASE_URL}/api/reimbursements`, { + reimbursement_date: '2026-03-24', + amount: 3500, + currency: 'CNY', + reason: '项目差旅报销', + expense_type: 'project', + project_id: '1', + applicant: '测试用户', + detail_items: [ + { + description: '住宿费', + amount: 2000, + category: 'accommodation', + attachments: [] + }, + { + description: '餐饮费', + amount: 1500, + category: 'food', + attachments: [] + } + ], + attachments: [] + }); + + if (reimbursementResponse.success) { + const reimbursementId = reimbursementResponse.data.id; + console.log(' ✓ 报销申请创建成功'); + + console.log(' - 提交报销申请到审批...'); + const submitResponse = await request('PUT', `${BASE_URL}/api/reimbursements/${reimbursementId}/submit`, {}); + + if (submitResponse.success) { + console.log(' ✓ 报销申请提交成功'); + + console.log(' - 审批报销申请...'); + const approveResponse = await request('PUT', `${BASE_URL}/api/reimbursements/${reimbursementId}/approve`, { + approver: '系统管理员' + }); + + if (approveResponse.success) { + console.log(' ✓ 报销申请审批成功'); + + console.log(' - 执行报销付款...'); + const executeResponse = await request('POST', `${BASE_URL}/api/executions`, { + apply_id: reimbursementId, + apply_type: 'reimbursement', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + if (executeResponse.success) { + console.log(' ✓ 报销付款执行成功'); + } else { + console.log(' ✗ 报销付款执行失败:', executeResponse.error); + } + } else { + console.log(' ✗ 报销申请审批失败:', approveResponse.error); + } + } else { + console.log(' ✗ 报销申请提交失败:', submitResponse.error); + } + } else { + console.log(' ✗ 报销申请创建失败:', reimbursementResponse.error); + } +} + +// 测试付款申请流程 +async function testPaymentFlow() { + console.log(' - 创建付款申请...'); + const paymentResponse = await request('POST', `${BASE_URL}/api/payment-requests`, { + payment_date: '2026-03-24', + payee: '测试供应商', + bank_account: '1234567890', + bank_name: '测试银行', + currency: 'CNY', + reason: '设备采购款', + detail_items: [ + { + description: '设备款', + amount: 50000, + attachments: [] + } + ], + applicant: '测试用户', + attachments: [] + }); + + if (paymentResponse.success) { + const paymentId = paymentResponse.data.id; + console.log(' ✓ 付款申请创建成功'); + + console.log(' - 提交付款申请到审批...'); + const submitResponse = await request('PUT', `${BASE_URL}/api/payments/${paymentId}/submit`, {}); + + if (submitResponse.success) { + console.log(' ✓ 付款申请提交成功'); + + console.log(' - 审批付款申请...'); + const approveResponse = await request('PUT', `${BASE_URL}/api/payments/${paymentId}/approve`, { + approver: '系统管理员' + }); + + if (approveResponse.success) { + console.log(' ✓ 付款申请审批成功'); + + console.log(' - 执行付款...'); + const executeResponse = await request('POST', `${BASE_URL}/api/executions`, { + apply_id: paymentId, + apply_type: 'payment', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + if (executeResponse.success) { + console.log(' ✓ 付款执行成功'); + } else { + console.log(' ✗ 付款执行失败:', executeResponse.error); + } + } else { + console.log(' ✗ 付款申请审批失败:', approveResponse.error); + } + } else { + console.log(' ✗ 付款申请提交失败:', submitResponse.error); + } + } else { + console.log(' ✗ 付款申请创建失败:', paymentResponse.error); + } +} + +// 测试核销申请流程 +async function testVerificationFlow() { + console.log(' - 创建核销申请...'); + const verificationResponse = await request('POST', `${BASE_URL}/api/verifications`, { + verification_date: '2026-03-24', + advance_id: '1', + actual_amount: 4500, + balance: 500, + reason: '项目差旅核销', + applicant: '测试用户', + detail_items: [ + { + description: '住宿费', + amount: 2500, + category: 'accommodation', + attachments: [] + }, + { + description: '餐饮费', + amount: 2000, + category: 'food', + attachments: [] + } + ], + attachments: [] + }); + + if (verificationResponse.success) { + const verificationId = verificationResponse.data.id; + console.log(' ✓ 核销申请创建成功'); + + console.log(' - 提交核销申请到审批...'); + const submitResponse = await request('PUT', `${BASE_URL}/api/verifications/${verificationId}/submit`, {}); + + if (submitResponse.success) { + console.log(' ✓ 核销申请提交成功'); + + console.log(' - 审批核销申请...'); + const approveResponse = await request('PUT', `${BASE_URL}/api/verifications/${verificationId}/approve`, { + approver: '系统管理员' + }); + + if (approveResponse.success) { + console.log(' ✓ 核销申请审批成功'); + + console.log(' - 执行核销...'); + const executeResponse = await request('POST', `${BASE_URL}/api/executions`, { + apply_id: verificationId, + apply_type: 'verification', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }); + + if (executeResponse.success) { + console.log(' ✓ 核销执行成功'); + } else { + console.log(' ✗ 核销执行失败:', executeResponse.error); + } + } else { + console.log(' ✗ 核销申请审批失败:', approveResponse.error); + } + } else { + console.log(' ✗ 核销申请提交失败:', submitResponse.error); + } + } else { + console.log(' ✗ 核销申请创建失败:', verificationResponse.error); + } +} + +// 测试执行记录查询 +async function testExecutionQueries() { + console.log(' - 查询执行记录列表...'); + const historyResponse = await request('GET', `${BASE_URL}/api/executions`); + + if (historyResponse.success) { + console.log(' ✓ 执行记录查询成功,共', historyResponse.data.length, '条记录'); + } else { + console.log(' ✗ 执行记录查询失败:', historyResponse.error); + } + + console.log(' - 查询待执行列表...'); + const pendingResponse = await request('GET', `${BASE_URL}/api/executions/pending`); + + if (pendingResponse.success) { + console.log(' ✓ 待执行列表查询成功,共', pendingResponse.data.length, '条记录'); + } else { + console.log(' ✗ 待执行列表查询失败:', pendingResponse.error); + } + + console.log(' - 查询已执行列表...'); + const executedResponse = await request('GET', `${BASE_URL}/api/executions/executed`); + + if (executedResponse.success) { + console.log(' ✓ 已执行列表查询成功,共', executedResponse.data.length, '条记录'); + } else { + console.log(' ✗ 已执行列表查询失败:', executedResponse.error); + } +} + +// 运行测试 +runTests(); diff --git a/backup_20260327/company-finance-system/backend/test-finance-simple.js b/backup_20260327/company-finance-system/backend/test-finance-simple.js new file mode 100644 index 0000000..b9beeb3 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-finance-simple.js @@ -0,0 +1,133 @@ +const db = require('./db-sqlite'); + +// 测试报销申请功能 +async function testReimbursements() { + console.log('=== 测试报销申请功能 ==='); + + try { + // 测试创建公司支出报销 + console.log('1. 测试创建公司支出报销'); + const companyResult = await db.query( + `INSERT INTO reimbursements (user_id, project_id, amount, currency, amount_cny, reason, reimbursement_date, reimbursement_code, status, applicant, expense_type, detail_items, attachments) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [1, null, 1000, 'CNY', 1000, '测试公司报销', new Date().toISOString().split('T')[0], 'REIM' + Date.now(), 'pending', '测试申请人', 'company', JSON.stringify([{ description: '测试明细', amount: 1000, category: 'general_operations' }]), JSON.stringify([])] + ); + console.log('✓ 公司支出报销创建成功'); + + // 测试创建项目支出报销 + console.log('2. 测试创建项目支出报销'); + const projectResult = await db.query( + `INSERT INTO reimbursements (user_id, project_id, amount, currency, amount_cny, reason, reimbursement_date, reimbursement_code, status, applicant, expense_type, detail_items, attachments) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [1, 1, 1000, 'CNY', 1000, '测试项目报销', new Date().toISOString().split('T')[0], 'REIM' + Date.now(), 'pending', '测试申请人', 'project', JSON.stringify([{ description: '测试明细', amount: 1000, category: 'accommodation' }]), JSON.stringify([])] + ); + console.log('✓ 项目支出报销创建成功'); + + // 测试查询报销列表 + console.log('3. 测试查询报销列表'); + const listResult = await db.query('SELECT * FROM reimbursements ORDER BY created_at DESC LIMIT 5'); + console.log('✓ 报销列表查询成功,共', listResult.rows.length, '条记录'); + + // 测试撤回功能 + if (listResult.rows.length > 0) { + console.log('4. 测试撤回功能'); + const reimbursementId = listResult.rows[0].id; + await db.query('UPDATE reimbursements SET status = ? WHERE id = ?', ['withdrawn', reimbursementId]); + console.log('✓ 报销撤回成功'); + } + + } catch (error) { + console.error('✗ 测试失败:', error.message); + } +} + +// 测试付款申请功能 +async function testPaymentRequests() { + console.log('\n=== 测试付款申请功能 ==='); + + try { + // 测试创建付款申请 + console.log('1. 测试创建付款申请'); + const result = await db.query( + `INSERT INTO payment_requests (request_code, applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, detail_items, attachments, status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ['PAY' + Date.now(), '测试申请人', '测试收款人', '1234567890', '测试银行', 1000, 1000, 'CNY', new Date().toISOString().split('T')[0], '测试付款', JSON.stringify([{ description: '测试明细', amount: 1000, category: 'general_operations' }]), JSON.stringify([]), 'pending'] + ); + console.log('✓ 付款申请创建成功'); + + // 测试查询付款申请列表 + console.log('2. 测试查询付款申请列表'); + const listResult = await db.query('SELECT * FROM payment_requests ORDER BY created_at DESC LIMIT 5'); + console.log('✓ 付款申请列表查询成功,共', listResult.rows.length, '条记录'); + + } catch (error) { + console.error('✗ 测试失败:', error.message); + } +} + +// 测试核销申请功能 +async function testVerifications() { + console.log('\n=== 测试核销申请功能 ==='); + + try { + // 测试创建核销申请 + console.log('1. 测试创建核销申请'); + const result = await db.query( + `INSERT INTO verifications (verification_code, applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, detail_items, attachments, status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ['VER' + Date.now(), '测试申请人', 'ADV20240101001', 1000, 800, 800, 'CNY', new Date().toISOString().split('T')[0], '测试核销', JSON.stringify([{ description: '测试明细', amount: 800, category: 'general_operations' }]), JSON.stringify([]), 'pending'] + ); + console.log('✓ 核销申请创建成功'); + + // 测试查询核销申请列表 + console.log('2. 测试查询核销申请列表'); + const listResult = await db.query('SELECT * FROM verifications ORDER BY created_at DESC LIMIT 5'); + console.log('✓ 核销申请列表查询成功,共', listResult.rows.length, '条记录'); + + } catch (error) { + console.error('✗ 测试失败:', error.message); + } +} + +// 测试支出分类 +async function testExpenseCategories() { + console.log('\n=== 测试支出分类 ==='); + + try { + // 测试项目支出分类 + console.log('1. 测试项目支出分类'); + const projectCategories = [ + '住宿', '餐饮', '加油', '零散材料', '客户关系', '分包关系', 'EDL关系', '额外施工', '其他' + ]; + console.log('✓ 项目支出分类:', projectCategories.join(', ')); + + // 测试公司支出分类 + console.log('2. 测试公司支出分类'); + const companyCategories = [ + '通用运营(房租/耗材)', '交通通勤', '业扩营销', '电力系统关系', '员工福利', '快递物流', '其他' + ]; + console.log('✓ 公司支出分类:', companyCategories.join(', ')); + + } catch (error) { + console.error('✗ 测试失败:', error.message); + } +} + +// 运行所有测试 +async function runAllTests() { + console.log('开始测试轻远电力老挝ERP财务功能...\n'); + + await testExpenseCategories(); + await testReimbursements(); + await testPaymentRequests(); + await testVerifications(); + + console.log('\n=== 测试完成 ==='); + console.log('所有功能测试已完成,请检查测试结果。'); + + // 关闭数据库连接 + db.close(); +} + +// 运行测试 +runAllTests(); diff --git a/backup_20260327/company-finance-system/backend/test-finance.js b/backup_20260327/company-finance-system/backend/test-finance.js new file mode 100644 index 0000000..7c6bfd5 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-finance.js @@ -0,0 +1,233 @@ +const request = require('supertest'); +const app = require('./api-complete'); + +// 测试报销申请功能 +describe('报销申请功能测试', () => { + it('应该支持公司支出和项目支出类型', async () => { + const response = await request(app) + .post('/api/reimbursements') + .send({ + amount: 1000, + type: 'company', + description: '测试报销', + expense_type: 'company', + reason: '测试报销', + reimbursement_date: new Date().toISOString().split('T')[0], + detail_items: [{ + description: '测试明细', + amount: 1000, + category: 'general_operations' + }], + attachments: [] + }); + + expect(response.statusCode).toBe(200); + expect(response.body.success).toBe(true); + }); + + it('项目支出时必须提供项目ID', async () => { + const response = await request(app) + .post('/api/reimbursements') + .send({ + amount: 1000, + type: 'project', + description: '测试项目报销', + expense_type: 'project', + project_id: 1, + reason: '测试项目报销', + reimbursement_date: new Date().toISOString().split('T')[0], + detail_items: [{ + description: '测试明细', + amount: 1000, + category: 'accommodation' + }], + attachments: [] + }); + + expect(response.statusCode).toBe(200); + expect(response.body.success).toBe(true); + }); + + it('应该正确处理支出分类', async () => { + // 测试项目支出分类 + const projectResponse = await request(app) + .post('/api/reimbursements') + .send({ + amount: 1000, + type: 'project', + description: '测试项目报销', + expense_type: 'project', + project_id: 1, + reason: '测试项目报销', + reimbursement_date: new Date().toISOString().split('T')[0], + detail_items: [{ + description: '测试明细', + amount: 1000, + category: 'accommodation' + }], + attachments: [] + }); + + expect(projectResponse.statusCode).toBe(200); + expect(projectResponse.body.success).toBe(true); + + // 测试公司支出分类 + const companyResponse = await request(app) + .post('/api/reimbursements') + .send({ + amount: 1000, + type: 'company', + description: '测试公司报销', + expense_type: 'company', + reason: '测试公司报销', + reimbursement_date: new Date().toISOString().split('T')[0], + detail_items: [{ + description: '测试明细', + amount: 1000, + category: 'general_operations' + }], + attachments: [] + }); + + expect(companyResponse.statusCode).toBe(200); + expect(companyResponse.body.success).toBe(true); + }); + + it('应该支持币种统一', async () => { + const response = await request(app) + .post('/api/reimbursements') + .send({ + amount: 1000, + currency: 'USD', + amount_cny: 7000, + type: 'company', + description: '测试外币报销', + expense_type: 'company', + reason: '测试外币报销', + reimbursement_date: new Date().toISOString().split('T')[0], + detail_items: [{ + description: '测试明细', + amount: 1000, + category: 'general_operations' + }], + attachments: [] + }); + + expect(response.statusCode).toBe(200); + expect(response.body.success).toBe(true); + }); + + it('应该支持附件上传', async () => { + const response = await request(app) + .post('/api/reimbursements') + .send({ + amount: 1000, + type: 'company', + description: '测试附件报销', + expense_type: 'company', + reason: '测试附件报销', + reimbursement_date: new Date().toISOString().split('T')[0], + detail_items: [{ + description: '测试明细', + amount: 1000, + category: 'general_operations', + attachments: ['https://example.com/test.jpg'] + }], + attachments: ['https://example.com/main.jpg'] + }); + + expect(response.statusCode).toBe(200); + expect(response.body.success).toBe(true); + }); +}); + +// 测试付款申请功能 +describe('付款申请功能测试', () => { + it('应该创建付款申请', async () => { + const response = await request(app) + .post('/api/payment-requests') + .send({ + applicant: '测试申请人', + payee: '测试收款人', + bank_account: '1234567890', + bank_name: '测试银行', + amount: 1000, + amount_cny: 1000, + currency: 'CNY', + payment_date: new Date().toISOString().split('T')[0], + reason: '测试付款', + detail_items: [{ + description: '测试明细', + amount: 1000, + category: 'general_operations' + }], + attachments: [] + }); + + expect(response.statusCode).toBe(200); + expect(response.body.success).toBe(true); + }); +}); + +// 测试核销申请功能 +describe('核销申请功能测试', () => { + it('应该创建核销申请', async () => { + const response = await request(app) + .post('/api/verifications') + .send({ + applicant: '测试申请人', + advance_code: 'ADV20240101001', + advance_amount: 1000, + amount: 800, + amount_cny: 800, + currency: 'CNY', + verification_date: new Date().toISOString().split('T')[0], + reason: '测试核销', + detail_items: [{ + description: '测试明细', + amount: 800, + category: 'general_operations' + }], + attachments: [] + }); + + expect(response.statusCode).toBe(200); + expect(response.body.success).toBe(true); + }); +}); + +// 测试审批流程 +describe('审批流程测试', () => { + it('应该支持撤回功能', async () => { + // 先创建一个报销申请 + const createResponse = await request(app) + .post('/api/reimbursements') + .send({ + amount: 1000, + type: 'company', + description: '测试撤回', + expense_type: 'company', + reason: '测试撤回', + reimbursement_date: new Date().toISOString().split('T')[0], + detail_items: [{ + description: '测试明细', + amount: 1000, + category: 'general_operations' + }], + attachments: [] + }); + + if (createResponse.body.success && createResponse.body.data) { + const id = createResponse.body.data.id; + + // 测试撤回功能 + const withdrawResponse = await request(app) + .post(`/api/reimbursements/${id}/withdraw`); + + expect(withdrawResponse.statusCode).toBe(200); + expect(withdrawResponse.body.success).toBe(true); + } + }); +}); + +console.log('测试完成'); diff --git a/backup_20260327/company-finance-system/backend/test-payment-flow.js b/backup_20260327/company-finance-system/backend/test-payment-flow.js new file mode 100644 index 0000000..3490093 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-payment-flow.js @@ -0,0 +1,368 @@ +const http = require('http'); + +// 测试基础URL +const BASE_URL = 'http://localhost:3005'; + +// 测试结果 +const testResults = []; + +// HTTP请求函数 +function httpRequest(options, postData = null) { + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + resolve({ status: res.statusCode, data: JSON.parse(data) }); + }); + }); + + req.on('error', (e) => { + reject(e); + }); + + if (postData) { + req.write(JSON.stringify(postData)); + } + + req.end(); + }); +} + +// 测试创建付款申请 +async function testCreatePayment() { + console.log('\n💰 测试创建付款申请...'); + + const options = { + hostname: 'localhost', + port: 3005, + path: '/api/payment-requests', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }; + + const paymentData = { + payment_date: '2026-03-24', + payee: '测试供应商', + bank_account: '1234567890123456789', + bank_name: '测试银行', + currency: 'CNY', + reason: '设备采购款', + detail_items: [ + { + description: '设备1', + amount: 10000, + attachments: [] + }, + { + description: '设备2', + amount: 20000, + attachments: [] + } + ], + attachments: [], + applicant: '测试用户' + }; + + try { + const response = await httpRequest(options, paymentData); + console.log('创建付款申请响应状态:', response.status); + console.log('创建付款申请响应数据:', JSON.stringify(response.data, null, 2)); + + if (response.status === 200 && response.data.success) { + testResults.push({ test: '创建付款申请', status: '✅ 成功', paymentId: response.data.data.id }); + console.log('✅ 付款申请创建成功,ID:', response.data.data.id); + return response.data.data.id; + } else { + testResults.push({ test: '创建付款申请', status: '❌ 失败', message: response.data.message || '创建失败' }); + console.log('❌ 付款申请创建失败:', response.data.message); + return null; + } + } catch (error) { + testResults.push({ test: '创建付款申请', status: '❌ 失败', message: error.message }); + console.log('❌ 付款申请创建失败:', error.message); + return null; + } +} + +// 测试提交付款申请 +async function testSubmitPayment(paymentId) { + console.log('\n📤 测试提交付款申请...'); + + const options = { + hostname: 'localhost', + port: 3005, + path: `/api/payment-requests/${paymentId}/submit`, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }; + + try { + const response = await httpRequest(options); + console.log('提交付款申请响应状态:', response.status); + console.log('提交付款申请响应数据:', JSON.stringify(response.data, null, 2)); + + if (response.status === 200 && response.data.success) { + testResults.push({ test: '提交付款申请', status: '✅ 成功' }); + console.log('✅ 付款申请提交成功'); + return true; + } else { + testResults.push({ test: '提交付款申请', status: '❌ 失败', message: response.data.message || '提交失败' }); + console.log('❌ 付款申请提交失败:', response.data.message); + return false; + } + } catch (error) { + testResults.push({ test: '提交付款申请', status: '❌ 失败', message: error.message }); + console.log('❌ 付款申请提交失败:', error.message); + return false; + } +} + +// 测试审批付款申请 +async function testApprovePayment(paymentId) { + console.log('\n✅ 测试审批付款申请...'); + + const options = { + hostname: 'localhost', + port: 3005, + path: `/api/payment-requests/${paymentId}/approve`, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }; + + try { + const response = await httpRequest(options, { remark: '审批通过' }); + console.log('审批付款申请响应状态:', response.status); + console.log('审批付款申请响应数据:', JSON.stringify(response.data, null, 2)); + + if (response.status === 200 && response.data.success) { + testResults.push({ test: '审批付款申请', status: '✅ 成功' }); + console.log('✅ 付款申请审批成功'); + return true; + } else { + testResults.push({ test: '审批付款申请', status: '❌ 失败', message: response.data.message || '审批失败' }); + console.log('❌ 付款申请审批失败:', response.data.message); + return false; + } + } catch (error) { + testResults.push({ test: '审批付款申请', status: '❌ 失败', message: error.message }); + console.log('❌ 付款申请审批失败:', error.message); + return false; + } +} + +// 测试执行付款申请 +async function testExecutePayment(paymentId) { + console.log('\n💳 测试执行付款申请...'); + + const options = { + hostname: 'localhost', + port: 3005, + path: '/api/executions', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }; + + const executionData = { + apply_id: paymentId, + apply_type: 'payment', + action: 'execute', + execute_method: '银行转账', + voucher_no: 'VCH' + Date.now(), + remark: '测试执行' + }; + + try { + const response = await httpRequest(options, executionData); + console.log('执行付款申请响应状态:', response.status); + console.log('执行付款申请响应数据:', JSON.stringify(response.data, null, 2)); + + if (response.status === 200 && response.data.success) { + testResults.push({ test: '执行付款申请', status: '✅ 成功' }); + console.log('✅ 付款申请执行成功'); + return true; + } else { + testResults.push({ test: '执行付款申请', status: '❌ 失败', message: response.data.message || '执行失败' }); + console.log('❌ 付款申请执行失败:', response.data.message); + return false; + } + } catch (error) { + testResults.push({ test: '执行付款申请', status: '❌ 失败', message: error.message }); + console.log('❌ 付款申请执行失败:', error.message); + return false; + } +} + +// 测试获取付款申请列表 +async function testGetPayments() { + console.log('\n📋 测试获取付款申请列表...'); + + const options = { + hostname: 'localhost', + port: 3005, + path: '/api/payment-requests', + method: 'GET' + }; + + try { + const response = await httpRequest(options); + console.log('获取付款申请列表响应状态:', response.status); + console.log('获取付款申请列表响应数据:', JSON.stringify(response.data, null, 2)); + + if (response.status === 200 && response.data.success) { + testResults.push({ test: '获取付款申请列表', status: '✅ 成功', message: `找到 ${response.data.data.length} 条记录` }); + console.log('✅ 付款申请列表获取成功,找到', response.data.data.length, '条记录'); + return true; + } else { + testResults.push({ test: '获取付款申请列表', status: '❌ 失败', message: response.data.message || '获取失败' }); + console.log('❌ 付款申请列表获取失败:', response.data.message); + return false; + } + } catch (error) { + testResults.push({ test: '获取付款申请列表', status: '❌ 失败', message: error.message }); + console.log('❌ 付款申请列表获取失败:', error.message); + return false; + } +} + +// 测试待执行列表 +async function testGetPendingExecutions() { + console.log('\n⏳ 测试获取待执行列表...'); + + const options = { + hostname: 'localhost', + port: 3005, + path: '/api/executions/pending', + method: 'GET' + }; + + try { + const response = await httpRequest(options); + console.log('获取待执行列表响应状态:', response.status); + console.log('获取待执行列表响应数据:', JSON.stringify(response.data, null, 2)); + + if (response.status === 200 && response.data.success) { + testResults.push({ test: '获取待执行列表', status: '✅ 成功', message: `找到 ${response.data.data.length} 条记录` }); + console.log('✅ 待执行列表获取成功,找到', response.data.data.length, '条记录'); + return true; + } else { + testResults.push({ test: '获取待执行列表', status: '❌ 失败', message: response.data.message || '获取失败' }); + console.log('❌ 待执行列表获取失败:', response.data.message); + return false; + } + } catch (error) { + testResults.push({ test: '获取待执行列表', status: '❌ 失败', message: error.message }); + console.log('❌ 待执行列表获取失败:', error.message); + return false; + } +} + +// 测试已执行列表 +async function testGetExecutedExecutions() { + console.log('\n✅ 测试获取已执行列表...'); + + const options = { + hostname: 'localhost', + port: 3005, + path: '/api/executions/executed', + method: 'GET' + }; + + try { + const response = await httpRequest(options); + console.log('获取已执行列表响应状态:', response.status); + console.log('获取已执行列表响应数据:', JSON.stringify(response.data, null, 2)); + + if (response.status === 200 && response.data.success) { + testResults.push({ test: '获取已执行列表', status: '✅ 成功', message: `找到 ${response.data.data.length} 条记录` }); + console.log('✅ 已执行列表获取成功,找到', response.data.data.length, '条记录'); + return true; + } else { + testResults.push({ test: '获取已执行列表', status: '❌ 失败', message: response.data.message || '获取失败' }); + console.log('❌ 已执行列表获取失败:', response.data.message); + return false; + } + } catch (error) { + testResults.push({ test: '获取已执行列表', status: '❌ 失败', message: error.message }); + console.log('❌ 已执行列表获取失败:', error.message); + return false; + } +} + +// 运行所有测试 +async function runAllTests() { + console.log('🚀 开始执行付款申请流程测试\n'); + + // 1. 获取付款申请列表 + await testGetPayments(); + + // 2. 创建付款申请 + const paymentId = await testCreatePayment(); + + if (paymentId) { + // 3. 提交付款申请 + const submitSuccess = await testSubmitPayment(paymentId); + + if (submitSuccess) { + // 4. 审批付款申请 + const approveSuccess = await testApprovePayment(paymentId); + + if (approveSuccess) { + // 5. 测试待执行列表 + await testGetPendingExecutions(); + + // 6. 执行付款申请 + const executeSuccess = await testExecutePayment(paymentId); + + if (executeSuccess) { + // 7. 测试已执行列表 + await testGetExecutedExecutions(); + } + } + } + } + + // 输出测试结果 + console.log('\n📋 测试结果汇总:'); + console.log('============================================='); + + let successCount = 0; + let failureCount = 0; + + testResults.forEach(result => { + console.log(`${result.test}: ${result.status}`); + if (result.status.includes('✅')) { + successCount++; + } else { + failureCount++; + } + }); + + console.log('============================================='); + console.log(`总测试数: ${testResults.length}`); + console.log(`成功: ${successCount}`); + console.log(`失败: ${failureCount}`); + + if (failureCount === 0) { + console.log('\n🎉 所有测试通过!付款申请流程正常。'); + } else { + console.log('\n⚠️ 部分测试失败,需要检查问题。'); + } + + console.log('\n测试完成。'); +} + +// 启动测试 +runAllTests().catch(error => { + console.error('测试过程中出现错误:', error); +}); diff --git a/backup_20260327/company-finance-system/backend/test-purchase-flow.js b/backup_20260327/company-finance-system/backend/test-purchase-flow.js new file mode 100644 index 0000000..fbd772b --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-purchase-flow.js @@ -0,0 +1,274 @@ +const http = require('http'); +const app = require('./final-backend'); + +let server; +let testPurchaseRequestId; + +// 简单的HTTP请求函数 +function request(options, data = null) { + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let body = ''; + res.on('data', (chunk) => { + body += chunk; + }); + res.on('end', () => { + try { + const parsed = JSON.parse(body); + resolve({ status: res.statusCode, body: parsed }); + } catch (e) { + resolve({ status: res.statusCode, body: body }); + } + }); + }); + + req.on('error', (e) => { + reject(e); + }); + + if (data) { + req.write(JSON.stringify(data)); + } + + req.end(); + }); +} + +// 测试采购流程 +async function testPurchaseFlow() { + console.log('=== 测试采购流程 ==='); + + // 1. 创建采购申请 + console.log('1. 创建采购申请...'); + const createResponse = await request({ + hostname: 'localhost', + port: 3005, + path: '/api/purchase-requests', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }, { + project_id: 1, + applicant: '测试申请人', + request_date: '2026-03-25', + expense_category: 'material', + total_amount: 15000, + items: [ + { + product_name: '测试商品A', + quantity: 10, + unit_price: 1000, + total_price: 10000 + }, + { + product_name: '测试商品B', + quantity: 5, + unit_price: 1000, + total_price: 5000 + } + ] + }); + + console.log('创建采购申请响应:', createResponse.status, createResponse.body); + if (createResponse.status === 200 && createResponse.body.success) { + testPurchaseRequestId = createResponse.body.data.id; + console.log('采购申请ID:', testPurchaseRequestId); + } else { + console.error('创建采购申请失败'); + return false; + } + + // 2. 获取采购申请列表 + console.log('\n2. 获取采购申请列表...'); + const listResponse = await request({ + hostname: 'localhost', + port: 3005, + path: '/api/purchase-requests', + method: 'GET' + }); + console.log('获取采购申请列表响应:', listResponse.status, listResponse.body); + if (listResponse.status !== 200 || !listResponse.body.success) { + console.error('获取采购申请列表失败'); + return false; + } + + // 3. 获取采购申请详情 + console.log('\n3. 获取采购申请详情...'); + const detailResponse = await request({ + hostname: 'localhost', + port: 3005, + path: `/api/purchase-requests/${testPurchaseRequestId}`, + method: 'GET' + }); + console.log('获取采购申请详情响应:', detailResponse.status, detailResponse.body); + if (detailResponse.status !== 200 || !detailResponse.body.success) { + console.error('获取采购申请详情失败'); + return false; + } + + // 4. 提交采购申请 + console.log('\n4. 提交采购申请...'); + const submitResponse = await request({ + hostname: 'localhost', + port: 3005, + path: `/api/purchase-requests/${testPurchaseRequestId}/submit`, + method: 'POST' + }); + console.log('提交采购申请响应:', submitResponse.status, submitResponse.body); + if (submitResponse.status !== 200 || !submitResponse.body.success) { + console.error('提交采购申请失败'); + return false; + } + + // 5. 审批采购申请 + console.log('\n5. 审批采购申请...'); + const approveResponse = await request({ + hostname: 'localhost', + port: 3005, + path: `/api/purchase-requests/${testPurchaseRequestId}/approve`, + method: 'POST' + }); + console.log('审批采购申请响应:', approveResponse.status, approveResponse.body); + if (approveResponse.status !== 200 || !approveResponse.body.success) { + console.error('审批采购申请失败'); + return false; + } + + return true; +} + +// 测试库存管理流程 +async function testInventoryFlow() { + console.log('\n=== 测试库存管理流程 ==='); + + // 1. 获取库存记录 + console.log('1. 获取库存记录...'); + const recordsResponse = await request({ + hostname: 'localhost', + port: 3005, + path: '/api/inventory', + method: 'GET' + }); + console.log('获取库存记录响应:', recordsResponse.status, recordsResponse.body); + if (recordsResponse.status !== 200 || !recordsResponse.body.success) { + console.error('获取库存记录失败'); + return false; + } + + // 2. 获取库存汇总 + console.log('\n2. 获取库存汇总...'); + const summaryResponse = await request({ + hostname: 'localhost', + port: 3005, + path: '/api/inventory/summary', + method: 'GET' + }); + console.log('获取库存汇总响应:', summaryResponse.status, summaryResponse.body); + if (summaryResponse.status !== 200 || !summaryResponse.body.success) { + console.error('获取库存汇总失败'); + return false; + } + + // 3. 创建出库记录 + console.log('\n3. 创建出库记录...'); + const outResponse = await request({ + hostname: 'localhost', + port: 3005, + path: '/api/inventory/out', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }, { + project_id: 1, + product_id: 1, + quantity: 10, + operator: '测试操作员' + }); + console.log('创建出库记录响应:', outResponse.status, outResponse.body); + if (outResponse.status !== 200 || !outResponse.body.success) { + console.error('创建出库记录失败'); + return false; + } + + return true; +} + +// 测试成本统计功能 +async function testCostStatistics() { + console.log('\n=== 测试成本统计功能 ==='); + + // 获取项目成本统计 + console.log('1. 获取项目成本统计...'); + const costResponse = await request({ + hostname: 'localhost', + port: 3005, + path: '/api/projects/1/cost-summary', + method: 'GET' + }); + console.log('获取项目成本统计响应:', costResponse.status, costResponse.body); + if (costResponse.status !== 200 || !costResponse.body.success) { + console.error('获取项目成本统计失败'); + return false; + } + + // 验证成本统计数据结构 + const costData = costResponse.body.data; + if (!costData.project_name || !costData.purchase_cost || costData.payment_cost === undefined || costData.total_cost === undefined || costData.profit === undefined) { + console.error('成本统计数据结构不完整'); + return false; + } + + console.log('成本统计数据验证通过:', { + project_name: costData.project_name, + purchase_cost: costData.purchase_cost, + payment_cost: costData.payment_cost, + total_cost: costData.total_cost, + profit: costData.profit + }); + + return true; +} + +// 主测试函数 +async function runTests() { + try { + // 启动服务器 + server = app.listen(3005, () => { + console.log('测试服务器启动在端口3005'); + }); + + // 等待服务器启动 + await new Promise(resolve => setTimeout(resolve, 1000)); + + // 运行测试 + const purchaseResult = await testPurchaseFlow(); + const inventoryResult = await testInventoryFlow(); + const costResult = await testCostStatistics(); + + // 输出测试结果 + console.log('\n=== 测试结果 ==='); + console.log('采购流程测试:', purchaseResult ? '通过' : '失败'); + console.log('库存管理流程测试:', inventoryResult ? '通过' : '失败'); + console.log('成本统计功能测试:', costResult ? '通过' : '失败'); + + if (purchaseResult && inventoryResult && costResult) { + console.log('\n🎉 所有测试通过!'); + } else { + console.log('\n❌ 部分测试失败!'); + } + + } catch (error) { + console.error('测试过程中发生错误:', error); + } finally { + // 关闭服务器 + if (server) { + server.close(); + console.log('测试服务器已关闭'); + } + } +} + +// 运行测试 +runTests(); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/test-server.js b/backup_20260327/company-finance-system/backend/test-server.js new file mode 100644 index 0000000..b774322 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-server.js @@ -0,0 +1,28 @@ +const express = require('express'); +const path = require('path'); +const app = express(); +const PORT = 5001; + +// 静态文件服务 +app.use(express.static(path.join(__dirname))); + +// 测试页面 +app.get('/test', (req, res) => { + res.sendFile(path.join(__dirname, 'test.html')); +}); + +// 健康检查 +app.get('/health', (req, res) => { + res.json({ + status: 'ok', + service: 'test-server', + timestamp: new Date().toISOString(), + version: '1.0.0' + }); +}); + +// 启动服务器 +app.listen(PORT, () => { + console.log(`测试服务器运行在 http://localhost:${PORT}`); + console.log(`测试页面: http://localhost:${PORT}/test`); +}); diff --git a/backup_20260327/company-finance-system/backend/test-simple-flow.js b/backup_20260327/company-finance-system/backend/test-simple-flow.js new file mode 100644 index 0000000..ab0403d --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-simple-flow.js @@ -0,0 +1,197 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +// 测试基础URL +const BASE_URL = 'http://localhost:3005'; + +// 测试数据 +const testUser = { + username: 'admin', + password: 'X123c321@' +}; + +let authToken = ''; + +// 测试结果 +const testResults = []; + +// HTTP请求函数 +function httpRequest(options, postData = null) { + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + resolve({ status: res.statusCode, data: JSON.parse(data) }); + }); + }); + + req.on('error', (e) => { + reject(e); + }); + + if (postData) { + req.write(JSON.stringify(postData)); + } + + req.end(); + }); +} + +// 登录函数 +async function login() { + console.log('\n🔐 登录测试...'); + + const options = { + hostname: 'localhost', + port: 3005, + path: '/api/auth/login', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }; + + try { + const response = await httpRequest(options, testUser); + + console.log('登录响应状态:', response.status); + console.log('登录响应数据:', JSON.stringify(response.data, null, 2)); + + if (response.status === 200 && response.data.success) { + authToken = 'test-token'; // 后端没有返回token,使用模拟token + testResults.push({ test: '登录', status: '✅ 成功' }); + console.log('✅ 登录成功'); + } else { + testResults.push({ test: '登录', status: '❌ 失败', message: response.data.message || '登录失败' }); + console.log('❌ 登录失败:', response.data.message); + } + } catch (error) { + testResults.push({ test: '登录', status: '❌ 失败', message: error.message }); + console.log('❌ 登录失败:', error.message); + console.log('错误详情:', error); + } +} + +// 测试执行记录查询 +async function testExecutionRecords() { + console.log('\n📊 测试执行记录查询...'); + + // 1. 查询执行记录列表 + console.log('1. 查询执行记录列表...'); + const options = { + hostname: 'localhost', + port: 3005, + path: '/api/executions', + method: 'GET' + }; + + try { + console.log('正在请求:', options.path); + const response = await httpRequest(options); + console.log('响应状态:', response.status); + console.log('响应数据:', JSON.stringify(response.data, null, 2)); + if (response.status === 200 && response.data.success) { + testResults.push({ test: '查询执行记录列表', status: '✅ 成功', message: `找到 ${response.data.data.length} 条记录` }); + console.log('✅ 执行记录查询成功,找到', response.data.data.length, '条记录'); + } else { + testResults.push({ test: '查询执行记录列表', status: '❌ 失败', message: response.data.message || '查询失败' }); + console.log('❌ 执行记录查询失败:', response.data.message); + } + } catch (error) { + testResults.push({ test: '查询执行记录列表', status: '❌ 失败', message: error.message }); + console.log('❌ 执行记录查询失败:', error.message); + console.log('错误详情:', error); + } + + // 2. 查询待执行列表 + console.log('2. 查询待执行列表...'); + const pendingOptions = { + hostname: 'localhost', + port: 3005, + path: '/api/executions/pending', + method: 'GET' + }; + + try { + const response = await httpRequest(pendingOptions); + if (response.status === 200 && response.data.success) { + testResults.push({ test: '查询待执行列表', status: '✅ 成功', message: `找到 ${response.data.data.length} 条记录` }); + console.log('✅ 待执行列表查询成功,找到', response.data.data.length, '条记录'); + } else { + testResults.push({ test: '查询待执行列表', status: '❌ 失败', message: response.data.message || '查询失败' }); + console.log('❌ 待执行列表查询失败:', response.data.message); + } + } catch (error) { + testResults.push({ test: '查询待执行列表', status: '❌ 失败', message: error.message }); + console.log('❌ 待执行列表查询失败:', error.message); + } + + // 3. 查询已执行列表 + console.log('3. 查询已执行列表...'); + const executedOptions = { + hostname: 'localhost', + port: 3005, + path: '/api/executions/executed', + method: 'GET' + }; + + try { + const response = await httpRequest(executedOptions); + if (response.status === 200 && response.data.success) { + testResults.push({ test: '查询已执行列表', status: '✅ 成功', message: `找到 ${response.data.data.length} 条记录` }); + console.log('✅ 已执行列表查询成功,找到', response.data.data.length, '条记录'); + } else { + testResults.push({ test: '查询已执行列表', status: '❌ 失败', message: response.data.message || '查询失败' }); + console.log('❌ 已执行列表查询失败:', response.data.message); + } + } catch (error) { + testResults.push({ test: '查询已执行列表', status: '❌ 失败', message: error.message }); + console.log('❌ 已执行列表查询失败:', error.message); + } +} + +// 运行测试 +async function runTests() { + console.log('🚀 开始执行财务流程测试\n'); + + // 直接测试执行记录查询,跳过登录 + await testExecutionRecords(); + + // 输出测试结果 + console.log('\n📋 测试结果汇总:'); + console.log('============================================='); + + let successCount = 0; + let failureCount = 0; + + testResults.forEach(result => { + console.log(`${result.test}: ${result.status}`); + if (result.status.includes('✅')) { + successCount++; + } else { + failureCount++; + } + }); + + console.log('============================================='); + console.log(`总测试数: ${testResults.length}`); + console.log(`成功: ${successCount}`); + console.log(`失败: ${failureCount}`); + + if (failureCount === 0) { + console.log('\n🎉 所有测试通过!执行记录查询功能正常。'); + } else { + console.log('\n⚠️ 部分测试失败,需要检查问题。'); + } + + console.log('\n测试完成。'); +} + +// 启动测试 +runTests().catch(error => { + console.error('测试过程中出现错误:', error); +}); diff --git a/backup_20260327/company-finance-system/backend/test-simple.js b/backup_20260327/company-finance-system/backend/test-simple.js new file mode 100644 index 0000000..5640d4a --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-simple.js @@ -0,0 +1,93 @@ +const http = require('http'); + +// 测试基础URL +const BASE_URL = 'http://localhost:3005'; + +// HTTP请求函数 +function request(method, url, data = null) { + return new Promise((resolve, reject) => { + const options = { + method, + headers: { + 'Content-Type': 'application/json' + } + }; + + const req = http.request(url, options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + try { + resolve(JSON.parse(data)); + } catch (e) { + resolve({ success: false, error: 'Invalid JSON response', raw: data }); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + if (data) { + req.write(JSON.stringify(data)); + } + + req.end(); + }); +} + +// 测试函数 +async function testAPI() { + console.log('=== 测试API端点 ==='); + + try { + // 测试登录 + console.log('1. 测试登录API...'); + const loginResponse = await request('POST', `${BASE_URL}/api/auth/login`, { + username: 'admin', + password: 'X123c321@' + }); + console.log('登录响应:', loginResponse); + + // 测试预支申请列表 + console.log('\n2. 测试预支申请列表API...'); + const advancesResponse = await request('GET', `${BASE_URL}/api/advances`); + console.log('预支申请列表响应:', advancesResponse); + + // 测试创建预支申请 + console.log('\n3. 测试创建预支申请API...'); + const createAdvanceResponse = await request('POST', `${BASE_URL}/api/advances`, { + advance_date: '2026-03-24', + amount: 5000, + currency: 'CNY', + reason: '测试预支', + project_id: 1, + applicant: '测试用户', + attachments: [] + }); + console.log('创建预支申请响应:', createAdvanceResponse); + + if (createAdvanceResponse.success && createAdvanceResponse.data && createAdvanceResponse.data.id) { + const advanceId = createAdvanceResponse.data.id; + + // 测试提交预支申请 + console.log('\n4. 测试提交预支申请API...'); + const submitResponse = await request('POST', `${BASE_URL}/api/advances/${advanceId}/submit`); + console.log('提交预支申请响应:', submitResponse); + } + + // 测试执行记录API + console.log('\n5. 测试执行记录API...'); + const executionsResponse = await request('GET', `${BASE_URL}/api/executions`); + console.log('执行记录响应:', executionsResponse); + + } catch (error) { + console.error('测试过程中发生错误:', error); + } +} + +// 运行测试 +testAPI(); diff --git a/backup_20260327/company-finance-system/backend/test-template.js b/backup_20260327/company-finance-system/backend/test-template.js new file mode 100644 index 0000000..80147c9 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-template.js @@ -0,0 +1,33 @@ +const http = require('http'); + +const options = { + hostname: 'localhost', + port: 3005, + path: '/api/products/template', + method: 'GET' +}; + +const req = http.request(options, (res) => { + console.log('状态码:', res.statusCode); + console.log('响应头:', JSON.stringify(res.headers, null, 2)); + + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + console.log('响应长度:', data.length); + if (res.statusCode === 200) { + console.log('✅ 模板下载API正常工作'); + } else { + console.log('❌ 模板下载失败:', data); + } + }); +}); + +req.on('error', (e) => { + console.error('请求失败:', e.message); +}); + +req.end(); diff --git a/backup_20260327/company-finance-system/backend/test-upload.html b/backup_20260327/company-finance-system/backend/test-upload.html new file mode 100644 index 0000000..9164f86 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test-upload.html @@ -0,0 +1,72 @@ + + + + 文件上传测试 + + + +
+

文件上传测试

+
+
+ + +
+ +
+
+
+ + + \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/test.html b/backup_20260327/company-finance-system/backend/test.html new file mode 100644 index 0000000..784684d --- /dev/null +++ b/backup_20260327/company-finance-system/backend/test.html @@ -0,0 +1,164 @@ + + + + 系统测试 - 公司财务管理系统 + + + + +
+

公司财务管理系统 - 生产环境测试

+

服务器: 43.161.248.209 | 时间:

+ +
+

🔧 后端API测试

+
测试中...
+ + 健康检查 +
+ +
+

🗄️ 数据库测试

+
测试中...
+ +
+ +
+

🌐 前端访问测试

+
测试中...
+ + 访问前端(8080) +
+ +
+

👤 登录测试

+
+

测试账号: admin | 密码: password

+ +
+
+
+ +
+

🚀 快速访问

+

+ 后端健康检查 + 客户API + 前端应用 +

+
+ +
+

📊 系统状态

+
加载中...
+ +
+
+ + + + diff --git a/backup_20260327/company-finance-system/backend/tests/test-advance-verification-status.spec.js b/backup_20260327/company-finance-system/backend/tests/test-advance-verification-status.spec.js new file mode 100644 index 0000000..ec7da86 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/tests/test-advance-verification-status.spec.js @@ -0,0 +1,104 @@ +const db = require('../db-sqlite'); + +describe('预支核销状态管理测试', () => { + let advanceId; + let verificationId; + + beforeAll(async () => { + // 清空测试数据 + await db.query('DELETE FROM verifications WHERE 1=1'); + await db.query('DELETE FROM advances WHERE 1=1'); + }); + + afterAll(async () => { + // 清理测试数据 + await db.query('DELETE FROM verifications WHERE 1=1'); + await db.query('DELETE FROM advances WHERE 1=1'); + }); + + // 测试1: 创建预支单 + test('创建预支单', async () => { + const result = await db.query( + 'INSERT INTO advances (user_id, project_id, amount, currency, reason, advance_date, advance_code, status, applicant) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + [1, 1, 1000, 'CNY', '测试预支', '2026-03-25', 'ADV-TEST-001', 'pending', '隆林'] + ); + advanceId = result.lastID; + expect(advanceId).toBeDefined(); + }); + + // 测试2: 审批预支单 + test('审批预支单', async () => { + await db.query('UPDATE advances SET status = ? WHERE id = ?', ['approved', advanceId]); + const advance = await db.query('SELECT * FROM advances WHERE id = ?', [advanceId]); + expect(advance.rows[0].status).toBe('approved'); + }); + + // 测试3: 执行预支单 + test('执行预支单', async () => { + await db.query('UPDATE advances SET status = ? WHERE id = ?', ['executed', advanceId]); + const advance = await db.query('SELECT * FROM advances WHERE id = ?', [advanceId]); + expect(advance.rows[0].status).toBe('executed'); + }); + + // 测试4: 创建部分核销单 + test('创建部分核销单', async () => { + const result = await db.query( + 'INSERT INTO verifications (advance_id, amount, currency, reason, verification_date, verification_code, status, applicant, advance_code, advance_amount, settlement) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [advanceId, 300, 'CNY', '部分核销', '2026-03-25', 'VER-TEST-001', 'pending', '隆林', 'ADV-TEST-001', 1000, 0] + ); + verificationId = result.lastID; + expect(verificationId).toBeDefined(); + }); + + // 测试5: 审批核销单 + test('审批核销单', async () => { + await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['approved', verificationId]); + const verification = await db.query('SELECT * FROM verifications WHERE id = ?', [verificationId]); + expect(verification.rows[0].status).toBe('approved'); + }); + + // 测试6: 执行核销单 + test('执行核销单', async () => { + await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['executed', verificationId]); + // 更新预支单状态为部分核销 + await db.query('UPDATE advances SET status = ? WHERE id = ?', ['partial_verification', advanceId]); + // 更新预支单已核销金额 + await db.query('UPDATE advances SET total_reimbursed = ? WHERE id = ?', [300, advanceId]); + + const verification = await db.query('SELECT * FROM verifications WHERE id = ?', [verificationId]); + expect(verification.rows[0].status).toBe('executed'); + }); + + // 测试7: 检查预支单状态 + test('检查预支单状态', async () => { + const advance = await db.query('SELECT * FROM advances WHERE id = ?', [advanceId]); + expect(advance.rows[0].total_reimbursed).toBe(300); + expect(advance.rows[0].status).toBe('partial_verification'); + }); + + // 测试8: 创建结算核销单(退款) + test('创建结算核销单(退款)', async () => { + await db.query( + 'INSERT INTO verifications (advance_id, amount, currency, reason, verification_date, verification_code, status, applicant, advance_code, advance_amount, settlement, settlement_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [advanceId, 600, 'CNY', '结算核销(退款)', '2026-03-25', 'VER-TEST-002', 'executed', '隆林', 'ADV-TEST-001', 1000, 1, 100] + ); + // 更新预支单状态为已完成 + await db.query('UPDATE advances SET status = ? WHERE id = ?', ['completed', advanceId]); + // 更新预支单已核销金额 + await db.query('UPDATE advances SET total_reimbursed = ? WHERE id = ?', [900, advanceId]); + }); + + // 测试9: 检查预支单是否已完结 + test('检查预支单是否已完结', async () => { + const advance = await db.query('SELECT * FROM advances WHERE id = ?', [advanceId]); + expect(advance.rows[0].total_reimbursed).toBe(900); + expect(advance.rows[0].status).toBe('completed'); + }); + + // 测试10: 获取预支核销状态列表 + test('获取预支核销状态列表', async () => { + const result = await db.query('SELECT * FROM advances'); + expect(Array.isArray(result.rows)).toBe(true); + expect(result.rows.length).toBeGreaterThan(0); + }); +}); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/tests/test-category-api.spec.js b/backup_20260327/company-finance-system/backend/tests/test-category-api.spec.js new file mode 100644 index 0000000..268e753 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/tests/test-category-api.spec.js @@ -0,0 +1,321 @@ +/** + * 分类管理API测试 + * TDD: 先写测试,再实现功能 + */ + +const request = require('supertest'); +const express = require('express'); + +// 模拟Express应用 +const app = express(); +app.use(express.json()); + +// 测试数据 +const testCategories = [ + { name: '测试一级分类1', parent_id: null, level: 1 }, + { name: '测试一级分类2', parent_id: null, level: 1 }, +]; + +describe('分类管理API测试', () => { + + // 测试1: 获取分类树 + describe('GET /api/categories/tree', () => { + test('应该返回分类树结构', async () => { + const response = await request(app) + .get('/api/categories/tree') + .expect(200); + + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.data)).toBe(true); + // 每个一级分类应该有children属性 + if (response.body.data.length > 0) { + expect(response.body.data[0]).toHaveProperty('children'); + } + }); + + test('应该支持按层级筛选', async () => { + const response = await request(app) + .get('/api/categories/tree?level=1') + .expect(200); + + expect(response.body.success).toBe(true); + // 返回的都是一级分类 + response.body.data.forEach(cat => { + expect(cat.level).toBe(1); + }); + }); + }); + + // 测试2: 创建分类 + describe('POST /api/categories', () => { + test('应该能创建一级分类', async () => { + const newCategory = { + name: '新材料分类', + parent_id: null, + level: 1, + description: '测试描述' + }; + + const response = await request(app) + .post('/api/categories') + .send(newCategory) + .expect(200); + + expect(response.body.success).toBe(true); + expect(response.body.data).toHaveProperty('id'); + expect(response.body.data.name).toBe(newCategory.name); + }); + + test('应该能在指定父分类下创建二级分类', async () => { + // 先创建父分类 + const parentResponse = await request(app) + .post('/api/categories') + .send({ name: '父分类', parent_id: null, level: 1 }); + + const parentId = parentResponse.body.data.id; + + // 创建子分类 + const childCategory = { + name: '子分类', + parent_id: parentId, + level: 2 + }; + + const response = await request(app) + .post('/api/categories') + .send(childCategory) + .expect(200); + + expect(response.body.success).toBe(true); + expect(response.body.data.parent_id).toBe(parentId); + expect(response.body.data.level).toBe(2); + }); + + test('分类名称不能为空', async () => { + const response = await request(app) + .post('/api/categories') + .send({ name: '', parent_id: null, level: 1 }) + .expect(400); + + expect(response.body.success).toBe(false); + expect(response.body.message).toContain('名称'); + }); + + test('同一父分类下不能有重名子分类', async () => { + // 创建父分类 + const parent = await request(app) + .post('/api/categories') + .send({ name: '唯一父分类', parent_id: null, level: 1 }); + + // 创建第一个子分类 + await request(app) + .post('/api/categories') + .send({ name: '同名子分类', parent_id: parent.body.data.id, level: 2 }); + + // 尝试创建同名子分类 + const response = await request(app) + .post('/api/categories') + .send({ name: '同名子分类', parent_id: parent.body.data.id, level: 2 }) + .expect(400); + + expect(response.body.success).toBe(false); + }); + }); + + // 测试3: 更新分类 + describe('PUT /api/categories/:id', () => { + test('应该能更新分类名称', async () => { + // 先创建分类 + const createResponse = await request(app) + .post('/api/categories') + .send({ name: '原名称', parent_id: null, level: 1 }); + + const categoryId = createResponse.body.data.id; + + // 更新分类 + const response = await request(app) + .put(`/api/categories/${categoryId}`) + .send({ name: '新名称' }) + .expect(200); + + expect(response.body.success).toBe(true); + + // 验证更新 + const getResponse = await request(app) + .get(`/api/categories/${categoryId}`) + .expect(200); + + expect(getResponse.body.data.name).toBe('新名称'); + }); + + test('不能将分类设置为自己的子分类', async () => { + // 创建父分类 + const parent = await request(app) + .post('/api/categories') + .send({ name: '父', parent_id: null, level: 1 }); + + // 创建子分类 + const child = await request(app) + .post('/api/categories') + .send({ name: '子', parent_id: parent.body.data.id, level: 2 }); + + // 尝试将父分类的parent_id设为子分类(循环引用) + const response = await request(app) + .put(`/api/categories/${parent.body.data.id}`) + .send({ parent_id: child.body.data.id }) + .expect(400); + + expect(response.body.success).toBe(false); + }); + }); + + // 测试4: 删除分类 + describe('DELETE /api/categories/:id', () => { + test('应该能删除空分类', async () => { + // 创建分类 + const createResponse = await request(app) + .post('/api/categories') + .send({ name: '待删除', parent_id: null, level: 1 }); + + const categoryId = createResponse.body.data.id; + + // 删除分类 + const response = await request(app) + .delete(`/api/categories/${categoryId}`) + .expect(200); + + expect(response.body.success).toBe(true); + + // 验证已删除 + const getResponse = await request(app) + .get(`/api/categories/${categoryId}`) + .expect(404); + }); + + test('删除一级分类应该级联删除二级分类', async () => { + // 创建父分类 + const parent = await request(app) + .post('/api/categories') + .send({ name: '父', parent_id: null, level: 1 }); + + // 创建子分类 + const child = await request(app) + .post('/api/categories') + .send({ name: '子', parent_id: parent.body.data.id, level: 2 }); + + // 删除父分类 + await request(app) + .delete(`/api/categories/${parent.body.data.id}`) + .expect(200); + + // 验证子分类也被删除 + await request(app) + .get(`/api/categories/${child.body.data.id}`) + .expect(404); + }); + + test('有商品的分类不应该被删除', async () => { + // 创建分类 + const category = await request(app) + .post('/api/categories') + .send({ name: '有商品的分类', parent_id: null, level: 1 }); + + // 在该分类下创建商品(模拟) + // ... 创建商品逻辑 + + // 尝试删除分类 + const response = await request(app) + .delete(`/api/categories/${category.body.data.id}`) + .expect(400); + + expect(response.body.success).toBe(false); + expect(response.body.message).toContain('商品'); + }); + }); + + // 测试5: 批量导入时自动创建分类 + describe('批量导入分类处理', () => { + test('导入时应该自动创建不存在的一级分类', async () => { + const importData = { + products: [ + { + name: '测试商品', + category_level1: '新一级分类', + category_level2: '新二级分类' + } + ] + }; + + const response = await request(app) + .post('/api/products/batch-import') + .send(importData) + .expect(200); + + expect(response.body.success).toBe(true); + + // 验证分类已创建 + const categories = await request(app) + .get('/api/categories/tree') + .expect(200); + + const level1Exists = categories.body.data.some( + cat => cat.name === '新一级分类' + ); + expect(level1Exists).toBe(true); + }); + + test('导入时应该自动创建不存在的二级分类', async () => { + // 先创建一级分类 + await request(app) + .post('/api/categories') + .send({ name: '已有的一级', parent_id: null, level: 1 }); + + // 导入带有新二级分类的商品 + const importData = { + products: [ + { + name: '测试商品2', + category_level1: '已有的一级', + category_level2: '新的二级' + } + ] + }; + + await request(app) + .post('/api/products/batch-import') + .send(importData) + .expect(200); + + // 验证二级分类已创建 + const categories = await request(app) + .get('/api/categories/tree') + .expect(200); + + const parent = categories.body.data.find( + cat => cat.name === '已有的一级' + ); + expect(parent).toBeTruthy(); + expect(parent.children).toBeDefined(); + + const level2Exists = parent.children.some( + child => child.name === '新的二级' + ); + expect(level2Exists).toBe(true); + }); + }); +}); + +// 运行测试 +if (require.main === module) { + const { execSync } = require('child_process'); + try { + execSync('npx jest test-category-api.spec.js --verbose', { + cwd: __dirname, + stdio: 'inherit' + }); + } catch (e) { + process.exit(1); + } +} + +module.exports = { app }; diff --git a/backup_20260327/company-finance-system/backend/tests/test-category-tree.spec.js b/backup_20260327/company-finance-system/backend/tests/test-category-tree.spec.js new file mode 100644 index 0000000..250e048 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/tests/test-category-tree.spec.js @@ -0,0 +1,365 @@ +/** + * 分类树结构测试 + * TDD: 先写测试,再实现功能 + */ + +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); + +// 使用测试数据库 +const TEST_DB_PATH = path.join(__dirname, '../test_company_finance.db'); + +// 测试数据库连接 +function createTestDb() { + return new sqlite3.Database(TEST_DB_PATH); +} + +// 初始化测试数据库 +async function initTestDb(db) { + return new Promise((resolve, reject) => { + // 读取迁移脚本 + const fs = require('fs'); + const migrationScript = fs.readFileSync( + path.join(__dirname, '../migrations/001_create_category_tree.sql'), + 'utf8' + ); + + db.exec(migrationScript, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +// 测试套件 +describe('分类树结构测试', () => { + let db; + + beforeAll(async () => { + db = createTestDb(); + await initTestDb(db); + }); + + afterAll((done) => { + db.close(() => { + // 清理测试数据库 + const fs = require('fs'); + try { + fs.unlinkSync(TEST_DB_PATH); + } catch (e) { + // 忽略删除错误 + } + done(); + }); + }); + + // 测试1: 分类表应该存在 + test('category_tree 表应该存在', async () => { + const result = await new Promise((resolve, reject) => { + db.get( + "SELECT name FROM sqlite_master WHERE type='table' AND name='category_tree'", + (err, row) => { + if (err) reject(err); + else resolve(row); + } + ); + }); + + expect(result).toBeTruthy(); + expect(result.name).toBe('category_tree'); + }); + + // 测试2: 应该能创建一级分类 + test('应该能创建一级分类', async () => { + const result = await new Promise((resolve, reject) => { + db.run( + 'INSERT INTO category_tree (name, parent_id, level, sort_order) VALUES (?, ?, ?, ?)', + ['测试一级分类', null, 1, 1], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + expect(result.id).toBeGreaterThan(0); + }); + + // 测试3: 应该能在一级分类下创建二级分类 + test('应该能创建二级分类', async () => { + // 先创建一级分类 + const parentResult = await new Promise((resolve, reject) => { + db.run( + 'INSERT INTO category_tree (name, parent_id, level, sort_order) VALUES (?, ?, ?, ?)', + ['父分类', null, 1, 1], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + // 再创建二级分类 + const childResult = await new Promise((resolve, reject) => { + db.run( + 'INSERT INTO category_tree (name, parent_id, level, sort_order) VALUES (?, ?, ?, ?)', + ['子分类', parentResult.id, 2, 1], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + expect(childResult.id).toBeGreaterThan(0); + + // 验证父子关系 + const childCategory = await new Promise((resolve, reject) => { + db.get( + 'SELECT * FROM category_tree WHERE id = ?', + [childResult.id], + (err, row) => { + if (err) reject(err); + else resolve(row); + } + ); + }); + + expect(childCategory.parent_id).toBe(parentResult.id); + expect(childCategory.level).toBe(2); + }); + + // 测试4: 应该能获取分类树 + test('应该能获取完整的分类树', async () => { + const categories = await new Promise((resolve, reject) => { + db.all( + 'SELECT * FROM category_tree ORDER BY level, sort_order', + (err, rows) => { + if (err) reject(err); + else resolve(rows); + } + ); + }); + + expect(Array.isArray(categories)).toBe(true); + expect(categories.length).toBeGreaterThan(0); + + // 验证有默认分类数据 + const defaultCategories = categories.filter(c => + ['电杆横担', '电缆电线', '变压器'].includes(c.name) + ); + expect(defaultCategories.length).toBeGreaterThan(0); + }); + + // 测试5: 删除一级分类应该级联删除二级分类 + test('删除一级分类应该级联删除二级分类', async () => { + // 创建测试数据 + const parent = await new Promise((resolve, reject) => { + db.run( + 'INSERT INTO category_tree (name, parent_id, level) VALUES (?, ?, ?)', + ['待删除父分类', null, 1], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + const child = await new Promise((resolve, reject) => { + db.run( + 'INSERT INTO category_tree (name, parent_id, level) VALUES (?, ?, ?)', + ['待删除子分类', parent.id, 2], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + // 删除父分类 + await new Promise((resolve, reject) => { + db.run('DELETE FROM category_tree WHERE id = ?', [parent.id], function(err) { + if (err) reject(err); + else resolve(); + }); + }); + + // 验证子分类也被删除 + const remainingChild = await new Promise((resolve, reject) => { + db.get('SELECT * FROM category_tree WHERE id = ?', [child.id], (err, row) => { + if (err) reject(err); + else resolve(row); + }); + }); + + expect(remainingChild).toBeUndefined(); + }); + + // 测试6: 应该能更新分类 + test('应该能更新分类', async () => { + // 创建分类 + const category = await new Promise((resolve, reject) => { + db.run( + 'INSERT INTO category_tree (name, parent_id, level) VALUES (?, ?, ?)', + ['原名称', null, 1], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + // 更新名称 + await new Promise((resolve, reject) => { + db.run( + 'UPDATE category_tree SET name = ? WHERE id = ?', + ['新名称', category.id], + function(err) { + if (err) reject(err); + else resolve(); + } + ); + }); + + // 验证更新 + const updated = await new Promise((resolve, reject) => { + db.get('SELECT * FROM category_tree WHERE id = ?', [category.id], (err, row) => { + if (err) reject(err); + else resolve(row); + }); + }); + + expect(updated.name).toBe('新名称'); + }); +}); + +// 商品表测试 +describe('商品表测试', () => { + let db; + + beforeAll(async () => { + db = createTestDb(); + await initTestDb(db); + }); + + afterAll((done) => { + db.close(done); + }); + + // 测试1: 商品表应该存在 + test('products 表应该存在', async () => { + const result = await new Promise((resolve, reject) => { + db.get( + "SELECT name FROM sqlite_master WHERE type='table' AND name='products'", + (err, row) => { + if (err) reject(err); + else resolve(row); + } + ); + }); + + expect(result).toBeTruthy(); + expect(result.name).toBe('products'); + }); + + // 测试2: 应该能创建商品 + test('应该能创建商品', async () => { + // 先获取一个分类ID + const category = await new Promise((resolve, reject) => { + db.get('SELECT id FROM category_tree LIMIT 1', (err, row) => { + if (err) reject(err); + else resolve(row); + }); + }); + + const result = await new Promise((resolve, reject) => { + db.run( + `INSERT INTO products ( + name, model, category_id, category_name, unit, + cost_price, price, brand, specification, source, remark + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + '测试商品', 'Model-001', category.id, '测试分类', '件', + 100.00, 150.00, '测试品牌', '规格参数', '中国', '测试备注' + ], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + expect(result.id).toBeGreaterThan(0); + }); + + // 测试3: 商品应该有默认值 + test('商品字段应该有正确的默认值', async () => { + const category = await new Promise((resolve, reject) => { + db.get('SELECT id FROM category_tree LIMIT 1', (err, row) => { + if (err) reject(err); + else resolve(row); + }); + }); + + const result = await new Promise((resolve, reject) => { + db.run( + 'INSERT INTO products (name, category_id) VALUES (?, ?)', + ['最小化商品', category.id], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + const product = await new Promise((resolve, reject) => { + db.get('SELECT * FROM products WHERE id = ?', [result.id], (err, row) => { + if (err) reject(err); + else resolve(row); + }); + }); + + expect(product.unit).toBe('件'); + expect(product.source).toBe('老挝'); + expect(product.status).toBe('active'); + expect(product.stock_quantity).toBe(0); + }); + + // 测试4: cost_price 可以为空 + test('cost_price 应该可以为空', async () => { + const category = await new Promise((resolve, reject) => { + db.get('SELECT id FROM category_tree LIMIT 1', (err, row) => { + if (err) reject(err); + else resolve(row); + }); + }); + + const result = await new Promise((resolve, reject) => { + db.run( + 'INSERT INTO products (name, category_id, cost_price) VALUES (?, ?, ?)', + ['无成本商品', category.id, null], + function(err) { + if (err) reject(err); + else resolve({ id: this.lastID }); + } + ); + }); + + expect(result.id).toBeGreaterThan(0); + }); +}); + +// 运行测试 +if (require.main === module) { + const { execSync } = require('child_process'); + try { + execSync('npx jest test-category-tree.spec.js --verbose', { + cwd: __dirname, + stdio: 'inherit' + }); + } catch (e) { + process.exit(1); + } +} + +module.exports = { createTestDb, initTestDb }; diff --git a/backup_20260327/company-finance-system/backend/tests/test-purchase-api.spec.js b/backup_20260327/company-finance-system/backend/tests/test-purchase-api.spec.js new file mode 100644 index 0000000..45449f6 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/tests/test-purchase-api.spec.js @@ -0,0 +1,156 @@ +const request = require('supertest'); +const app = require('../final-backend'); + +describe('采购付款分离 - API层测试', () => { + describe('采购申请API', () => { + let testPurchaseRequestId; + + test('POST /api/purchase-requests - 应该能创建采购申请', async () => { + const response = await request(app) + .post('/api/purchase-requests') + .send({ + project_id: 1, + applicant: '测试申请人', + request_date: '2026-03-25', + expense_category: 'material', + total_amount: 15000, + items: [ + { + product_name: '测试商品A', + quantity: 10, + unit_price: 1000, + total_price: 10000 + }, + { + product_name: '测试商品B', + quantity: 5, + unit_price: 1000, + total_price: 5000 + } + ] + }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(response.body.data).toHaveProperty('id'); + expect(response.body.data).toHaveProperty('request_code'); + testPurchaseRequestId = response.body.data.id; + }); + + test('GET /api/purchase-requests - 应该能获取采购申请列表', async () => { + const response = await request(app).get('/api/purchase-requests'); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.data)).toBe(true); + }); + + test('GET /api/purchase-requests/:id - 应该能获取采购申请详情', async () => { + const response = await request(app).get(`/api/purchase-requests/${testPurchaseRequestId}`); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(response.body.data).toHaveProperty('items'); + expect(response.body.data.items.length).toBe(2); + }); + + test('POST /api/purchase-requests/:id/submit - 应该能提交采购申请', async () => { + const response = await request(app) + .post(`/api/purchase-requests/${testPurchaseRequestId}/submit`); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('POST /api/purchase-requests/:id/approve - 应该能审批通过采购申请', async () => { + const response = await request(app) + .post(`/api/purchase-requests/${testPurchaseRequestId}/approve`); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('PUT /api/purchase-requests/:id - 应该能更新采购申请', async () => { + const response = await request(app) + .put(`/api/purchase-requests/${testPurchaseRequestId}`) + .send({ + project_id: 1, + applicant: '测试申请人', + request_date: '2026-03-25', + expense_category: 'material', + total_amount: 20000, + remark: '更新备注' + }); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + }); + + describe('库存管理API', () => { + test('GET /api/inventory - 应该能获取库存记录', async () => { + const response = await request(app).get('/api/inventory'); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.data)).toBe(true); + }); + + test('GET /api/inventory/summary - 应该能获取库存汇总', async () => { + const response = await request(app).get('/api/inventory/summary'); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.data)).toBe(true); + }); + + test('POST /api/inventory/out - 应该能创建出库记录', async () => { + const response = await request(app) + .post('/api/inventory/out') + .send({ + project_id: 1, + product_id: 1, + quantity: 10, + operator: '测试操作员' + }); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + }); + + describe('项目成本统计API', () => { + test('GET /api/projects/:id/cost-summary - 应该能获取项目成本统计', async () => { + const response = await request(app).get('/api/projects/1/cost-summary'); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(response.body.data).toHaveProperty('project_name'); + expect(response.body.data).toHaveProperty('purchase_cost'); + expect(response.body.data).toHaveProperty('payment_cost'); + expect(response.body.data).toHaveProperty('total_cost'); + expect(response.body.data).toHaveProperty('profit'); + }); + }); + + describe('采购申请删除API', () => { + let tempPurchaseRequestId; + + beforeAll(async () => { + const response = await request(app) + .post('/api/purchase-requests') + .send({ + project_id: 1, + applicant: '临时测试', + request_date: '2026-03-25', + expense_category: 'material', + total_amount: 1000 + }); + tempPurchaseRequestId = response.body.data.id; + }); + + test('DELETE /api/purchase-requests/:id - 应该能删除采购申请', async () => { + const response = await request(app) + .delete(`/api/purchase-requests/${tempPurchaseRequestId}`); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('GET /api/purchase-requests/:id - 删除后应该返回404', async () => { + const response = await request(app) + .get(`/api/purchase-requests/${tempPurchaseRequestId}`); + expect(response.status).toBe(404); + }); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/tests/test-purchase-database.spec.js b/backup_20260327/company-finance-system/backend/tests/test-purchase-database.spec.js new file mode 100644 index 0000000..43fb4fe --- /dev/null +++ b/backup_20260327/company-finance-system/backend/tests/test-purchase-database.spec.js @@ -0,0 +1,100 @@ +const db = require('../db-sqlite'); +const path = require('path'); + +describe('采购付款分离 - 数据库层测试', () => { + beforeAll(async () => { + await new Promise(resolve => setTimeout(resolve, 1000)); + }); + + describe('采购申请表 (purchase_requests)', () => { + test('应该能创建采购申请', async () => { + const result = await db.query(` + INSERT INTO purchase_requests + (request_code, project_id, applicant, request_date, expense_category, total_amount, status) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, ['PUR-TEST-001', 1, '测试申请人', '2026-03-25', 'material', 10000.00, 'pending']); + expect(result.changes).toBe(1); + }); + + test('采购申请编号应该唯一', async () => { + try { + await db.query(` + INSERT INTO purchase_requests + (request_code, project_id, applicant, request_date, expense_category, total_amount, status) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, ['PUR-TEST-001', 1, '测试申请人2', '2026-03-25', 'material', 20000.00, 'pending']); + fail('应该抛出唯一约束错误'); + } catch (error) { + expect(error.message).toContain('UNIQUE constraint failed'); + } + }); + + test('应该能查询采购申请', async () => { + const result = await db.query('SELECT * FROM purchase_requests WHERE request_code = ?', ['PUR-TEST-001']); + expect(result.length).toBeGreaterThan(0); + expect(result[0].request_code).toBe('PUR-TEST-001'); + }); + }); + + describe('采购明细表 (purchase_request_items)', () => { + let purchaseRequestId; + + beforeAll(async () => { + const result = await db.query(` + INSERT INTO purchase_requests + (request_code, project_id, applicant, request_date, expense_category, total_amount, status) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, ['PUR-TEST-002', 1, '测试申请人', '2026-03-25', 'material', 5000.00, 'pending']); + purchaseRequestId = result.lastID; + }); + + test('应该能创建采购明细', async () => { + const result = await db.query(` + INSERT INTO purchase_request_items + (purchase_request_id, product_name, quantity, unit_price, total_price) + VALUES (?, ?, ?, ?, ?) + `, [purchaseRequestId, '测试商品', 10, 500.00, 5000.00]); + expect(result.changes).toBe(1); + }); + + test('应该能查询采购明细', async () => { + const result = await db.query('SELECT * FROM purchase_request_items WHERE purchase_request_id = ?', [purchaseRequestId]); + expect(result.length).toBeGreaterThan(0); + expect(result[0].product_name).toBe('测试商品'); + }); + }); + + describe('库存记录表 (inventory_records)', () => { + test('应该能创建库存入库记录', async () => { + const result = await db.query(` + INSERT INTO inventory_records + (record_type, product_id, quantity, unit_price, total_amount, record_date, operator) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, ['in', 1, 100, 10.00, 1000.00, '2026-03-25', '测试操作员']); + expect(result.changes).toBe(1); + }); + + test('应该能创建库存出库记录', async () => { + const result = await db.query(` + INSERT INTO inventory_records + (record_type, project_id, product_id, quantity, unit_price, total_amount, record_date, operator) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `, ['out', 1, 1, 50, 10.00, 500.00, '2026-03-25', '测试操作员']); + expect(result.changes).toBe(1); + }); + }); + + describe('付款申请表修改', () => { + test('付款申请表应该有purchase_request_id字段', async () => { + const result = await db.query("PRAGMA table_info(payment_requests)"); + const hasPurchaseRequestId = result.some(col => col.name === 'purchase_request_id'); + expect(hasPurchaseRequestId).toBe(true); + }); + + test('付款申请表应该有payment_type字段', async () => { + const result = await db.query("PRAGMA table_info(payment_requests)"); + const hasPaymentType = result.some(col => col.name === 'payment_type'); + expect(hasPaymentType).toBe(true); + }); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/tests/test-verification-flow.spec.js b/backup_20260327/company-finance-system/backend/tests/test-verification-flow.spec.js new file mode 100644 index 0000000..a0fb18d --- /dev/null +++ b/backup_20260327/company-finance-system/backend/tests/test-verification-flow.spec.js @@ -0,0 +1,183 @@ +const request = require('supertest'); +const app = require('../final-backend'); +const db = require('../db-sqlite'); + +describe('核销申请流程测试', () => { + let server; + let advanceId; + let verificationId1; + let verificationId2; + + beforeAll(async () => { + server = app.listen(3006); + + // 清理测试数据 + await db.query('DELETE FROM verifications WHERE 1=1'); + await db.query('DELETE FROM advances WHERE 1=1'); + + // 创建测试预支申请 + const advanceResult = await db.query( + 'INSERT INTO advances (user_id, project_id, amount, currency, reason, advance_date, advance_code, status, applicant, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [1, null, 10000, 'CNY', '测试预支', '2026-03-25', 'ADV-TEST', 'executed', '测试用户', '[]'] + ); + advanceId = advanceResult.lastID; + + // 更新预支单状态为待核销 + await db.query('UPDATE advances SET status = ? WHERE id = ?', ['pending_verification', advanceId]); + }); + + afterAll(async () => { + server.close(); + }); + + describe('预支单状态管理', () => { + test('预支单初始状态应该是待核销', async () => { + const result = await db.query('SELECT status FROM advances WHERE id = ?', [advanceId]); + expect(result.rows[0].status).toBe('pending_verification'); + }); + }); + + describe('核销申请创建', () => { + test('创建第一张核销申请(非结算)', async () => { + const response = await request(server) + .post('/api/verifications') + .send({ + verification_date: '2026-03-25', + advance_id: advanceId, + advance_code: 'ADV-TEST', + advance_amount: 10000, + currency: 'CNY', + reason: '测试核销1', + detail_items: [{ description: '测试费用1', amount: 6000, category: 'accommodation' }], + attachments: [], + applicant: '测试用户', + expense_type: 'company', + project_id: null, + settlement: false + }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + verificationId1 = response.body.data.id; + + // 检查预支单已核销金额是否更新 + const advanceResult = await db.query('SELECT total_reimbursed FROM advances WHERE id = ?', [advanceId]); + expect(advanceResult.rows[0].total_reimbursed).toBe(6000); + }); + + test('创建第二张核销申请(结算)', async () => { + const response = await request(server) + .post('/api/verifications') + .send({ + verification_date: '2026-03-25', + advance_id: advanceId, + advance_code: 'ADV-TEST', + advance_amount: 10000, + currency: 'CNY', + reason: '测试核销2', + detail_items: [{ description: '测试费用2', amount: 5000, category: 'food' }], + attachments: [], + applicant: '测试用户', + expense_type: 'company', + project_id: null, + settlement: true, + settlement_amount: -1000 + }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + verificationId2 = response.body.data.id; + + // 检查预支单已核销金额是否更新 + const advanceResult = await db.query('SELECT total_reimbursed FROM advances WHERE id = ?', [advanceId]); + expect(advanceResult.rows[0].total_reimbursed).toBe(11000); + }); + }); + + describe('核销申请审批和执行', () => { + test('审批第一张核销申请', async () => { + const response = await request(server) + .post(`/api/verifications/${verificationId1}/approve`) + .send({ remark: '批准' }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('执行第一张核销申请(非结算)', async () => { + const response = await request(server) + .post(`/api/verifications/${verificationId1}/execute`) + .send({ execute_method: '银行转账', voucher_no: 'VOUCHER-001' }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + + // 检查预支单状态应该变为部分核销 + const advanceResult = await db.query('SELECT status FROM advances WHERE id = ?', [advanceId]); + expect(advanceResult.rows[0].status).toBe('partial_verification'); + }); + + test('审批第二张核销申请', async () => { + const response = await request(server) + .post(`/api/verifications/${verificationId2}/approve`) + .send({ remark: '批准' }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + test('执行第二张核销申请(结算)', async () => { + const response = await request(server) + .post(`/api/verifications/${verificationId2}/execute`) + .send({ execute_method: '银行转账', voucher_no: 'VOUCHER-002' }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + + // 检查预支单状态应该变为已完成 + const advanceResult = await db.query('SELECT status FROM advances WHERE id = ?', [advanceId]); + expect(advanceResult.rows[0].status).toBe('completed'); + }); + }); + + describe('核销申请退回', () => { + test('创建测试核销申请', async () => { + const response = await request(server) + .post('/api/verifications') + .send({ + verification_date: '2026-03-25', + advance_id: advanceId, + advance_code: 'ADV-TEST', + advance_amount: 10000, + currency: 'CNY', + reason: '测试退回', + detail_items: [{ description: '测试费用', amount: 2000, category: 'transportation' }], + attachments: [], + applicant: '测试用户', + expense_type: 'company', + project_id: null, + settlement: false + }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + const testVerificationId = response.body.data.id; + + // 检查预支单已核销金额是否更新 + let advanceResult = await db.query('SELECT total_reimbursed FROM advances WHERE id = ?', [advanceId]); + expect(advanceResult.rows[0].total_reimbursed).toBe(13000); + + // 退回核销申请 + const rejectResponse = await request(server) + .post(`/api/verifications/${testVerificationId}/reject`) + .send({ remark: '退回' }); + + expect(rejectResponse.status).toBe(200); + expect(rejectResponse.body.success).toBe(true); + + // 检查预支单已核销金额是否恢复 + advanceResult = await db.query('SELECT total_reimbursed FROM advances WHERE id = ?', [advanceId]); + expect(advanceResult.rows[0].total_reimbursed).toBe(11000); + }); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/update-executions-table.js b/backup_20260327/company-finance-system/backend/update-executions-table.js new file mode 100644 index 0000000..18ffd39 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/update-executions-table.js @@ -0,0 +1,42 @@ +const sqlite3 = require('sqlite3').verbose(); +const db = new sqlite3.Database('company_finance.db'); + +// 更新执行记录表,添加 voucher_files 字段 +db.serialize(() => { + console.log('开始更新执行记录表...'); + + // 检查 voucher_files 字段是否存在 + db.all("PRAGMA table_info(executions)", (err, rows) => { + if (err) { + console.error('获取表结构失败:', err.message); + db.close(); + return; + } + + const hasVoucherFiles = rows.some(row => row.name === 'voucher_files'); + + if (!hasVoucherFiles) { + // 添加 voucher_files 字段 + db.run(`ALTER TABLE executions ADD COLUMN voucher_files TEXT`, (err) => { + if (err) { + console.error('添加 voucher_files 字段失败:', err.message); + } else { + console.log('✓ voucher_files 字段添加成功'); + } + }); + } else { + console.log('✓ voucher_files 字段已存在'); + } + + // 关闭数据库连接 + setTimeout(() => { + db.close((err) => { + if (err) { + console.error('关闭数据库失败:', err.message); + } else { + console.log('数据库连接已关闭'); + } + }); + }, 100); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/update-payment-requests-table.js b/backup_20260327/company-finance-system/backend/update-payment-requests-table.js new file mode 100644 index 0000000..675eb6c --- /dev/null +++ b/backup_20260327/company-finance-system/backend/update-payment-requests-table.js @@ -0,0 +1,56 @@ +const sqlite3 = require('sqlite3').verbose(); +const db = new sqlite3.Database('company_finance.db'); + +// 更新付款申请表,添加新字段 +db.serialize(() => { + console.log('开始更新付款申请表...'); + + // 检查字段是否存在 + db.all("PRAGMA table_info(payment_requests)", (err, rows) => { + if (err) { + console.error('获取表结构失败:', err.message); + db.close(); + return; + } + + const existingColumns = rows.map(row => row.name); + + // 需要添加的字段 + const columnsToAdd = [ + { name: 'payee_type', type: 'TEXT' }, // 收款单位类型:subcontractor/supplier/customer/other + { name: 'payee_id', type: 'INTEGER' }, // 收款单位ID(当类型为subcontractor/supplier/customer时) + { name: 'expense_type', type: 'TEXT' }, // 支出类型:company/project + { name: 'expense_category', type: 'TEXT' }, // 支出分类 + { name: 'project_id', type: 'INTEGER' } // 关联项目ID(当expense_type为project时) + ]; + + let addedCount = 0; + + columnsToAdd.forEach(column => { + if (!existingColumns.includes(column.name)) { + db.run(`ALTER TABLE payment_requests ADD COLUMN ${column.name} ${column.type}`, (err) => { + if (err) { + console.error(`添加 ${column.name} 字段失败:`, err.message); + } else { + console.log(`✓ ${column.name} 字段添加成功`); + addedCount++; + } + }); + } else { + console.log(`✓ ${column.name} 字段已存在`); + } + }); + + // 关闭数据库连接 + setTimeout(() => { + console.log(`\n共添加 ${addedCount} 个新字段`); + db.close((err) => { + if (err) { + console.error('关闭数据库失败:', err.message); + } else { + console.log('数据库连接已关闭'); + } + }); + }, 500); + }); +}); diff --git a/backup_20260327/company-finance-system/backend/update-products.js b/backup_20260327/company-finance-system/backend/update-products.js new file mode 100644 index 0000000..5979c28 --- /dev/null +++ b/backup_20260327/company-finance-system/backend/update-products.js @@ -0,0 +1,29 @@ +const db = require('./db-sqlite'); + +// 更新商品数据,将code字段中的原始型号信息提取出来存储到model字段 +async function updateProducts() { + try { + // 获取所有商品 + const result = await db.query('SELECT id, code FROM products'); + const products = result.rows; + + // 遍历商品,更新model字段 + for (const product of products) { + // 提取code字段中第一个下划线之前的部分作为model + const underscoreIndex = product.code.indexOf('_'); + if (underscoreIndex > 0) { + const model = product.code.substring(0, underscoreIndex); + await db.query('UPDATE products SET model = ? WHERE id = ?', [model, product.id]); + } + } + + console.log('商品数据更新成功'); + } catch (error) { + console.error('更新商品数据失败:', error); + } finally { + // 关闭数据库连接 + db.db.close(); + } +} + +updateProducts(); \ No newline at end of file diff --git a/backup_20260327/company-finance-system/backend/商品导入模板.xlsx b/backup_20260327/company-finance-system/backend/商品导入模板.xlsx new file mode 100644 index 0000000..3bc7724 Binary files /dev/null and b/backup_20260327/company-finance-system/backend/商品导入模板.xlsx differ diff --git a/backup_20260327/company-finance-system/backend/商品导入模板_更新.xlsx b/backup_20260327/company-finance-system/backend/商品导入模板_更新.xlsx new file mode 100644 index 0000000..7dab9a3 Binary files /dev/null and b/backup_20260327/company-finance-system/backend/商品导入模板_更新.xlsx differ diff --git a/backup_20260327/company-finance-system/company-finance-frontend/index.html b/backup_20260327/company-finance-system/company-finance-frontend/index.html new file mode 100644 index 0000000..afbdd93 --- /dev/null +++ b/backup_20260327/company-finance-system/company-finance-frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 项目管理 - 轻远电力 + + +
+ + + diff --git a/backup_20260327/company-finance-system/company-finance-frontend/package.json b/backup_20260327/company-finance-system/company-finance-frontend/package.json new file mode 100644 index 0000000..abc21c0 --- /dev/null +++ b/backup_20260327/company-finance-system/company-finance-frontend/package.json @@ -0,0 +1,36 @@ +{ + "name": "company-finance-frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.14.2", + "axios": "^1.4.0", + "antd": "^5.7.0", + "@ant-design/icons": "^5.2.6", + "dayjs": "^1.11.9", + "i18next": "^23.2.11", + "react-i18next": "^13.0.0", + "zustand": "^4.4.1" + }, + "devDependencies": { + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react": "^4.0.0", + "eslint": "^8.45.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "typescript": "^5.1.6", + "vite": "^4.4.0" + } +} diff --git a/backup_20260327/company-finance-system/company-finance-frontend/public/vite.svg b/backup_20260327/company-finance-system/company-finance-frontend/public/vite.svg new file mode 100644 index 0000000..4dc88f3 --- /dev/null +++ b/backup_20260327/company-finance-system/company-finance-frontend/public/vite.svg @@ -0,0 +1 @@ + diff --git a/backup_20260327/company-finance-system/company-finance-frontend/src/App.tsx b/backup_20260327/company-finance-system/company-finance-frontend/src/App.tsx new file mode 100644 index 0000000..3f73409 --- /dev/null +++ b/backup_20260327/company-finance-system/company-finance-frontend/src/App.tsx @@ -0,0 +1,82 @@ +import React, { Suspense } from 'react' +import { Routes, Route, Navigate } from 'react-router-dom' +import { Spin, Layout } from 'antd' + +const { Content } = Layout + +// 懒加载页面组件 +const ProjectsPage = React.lazy(() => import('./pages/projects/ProjectsPage')) +const ProjectDetail = React.lazy(() => import('./pages/projects/ProjectDetail')) + +// 施工管理页面 +const ConstructionList = React.lazy(() => import('./pages/construction/ConstructionList')) +const ConstructionLog = React.lazy(() => import('./pages/construction/ConstructionLog')) +const ConstructionMilestones = React.lazy(() => import('./pages/construction/ConstructionMilestones')) +const BudgetProjectList = React.lazy(() => import('./pages/budget/BudgetProjectList')) +const BudgetProjectCreate = React.lazy(() => import('./pages/budget/BudgetProjectCreate')) + +// 加载中组件 +const LoadingFallback = () => ( +
+ +
+) + +// 简单布局 +const SimpleLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + + {children} + + +) + +const App: React.FC = () => { + return ( + + }> + + {/* 项目管理路由 */} + } /> + } /> + + {/* 预算报价路由 */} + } /> + } /> + } /> + + {/* 施工管理路由 */} + } /> + } /> + } /> + + {/* 默认重定向到项目列表 */} + } /> + + {/* 404页面 */} + +

404 - 页面未找到

+

您访问的页面不存在或已被移除。

+ 返回首页 + + } /> +
+
+
+ ) +} + +export default App diff --git a/backup_20260327/company-finance-system/company-finance-frontend/src/components/FileUpload.tsx b/backup_20260327/company-finance-system/company-finance-frontend/src/components/FileUpload.tsx new file mode 100644 index 0000000..f1a17e5 --- /dev/null +++ b/backup_20260327/company-finance-system/company-finance-frontend/src/components/FileUpload.tsx @@ -0,0 +1,129 @@ +import React, { useState } from 'react'; +import { Upload, Button, message, Image, Spin } from 'antd'; +import { UploadOutlined, FileOutlined, DeleteOutlined } from '@ant-design/icons'; +import type { UploadFile } from 'antd/es/upload/interface'; + +interface FileUploadProps { + value?: string; + onChange?: (url: string) => void; + accept?: string; + maxSize?: number; // MB + disabled?: boolean; +} + +const FileUpload: React.FC = ({ + value, + onChange, + accept = '.pdf,.doc,.docx,.jpg,.jpeg,.png,.xlsx,.xls', + maxSize = 10, + disabled = false, +}) => { + const [loading, setLoading] = useState(false); + const [fileList, setFileList] = useState([]); + + const beforeUpload = (file: File) => { + const isLt = file.size / 1024 / 1024 < maxSize; + if (!isLt) { + message.error(`文件大小不能超过 ${maxSize}MB`); + return false; + } + return true; + }; + + const handleUpload = async (options: any) => { + const { file, onSuccess, onError } = options; + setLoading(true); + + const formData = new FormData(); + formData.append('file', file); + + try { + const response = await fetch('/api/upload', { + method: 'POST', + body: formData, + }); + + const result = await response.json(); + + if (result.success) { + message.success('上传成功'); + onChange?.(result.data.url); + onSuccess(result.data, file); + } else { + message.error(result.error || '上传失败'); + onError?.(new Error(result.error)); + } + } catch (error: any) { + message.error('上传失败'); + onError?.(error); + } finally { + setLoading(false); + } + }; + + const handleRemove = () => { + onChange?.(''); + setFileList([]); + }; + + // 判断文件类型 + const getFileType = (url: string) => { + const ext = url.split('.').pop()?.toLowerCase(); + if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext || '')) { + return 'image'; + } + return 'file'; + }; + + return ( +
+ {value ? ( +
+ {getFileType(value) === 'image' ? ( + + ) : ( +
+ +
+ )} + + {!disabled && ( + + )} +
+ ) : ( + + + + )} +
+ ); +}; + +export default FileUpload; diff --git a/backup_20260327/company-finance-system/company-finance-frontend/src/index.css b/backup_20260327/company-finance-system/company-finance-frontend/src/index.css new file mode 100644 index 0000000..1d03ac5 --- /dev/null +++ b/backup_20260327/company-finance-system/company-finance-frontend/src/index.css @@ -0,0 +1,39 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#root { + min-height: 100vh; +} + +/* 移动端适配 */ +@media (max-width: 768px) { + .ant-card { + margin: 8px; + } + + .ant-table { + font-size: 12px; + } + + .ant-descriptions-bordered .ant-descriptions-item-label { + background-color: #fafafa; + } +} + +/* 打印样式 */ +@media print { + .ant-btn { + display: none !important; + } +} diff --git a/backup_20260327/company-finance-system/company-finance-frontend/src/main.tsx b/backup_20260327/company-finance-system/company-finance-frontend/src/main.tsx new file mode 100644 index 0000000..5a57638 --- /dev/null +++ b/backup_20260327/company-finance-system/company-finance-frontend/src/main.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' +import { ConfigProvider } from 'antd' +import zhCN from 'antd/locale/zh_CN' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + , +) diff --git a/backup_20260327/company-finance-system/company-finance-frontend/src/pages/AdvanceList.tsx b/backup_20260327/company-finance-system/company-finance-frontend/src/pages/AdvanceList.tsx new file mode 100644 index 0000000..28d292b --- /dev/null +++ b/backup_20260327/company-finance-system/company-finance-frontend/src/pages/AdvanceList.tsx @@ -0,0 +1,231 @@ +import React, { useState, useEffect } from 'react'; +import { Table, Button, Card, Space, Tag, Modal, Form, Input, InputNumber, Select, DatePicker, message } from 'antd'; +import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'; +import axios from 'axios'; +import dayjs from 'dayjs'; + +const { Option } = Select; +const { TextArea } = Input; + +const AdvanceList: React.FC = () => { + const [advances, setAdvances] = useState([]); + const [projects, setProjects] = useState([]); + const [loading, setLoading] = useState(false); + const [modalVisible, setModalVisible] = useState(false); + const [editingId, setEditingId] = useState(null); + const [form] = Form.useForm(); + const [isMobile, setIsMobile] = useState(false); + const [exchangeRates, setExchangeRates] = useState>({}); + + useEffect(() => { + const checkMobile = () => setIsMobile(window.innerWidth <= 768); + checkMobile(); + window.addEventListener('resize', checkMobile); + return () => window.removeEventListener('resize', checkMobile); + }, []); + + useEffect(() => { + fetchAdvances(); + fetchProjects(); + fetchExchangeRates(); + }, []); + + const fetchAdvances = async () => { + setLoading(true); + try { + const res = await axios.get('/api/advances'); + if (res.data.success) setAdvances(res.data.data); + } catch (error) { + message.error('获取预支列表失败'); + } finally { + setLoading(false); + } + }; + + const fetchProjects = async () => { + try { + const res = await axios.get('/api/projects'); + if (res.data.success) setProjects(res.data.data); + } catch (error) {} + }; + + const fetchExchangeRates = async () => { + try { + const res = await axios.get('/api/exchange-rates/latest'); + if (res.data.success) { + const rates: Record = {}; + Object.keys(res.data.data).forEach(key => { + rates[key] = parseFloat(res.data.data[key]) || 1; + }); + setExchangeRates(rates); + } + } catch (error) {} + }; + + const handleCreate = () => { + setEditingId(null); + form.resetFields(); + setModalVisible(true); + }; + + const handleEdit = (record: any) => { + setEditingId(record.id); + form.setFieldsValue({ + ...record, + advance_date: record.advance_date ? dayjs(record.advance_date) : null, + }); + setModalVisible(true); + }; + + const handleDelete = async (id: number) => { + try { + await axios.delete('/api/advances/' + id); + message.success('删除成功'); + fetchAdvances(); + } catch (error) { + message.error('删除失败'); + } + }; + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + const data = { + ...values, + advance_date: values.advance_date?.format('YYYY-MM-DD'), + amount_cny: values.currency === 'CNY' ? values.amount : convertToCNY(values.amount, values.currency), + }; + if (editingId) { + await axios.put('/api/advances/' + editingId, data); + message.success('更新成功'); + } else { + await axios.post('/api/advances', data); + message.success('创建成功'); + } + setModalVisible(false); + fetchAdvances(); + } catch (error) { + message.error('操作失败'); + } + }; + + // 汇率换算 - 将外币转换为人民币 + const convertToCNY = (amount: number, currency: string): number => { + if (currency === 'CNY') return amount; + // 外币转人民币:需要知道 1外币 = ?人民币 + // 数据库存的是 CNY_XXX,即 1人民币 = ?外币 + // 所以 1外币 = 1/rate 人民币 + const rateKey = 'CNY_' + currency; + const rate = exchangeRates[rateKey] || 1; + return amount / rate; + }; + + // 监听金额和币种变化 + const amount = Form.useWatch('amount', form); + const currency = Form.useWatch('currency', form); + const expenseType = Form.useWatch('expense_type', form); + const amountCNY = amount && currency ? convertToCNY(amount, currency) : 0; + + const getStatusTag = (status: string) => { + const statusMap: Record = { + pending: { color: 'processing', text: '待审批' }, + approved: { color: 'success', text: '已批准' }, + rejected: { color: 'error', text: '已拒绝' }, + settled: { color: 'blue', text: '已核销' }, + }; + const config = statusMap[status] || { color: 'default', text: status }; + return {config.text}; + }; + + const formatAmount = (amount: number, currency: string = 'CNY') => { + const symbols: Record = { CNY: '¥', USD: '$', LAK: '₭', THB: '฿' }; + return (symbols[currency] || '¥') + (amount || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + }; + + const columns = [ + { title: '预支编号', dataIndex: 'advance_code', key: 'advance_code', width: 120 }, + { title: '预支日期', dataIndex: 'advance_date', key: 'advance_date', width: 100 }, + { title: '支出类型', dataIndex: 'expense_type', key: 'expense_type', render: (v: string) => v === 'project' ? '项目支出' : '公用支出' }, + { title: '项目', dataIndex: 'project_name', key: 'project_name', render: (v: string) => v || '-' }, + { title: '金额', dataIndex: 'amount', key: 'amount', render: (v: number, r: any) => formatAmount(v, r.currency) }, + { title: '等价人民币', dataIndex: 'amount_cny', key: 'amount_cny', render: (v: number) => {formatAmount(v)} }, + { title: '事由', dataIndex: 'reason', key: 'reason', ellipsis: true }, + { title: '状态', dataIndex: 'status', key: 'status', render: (status: string) => getStatusTag(status) }, + { title: '操作', key: 'action', width: 180, render: (_: any, record: any) => ( + + + + + )} + ]; + + return ( +
+
+

预支管理

+

管理员工预支申请

+
+ + } onClick={handleCreate}>新建预支}> + + + + setModalVisible(false)} width={600}> +
+ + + + + + + + + {expenseType === 'project' && ( + + + + )} + + + + + + + + v ? v.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : ''} + parser={v => v ? v.replace(/,/g, '') : ''} + placeholder="输入金额" + /> + + + {amountCNY > 0 && ( +
+ 等价人民币:¥ {amountCNY.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +
+ )} +
+ + +