diff --git a/backend/package-lock.json b/backend/package-lock.json index e69cdad..c146dc4 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.0.0", + "exceljs": "^4.4.0", "express": "^4.18.0", "express-session": "^1.17.3", "keycloak-connect": "^23.0.7", @@ -45,6 +46,47 @@ "node": ">=12" } }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmmirror.com/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -287,6 +329,7 @@ "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -417,6 +460,59 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/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/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -455,6 +551,12 @@ "node": ">=4" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", @@ -487,7 +589,26 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/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/basic-ftp": { @@ -506,6 +627,28 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", "license": "MIT" }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -519,6 +662,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/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/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/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/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.2.tgz", @@ -568,7 +742,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -594,12 +767,35 @@ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", "license": "MIT" }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/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-crc32": { "version": "0.2.13", "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "license": "MIT", - "optional": true, "engines": { "node": "*" } @@ -610,6 +806,23 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -659,6 +872,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -727,11 +952,39 @@ "license": "MIT", "optional": true }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/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/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -804,6 +1057,45 @@ "node": ">= 0.10" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/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/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -821,6 +1113,12 @@ "node": ">= 14" } }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -935,6 +1233,15 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", @@ -970,7 +1277,6 @@ "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", - "optional": true, "dependencies": { "once": "^1.4.0" } @@ -1092,6 +1398,40 @@ "node": ">= 0.6" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/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/express": { "version": "4.21.2", "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", @@ -1223,6 +1563,19 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -1335,6 +1688,18 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1350,6 +1715,22 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", @@ -1436,6 +1817,27 @@ "node": ">= 14" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1461,6 +1863,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1588,6 +1996,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/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-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -1595,6 +2023,23 @@ "dev": true, "license": "ISC" }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", @@ -1710,6 +2155,18 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/jwk-to-pem": { "version": "2.0.7", "resolved": "https://registry.npmmirror.com/jwk-to-pem/-/jwk-to-pem-2.0.7.tgz", @@ -1736,6 +2193,112 @@ "chromedriver": "latest" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz", @@ -1858,7 +2421,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2013,7 +2575,6 @@ "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" @@ -2066,7 +2627,6 @@ "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", - "optional": true, "dependencies": { "wrappy": "1" } @@ -2105,6 +2665,12 @@ "node": ">= 14" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", @@ -2114,6 +2680,15 @@ "node": ">= 0.8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -2147,6 +2722,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/engines": "5.22.0" }, @@ -2293,6 +2869,36 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2306,6 +2912,19 @@ "node": ">=8.10.0" } }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2332,6 +2951,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -2413,6 +3044,12 @@ "node": ">= 0.8.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -2609,6 +3246,36 @@ "node": ">=4" } }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/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-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/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/tcp-port-used": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/tcp-port-used/-/tcp-port-used-1.0.2.tgz", @@ -2645,6 +3312,15 @@ "license": "MIT", "optional": true }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2677,6 +3353,15 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -2753,6 +3438,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2796,6 +3482,24 @@ "node": ">= 0.8" } }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmmirror.com/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2811,6 +3515,15 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/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", @@ -2831,8 +3544,13 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC", - "optional": true + "license": "ISC" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" }, "node_modules/xtend": { "version": "4.0.2", @@ -2863,6 +3581,55 @@ "engines": { "node": ">=6" } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/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" + } } } } diff --git a/backend/package.json b/backend/package.json index 1d5e1e0..cd3396c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,7 +1,7 @@ { "name": "backend", "version": "1.0.0", - "description": "摄影设备借还系统后端", + "description": "固定资产管理系统后端", "main": "dist/index.js", "scripts": { "dev": "nodemon --exec ts-node src/index.ts", diff --git a/backend/prisma/migrations/20260310_add_asset_type_short_name/migration.sql b/backend/prisma/migrations/20260310_add_asset_type_short_name/migration.sql new file mode 100644 index 0000000..4268d1d --- /dev/null +++ b/backend/prisma/migrations/20260310_add_asset_type_short_name/migration.sql @@ -0,0 +1,5 @@ +ALTER TABLE `asset_type` + ADD COLUMN `short_name` VARCHAR(20) NULL; + +CREATE UNIQUE INDEX `asset_type_short_name_key` + ON `asset_type`(`short_name`); diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..7dec767 --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1 @@ +provider = "mysql" diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 9d1b17b..2f56ff9 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -13,6 +13,7 @@ datasource db { model AssetType { id Int @id @default(autoincrement()) name String @unique @db.VarChar(50) + short_name String? @unique @db.VarChar(20) status String @default("active") @db.VarChar(20) can_be_parent Boolean @default(false) // 可作为父资产 can_be_child Boolean @default(false) // 可作为子资产 diff --git a/backend/src/controllers/assetTypeController.ts b/backend/src/controllers/assetTypeController.ts index c1a79ba..9e04005 100644 --- a/backend/src/controllers/assetTypeController.ts +++ b/backend/src/controllers/assetTypeController.ts @@ -5,6 +5,7 @@ import { successResponse, errorResponse } from '../utils/response' const baseSelect = { id: true, name: true, + short_name: true, status: true, can_be_parent: true, can_be_child: true, @@ -12,6 +13,16 @@ const baseSelect = { updated_at: true } +const normalizeShortName = (shortName?: string | null) => { + if (shortName === undefined) return undefined + const value = shortName?.trim().toUpperCase() || '' + if (!value) return null + if (!/^[A-Z0-9]{2,20}$/.test(value)) { + throw new Error('简称只能包含 2-20 位大写字母或数字') + } + return value +} + // 获取启用的资产类型列表(下拉选项) export const getAssetTypeOptions = async (_req: Request, res: Response) => { try { @@ -37,7 +48,10 @@ export const getAssetTypeList = async (req: Request, res: Response) => { where.status = status } if (keyword) { - where.name = { contains: keyword as string } + where.OR = [ + { name: { contains: keyword as string } }, + { short_name: { contains: keyword as string } } + ] } const skip = (Number(page) - 1) * Number(pageSize) @@ -76,8 +90,9 @@ export const getAssetTypeList = async (req: Request, res: Response) => { // 创建资产类型 export const createAssetType = async (req: Request, res: Response) => { try { - const { name, can_be_parent, can_be_child } = req.body as { + const { name, short_name, can_be_parent, can_be_child } = req.body as { name?: string + short_name?: string can_be_parent?: boolean can_be_child?: boolean } @@ -86,9 +101,12 @@ export const createAssetType = async (req: Request, res: Response) => { return errorResponse(res, '类型名称不能为空', 422) } + const normalizedShortName = normalizeShortName(short_name) + const assetType = await prisma.assetType.create({ data: { name: name.trim(), + short_name: normalizedShortName, can_be_parent: can_be_parent ?? false, can_be_child: can_be_child ?? false }, @@ -98,7 +116,7 @@ export const createAssetType = async (req: Request, res: Response) => { successResponse(res, assetType, '新增资产类型成功') } catch (error: any) { if (error.code === 'P2002') { - return errorResponse(res, '类型名称已存在') + return errorResponse(res, '类型名称或简称已存在') } errorResponse(res, error.message || '新增资产类型失败') } @@ -108,8 +126,9 @@ export const createAssetType = async (req: Request, res: Response) => { export const updateAssetType = async (req: Request, res: Response) => { try { const { id } = req.params - const { name, status, can_be_parent, can_be_child } = req.body as { + const { name, short_name, status, can_be_parent, can_be_child } = req.body as { name?: string + short_name?: string status?: string can_be_parent?: boolean can_be_child?: boolean @@ -127,10 +146,18 @@ export const updateAssetType = async (req: Request, res: Response) => { return errorResponse(res, '类型名称不能为空', 422) } + let normalizedShortName: string | null | undefined + try { + normalizedShortName = normalizeShortName(short_name) + } catch (error: any) { + return errorResponse(res, error.message || '简称格式不正确', 422) + } + const updated = await prisma.assetType.update({ where: { id: Number(id) }, data: { name: name?.trim() ?? existing.name, + short_name: normalizedShortName !== undefined ? normalizedShortName : existing.short_name, status: status ?? existing.status, can_be_parent: can_be_parent ?? existing.can_be_parent, can_be_child: can_be_child ?? existing.can_be_child @@ -141,7 +168,7 @@ export const updateAssetType = async (req: Request, res: Response) => { successResponse(res, updated, '更新资产类型成功') } catch (error: any) { if (error.code === 'P2002') { - return errorResponse(res, '类型名称已存在') + return errorResponse(res, '类型名称或简称已存在') } errorResponse(res, error.message || '更新资产类型失败') } diff --git a/backend/src/controllers/batchController.ts b/backend/src/controllers/batchController.ts index f4bf16d..83f0a2f 100644 --- a/backend/src/controllers/batchController.ts +++ b/backend/src/controllers/batchController.ts @@ -3,6 +3,122 @@ import prisma from '../utils/prisma' import { successResponse, errorResponse } from '../utils/response' import { logOperation } from './operationLogController' +const buildBorrowNotes = (notes?: string, isLongTerm?: boolean) => { + const longTerm = Boolean(isLongTerm) + return longTerm && notes + ? `${notes};在职长期` + : longTerm + ? '在职长期' + : notes || undefined +} + +const getChildrenSnapshot = async (equipmentId: number) => { + const children = await prisma.equipment.findMany({ + where: { parent_id: equipmentId }, + select: { + id: true, + equipment_code: true, + equipment_name: true, + equipment_model: true, + equipment_type: true, + unit_price: true + } + }) + + return children.length > 0 ? JSON.stringify(children) : undefined +} + +const batchBorrowTransaction = async ( + equipment: any, + person: any, + expectedReturnTime?: string, + isLongTerm?: boolean, + notes?: string +) => { + const childrenSnapshot = await getChildrenSnapshot(equipment.id) + const finalNotes = buildBorrowNotes(notes, isLongTerm) + + return prisma.$transaction(async (tx) => { + await tx.borrowRecord.create({ + data: { + equipment_id: equipment.id, + equipment_code: equipment.equipment_code, + person_id: person.id, + person_name: person.real_name, + person_nickname: person.nickname, + person_identifier: person.id_number, + expected_return_time: isLongTerm ? null : (expectedReturnTime ? new Date(expectedReturnTime) : null), + is_long_term: Boolean(isLongTerm), + notes: finalNotes, + status: '已借出', + children_snapshot: childrenSnapshot + } + }) + + const nextStatus = equipment.remaining_quantity === 1 ? '已借出' : '可用' + const updated = await tx.equipment.updateMany({ + where: { + id: equipment.id, + status: '可用', + remaining_quantity: equipment.remaining_quantity + }, + data: { + remaining_quantity: { decrement: 1 }, + borrowed_value: { increment: Number(equipment.unit_price) }, + available_value: { decrement: Number(equipment.unit_price) }, + status: nextStatus + } + }) + + if (updated.count !== 1) { + throw new Error('资产库存已变化,请刷新后重试') + } + }) +} + +const batchReturnTransaction = async ( + equipment: any, + borrowRecord: any, + returnCondition: string, + notes?: string +) => { + return prisma.$transaction(async (tx) => { + const updatedRecord = await tx.borrowRecord.updateMany({ + where: { + id: borrowRecord.id, + status: '已借出' + }, + data: { + return_time: new Date(), + status: '已归还', + return_condition: returnCondition, + notes + } + }) + + if (updatedRecord.count !== 1) { + throw new Error('该借用记录已归还,请刷新后重试') + } + + const updatedEquipment = await tx.equipment.updateMany({ + where: { + id: equipment.id, + remaining_quantity: { lt: equipment.total_quantity } + }, + data: { + remaining_quantity: { increment: 1 }, + borrowed_value: { decrement: Number(equipment.unit_price) }, + available_value: { increment: Number(equipment.unit_price) }, + status: '可用' + } + }) + + if (updatedEquipment.count !== 1) { + throw new Error('资产库存已达到上限,请检查归还记录') + } + }) +} + // 批量借出 export const batchBorrow = async (req: Request, res: Response) => { try { @@ -37,32 +153,13 @@ export const batchBorrow = async (req: Request, res: Response) => { continue } - const longTerm = Boolean(is_long_term) - await prisma.borrowRecord.create({ - data: { - equipment_id: equipment.id, - equipment_code: equipment.equipment_code, - person_id: person.id, - person_name: person.real_name, - person_nickname: person.nickname, - person_identifier: person.id_number, - expected_return_time: longTerm ? null : (expected_return_time ? new Date(expected_return_time) : null), - is_long_term: longTerm, - notes: longTerm ? `${notes || ''};在职长期` : notes, - status: '已借出' - } - }) - - const newRemaining = equipment.remaining_quantity - 1 - await prisma.equipment.update({ - where: { id: equipment.id }, - data: { - remaining_quantity: newRemaining, - status: newRemaining === 0 ? '已借出' : '可用', - borrowed_value: Number(equipment.unit_price) * (equipment.total_quantity - newRemaining), - available_value: Number(equipment.unit_price) * newRemaining - } - }) + await batchBorrowTransaction( + equipment, + person, + expected_return_time, + is_long_term, + notes + ) results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'success', message: '借出成功' }) successCount++ @@ -117,10 +214,12 @@ export const batchReturn = async (req: Request, res: Response) => { continue } - const borrowRecord = await prisma.borrowRecord.findFirst({ + const activeBorrowRecords = await prisma.borrowRecord.findMany({ where: { equipment_id: equipment.id, status: '已借出' }, - orderBy: { borrow_time: 'desc' } + orderBy: { borrow_time: 'desc' }, + take: 2 }) + const borrowRecord = activeBorrowRecords[0] if (!borrowRecord) { results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'failed', message: '未找到借用记录' }) @@ -128,21 +227,13 @@ export const batchReturn = async (req: Request, res: Response) => { continue } - await prisma.borrowRecord.update({ - where: { id: borrowRecord.id }, - data: { return_time: new Date(), status: '已归还', return_condition, notes } - }) + if (activeBorrowRecords.length > 1) { + results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'failed', message: '存在多条未归还记录,请使用单个归还' }) + failedCount++ + continue + } - const newRemaining = equipment.remaining_quantity + 1 - await prisma.equipment.update({ - where: { id: equipment.id }, - data: { - remaining_quantity: newRemaining, - status: '可用', - borrowed_value: Number(equipment.unit_price) * (equipment.total_quantity - newRemaining), - available_value: Number(equipment.unit_price) * newRemaining - } - }) + await batchReturnTransaction(equipment, borrowRecord, return_condition, notes) results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'success', message: '归还成功' }) successCount++ diff --git a/backend/src/controllers/batchImportController.ts b/backend/src/controllers/batchImportController.ts index 26d7cea..6effd13 100644 --- a/backend/src/controllers/batchImportController.ts +++ b/backend/src/controllers/batchImportController.ts @@ -81,7 +81,7 @@ export const batchImportPersons = async (req: Request, res: Response) => { } // 记录操作日志 - const user = (req as any).user + const user = res.locals.user await prisma.operationLog.create({ data: { user_id: user?.id || null, @@ -201,7 +201,7 @@ export const batchImportEquipments = async (req: Request, res: Response) => { } // 记录操作日志 - const user = (req as any).user + const user = res.locals.user await prisma.operationLog.create({ data: { user_id: user?.id || null, diff --git a/backend/src/controllers/borrowController.ts b/backend/src/controllers/borrowController.ts index 61699a4..55c5781 100644 --- a/backend/src/controllers/borrowController.ts +++ b/backend/src/controllers/borrowController.ts @@ -62,33 +62,206 @@ const ensureEquipmentBorrowable = (equipment: any) => { } } -const updateEquipmentAfterBorrow = async (equipment: any) => { - const newRemainingQuantity = equipment.remaining_quantity - 1 - if (newRemainingQuantity < 0) { - throw new Error('资产库存不足') - } - const borrowedQuantity = equipment.total_quantity - newRemainingQuantity - await prisma.equipment.update({ - where: { id: equipment.id }, - data: { - remaining_quantity: newRemainingQuantity, - status: newRemainingQuantity === 0 ? '已借出' : '可用', - borrowed_value: Number(equipment.unit_price) * borrowedQuantity, - available_value: Number(equipment.unit_price) * newRemainingQuantity +const appendLongTermNotes = (notes?: string, isLongTerm?: boolean) => { + const longTerm = Boolean(isLongTerm) + return longTerm && notes + ? `${notes};在职长期` + : longTerm + ? '在职长期' + : notes || undefined +} + +const getChildrenSnapshot = async (equipmentId: number) => { + const children = await prisma.equipment.findMany({ + where: { parent_id: equipmentId }, + select: { + id: true, + equipment_code: true, + equipment_name: true, + equipment_model: true, + equipment_type: true, + unit_price: true } }) + + return { + children, + snapshot: children.length > 0 ? JSON.stringify(children) : undefined + } +} + +const borrowEquipmentInTransaction = async ( + equipment: any, + person: any, + expectedReturn: Date | null, + combinedNotes?: string, + isLongTerm?: boolean, + childrenSnapshot?: string +) => { + return prisma.$transaction(async (tx) => { + const record = await tx.borrowRecord.create({ + data: { + equipment_id: equipment.id, + equipment_code: equipment.equipment_code, + person_id: person.id, + person_name: person.real_name, + person_nickname: person.nickname, + person_identifier: person.id_number, + expected_return_time: expectedReturn, + is_long_term: Boolean(isLongTerm), + notes: combinedNotes, + status: '已借出', + children_snapshot: childrenSnapshot + } + }) + + const nextStatus = equipment.remaining_quantity === 1 ? '已借出' : '可用' + const updateResult = await tx.equipment.updateMany({ + where: { + id: equipment.id, + status: '可用', + remaining_quantity: equipment.remaining_quantity + }, + data: { + remaining_quantity: { decrement: 1 }, + borrowed_value: { increment: Number(equipment.unit_price) }, + available_value: { decrement: Number(equipment.unit_price) }, + status: nextStatus + } + }) + + if (updateResult.count !== 1) { + throw new Error('资产库存已变化,请刷新后重试') + } + + const updatedEquipment = await tx.equipment.findUnique({ + where: { id: equipment.id }, + include: equipmentInclude + }) + + return { record, updatedEquipment } + }) } -const updateEquipmentAfterReturn = async (equipment: any) => { - const newRemainingQuantity = equipment.remaining_quantity + 1 - const borrowedQuantity = equipment.total_quantity - newRemainingQuantity - await prisma.equipment.update({ - where: { id: equipment.id }, - data: { - remaining_quantity: newRemainingQuantity, - status: '可用', - borrowed_value: Number(equipment.unit_price) * borrowedQuantity, - available_value: Number(equipment.unit_price) * newRemainingQuantity +const findActiveBorrowRecord = async ( + equipmentId: number, + borrowRecordId?: number, + personId?: number +) => { + if (borrowRecordId) { + return prisma.borrowRecord.findFirst({ + where: { + id: borrowRecordId, + equipment_id: equipmentId, + status: '已借出' + } + }) + } + + if (personId) { + return prisma.borrowRecord.findFirst({ + where: { + equipment_id: equipmentId, + person_id: personId, + status: '已借出' + }, + orderBy: { borrow_time: 'desc' } + }) + } + + const activeRecords = await prisma.borrowRecord.findMany({ + where: { + equipment_id: equipmentId, + status: '已借出' + }, + orderBy: { borrow_time: 'desc' }, + take: 2 + }) + + if (activeRecords.length > 1) { + throw new Error('该资产存在多条未归还记录,请明确选择归还使用者') + } + + return activeRecords[0] || null +} + +const getChildrenDiff = async (equipmentId: number, borrowRecord: any) => { + if (!borrowRecord?.children_snapshot) { + return null + } + + const borrowedChildren = JSON.parse(borrowRecord.children_snapshot) as any[] + const currentChildren = await prisma.equipment.findMany({ + where: { parent_id: equipmentId }, + select: { + id: true, + equipment_code: true, + equipment_name: true, + equipment_model: true, + equipment_type: true, + unit_price: true + } + }) + + const borrowedIds = new Set(borrowedChildren.map((child) => child.id)) + const currentIds = new Set(currentChildren.map((child) => child.id)) + const removed = borrowedChildren.filter((child) => !currentIds.has(child.id)) + const added = currentChildren.filter((child) => !borrowedIds.has(child.id)) + + if (removed.length === 0 && added.length === 0) { + return null + } + + return { removed, added } +} + +const returnEquipmentInTransaction = async ( + equipment: any, + borrowRecord: any, + returnCondition: string, + notes?: string +) => { + return prisma.$transaction(async (tx) => { + const returnTime = new Date() + const updatedBorrowRecord = await tx.borrowRecord.updateMany({ + where: { + id: borrowRecord.id, + status: '已借出' + }, + data: { + return_time: returnTime, + status: '已归还', + return_condition: returnCondition, + notes + } + }) + + if (updatedBorrowRecord.count !== 1) { + throw new Error('该借用记录已归还,请刷新后重试') + } + + const equipmentUpdate = await tx.equipment.updateMany({ + where: { + id: equipment.id, + remaining_quantity: { lt: equipment.total_quantity } + }, + data: { + remaining_quantity: { increment: 1 }, + borrowed_value: { decrement: Number(equipment.unit_price) }, + available_value: { increment: Number(equipment.unit_price) }, + status: '可用' + } + }) + + if (equipmentUpdate.count !== 1) { + throw new Error('资产库存已达到上限,请检查归还记录') + } + + return { + id: borrowRecord.id, + person_id: borrowRecord.person_id, + return_time: returnTime, + status: '已归还' } }) } @@ -123,7 +296,8 @@ const mapBorrowRecord = (record: any) => ({ return_time: record.return_time, status: record.status, return_condition: record.return_condition, - notes: record.notes + notes: record.notes, + children_snapshot: record.children_snapshot }) // 借用资产 @@ -158,48 +332,16 @@ export const borrowEquipment = async (req: Request, res: Response) => { const longTerm = Boolean(is_long_term) const expectedReturn = longTerm ? null : parseDate(expected_return_time) - - const combinedNotes = - longTerm && notes - ? `${notes};在职长期` - : longTerm - ? '在职长期' - : notes || undefined - - // 获取子资产快照(如果是父资产) - let childrenSnapshot: string | undefined - const children = await prisma.equipment.findMany({ - where: { parent_id: equipment.id }, - select: { - id: true, - equipment_code: true, - equipment_name: true, - equipment_model: true, - equipment_type: true, - unit_price: true - } - }) - if (children.length > 0) { - childrenSnapshot = JSON.stringify(children) - } - - const borrowRecord = await prisma.borrowRecord.create({ - data: { - equipment_id: equipment.id, - equipment_code: equipment.equipment_code, - person_id: person.id, - person_name: person.real_name, - person_nickname: person.nickname, - person_identifier: person.id_number, - expected_return_time: expectedReturn, - is_long_term: longTerm, - notes: combinedNotes, - status: '已借出', - children_snapshot: childrenSnapshot - } - }) - - await updateEquipmentAfterBorrow(equipment) + const combinedNotes = appendLongTermNotes(notes, longTerm) + const { children, snapshot } = await getChildrenSnapshot(equipment.id) + const { record: borrowRecord } = await borrowEquipmentInTransaction( + equipment, + person, + expectedReturn, + combinedNotes, + longTerm, + snapshot + ) successResponse( res, @@ -246,51 +388,16 @@ export const scanBorrow = async (req: Request, res: Response) => { const longTerm = Boolean(is_long_term) const expectedReturn = longTerm ? null : parseDate(expected_return_time) - const combinedNotes = - longTerm && notes - ? `${notes};在职长期` - : longTerm - ? '在职长期' - : notes || undefined - - // 获取子资产快照(如果是父资产) - let childrenSnapshot: string | undefined - const children = await prisma.equipment.findMany({ - where: { parent_id: equipment.id }, - select: { - id: true, - equipment_code: true, - equipment_name: true, - equipment_model: true, - equipment_type: true, - unit_price: true - } - }) - if (children.length > 0) { - childrenSnapshot = JSON.stringify(children) - } - - const borrowRecord = await prisma.borrowRecord.create({ - data: { - equipment_id: equipment.id, - equipment_code: equipment.equipment_code, - person_id: person.id, - person_name: person.real_name, - person_nickname: person.nickname, - person_identifier: person.id_number, - expected_return_time: expectedReturn, - is_long_term: longTerm, - notes: combinedNotes, - status: '已借出', - children_snapshot: childrenSnapshot - } - }) - - await updateEquipmentAfterBorrow(equipment) - const updatedEquipment = await prisma.equipment.findUnique({ - where: { id: equipment.id }, - include: equipmentInclude - }) + const combinedNotes = appendLongTermNotes(notes, longTerm) + const { children, snapshot } = await getChildrenSnapshot(equipment.id) + const { record: borrowRecord, updatedEquipment } = await borrowEquipmentInTransaction( + equipment, + person, + expectedReturn, + combinedNotes, + longTerm, + snapshot + ) successResponse( res, @@ -343,12 +450,7 @@ export const scanBorrowBatch = async (req: Request, res: Response) => { const longTerm = Boolean(is_long_term) const expectedReturn = longTerm ? null : parseDate(expected_return_time) - const combinedNotes = - longTerm && notes - ? `${notes};在职长期` - : longTerm - ? '在职长期' - : notes || undefined + const combinedNotes = appendLongTermNotes(notes, longTerm) const results: any[] = [] let successCount = 0 let failedCount = 0 @@ -370,22 +472,15 @@ export const scanBorrowBatch = async (req: Request, res: Response) => { continue } - await prisma.borrowRecord.create({ - data: { - equipment_id: equipment.id, - equipment_code: equipment.equipment_code, - person_id: person.id, - person_name: person.real_name, - person_nickname: person.nickname, - person_identifier: person.id_number, - expected_return_time: expectedReturn, - is_long_term: longTerm, - notes: combinedNotes, - status: '已借出' - } - }) - - await updateEquipmentAfterBorrow(equipment) + const { snapshot } = await getChildrenSnapshot(equipment.id) + await borrowEquipmentInTransaction( + equipment, + person, + expectedReturn, + combinedNotes, + longTerm, + snapshot + ) results.push({ equipment_code: code, status: 'success', message: '出库成功' }) successCount++ @@ -425,6 +520,8 @@ export const returnEquipment = async (req: Request, res: Response) => { const { equipment_id, equipment_code, + borrow_record_id, + person_id, return_condition = '正常', notes, confirm_children_diff @@ -437,67 +534,37 @@ export const returnEquipment = async (req: Request, res: Response) => { return errorResponse(res, '资产不存在', 404) } - const borrowRecord = await prisma.borrowRecord.findFirst({ - where: { - equipment_id: equipment.id, - status: '已借出' - }, - orderBy: { borrow_time: 'desc' } - }) + let borrowRecord + try { + borrowRecord = await findActiveBorrowRecord( + equipment.id, + parseNumber(borrow_record_id), + parseNumber(person_id) + ) + } catch (error: any) { + return errorResponse(res, error.message || '未找到对应的借用记录', 422) + } if (!borrowRecord) { return errorResponse(res, '未找到对应的借用记录') } - // 检查配件差异 - let childrenDiff: any = null - if (borrowRecord.children_snapshot) { - const borrowedChildren = JSON.parse(borrowRecord.children_snapshot) as any[] - const currentChildren = await prisma.equipment.findMany({ - where: { parent_id: equipment.id }, - select: { - id: true, - equipment_code: true, - equipment_name: true, - equipment_model: true, - equipment_type: true, - unit_price: true - } - }) - - const borrowedIds = new Set(borrowedChildren.map(c => c.id)) - const currentIds = new Set(currentChildren.map(c => c.id)) - - const removed = borrowedChildren.filter(c => !currentIds.has(c.id)) - const added = currentChildren.filter(c => !borrowedIds.has(c.id)) - - if (removed.length > 0 || added.length > 0) { - childrenDiff = { removed, added } - - // 如果有差异且未确认,返回差异信息要求确认 - if (!confirm_children_diff) { - return successResponse(res, { - need_confirm: true, - children_diff: childrenDiff, - message: '配件与借出时不一致,请确认后继续' - }, '配件差异提醒') - } - } + const childrenDiff = await getChildrenDiff(equipment.id, borrowRecord) + if (childrenDiff && !confirm_children_diff) { + return successResponse(res, { + need_confirm: true, + children_diff: childrenDiff, + message: '配件与借出时不一致,请确认后继续' + }, '配件差异提醒') } - - await prisma.borrowRecord.update({ - where: { id: borrowRecord.id }, - data: { - return_time: new Date(), - status: '已归还', - return_condition, - notes: childrenDiff - ? `${notes || ''};配件变更:移除${childrenDiff.removed.length}个,新增${childrenDiff.added.length}个` - : notes - } - }) - - await updateEquipmentAfterReturn(equipment) + + const finalNotes = childrenDiff + ? [notes, `配件变更:移除${childrenDiff.removed.length}个,新增${childrenDiff.added.length}个`] + .filter(Boolean) + .join(';') + : notes + + await returnEquipmentInTransaction(equipment, borrowRecord, return_condition, finalNotes) successResponse( res, @@ -527,7 +594,8 @@ export const scanReturn = async (req: Request, res: Response) => { return_condition = '正常', notes, person_id, - borrow_record_id + borrow_record_id, + confirm_children_diff } = req.body as Record const equipment = await getEquipmentByIdentifier(undefined, equipment_code) @@ -536,57 +604,42 @@ export const scanReturn = async (req: Request, res: Response) => { return errorResponse(res, '资产不存在', 404) } - const personIdNum = parseNumber(person_id) - const borrowRecordIdNum = parseNumber(borrow_record_id) - let borrowRecord = null - - if (borrowRecordIdNum) { - borrowRecord = await prisma.borrowRecord.findFirst({ - where: { - id: borrowRecordIdNum, - equipment_id: equipment.id, - status: '已借出' - } - }) - } - - if (!borrowRecord && personIdNum) { - borrowRecord = await prisma.borrowRecord.findFirst({ - where: { - equipment_id: equipment.id, - person_id: personIdNum, - status: '已借出' - }, - orderBy: { borrow_time: 'desc' } - }) - } - - if (!borrowRecord) { - borrowRecord = await prisma.borrowRecord.findFirst({ - where: { - equipment_id: equipment.id, - status: '已借出' - }, - orderBy: { borrow_time: 'desc' } - }) + try { + borrowRecord = await findActiveBorrowRecord( + equipment.id, + parseNumber(borrow_record_id), + parseNumber(person_id) + ) + } catch (error: any) { + return errorResponse(res, error.message || '未找到对应的借用记录,请选择正确的使用者', 422) } if (!borrowRecord) { return errorResponse(res, '未找到对应的借用记录,请选择正确的使用者', 404) } - const updatedRecord = await prisma.borrowRecord.update({ - where: { id: borrowRecord.id }, - data: { - return_time: new Date(), - status: '已归还', - return_condition, - notes - } - }) - - await updateEquipmentAfterReturn(equipment) + const childrenDiff = await getChildrenDiff(equipment.id, borrowRecord) + if (childrenDiff && !confirm_children_diff) { + return successResponse(res, { + need_confirm: true, + children_diff: childrenDiff, + message: '配件与借出时不一致,请确认后继续' + }, '配件差异提醒') + } + + const finalNotes = childrenDiff + ? [notes, `配件变更:移除${childrenDiff.removed.length}个,新增${childrenDiff.added.length}个`] + .filter(Boolean) + .join(';') + : notes + + const updatedRecord = await returnEquipmentInTransaction( + equipment, + borrowRecord, + return_condition, + finalNotes + ) successResponse( res, @@ -638,31 +691,36 @@ export const scanReturnBatch = async (req: Request, res: Response) => { continue } - const borrowRecord = await prisma.borrowRecord.findFirst({ + const activeBorrowRecords = await prisma.borrowRecord.findMany({ where: { equipment_id: equipment.id, status: '已借出' }, - orderBy: { borrow_time: 'desc' } + orderBy: { borrow_time: 'desc' }, + take: 2 }) + const borrowRecord = activeBorrowRecords[0] if (!borrowRecord) { results.push({ equipment_code: code, status: 'failed', message: '未找到对应的借用记录' }) failedCount++ continue } - - await prisma.borrowRecord.update({ - where: { id: borrowRecord.id }, - data: { - return_time: new Date(), - status: '已归还', - return_condition, - notes - } - }) - - await updateEquipmentAfterReturn(equipment) + + if (activeBorrowRecords.length > 1) { + results.push({ equipment_code: code, status: 'failed', message: '存在多条未归还记录,请使用单个扫码归还' }) + failedCount++ + continue + } + + const childrenDiff = await getChildrenDiff(equipment.id, borrowRecord) + if (childrenDiff) { + results.push({ equipment_code: code, status: 'failed', message: '配件与借出时不一致,请使用单个扫码归还确认' }) + failedCount++ + continue + } + + await returnEquipmentInTransaction(equipment, borrowRecord, return_condition, notes) results.push({ equipment_code: code, status: 'success', message: '入库成功' }) successCount++ @@ -827,4 +885,3 @@ export const getBorrowRecordList = async (req: Request, res: Response) => { errorResponse(res, error.message || '获取借用记录列表失败') } } - diff --git a/backend/src/controllers/equipmentController.ts b/backend/src/controllers/equipmentController.ts index 2fda518..afa4a56 100644 --- a/backend/src/controllers/equipmentController.ts +++ b/backend/src/controllers/equipmentController.ts @@ -37,6 +37,7 @@ const includeStorageLocation = { select: { id: true, name: true, + short_name: true, status: true } } @@ -453,6 +454,25 @@ export const deleteEquipment = async (req: Request, res: Response) => { const equipmentId = Number(id) + const equipment = await prisma.equipment.findUnique({ + where: { id: equipmentId }, + include: { + _count: { + select: { + borrow_records: true, + children: true, + mountHistories: true, + disposalRecords: true, + maintenanceRecords: true + } + } + } + }) + + if (!equipment) { + return errorResponse(res, '资产不存在', 404) + } + // 检查是否有未归还的借用记录 const activeBorrowCount = await prisma.borrowRecord.count({ where: { @@ -465,15 +485,28 @@ export const deleteEquipment = async (req: Request, res: Response) => { return errorResponse(res, '该资产存在未归还的借用记录,无法删除') } - // 清理历史借用记录,避免外键约束阻止删除 - await prisma.borrowRecord.deleteMany({ - where: { - equipment_id: equipmentId - } - }) - - await prisma.equipment.delete({ - where: { id: equipmentId } + if (equipment.parent_id) { + return errorResponse(res, '该资产当前作为子资产挂载中,无法删除') + } + + if (equipment._count.children > 0) { + return errorResponse(res, '该资产存在已挂载的子资产,无法删除') + } + + if (equipment._count.mountHistories > 0 || equipment._count.disposalRecords > 0 || equipment._count.maintenanceRecords > 0) { + return errorResponse(res, '该资产已存在维修、报废或挂载历史,无法删除') + } + + await prisma.$transaction(async (tx) => { + await tx.borrowRecord.deleteMany({ + where: { + equipment_id: equipmentId + } + }) + + await tx.equipment.delete({ + where: { id: equipmentId } + }) }) successResponse(res, null, '删除成功') @@ -495,7 +528,7 @@ export const generateCode = async (req: Request, res: Response) => { const code = await generateEquipmentCode(equipment_type, prisma, locationId) successResponse(res, { equipment_code: code, - code_format: '{类型数字}{地址ID(4位)}{序号(5位)}' + code_format: '{类型简称}-{地址ID(4位)}-{序号(5位)}' }, '生成成功') } else { errorResponse(res, '自定义录入模式不需要生成编码') @@ -565,4 +598,3 @@ export const scanEquipment = async (req: Request, res: Response) => { errorResponse(res, error.message || '查询资产失败') } } - diff --git a/backend/src/controllers/mountController.ts b/backend/src/controllers/mountController.ts index 2c51740..9410a55 100644 --- a/backend/src/controllers/mountController.ts +++ b/backend/src/controllers/mountController.ts @@ -38,7 +38,10 @@ export const mountChild = async (req: Request, res: Response) => { try { const { parentId } = req.params const { child_id, notes } = req.body as { child_id: number; notes?: string } - const userId = (req as any).user?.id || 1 + const userId = Number(res.locals.user?.id) + if (!userId) { + return errorResponse(res, '用户未登录', 401) + } // 验证父资产存在 const parent = await prisma.equipment.findUnique({ @@ -131,7 +134,10 @@ export const unmountChild = async (req: Request, res: Response) => { try { const { parentId } = req.params const { child_id, notes } = req.body as { child_id: number; notes?: string } - const userId = (req as any).user?.id || 1 + const userId = Number(res.locals.user?.id) + if (!userId) { + return errorResponse(res, '用户未登录', 401) + } // 验证子资产存在且挂载在该父资产上 const child = await prisma.equipment.findUnique({ @@ -185,7 +191,10 @@ export const replaceChild = async (req: Request, res: Response) => { new_child_id: number notes?: string } - const userId = (req as any).user?.id || 1 + const userId = Number(res.locals.user?.id) + if (!userId) { + return errorResponse(res, '用户未登录', 401) + } // 验证旧配件挂载在该父资产上 const oldChild = await prisma.equipment.findUnique({ diff --git a/backend/src/controllers/personController.ts b/backend/src/controllers/personController.ts index 1b53ca7..975e05f 100644 --- a/backend/src/controllers/personController.ts +++ b/backend/src/controllers/personController.ts @@ -222,12 +222,13 @@ export const deletePerson = async (req: Request, res: Response) => { return errorResponse(res, '该使用者存在未归还的借用记录,无法删除') } - // 删除历史借用记录 - await prisma.borrowRecord.deleteMany({ - where: { person_id: personId } - }) + await prisma.$transaction(async (tx) => { + await tx.borrowRecord.deleteMany({ + where: { person_id: personId } + }) - await prisma.person.delete({ where: { id: personId } }) + await tx.person.delete({ where: { id: personId } }) + }) successResponse(res, null, '删除成功') } catch (error: any) { diff --git a/backend/src/controllers/settingsController.ts b/backend/src/controllers/settingsController.ts index 337b9b7..5f4bb69 100644 --- a/backend/src/controllers/settingsController.ts +++ b/backend/src/controllers/settingsController.ts @@ -2,9 +2,9 @@ import type { Request, Response } from 'express' import prisma from '../utils/prisma' import { successResponse, errorResponse } from '../utils/response' -const DEFAULT_SITE_NAME = '摄影资产借还系统' +const DEFAULT_SITE_NAME = '固定资产管理系统' const DEFAULT_THEME_COLOR = '#409EFF' -const DEFAULT_LOGO_URL = 'https://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png' +const DEFAULT_LOGO_URL = '' const mapConfigResponse = (config: any) => { if (!config) { @@ -20,8 +20,8 @@ const mapConfigResponse = (config: any) => { return { site_name: config.site_name || DEFAULT_SITE_NAME, - logo_url: DEFAULT_LOGO_URL, - favicon_url: '', + logo_url: config.logo_url || DEFAULT_LOGO_URL, + favicon_url: config.favicon_url || '', theme_color: config.theme_color || DEFAULT_THEME_COLOR, updated_at: config.updated_at, updated_by: config.updatedBy @@ -72,8 +72,8 @@ export const updateSiteConfig = async (req: Request, res: Response) => { theme_color: theme_color || DEFAULT_THEME_COLOR, updated_at: new Date(), updated_by: res.locals.user?.id ?? null, - logo_url: DEFAULT_LOGO_URL, - favicon_url: null + logo_url: existing?.logo_url || DEFAULT_LOGO_URL, + favicon_url: existing?.favicon_url || null } const result = existing @@ -105,4 +105,3 @@ export const updateSiteConfig = async (req: Request, res: Response) => { errorResponse(res, error.message || '更新站点配置失败') } } - diff --git a/backend/src/controllers/statisticsController.ts b/backend/src/controllers/statisticsController.ts index 1f4c7bb..c0fbe78 100644 --- a/backend/src/controllers/statisticsController.ts +++ b/backend/src/controllers/statisticsController.ts @@ -164,16 +164,32 @@ export const getValueStatistics = async (req: Request, res: Response) => { .filter(eq => eq.status === '维护中') .reduce((sum, eq) => sum + Number(eq.available_value), 0) - // 按资产类型统计 - const valueByType = ['相机', '镜头', '三脚架', '闪光灯', '摄影配饰品'].map(type => { - const typeEquipment = equipmentList.filter(eq => eq.equipment_type === type) - return { - equipment_type: type, - total_value: typeEquipment.reduce((sum, eq) => sum + Number(eq.total_value), 0), - borrowed_value: typeEquipment.reduce((sum, eq) => sum + Number(eq.borrowed_value), 0), - available_value: typeEquipment.reduce((sum, eq) => sum + Number(eq.available_value), 0) + // 按资产类型统计,避免沿用旧项目中的固定类型名单 + const valueByTypeMap = equipmentList.reduce((acc, eq) => { + const type = eq.equipment_type || '未分类' + if (!acc[type]) { + acc[type] = { + equipment_type: type, + total_value: 0, + borrowed_value: 0, + available_value: 0 + } } - }).filter(item => item.total_value > 0) + + acc[type].total_value += Number(eq.total_value) + acc[type].borrowed_value += Number(eq.borrowed_value) + acc[type].available_value += Number(eq.available_value) + return acc + }, {} as Record) + + const valueByType = Object.values(valueByTypeMap) + .filter((item) => item.total_value > 0) + .sort((a, b) => b.total_value - a.total_value) // 价值最高的资产(Top 10) const topEquipment = equipmentList @@ -199,4 +215,3 @@ export const getValueStatistics = async (req: Request, res: Response) => { errorResponse(res, error.message || '获取价值统计失败') } } - diff --git a/backend/src/controllers/userController.ts b/backend/src/controllers/userController.ts index f3aec3f..c7fa1eb 100644 --- a/backend/src/controllers/userController.ts +++ b/backend/src/controllers/userController.ts @@ -201,7 +201,20 @@ export const deleteUser = async (req: Request, res: Response) => { const { id } = req.params const userId = Number(id) - const user = await prisma.user.findUnique({ where: { id: userId } }) + const user = await prisma.user.findUnique({ + where: { id: userId }, + include: { + _count: { + select: { + site_configs: true, + mount_histories: true, + disposal_applied: true, + disposal_approved: true, + maintenance_records: true + } + } + } + }) if (!user) { return errorResponse(res, '用户不存在', 404) } @@ -211,12 +224,23 @@ export const deleteUser = async (req: Request, res: Response) => { return errorResponse(res, '不能删除管理员账户') } - // 删除关联的操作日志 - await prisma.operationLog.deleteMany({ - where: { user_id: userId } - }) + if ( + user._count.site_configs > 0 || + user._count.mount_histories > 0 || + user._count.disposal_applied > 0 || + user._count.disposal_approved > 0 || + user._count.maintenance_records > 0 + ) { + return errorResponse(res, '该用户已存在业务历史记录,无法删除') + } - await prisma.user.delete({ where: { id: userId } }) + await prisma.$transaction(async (tx) => { + await tx.operationLog.deleteMany({ + where: { user_id: userId } + }) + + await tx.user.delete({ where: { id: userId } }) + }) successResponse(res, null, '删除成功') } catch (error: any) { diff --git a/backend/src/index.ts b/backend/src/index.ts index 79aba25..14b73b7 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -62,7 +62,7 @@ app.use('/api/return', protect(), returnRouter) app.use('/api/statistics', protect(), statisticsRoutes) app.use('/api/barcode', protect(), barcodeRoutes) app.use('/api/auth', authRoutes) -app.use('/api/settings', protect(), requireAdmin(), settingsRoutes) +app.use('/api/settings', settingsRoutes) app.use('/api/users', protect(), requireAdmin(), usersRoutes) app.use('/api/persons', personRoutes) app.use('/api/storage-locations', storageLocationRoutes) @@ -95,4 +95,3 @@ app.use((err: any, req: express.Request, res: express.Response, next: express.Ne app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`) }) - diff --git a/backend/src/routes/disposal.ts b/backend/src/routes/disposal.ts index 4fc0ea1..21dbce0 100644 --- a/backend/src/routes/disposal.ts +++ b/backend/src/routes/disposal.ts @@ -7,9 +7,13 @@ import { completeDisposal, getEquipmentDisposalHistory } from '../controllers/disposalController' +import { protect, requireAdmin } from '../middleware/auth' const router = Router() +router.use(protect()) +router.use(requireAdmin()) + router.post('/', createDisposal) router.get('/list', getDisposalList) router.get('/:id', getDisposalDetail) diff --git a/backend/src/routes/maintenance.ts b/backend/src/routes/maintenance.ts index db958f2..c13e31c 100644 --- a/backend/src/routes/maintenance.ts +++ b/backend/src/routes/maintenance.ts @@ -8,9 +8,13 @@ import { cancelMaintenance, getEquipmentMaintenanceHistory } from '../controllers/maintenanceController' +import { protect, requireAdmin } from '../middleware/auth' const router = Router() +router.use(protect()) +router.use(requireAdmin()) + router.post('/', createMaintenance) router.get('/list', getMaintenanceList) router.get('/:id', getMaintenanceDetail) diff --git a/backend/src/routes/mount.ts b/backend/src/routes/mount.ts index b6ad0a5..64a3383 100644 --- a/backend/src/routes/mount.ts +++ b/backend/src/routes/mount.ts @@ -7,11 +7,14 @@ import { getMountHistory, getAvailableChildren } from '../controllers/mountController' +import { protect, requireAdmin } from '../middleware/auth' const router = Router() +router.use(protect()) + // 获取可挂载的子资产列表 -router.get('/available-children', getAvailableChildren) +router.get('/available-children', requireAdmin(), getAvailableChildren) // 获取资产的挂载历史 router.get('/:id/mount-history', getMountHistory) @@ -20,12 +23,12 @@ router.get('/:id/mount-history', getMountHistory) router.get('/:parentId/children', getChildren) // 挂载子资产到父资产 -router.post('/:parentId/mount', mountChild) +router.post('/:parentId/mount', requireAdmin(), mountChild) // 从父资产卸载子资产 -router.post('/:parentId/unmount', unmountChild) +router.post('/:parentId/unmount', requireAdmin(), unmountChild) // 更换配件 -router.post('/:parentId/replace', replaceChild) +router.post('/:parentId/replace', requireAdmin(), replaceChild) export default router diff --git a/backend/src/utils/equipmentCode.ts b/backend/src/utils/equipmentCode.ts index bfe0418..b1be17d 100644 --- a/backend/src/utils/equipmentCode.ts +++ b/backend/src/utils/equipmentCode.ts @@ -1,44 +1,44 @@ -// 资产类型数字代码映射(纯数字编码) -const EQUIPMENT_TYPE_NUMBERS: Record = { - '相机': 1, - '镜头': 2, - '三脚架': 3, - '闪光灯': 4, - '摄影配饰品': 5 +const EQUIPMENT_CODE_PATTERN = /^[A-Z0-9]{2,20}-\d{4}-\d{5}$/ + +const normalizeShortName = (value?: string | null) => { + const normalized = value?.trim().toUpperCase() || '' + if (!normalized) return null + return normalized.replace(/[^A-Z0-9]/g, '') +} + +const resolveTypeShortName = async (equipmentType: string, prisma: any) => { + const assetType = await prisma.assetType.findFirst({ + where: { name: equipmentType }, + select: { + id: true, + short_name: true + } + }) + + if (!assetType) { + throw new Error(`资产类型 "${equipmentType}" 不存在`) + } + + const shortName = normalizeShortName(assetType.short_name) + return shortName || `AT${assetType.id}` } /** - * 生成资产编码(纯数字格式) - * 格式: {类型数字}{地址ID(4位)}{序号(5位)} - * 例如: 1000100001 (类型1,地址1,序号00001) - * 如果没有地址,地址ID为0000 - * - * 确保唯一性: - * 1. 查找该类型和地址的最大序号,避免并发问题 - * 2. 检查生成的编码是否已存在,如果存在则递增序号重试 - * 3. 最多重试10次,防止无限循环 + * 生成资产编码 + * 格式: {类型简称}-{地址ID(4位)}-{序号(5位)} + * 例如: PC-0001-00001 */ export const generateEquipmentCode = async ( equipmentType: string, prisma: any, storageLocationId?: number | null ): Promise => { - const typeNumber = EQUIPMENT_TYPE_NUMBERS[equipmentType] || 0 - - // 获取地址ID,如果没有地址则为0 - const locationId = storageLocationId || 0 - - // 格式前缀:{类型数字}{地址ID(4位)} - const typeCode = String(typeNumber) - const locationCode = String(locationId).padStart(4, '0') - const prefix = `${typeCode}${locationCode}` - - // 查找该类型和地址的所有资产,提取最大序号 - // 使用这种方式比count更安全,可以处理并发情况 + const typeCode = await resolveTypeShortName(equipmentType, prisma) + const locationCode = String(storageLocationId || 0).padStart(4, '0') + const prefix = `${typeCode}-${locationCode}-` + const existingEquipment = await prisma.equipment.findMany({ where: { - equipment_type: equipmentType, - storage_location_id: storageLocationId || null, equipment_code: { startsWith: prefix } @@ -49,58 +49,42 @@ export const generateEquipmentCode = async ( orderBy: { equipment_code: 'desc' }, - take: 1 // 只取最大的一条 + take: 1 }) - - // 计算起始序号 + let startSequence = 1 if (existingEquipment.length > 0) { - // 从最大编码中提取序号 const maxCode = existingEquipment[0].equipment_code if (maxCode.startsWith(prefix)) { const sequencePart = maxCode.substring(prefix.length) const maxSequence = parseInt(sequencePart, 10) - if (!isNaN(maxSequence)) { + if (!Number.isNaN(maxSequence)) { startSequence = maxSequence + 1 } } } - - // 检查序号是否溢出(5位数字最大99999) + if (startSequence > 99999) { - throw new Error(`资产编码序号已溢出,该类型和地址的资产数量已超过99999`) + throw new Error(`资产编码序号已溢出,该类型和地址的资产数量已超过 99999`) } - - // 生成编码并检查唯一性,最多重试10次 - const maxRetries = 10 + + const maxRetries = 20 for (let attempt = 0; attempt < maxRetries; attempt++) { const sequence = String(startSequence + attempt).padStart(5, '0') const code = `${prefix}${sequence}` - - // 检查编码是否已存在 + const existing = await prisma.equipment.findUnique({ where: { equipment_code: code } }) - + if (!existing) { - // 编码唯一,返回 return code } - - // 如果编码已存在,继续尝试下一个序号 - console.warn(`资产编码 ${code} 已存在,尝试下一个序号`) } - - // 如果重试10次后仍然冲突,抛出错误 - throw new Error(`无法生成唯一的资产编码,已重试${maxRetries}次`) + + throw new Error(`无法生成唯一的资产编码,已重试 ${maxRetries} 次`) } -/** - * 验证资产编码格式(纯数字) - */ export const validateEquipmentCode = (code: string): boolean => { - // 纯数字格式,长度10位:{类型1位}{地址4位}{序号5位} - const pattern = /^\d{10}$/ - return pattern.test(code) + return EQUIPMENT_CODE_PATTERN.test(code.trim().toUpperCase()) } - diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4d51317..8ec0881 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,18 +9,22 @@ "version": "0.0.0", "dependencies": { "@heroicons/vue": "^2.2.0", + "@types/qrcode": "^1.5.6", "axios": "^1.13.2", "echarts": "^6.0.0", "html2canvas": "^1.4.1", "jsbarcode": "^3.12.1", "keycloak-js": "^23.0.7", "pinia": "^3.0.4", + "qrcode": "^1.5.4", "vue": "^3.5.24", - "vue-router": "^4.6.3" + "vue-router": "^4.6.3", + "xlsx": "^0.18.5" }, "devDependencies": { "@tailwindcss/forms": "^0.5.10", "@types/node": "^24.10.0", + "@types/xlsx": "^0.0.35", "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", "autoprefixer": "^10.4.22", @@ -985,12 +989,27 @@ "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmmirror.com/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/xlsx": { + "version": "0.0.35", + "resolved": "https://registry.npmmirror.com/@types/xlsx/-/xlsx-0.0.35.tgz", + "integrity": "sha512-s0x3DYHZzOkxtjqOk/Nv1ezGzpbN7I8WX+lzlV/nFfTDOv7x4d8ZwGHcnaiB8UCx89omPsftQhS5II3jeWePxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", @@ -1213,6 +1232,15 @@ } } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/alien-signals": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.0.tgz", @@ -1453,6 +1481,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -1480,6 +1509,15 @@ "node": ">= 0.4" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1511,6 +1549,19 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1549,11 +1600,100 @@ "node": ">= 6" } }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/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/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/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/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/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/cliui/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "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==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1566,7 +1706,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -1606,6 +1745,18 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1649,6 +1800,15 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1658,18 +1818,6 @@ "node": ">=0.4.0" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1677,6 +1825,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -1915,6 +2069,19 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -1968,6 +2135,15 @@ "node": ">= 6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -2006,6 +2182,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/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", @@ -2190,7 +2375,6 @@ "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==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2254,18 +2438,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/js-sha256": { "version": "0.10.1", "resolved": "https://registry.npmmirror.com/js-sha256/-/js-sha256-0.10.1.tgz", @@ -2298,280 +2470,6 @@ "jwt-decode": "^4.0.0" } }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "dev": true, - "license": "MPL-2.0", - "optional": true, - "peer": true, - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -2592,6 +2490,18 @@ "dev": true, "license": "MIT" }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -2801,6 +2711,42 @@ "node": ">= 6" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "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", @@ -2815,6 +2761,15 @@ "dev": true, "license": "MIT" }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2867,6 +2822,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2915,6 +2871,15 @@ "node": ">= 6" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -2934,6 +2899,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3083,6 +3049,23 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3140,6 +3123,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/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-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -3244,6 +3242,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3298,6 +3302,18 @@ "node": ">=0.10.0" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -3456,6 +3472,7 @@ "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -3579,6 +3596,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3591,7 +3609,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -3647,6 +3664,7 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -3728,6 +3746,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz", "integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.24", "@vue/compiler-sfc": "3.5.24", @@ -3798,6 +3817,30 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -3896,6 +3939,109 @@ "node": ">=8" } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/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/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/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/zrender": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", diff --git a/frontend/src/api/borrow.ts b/frontend/src/api/borrow.ts index 502ff0c..1fd55eb 100644 --- a/frontend/src/api/borrow.ts +++ b/frontend/src/api/borrow.ts @@ -68,8 +68,11 @@ export const scanBorrowBatch = async (data: { export const returnEquipment = async (data: { equipment_id: number equipment_code: string + person_id?: number + borrow_record_id?: number return_condition?: string notes?: string + confirm_children_diff?: boolean }) => { const response = await api.post>('/return', data) return response.data @@ -82,6 +85,7 @@ export const scanReturn = async (data: { notes?: string person_id?: number borrow_record_id?: number + confirm_children_diff?: boolean }) => { const response = await api.post< ApiResponse<{ @@ -91,6 +95,11 @@ export const scanReturn = async (data: { return_time: string status: string person_id?: number + need_confirm?: boolean + children_diff?: { + removed: Array> + added: Array> + } }> >('/return/scan', data) return response.data @@ -143,4 +152,3 @@ export const getBorrowRecordList = async (params: { }) return response.data } - diff --git a/frontend/src/main.ts b/frontend/src/main.ts index f458d53..b2ea4db 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -19,12 +19,6 @@ const bootstrap = async () => { const authMode = import.meta.env.VITE_AUTH_MODE || 'keycloak' if (authMode === 'local') { - localStorage.removeItem('token') - authStore.$patch({ - token: null, - user: null, - isAuthenticated: false - }) try { const resp = await api.get('/auth/user') if (resp?.data?.data) { @@ -39,9 +33,21 @@ const bootstrap = async () => { } authStore.setUser(normalizedUser) authStore.setToken('session') + } else { + authStore.$patch({ + token: null, + user: null, + isAuthenticated: false + }) + localStorage.removeItem('token') } } catch { - // ignore, user not logged in + authStore.$patch({ + token: null, + user: null, + isAuthenticated: false + }) + localStorage.removeItem('token') } app.use(router) diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index a785035..ec29185 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -92,7 +92,8 @@ router.beforeEach((to, _from, next) => { if (authMode === 'local') { next('/login') } else { - keycloak.login() + keycloak.login() + next(false) } return } @@ -106,4 +107,3 @@ router.beforeEach((to, _from, next) => { }) export default router - diff --git a/frontend/src/stores/site.ts b/frontend/src/stores/site.ts index fe6ce64..e7ad737 100644 --- a/frontend/src/stores/site.ts +++ b/frontend/src/stores/site.ts @@ -4,7 +4,7 @@ import type { SiteConfigResponse } from '@/api/settings' const DEFAULT_SITE_NAME = '固定资产管理系统' const DEFAULT_THEME_COLOR = '#409EFF' -const DEFAULT_LOGO_URL = 'https://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png' +const DEFAULT_LOGO_URL = '' const normalizeHex = (color: string) => { if (!color) return null @@ -139,4 +139,3 @@ export const useSiteStore = defineStore('site', { } } }) - diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index d57a962..0342fc0 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -154,6 +154,7 @@ export interface BorrowRecord { status: '已借出' | '已归还' | '逾期' return_condition?: '正常' | '损坏' | '丢失' notes?: string + children_snapshot?: string created_at: string updated_at: string } @@ -228,4 +229,3 @@ export interface ValueStatistics { available_value: number }> } - diff --git a/frontend/src/views/borrow/BorrowScan.vue b/frontend/src/views/borrow/BorrowScan.vue index 438a82e..5908462 100644 --- a/frontend/src/views/borrow/BorrowScan.vue +++ b/frontend/src/views/borrow/BorrowScan.vue @@ -228,6 +228,7 @@ const personKeyword = ref('') const selectedPersonId = ref(null) const persons = ref([]) let searchTimer: number | null = null +let latestPersonRequestId = 0 const canSubmit = computed(() => { const hasEquipment = Boolean(equipmentInfo.value) @@ -273,6 +274,13 @@ const handleScan = async () => { successInfo.value = null mountedChildren.value = [] mountedChildrenValue.value = 0 + selectedPersonId.value = null + personKeyword.value = '' + borrowForm.value = { + expected_return_time: '', + notes: '', + is_long_term: false + } playBeepSound() // 获取已挂载的子资产 @@ -336,11 +344,20 @@ const handleSubmit = async () => { } else if (equipmentInfo.value.remaining_quantity === 0) { equipmentInfo.value.status = '已借出' } + equipmentInfo.value = null + mountedChildren.value = [] + mountedChildrenValue.value = 0 + selectedPersonId.value = null + personKeyword.value = '' equipmentConfirmed.value = false - borrowForm.value.notes = '' - if (!borrowForm.value.is_long_term) { - borrowForm.value.expected_return_time = '' + borrowForm.value = { + expected_return_time: '', + notes: '', + is_long_term: false } + nextTick(() => { + scanInputRef.value?.focus() + }) } } catch (error: any) { alert(error.message || '出库失败') @@ -350,6 +367,7 @@ const handleSubmit = async () => { } const fetchPersons = async (keyword = '') => { + const requestId = ++latestPersonRequestId try { const response = await getPersonList({ page: 1, @@ -357,7 +375,7 @@ const fetchPersons = async (keyword = '') => { keyword: keyword || undefined, status: 'active' }) - if (response.data) { + if (response.data && requestId === latestPersonRequestId) { persons.value = response.data.list if (selectedPersonId.value) { const exists = persons.value.some((person) => person.id === selectedPersonId.value) @@ -412,4 +430,3 @@ onUnmounted(() => { } }) - diff --git a/frontend/src/views/borrow/ReturnScan.vue b/frontend/src/views/borrow/ReturnScan.vue index 4f4745f..f2f746b 100644 --- a/frontend/src/views/borrow/ReturnScan.vue +++ b/frontend/src/views/borrow/ReturnScan.vue @@ -348,13 +348,40 @@ const handleSubmit = async () => { loading.value = true try { - await scanReturn({ + let confirmChildrenDiff = false + if (hasChildrenDiff.value) { + confirmChildrenDiff = window.confirm('检测到配件与借出时不一致,确认继续入库吗?') + if (!confirmChildrenDiff) { + loading.value = false + return + } + } + + const response = await scanReturn({ equipment_code: borrowRecordInfo.value.equipment_code, return_condition: returnInfo.value.return_condition, notes: returnInfo.value.notes || undefined, person_id: record.person_id, - borrow_record_id: record.id + borrow_record_id: record.id, + confirm_children_diff: confirmChildrenDiff }) + + if (response.data?.need_confirm) { + const confirmed = window.confirm('后端检测到配件与借出时不一致,确认继续入库吗?') + if (!confirmed) { + loading.value = false + return + } + + await scanReturn({ + equipment_code: borrowRecordInfo.value.equipment_code, + return_condition: returnInfo.value.return_condition, + notes: returnInfo.value.notes || undefined, + person_id: record.person_id, + borrow_record_id: record.id, + confirm_children_diff: true + }) + } alert('入库成功!') router.push('/borrow') @@ -371,4 +398,3 @@ onMounted(() => { }) }) - diff --git a/frontend/src/views/dashboard/Dashboard.vue b/frontend/src/views/dashboard/Dashboard.vue index 38b081f..01755f5 100644 --- a/frontend/src/views/dashboard/Dashboard.vue +++ b/frontend/src/views/dashboard/Dashboard.vue @@ -212,11 +212,11 @@ const updateCategoryChart = () => { radius: ['45%', '70%'], avoidLabelOverlap: false, data: categories.length - ? categories.map((item) => ({ + ? categories.map((item, index) => ({ value: item.total_quantity, name: item.equipment_type, itemStyle: { - color: categoryColorPalette[item.equipment_type] || undefined + color: getCategoryColor(index) } })) : [{ value: 1, name: '暂无数据', itemStyle: { color: '#d1d1d6' } }], @@ -234,13 +234,18 @@ const updateCategoryChart = () => { }) } -const categoryColorPalette: Record = { - 相机: '#0071e3', - 镜头: '#34c759', - 三脚架: '#ff9500', - 闪光灯: '#af52de', - 摄影配饰品: '#ff2d55' -} +const categoryColorPalette = [ + '#0071e3', + '#34c759', + '#ff9500', + '#af52de', + '#ff2d55', + '#5ac8fa', + '#ff3b30', + '#30b0c7' +] + +const getCategoryColor = (index: number) => categoryColorPalette[index % categoryColorPalette.length] const getStatusClass = (status: string) => { const classes: Record = { @@ -279,4 +284,3 @@ onUnmounted(() => { opacity: 0; } - diff --git a/frontend/src/views/settings/components/AssetTypeManagement.vue b/frontend/src/views/settings/components/AssetTypeManagement.vue index 7a93458..e479c8a 100644 --- a/frontend/src/views/settings/components/AssetTypeManagement.vue +++ b/frontend/src/views/settings/components/AssetTypeManagement.vue @@ -32,16 +32,16 @@ v-model.trim="createForm.name" class="input-apple" type="text" - placeholder="例如:相机、镜头" + placeholder="例如:办公电脑、显示器" />
- +
@@ -57,6 +57,9 @@

+ 简称将用于自动生成资产编码,例如 `PC-0001-00001`。 +

+

父资产(如主机)可挂载子资产;子资产(如显卡、内存)可被挂载到父资产上